diff --git a/Sources/RawStructuredFieldValues/ComponentTypes.swift b/Sources/RawStructuredFieldValues/ComponentTypes.swift index aafa307..75cba3f 100644 --- a/Sources/RawStructuredFieldValues/ComponentTypes.swift +++ b/Sources/RawStructuredFieldValues/ComponentTypes.swift @@ -32,6 +32,7 @@ extension ItemOrInnerList: Hashable {} /// `BareItem` is a representation of the base data types at the bottom of a structured /// header field. These types are not parameterised: they are raw data. +@available(*, deprecated, renamed: "RFC9651BareItem") public enum BareItem: Sendable { /// A boolean item. case bool(Bool) @@ -53,24 +54,28 @@ public enum BareItem: Sendable { case token(String) } +@available(*, deprecated) extension BareItem: ExpressibleByBooleanLiteral { public init(booleanLiteral value: Bool) { self = .bool(value) } } +@available(*, deprecated) extension BareItem: ExpressibleByIntegerLiteral { public init(integerLiteral value: Int) { self = .integer(value) } } +@available(*, deprecated) extension BareItem: ExpressibleByFloatLiteral { public init(floatLiteral value: Float64) { self = .decimal(.init(floatLiteral: value)) } } +@available(*, deprecated) extension BareItem: ExpressibleByStringLiteral { public init(stringLiteral value: StringLiteralType) { if value.structuredHeadersIsValidToken { @@ -81,23 +86,157 @@ extension BareItem: ExpressibleByStringLiteral { } } +@available(*, deprecated) +extension BareItem { + init(transforming newItem: RFC9651BareItem) throws { + switch newItem { + case .bool(let b): + self = .bool(b) + + case .integer(let i): + self = .integer(i) + + case .decimal(let d): + self = .decimal(d) + + case .string(let s): + self = .string(s) + + case .undecodedByteSequence(let s): + self = .undecodedByteSequence(s) + + case .token(let t): + self = .token(t) + } + } +} + +@available(*, deprecated) extension BareItem: Hashable {} +/// `RFC9651BareItem` is a representation of the base data types at the bottom of a structured +/// header field. These types are not parameterised: they are raw data. +public enum RFC9651BareItem: Sendable { + /// A boolean item. + case bool(Bool) + + /// An integer item. + case integer(Int) + + /// A decimal item. + case decimal(PseudoDecimal) + + /// A string item. + case string(String) + + /// A byte sequence. This case must contain base64-encoded data, as + /// `StructuredHeaders` does not do base64 encoding or decoding. + case undecodedByteSequence(String) + + /// A token item. + case token(String) +} + +extension RFC9651BareItem: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = .bool(value) + } +} + +extension RFC9651BareItem: ExpressibleByIntegerLiteral { + public init(integerLiteral value: Int) { + self = .integer(value) + } +} + +extension RFC9651BareItem: ExpressibleByFloatLiteral { + public init(floatLiteral value: Float64) { + self = .decimal(.init(floatLiteral: value)) + } +} + +extension RFC9651BareItem: ExpressibleByStringLiteral { + public init(stringLiteral value: StringLiteralType) { + if value.structuredHeadersIsValidToken { + self = .token(value) + } else { + self = .string(value) + } + } +} + +extension RFC9651BareItem { + @available(*, deprecated) + init(transforming oldItem: BareItem) throws { + switch oldItem { + case .bool(let b): + self = .bool(b) + + case .integer(let i): + self = .integer(i) + + case .decimal(let d): + self = .decimal(d) + + case .string(let s): + self = .string(s) + + case .undecodedByteSequence(let s): + self = .undecodedByteSequence(s) + + case .token(let t): + self = .token(t) + } + } +} + +extension RFC9651BareItem: Hashable {} + // MARK: - Item /// `Item` represents a structured header field item: a combination of a `bareItem` /// and some parameters. public struct Item: Sendable { /// The `BareItem` that this `Item` contains. - public var bareItem: BareItem + @available(*, deprecated, renamed: "rfc9651BareItem") + public var bareItem: BareItem { + get { + try! .init(transforming: self.rfc9651BareItem) + } + set { + try! self.rfc9651BareItem = .init(transforming: newValue) + } + } /// The parameters associated with `bareItem` - public var parameters: OrderedMap + @available(*, deprecated, renamed: "rfc9651Parameters") + public var parameters: OrderedMap { + get { + try! self.rfc9651Parameters.mapValues { try .init(transforming: $0) } + } + set { + try! self.rfc9651Parameters = newValue.mapValues { try .init(transforming: $0) } + } + } + + /// The `BareItem` that this `Item` contains. + public var rfc9651BareItem: RFC9651BareItem + /// The parameters associated with `rfc9651BareItem` + public var rfc9651Parameters: OrderedMap + + @available(*, deprecated) public init(bareItem: BareItem, parameters: OrderedMap) { + self.rfc9651BareItem = .integer(1) + self.rfc9651Parameters = OrderedMap() self.bareItem = bareItem self.parameters = parameters } + + public init(bareItem: RFC9651BareItem, parameters: OrderedMap) { + self.rfc9651BareItem = bareItem + self.rfc9651Parameters = parameters + } } extension Item: Hashable {} @@ -184,12 +323,30 @@ public struct InnerList: Hashable, Sendable { public var bareInnerList: BareInnerList /// The parameters associated with the `bareInnerList`. - public var parameters: OrderedMap + @available(*, deprecated, renamed: "rfc9651Parameters") + public var parameters: OrderedMap { + get { + try! self.rfc9651Parameters.mapValues { try .init(transforming: $0) } + } + set { + try! self.rfc9651Parameters = newValue.mapValues { try .init(transforming: $0) } + } + } + /// The parameters associated with the `bareInnerList`. + public var rfc9651Parameters: OrderedMap + + @available(*, deprecated) public init(bareInnerList: BareInnerList, parameters: OrderedMap) { + self.rfc9651Parameters = OrderedMap() self.bareInnerList = bareInnerList self.parameters = parameters } + + public init(bareInnerList: BareInnerList, parameters: OrderedMap) { + self.bareInnerList = bareInnerList + self.rfc9651Parameters = parameters + } } extension String { diff --git a/Sources/RawStructuredFieldValues/FieldParser.swift b/Sources/RawStructuredFieldValues/FieldParser.swift index 043dbbe..375b5d9 100644 --- a/Sources/RawStructuredFieldValues/FieldParser.swift +++ b/Sources/RawStructuredFieldValues/FieldParser.swift @@ -29,7 +29,9 @@ extension StructuredFieldValueParser: Sendable where BaseData: Sendable, BaseDat extension StructuredFieldValueParser { // Helper typealiases to avoid the explosion of generic parameters + @available(*, deprecated, renamed: "RFC9651BareItem") public typealias BareItem = RawStructuredFieldValues.BareItem + public typealias RFC9651BareItem = RawStructuredFieldValues.RFC9651BareItem public typealias Item = RawStructuredFieldValues.Item public typealias BareInnerList = RawStructuredFieldValues.BareInnerList public typealias InnerList = RawStructuredFieldValues.InnerList @@ -204,7 +206,7 @@ extension StructuredFieldValueParser { return Item(bareItem: bareItem, parameters: parameters) } - private mutating func _parseABareItem() throws -> BareItem { + private mutating func _parseABareItem() throws -> RFC9651BareItem { guard let first = self.underlyingData.first else { throw StructuredHeaderError.invalidItem } @@ -225,7 +227,7 @@ extension StructuredFieldValueParser { } } - private mutating func _parseAnIntegerOrDecimal() throws -> BareItem { + private mutating func _parseAnIntegerOrDecimal() throws -> RFC9651BareItem { var sign = 1 var type = IntegerOrDecimal.integer @@ -301,7 +303,7 @@ extension StructuredFieldValueParser { } } - private mutating func _parseAString() throws -> BareItem { + private mutating func _parseAString() throws -> RFC9651BareItem { assert(self.underlyingData.first == asciiDquote) self.underlyingData.consumeFirst() @@ -376,7 +378,7 @@ extension StructuredFieldValueParser { } } - private mutating func _parseAByteSequence() throws -> BareItem { + private mutating func _parseAByteSequence() throws -> RFC9651BareItem { assert(self.underlyingData.first == asciiColon) self.underlyingData.consumeFirst() @@ -406,7 +408,7 @@ extension StructuredFieldValueParser { throw StructuredHeaderError.invalidByteSequence } - private mutating func _parseABoolean() throws -> BareItem { + private mutating func _parseABoolean() throws -> RFC9651BareItem { assert(self.underlyingData.first == asciiQuestionMark) self.underlyingData.consumeFirst() @@ -423,7 +425,7 @@ extension StructuredFieldValueParser { } } - private mutating func _parseAToken() throws -> BareItem { + private mutating func _parseAToken() throws -> RFC9651BareItem { assert(asciiCapitals.contains(self.underlyingData.first!) || asciiLowercases.contains(self.underlyingData.first!) || self.underlyingData.first! == asciiAsterisk) var index = self.underlyingData.startIndex @@ -457,8 +459,8 @@ extension StructuredFieldValueParser { return .token(String(decoding: tokenSlice, as: UTF8.self)) } - private mutating func _parseParameters() throws -> OrderedMap { - var parameters = OrderedMap() + private mutating func _parseParameters() throws -> OrderedMap { + var parameters = OrderedMap() // We want to loop while we still have bytes _and_ while the first character is asciiSemicolon. // This covers both. @@ -467,7 +469,7 @@ extension StructuredFieldValueParser { self.underlyingData.consumeFirst() self.underlyingData.stripLeadingSpaces() let paramName = try self._parseAKey() - var paramValue: BareItem = true + var paramValue: RFC9651BareItem = true if self.underlyingData.first == asciiEqual { self.underlyingData.consumeFirst() diff --git a/Sources/RawStructuredFieldValues/FieldSerializer.swift b/Sources/RawStructuredFieldValues/FieldSerializer.swift index 0ebf48d..4d89e31 100644 --- a/Sources/RawStructuredFieldValues/FieldSerializer.swift +++ b/Sources/RawStructuredFieldValues/FieldSerializer.swift @@ -80,8 +80,8 @@ extension StructuredFieldValueSerializer { for (name, value) in dictionary { try self.serializeAKey(name) - if case .item(let item) = value, case .bool(true) = item.bareItem { - try self.serializeParameters(item.parameters) + if case .item(let item) = value, case .bool(true) = item.rfc9651BareItem { + try self.serializeParameters(item.rfc9651Parameters) } else { self.data.append(asciiEqual) @@ -137,15 +137,15 @@ extension StructuredFieldValueSerializer { self.data.append(asciiCloseParenthesis) - try self.serializeParameters(innerList.parameters) + try self.serializeParameters(innerList.rfc9651Parameters) } private mutating func serializeAnItem(_ item: Item) throws { - try self.serializeABareItem(item.bareItem) - try self.serializeParameters(item.parameters) + try self.serializeABareItem(item.rfc9651BareItem) + try self.serializeParameters(item.rfc9651Parameters) } - private mutating func serializeParameters(_ parameters: OrderedMap) throws { + private mutating func serializeParameters(_ parameters: OrderedMap) throws { for (key, value) in parameters { self.data.append(asciiSemicolon) try self.serializeAKey(key) @@ -166,7 +166,7 @@ extension StructuredFieldValueSerializer { self.data.append(contentsOf: key.utf8) } - private mutating func serializeABareItem(_ item: BareItem) throws { + private mutating func serializeABareItem(_ item: RFC9651BareItem) throws { switch item { case .integer(let int): guard let wideInt = Int64(exactly: int), validIntegerRange.contains(wideInt) else { diff --git a/Sources/RawStructuredFieldValues/OrderedMap.swift b/Sources/RawStructuredFieldValues/OrderedMap.swift index a92242a..fda00d5 100644 --- a/Sources/RawStructuredFieldValues/OrderedMap.swift +++ b/Sources/RawStructuredFieldValues/OrderedMap.swift @@ -49,6 +49,12 @@ public struct OrderedMap where Key: Hashable { } } } + + func mapValues(_ body: (Value) throws -> NewValue) rethrows -> OrderedMap { + var returnValue = OrderedMap() + returnValue.backing = try self.backing.map { try .init(key: $0.key, value: body($0.value)) } + return returnValue + } } // MARK: - Helper struct for storing elements diff --git a/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift b/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift index 2ed664e..08f139b 100644 --- a/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift @@ -15,11 +15,11 @@ import Foundation import RawStructuredFieldValues struct BareItemDecoder { - private var item: BareItem + private var item: RFC9651BareItem private var _codingPath: [_StructuredHeaderCodingKey] - init(_ item: BareItem, codingPath: [_StructuredHeaderCodingKey]) { + init(_ item: RFC9651BareItem, codingPath: [_StructuredHeaderCodingKey]) { self.item = item self._codingPath = codingPath } diff --git a/Sources/StructuredFieldValues/Decoder/ParametersDecoder.swift b/Sources/StructuredFieldValues/Decoder/ParametersDecoder.swift index 50aca40..19bdc0c 100644 --- a/Sources/StructuredFieldValues/Decoder/ParametersDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/ParametersDecoder.swift @@ -15,11 +15,11 @@ import Foundation import RawStructuredFieldValues struct ParametersDecoder where BaseData.Element == UInt8 { - private var parameters: OrderedMap + private var parameters: OrderedMap private var decoder: _StructuredFieldDecoder - init(_ parameters: OrderedMap, decoder: _StructuredFieldDecoder) { + init(_ parameters: OrderedMap, decoder: _StructuredFieldDecoder) { self.parameters = parameters self.decoder = decoder } diff --git a/Sources/StructuredFieldValues/Decoder/StructuredFieldValueDecoder.swift b/Sources/StructuredFieldValues/Decoder/StructuredFieldValueDecoder.swift index e11e1da..91a076c 100644 --- a/Sources/StructuredFieldValues/Decoder/StructuredFieldValueDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/StructuredFieldValueDecoder.swift @@ -184,7 +184,7 @@ extension _StructuredFieldDecoder: Decoder { // We have single value containers for items and bareItems. switch self.currentElement! { case .item(let item): - return BareItemDecoder(item.bareItem, codingPath: self._codingStack.map { $0.key }) + return BareItemDecoder(item.rfc9651BareItem, codingPath: self._codingStack.map { $0.key }) case .bareItem(let bareItem): return BareItemDecoder(bareItem, codingPath: self._codingStack.map { $0.key }) case .dictionary, .list, .innerList, .bareInnerList, .parameters: @@ -224,9 +224,9 @@ extension _StructuredFieldDecoder { case list([ItemOrInnerList]) case item(Item) case innerList(InnerList) - case bareItem(BareItem) + case bareItem(RFC9651BareItem) case bareInnerList(BareInnerList) - case parameters(OrderedMap) + case parameters(OrderedMap) func innerElement(for key: _StructuredHeaderCodingKey) throws -> Element { switch self { @@ -263,9 +263,9 @@ extension _StructuredFieldDecoder { // Two keys, "item" and "parameters". switch key.stringValue { case "item": - return .bareItem(item.bareItem) + return .bareItem(item.rfc9651BareItem) case "parameters": - return .parameters(item.parameters) + return .parameters(item.rfc9651Parameters) default: throw StructuredHeaderError.invalidTypeForItem } @@ -280,7 +280,7 @@ extension _StructuredFieldDecoder { case "items": return .bareInnerList(innerList.bareInnerList) case "parameters": - return .parameters(innerList.parameters) + return .parameters(innerList.rfc9651Parameters) default: throw StructuredHeaderError.invalidTypeForItem } diff --git a/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift b/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift index 505e225..59a881e 100644 --- a/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift +++ b/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift @@ -766,14 +766,14 @@ extension _StructuredFieldEncoder { case innerList(InnerList) case item(PartialItem) case bareInnerList(BareInnerList) - case parameters(OrderedMap) - case itemOrInnerList(OrderedMap) + case parameters(OrderedMap) + case itemOrInnerList(OrderedMap) /// A helper struct used to tolerate the fact that we need partial items, /// but our `Item` struct doesn't like that much. struct PartialItem { - var bareItem: BareItem? - var parameters: OrderedMap + var bareItem: RFC9651BareItem? + var parameters: OrderedMap } /// This is called when a complete object has been built. @@ -787,7 +787,7 @@ extension _StructuredFieldEncoder { self = .item(item) case (.innerList(var list), .parameters(let params)) where key.stringValue == "parameters": - list.parameters = params + list.rfc9651Parameters = params self = .innerList(list) case (.innerList(var list), .bareInnerList(let bare)) where key.stringValue == "items": @@ -846,7 +846,7 @@ extension _StructuredFieldEncoder { /// /// If the key is missing we will require the type to be `item`, in which case /// this will be for the "item" key. - mutating func insertBareItem(_ bareItem: BareItem, atKey key: String? = nil) throws { + mutating func insertBareItem(_ bareItem: RFC9651BareItem, atKey key: String? = nil) throws { switch self { case .itemHeader: guard key == nil || key == "item" else { @@ -895,7 +895,7 @@ extension _StructuredFieldEncoder { /// Appends a bare item to the given container. This must be a list-type /// container that stores either bare items, or items. - mutating func appendBareItem(_ bareItem: BareItem) throws { + mutating func appendBareItem(_ bareItem: RFC9651BareItem) throws { switch self { case .listHeader: self = .list([.item(Item(bareItem: bareItem, parameters: [:]))]) diff --git a/Sources/sh-parser/main.swift b/Sources/sh-parser/main.swift index eb783bd..d897d94 100644 --- a/Sources/sh-parser/main.swift +++ b/Sources/sh-parser/main.swift @@ -97,7 +97,7 @@ extension OrderedMap where Key == String, Value == ItemOrInnerList { } } -extension OrderedMap where Key == String, Value == BareItem { +extension OrderedMap where Key == String, Value == RFC9651BareItem { func prettyPrint(depth: Int) { let tabs = String(repeating: "\t", count: depth) @@ -122,9 +122,9 @@ extension Item { func prettyPrint(depth: Int) { let tabs = String(repeating: "\t", count: depth) - print("\(tabs)- item: \(self.bareItem.prettyFormat())") - print("\(tabs)- parameters (\(parameters.count) entries):") - self.parameters.prettyPrint(depth: depth + 1) + print("\(tabs)- item: \(self.rfc9651BareItem.prettyFormat())") + print("\(tabs)- parameters (\(rfc9651Parameters.count) entries):") + self.rfc9651Parameters.prettyPrint(depth: depth + 1) } } @@ -132,10 +132,10 @@ extension InnerList { func prettyPrint(depth: Int) { let tabs = String(repeating: "\t", count: depth) - print("\(tabs)- innerList (\(parameters.count) entries):") + print("\(tabs)- innerList (\(rfc9651Parameters.count) entries):") self.bareInnerList.prettyPrint(depth: depth + 1) - print("\(tabs)- parameters (\(parameters.count) entries):") - self.parameters.prettyPrint(depth: depth + 1) + print("\(tabs)- parameters (\(rfc9651Parameters.count) entries):") + self.rfc9651Parameters.prettyPrint(depth: depth + 1) } } @@ -149,7 +149,7 @@ extension BareInnerList { } } -extension BareItem { +extension RFC9651BareItem { func prettyFormat() -> String { switch self { case .bool(let bool): diff --git a/Tests/StructuredFieldValuesTests/StructuredFieldParserTests.swift b/Tests/StructuredFieldValuesTests/StructuredFieldParserTests.swift index 1c18482..6921d20 100644 --- a/Tests/StructuredFieldValuesTests/StructuredFieldParserTests.swift +++ b/Tests/StructuredFieldValuesTests/StructuredFieldParserTests.swift @@ -26,7 +26,7 @@ final class StructuredFieldParserTests: XCTestCase { case item(Item) } - private func _validateBareItem(_ bareItem: BareItem, against schema: JSONSchema, fixtureName: String) throws { + private func _validateBareItem(_ bareItem: RFC9651BareItem, against schema: JSONSchema, fixtureName: String) throws { switch (bareItem, schema) { case (.integer(let baseInt), .integer(let jsonInt)): XCTAssertEqual(baseInt, jsonInt, "\(fixtureName): Got \(bareItem), expected \(schema)") @@ -65,7 +65,7 @@ final class StructuredFieldParserTests: XCTestCase { } } - private func _validateParameters(_ parameters: OrderedMap, against schema: JSONSchema, fixtureName: String) throws { + private func _validateParameters(_ parameters: OrderedMap, against schema: JSONSchema, fixtureName: String) throws { guard case .dictionary(let expectedParameters) = schema else { XCTFail("\(fixtureName): Expected parameters to be a JSON dictionary, but got \(schema)") return @@ -88,10 +88,10 @@ final class StructuredFieldParserTests: XCTestCase { } // First, match the item. - try self._validateBareItem(item.bareItem, against: arrayElements.first!, fixtureName: fixtureName) + try self._validateBareItem(item.rfc9651BareItem, against: arrayElements.first!, fixtureName: fixtureName) // Now the parameters. - try self._validateParameters(item.parameters, against: arrayElements.last!, fixtureName: fixtureName) + try self._validateParameters(item.rfc9651Parameters, against: arrayElements.last!, fixtureName: fixtureName) } private func _validateInnerList(_ innerList: InnerList, against schema: JSONSchema, fixtureName: String) throws { @@ -108,7 +108,7 @@ final class StructuredFieldParserTests: XCTestCase { try self._validateItem(actualItem, against: expectedItem, fixtureName: fixtureName) } - try self._validateParameters(innerList.parameters, against: expectedParameters, fixtureName: fixtureName) + try self._validateParameters(innerList.rfc9651Parameters, against: expectedParameters, fixtureName: fixtureName) } private func _validateList(_ result: [ItemOrInnerList], against schema: JSONSchema, fixtureName: String) throws { diff --git a/Tests/StructuredFieldValuesTests/StructuredFieldSerializerTests.swift b/Tests/StructuredFieldValuesTests/StructuredFieldSerializerTests.swift index 959cc8b..2be9165 100644 --- a/Tests/StructuredFieldValuesTests/StructuredFieldSerializerTests.swift +++ b/Tests/StructuredFieldValuesTests/StructuredFieldSerializerTests.swift @@ -176,11 +176,11 @@ extension Item { fatalError("Invalid item: \(schema)") } - self.init(bareItem: try BareItem(first), parameters: try OrderedMap(parameters: last)) + self.init(bareItem: try RFC9651BareItem(first), parameters: try OrderedMap(parameters: last)) } } -extension BareItem { +extension RFC9651BareItem { init(_ schema: JSONSchema) throws { switch schema { case .integer(let int): @@ -233,7 +233,7 @@ extension BareInnerList { } } -extension OrderedMap where Key == String, Value == BareItem { +extension OrderedMap where Key == String, Value == RFC9651BareItem { init(parameters: JSONSchema) throws { guard case .dictionary(let jsonDict) = parameters else { fatalError("Invalid format for parameters: \(parameters)") @@ -242,7 +242,7 @@ extension OrderedMap where Key == String, Value == BareItem { self.init() for (name, value) in jsonDict { - self[name] = try BareItem(value) + self[name] = try RFC9651BareItem(value) } } }