From ad0ec039fccd668c1988bd2451fc1ac4482ed5a4 Mon Sep 17 00:00:00 2001 From: cnkwocha Date: Fri, 29 Nov 2024 11:20:20 +0000 Subject: [PATCH 1/3] Add support for Display String type to StructuredFieldValues Motivation: The parser and serializer for the Display String type have been implemented. The high-level encoder and decoder should follow. Modifications: Implement the encoder and decoder for Display String. Result: The StructuredFieldValues module will support the Display String type. --- .../Decoder/BareInnerListDecoder.swift | 12 +- .../Decoder/BareItemDecoder.swift | 10 ++ .../Decoder/DictionaryKeyedContainer.swift | 12 +- .../Decoder/KeyedInnerListDecoder.swift | 12 +- .../Decoder/KeyedItemDecoder.swift | 12 +- .../Decoder/KeyedTopLevelListDecoder.swift | 12 +- .../Decoder/ParametersDecoder.swift | 12 +- .../Decoder/StructuredFieldValueDecoder.swift | 12 +- .../Decoder/TopLevelListDecoder.swift | 12 +- .../StructuredFieldValues/DisplayString.swift | 27 ++++ .../Encoder/StructuredFieldValueEncoder.swift | 43 ++++-- .../StructuredFieldDecoderTests.swift | 137 +++++++++++++++++- .../StructuredFieldEncoderTests.swift | 22 +++ 13 files changed, 290 insertions(+), 45 deletions(-) create mode 100644 Sources/StructuredFieldValues/DisplayString.swift diff --git a/Sources/StructuredFieldValues/Decoder/BareInnerListDecoder.swift b/Sources/StructuredFieldValues/Decoder/BareInnerListDecoder.swift index 5eb4d25..8de16f0 100644 --- a/Sources/StructuredFieldValues/Decoder/BareInnerListDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/BareInnerListDecoder.swift @@ -66,16 +66,20 @@ extension BareInnerListDecoder: UnkeyedDecodingContainer { self.decoder.pop() } - if type is Data.Type { + switch type { + case is Data.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Data.self) as! T - } else if type is Decimal.Type { + case is Decimal.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Decimal.self) as! T - } else if type is Date.Type { + case is Date.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Date.self) as! T - } else { + case is DisplayString.Type: + let container = try self.decoder.singleValueContainer() + return try container.decode(DisplayString.self) as! T + default: return try type.init(from: self.decoder) } } diff --git a/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift b/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift index 3136010..cd4e2aa 100644 --- a/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift @@ -141,6 +141,14 @@ extension BareItemDecoder: SingleValueDecodingContainer { return Date(timeIntervalSince1970: Double(date)) } + func decode(_: DisplayString.Type) throws -> DisplayString { + guard case .displayString(let string) = self.item else { + throw StructuredHeaderError.invalidTypeForItem + } + + return DisplayString(string) + } + func decodeNil() -> Bool { // Items are never nil. false @@ -182,6 +190,8 @@ extension BareItemDecoder: SingleValueDecodingContainer { return try self.decode(Decimal.self) as! T case is Date.Type: return try self.decode(Date.self) as! T + case is DisplayString.Type: + return try self.decode(DisplayString.self) as! T default: throw StructuredHeaderError.invalidTypeForItem } diff --git a/Sources/StructuredFieldValues/Decoder/DictionaryKeyedContainer.swift b/Sources/StructuredFieldValues/Decoder/DictionaryKeyedContainer.swift index 20b1860..42f7efb 100644 --- a/Sources/StructuredFieldValues/Decoder/DictionaryKeyedContainer.swift +++ b/Sources/StructuredFieldValues/Decoder/DictionaryKeyedContainer.swift @@ -49,16 +49,20 @@ extension DictionaryKeyedContainer: KeyedDecodingContainerProtocol { self.decoder.pop() } - if type is Data.Type { + switch type { + case is Data.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Data.self) as! T - } else if type is Decimal.Type { + case is Decimal.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Decimal.self) as! T - } else if type is Date.Type { + case is Date.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Date.self) as! T - } else { + case is DisplayString.Type: + let container = try self.decoder.singleValueContainer() + return try container.decode(DisplayString.self) as! T + default: return try type.init(from: self.decoder) } } diff --git a/Sources/StructuredFieldValues/Decoder/KeyedInnerListDecoder.swift b/Sources/StructuredFieldValues/Decoder/KeyedInnerListDecoder.swift index 5f4cb09..9f28e81 100644 --- a/Sources/StructuredFieldValues/Decoder/KeyedInnerListDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/KeyedInnerListDecoder.swift @@ -54,16 +54,20 @@ extension KeyedInnerListDecoder: KeyedDecodingContainerProtocol { self.decoder.pop() } - if type is Data.Type { + switch type { + case is Data.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Data.self) as! T - } else if type is Decimal.Type { + case is Decimal.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Decimal.self) as! T - } else if type is Date.Type { + case is Date.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Date.self) as! T - } else { + case is DisplayString.Type: + let container = try self.decoder.singleValueContainer() + return try container.decode(DisplayString.self) as! T + default: return try type.init(from: self.decoder) } } diff --git a/Sources/StructuredFieldValues/Decoder/KeyedItemDecoder.swift b/Sources/StructuredFieldValues/Decoder/KeyedItemDecoder.swift index b46b197..ca50611 100644 --- a/Sources/StructuredFieldValues/Decoder/KeyedItemDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/KeyedItemDecoder.swift @@ -54,16 +54,20 @@ extension KeyedItemDecoder: KeyedDecodingContainerProtocol { self.decoder.pop() } - if type is Data.Type { + switch type { + case is Data.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Data.self) as! T - } else if type is Decimal.Type { + case is Decimal.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Decimal.self) as! T - } else if type is Date.Type { + case is Date.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Date.self) as! T - } else { + case is DisplayString.Type: + let container = try self.decoder.singleValueContainer() + return try container.decode(DisplayString.self) as! T + default: return try type.init(from: self.decoder) } } diff --git a/Sources/StructuredFieldValues/Decoder/KeyedTopLevelListDecoder.swift b/Sources/StructuredFieldValues/Decoder/KeyedTopLevelListDecoder.swift index af2c781..811b65f 100644 --- a/Sources/StructuredFieldValues/Decoder/KeyedTopLevelListDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/KeyedTopLevelListDecoder.swift @@ -54,16 +54,20 @@ extension KeyedTopLevelListDecoder: KeyedDecodingContainerProtocol { self.decoder.pop() } - if type is Data.Type { + switch type { + case is Data.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Data.self) as! T - } else if type is Decimal.Type { + case is Decimal.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Decimal.self) as! T - } else if type is Date.Type { + case is Date.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Date.self) as! T - } else { + case is DisplayString.Type: + let container = try self.decoder.singleValueContainer() + return try container.decode(DisplayString.self) as! T + default: return try type.init(from: self.decoder) } } diff --git a/Sources/StructuredFieldValues/Decoder/ParametersDecoder.swift b/Sources/StructuredFieldValues/Decoder/ParametersDecoder.swift index 10d35b7..80976a5 100644 --- a/Sources/StructuredFieldValues/Decoder/ParametersDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/ParametersDecoder.swift @@ -49,16 +49,20 @@ extension ParametersDecoder: KeyedDecodingContainerProtocol { self.decoder.pop() } - if type is Data.Type { + switch type { + case is Data.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Data.self) as! T - } else if type is Decimal.Type { + case is Decimal.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Decimal.self) as! T - } else if type is Date.Type { + case is Date.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Date.self) as! T - } else { + case is DisplayString.Type: + let container = try self.decoder.singleValueContainer() + return try container.decode(DisplayString.self) as! T + default: return try type.init(from: self.decoder) } } diff --git a/Sources/StructuredFieldValues/Decoder/StructuredFieldValueDecoder.swift b/Sources/StructuredFieldValues/Decoder/StructuredFieldValueDecoder.swift index 87d9b69..c9d8861 100644 --- a/Sources/StructuredFieldValues/Decoder/StructuredFieldValueDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/StructuredFieldValueDecoder.swift @@ -111,16 +111,20 @@ extension StructuredFieldValueDecoder { // An escape hatch here for top-level data: if we don't do this, it'll ask for // an unkeyed container and get very confused. - if type is Data.Type { + switch type { + case is Data.Type: let container = try decoder.singleValueContainer() return try container.decode(Data.self) as! StructuredField - } else if type is Decimal.Type { + case is Decimal.Type: let container = try decoder.singleValueContainer() return try container.decode(Decimal.self) as! StructuredField - } else if type is Date.Type { + case is Date.Type: let container = try decoder.singleValueContainer() return try container.decode(Date.self) as! StructuredField - } else { + case is DisplayString.Type: + let container = try decoder.singleValueContainer() + return try container.decode(DisplayString.self) as! StructuredField + default: return try type.init(from: decoder) } } diff --git a/Sources/StructuredFieldValues/Decoder/TopLevelListDecoder.swift b/Sources/StructuredFieldValues/Decoder/TopLevelListDecoder.swift index 83fd430..b7399c7 100644 --- a/Sources/StructuredFieldValues/Decoder/TopLevelListDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/TopLevelListDecoder.swift @@ -66,16 +66,20 @@ extension TopLevelListDecoder: UnkeyedDecodingContainer { self.decoder.pop() } - if type is Data.Type { + switch type { + case is Data.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Data.self) as! T - } else if type is Decimal.Type { + case is Decimal.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Decimal.self) as! T - } else if type is Date.Type { + case is Date.Type: let container = try self.decoder.singleValueContainer() return try container.decode(Date.self) as! T - } else { + case is DisplayString.Type: + let container = try self.decoder.singleValueContainer() + return try container.decode(DisplayString.self) as! T + default: return try type.init(from: self.decoder) } } diff --git a/Sources/StructuredFieldValues/DisplayString.swift b/Sources/StructuredFieldValues/DisplayString.swift new file mode 100644 index 0000000..2ee2841 --- /dev/null +++ b/Sources/StructuredFieldValues/DisplayString.swift @@ -0,0 +1,27 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2020-2024 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// A type that represents the Display String Structured Type. +public struct DisplayString: Codable, Equatable { + /// The value of this Display String. + public private(set) var description: String + + /// Initializes a new Display String. + /// + /// - parameters: + /// - description: The value of this Display String. + public init(_ description: String) { + self.description = description + } +} diff --git a/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift b/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift index 3ab1ae1..ca1aaf1 100644 --- a/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift +++ b/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift @@ -150,19 +150,20 @@ class _StructuredFieldEncoder { fileprivate func encodeItemField(_ data: StructuredField) throws -> [UInt8] { self.push(key: .init(stringValue: ""), newStorage: .itemHeader) - // There's an awkward special hook here: if the outer type is `Data` or `Decimal`, - // we skip the regular encoding path. This is because otherwise `Data` will - // ask for an unkeyed container and `Decimal` for a keyed one, - // and it all falls apart. + // There's an awkward special hook here: if the outer type is `Data`, `Decimal`, `Date` or + // `DisplayString`, we skip the regular encoding path. // // Everything else goes through the normal flow. - if let value = data as? Data { - try self.encode(value) - } else if let value = data as? Decimal { - try self.encode(value) - } else if let value = data as? Date { - try self.encode(value) - } else { + switch data { + case is Data: + try self.encode(data) + case is Decimal: + try self.encode(data) + case is Date: + try self.encode(data) + case is DisplayString: + try self.encode(data) + default: try data.encode(to: self) } @@ -316,6 +317,10 @@ extension _StructuredFieldEncoder: SingleValueEncodingContainer { try self.currentStackEntry.storage.insertBareItem(.date(date)) } + func encode(_ data: DisplayString) throws { + try self.currentStackEntry.storage.insertBareItem(.displayString(data.description)) + } + func encode(_ value: T) throws where T: Encodable { switch value { case let value as UInt8: @@ -352,6 +357,8 @@ extension _StructuredFieldEncoder: SingleValueEncodingContainer { try self.encode(value) case let value as Date: try self.encode(value) + case let value as DisplayString: + try self.encode(value) default: throw StructuredHeaderError.invalidTypeForItem } @@ -480,6 +487,10 @@ extension _StructuredFieldEncoder { try self.currentStackEntry.storage.appendBareItem(.date(date)) } + func append(_ value: DisplayString) throws { + try self.currentStackEntry.storage.appendBareItem(.displayString(value.description)) + } + func append(_ value: T) throws where T: Encodable { switch value { case let value as UInt8: @@ -516,6 +527,8 @@ extension _StructuredFieldEncoder { try self.append(value) case let value as Date: try self.append(value) + case let value as DisplayString: + try self.append(value) default: // Some other codable type. switch self.currentStackEntry.storage { @@ -658,6 +671,12 @@ extension _StructuredFieldEncoder { try self.currentStackEntry.storage.insertBareItem(.date(date), atKey: key) } + func encode(_ value: DisplayString, forKey key: String) throws { + let key = self.sanitizeKey(key) + let displayString = value.description + try self.currentStackEntry.storage.insertBareItem(.displayString(displayString), atKey: key) + } + func encode(_ value: T, forKey key: String) throws where T: Encodable { let key = self.sanitizeKey(key) @@ -696,6 +715,8 @@ extension _StructuredFieldEncoder { try self.encode(value, forKey: key) case let value as Date: try self.encode(value, forKey: key) + case let value as DisplayString: + try self.encode(value, forKey: key) default: // Ok, we don't know what this is. This can only happen for a dictionary, or // for anything with parameters, or for lists, or for inner lists. diff --git a/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift b/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift index 5d14101..4839534 100644 --- a/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift +++ b/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift @@ -89,7 +89,7 @@ struct ListyDictionaryField: StructuredFieldValue, Equatable { final class StructuredFieldDecoderTests: XCTestCase { func testSimpleCodableDecode() throws { let headerField = - "primary=bar;q=1.0, secondary=baz;q=0.5;fallback=last, acceptablejurisdictions=(AU;q=1.0 GB;q=0.9 FR);fallback=\"primary\"" + "primary=bar;q=1.0, secondary=baz;q=0.5;fallback=last, acceptablejurisdictions=(AU;q=1.0 GB;q=0.9 FR);fallback=\"primary\"" let parsed = try StructuredFieldValueDecoder().decode(ListyDictionaryField.self, from: Array(headerField.utf8)) let expected = ListyDictionaryField( primary: .init(item: "bar", parameters: .init(q: 1, fallback: nil)), @@ -115,7 +115,7 @@ final class StructuredFieldDecoderTests: XCTestCase { var acceptablejurisdictions: [String] } let headerField = - "primary=bar;q=1.0, secondary=baz;q=0.5;fallback=last, acceptablejurisdictions=(AU;q=1.0 GB;q=0.9 FR);fallback=\"primary\"" + "primary=bar;q=1.0, secondary=baz;q=0.5;fallback=last, acceptablejurisdictions=(AU;q=1.0 GB;q=0.9 FR);fallback=\"primary\"" let parsed = try StructuredFieldValueDecoder().decode( ListyDictionaryNoParams.self, from: Array(headerField.utf8) @@ -611,4 +611,137 @@ final class StructuredFieldDecoderTests: XCTestCase { try StructuredFieldValueDecoder().decode(from: Array(headerField.utf8)) ) } + + func testDecodingDisplayStringAsTopLevelData() throws { + XCTAssertEqual( + ItemField(DisplayString("füü")), + try StructuredFieldValueDecoder().decode(from: Array("%\"f%c3%bc%c3%bc\"".utf8)) + ) + } + + func testDecodingDisplayStringAsParameterisedData() throws { + struct Item: StructuredFieldValue, Equatable { + static let structuredFieldType: StructuredFieldType = .item + var item: DisplayString + var parameters: [String: Float] + } + + XCTAssertEqual( + Item( + item: DisplayString("füü"), + parameters: [:] + ), + try StructuredFieldValueDecoder().decode( + Item.self, + from: Array("%\"f%c3%bc%c3%bc\"".utf8) + ) + ) + + XCTAssertEqual( + Item(item: DisplayString("füü"), parameters: ["q": 0.8]), + try StructuredFieldValueDecoder().decode( + Item.self, + from: Array("%\"f%c3%bc%c3%bc\";q=0.8".utf8) + ) + ) + } + + func testDecodingDisplayStringInParameterField() throws { + struct Item: StructuredFieldValue, Equatable { + static let structuredFieldType: StructuredFieldType = .item + var item: Int + var parameters: [String: DisplayString] + } + + XCTAssertEqual( + Item(item: 1, parameters: ["q": DisplayString("füü")]), + try StructuredFieldValueDecoder().decode( + Item.self, + from: Array("1;q=%\"f%c3%bc%c3%bc\"".utf8) + ) + ) + } + + func testDecodingDisplayStringInOuterListRaw() throws { + XCTAssertEqual( + List( + [ + DisplayString("füü"), + DisplayString("foo \"bar\" \\ baz"), + ] + ), + try StructuredFieldValueDecoder().decode( + from: Array("%\"f%c3%bc%c3%bc\", %\"foo %22bar%22 \\ baz\"".utf8) + ) + ) + } + + func testDecodingDisplayStringInInnerListRaw() throws { + XCTAssertEqual( + List( + Array( + repeating: [ + DisplayString("füü"), + DisplayString("foo \"bar\" \\ baz"), + ], + count: 2 + ) + ), + try StructuredFieldValueDecoder().decode( + from: Array( + """ + (%\"f%c3%bc%c3%bc\" %\"foo %22bar%22 \\ baz\"), (%\"f%c3%bc%c3%bc\" %\"foo \ + %22bar%22 \\ baz\") + """.utf8 + ) + ) + ) + } + + func testDecodingDisplayStringInInnerListKeyed() throws { + struct ListField: Codable, Equatable { + var items: [DisplayString] + var parameters: [String: Bool] + } + XCTAssertEqual( + List( + Array( + repeating: ListField( + items: [ + DisplayString("füü"), + DisplayString("foo \"bar\" \\ baz"), + ], + parameters: ["foo": true] + ), + count: 2 + ) + ), + try StructuredFieldValueDecoder().decode( + from: Array( + """ + (%\"f%c3%bc%c3%bc\" %\"foo %22bar%22 \\ baz\");foo, (%\"f%c3%bc%c3%bc\" %\"foo \ + %22bar%22 \\ baz\");foo + """.utf8 + ) + ) + ) + } + + func testDecodingDisplayStringInDictionaries() throws { + struct DictionaryField: StructuredFieldValue, Equatable { + static let structuredFieldType: StructuredFieldType = .dictionary + var bin: DisplayString + var box: DisplayString + } + + XCTAssertEqual( + DictionaryField( + bin: DisplayString("füü"), + box: DisplayString("foo \"bar\" \\ baz") + ), + try StructuredFieldValueDecoder().decode( + from: Array("bin=%\"f%c3%bc%c3%bc\", box=%\"foo %22bar%22 \\ baz\"".utf8) + ) + ) + } } diff --git a/Tests/StructuredFieldValuesTests/StructuredFieldEncoderTests.swift b/Tests/StructuredFieldValuesTests/StructuredFieldEncoderTests.swift index af12517..243fe24 100644 --- a/Tests/StructuredFieldValuesTests/StructuredFieldEncoderTests.swift +++ b/Tests/StructuredFieldValuesTests/StructuredFieldEncoderTests.swift @@ -53,6 +53,12 @@ final class StructuredFieldEncoderTests: XCTestCase { Array("@-1659578233".utf8), try encoder.encode(ItemField(Date(timeIntervalSince1970: -1_659_578_233))) ) + + // Display String + XCTAssertEqual( + Array("%\"f%c3%bc%c3%bc\"".utf8), + try encoder.encode(ItemField(DisplayString("füü"))) + ) } func testEncodeKeyedItemHeader() throws { @@ -126,6 +132,16 @@ final class StructuredFieldEncoderTests: XCTestCase { ) ) ) + + // Display String + XCTAssertEqual( + Array("%\"f%c3%bc%c3%bc\";x".utf8), + try encoder.encode(KeyedItem(item: DisplayString("füü"), parameters: ["x": true])) + ) + XCTAssertEqual( + Array("%\"foo %22bar%22 \\ baz\"".utf8), + try encoder.encode(KeyedItem(item: DisplayString("foo \"bar\" \\ baz"), parameters: [:])) + ) } func testEncodeKeyedItemHeaderWithParamsAsStruct() throws { @@ -232,6 +248,12 @@ final class StructuredFieldEncoderTests: XCTestCase { ) ) ) + + // Display String + XCTAssertEqual( + Array("%\"f%c3%bc%c3%bc\", %\"foo %22bar%22 \\ baz\"".utf8), + try encoder.encode(List([DisplayString("füü"), DisplayString("foo \"bar\" \\ baz")])) + ) } func testListFieldInnerItemsWithDict() throws { From 061d2a0352e6e001148aa77840c258197a76e77e Mon Sep 17 00:00:00 2001 From: cnkwocha Date: Fri, 29 Nov 2024 11:44:11 +0000 Subject: [PATCH 2/3] Fix formatting --- .../StructuredFieldDecoderTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift b/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift index 4839534..47a066e 100644 --- a/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift +++ b/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift @@ -89,7 +89,7 @@ struct ListyDictionaryField: StructuredFieldValue, Equatable { final class StructuredFieldDecoderTests: XCTestCase { func testSimpleCodableDecode() throws { let headerField = - "primary=bar;q=1.0, secondary=baz;q=0.5;fallback=last, acceptablejurisdictions=(AU;q=1.0 GB;q=0.9 FR);fallback=\"primary\"" + "primary=bar;q=1.0, secondary=baz;q=0.5;fallback=last, acceptablejurisdictions=(AU;q=1.0 GB;q=0.9 FR);fallback=\"primary\"" let parsed = try StructuredFieldValueDecoder().decode(ListyDictionaryField.self, from: Array(headerField.utf8)) let expected = ListyDictionaryField( primary: .init(item: "bar", parameters: .init(q: 1, fallback: nil)), @@ -115,7 +115,7 @@ final class StructuredFieldDecoderTests: XCTestCase { var acceptablejurisdictions: [String] } let headerField = - "primary=bar;q=1.0, secondary=baz;q=0.5;fallback=last, acceptablejurisdictions=(AU;q=1.0 GB;q=0.9 FR);fallback=\"primary\"" + "primary=bar;q=1.0, secondary=baz;q=0.5;fallback=last, acceptablejurisdictions=(AU;q=1.0 GB;q=0.9 FR);fallback=\"primary\"" let parsed = try StructuredFieldValueDecoder().decode( ListyDictionaryNoParams.self, from: Array(headerField.utf8) From f2ae7bb971e6dd59e3125a49ba71bd479db9801e Mon Sep 17 00:00:00 2001 From: cnkwocha Date: Mon, 2 Dec 2024 09:08:25 +0000 Subject: [PATCH 3/3] Implement feedback --- .../Decoder/BareItemDecoder.swift | 2 +- .../StructuredFieldValues/DisplayString.swift | 14 ++++------- .../Encoder/StructuredFieldValueEncoder.swift | 6 ++--- .../StructuredFieldDecoderTests.swift | 24 +++++++++---------- .../StructuredFieldEncoderTests.swift | 16 +++++++++---- 5 files changed, 33 insertions(+), 29 deletions(-) diff --git a/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift b/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift index cd4e2aa..3abf85b 100644 --- a/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift +++ b/Sources/StructuredFieldValues/Decoder/BareItemDecoder.swift @@ -146,7 +146,7 @@ extension BareItemDecoder: SingleValueDecodingContainer { throw StructuredHeaderError.invalidTypeForItem } - return DisplayString(string) + return DisplayString(rawValue: string) } func decodeNil() -> Bool { diff --git a/Sources/StructuredFieldValues/DisplayString.swift b/Sources/StructuredFieldValues/DisplayString.swift index 2ee2841..2dfcf02 100644 --- a/Sources/StructuredFieldValues/DisplayString.swift +++ b/Sources/StructuredFieldValues/DisplayString.swift @@ -13,15 +13,11 @@ //===----------------------------------------------------------------------===// /// A type that represents the Display String Structured Type. -public struct DisplayString: Codable, Equatable { - /// The value of this Display String. - public private(set) var description: String +public struct DisplayString: RawRepresentable, Codable, Equatable, Hashable { + public typealias RawValue = String + public var rawValue: String - /// Initializes a new Display String. - /// - /// - parameters: - /// - description: The value of this Display String. - public init(_ description: String) { - self.description = description + public init(rawValue: String) { + self.rawValue = rawValue } } diff --git a/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift b/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift index ca1aaf1..7c56dee 100644 --- a/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift +++ b/Sources/StructuredFieldValues/Encoder/StructuredFieldValueEncoder.swift @@ -318,7 +318,7 @@ extension _StructuredFieldEncoder: SingleValueEncodingContainer { } func encode(_ data: DisplayString) throws { - try self.currentStackEntry.storage.insertBareItem(.displayString(data.description)) + try self.currentStackEntry.storage.insertBareItem(.displayString(data.rawValue)) } func encode(_ value: T) throws where T: Encodable { @@ -488,7 +488,7 @@ extension _StructuredFieldEncoder { } func append(_ value: DisplayString) throws { - try self.currentStackEntry.storage.appendBareItem(.displayString(value.description)) + try self.currentStackEntry.storage.appendBareItem(.displayString(value.rawValue)) } func append(_ value: T) throws where T: Encodable { @@ -673,7 +673,7 @@ extension _StructuredFieldEncoder { func encode(_ value: DisplayString, forKey key: String) throws { let key = self.sanitizeKey(key) - let displayString = value.description + let displayString = value.rawValue try self.currentStackEntry.storage.insertBareItem(.displayString(displayString), atKey: key) } diff --git a/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift b/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift index 47a066e..d56350a 100644 --- a/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift +++ b/Tests/StructuredFieldValuesTests/StructuredFieldDecoderTests.swift @@ -614,7 +614,7 @@ final class StructuredFieldDecoderTests: XCTestCase { func testDecodingDisplayStringAsTopLevelData() throws { XCTAssertEqual( - ItemField(DisplayString("füü")), + ItemField(DisplayString(rawValue: "füü")), try StructuredFieldValueDecoder().decode(from: Array("%\"f%c3%bc%c3%bc\"".utf8)) ) } @@ -628,7 +628,7 @@ final class StructuredFieldDecoderTests: XCTestCase { XCTAssertEqual( Item( - item: DisplayString("füü"), + item: DisplayString(rawValue: "füü"), parameters: [:] ), try StructuredFieldValueDecoder().decode( @@ -638,7 +638,7 @@ final class StructuredFieldDecoderTests: XCTestCase { ) XCTAssertEqual( - Item(item: DisplayString("füü"), parameters: ["q": 0.8]), + Item(item: DisplayString(rawValue: "füü"), parameters: ["q": 0.8]), try StructuredFieldValueDecoder().decode( Item.self, from: Array("%\"f%c3%bc%c3%bc\";q=0.8".utf8) @@ -654,7 +654,7 @@ final class StructuredFieldDecoderTests: XCTestCase { } XCTAssertEqual( - Item(item: 1, parameters: ["q": DisplayString("füü")]), + Item(item: 1, parameters: ["q": DisplayString(rawValue: "füü")]), try StructuredFieldValueDecoder().decode( Item.self, from: Array("1;q=%\"f%c3%bc%c3%bc\"".utf8) @@ -666,8 +666,8 @@ final class StructuredFieldDecoderTests: XCTestCase { XCTAssertEqual( List( [ - DisplayString("füü"), - DisplayString("foo \"bar\" \\ baz"), + DisplayString(rawValue: "füü"), + DisplayString(rawValue: "foo \"bar\" \\ baz"), ] ), try StructuredFieldValueDecoder().decode( @@ -681,8 +681,8 @@ final class StructuredFieldDecoderTests: XCTestCase { List( Array( repeating: [ - DisplayString("füü"), - DisplayString("foo \"bar\" \\ baz"), + DisplayString(rawValue: "füü"), + DisplayString(rawValue: "foo \"bar\" \\ baz"), ], count: 2 ) @@ -708,8 +708,8 @@ final class StructuredFieldDecoderTests: XCTestCase { Array( repeating: ListField( items: [ - DisplayString("füü"), - DisplayString("foo \"bar\" \\ baz"), + DisplayString(rawValue: "füü"), + DisplayString(rawValue: "foo \"bar\" \\ baz"), ], parameters: ["foo": true] ), @@ -736,8 +736,8 @@ final class StructuredFieldDecoderTests: XCTestCase { XCTAssertEqual( DictionaryField( - bin: DisplayString("füü"), - box: DisplayString("foo \"bar\" \\ baz") + bin: DisplayString(rawValue: "füü"), + box: DisplayString(rawValue: "foo \"bar\" \\ baz") ), try StructuredFieldValueDecoder().decode( from: Array("bin=%\"f%c3%bc%c3%bc\", box=%\"foo %22bar%22 \\ baz\"".utf8) diff --git a/Tests/StructuredFieldValuesTests/StructuredFieldEncoderTests.swift b/Tests/StructuredFieldValuesTests/StructuredFieldEncoderTests.swift index 243fe24..a6ce1b8 100644 --- a/Tests/StructuredFieldValuesTests/StructuredFieldEncoderTests.swift +++ b/Tests/StructuredFieldValuesTests/StructuredFieldEncoderTests.swift @@ -57,7 +57,7 @@ final class StructuredFieldEncoderTests: XCTestCase { // Display String XCTAssertEqual( Array("%\"f%c3%bc%c3%bc\"".utf8), - try encoder.encode(ItemField(DisplayString("füü"))) + try encoder.encode(ItemField(DisplayString(rawValue: "füü"))) ) } @@ -136,11 +136,15 @@ final class StructuredFieldEncoderTests: XCTestCase { // Display String XCTAssertEqual( Array("%\"f%c3%bc%c3%bc\";x".utf8), - try encoder.encode(KeyedItem(item: DisplayString("füü"), parameters: ["x": true])) + try encoder.encode( + KeyedItem(item: DisplayString(rawValue: "füü"), parameters: ["x": true]) + ) ) XCTAssertEqual( Array("%\"foo %22bar%22 \\ baz\"".utf8), - try encoder.encode(KeyedItem(item: DisplayString("foo \"bar\" \\ baz"), parameters: [:])) + try encoder.encode( + KeyedItem(item: DisplayString(rawValue: "foo \"bar\" \\ baz"), parameters: [:]) + ) ) } @@ -252,7 +256,11 @@ final class StructuredFieldEncoderTests: XCTestCase { // Display String XCTAssertEqual( Array("%\"f%c3%bc%c3%bc\", %\"foo %22bar%22 \\ baz\"".utf8), - try encoder.encode(List([DisplayString("füü"), DisplayString("foo \"bar\" \\ baz")])) + try encoder.encode( + List( + [DisplayString(rawValue: "füü"), DisplayString(rawValue: "foo \"bar\" \\ baz")] + ) + ) ) }