Skip to content

Commit 101dec0

Browse files
committed
Introduce StructuredHeaderField protocol.
Motivation: We had a proliferation of decode/encode methods that required users to tell us how to encode their structured header field. This is a bit annoying for two reasons: firstly, it requires users to encode this piece of information separately from their header field; secondly, it makes it very hard to offer a generic interface to load something as a structured header field! Modifications: - Add a new StructuredHeaderField protocol that refines Codable and expresses what type of header field is used. - Remove the fancy encode/decode methods. Result: Simpler interfaces.
1 parent 6a2b13e commit 101dec0

File tree

4 files changed

+279
-120
lines changed

4 files changed

+279
-120
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftNIO open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the SwiftNIO project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
import Foundation
15+
import StructuredHeaders
16+
17+
private let keyedTopLevelListDecoderSupportedKeys = ["items"]
18+
19+
/// Used when someone has requested a keyed decoder for a property of list type.
20+
///
21+
/// There is only one valid key for this: "items".
22+
struct KeyedTopLevelListDecoder<Key: CodingKey, BaseData: RandomAccessCollection> where BaseData.Element == UInt8, BaseData.SubSequence: Hashable {
23+
private var list: [ItemOrInnerList<BaseData.SubSequence>]
24+
25+
private var decoder: _StructuredFieldDecoder<BaseData>
26+
27+
init(_ list: [ItemOrInnerList<BaseData.SubSequence>], decoder: _StructuredFieldDecoder<BaseData>) {
28+
self.list = list
29+
self.decoder = decoder
30+
}
31+
}
32+
33+
extension KeyedTopLevelListDecoder: KeyedDecodingContainerProtocol {
34+
var codingPath: [CodingKey] {
35+
self.decoder.codingPath
36+
}
37+
38+
var allKeys: [Key] {
39+
keyedTopLevelListDecoderSupportedKeys.compactMap { Key(stringValue: $0) }
40+
}
41+
42+
func contains(_ key: Key) -> Bool {
43+
keyedTopLevelListDecoderSupportedKeys.contains(key.stringValue)
44+
}
45+
46+
func decodeNil(forKey key: Key) throws -> Bool {
47+
// Keys are never nil for this type.
48+
false
49+
}
50+
51+
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable {
52+
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
53+
defer {
54+
self.decoder.pop()
55+
}
56+
57+
if type is Data.Type {
58+
let container = try self.decoder.singleValueContainer()
59+
return try container.decode(Data.self) as! T
60+
} else if type is Decimal.Type {
61+
let container = try self.decoder.singleValueContainer()
62+
return try container.decode(Decimal.self) as! T
63+
} else {
64+
return try type.init(from: self.decoder)
65+
}
66+
}
67+
68+
func nestedContainer<NestedKey: CodingKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> {
69+
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
70+
defer {
71+
self.decoder.pop()
72+
}
73+
return try self.decoder.container(keyedBy: type)
74+
}
75+
76+
func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
77+
try self.decoder.push(_StructuredHeaderCodingKey(key, keyDecodingStrategy: self.decoder.keyDecodingStrategy))
78+
defer {
79+
self.decoder.pop()
80+
}
81+
return try self.decoder.unkeyedContainer()
82+
}
83+
84+
func superDecoder() throws -> Decoder {
85+
// Items never support inherited types.
86+
throw StructuredHeaderError.invalidTypeForItem
87+
}
88+
89+
func superDecoder(forKey key: Key) throws -> Decoder {
90+
// Items never support inherited types.
91+
throw StructuredHeaderError.invalidTypeForItem
92+
}
93+
}

