Daniel Eggert a169cebb45
URI Templating (#1198)
* Initial implementation.

* Cleanup and fix normalizedAddingPercentEncoding()

* Use failing initializer on URL instead of makeURL()

* Add standard copyright header

* Mark helper functions as fileprivate

* URL.Template.init() needs to be failable

* Rename InvalidTemplateExpression → URL.Template.InvalidExpression

* Add missing @available

* Update copyright header

* Convert tests XCTest → Swift Testing

* Use UInt8.isValidHexDigit

* Use URLParser.swift methods for unreserved + reserved characters

* Cleanup normalizedAddingPercentEncoding()

* Use String(decoding:as:)

* guard & white-space

* Cleanup “defer”

* Rename files URI… → URL…

* Add new files to CMakeLists.txt

* Add benchmarks.

* Add missing @available, 2

* Fix doc comment.

* Remove ExpressibleByStringLiteral conformance for URL.Template.VariableName

* Improve documentation comments

* Fix for  7b14d0bc62

* Fix doc comment

* Do not force unwrap “maximum length”
2025-04-21 16:01:10 -07:00

110 lines
3.5 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#if canImport(CollectionsInternal)
internal import CollectionsInternal
#elseif canImport(OrderedCollections)
internal import OrderedCollections
#elseif canImport(_FoundationCollections)
internal import _FoundationCollections
#endif
extension URL.Template {
/// The value of a variable used for expanding a template.
///
/// A value can either be some text, a list, or an associate list (a dictionary).
///
/// ### Examples
/// ```swift
/// let hello: URL.Template.Value = .text("Hello World!")
/// let list: URL.Template.Value = .list(["red", "green", "blue"])
/// let keys: URL.Template.Value = .associativeList([
/// "semi": ";",
/// "dot": ".",
/// "comma": ",",
/// ])
/// ```
/// Alternatively, for constants, the `ExpressibleByLiteral` implementations
/// can be used, i.e.
/// ```swift
/// let hello: URL.Template.Value = "Hello World!"
/// let list: URL.Template.Value = ["red", "green", "blue"]
/// let keys: URL.Template.Value = [
/// "semi": ";",
/// "dot": ".",
/// "comma": ",",
/// ]
/// ```
public struct Value: Sendable, Hashable {
let underlying: Underlying
}
}
extension URL.Template.Value {
/// A text value to be used with a ``URL.Template``.
public static func text(_ text: String) -> URL.Template.Value {
URL.Template.Value(underlying: .text(text))
}
/// A list value (an array of `String`s) to be used with a ``URL.Template``.
public static func list(_ list: some Sequence<String>) -> URL.Template.Value {
URL.Template.Value(underlying: .list(Array(list)))
}
/// An associative list value (ordered key-value pairs) to be used with a ``URL.Template``.
public static func associativeList(_ list: some Sequence<(key: String, value: String)>) -> URL.Template.Value {
URL.Template.Value(underlying: .associativeList(OrderedDictionary(uniqueKeysWithValues: list)))
}
}
// MARK: -
extension URL.Template.Value: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self = .text(value)
}
}
extension URL.Template.Value: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: String...) {
self.init(underlying: .list(elements))
}
}
extension URL.Template.Value: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, String)...) {
self.init(underlying: .associativeList(OrderedDictionary(uniqueKeysWithValues: elements)))
}
}
// MARK: -
extension URL.Template.Value: CustomStringConvertible {
public var description: String {
switch underlying {
case .text(let v): return v
case .list(let v): return "\(v)"
case .associativeList(let v): return "\(v)"
}
}
}
// MARK: -
extension URL.Template.Value {
enum Underlying: Sendable, Hashable {
case text(String)
case list([String])
case associativeList(OrderedDictionary<String, String>)
}
}