Skip to content

Commit 4d6716f

Browse files
committed
Refactor URIDecoder/URIParser to improve handling of the deepObject style
1 parent daa2fb5 commit 4d6716f

18 files changed

+1328
-665
lines changed

Sources/OpenAPIRuntime/URICoder/Common/URICoderConfiguration.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,18 @@ struct URICoderConfiguration {
2121
enum Style {
2222

2323
/// A style for simple string variable expansion.
24+
///
25+
/// The whole string always belongs to the root key.
2426
case simple
2527

2628
/// A style for form-based URI expansion.
29+
///
30+
/// Only some key/value pairs can belong to the root key, rest are ignored.
2731
case form
32+
2833
/// A style for nested variable expansion
34+
///
35+
/// Only some key/value pairs can belong to the root key, rest are ignored.
2936
case deepObject
3037
}
3138

Sources/OpenAPIRuntime/URICoder/Common/URIEncodedNode.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,18 @@ extension URIEncodedNode {
128128
}
129129
}
130130

131+
/// Marks the node as an array, starting of empty.
132+
/// - Throws: If the node is already set to be anything else but an array.
133+
mutating func markAsArray() throws {
134+
switch self {
135+
case .array:
136+
// Already an array.
137+
break
138+
case .unset: self = .array([])
139+
default: throw InsertionError.appendingToNonArrayContainer
140+
}
141+
}
142+
131143
/// Appends a value to the array node.
132144
/// - Parameter childValue: The node to append to the underlying array.
133145
/// - Throws: If the node is already set to be anything else but an array.

Sources/OpenAPIRuntime/URICoder/Common/URIParsedNode.swift

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,44 @@
1515
import Foundation
1616

1717
/// The type used for keys by `URIParser`.
18-
typealias URIParsedKey = String.SubSequence
18+
typealias URIParsedKeyComponent = String.SubSequence
19+
20+
struct URIParsedKey: Hashable, CustomStringConvertible {
21+
22+
private(set) var components: [URIParsedKeyComponent]
23+
24+
init(_ components: [URIParsedKeyComponent]) { self.components = components }
25+
26+
static var empty: Self { .init([]) }
27+
28+
func appending(_ component: URIParsedKeyComponent) -> Self {
29+
var copy = self
30+
copy.components.append(component)
31+
return copy
32+
}
33+
34+
var description: String {
35+
if components.isEmpty { return "<empty>" }
36+
return components.joined(separator: "/")
37+
}
38+
}
1939

2040
/// The type used for values by `URIParser`.
2141
typealias URIParsedValue = String.SubSequence
2242

2343
/// The type used for an array of values by `URIParser`.
2444
typealias URIParsedValueArray = [URIParsedValue]
2545

26-
/// The type used for a node and a dictionary by `URIParser`.
27-
typealias URIParsedNode = [URIParsedKey: URIParsedValueArray]
46+
/// A key-value pair.
47+
struct URIParsedPair: Equatable {
48+
var key: URIParsedKey
49+
var value: URIParsedValue
50+
}
51+
52+
typealias URIParsedPairArray = [URIParsedPair]
53+
54+
typealias URIDecodedPrimitive = URIParsedValue
55+
56+
typealias URIDecodedDictionary = [Substring: URIParsedValueArray]
57+
58+
typealias URIDecodedArray = URIParsedValueArray

Sources/OpenAPIRuntime/URICoder/Decoding/URIDecoder.swift

