Skip to content

Commit

Permalink
Introduce URIEncoder and URIDecoder types (#41)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
czechboy0 authored Aug 29, 2023
1 parent cb17921 commit 0769bd0
Show file tree
Hide file tree
Showing 25 changed files with 4,300 additions and 0 deletions.
9 changes: 9 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,12 @@ This product contains derivations of various scripts and templates from SwiftNIO
* https://www.apache.org/licenses/LICENSE-2.0
* HOMEPAGE:
* https://github.com/apple/swift-nio

---

This product contains coder implementations inspired by swift-http-structured-headers.

* LICENSE (Apache License 2.0):
* https://www.apache.org/licenses/LICENSE-2.0
* HOMEPAGE:
* https://github.com/apple/swift-http-structured-headers
46 changes: 46 additions & 0 deletions Sources/OpenAPIRuntime/URICoder/Common/URICodeCodingKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Foundation

/// The coding key used by the URI encoder and decoder.
struct URICoderCodingKey {

/// The string to use in a named collection (e.g. a string-keyed dictionary).
var stringValue: String

/// The value to use in an integer-indexed collection (e.g. an int-keyed
/// dictionary).
var intValue: Int?

/// Creates a new key with the same string and int value as the provided key.
/// - Parameter key: The key whose values to copy.
init(_ key: some CodingKey) {
self.stringValue = key.stringValue
self.intValue = key.intValue
}
}

extension URICoderCodingKey: CodingKey {

init(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}

init(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
}
52 changes: 52 additions & 0 deletions Sources/OpenAPIRuntime/URICoder/Common/URICoderConfiguration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Foundation

/// A bag of configuration values used by the URI encoder and decoder.
struct URICoderConfiguration {

/// A variable expansion style as described by RFC 6570 and OpenAPI 3.0.3.
enum Style {

/// A style for simple string variable expansion.
case simple

/// A style for form-based URI expansion.
case form
}

/// A character used to escape the space character.
enum SpaceEscapingCharacter: String {

/// A percent encoded value for the space character.
case percentEncoded = "%20"

/// The plus character.
case plus = "+"
}

/// The variable expansion style.
var style: Style

/// A Boolean value indicating whether the key should be repeated with
/// each value, as described by RFC 6570 and OpenAPI 3.0.3.
var explode: Bool

/// The character used to escape the space character.
var spaceEscapingCharacter: SpaceEscapingCharacter

/// The coder used for serializing the Date type.
var dateTranscoder: any DateTranscoder
}
149 changes: 149 additions & 0 deletions Sources/OpenAPIRuntime/URICoder/Common/URIEncodedNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Foundation

/// A node produced by `URIValueToNodeEncoder`.
enum URIEncodedNode: Equatable {

/// No value.
case unset

/// A single primitive value.
case primitive(Primitive)

/// An array of nodes.
case array([Self])

/// A dictionary with node values.
case dictionary([String: Self])

/// A primitive value.
enum Primitive: Equatable {

/// A boolean value.
case bool(Bool)

/// A string value.
case string(String)

/// An integer value.
case integer(Int)

/// A floating-point value.
case double(Double)

/// A date value.
case date(Date)
}
}

extension URIEncodedNode {

/// An error thrown by the methods modifying `URIEncodedNode`.
enum InsertionError: Swift.Error {

/// The encoder encoded a second primitive value.
case settingPrimitiveValueAgain

/// The encoder set a single value on a container.
case settingValueOnAContainer

/// The encoder appended to a node that wasn't an array.
case appendingToNonArrayContainer

/// The encoder inserted a value for key into a node that wasn't
/// a dictionary.
case insertingChildValueIntoNonContainer

/// The encoder added a value to an array, but the key was not a valid
/// integer key.
case insertingChildValueIntoArrayUsingNonIntValueKey
}

/// Sets the node to be a primitive node with the provided value.
/// - Parameter value: The primitive value to set into the node.
/// - Throws: If the node is already set.
mutating func set(_ value: Primitive) throws {
switch self {
case .unset:
self = .primitive(value)
case .primitive:
throw InsertionError.settingPrimitiveValueAgain
case .array, .dictionary:
throw InsertionError.settingValueOnAContainer
}
}

/// Inserts a value for a key into the node, which is interpreted as a
/// dictionary.
/// - Parameters:
/// - childValue: The value to save under the provided key.
/// - key: The key to save the value for into the dictionary.
/// - Throws: If the node is already set to be anything else but a
/// dictionary.
mutating func insert<Key: CodingKey>(
_ childValue: Self,
atKey key: Key
) throws {
switch self {
case .dictionary(var dictionary):
self = .unset
dictionary[key.stringValue] = childValue
self = .dictionary(dictionary)
case .array(var array):
// Check that this is a valid key for an unkeyed container,
// but don't actually extract the index, we only support appending
// here.
guard let intValue = key.intValue else {
throw InsertionError.insertingChildValueIntoArrayUsingNonIntValueKey
}
precondition(
intValue == array.count,
"Unkeyed container inserting at an incorrect index"
)
self = .unset
array.append(childValue)
self = .array(array)
case .unset:
if let intValue = key.intValue {
precondition(
intValue == 0,
"Unkeyed container inserting at an incorrect index"
)
self = .array([childValue])
} else {
self = .dictionary([key.stringValue: childValue])
}
default:
throw InsertionError.insertingChildValueIntoNonContainer
}
}

/// Appends a value to the array node.
/// - Parameter childValue: The node to append to the underlying array.
/// - Throws: If the node is already set to be anything else but an array.
mutating func append(_ childValue: Self) throws {
switch self {
case .array(var items):
self = .unset
items.append(childValue)
self = .array(items)
case .unset:
self = .array([childValue])
default:
throw InsertionError.appendingToNonArrayContainer
}
}
}
27 changes: 27 additions & 0 deletions Sources/OpenAPIRuntime/URICoder/Common/URIParsedNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Foundation

/// The type used for keys by `URIParser`.
typealias URIParsedKey = String.SubSequence

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

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

/// The type used for a node and a dictionary by `URIParser`.
typealias URIParsedNode = [URIParsedKey: URIParsedValueArray]
Loading

0 comments on commit 0769bd0

Please sign in to comment.