Skip to content

Commit 0769bd0

Browse files
authored
Introduce URIEncoder and URIDecoder types (#41)
Introduce URIEncoder and URIDecoder types ### Motivation Fixes apple/swift-openapi-generator#192. For refactoring how we encode: - path parameters - query parameters - headers - (new feature, coming later) `application/x-www-form-urlencoded` bodies Supports: - form + explode - form + unexplode - simple + explode - simple + unexplode - form where space is encoded as + instead of %20 (for bodies) + explode - form where space is encoded as + instead of %20 (for bodies) + unexplode ### Modifications First step - introduce two new types: `URIEncoder` and `URIDecoder`. They're configurable types that can handle the URI Template (RFC 6570) form and simple styles, refined by OpenAPI 3.0.3, and also the `application/x-www-form-urlencoded` format (mostly identical to URI Template). ### Result The types can be used now, subsequent PRs will integrate them. ### Test Plan Added unit tests for the individual parts of the coder, but also roundtrip tests. Reviewed by: glbrntt Builds: ✔︎ pull request validation (5.8) - Build finished. ✔︎ pull request validation (5.9) - Build finished. ✔︎ pull request validation (api breakage) - Build finished. ✔︎ pull request validation (docc test) - Build finished. ✔︎ pull request validation (integration test) - Build finished. ✔︎ pull request validation (nightly) - Build finished. ✔︎ pull request validation (soundness) - Build finished. #41
1 parent cb17921 commit 0769bd0

25 files changed

+4300
-0
lines changed

NOTICE.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,12 @@ This product contains derivations of various scripts and templates from SwiftNIO
3232
* https://www.apache.org/licenses/LICENSE-2.0
3333
* HOMEPAGE:
3434
* https://github.com/apple/swift-nio
35+
36+
---
37+
38+
This product contains coder implementations inspired by swift-http-structured-headers.
39+
40+
* LICENSE (Apache License 2.0):
41+
* https://www.apache.org/licenses/LICENSE-2.0
42+
* HOMEPAGE:
43+
* https://github.com/apple/swift-http-structured-headers
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftOpenAPIGenerator open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Foundation
16+
17+
/// The coding key used by the URI encoder and decoder.
18+
struct URICoderCodingKey {
19+
20+
/// The string to use in a named collection (e.g. a string-keyed dictionary).
21+
var stringValue: String
22+
23+
/// The value to use in an integer-indexed collection (e.g. an int-keyed
24+
/// dictionary).
25+
var intValue: Int?
26+
27+
/// Creates a new key with the same string and int value as the provided key.
28+
/// - Parameter key: The key whose values to copy.
29+
init(_ key: some CodingKey) {
30+
self.stringValue = key.stringValue
31+
self.intValue = key.intValue
32+
}
33+
}
34+
35+
extension URICoderCodingKey: CodingKey {
36+
37+
init(stringValue: String) {
38+
self.stringValue = stringValue
39+
self.intValue = nil
40+
}
41+
42+
init(intValue: Int) {
43+
self.stringValue = "\(intValue)"
44+
self.intValue = intValue
45+
}
46+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftOpenAPIGenerator open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Foundation
16+
17+
/// A bag of configuration values used by the URI encoder and decoder.
18+
struct URICoderConfiguration {
19+
20+
/// A variable expansion style as described by RFC 6570 and OpenAPI 3.0.3.
21+
enum Style {
22+
23+
/// A style for simple string variable expansion.
24+
case simple
25+
26+
/// A style for form-based URI expansion.
27+
case form
28+
}
29+
30+
/// A character used to escape the space character.
31+
enum SpaceEscapingCharacter: String {
32+
33+
/// A percent encoded value for the space character.
34+
case percentEncoded = "%20"
35+
36+
/// The plus character.
37+
case plus = "+"
38+
}
39+
40+
/// The variable expansion style.
41+
var style: Style
42+
43+
/// A Boolean value indicating whether the key should be repeated with
44+
/// each value, as described by RFC 6570 and OpenAPI 3.0.3.
45+
var explode: Bool
46+
47+
/// The character used to escape the space character.
48+
var spaceEscapingCharacter: SpaceEscapingCharacter
49+
50+
/// The coder used for serializing the Date type.
51+
var dateTranscoder: any DateTranscoder
52+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftOpenAPIGenerator open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Foundation
16+
17+
/// A node produced by `URIValueToNodeEncoder`.
18+
enum URIEncodedNode: Equatable {
19+
20+
/// No value.
21+
case unset
22+
23+
/// A single primitive value.
24+
case primitive(Primitive)
25+
26+
/// An array of nodes.
27+
case array([Self])
28+
29+
/// A dictionary with node values.
30+
case dictionary([String: Self])
31+
32+
/// A primitive value.
33+
enum Primitive: Equatable {
34+
35+
/// A boolean value.
36+
case bool(Bool)
37+
38+
/// A string value.
39+
case string(String)
40+
41+
/// An integer value.
42+
case integer(Int)
43+
44+
/// A floating-point value.
45+
case double(Double)
46+
47+
/// A date value.
48+
case date(Date)
49+
}
50+
}
51+
52+
extension URIEncodedNode {
53+
54+
/// An error thrown by the methods modifying `URIEncodedNode`.
55+
enum InsertionError: Swift.Error {
56+
57+
/// The encoder encoded a second primitive value.
58+
case settingPrimitiveValueAgain
59+
60+
/// The encoder set a single value on a container.
61+
case settingValueOnAContainer
62+
63+
/// The encoder appended to a node that wasn't an array.
64+
case appendingToNonArrayContainer
65+
66+
/// The encoder inserted a value for key into a node that wasn't
67+
/// a dictionary.
68+
case insertingChildValueIntoNonContainer
69+
70+
/// The encoder added a value to an array, but the key was not a valid
71+
/// integer key.
72+
case insertingChildValueIntoArrayUsingNonIntValueKey
73+
}
74+
75+
/// Sets the node to be a primitive node with the provided value.
76+
/// - Parameter value: The primitive value to set into the node.
77+
/// - Throws: If the node is already set.
78+
mutating func set(_ value: Primitive) throws {
79+
switch self {
80+
case .unset:
81+
self = .primitive(value)
82+
case .primitive:
83+
throw InsertionError.settingPrimitiveValueAgain
84+
case .array, .dictionary:
85+
throw InsertionError.settingValueOnAContainer
86+
}
87+
}
88+
89+
/// Inserts a value for a key into the node, which is interpreted as a
90+
/// dictionary.
91+
/// - Parameters:
92+
/// - childValue: The value to save under the provided key.
93+
/// - key: The key to save the value for into the dictionary.
94+
/// - Throws: If the node is already set to be anything else but a
95+
/// dictionary.
96+
mutating func insert<Key: CodingKey>(
97+
_ childValue: Self,
98+
atKey key: Key
99+
) throws {
100+
switch self {
101+
case .dictionary(var dictionary):
102+
self = .unset
103+
dictionary[key.stringValue] = childValue
104+
self = .dictionary(dictionary)
105+
case .array(var array):
106+
// Check that this is a valid key for an unkeyed container,
107+
// but don't actually extract the index, we only support appending
108+
// here.
109+
guard let intValue = key.intValue else {
110+
throw InsertionError.insertingChildValueIntoArrayUsingNonIntValueKey
111+
}
112+
precondition(
113+
intValue == array.count,
114+
"Unkeyed container inserting at an incorrect index"
115+
)
116+
self = .unset
117+
array.append(childValue)
118+
self = .array(array)
119+
case .unset:
120+
if let intValue = key.intValue {
121+
precondition(
122+
intValue == 0,
123+
"Unkeyed container inserting at an incorrect index"
124+
)
125+
self = .array([childValue])
126+
} else {
127+
self = .dictionary([key.stringValue: childValue])
128+
}
129+
default:
130+
throw InsertionError.insertingChildValueIntoNonContainer
131+
}
132+
}
133+
134+
/// Appends a value to the array node.
135+
/// - Parameter childValue: The node to append to the underlying array.
136+
/// - Throws: If the node is already set to be anything else but an array.
137+
mutating func append(_ childValue: Self) throws {
138+
switch self {
139+
case .array(var items):
140+
self = .unset
141+
items.append(childValue)
142+
self = .array(items)
143+
case .unset:
144+
self = .array([childValue])
145+
default:
146+
throw InsertionError.appendingToNonArrayContainer
147+
}
148+
}
149+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftOpenAPIGenerator open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Foundation
16+
17+
/// The type used for keys by `URIParser`.
18+
typealias URIParsedKey = String.SubSequence
19+
20+
/// The type used for values by `URIParser`.
21+
typealias URIParsedValue = String.SubSequence
22+
23+
/// The type used for an array of values by `URIParser`.
24+
typealias URIParsedValueArray = [URIParsedValue]
25+
26+
/// The type used for a node and a dictionary by `URIParser`.
27+
typealias URIParsedNode = [URIParsedKey: URIParsedValueArray]

0 commit comments

Comments
 (0)