diff options
Diffstat (limited to 'Sources/Cepis/Client.swift')
| -rw-r--r-- | Sources/Cepis/Client.swift | 41 |
1 files changed, 37 insertions, 4 deletions
diff --git a/Sources/Cepis/Client.swift b/Sources/Cepis/Client.swift index e0d5135..f24effb 100644 --- a/Sources/Cepis/Client.swift +++ b/Sources/Cepis/Client.swift @@ -74,7 +74,7 @@ public struct Client: @unchecked Sendable { public static let maxBatch = 500 /// This library's version, as it appears in the default `User-Agent`. - public static let version = "0.1.0" + public static let version = "0.2.0" /// A shared client with the default configuration. /// @@ -176,9 +176,16 @@ public struct Client: @unchecked Sendable { /// the API's own message: the format already passed local validation, so /// the server knows something this client does not. /// + /// A 404 may carry a ``Faixa``: the municipality the CEP *would* have + /// belonged to, on + /// ``CepisError/cepNotFound(message:faixa:)``. It is context for the error + /// and nothing more — the CEP still does not exist, and the call still + /// throws. + /// /// - Throws: ``CepisError/invalidCEP(_:)`` when the CEP is not 8 digits, - /// before any request is made; ``CepisError/cepNotFound(_:)`` when it is - /// well-formed but absent from the database; + /// before any request is made; + /// ``CepisError/cepNotFound(message:faixa:)`` when it is well-formed but + /// absent from the database; /// ``CepisError/rateLimited(message:retryAfter:)``, /// ``CepisError/serverError(message:status:)`` or /// ``CepisError/networkError(message:)`` once every attempt has failed. @@ -187,7 +194,9 @@ public struct Client: @unchecked Sendable { let response = try await perform(path: "/\(number)") if response.status == 404 { - throw CepisError.cepNotFound("CEP \(number) does not exist in the DNE database") + throw CepisError.cepNotFound( + message: "CEP \(number) does not exist in the DNE database", + faixa: Client.faixa(response.body)) } if response.status == 400 { throw CepisError.invalidCEP( @@ -420,6 +429,30 @@ public struct Client: @unchecked Sendable { } } + /// Reads the optional `faixa` out of a 404 body, or gives up quietly. + /// + /// Every way this can go wrong ends in `nil`: a body that is not JSON, a + /// `faixa` that is absent, null, empty or malformed. That is deliberate. + /// The caller is already throwing ``CepisError/cepNotFound(message:faixa:)`` + /// and the faixa is only a decoration on it; letting a decode failure here + /// escalate would swap a clean not-found for an + /// ``CepisError/invalidResponse(_:)`` and lose the real answer. + private static func faixa(_ body: Data) -> Faixa? { + struct NotFound: Decodable { + let faixa: Faixa? + + enum CodingKeys: String, CodingKey { + case faixa + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.faixa = (try? container.decodeIfPresent(Faixa.self, forKey: .faixa)) ?? nil + } + } + return (try? JSONDecoder().decode(NotFound.self, from: body))?.faixa + } + private static func apiMessage(_ body: Data, fallback: String) -> String { struct APIError: Decodable { let mensagem: String? |