From 3e3b4e635d4a9ecef859bb428262dcd968acc30b Mon Sep 17 00:00:00 2001 From: Kostis Stefanou Date: Tue, 9 Apr 2024 18:33:17 +0300 Subject: [PATCH 1/6] [Runtime] Add support of deepObject style in query params ### Motivation The runtime changes for: https://github.com/apple/swift-openapi-generator/issues/259 ### Modifications Added `deepObject` style to serializer & parser in order to support nested keys on query parameters. ### Result Support nested keys on query parameters. ### Test Plan These are just the runtime changes, tested together with generated changes. --- .../Conversion/CurrencyExtensions.swift | 4 +- .../Conversion/ParameterStyles.swift | 6 ++ .../Common/URICoderConfiguration.swift | 3 + .../URICoder/Parsing/URIParser.swift | 45 +++++++++++++ .../Serialization/URISerializer.swift | 26 +++++++- .../URICoder/Encoding/Test_URIEncoder.swift | 8 +++ .../URICoder/Parsing/Test_URIParser.swift | 22 +++++-- .../Serialization/Test_URISerializer.swift | 29 ++++++--- .../URICoder/Test_URICodingRoundtrip.swift | 65 +++++++++++++------ .../URICoder/URICoderTestUtils.swift | 7 ++ 10 files changed, 178 insertions(+), 37 deletions(-) diff --git a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift index 38a17115..9c9fdef3 100644 --- a/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift +++ b/Sources/OpenAPIRuntime/Conversion/CurrencyExtensions.swift @@ -29,7 +29,9 @@ extension ParameterStyle { ) { let resolvedStyle = style ?? .defaultForQueryItems let resolvedExplode = explode ?? ParameterStyle.defaultExplodeFor(forStyle: resolvedStyle) - guard resolvedStyle == .form else { + switch resolvedStyle { + case .form, .deepObject: break + default: throw RuntimeError.unsupportedParameterStyle( name: name, location: .query, diff --git a/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift b/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift index fb95bce7..a9d11a9e 100644 --- a/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift +++ b/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift @@ -26,6 +26,11 @@ /// /// Details: https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.2 case simple + + /// The deepObject style. + /// + /// Details: https://spec.openapis.org/oas/v3.1.0.html#style-values + case deepObject } extension ParameterStyle { @@ -53,6 +58,7 @@ extension URICoderConfiguration.Style { switch style { case .form: self = .form case .simple: self = .simple + case .deepObject: self = .deepObject } } } diff --git a/Sources/OpenAPIRuntime/URICoder/Common/URICoderConfiguration.swift b/Sources/OpenAPIRuntime/URICoder/Common/URICoderConfiguration.swift index bfb42c48..64bf45d4 100644 --- a/Sources/OpenAPIRuntime/URICoder/Common/URICoderConfiguration.swift +++ b/Sources/OpenAPIRuntime/URICoder/Common/URICoderConfiguration.swift @@ -25,6 +25,9 @@ struct URICoderConfiguration { /// A style for form-based URI expansion. case form + + /// A style for nested variable expansion + case deepObject } /// A character used to escape the space character. diff --git a/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift b/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift index 3be75420..2b5cad22 100644 --- a/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift +++ b/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift @@ -43,6 +43,9 @@ private enum ParsingError: Swift.Error { /// A malformed key-value pair was detected. case malformedKeyValuePair(Raw) + + /// An invalid configuration was detected. + case invalidConfiguration(String) } // MARK: - Parser implementations @@ -61,6 +64,7 @@ extension URIParser { switch configuration.style { case .form: return [:] case .simple: return ["": [""]] + case .deepObject: return [:] } } switch (configuration.style, configuration.explode) { @@ -68,6 +72,10 @@ extension URIParser { case (.form, false): return try parseUnexplodedFormRoot() case (.simple, true): return try parseExplodedSimpleRoot() case (.simple, false): return try parseUnexplodedSimpleRoot() + case (.deepObject, true): return try parseExplodedDeepObjectRoot() + case (.deepObject, false): + let reason = "Deep object style is only valid with explode set to true" + throw ParsingError.invalidConfiguration(reason) } } @@ -205,6 +213,43 @@ extension URIParser { } } } + + /// Parses the root node assuming the raw string uses the deepObject style + /// and the explode parameter is enabled. + /// - Returns: The parsed root node. + /// - Throws: An error if parsing fails. + private mutating func parseExplodedDeepObjectRoot() throws -> URIParsedNode { + try parseGenericRoot { data, appendPair in + let keyValueSeparator: Character = "=" + let pairSeparator: Character = "&" + let nestedKeyStartingCharacter: Character = "[" + let nestedKeyEndingCharacter: Character = "]" + + func nestedKey(from deepObjectKey: String.SubSequence) -> Raw { + var unescapedDeepObjectKey = Substring(deepObjectKey.removingPercentEncoding ?? "") + let topLevelKey = unescapedDeepObjectKey.parseUpToCharacterOrEnd(nestedKeyStartingCharacter) + let nestedKey = unescapedDeepObjectKey.parseUpToCharacterOrEnd(nestedKeyEndingCharacter) + return nestedKey.isEmpty ? topLevelKey : nestedKey + } + + while !data.isEmpty { + let (firstResult, firstValue) = data.parseUpToEitherCharacterOrEnd( + first: keyValueSeparator, + second: pairSeparator + ) + + guard case .foundFirst = firstResult else { + throw ParsingError.malformedKeyValuePair(firstValue) + } + // Hit the key/value separator, so a value will follow. + let secondValue = data.parseUpToCharacterOrEnd(pairSeparator) + let key = nestedKey(from: firstValue) + let value = secondValue + + appendPair(key, [value]) + } + } + } } // MARK: - URIParser utilities diff --git a/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift b/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift index 26071f85..82641c90 100644 --- a/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift +++ b/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift @@ -69,6 +69,12 @@ extension URISerializer { /// Nested containers are not supported. case nestedContainersNotSupported + + /// Deep object arrays are not supported. + case deepObjectsArrayNotSupported + + /// An invalid configuration was detected. + case invalidConfiguration(String) } /// Computes an escaped version of the provided string. @@ -117,6 +123,7 @@ extension URISerializer { switch configuration.style { case .form: keyAndValueSeparator = "=" case .simple: keyAndValueSeparator = nil + case .deepObject: keyAndValueSeparator = "=" } try serializePrimitiveKeyValuePair(primitive, forKey: key, separator: keyAndValueSeparator) case .array(let array): try serializeArray(array.map(unwrapPrimitiveValue), forKey: key) @@ -180,6 +187,8 @@ extension URISerializer { case (.simple, _): keyAndValueSeparator = nil pairSeparator = "," + case (.deepObject, _): + throw SerializationError.deepObjectsArrayNotSupported } func serializeNext(_ element: URIEncodedNode.Primitive) throws { if let keyAndValueSeparator { @@ -228,8 +237,18 @@ extension URISerializer { case (.simple, false): keyAndValueSeparator = "," pairSeparator = "," + case (.deepObject, true): + keyAndValueSeparator = "=" + pairSeparator = "&" + case (.deepObject, false): + let reason = "Deep object style is only valid with explode set to true" + throw SerializationError.invalidConfiguration(reason) } + func serializeNestedKey(_ elementKey: String, forKey rootKey: String) -> String { + guard case .deepObject = configuration.style else { return elementKey } + return rootKey + "[" + elementKey + "]" + } func serializeNext(_ element: URIEncodedNode.Primitive, forKey elementKey: String) throws { try serializePrimitiveKeyValuePair(element, forKey: elementKey, separator: keyAndValueSeparator) } @@ -238,10 +257,13 @@ extension URISerializer { data.append(containerKeyAndValue) } for (elementKey, element) in sortedDictionary.dropLast() { - try serializeNext(element, forKey: elementKey) + try serializeNext(element, forKey: serializeNestedKey(elementKey, forKey: key)) data.append(pairSeparator) } - if let (elementKey, element) = sortedDictionary.last { try serializeNext(element, forKey: elementKey) } + + if let (elementKey, element) = sortedDictionary.last { + try serializeNext(element, forKey: serializeNestedKey(elementKey, forKey: key)) + } } } diff --git a/Tests/OpenAPIRuntimeTests/URICoder/Encoding/Test_URIEncoder.swift b/Tests/OpenAPIRuntimeTests/URICoder/Encoding/Test_URIEncoder.swift index fe9d445e..02b63ea3 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/Encoding/Test_URIEncoder.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/Encoding/Test_URIEncoder.swift @@ -23,4 +23,12 @@ final class Test_URIEncoder: Test_Runtime { let encodedString = try encoder.encode(Foo(bar: "hello world"), forKey: "root") XCTAssertEqual(encodedString, "bar=hello+world") } + + func testNestedEncoding() throws { + struct Foo: Encodable { var bar: String } + let serializer = URISerializer(configuration: .deepObjectExplode) + let encoder = URIEncoder(serializer: serializer) + let encodedString = try encoder.encode(Foo(bar: "hello world"), forKey: "root") + XCTAssertEqual(encodedString, "root%5Bbar%5D=hello%20world") + } } diff --git a/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift b/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift index 9bd8f3e8..94c48492 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift @@ -17,7 +17,7 @@ import XCTest final class Test_URIParser: Test_Runtime { let testedVariants: [URICoderConfiguration] = [ - .formExplode, .formUnexplode, .simpleExplode, .simpleUnexplode, .formDataExplode, .formDataUnexplode, + .formExplode, .formUnexplode, .simpleExplode, .simpleUnexplode, .formDataExplode, .formDataUnexplode, .deepObjectExplode ] func testParsing() throws { @@ -29,7 +29,8 @@ final class Test_URIParser: Test_Runtime { simpleExplode: .custom("", value: ["": [""]]), simpleUnexplode: .custom("", value: ["": [""]]), formDataExplode: "empty=", - formDataUnexplode: "empty=" + formDataUnexplode: "empty=", + deepObjectExplode: "empty=" ), value: ["empty": [""]] ), @@ -40,7 +41,8 @@ final class Test_URIParser: Test_Runtime { simpleExplode: .custom("", value: ["": [""]]), simpleUnexplode: .custom("", value: ["": [""]]), formDataExplode: "", - formDataUnexplode: "" + formDataUnexplode: "", + deepObjectExplode: "" ), value: [:] ), @@ -51,7 +53,8 @@ final class Test_URIParser: Test_Runtime { simpleExplode: .custom("fred", value: ["": ["fred"]]), simpleUnexplode: .custom("fred", value: ["": ["fred"]]), formDataExplode: "who=fred", - formDataUnexplode: "who=fred" + formDataUnexplode: "who=fred", + deepObjectExplode: "who=fred" ), value: ["who": ["fred"]] ), @@ -62,7 +65,8 @@ final class Test_URIParser: Test_Runtime { simpleExplode: .custom("Hello%20World", value: ["": ["Hello World"]]), simpleUnexplode: .custom("Hello%20World", value: ["": ["Hello World"]]), formDataExplode: "hello=Hello+World", - formDataUnexplode: "hello=Hello+World" + formDataUnexplode: "hello=Hello+World", + deepObjectExplode: "hello=Hello%20World" ), value: ["hello": ["Hello World"]] ), @@ -73,7 +77,8 @@ final class Test_URIParser: Test_Runtime { simpleExplode: .custom("red,green,blue", value: ["": ["red", "green", "blue"]]), simpleUnexplode: .custom("red,green,blue", value: ["": ["red", "green", "blue"]]), formDataExplode: "list=red&list=green&list=blue", - formDataUnexplode: "list=red,green,blue" + formDataUnexplode: "list=red,green,blue", + deepObjectExplode: "list=red&list=green&list=blue" ), value: ["list": ["red", "green", "blue"]] ), @@ -93,7 +98,8 @@ final class Test_URIParser: Test_Runtime { formDataUnexplode: .custom( "keys=comma,%2C,dot,.,semi,%3B", value: ["keys": ["comma", ",", "dot", ".", "semi", ";"]] - ) + ), + deepObjectExplode: "comma=%2C&dot=.&semi=%3B" ), value: ["semi": [";"], "dot": ["."], "comma": [","]] ), @@ -133,6 +139,7 @@ extension Test_URIParser { static let simpleUnexplode: Self = .init(name: "simpleUnexplode", config: .simpleUnexplode) static let formDataExplode: Self = .init(name: "formDataExplode", config: .formDataExplode) static let formDataUnexplode: Self = .init(name: "formDataUnexplode", config: .formDataUnexplode) + static let deepObjectExplode: Self = .init(name: "deepObjectExplode", config: .deepObjectExplode) } struct Variants { @@ -161,6 +168,7 @@ extension Test_URIParser { var simpleUnexplode: Input var formDataExplode: Input var formDataUnexplode: Input + var deepObjectExplode: Input } var variants: Variants var value: URIParsedNode diff --git a/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift b/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift index f93fabed..e27e26b2 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift @@ -31,7 +31,8 @@ final class Test_URISerializer: Test_Runtime { simpleExplode: "", simpleUnexplode: "", formDataExplode: "empty=", - formDataUnexplode: "empty=" + formDataUnexplode: "empty=", + deepObjectExplode: "empty=" ) ), makeCase( @@ -43,7 +44,8 @@ final class Test_URISerializer: Test_Runtime { simpleExplode: "fred", simpleUnexplode: "fred", formDataExplode: "who=fred", - formDataUnexplode: "who=fred" + formDataUnexplode: "who=fred", + deepObjectExplode: "who=fred" ) ), makeCase( @@ -55,7 +57,8 @@ final class Test_URISerializer: Test_Runtime { simpleExplode: "1234", simpleUnexplode: "1234", formDataExplode: "x=1234", - formDataUnexplode: "x=1234" + formDataUnexplode: "x=1234", + deepObjectExplode: "x=1234" ) ), makeCase( @@ -67,7 +70,8 @@ final class Test_URISerializer: Test_Runtime { simpleExplode: "12.34", simpleUnexplode: "12.34", formDataExplode: "x=12.34", - formDataUnexplode: "x=12.34" + formDataUnexplode: "x=12.34", + deepObjectExplode: "x=12.34" ) ), makeCase( @@ -79,7 +83,8 @@ final class Test_URISerializer: Test_Runtime { simpleExplode: "true", simpleUnexplode: "true", formDataExplode: "enabled=true", - formDataUnexplode: "enabled=true" + formDataUnexplode: "enabled=true", + deepObjectExplode: "enabled=true" ) ), makeCase( @@ -91,7 +96,8 @@ final class Test_URISerializer: Test_Runtime { simpleExplode: "Hello%20World", simpleUnexplode: "Hello%20World", formDataExplode: "hello=Hello+World", - formDataUnexplode: "hello=Hello+World" + formDataUnexplode: "hello=Hello+World", + deepObjectExplode: "hello=Hello%20World" ) ), makeCase( @@ -103,7 +109,8 @@ final class Test_URISerializer: Test_Runtime { simpleExplode: "red,green,blue", simpleUnexplode: "red,green,blue", formDataExplode: "list=red&list=green&list=blue", - formDataUnexplode: "list=red,green,blue" + formDataUnexplode: "list=red,green,blue", + deepObjectExplode: nil ) ), makeCase( @@ -118,7 +125,8 @@ final class Test_URISerializer: Test_Runtime { simpleExplode: "comma=%2C,dot=.,semi=%3B", simpleUnexplode: "comma,%2C,dot,.,semi,%3B", formDataExplode: "comma=%2C&dot=.&semi=%3B", - formDataUnexplode: "keys=comma,%2C,dot,.,semi,%3B" + formDataUnexplode: "keys=comma,%2C,dot,.,semi,%3B", + deepObjectExplode: "keys%5Bcomma%5D=%2C&keys%5Bdot%5D=.&keys%5Bsemi%5D=%3B" ) ), ] @@ -140,6 +148,9 @@ final class Test_URISerializer: Test_Runtime { try testVariant(.simpleUnexplode, testCase.variants.simpleUnexplode) try testVariant(.formDataExplode, testCase.variants.formDataExplode) try testVariant(.formDataUnexplode, testCase.variants.formDataUnexplode) + if let deepObjectExplode = testCase.variants.deepObjectExplode { + try testVariant(.deepObjectExplode, deepObjectExplode) + } } } } @@ -156,6 +167,7 @@ extension Test_URISerializer { static let simpleUnexplode: Self = .init(name: "simpleUnexplode", config: .simpleUnexplode) static let formDataExplode: Self = .init(name: "formDataExplode", config: .formDataExplode) static let formDataUnexplode: Self = .init(name: "formDataUnexplode", config: .formDataUnexplode) + static let deepObjectExplode: Self = .init(name: "deepObjectExplode", config: .deepObjectExplode) } struct Variants { var formExplode: String @@ -164,6 +176,7 @@ extension Test_URISerializer { var simpleUnexplode: String var formDataExplode: String var formDataUnexplode: String + var deepObjectExplode: String? } var value: URIEncodedNode var key: String diff --git a/Tests/OpenAPIRuntimeTests/URICoder/Test_URICodingRoundtrip.swift b/Tests/OpenAPIRuntimeTests/URICoder/Test_URICodingRoundtrip.swift index ccfe52c4..200cba1a 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/Test_URICodingRoundtrip.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/Test_URICodingRoundtrip.swift @@ -96,7 +96,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "", simpleUnexplode: "", formDataExplode: "root=", - formDataUnexplode: "root=" + formDataUnexplode: "root=", + deepObjectExplode: "root=" ) ) @@ -110,7 +111,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "Hello%20World%21", simpleUnexplode: "Hello%20World%21", formDataExplode: "root=Hello+World%21", - formDataUnexplode: "root=Hello+World%21" + formDataUnexplode: "root=Hello+World%21", + deepObjectExplode: "root=Hello%20World%21" ) ) @@ -124,7 +126,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "red", simpleUnexplode: "red", formDataExplode: "root=red", - formDataUnexplode: "root=red" + formDataUnexplode: "root=red", + deepObjectExplode: "root=red" ) ) @@ -138,7 +141,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "1234", simpleUnexplode: "1234", formDataExplode: "root=1234", - formDataUnexplode: "root=1234" + formDataUnexplode: "root=1234", + deepObjectExplode: "root=1234" ) ) @@ -152,7 +156,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "12.34", simpleUnexplode: "12.34", formDataExplode: "root=12.34", - formDataUnexplode: "root=12.34" + formDataUnexplode: "root=12.34", + deepObjectExplode: "root=12.34" ) ) @@ -166,7 +171,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "true", simpleUnexplode: "true", formDataExplode: "root=true", - formDataUnexplode: "root=true" + formDataUnexplode: "root=true", + deepObjectExplode: "root=true" ) ) @@ -180,7 +186,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "2023-08-25T07%3A34%3A59Z", simpleUnexplode: "2023-08-25T07%3A34%3A59Z", formDataExplode: "root=2023-08-25T07%3A34%3A59Z", - formDataUnexplode: "root=2023-08-25T07%3A34%3A59Z" + formDataUnexplode: "root=2023-08-25T07%3A34%3A59Z", + deepObjectExplode: "root=2023-08-25T07%3A34%3A59Z" ) ) @@ -194,7 +201,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "a,b,c", simpleUnexplode: "a,b,c", formDataExplode: "list=a&list=b&list=c", - formDataUnexplode: "list=a,b,c" + formDataUnexplode: "list=a,b,c", + deepObjectExplode: nil ) ) @@ -208,7 +216,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "2023-08-25T07%3A34%3A59Z,2023-08-25T07%3A35%3A01Z", simpleUnexplode: "2023-08-25T07%3A34%3A59Z,2023-08-25T07%3A35%3A01Z", formDataExplode: "list=2023-08-25T07%3A34%3A59Z&list=2023-08-25T07%3A35%3A01Z", - formDataUnexplode: "list=2023-08-25T07%3A34%3A59Z,2023-08-25T07%3A35%3A01Z" + formDataUnexplode: "list=2023-08-25T07%3A34%3A59Z,2023-08-25T07%3A35%3A01Z", + deepObjectExplode: nil ) ) @@ -222,7 +231,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: .custom("", value: [""]), simpleUnexplode: .custom("", value: [""]), formDataExplode: "", - formDataUnexplode: "" + formDataUnexplode: "", + deepObjectExplode: nil ) ) @@ -236,7 +246,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "red,green,blue", simpleUnexplode: "red,green,blue", formDataExplode: "list=red&list=green&list=blue", - formDataUnexplode: "list=red,green,blue" + formDataUnexplode: "list=red,green,blue", + deepObjectExplode: nil ) ) @@ -250,7 +261,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "bar=24,color=red,date=2023-08-25T07%3A34%3A59Z,empty=,foo=hi%21", simpleUnexplode: "bar,24,color,red,date,2023-08-25T07%3A34%3A59Z,empty,,foo,hi%21", formDataExplode: "bar=24&color=red&date=2023-08-25T07%3A34%3A59Z&empty=&foo=hi%21", - formDataUnexplode: "keys=bar,24,color,red,date,2023-08-25T07%3A34%3A59Z,empty,,foo,hi%21" + formDataUnexplode: "keys=bar,24,color,red,date,2023-08-25T07%3A34%3A59Z,empty,,foo,hi%21", + deepObjectExplode: "keys%5Bbar%5D=24&keys%5Bcolor%5D=red&keys%5Bdate%5D=2023-08-25T07%3A34%3A59Z&keys%5Bempty%5D=&keys%5Bfoo%5D=hi%21" ) ) @@ -265,7 +277,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "2023-01-18T10%3A04%3A11Z", simpleUnexplode: "2023-01-18T10%3A04%3A11Z", formDataExplode: "root=2023-01-18T10%3A04%3A11Z", - formDataUnexplode: "root=2023-01-18T10%3A04%3A11Z" + formDataUnexplode: "root=2023-01-18T10%3A04%3A11Z", + deepObjectExplode: "root=2023-01-18T10%3A04%3A11Z" ) ) try _test( @@ -277,7 +290,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "green", simpleUnexplode: "green", formDataExplode: "root=green", - formDataUnexplode: "root=green" + formDataUnexplode: "root=green", + deepObjectExplode: "root=green" ) ) try _test( @@ -289,7 +303,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "foo=bar", simpleUnexplode: "foo,bar", formDataExplode: "foo=bar", - formDataUnexplode: "root=foo,bar" + formDataUnexplode: "root=foo,bar", + deepObjectExplode: "root%5Bfoo%5D=bar" ) ) @@ -304,7 +319,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "", simpleUnexplode: "", formDataExplode: "", - formDataUnexplode: "" + formDataUnexplode: "", + deepObjectExplode: "" ) ) @@ -318,7 +334,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: "bar=24,color=red,empty=,foo=hi%21", simpleUnexplode: "bar,24,color,red,empty,,foo,hi%21", formDataExplode: "bar=24&color=red&empty=&foo=hi%21", - formDataUnexplode: "keys=bar,24,color,red,empty,,foo,hi%21" + formDataUnexplode: "keys=bar,24,color,red,empty,,foo,hi%21", + deepObjectExplode: "keys%5Bbar%5D=24&keys%5Bcolor%5D=red&keys%5Bempty%5D=&keys%5Bfoo%5D=hi%21" ) ) @@ -332,7 +349,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleExplode: .custom("", value: ["": ""]), simpleUnexplode: .custom("", value: ["": ""]), formDataExplode: "", - formDataUnexplode: "" + formDataUnexplode: "", + deepObjectExplode: "" ) ) } @@ -347,6 +365,7 @@ final class Test_URICodingRoundtrip: Test_Runtime { static let simpleUnexplode: Self = .init(name: "simpleUnexplode", configuration: .simpleUnexplode) static let formDataExplode: Self = .init(name: "formDataExplode", configuration: .formDataExplode) static let formDataUnexplode: Self = .init(name: "formDataUnexplode", configuration: .formDataUnexplode) + static let deepObjectExplode: Self = .init(name: "deepObjectExplode", configuration: .deepObjectExplode) } struct Variants { @@ -370,6 +389,7 @@ final class Test_URICodingRoundtrip: Test_Runtime { var simpleUnexplode: Input var formDataExplode: Input var formDataUnexplode: Input + var deepObjectExplode: Input? } func _test( @@ -397,6 +417,13 @@ final class Test_URICodingRoundtrip: Test_Runtime { configuration: .formDataUnexplode, variant: variants.formDataUnexplode ) + + if let deepObjectExplode = variants.deepObjectExplode { + try testVariant( + name: "deepObjectExplode", + configuration: .deepObjectExplode, + variant: deepObjectExplode + ) + } } - } diff --git a/Tests/OpenAPIRuntimeTests/URICoder/URICoderTestUtils.swift b/Tests/OpenAPIRuntimeTests/URICoder/URICoderTestUtils.swift index 375c266a..38b9e860 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/URICoderTestUtils.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/URICoderTestUtils.swift @@ -59,4 +59,11 @@ extension URICoderConfiguration { spaceEscapingCharacter: .plus, dateTranscoder: defaultDateTranscoder ) + + static let deepObjectExplode: Self = .init( + style: .deepObject, + explode: true, + spaceEscapingCharacter: .percentEncoded, + dateTranscoder: defaultDateTranscoder + ) } From 74af5375f91ba25708675ce58e30f2d07f8c0dcd Mon Sep 17 00:00:00 2001 From: Kostis Stefanou Date: Wed, 10 Apr 2024 13:23:02 +0300 Subject: [PATCH 2/6] Update URISerializer & Parser Tests --- .../Serialization/URISerializer.swift | 2 +- .../URICoder/Parsing/Test_URIParser.swift | 11 +-- .../Serialization/Test_URISerializer.swift | 73 ++++++++++++++----- 3 files changed, 60 insertions(+), 26 deletions(-) diff --git a/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift b/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift index 82641c90..46eff197 100644 --- a/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift +++ b/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift @@ -65,7 +65,7 @@ extension CharacterSet { extension URISerializer { /// A serializer error. - private enum SerializationError: Swift.Error { + enum SerializationError: Swift.Error, Equatable { /// Nested containers are not supported. case nestedContainersNotSupported diff --git a/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift b/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift index 94c48492..9cfc0816 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift @@ -30,7 +30,7 @@ final class Test_URIParser: Test_Runtime { simpleUnexplode: .custom("", value: ["": [""]]), formDataExplode: "empty=", formDataUnexplode: "empty=", - deepObjectExplode: "empty=" + deepObjectExplode: "object%5Bempty%5D=" ), value: ["empty": [""]] ), @@ -54,7 +54,7 @@ final class Test_URIParser: Test_Runtime { simpleUnexplode: .custom("fred", value: ["": ["fred"]]), formDataExplode: "who=fred", formDataUnexplode: "who=fred", - deepObjectExplode: "who=fred" + deepObjectExplode: "object%5Bwho%5D=fred" ), value: ["who": ["fred"]] ), @@ -66,7 +66,7 @@ final class Test_URIParser: Test_Runtime { simpleUnexplode: .custom("Hello%20World", value: ["": ["Hello World"]]), formDataExplode: "hello=Hello+World", formDataUnexplode: "hello=Hello+World", - deepObjectExplode: "hello=Hello%20World" + deepObjectExplode: "object%5Bhello%5D=Hello%20World" ), value: ["hello": ["Hello World"]] ), @@ -78,7 +78,7 @@ final class Test_URIParser: Test_Runtime { simpleUnexplode: .custom("red,green,blue", value: ["": ["red", "green", "blue"]]), formDataExplode: "list=red&list=green&list=blue", formDataUnexplode: "list=red,green,blue", - deepObjectExplode: "list=red&list=green&list=blue" + deepObjectExplode: .custom("object%5Blist%5D=red&object%5Blist%5D=green&object%5Blist%5D=blue", value: [:]) ), value: ["list": ["red", "green", "blue"]] ), @@ -99,7 +99,7 @@ final class Test_URIParser: Test_Runtime { "keys=comma,%2C,dot,.,semi,%3B", value: ["keys": ["comma", ",", "dot", ".", "semi", ";"]] ), - deepObjectExplode: "comma=%2C&dot=.&semi=%3B" + deepObjectExplode: "keys%5Bcomma%5D=%2C&keys%5Bdot%5D=.&keys%5Bsemi%5D=%3B" ), value: ["semi": [";"], "dot": ["."], "comma": [","]] ), @@ -123,6 +123,7 @@ final class Test_URIParser: Test_Runtime { try testVariant(.simpleUnexplode, variants.simpleUnexplode) try testVariant(.formDataExplode, variants.formDataExplode) try testVariant(.formDataUnexplode, variants.formDataUnexplode) + try testVariant(.deepObjectExplode, variants.deepObjectExplode) } } } diff --git a/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift b/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift index e27e26b2..63b38316 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift @@ -110,7 +110,10 @@ final class Test_URISerializer: Test_Runtime { simpleUnexplode: "red,green,blue", formDataExplode: "list=red&list=green&list=blue", formDataUnexplode: "list=red,green,blue", - deepObjectExplode: nil + deepObjectExplode: .custom( + "list=red&list=green&list=blue", + expectedError: .deepObjectsArrayNotSupported + ) ) ), makeCase( @@ -131,16 +134,28 @@ final class Test_URISerializer: Test_Runtime { ), ] for testCase in cases { - func testVariant(_ variant: Case.Variant, _ expectedString: String) throws { + func testVariant(_ variant: Case.Variant, _ input: Case.Variants.Input) throws { var serializer = URISerializer(configuration: variant.config) - let encodedString = try serializer.serializeNode(testCase.value, forKey: testCase.key) - XCTAssertEqual( - encodedString, - expectedString, - "Failed for config: \(variant.name)", - file: testCase.file, - line: testCase.line - ) + do { + let encodedString = try serializer.serializeNode(testCase.value, forKey: testCase.key) + XCTAssertEqual( + encodedString, + input.string, + "Failed for config: \(variant.name)", + file: testCase.file, + line: testCase.line + ) + } catch { + guard let expectedError = input.expectedError, + let serializationError = error as? URISerializer.SerializationError else { throw error } + XCTAssertEqual( + expectedError, + serializationError, + "Failed for config: \(variant.name)", + file: testCase.file, + line: testCase.line + ) + } } try testVariant(.formExplode, testCase.variants.formExplode) try testVariant(.formUnexplode, testCase.variants.formUnexplode) @@ -148,9 +163,7 @@ final class Test_URISerializer: Test_Runtime { try testVariant(.simpleUnexplode, testCase.variants.simpleUnexplode) try testVariant(.formDataExplode, testCase.variants.formDataExplode) try testVariant(.formDataUnexplode, testCase.variants.formDataUnexplode) - if let deepObjectExplode = testCase.variants.deepObjectExplode { - try testVariant(.deepObjectExplode, deepObjectExplode) - } + try testVariant(.deepObjectExplode, testCase.variants.deepObjectExplode) } } } @@ -170,13 +183,33 @@ extension Test_URISerializer { static let deepObjectExplode: Self = .init(name: "deepObjectExplode", config: .deepObjectExplode) } struct Variants { - var formExplode: String - var formUnexplode: String - var simpleExplode: String - var simpleUnexplode: String - var formDataExplode: String - var formDataUnexplode: String - var deepObjectExplode: String? + + struct Input: ExpressibleByStringLiteral { + var string: String + var expectedError: URISerializer.SerializationError? + + init(string: String, expectedError: URISerializer.SerializationError? = nil) { + self.string = string + self.expectedError = expectedError + } + + static func custom(_ string: String, expectedError: URISerializer.SerializationError) -> Self { + .init(string: string, expectedError: expectedError) + } + + init(stringLiteral value: String) { + self.string = value + self.expectedError = nil + } + } + + var formExplode: Input + var formUnexplode: Input + var simpleExplode: Input + var simpleUnexplode: Input + var formDataExplode: Input + var formDataUnexplode: Input + var deepObjectExplode: Input } var value: URIEncodedNode var key: String From 9b5e8b52a7ceaff6d131abc364cb08699a8dd8ed Mon Sep 17 00:00:00 2001 From: Kostis Stefanou Date: Wed, 10 Apr 2024 14:00:32 +0300 Subject: [PATCH 3/6] Add support of error testing in URIParser --- .../URICoder/Parsing/URIParser.swift | 11 +++-- .../URICoder/Parsing/Test_URIParser.swift | 44 ++++++++++++++----- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift b/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift index 2b5cad22..420626ca 100644 --- a/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift +++ b/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift @@ -36,10 +36,10 @@ struct URIParser: Sendable { } /// A typealias for the underlying raw string storage. -private typealias Raw = String.SubSequence +typealias Raw = String.SubSequence /// A parser error. -private enum ParsingError: Swift.Error { +enum ParsingError: Swift.Error, Equatable { /// A malformed key-value pair was detected. case malformedKeyValuePair(Raw) @@ -219,7 +219,7 @@ extension URIParser { /// - Returns: The parsed root node. /// - Throws: An error if parsing fails. private mutating func parseExplodedDeepObjectRoot() throws -> URIParsedNode { - try parseGenericRoot { data, appendPair in + let parseNode = try parseGenericRoot { data, appendPair in let keyValueSeparator: Character = "=" let pairSeparator: Character = "&" let nestedKeyStartingCharacter: Character = "[" @@ -249,6 +249,11 @@ extension URIParser { appendPair(key, [value]) } } + + try parseNode.forEach { (key, value) in + if value.count > 1 { throw ParsingError.malformedKeyValuePair(key) } + } + return parseNode } } diff --git a/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift b/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift index 9cfc0816..f1be977a 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift @@ -78,7 +78,10 @@ final class Test_URIParser: Test_Runtime { simpleUnexplode: .custom("red,green,blue", value: ["": ["red", "green", "blue"]]), formDataExplode: "list=red&list=green&list=blue", formDataUnexplode: "list=red,green,blue", - deepObjectExplode: .custom("object%5Blist%5D=red&object%5Blist%5D=green&object%5Blist%5D=blue", value: [:]) + deepObjectExplode: .custom( + "object%5Blist%5D=red&object%5Blist%5D=green&object%5Blist%5D=blue", + expectedError: .malformedKeyValuePair("list") + ) ), value: ["list": ["red", "green", "blue"]] ), @@ -107,14 +110,26 @@ final class Test_URIParser: Test_Runtime { for testCase in cases { func testVariant(_ variant: Case.Variant, _ input: Case.Variants.Input) throws { var parser = URIParser(configuration: variant.config, data: input.string[...]) - let parsedNode = try parser.parseRoot() - XCTAssertEqual( - parsedNode, - input.valueOverride ?? testCase.value, - "Failed for config: \(variant.name)", - file: testCase.file, - line: testCase.line - ) + do { + let parsedNode = try parser.parseRoot() + XCTAssertEqual( + parsedNode, + input.valueOverride ?? testCase.value, + "Failed for config: \(variant.name)", + file: testCase.file, + line: testCase.line + ) + } catch { + guard let expectedError = input.expectedError, + let serializationError = error as? ParsingError else { throw error } + XCTAssertEqual( + expectedError, + serializationError, + "Failed for config: \(variant.name)", + file: testCase.file, + line: testCase.line + ) + } } let variants = testCase.variants try testVariant(.formExplode, variants.formExplode) @@ -147,19 +162,26 @@ extension Test_URIParser { struct Input: ExpressibleByStringLiteral { var string: String var valueOverride: URIParsedNode? + var expectedError: ParsingError? - init(string: String, valueOverride: URIParsedNode? = nil) { + init(string: String, valueOverride: URIParsedNode? = nil, expectedError: ParsingError? = nil) { self.string = string self.valueOverride = valueOverride + self.expectedError = expectedError } static func custom(_ string: String, value: URIParsedNode) -> Self { - .init(string: string, valueOverride: value) + .init(string: string, valueOverride: value, expectedError: nil) + } + + static func custom(_ string: String, expectedError: ParsingError) -> Self { + .init(string: string, valueOverride: nil, expectedError: expectedError) } init(stringLiteral value: String) { self.string = value self.valueOverride = nil + self.expectedError = nil } } From 6bdd19a8542910682f40ac4b83f0d91fe09578fc Mon Sep 17 00:00:00 2001 From: Kostis Stefanou Date: Fri, 12 Apr 2024 13:42:18 +0300 Subject: [PATCH 4/6] Throw Error when serializing non dictionary deepObject values --- .../URICoder/Parsing/URIParser.swift | 2 +- .../Serialization/URISerializer.swift | 7 +++- .../URICoder/Parsing/Test_URIParser.swift | 10 ++++- .../Serialization/Test_URISerializer.swift | 40 +++++++++++++++---- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift b/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift index 420626ca..0eb02839 100644 --- a/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift +++ b/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift @@ -39,7 +39,7 @@ struct URIParser: Sendable { typealias Raw = String.SubSequence /// A parser error. -enum ParsingError: Swift.Error, Equatable { +enum ParsingError: Swift.Error, Hashable { /// A malformed key-value pair was detected. case malformedKeyValuePair(Raw) diff --git a/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift b/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift index 46eff197..da3983c9 100644 --- a/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift +++ b/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift @@ -65,7 +65,7 @@ extension CharacterSet { extension URISerializer { /// A serializer error. - enum SerializationError: Swift.Error, Equatable { + enum SerializationError: Swift.Error, Hashable { /// Nested containers are not supported. case nestedContainersNotSupported @@ -73,6 +73,9 @@ extension URISerializer { /// Deep object arrays are not supported. case deepObjectsArrayNotSupported + /// Deep object with primitive values are not supported. + case deepObjectsWithPrimitiveValuesNotSupported + /// An invalid configuration was detected. case invalidConfiguration(String) } @@ -123,7 +126,7 @@ extension URISerializer { switch configuration.style { case .form: keyAndValueSeparator = "=" case .simple: keyAndValueSeparator = nil - case .deepObject: keyAndValueSeparator = "=" + case .deepObject: throw SerializationError.deepObjectsWithPrimitiveValuesNotSupported } try serializePrimitiveKeyValuePair(primitive, forKey: key, separator: keyAndValueSeparator) case .array(let array): try serializeArray(array.map(unwrapPrimitiveValue), forKey: key) diff --git a/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift b/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift index f1be977a..0960a957 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift @@ -121,7 +121,15 @@ final class Test_URIParser: Test_Runtime { ) } catch { guard let expectedError = input.expectedError, - let serializationError = error as? ParsingError else { throw error } + let serializationError = error as? ParsingError else { + XCTAssert( + false, + "Unexpected error thrown: \(error)", + file: testCase.file, + line: testCase.line + ) + return + } XCTAssertEqual( expectedError, serializationError, diff --git a/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift b/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift index 63b38316..4843ffeb 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift @@ -32,7 +32,10 @@ final class Test_URISerializer: Test_Runtime { simpleUnexplode: "", formDataExplode: "empty=", formDataUnexplode: "empty=", - deepObjectExplode: "empty=" + deepObjectExplode: .custom( + "empty=", + expectedError: .deepObjectsWithPrimitiveValuesNotSupported + ) ) ), makeCase( @@ -45,7 +48,10 @@ final class Test_URISerializer: Test_Runtime { simpleUnexplode: "fred", formDataExplode: "who=fred", formDataUnexplode: "who=fred", - deepObjectExplode: "who=fred" + deepObjectExplode: .custom( + "who=fred", + expectedError: .deepObjectsWithPrimitiveValuesNotSupported + ) ) ), makeCase( @@ -58,7 +64,10 @@ final class Test_URISerializer: Test_Runtime { simpleUnexplode: "1234", formDataExplode: "x=1234", formDataUnexplode: "x=1234", - deepObjectExplode: "x=1234" + deepObjectExplode: .custom( + "x=1234", + expectedError: .deepObjectsWithPrimitiveValuesNotSupported + ) ) ), makeCase( @@ -71,7 +80,10 @@ final class Test_URISerializer: Test_Runtime { simpleUnexplode: "12.34", formDataExplode: "x=12.34", formDataUnexplode: "x=12.34", - deepObjectExplode: "x=12.34" + deepObjectExplode: .custom( + "x=12.34", + expectedError: .deepObjectsWithPrimitiveValuesNotSupported + ) ) ), makeCase( @@ -84,7 +96,10 @@ final class Test_URISerializer: Test_Runtime { simpleUnexplode: "true", formDataExplode: "enabled=true", formDataUnexplode: "enabled=true", - deepObjectExplode: "enabled=true" + deepObjectExplode: .custom( + "enabled=true", + expectedError: .deepObjectsWithPrimitiveValuesNotSupported + ) ) ), makeCase( @@ -97,7 +112,10 @@ final class Test_URISerializer: Test_Runtime { simpleUnexplode: "Hello%20World", formDataExplode: "hello=Hello+World", formDataUnexplode: "hello=Hello+World", - deepObjectExplode: "hello=Hello%20World" + deepObjectExplode: .custom( + "hello=Hello%20World", + expectedError: .deepObjectsWithPrimitiveValuesNotSupported + ) ) ), makeCase( @@ -147,7 +165,15 @@ final class Test_URISerializer: Test_Runtime { ) } catch { guard let expectedError = input.expectedError, - let serializationError = error as? URISerializer.SerializationError else { throw error } + let serializationError = error as? URISerializer.SerializationError else { + XCTAssert( + false, + "Unexpected error thrown: \(error)", + file: testCase.file, + line: testCase.line + ) + return + } XCTAssertEqual( expectedError, serializationError, From 3aae2c77eebaffaa1912f1f094aaae68be11bee2 Mon Sep 17 00:00:00 2001 From: Kostis Stefanou Date: Fri, 12 Apr 2024 14:05:01 +0300 Subject: [PATCH 5/6] Update tests for URICoding --- .../URICoder/Parsing/Test_URIParser.swift | 4 +- .../URICoder/Test_URICodingRoundtrip.swift | 125 +++++++++++++----- 2 files changed, 97 insertions(+), 32 deletions(-) diff --git a/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift b/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift index 0960a957..846be9bc 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift @@ -121,7 +121,7 @@ final class Test_URIParser: Test_Runtime { ) } catch { guard let expectedError = input.expectedError, - let serializationError = error as? ParsingError else { + let parsingError = error as? ParsingError else { XCTAssert( false, "Unexpected error thrown: \(error)", @@ -132,7 +132,7 @@ final class Test_URIParser: Test_Runtime { } XCTAssertEqual( expectedError, - serializationError, + parsingError, "Failed for config: \(variant.name)", file: testCase.file, line: testCase.line diff --git a/Tests/OpenAPIRuntimeTests/URICoder/Test_URICodingRoundtrip.swift b/Tests/OpenAPIRuntimeTests/URICoder/Test_URICodingRoundtrip.swift index 200cba1a..ee327933 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/Test_URICodingRoundtrip.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/Test_URICodingRoundtrip.swift @@ -97,7 +97,10 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "", formDataExplode: "root=", formDataUnexplode: "root=", - deepObjectExplode: "root=" + deepObjectExplode: .custom( + "root=", + expectedError: .deepObjectsWithPrimitiveValuesNotSupported + ) ) ) @@ -112,7 +115,10 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "Hello%20World%21", formDataExplode: "root=Hello+World%21", formDataUnexplode: "root=Hello+World%21", - deepObjectExplode: "root=Hello%20World%21" + deepObjectExplode: .custom( + "root=Hello%20World%21", + expectedError: .deepObjectsWithPrimitiveValuesNotSupported + ) ) ) @@ -127,7 +133,10 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "red", formDataExplode: "root=red", formDataUnexplode: "root=red", - deepObjectExplode: "root=red" + deepObjectExplode: .custom( + "root=red", + expectedError: .deepObjectsWithPrimitiveValuesNotSupported + ) ) ) @@ -142,7 +151,10 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "1234", formDataExplode: "root=1234", formDataUnexplode: "root=1234", - deepObjectExplode: "root=1234" + deepObjectExplode: .custom( + "root=1234", + expectedError: .deepObjectsWithPrimitiveValuesNotSupported + ) ) ) @@ -157,7 +169,10 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "12.34", formDataExplode: "root=12.34", formDataUnexplode: "root=12.34", - deepObjectExplode: "root=12.34" + deepObjectExplode: .custom( + "root=12.34", + expectedError: .deepObjectsWithPrimitiveValuesNotSupported + ) ) ) @@ -172,7 +187,10 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "true", formDataExplode: "root=true", formDataUnexplode: "root=true", - deepObjectExplode: "root=true" + deepObjectExplode: .custom( + "root=true", + expectedError: .deepObjectsWithPrimitiveValuesNotSupported + ) ) ) @@ -187,7 +205,10 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "2023-08-25T07%3A34%3A59Z", formDataExplode: "root=2023-08-25T07%3A34%3A59Z", formDataUnexplode: "root=2023-08-25T07%3A34%3A59Z", - deepObjectExplode: "root=2023-08-25T07%3A34%3A59Z" + deepObjectExplode: .custom( + "root=2023-08-25T07%3A34%3A59Z", + expectedError: .deepObjectsWithPrimitiveValuesNotSupported + ) ) ) @@ -202,7 +223,10 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "a,b,c", formDataExplode: "list=a&list=b&list=c", formDataUnexplode: "list=a,b,c", - deepObjectExplode: nil + deepObjectExplode: .custom( + "list=a&list=b&list=c", + expectedError: .deepObjectsArrayNotSupported + ) ) ) @@ -217,7 +241,10 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "2023-08-25T07%3A34%3A59Z,2023-08-25T07%3A35%3A01Z", formDataExplode: "list=2023-08-25T07%3A34%3A59Z&list=2023-08-25T07%3A35%3A01Z", formDataUnexplode: "list=2023-08-25T07%3A34%3A59Z,2023-08-25T07%3A35%3A01Z", - deepObjectExplode: nil + deepObjectExplode: .custom( + "list=2023-08-25T07%3A34%3A59Z&list=2023-08-25T07%3A35%3A01Z", + expectedError: .deepObjectsArrayNotSupported + ) ) ) @@ -232,7 +259,10 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: .custom("", value: [""]), formDataExplode: "", formDataUnexplode: "", - deepObjectExplode: nil + deepObjectExplode: .custom( + "", + expectedError: .deepObjectsArrayNotSupported + ) ) ) @@ -247,7 +277,10 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "red,green,blue", formDataExplode: "list=red&list=green&list=blue", formDataUnexplode: "list=red,green,blue", - deepObjectExplode: nil + deepObjectExplode: .custom( + "list=red&list=green&list=blue", + expectedError: .deepObjectsArrayNotSupported + ) ) ) @@ -278,7 +311,9 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "2023-01-18T10%3A04%3A11Z", formDataExplode: "root=2023-01-18T10%3A04%3A11Z", formDataUnexplode: "root=2023-01-18T10%3A04%3A11Z", - deepObjectExplode: "root=2023-01-18T10%3A04%3A11Z" + deepObjectExplode: .custom( + "root=2023-01-18T10%3A04%3A11Z", + expectedError: .deepObjectsWithPrimitiveValuesNotSupported) ) ) try _test( @@ -291,7 +326,9 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "green", formDataExplode: "root=green", formDataUnexplode: "root=green", - deepObjectExplode: "root=green" + deepObjectExplode: .custom( + "root=green", + expectedError: .deepObjectsWithPrimitiveValuesNotSupported) ) ) try _test( @@ -372,15 +409,25 @@ final class Test_URICodingRoundtrip: Test_Runtime { struct Input: ExpressibleByStringLiteral { var string: String var customValue: T? - - init(string: String, customValue: T?) { + var expectedError: URISerializer.SerializationError? + + init(string: String, customValue: T?, expectedError: URISerializer.SerializationError?) { self.string = string self.customValue = customValue + self.expectedError = expectedError } - init(stringLiteral value: String) { self.init(string: value, customValue: nil) } + init(stringLiteral value: String) { + self.init(string: value, customValue: nil, expectedError: nil) + } - static func custom(_ string: String, value: T) -> Self { .init(string: string, customValue: value) } + static func custom(_ string: String, value: T) -> Self { + .init(string: string, customValue: value, expectedError: nil) + } + + static func custom(_ string: String, expectedError: URISerializer.SerializationError) -> Self { + .init(string: string, customValue: nil, expectedError: expectedError) + } } var formExplode: Input @@ -389,7 +436,7 @@ final class Test_URICodingRoundtrip: Test_Runtime { var simpleUnexplode: Input var formDataExplode: Input var formDataUnexplode: Input - var deepObjectExplode: Input? + var deepObjectExplode: Input } func _test( @@ -401,11 +448,31 @@ final class Test_URICodingRoundtrip: Test_Runtime { ) throws { func testVariant(name: String, configuration: URICoderConfiguration, variant: Variants.Input) throws { let encoder = URIEncoder(configuration: configuration) - let encodedString = try encoder.encode(value, forKey: key) - XCTAssertEqual(encodedString, variant.string, "Variant: \(name)", file: file, line: line) - let decoder = URIDecoder(configuration: configuration) - let decodedValue = try decoder.decode(T.self, forKey: key, from: encodedString[...]) - XCTAssertEqual(decodedValue, variant.customValue ?? value, "Variant: \(name)", file: file, line: line) + do { + let encodedString = try encoder.encode(value, forKey: key) + XCTAssertEqual(encodedString, variant.string, "Variant: \(name)", file: file, line: line) + let decoder = URIDecoder(configuration: configuration) + let decodedValue = try decoder.decode(T.self, forKey: key, from: encodedString[...]) + XCTAssertEqual(decodedValue, variant.customValue ?? value, "Variant: \(name)", file: file, line: line) + } catch { + guard let expectedError = variant.expectedError, + let serializationError = error as? URISerializer.SerializationError else { + XCTAssert( + false, + "Unexpected error thrown: \(error)", + file: file, + line: line + ) + return + } + XCTAssertEqual( + expectedError, + serializationError, + "Failed for config: \(variant.string)", + file: file, + line: line + ) + } } try testVariant(name: "formExplode", configuration: .formExplode, variant: variants.formExplode) try testVariant(name: "formUnexplode", configuration: .formUnexplode, variant: variants.formUnexplode) @@ -418,12 +485,10 @@ final class Test_URICodingRoundtrip: Test_Runtime { variant: variants.formDataUnexplode ) - if let deepObjectExplode = variants.deepObjectExplode { - try testVariant( - name: "deepObjectExplode", - configuration: .deepObjectExplode, - variant: deepObjectExplode - ) - } + try testVariant( + name: "deepObjectExplode", + configuration: .deepObjectExplode, + variant: variants.deepObjectExplode + ) } } From 9633ee28e882fbd9d6efa5959721446b66869f31 Mon Sep 17 00:00:00 2001 From: Kostis Stefanou Date: Tue, 16 Apr 2024 11:26:18 +0300 Subject: [PATCH 6/6] Code format fixes --- .../Conversion/ParameterStyles.swift | 1 - .../Common/URICoderConfiguration.swift | 1 - .../URICoder/Parsing/URIParser.swift | 15 +---- .../Serialization/URISerializer.swift | 7 +-- .../URICoder/Encoding/Test_URIEncoder.swift | 1 - .../URICoder/Parsing/Test_URIParser.swift | 16 ++--- .../Serialization/Test_URISerializer.swift | 37 +++-------- .../URICoder/Test_URICodingRoundtrip.swift | 62 +++++-------------- .../URICoder/URICoderTestUtils.swift | 1 - 9 files changed, 32 insertions(+), 109 deletions(-) diff --git a/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift b/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift index a9d11a9e..07aa6092 100644 --- a/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift +++ b/Sources/OpenAPIRuntime/Conversion/ParameterStyles.swift @@ -26,7 +26,6 @@ /// /// Details: https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.2 case simple - /// The deepObject style. /// /// Details: https://spec.openapis.org/oas/v3.1.0.html#style-values diff --git a/Sources/OpenAPIRuntime/URICoder/Common/URICoderConfiguration.swift b/Sources/OpenAPIRuntime/URICoder/Common/URICoderConfiguration.swift index 64bf45d4..ccbdb8c5 100644 --- a/Sources/OpenAPIRuntime/URICoder/Common/URICoderConfiguration.swift +++ b/Sources/OpenAPIRuntime/URICoder/Common/URICoderConfiguration.swift @@ -25,7 +25,6 @@ struct URICoderConfiguration { /// A style for form-based URI expansion. case form - /// A style for nested variable expansion case deepObject } diff --git a/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift b/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift index 0eb02839..c1cb5940 100644 --- a/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift +++ b/Sources/OpenAPIRuntime/URICoder/Parsing/URIParser.swift @@ -43,7 +43,6 @@ enum ParsingError: Swift.Error, Hashable { /// A malformed key-value pair was detected. case malformedKeyValuePair(Raw) - /// An invalid configuration was detected. case invalidConfiguration(String) } @@ -213,7 +212,6 @@ extension URIParser { } } } - /// Parses the root node assuming the raw string uses the deepObject style /// and the explode parameter is enabled. /// - Returns: The parsed root node. @@ -224,35 +222,26 @@ extension URIParser { let pairSeparator: Character = "&" let nestedKeyStartingCharacter: Character = "[" let nestedKeyEndingCharacter: Character = "]" - func nestedKey(from deepObjectKey: String.SubSequence) -> Raw { var unescapedDeepObjectKey = Substring(deepObjectKey.removingPercentEncoding ?? "") let topLevelKey = unescapedDeepObjectKey.parseUpToCharacterOrEnd(nestedKeyStartingCharacter) let nestedKey = unescapedDeepObjectKey.parseUpToCharacterOrEnd(nestedKeyEndingCharacter) return nestedKey.isEmpty ? topLevelKey : nestedKey } - while !data.isEmpty { let (firstResult, firstValue) = data.parseUpToEitherCharacterOrEnd( first: keyValueSeparator, second: pairSeparator ) - - guard case .foundFirst = firstResult else { - throw ParsingError.malformedKeyValuePair(firstValue) - } + guard case .foundFirst = firstResult else { throw ParsingError.malformedKeyValuePair(firstValue) } // Hit the key/value separator, so a value will follow. let secondValue = data.parseUpToCharacterOrEnd(pairSeparator) let key = nestedKey(from: firstValue) let value = secondValue - appendPair(key, [value]) } } - - try parseNode.forEach { (key, value) in - if value.count > 1 { throw ParsingError.malformedKeyValuePair(key) } - } + for (key, value) in parseNode where value.count > 1 { throw ParsingError.malformedKeyValuePair(key) } return parseNode } } diff --git a/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift b/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift index da3983c9..45d3b0da 100644 --- a/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift +++ b/Sources/OpenAPIRuntime/URICoder/Serialization/URISerializer.swift @@ -69,13 +69,10 @@ extension URISerializer { /// Nested containers are not supported. case nestedContainersNotSupported - /// Deep object arrays are not supported. case deepObjectsArrayNotSupported - /// Deep object with primitive values are not supported. case deepObjectsWithPrimitiveValuesNotSupported - /// An invalid configuration was detected. case invalidConfiguration(String) } @@ -190,8 +187,7 @@ extension URISerializer { case (.simple, _): keyAndValueSeparator = nil pairSeparator = "," - case (.deepObject, _): - throw SerializationError.deepObjectsArrayNotSupported + case (.deepObject, _): throw SerializationError.deepObjectsArrayNotSupported } func serializeNext(_ element: URIEncodedNode.Primitive) throws { if let keyAndValueSeparator { @@ -263,7 +259,6 @@ extension URISerializer { try serializeNext(element, forKey: serializeNestedKey(elementKey, forKey: key)) data.append(pairSeparator) } - if let (elementKey, element) = sortedDictionary.last { try serializeNext(element, forKey: serializeNestedKey(elementKey, forKey: key)) } diff --git a/Tests/OpenAPIRuntimeTests/URICoder/Encoding/Test_URIEncoder.swift b/Tests/OpenAPIRuntimeTests/URICoder/Encoding/Test_URIEncoder.swift index 02b63ea3..fd5cdd20 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/Encoding/Test_URIEncoder.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/Encoding/Test_URIEncoder.swift @@ -23,7 +23,6 @@ final class Test_URIEncoder: Test_Runtime { let encodedString = try encoder.encode(Foo(bar: "hello world"), forKey: "root") XCTAssertEqual(encodedString, "bar=hello+world") } - func testNestedEncoding() throws { struct Foo: Encodable { var bar: String } let serializer = URISerializer(configuration: .deepObjectExplode) diff --git a/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift b/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift index 846be9bc..86c962e1 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/Parsing/Test_URIParser.swift @@ -17,7 +17,8 @@ import XCTest final class Test_URIParser: Test_Runtime { let testedVariants: [URICoderConfiguration] = [ - .formExplode, .formUnexplode, .simpleExplode, .simpleUnexplode, .formDataExplode, .formDataUnexplode, .deepObjectExplode + .formExplode, .formUnexplode, .simpleExplode, .simpleUnexplode, .formDataExplode, .formDataUnexplode, + .deepObjectExplode, ] func testParsing() throws { @@ -101,7 +102,7 @@ final class Test_URIParser: Test_Runtime { formDataUnexplode: .custom( "keys=comma,%2C,dot,.,semi,%3B", value: ["keys": ["comma", ",", "dot", ".", "semi", ";"]] - ), + ), deepObjectExplode: "keys%5Bcomma%5D=%2C&keys%5Bdot%5D=.&keys%5Bsemi%5D=%3B" ), value: ["semi": [";"], "dot": ["."], "comma": [","]] @@ -120,14 +121,8 @@ final class Test_URIParser: Test_Runtime { line: testCase.line ) } catch { - guard let expectedError = input.expectedError, - let parsingError = error as? ParsingError else { - XCTAssert( - false, - "Unexpected error thrown: \(error)", - file: testCase.file, - line: testCase.line - ) + guard let expectedError = input.expectedError, let parsingError = error as? ParsingError else { + XCTAssert(false, "Unexpected error thrown: \(error)", file: testCase.file, line: testCase.line) return } XCTAssertEqual( @@ -181,7 +176,6 @@ extension Test_URIParser { static func custom(_ string: String, value: URIParsedNode) -> Self { .init(string: string, valueOverride: value, expectedError: nil) } - static func custom(_ string: String, expectedError: ParsingError) -> Self { .init(string: string, valueOverride: nil, expectedError: expectedError) } diff --git a/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift b/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift index 4843ffeb..688c508a 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/Serialization/Test_URISerializer.swift @@ -32,10 +32,7 @@ final class Test_URISerializer: Test_Runtime { simpleUnexplode: "", formDataExplode: "empty=", formDataUnexplode: "empty=", - deepObjectExplode: .custom( - "empty=", - expectedError: .deepObjectsWithPrimitiveValuesNotSupported - ) + deepObjectExplode: .custom("empty=", expectedError: .deepObjectsWithPrimitiveValuesNotSupported) ) ), makeCase( @@ -48,10 +45,7 @@ final class Test_URISerializer: Test_Runtime { simpleUnexplode: "fred", formDataExplode: "who=fred", formDataUnexplode: "who=fred", - deepObjectExplode: .custom( - "who=fred", - expectedError: .deepObjectsWithPrimitiveValuesNotSupported - ) + deepObjectExplode: .custom("who=fred", expectedError: .deepObjectsWithPrimitiveValuesNotSupported) ) ), makeCase( @@ -64,10 +58,7 @@ final class Test_URISerializer: Test_Runtime { simpleUnexplode: "1234", formDataExplode: "x=1234", formDataUnexplode: "x=1234", - deepObjectExplode: .custom( - "x=1234", - expectedError: .deepObjectsWithPrimitiveValuesNotSupported - ) + deepObjectExplode: .custom("x=1234", expectedError: .deepObjectsWithPrimitiveValuesNotSupported) ) ), makeCase( @@ -80,10 +71,7 @@ final class Test_URISerializer: Test_Runtime { simpleUnexplode: "12.34", formDataExplode: "x=12.34", formDataUnexplode: "x=12.34", - deepObjectExplode: .custom( - "x=12.34", - expectedError: .deepObjectsWithPrimitiveValuesNotSupported - ) + deepObjectExplode: .custom("x=12.34", expectedError: .deepObjectsWithPrimitiveValuesNotSupported) ) ), makeCase( @@ -129,7 +117,7 @@ final class Test_URISerializer: Test_Runtime { formDataExplode: "list=red&list=green&list=blue", formDataUnexplode: "list=red,green,blue", deepObjectExplode: .custom( - "list=red&list=green&list=blue", + "list=red&list=green&list=blue", expectedError: .deepObjectsArrayNotSupported ) ) @@ -165,13 +153,9 @@ final class Test_URISerializer: Test_Runtime { ) } catch { guard let expectedError = input.expectedError, - let serializationError = error as? URISerializer.SerializationError else { - XCTAssert( - false, - "Unexpected error thrown: \(error)", - file: testCase.file, - line: testCase.line - ) + let serializationError = error as? URISerializer.SerializationError + else { + XCTAssert(false, "Unexpected error thrown: \(error)", file: testCase.file, line: testCase.line) return } XCTAssertEqual( @@ -209,26 +193,21 @@ extension Test_URISerializer { static let deepObjectExplode: Self = .init(name: "deepObjectExplode", config: .deepObjectExplode) } struct Variants { - struct Input: ExpressibleByStringLiteral { var string: String var expectedError: URISerializer.SerializationError? - init(string: String, expectedError: URISerializer.SerializationError? = nil) { self.string = string self.expectedError = expectedError } - static func custom(_ string: String, expectedError: URISerializer.SerializationError) -> Self { .init(string: string, expectedError: expectedError) } - init(stringLiteral value: String) { self.string = value self.expectedError = nil } } - var formExplode: Input var formUnexplode: Input var simpleExplode: Input diff --git a/Tests/OpenAPIRuntimeTests/URICoder/Test_URICodingRoundtrip.swift b/Tests/OpenAPIRuntimeTests/URICoder/Test_URICodingRoundtrip.swift index ee327933..cc0dc29c 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/Test_URICodingRoundtrip.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/Test_URICodingRoundtrip.swift @@ -97,10 +97,7 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "", formDataExplode: "root=", formDataUnexplode: "root=", - deepObjectExplode: .custom( - "root=", - expectedError: .deepObjectsWithPrimitiveValuesNotSupported - ) + deepObjectExplode: .custom("root=", expectedError: .deepObjectsWithPrimitiveValuesNotSupported) ) ) @@ -133,10 +130,7 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "red", formDataExplode: "root=red", formDataUnexplode: "root=red", - deepObjectExplode: .custom( - "root=red", - expectedError: .deepObjectsWithPrimitiveValuesNotSupported - ) + deepObjectExplode: .custom("root=red", expectedError: .deepObjectsWithPrimitiveValuesNotSupported) ) ) @@ -151,10 +145,7 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "1234", formDataExplode: "root=1234", formDataUnexplode: "root=1234", - deepObjectExplode: .custom( - "root=1234", - expectedError: .deepObjectsWithPrimitiveValuesNotSupported - ) + deepObjectExplode: .custom("root=1234", expectedError: .deepObjectsWithPrimitiveValuesNotSupported) ) ) @@ -169,10 +160,7 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "12.34", formDataExplode: "root=12.34", formDataUnexplode: "root=12.34", - deepObjectExplode: .custom( - "root=12.34", - expectedError: .deepObjectsWithPrimitiveValuesNotSupported - ) + deepObjectExplode: .custom("root=12.34", expectedError: .deepObjectsWithPrimitiveValuesNotSupported) ) ) @@ -187,10 +175,7 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "true", formDataExplode: "root=true", formDataUnexplode: "root=true", - deepObjectExplode: .custom( - "root=true", - expectedError: .deepObjectsWithPrimitiveValuesNotSupported - ) + deepObjectExplode: .custom("root=true", expectedError: .deepObjectsWithPrimitiveValuesNotSupported) ) ) @@ -223,10 +208,7 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "a,b,c", formDataExplode: "list=a&list=b&list=c", formDataUnexplode: "list=a,b,c", - deepObjectExplode: .custom( - "list=a&list=b&list=c", - expectedError: .deepObjectsArrayNotSupported - ) + deepObjectExplode: .custom("list=a&list=b&list=c", expectedError: .deepObjectsArrayNotSupported) ) ) @@ -259,10 +241,7 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: .custom("", value: [""]), formDataExplode: "", formDataUnexplode: "", - deepObjectExplode: .custom( - "", - expectedError: .deepObjectsArrayNotSupported - ) + deepObjectExplode: .custom("", expectedError: .deepObjectsArrayNotSupported) ) ) @@ -295,7 +274,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "bar,24,color,red,date,2023-08-25T07%3A34%3A59Z,empty,,foo,hi%21", formDataExplode: "bar=24&color=red&date=2023-08-25T07%3A34%3A59Z&empty=&foo=hi%21", formDataUnexplode: "keys=bar,24,color,red,date,2023-08-25T07%3A34%3A59Z,empty,,foo,hi%21", - deepObjectExplode: "keys%5Bbar%5D=24&keys%5Bcolor%5D=red&keys%5Bdate%5D=2023-08-25T07%3A34%3A59Z&keys%5Bempty%5D=&keys%5Bfoo%5D=hi%21" + deepObjectExplode: + "keys%5Bbar%5D=24&keys%5Bcolor%5D=red&keys%5Bdate%5D=2023-08-25T07%3A34%3A59Z&keys%5Bempty%5D=&keys%5Bfoo%5D=hi%21" ) ) @@ -313,7 +293,8 @@ final class Test_URICodingRoundtrip: Test_Runtime { formDataUnexplode: "root=2023-01-18T10%3A04%3A11Z", deepObjectExplode: .custom( "root=2023-01-18T10%3A04%3A11Z", - expectedError: .deepObjectsWithPrimitiveValuesNotSupported) + expectedError: .deepObjectsWithPrimitiveValuesNotSupported + ) ) ) try _test( @@ -326,9 +307,7 @@ final class Test_URICodingRoundtrip: Test_Runtime { simpleUnexplode: "green", formDataExplode: "root=green", formDataUnexplode: "root=green", - deepObjectExplode: .custom( - "root=green", - expectedError: .deepObjectsWithPrimitiveValuesNotSupported) + deepObjectExplode: .custom("root=green", expectedError: .deepObjectsWithPrimitiveValuesNotSupported) ) ) try _test( @@ -410,21 +389,17 @@ final class Test_URICodingRoundtrip: Test_Runtime { var string: String var customValue: T? var expectedError: URISerializer.SerializationError? - init(string: String, customValue: T?, expectedError: URISerializer.SerializationError?) { self.string = string self.customValue = customValue self.expectedError = expectedError } - init(stringLiteral value: String) { - self.init(string: value, customValue: nil, expectedError: nil) - } + init(stringLiteral value: String) { self.init(string: value, customValue: nil, expectedError: nil) } static func custom(_ string: String, value: T) -> Self { .init(string: string, customValue: value, expectedError: nil) } - static func custom(_ string: String, expectedError: URISerializer.SerializationError) -> Self { .init(string: string, customValue: nil, expectedError: expectedError) } @@ -456,13 +431,9 @@ final class Test_URICodingRoundtrip: Test_Runtime { XCTAssertEqual(decodedValue, variant.customValue ?? value, "Variant: \(name)", file: file, line: line) } catch { guard let expectedError = variant.expectedError, - let serializationError = error as? URISerializer.SerializationError else { - XCTAssert( - false, - "Unexpected error thrown: \(error)", - file: file, - line: line - ) + let serializationError = error as? URISerializer.SerializationError + else { + XCTAssert(false, "Unexpected error thrown: \(error)", file: file, line: line) return } XCTAssertEqual( @@ -484,7 +455,6 @@ final class Test_URICodingRoundtrip: Test_Runtime { configuration: .formDataUnexplode, variant: variants.formDataUnexplode ) - try testVariant( name: "deepObjectExplode", configuration: .deepObjectExplode, diff --git a/Tests/OpenAPIRuntimeTests/URICoder/URICoderTestUtils.swift b/Tests/OpenAPIRuntimeTests/URICoder/URICoderTestUtils.swift index 38b9e860..65235d82 100644 --- a/Tests/OpenAPIRuntimeTests/URICoder/URICoderTestUtils.swift +++ b/Tests/OpenAPIRuntimeTests/URICoder/URICoderTestUtils.swift @@ -59,7 +59,6 @@ extension URICoderConfiguration { spaceEscapingCharacter: .plus, dateTranscoder: defaultDateTranscoder ) - static let deepObjectExplode: Self = .init( style: .deepObject, explode: true,