Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for array indices in query params decoding #542

Merged
merged 2 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ enum URLEncodedFormNode: CustomStringConvertible, Equatable {
/// holds an array of nodes
case array(Array)

enum Error: Swift.Error {
enum Error: Swift.Error, Equatable {
case failedToDecode(String? = nil)
case notSupported
case invalidArrayIndex(Int)
}

/// Initialize node from URL encoded form data
Expand Down Expand Up @@ -51,7 +52,7 @@ enum URLEncodedFormNode: CustomStringConvertible, Equatable {
/// function for create `URLEncodedFormNode` from `KeyParser.Key.Type`
func createNode(from key: KeyParser.KeyType) -> URLEncodedFormNode {
switch key {
case .array:
case .array, .arrayWithIndices:
return .array(.init())
case .map:
return .map(.init())
Expand Down Expand Up @@ -84,6 +85,11 @@ enum URLEncodedFormNode: CustomStringConvertible, Equatable {
// currently don't support arrays and maps inside arrays
throw Error.notSupported
}
case (.array(let array), .arrayWithIndices(let index)):
guard keys.count == 0, array.values.count == index else {
throw Error.invalidArrayIndex(index)
}
array.values.append(.leaf(value))
default:
throw Error.failedToDecode()
}
Expand Down Expand Up @@ -168,7 +174,7 @@ enum URLEncodedFormNode: CustomStringConvertible, Equatable {

/// Parse URL encoded key
enum KeyParser {
enum KeyType: Equatable { case map(Substring), array }
enum KeyType: Equatable { case map(Substring), array, arrayWithIndices(Int) }

static func parse(_ key: String) -> [KeyType]? {
var index = key.startIndex
Expand All @@ -186,13 +192,19 @@ enum KeyParser {
index = key.index(after: index)
// an open bracket is unexpected
guard index != key.endIndex else { return nil }

if key[index] == "]" {
values.append(.array)
index = key.index(after: index)
} else {
// an open bracket is unexpected
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of parsing the array index above you should do it here once we have realised the next character isn't a ]. You could also replace the values.append(.map(key[index..<bracketIndex])) call below with the following to do the integer conversion.

// If key can convert to an integer assume it is an array index
if let index = Int(key[index..<bracketIndex]) {
    values.append(.arrayWithIndices(index))
} else {
    values.append(.map(key[index..<bracketIndex]))
}

guard let bracketIndex = key[index...].firstIndex(of: "]") else { return nil }
values.append(.map(key[index..<bracketIndex]))
// If key can convert to an integer assume it is an array index
if let index = Int(key[index..<bracketIndex]) {
values.append(.arrayWithIndices(index))
} else {
values.append(.map(key[index..<bracketIndex]))
}
index = bracketIndex
index = key.index(after: index)
}
Expand Down
39 changes: 38 additions & 1 deletion Tests/HummingbirdTests/URLEncodedForm/URLDecoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//

import Hummingbird
@testable import Hummingbird
import XCTest

class URLDecodedFormDecoderTests: XCTestCase {
Expand Down Expand Up @@ -79,6 +79,43 @@ class URLDecodedFormDecoderTests: XCTestCase {
self.testForm(test, query: "b[]=true&b[]=false&i[]=34&i8[]=23&i16[]=9&i32[]=-6872&i64[]=23&u[]=0&u8[]=255&u16[]=7673&u32[]=88222&u64[]=234&f[]=-1.1&d[]=8")
}

func testArraysWithIndices() {
struct Test: Codable, Equatable {
let arr: [Int]
}
let test = Test(arr: [12, 45, 54, 55, -5, 5])
self.testForm(test, query: "arr[0]=12&arr[1]=45&arr[2]=54&arr[3]=55&arr[4]=-5&arr[5]=5")

let test2 = Test(arr: [12, 45, 54, 55, -5, 5, 9, 33, 0, 9, 4, 33])
let query = """
arr[0]=12\
&arr[1]=45\
&arr[2]=54\
&arr[3]=55\
&arr[4]=-5\
&arr[5]=5\
&arr[6]=9\
&arr[7]=33\
&arr[8]=0\
&arr[9]=9\
&arr[10]=4\
&arr[11]=33
"""
self.testForm(test2, query: query)
}

func testArrayWithIndicesThrows() {
struct Test: Codable, Equatable {
let arr: [Int]
}
let decoder = URLEncodedFormDecoder()
// incorrect indices
let query = "arr[0]=2&arr[2]=4"
XCTAssertThrowsError(try decoder.decode(Test.self, from: query)) { error in
XCTAssertEqual(error as? URLEncodedFormNode.Error, URLEncodedFormNode.Error.invalidArrayIndex(2))
}
}

func testStringSpecialCharactersDecode() {
struct Test: Codable, Equatable {
let a: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ class URLEncodedFormNodeTests: XCTestCase {
XCTAssertEqual(values, [.map("array"), .array])
}

func testKeyParserArrayWithIndices() {
let values = KeyParser.parse("array[0]")
XCTAssertEqual(values, [.map("array"), .arrayWithIndices(0)])
}

func testKeyParserMap() {
let values = KeyParser.parse("array[object]")
XCTAssertEqual(values, [.map("array"), .map("object")])
Expand Down