Skip to content

Commit

Permalink
Release/0.2.7 (#160)
Browse files Browse the repository at this point in the history
  • Loading branch information
EgzonArifi authored Aug 17, 2023
2 parents 8b743c6 + c937c4e commit 6defe06
Show file tree
Hide file tree
Showing 13 changed files with 151 additions and 15 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
<p align="center">
<img src="Resources/Images/Lingua.png" width="400" max-width="90%" alt="Lingua" />
    <a href="Resources/Docs/README.md">
<img src="Resources/Images/Lingua.png" width="90%" alt="Lingua" />
    </a>
</p>
<p align="center">
<a href="Resources/Docs/README.md">
<img src="http://img.shields.io/badge/read_the-docs-2196f3.svg" alt="Documentation">
</a>
<a href="https://www.swift.org" alt="Swift">
<img src="https://img.shields.io/badge/Swift-5.7-orange.svg" />
</a>
Expand Down Expand Up @@ -49,11 +54,8 @@ We have prepared a tamplate for sheet structure for you. What you have to do is

[Mobile Localizations Template - Google Sheets](https://docs.google.com/spreadsheets/d/1Cnqy4gZqh9pGcTF_0jb8QGOnysejZ8dVfSj8dgX4kzM)



**Sheet structure**: For detailed information about the sheet structure used in the Lingua localization tool, please refer to the [Sheet Structure Documentation](Resources/Docs/App/SHEET_STRUCTURE.md).


**Important:** Make sure to replace the existing API key in your application with the newly generated one. Also, ensure that the Google Sheet you're trying to access has its sharing settings configured to allow access to anyone with the link. You can do this by clicking on "Share" in the upper right corner of the Google Sheet and selecting "Anyone with the link."

### b. Obtain the sheet id
Expand Down
27 changes: 27 additions & 0 deletions Resources/Docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Lingua Documentation

Welcome to the main documentation repository for **Lingua**. This directory provides a comprehensive guide to our tool, covering both its usage and underlying architecture. Here's a breakdown of the two main sections:

## 1. [App - Usage & Best Practices](./App/)

This section focuses on helping users effectively utilize "Your Tool Name". Here, you'll find:

- **Usage Instructions**: Step-by-step guides on how to get started and utilize the main features of our tool.
- **Best Practices**: Tips and recommendations for maximizing the utility and efficiency of **Lingua**.
- ... and more!

[📁 Click here to explore the App documentation](./App/)

## 2. [Lingua Architecture](./Architecture/)

Dive deeper into the design and structure of **Lingua** with our architecture documentation. This section provides insights into:

- **System Design**: Understand the overall system layout and how different components interact.
- **Technical Specs**: Details about the technologies used, data flow diagrams, and more.
- ... and other technical insights!

[📁 Click here to explore the Lingua Architecture documentation](./Architecture/)

---

We encourage users to familiarize themselves with both sections to fully understand and utilize **Lingua** to its maximum potential. If you have further questions or feedback, please don't hesitate to reach out.
Binary file modified Resources/Images/Lingua.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ final class LocalizationProcessor: CommandLineProcessable {
throw error
} catch {
logger.log(error.localizedDescription, level: .error)
logger.printUsage()
throw error
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ struct GoogleSheetDataLoaderFactory {
static func make(with config: Config.Localization) -> SheetDataLoader {
let config = GoogleSheetsAPIConfig(apiKey: config.apiKey, sheetId: config.sheetId)
let requestBuilder = URLRequestBuilder(baseURLString: config.baseUrl)
let requestExecutor = APIRequestExecutor(requestBuilder: requestBuilder)
let requestExecutor = APIRequestExecutor(requestBuilder: requestBuilder,
errorHandler: GoogleSheetsErrorHandler())
let fetcher = GoogleSheetsFetcher(config: config, requestExecutor: requestExecutor)
let dataLoader = GoogleSheetDataLoader(fetcher: fetcher)
return dataLoader
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Foundation

final class GoogleSheetsErrorHandler: APIErrorHandler {
private let decoder: JSONDecoding

init(decoder: JSONDecoding = JSONDecoder()) {
self.decoder = decoder
}

func handleError<T: Decodable>(data: Data, statusCode: Int) throws -> T {
if let googleError = try? decoder.decode(GoogleSheetErrorResponse.self, from: data) {
throw GoogleSheetsError(from: googleError)
} else {
throw InvalidHTTPResponseError(statusCode: statusCode, data: data)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Foundation

struct GoogleSheetErrorResponse: Codable {
let error: ErrorResponse

struct ErrorResponse: Codable {
let code: Int
let message: String
let status: Status
}

enum Status: String, Codable {
case permissionDenied = "PERMISSION_DENIED"
case notFound = "NOT_FOUND"
case invalidArgument = "INVALID_ARGUMENT"

var description: String {
switch self {
case .permissionDenied:
return """
Ensure that the Google Sheet you're trying to access has its sharing
settings configured to allow access to anyone with the link. You can do this by
clicking on \"Share\" in the upper right corner of the Google Sheet and
selecting \"Anyone with the link.\"
"""
case .notFound:
return """
The resource you're trying to access couldn't be found.
Please ensure you're looking in the right place and the sheet id is correct.
"""
case .invalidArgument:
return """
It seems there was an error with the data provided.
Please check your inputs and try again.
"""
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation

struct GoogleSheetsError: LocalizedError {
private let response: GoogleSheetErrorResponse

init(from response: GoogleSheetErrorResponse) {
self.response = response
}

var errorDescription: String? {
"\(response.error.message)\n" + "Additional info: \(response.error.status.description)"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

protocol APIErrorHandler {
func handleError<T: Decodable>(data: Data, statusCode: Int) throws -> T
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@ final class APIRequestExecutor {
private let jsonDecoder: JSONDecoding
private let jsonEncoder: JSONEncoding
private let validStatusCodes: Set<Int>
private let errorHandler: APIErrorHandler?

init(requestBuilder: URLRequestBuilder,
httpClient: HTTPClient = URLSessionHTTPClient(),
jsonDecoder: JSONDecoding = JSONDecoder(),
jsonEncoder: JSONEncoding = JSONEncoder(),
validStatusCodes: Set<Int> = Set(200...299)) {
validStatusCodes: Set<Int> = Set(200...299),
errorHandler: APIErrorHandler? = nil) {
self.requestBuilder = requestBuilder
self.httpClient = httpClient
self.jsonDecoder = jsonDecoder
self.jsonEncoder = jsonEncoder
self.validStatusCodes = validStatusCodes
self.errorHandler = errorHandler
}
}

Expand All @@ -30,7 +33,11 @@ extension APIRequestExecutor: RequestExecutor {
let (data, httpResponse) = try await httpClient.fetchData(with: request)

guard validStatusCodes.contains(httpResponse.statusCode) else {
throw InvalidHTTPResponseError(statusCode: httpResponse.statusCode, data: data)
if let errorHandler = errorHandler {
return try errorHandler.handleError(data: data, statusCode: httpResponse.statusCode)
} else {
throw InvalidHTTPResponseError(statusCode: httpResponse.statusCode, data: data)
}
}

return try jsonDecoder.decode(T.self, from: data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ private extension LocalizedSwiftCodeGenerator {

private extension String {
func commented() -> String {
components(separatedBy: "\n")
.map { "/// \($0)" }
.joined(separator: "\n\t\t")
"/// " + replacingOccurrences(of: "\n", with: "\\n")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ final class LocalizationProcessorTests: XCTestCase {
XCTAssertEqual(actors.logger.messages, [.message(message: "Loading configuration file...", level: .info),
.message(message: "Initializing localization module...", level: .info),
.message(message: "Starting localization...", level: .info),
.message(message: DirectoryOperationError.folderCreationFailed.localizedDescription, level: .error),
.message(message: printUsage, level: .info)])
.message(message: DirectoryOperationError.folderCreationFailed.localizedDescription, level: .error)])
XCTAssertEqual(actors.mockLocalizationModule.messages, [])
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,42 @@ final class APIRequestExecutorTests: XCTestCase {
XCTAssertEqual(error, APIError.invalidRequest)
}
}

func test_send_whenGoogleSheetsErrorReceived_throwsGoogleSheetsError() async throws {
let (sut, httpClient, _) = makeSUT(errorHandler: GoogleSheetsErrorHandler())
let googleSheetsErrorResponse = GoogleSheetErrorResponse(
error: .init(code: 403, message: "Some Error Message", status: .permissionDenied)
)
let responseData = try? JSONEncoder().encode(googleSheetsErrorResponse)

httpClient.data = responseData
httpClient.response = HTTPURLResponse.anyURLResponse(statusCode: 403)
httpClient.error = nil

let testRequest = TestRequest()
do {
let _: TestDecodable = try await sut.send(testRequest)
XCTFail("Expected GoogleSheetsError to be thrown")
} catch {
let error = try XCTUnwrap(error as? GoogleSheetsError)
XCTAssertEqual(error.localizedDescription, """
Some Error Message
Additional info: Ensure that the Google Sheet you're trying to access has its sharing
settings configured to allow access to anyone with the link. You can do this by
clicking on "Share" in the upper right corner of the Google Sheet and
selecting "Anyone with the link."
""")
}
}
}

private extension APIRequestExecutorTests {
func makeSUT(baseURLString: String = "https://testapi.com") -> (sut: APIRequestExecutor, httpClient: MockHTTPClient, url: URL) {
func makeSUT(baseURLString: String = "https://testapi.com", errorHandler: APIErrorHandler? = nil) -> (sut: APIRequestExecutor, httpClient: MockHTTPClient, url: URL) {
let url: URL = .anyURL()
let httpClient = MockHTTPClient()
let sut = APIRequestExecutor(requestBuilder: .init(baseURLString: baseURLString), httpClient: httpClient)
let sut = APIRequestExecutor(requestBuilder: .init(baseURLString: baseURLString),
httpClient: httpClient,
errorHandler: errorHandler)
return (sut, httpClient, url)
}
}
Expand Down

0 comments on commit 6defe06

Please sign in to comment.