Lines changed: 8 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ extension URIDecoder {
7272
/// - Returns: The decoded value.
7373
/// - Throws: An error if decoding fails, for example, due to incompatible data or key.
7474
func decode<T: Decodable>(_ type: T.Type = T.self, forKey key: String = "", from data: Substring) throws -> T {
75-
try withCachedParser(from: data) { decoder in try decoder.decode(type, forKey: key) }
75+
try withDecoder(from: data, forKey: key) { decoder in try decoder.decodeRoot(type) }
7676
}
7777

7878
/// Attempt to decode an object from an URI string, if present.
@@ -90,76 +90,22 @@ extension URIDecoder {
9090
/// - Throws: An error if decoding fails, for example, due to incompatible data or key.
9191
func decodeIfPresent<T: Decodable>(_ type: T.Type = T.self, forKey key: String = "", from data: Substring) throws
9292
-> T?
93-
{ try withCachedParser(from: data) { decoder in try decoder.decodeIfPresent(type, forKey: key) } }
93+
{ try withDecoder(from: data, forKey: key) { decoder in try decoder.decodeRootIfPresent(type) } }
9494

9595
/// Make multiple decode calls on the parsed URI.
9696
///
9797
/// Use to avoid repeatedly reparsing the raw string.
9898
/// - Parameters:
9999
/// - data: The URI-encoded string.
100+
/// - key: The root key to decode.
100101
/// - calls: The closure that contains 0 or more calls to
101-
/// the `decode` method on `URICachedDecoder`.
102+
/// the `decode` method on `URIDecoderImpl`.
102103
/// - Returns: The result of the closure invocation.
103104
/// - Throws: An error if parsing or decoding fails.
104-
func withCachedParser<R>(from data: Substring, calls: (URICachedDecoder) throws -> R) throws -> R {
105-
var parser = URIParser(configuration: configuration, data: data)
106-
let parsedNode = try parser.parseRoot()
107-
let decoder = URICachedDecoder(configuration: configuration, node: parsedNode)
105+
func withDecoder<R>(from data: Substring, forKey key: String, calls: (URIValueFromNodeDecoder) throws -> R) throws
106+
-> R
107+
{
108+
let decoder = URIValueFromNodeDecoder(data: data, rootKey: key[...], configuration: configuration)
108109
return try calls(decoder)
109110
}
110111
}
111-
112-
struct URICachedDecoder {
113-
114-
/// The configuration used by the decoder.
115-
fileprivate let configuration: URICoderConfiguration
116-
117-
/// The node from which to decode a value on demand.
118-
fileprivate let node: URIParsedNode
119-
120-
/// Attempt to decode an object from an URI-encoded string.
121-
///
122-
/// Under the hood, `URICachedDecoder` already has a pre-parsed
123-
/// `URIParsedNode` and uses `URIValueFromNodeDecoder` to decode
124-
/// the `Decodable` value.
125-
///
126-
/// - Parameters:
127-
/// - type: The type to decode.
128-
/// - key: The key of the decoded value. Only used with certain styles
129-
/// and explode options, ignored otherwise.
130-
/// - Returns: The decoded value.
131-
/// - Throws: An error if decoding fails.
132-
func decode<T: Decodable>(_ type: T.Type = T.self, forKey key: String = "") throws -> T {
133-
let decoder = URIValueFromNodeDecoder(
134-
node: node,
135-
rootKey: key[...],
136-
style: configuration.style,
137-
explode: configuration.explode,
138-
dateTranscoder: configuration.dateTranscoder
139-
)
140-
return try decoder.decodeRoot()
141-
}
142-
143-
/// Attempt to decode an object from an URI-encoded string, if present.
144-
///
145-
/// Under the hood, `URICachedDecoder` already has a pre-parsed
146-
/// `URIParsedNode` and uses `URIValueFromNodeDecoder` to decode
147-
/// the `Decodable` value.
148-
///
149-
/// - Parameters:
150-
/// - type: The type to decode.
151-
/// - key: The key of the decoded value. Only used with certain styles
152-
/// and explode options, ignored otherwise.
153-
/// - Returns: The decoded value.
154-
/// - Throws: An error if decoding fails.
155-
func decodeIfPresent<T: Decodable>(_ type: T.Type = T.self, forKey key: String = "") throws -> T? {
156-
let decoder = URIValueFromNodeDecoder(
157-
node: node,
158-
rootKey: key[...],
159-
style: configuration.style,
160-
explode: configuration.explode,
161-
dateTranscoder: configuration.dateTranscoder
162-
)
163-
return try decoder.decodeRootIfPresent()
164-
}
165-
}

Sources/OpenAPIRuntime/URICoder/Decoding/URIValueFromNodeDecoder+Keyed.swift

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ struct URIKeyedDecodingContainer<Key: CodingKey> {
1919

2020
/// The associated decoder.
2121
let decoder: URIValueFromNodeDecoder
22-
23-
/// The underlying dictionary.
24-
let values: URIParsedNode
2522
}
2623

2724
extension URIKeyedDecodingContainer {
@@ -32,7 +29,7 @@ extension URIKeyedDecodingContainer {
3229
/// - Returns: The value found for the provided key.
3330
/// - Throws: An error if no value for the key was found.
3431
private func _decodeValue(forKey key: Key) throws -> URIParsedValue {
35-
guard let value = values[key.stringValue[...]]?.first else {
32+
guard let value = try decoder.nestedElementInCurrentDictionary(forKey: key.stringValue) else {
3633
throw DecodingError.keyNotFound(key, .init(codingPath: codingPath, debugDescription: "Key not found."))
3734
}
3835
return value
@@ -97,9 +94,15 @@ extension URIKeyedDecodingContainer {
9794

9895
extension URIKeyedDecodingContainer: KeyedDecodingContainerProtocol {
9996

100-
var allKeys: [Key] { values.keys.map { key in Key.init(stringValue: String(key))! } }
97+
var allKeys: [Key] {
98+
do { return try decoder.elementKeysInCurrentDictionary().compactMap { .init(stringValue: $0) } } catch {
99+
return []
100+
}
101+
}
101102

102-
func contains(_ key: Key) -> Bool { values[key.stringValue[...]] != nil }
103+
func contains(_ key: Key) -> Bool {
104+
do { return try decoder.containsElementInCurrentDictionary(forKey: key.stringValue) } catch { return false }
105+
}
103106

104107
var codingPath: [any CodingKey] { decoder.codingPath }
105108

@@ -153,7 +156,7 @@ extension URIKeyedDecodingContainer: KeyedDecodingContainerProtocol {
153156
case is UInt64.Type: return try decode(UInt64.self, forKey: key) as! T
154157
case is Date.Type: return try decoder.dateTranscoder.decode(String(_decodeValue(forKey: key))) as! T
155158
default:
156-
try decoder.push(.init(key))
159+
decoder.push(.init(key))
157160
defer { decoder.pop() }
158161
return try type.init(from: decoder)
159162
}

Sources/OpenAPIRuntime/URICoder/Decoding/URIValueFromNodeDecoder+Single.swift

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ struct URISingleValueDecodingContainer {
2424
extension URISingleValueDecodingContainer {
2525

2626
/// The underlying value as a single value.
27-
var value: URIParsedValue { get throws { try decoder.currentElementAsSingleValue() } }
27+
var value: URIParsedValue? { get throws { try decoder.currentElementAsSingleValue() } }
2828

2929
/// Returns the value found in the underlying node converted to
3030
/// the provided type.
@@ -33,7 +33,17 @@ extension URISingleValueDecodingContainer {
3333
/// - Returns: The converted value found.
3434
/// - Throws: An error if the conversion failed.
3535
private func _decodeBinaryFloatingPoint<T: BinaryFloatingPoint>(_: T.Type = T.self) throws -> T {
36-
guard let double = try Double(value) else {
36+
guard let value = try value else {
37+
throw DecodingError.valueNotFound(
38+
T.self,
39+
DecodingError.Context.init(
40+
codingPath: codingPath,
41+
debugDescription: "Value not found.",
42+
underlyingError: nil
43+
)
44+
)
45+
}
46+
guard let double = Double(value) else {
3747
throw DecodingError.typeMismatch(
3848
T.self,
3949
.init(codingPath: codingPath, debugDescription: "Failed to convert to Double.")
@@ -49,7 +59,17 @@ extension URISingleValueDecodingContainer {
4959
/// - Returns: The converted value found.
5060
/// - Throws: An error if the conversion failed.
5161
private func _decodeFixedWidthInteger<T: FixedWidthInteger>(_: T.Type = T.self) throws -> T {
52-
guard let parsedValue = try T(value) else {
62+
guard let value = try value else {
63+
throw DecodingError.valueNotFound(
64+
T.self,
65+
DecodingError.Context.init(
66+
codingPath: codingPath,
67+
debugDescription: "Value not found.",
68+
underlyingError: nil
69+
)
70+
)
71+
}
72+
guard let parsedValue = T(value) else {
5373
throw DecodingError.typeMismatch(
5474
T.self,
5575
.init(codingPath: codingPath, debugDescription: "Failed to convert to the requested type.")
@@ -65,7 +85,17 @@ extension URISingleValueDecodingContainer {
6585
/// - Returns: The converted value found.
6686
/// - Throws: An error if the conversion failed.
6787
private func _decodeLosslessStringConvertible<T: LosslessStringConvertible>(_: T.Type = T.self) throws -> T {
68-
guard let parsedValue = try T(String(value)) else {
88+
guard let value = try value else {
89+
throw DecodingError.valueNotFound(
90+
T.self,
91+
DecodingError.Context.init(
92+
codingPath: codingPath,
93+
debugDescription: "Value not found.",
94+
underlyingError: nil
95+
)
96+
)
97+
}
98+
guard let parsedValue = T(String(value)) else {
6999
throw DecodingError.typeMismatch(
70100
T.self,
71101
.init(codingPath: codingPath, debugDescription: "Failed to convert to the requested type.")
@@ -79,11 +109,23 @@ extension URISingleValueDecodingContainer: SingleValueDecodingContainer {
79109

80110
var codingPath: [any CodingKey] { decoder.codingPath }
81111

82-
func decodeNil() -> Bool { false }
112+
func decodeNil() -> Bool { do { return try value == nil } catch { return false } }
83113

84114
func decode(_ type: Bool.Type) throws -> Bool { try _decodeLosslessStringConvertible() }
85115

86-
func decode(_ type: String.Type) throws -> String { try String(value) }
116+
func decode(_ type: String.Type) throws -> String {
117+
guard let value = try value else {
118+
throw DecodingError.valueNotFound(
119+
String.self,
120+
DecodingError.Context.init(
121+
codingPath: codingPath,
122+
debugDescription: "Value not found.",
123+
underlyingError: nil
124+
)
125+
)
126+
}
127+
return String(value)
128+
}
87129

88130
func decode(_ type: Double.Type) throws -> Double { try _decodeBinaryFloatingPoint() }
89131

@@ -125,7 +167,18 @@ extension URISingleValueDecodingContainer: SingleValueDecodingContainer {
125167
case is UInt16.Type: return try decode(UInt16.self) as! T
126168
case is UInt32.Type: return try decode(UInt32.self) as! T
127169
case is UInt64.Type: return try decode(UInt64.self) as! T
128-
case is Date.Type: return try decoder.dateTranscoder.decode(String(value)) as! T
170+
case is Date.Type:
171+
guard let value = try value else {
172+
throw DecodingError.valueNotFound(
173+
T.self,
174+
DecodingError.Context.init(
175+
codingPath: codingPath,
176+
debugDescription: "Value not found.",
177+
underlyingError: nil
178+
)
179+
)
180+
}
181+
return try decoder.dateTranscoder.decode(String(value)) as! T
129182
default: return try T.init(from: decoder)
130183
}
131184
}

0 commit comments

Comments
 (0)