11import Foundation
22
3- /// A deserializer that decodes a `SmartResponse` body into a `Decodable` type .
3+ /// A deserializer that decodes a `SmartResponse` body into a concrete `Decodable` model .
44///
5- /// Supports decoding with optional key paths for nested JSON structures and fallback behavior
6- /// if decoding fails. Uses a provided or default `JSONDecoder`.
5+ /// `DecodableContent` supports:
6+ /// - Decoding the top-level body or a nested value via a JSON key path.
7+ /// - Supplying a custom `JSONDecoder` (via a closure) or using a default one.
8+ /// - Fallback behavior when decoding fails: return a specific error, a default value, or the thrown decoding error.
79///
8- struct DecodableContent < Response: Decodable > : Deserializable {
9- /// An optional closure that returns a `JSONDecoder` for decoding.
10+ /// The instance is value-typed and thread-safe as long as the provided `decoder` closure is pure and thread-safe.
11+ ///
12+ /// - SeeAlso: `DecodableKeyPath`, `SmartResponse`, `RequestDecodingError`
13+ public struct DecodableContent < Response: Decodable > : Deserializable {
14+ /// Optional factory for the `JSONDecoder` used during decoding.
15+ ///
16+ /// If `nil`, a fresh `JSONDecoder()` is constructed for each decode. Provide a closure
17+ /// if you need custom strategies (dates, keys, data) or performance optimizations via
18+ /// a reused decoder instance.
19+ public let decoder : JSONDecoding ?
20+ /// Describes the JSON key path of the target value and the fallback strategy when decoding fails.
1021 ///
11- /// If `nil`, a default `JSONDecoder` is used.
12- let decoder : JSONDecoding ?
13- /// Defines the key path to the target value within the response body and fallback behavior.
14- let keyPath : DecodableKeyPath < Response >
22+ /// Use an empty path to decode the top-level body. Non-empty paths navigate into nested JSON.
23+ public let keyPath : DecodableKeyPath < Response >
24+
25+ /// Creates a `DecodableContent` deserializer.
26+ /// - Parameters:
27+ /// - decoder: Optional closure that supplies a `JSONDecoder` per decode call.
28+ /// - keyPath: Key-path descriptor and fallback policy for decoding.
29+ public init ( decoder: JSONDecoding ? , keyPath: DecodableKeyPath < Response > ) {
30+ self . decoder = decoder
31+ self . keyPath = keyPath
32+ }
1533
16- /// Attempts to decode the response body into the expected type using a decoder and optional key path.
34+ /// Attempts to decode the `SmartResponse` body into `Response` using the configured decoder and key path.
35+ ///
36+ /// Error precedence is as follows:
37+ /// 1. If `data.error` is non-`nil`, return `.failure` with that error.
38+ /// 2. If `data.body` is `nil`, return `.failure(RequestDecodingError.nilResponse)`.
39+ /// 3. If `data.body` is empty, return `.failure(RequestDecodingError.emptyResponse)`.
40+ /// 4. If JSON decoding throws, apply `keyPath.fallback`:
41+ /// - `.error(e)`: return `.failure(e)`
42+ /// - `.value(v)`: return `.success(v)`
43+ /// - `.none`: return `.failure` with the thrown decoding error
1744 ///
1845 /// - Parameters:
19- /// - data: The response containing the body and any error.
20- /// - parameters: Request parameters (unused in this context).
21- /// - Returns: A result containing either the decoded value or an error.
22- func decode( with data: SmartResponse , parameters: Parameters ) -> Result < Response , Error > {
46+ /// - data: The response containing the raw body bytes and/or an error.
47+ /// - parameters: Request parameters. Unused by this type.
48+ /// - Returns: `.success` with a decoded `Response` or `.failure` describing why decoding was not possible.
49+ ///
50+ /// # Example
51+ /// Decode top-level JSON:
52+ /// ```swift
53+ /// struct User: Decodable { let id: Int; let name: String }
54+ /// let content = DecodableContent<User>(decoder: nil, keyPath: .root())
55+ /// let result = content.decode(with: response, parameters: [:])
56+ /// ```
57+ ///
58+ /// Decode a nested field using a key path and default value on failure:
59+ /// ```swift
60+ /// let kp = DecodableKeyPath<[User]>(path: ["data", "users"], fallback: .value([]))
61+ /// let content = DecodableContent<[User]>(decoder: { JSONDecoder() }, keyPath: kp)
62+ /// let result = content.decode(with: response, parameters: [:])
63+ /// ```
64+ public func decode( with data: SmartResponse , parameters: Parameters ) -> Result < Response , Error > {
2365 if let error = data. error {
2466 return . failure( error)
2567 } else if let data = data. body {
@@ -30,11 +72,11 @@ struct DecodableContent<Response: Decodable>: Deserializable {
3072 do {
3173 let decoder = decoder ? ( ) ?? . init( )
3274 let result : Response =
33- if keyPath. path. isEmpty {
34- try decoder. decode ( Response . self, from: data)
35- } else {
36- try data. decode ( Response . self, keyPath: keyPath. path, decoder: decoder)
37- }
75+ if keyPath. path. isEmpty {
76+ try decoder. decode ( Response . self, from: data)
77+ } else {
78+ try data. decode ( Response . self, keyPath: keyPath. path, decoder: decoder)
79+ }
3880 return . success( result)
3981 } catch {
4082 switch keyPath. fallback {
0 commit comments