Skip to content

Introduce URIEncoder and URIDecoder types #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 a coder implementation 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
36 changes: 36 additions & 0 deletions Sources/OpenAPIRuntime/URICoder/Common/URICodeCodingKey.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//===----------------------------------------------------------------------===//
//
// 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.
struct URICoderCodingKey: CodingKey {
var stringValue: String
var intValue: Int?

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

init(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}

init(_ key: some CodingKey) {
self.stringValue = key.stringValue
self.intValue = key.intValue
}
}
72 changes: 72 additions & 0 deletions Sources/OpenAPIRuntime/URICoder/Common/URICoderConfiguration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//===----------------------------------------------------------------------===//
//
// 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 parser and serializer.
struct URICoderConfiguration {

// TODO: Wrap in a struct, as this will grow.
// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#style-values
enum Style {
case simple
case form
}

var style: Style
var explode: Bool
var spaceEscapingCharacter: String

private init(style: Style, explode: Bool, spaceEscapingCharacter: String) {
self.style = style
self.explode = explode
self.spaceEscapingCharacter = spaceEscapingCharacter
}

static let formExplode: Self = .init(
style: .form,
explode: true,
spaceEscapingCharacter: "%20"
)

static let formUnexplode: Self = .init(
style: .form,
explode: false,
spaceEscapingCharacter: "%20"
)

static let simpleExplode: Self = .init(
style: .simple,
explode: true,
spaceEscapingCharacter: "%20"
)

static let simpleUnexplode: Self = .init(
style: .simple,
explode: false,
spaceEscapingCharacter: "%20"
)

static let formDataExplode: Self = .init(
style: .form,
explode: true,
spaceEscapingCharacter: "+"
)

static let formDataUnexplode: Self = .init(
style: .form,
explode: false,
spaceEscapingCharacter: "+"
)
}
99 changes: 99 additions & 0 deletions Sources/OpenAPIRuntime/URICoder/Common/URIEncodedNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//===----------------------------------------------------------------------===//
//
// 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

enum URIEncodedNode: Equatable {

case unset
case primitive(Primitive)
case array([Self])
case dictionary([String: Self])

enum Primitive: Equatable {
case bool(Bool)
case string(String)
case integer(Int)
case double(Double)
}
}

extension URIEncodedNode {

enum InsertionError: Swift.Error {
case settingPrimitiveValueAgain
case settingValueOnAContainer
case appendingToNonArrayContainer
case insertingChildValueIntoNonContainer
case insertingChildValueIntoArrayUsingNonIntValueKey
}

mutating func set(_ value: Primitive) throws {
switch self {
case .unset:
self = .primitive(value)
case .primitive:
throw InsertionError.settingPrimitiveValueAgain
case .array, .dictionary:
throw InsertionError.settingValueOnAContainer
}
}

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 _ = key.intValue {
self = .array([childValue])
} else {
self = .dictionary([key.stringValue: childValue])
}
default:
throw InsertionError.insertingChildValueIntoNonContainer
}
}

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
}
}
}
20 changes: 20 additions & 0 deletions Sources/OpenAPIRuntime/URICoder/Common/URIParsedNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//===----------------------------------------------------------------------===//
//
// 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

typealias URIParsedKey = String.SubSequence
typealias URIParsedValue = String.SubSequence
typealias URIParsedValueArray = [URIParsedValue]
typealias URIParsedNode = [URIParsedKey: URIParsedValueArray]
53 changes: 53 additions & 0 deletions Sources/OpenAPIRuntime/URICoder/Decoding/URIDecoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//===----------------------------------------------------------------------===//
//
// 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 type that decodes a `Decodable` objects from an URI-encoded string
/// using the rules from RFC 6570, RFC 1866, and OpenAPI 3.0.3, depending on
/// the configuration.
struct URIDecoder: Sendable {

private let configuration: URICoderConfiguration

init(configuration: URICoderConfiguration) {
self.configuration = configuration
}
}

extension URIDecoder {

/// Attempt to decode an object from an URI string.
///
/// Under the hood, URIDecoder first parses the string into a URIParsedNode
/// using URIParser, and then uses URIValueFromNodeDecoder to decode
/// the Decodable value.
///
/// - Parameters:
/// - type: The type to decode.
/// - data: The URI-encoded string.
/// - Returns: The decoded value.
func decode<T: Decodable>(
_ type: T.Type = T.self,
from data: String
) throws -> T {
var parser = URIParser(configuration: configuration, data: data)
let parsedNode = try parser.parseRoot()
let decoder = URIValueFromNodeDecoder(
node: parsedNode,
explode: configuration.explode
)
return try decoder.decodeRoot()
}
}
Loading