Sources/CodableStructuredHeaders/Decoder/StructuredFieldDecoder.swift

Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,20 @@ extension StructuredFieldDecoder {
4141
extension StructuredFieldDecoder {
4242
/// Attempt to decode an object from a structured header field.
4343
///
44-
/// This method will attempt to guess what kind of structured header field is being
45-
/// parsed based on the structure of `type`. This is useful for quick prototyping, but
46-
/// generally this method should be avoided in favour of one of the other `decode`
47-
/// methods on this type.
48-
///
4944
/// - parameters:
5045
/// - type: The type of the object to decode.
5146
/// - data: The bytes of the structured header field.
5247
/// - throws: If the header field could not be parsed, or could not be decoded.
5348
/// - returns: An object of type `StructuredField`.
54-
public func decode<StructuredField: Decodable, BaseData: RandomAccessCollection>(_ type: StructuredField.Type = StructuredField.self, from data: BaseData) throws -> StructuredField where BaseData.Element == UInt8, BaseData.SubSequence: Hashable {
55-
let parser = StructuredFieldParser(data)
56-
let decoder = _StructuredFieldDecoder(parser, keyDecodingStrategy: self.keyDecodingStrategy)
57-
return try type.init(from: decoder)
49+
public func decode<StructuredField: StructuredHeaderField, BaseData: RandomAccessCollection>(_ type: StructuredField.Type = StructuredField.self, from data: BaseData) throws -> StructuredField where BaseData.Element == UInt8, BaseData.SubSequence: Hashable {
50+
switch StructuredField.structuredFieldType {
51+
case .item:
52+
return try self.decodeItemField(from: data)
53+
case .list:
54+
return try self.decodeListField(from: data)
55+
case .dictionary:
56+
return try self.decodeDictionaryField(from: data)
57+
}
5858
}
5959

6060
/// Attempt to decode an object from a structured header dictionary field.
@@ -64,7 +64,7 @@ extension StructuredFieldDecoder {
6464
/// - data: The bytes of the structured header field.
6565
/// - throws: If the header field could not be parsed, or could not be decoded.
6666
/// - returns: An object of type `StructuredField`.
67-
public func decodeDictionaryField<StructuredField: Decodable, BaseData: RandomAccessCollection>(_ type: StructuredField.Type = StructuredField.self, from data: BaseData) throws -> StructuredField where BaseData.Element == UInt8, BaseData.SubSequence: Hashable {
67+
private func decodeDictionaryField<StructuredField: Decodable, BaseData: RandomAccessCollection>(_ type: StructuredField.Type = StructuredField.self, from data: BaseData) throws -> StructuredField where BaseData.Element == UInt8, BaseData.SubSequence: Hashable {
6868
let parser = StructuredFieldParser(data)
6969
let decoder = _StructuredFieldDecoder(parser, keyDecodingStrategy: self.keyDecodingStrategy)
7070
try decoder.parseDictionaryField()
@@ -78,7 +78,7 @@ extension StructuredFieldDecoder {
7878
/// - data: The bytes of the structured header field.
7979
/// - throws: If the header field could not be parsed, or could not be decoded.
8080
/// - returns: An object of type `StructuredField`.
81-
public func decodeListField<StructuredField: Decodable, BaseData: RandomAccessCollection>(_ type: StructuredField.Type = StructuredField.self, from data: BaseData) throws -> StructuredField where BaseData.Element == UInt8, BaseData.SubSequence: Hashable {
81+
private func decodeListField<StructuredField: Decodable, BaseData: RandomAccessCollection>(_ type: StructuredField.Type = StructuredField.self, from data: BaseData) throws -> StructuredField where BaseData.Element == UInt8, BaseData.SubSequence: Hashable {
8282
let parser = StructuredFieldParser(data)
8383
let decoder = _StructuredFieldDecoder(parser, keyDecodingStrategy: self.keyDecodingStrategy)
8484
try decoder.parseListField()
@@ -92,7 +92,7 @@ extension StructuredFieldDecoder {
9292
/// - data: The bytes of the structured header field.
9393
/// - throws: If the header field could not be parsed, or could not be decoded.
9494
/// - returns: An object of type `StructuredField`.
95-
public func decodeItemField<StructuredField: Decodable, BaseData: RandomAccessCollection>(_ type: StructuredField.Type = StructuredField.self, from data: BaseData) throws -> StructuredField where BaseData.Element == UInt8, BaseData.SubSequence: Hashable {
95+
private func decodeItemField<StructuredField: Decodable, BaseData: RandomAccessCollection>(_ type: StructuredField.Type = StructuredField.self, from data: BaseData) throws -> StructuredField where BaseData.Element == UInt8, BaseData.SubSequence: Hashable {
9696
let parser = StructuredFieldParser(data)
9797
let decoder = _StructuredFieldDecoder(parser, keyDecodingStrategy: self.keyDecodingStrategy)
9898
try decoder.parseItemField()
@@ -148,34 +148,24 @@ extension _StructuredFieldDecoder: Decoder {
148148
}
149149

150150
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key: CodingKey {
151-
if self.currentElement == nil {
152-
// First parse. This may be a dictionary, but depending on the keys this may also be list or item.
153-
// We can't really detect this (Key isn't caseiterable) so we just kinda have to hope. Users can tell
154-
// us with alternative methods if we're wrong.
155-
try self.parseDictionaryField()
156-
}
157-
158151
switch self.currentElement! {
159152
case .dictionary(let dictionary):
160153
return KeyedDecodingContainer(DictionaryKeyedContainer(dictionary, decoder: self))
161154
case .item(let item):
162155
return KeyedDecodingContainer(KeyedItemDecoder(item, decoder: self))
156+
case .list(let list):
157+
return KeyedDecodingContainer(KeyedTopLevelListDecoder(list, decoder: self))
163158
case .innerList(let innerList):
164159
return KeyedDecodingContainer(KeyedInnerListDecoder(innerList, decoder: self))
165160
case .parameters(let parameters):
166161
return KeyedDecodingContainer(ParametersDecoder(parameters, decoder: self))
167-
case .bareItem, .bareInnerList, .list:
162+
case .bareItem, .bareInnerList:
168163
// No keyed container for these types.
169164
throw StructuredHeaderError.invalidTypeForItem
170165
}
171166
}
172167

173168
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
174-
if self.currentElement == nil {
175-
// First parse, this is a list header.
176-
try self.parseListField()
177-
}
178-
179169
// We have unkeyed containers for lists, inner lists, and bare inner lists.
180170
switch self.currentElement! {
181171
case .list(let items):
@@ -191,11 +181,6 @@ extension _StructuredFieldDecoder: Decoder {
191181
}
192182

193183
func singleValueContainer() throws -> SingleValueDecodingContainer {
194-
if self.currentElement == nil {
195-
// First parse, this is a an item header.
196-
try self.parseItemField()
197-
}
198-
199184
// We have single value containers for items and bareItems.
200185
switch self.currentElement! {
201186
case .item(let item):
@@ -256,16 +241,24 @@ extension _StructuredFieldDecoder {
256241
return .innerList(innerList)
257242
}
258243
case .list(let list):
259-
guard let offset = key.intValue, offset < list.count else {
244+
if let offset = key.intValue {
245+
guard offset < list.count else {
246+
throw StructuredHeaderError.invalidTypeForItem
247+
}
248+
let index = list.index(list.startIndex, offsetBy: offset)
249+
switch list[index] {
250+
case .item(let item):
251+
return .item(item)
252+
case .innerList(let innerList):
253+
return .innerList(innerList)
254+
}
255+
} else if key.stringValue == "items" {
256+
// Oh, the outer layer is keyed. That's fine, just put ourselves
257+
// back on the stack.
258+
return .list(list)
259+
} else {
260260
throw StructuredHeaderError.invalidTypeForItem
261261
}
262-
let index = list.index(list.startIndex, offsetBy: offset)
263-
switch list[index] {
264-
case .item(let item):
265-
return .item(item)
266-
case .innerList(let innerList):
267-
return .innerList(innerList)
268-
}
269262
case .item(let item):
270263
// Two keys, "item" and "parameters".
271264
switch key.stringValue {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftNIO open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the SwiftNIO project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
/// A `StructuredHeaderField` is a `Codable` representation of a HTTP Structured
16+
/// Header Field.
17+
///
18+
/// This protocol is a helper protocol that refines `Codable` to indicate what kind
19+
/// of header field a given field uses.
20+
public protocol StructuredHeaderField: Codable {
21+
static var structuredFieldType: StructuredHeaderFieldType { get }
22+
}
23+
24+
25+
/// The kinds of header fields used in HTTP Structured Headers.
26+
public enum StructuredHeaderFieldType {
27+
/// An item field consists of a single item, optionally with parameters.
28+
case item
29+
30+
/// A list field consists of a list made up of inner lists or items.
31+
case list
32+
33+
/// A dictionary field is an ordered collection of key-value pairs.
34+
case dictionary
35+
}

0 commit comments

Comments
 (0)