@@ -43,6 +43,9 @@ private enum ParsingError: Swift.Error {
43
43
44
44
/// A malformed key-value pair was detected.
45
45
case malformedKeyValuePair( Raw )
46
+
47
+ /// An invalid configuration was detected.
48
+ case invalidConfiguration( String )
46
49
}
47
50
48
51
// MARK: - Parser implementations
@@ -61,13 +64,18 @@ extension URIParser {
61
64
switch configuration. style {
62
65
case . form: return [ : ]
63
66
case . simple: return [ " " : [ " " ] ]
67
+ case . deepObject: return [ : ]
64
68
}
65
69
}
66
70
switch ( configuration. style, configuration. explode) {
67
71
case ( . form, true ) : return try parseExplodedFormRoot ( )
68
72
case ( . form, false ) : return try parseUnexplodedFormRoot ( )
69
73
case ( . simple, true ) : return try parseExplodedSimpleRoot ( )
70
74
case ( . simple, false ) : return try parseUnexplodedSimpleRoot ( )
75
+ case ( . deepObject, true ) : return try parseExplodedDeepObjectRoot ( )
76
+ case ( . deepObject, false ) :
77
+ let reason = " Deep object style is only valid with explode set to true "
78
+ throw ParsingError . invalidConfiguration ( reason)
71
79
}
72
80
}
73
81
@@ -205,6 +213,45 @@ extension URIParser {
205
213
}
206
214
}
207
215
}
216
+
217
+ /// Parses the root node assuming the raw string uses the deepObject style
218
+ /// and the explode parameter is enabled.
219
+ /// - Returns: The parsed root node.
220
+ /// - Throws: An error if parsing fails.
221
+ private mutating func parseExplodedDeepObjectRoot( ) throws -> URIParsedNode {
222
+ try parseGenericRoot { data, appendPair in
223
+ let keyValueSeparator : Character = " = "
224
+ let pairSeparator : Character = " & "
225
+ let nestedKeyStartingCharacter : Character = " [ "
226
+ let nestedKeyEndingCharacter : Character = " ] "
227
+
228
+ func nestedKey( from deepObjectKey: String . SubSequence ) -> Raw {
229
+ var unescapedFirstValue = Substring ( deepObjectKey. removingPercentEncoding ?? " " )
230
+ let nestedKey = unescapedFirstValue. parseUpToCharacterOrEnd (
231
+ startingCharacter: nestedKeyStartingCharacter,
232
+ nestedKeyEndingCharacter
233
+ )
234
+ return nestedKey
235
+ }
236
+
237
+ while !data. isEmpty {
238
+ let ( firstResult, firstValue) = data. parseUpToEitherCharacterOrEnd (
239
+ first: keyValueSeparator,
240
+ second: pairSeparator
241
+ )
242
+
243
+ guard case . foundFirst = firstResult else {
244
+ throw ParsingError . malformedKeyValuePair ( firstValue)
245
+ }
246
+ // Hit the key/value separator, so a value will follow.
247
+ let secondValue = data. parseUpToCharacterOrEnd ( pairSeparator)
248
+ let key = nestedKey ( from: firstValue)
249
+ let value = secondValue
250
+
251
+ appendPair ( key, [ value] )
252
+ }
253
+ }
254
+ }
208
255
}
209
256
210
257
// MARK: - URIParser utilities
@@ -302,17 +349,55 @@ extension String.SubSequence {
302
349
return finalize ( . foundSecondOrEnd)
303
350
}
304
351
352
+
305
353
/// Accumulates characters until the provided character is found,
306
354
/// or the end is reached. Moves the underlying startIndex.
307
- /// - Parameter character: A character to stop at.
355
+ /// - Parameters:
356
+ /// - startingCharacter: A character to start with.
357
+ /// - endingCharacter: A character to stop at.
358
+ /// If not provided or not found then uses the current start index as a starting character.
308
359
/// - Returns: The accumulated substring.
309
- fileprivate mutating func parseUpToCharacterOrEnd( _ character: Character ) -> Self {
310
- let startIndex = startIndex
360
+ fileprivate mutating func parseUpToCharacterOrEnd( startingCharacter: Character ? = nil , _ endingCharacter: Character ) -> Self {
311
361
guard startIndex != endIndex else { return . init( ) }
312
- var currentIndex = startIndex
313
-
362
+
363
+ let startingCharacterIndex : Substring . Index = {
364
+ guard let startingCharacter,
365
+ let index = firstIndex ( of: startingCharacter) else {
366
+ return startIndex
367
+ }
368
+ return
369
+ } ( )
370
+ var currentIndex = startingCharacterIndex
371
+
314
372
func finalize( ) -> Self {
315
- let parsed = self [ startIndex..< currentIndex]
373
+ let parsed = self [ startingCharacterIndex..< currentIndex]
374
+ guard currentIndex == endIndex else {
375
+ self = self [ index ( after: currentIndex) ... ]
376
+ return parsed
377
+ }
378
+ self = . init( )
379
+ return parsed
380
+ }
381
+ while currentIndex != endIndex {
382
+ let currentChar = self [ currentIndex]
383
+ if currentChar == endingCharacter { return finalize ( ) } else { formIndex ( after: & currentIndex) }
384
+ }
385
+ return finalize ( )
386
+ }
387
+
388
+
389
+ /// Accumulates characters from the `startingCharacter` character provided,
390
+ /// until the `endingCharacter` is reached. Moves the underlying startIndex.
391
+ /// - Parameters:
392
+ /// - startingCharacter: A character to start with.
393
+ /// - endingCharacter: A character to stop at.
394
+ /// - Returns: The accumulated substring.
395
+ fileprivate mutating func parseBetweenCharacters( startingCharacter: Character , endingCharacter: Character ) -> Self {
396
+ guard let startingCharacterIndex = firstIndex ( of: startingCharacter) else { return self }
397
+ var currentIndex = startingCharacterIndex
398
+
399
+ func finalize( ) -> Self {
400
+ let parsed = self [ index ( after: startingCharacterIndex) ..< currentIndex]
316
401
guard currentIndex == endIndex else {
317
402
self = self [ index ( after: currentIndex) ... ]
318
403
return parsed
@@ -322,7 +407,7 @@ extension String.SubSequence {
322
407
}
323
408
while currentIndex != endIndex {
324
409
let currentChar = self [ currentIndex]
325
- if currentChar == character { return finalize ( ) } else { formIndex ( after: & currentIndex) }
410
+ if currentChar == endingCharacter { return finalize ( ) } else { formIndex ( after: & currentIndex) }
326
411
}
327
412
return finalize ( )
328
413
}
0 commit comments