Skip to content
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 @@ -31,14 +31,14 @@

import Foundation

public extension HTTPBodyPattern where Self == JSONValuePattern {
public extension HTTPBodyPattern where Self == JSONBodyPattern {

static func jsonValue(where predicate: @escaping @Sendable (JSONValue) throws -> Bool) -> JSONValuePattern {
JSONValuePattern(predicate)
static func jsonValue(where predicate: @escaping @Sendable (JSONValue) throws -> Bool) -> JSONBodyPattern {
JSONBodyPattern(predicate)
}
}

public struct JSONValuePattern: HTTPBodyPattern {
public struct JSONBodyPattern: HTTPBodyPattern {

private let predicate: @Sendable (JSONValue) throws -> Bool

Expand Down
192 changes: 33 additions & 159 deletions FlyingFox/Sources/JSON/JSONValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,52 +130,22 @@ public extension JSONValue {
} else {
self = .number(nsNumber.doubleValue)
}
} else if let int = any as? Int {
self = .number(Double(int))
} else if let double = any as? Double {
self = .number(double)
} else if let bool = any as? Bool {
self = .boolean(bool)
} else if any is NSNull {
self = .null
} else if case nil as Any? = any {
self = .null
} else {
throw Error("Unsupported Value")
}
}

init?(_ value: JSONValue?) {
guard let value else { return nil }
self = value
}

init?(_ value: [String: JSONValue]?) {
guard let value else { return nil }
self = .object(value)
}

init?(_ value: [JSONValue]?) {
guard let value else { return nil }
self = .array(value)
}

init?(_ value: String?) {
guard let value else { return nil }
self = .string(value)
}

init?(_ value: Double?) {
guard let value else { return nil }
self = .number(value)
}

init?(_ value: Int?) {
guard let value else { return nil }
self = .number(Double(value))
}

init?(_ value: Bool?) {
guard let value else { return nil }
self = .boolean(value)
init<T>(_ any: T?) throws {
switch any {
case .none:
self = .null
case .some(let value):
self = try JSONValue(value)
}
}

func asAny() -> Any {
Expand All @@ -195,54 +165,20 @@ public extension JSONValue {
}
}

func asObject() throws -> [String: JSONValue] {
private func asObject() throws -> [String: JSONValue] {
guard case let .object(val) = self else {
throw Error("Expected object")
}
return val
}

func asArray() throws -> [JSONValue] {
private func asArray() throws -> [JSONValue] {
guard case let .array(val) = self else {
throw Error("Expected array")
}
return val
}

func asString() throws -> String {
guard case let .string(val) = self else {
throw Error("Expected string")
}
return val
}

func asNumber() throws -> Double {
guard case .number(let val) = self else {
throw Error("Expected number")
}
return val
}

func asBool() throws -> Bool {
switch self {
case .boolean(let val):
return val
case .number(let val) where val == 0:
return false
case .number(let val) where val == 1:
return true
default:
throw Error("Expected boolean")
}
}

func asNull() throws -> NSNull {
guard case .null = self else {
throw Error("Expected null")
}
return NSNull()
}

private struct Error: LocalizedError {
var errorDescription: String?

Expand All @@ -261,96 +197,34 @@ public extension JSONValue {
}
}

public extension JSONValue {

mutating func updateValue(parsing text: String) throws {
if let null = try? Self.parseNull(string: text) {
self = null
return
}
switch self {
case .object:
self = try Self.parseObject(string: text)
case .array:
self = try Self.parseArray(string: text)
case .string:
self = .string(text)
case .number:
self = try Self.parseNumber(string: text)
case .boolean:
self = try Self.parseBoolean(string: text)
case .null:
self = Self.parseAny(string: text)
}
}
public func == (lhs: JSONValue, rhs: String) -> Bool {
lhs == JSONValue.string(rhs)
}

static func parseObject(string: String) throws -> JSONValue {
let data = string.data(using: .utf8)!
guard case let .object(object) = try JSONValue(data: data) else {
throw Error("Invalid object")
}
return .object(object)
}
public func != (lhs: JSONValue, rhs: String) -> Bool {
!(lhs == rhs)
}

static func parseArray(string: String) throws -> JSONValue {
let data = string.data(using: .utf8)!
guard case let .array(array) = try JSONValue(data: data) else {
throw Error("Invalid array")
}
return .array(array)
}
public func == (lhs: JSONValue, rhs: Double) -> Bool {
lhs == JSONValue.number(rhs)
}

static func parseNumber(string: String) throws -> JSONValue {
guard let value = JSONValue.numberFormatter.number(from: string)?.doubleValue else {
throw Error("Invalid number")
}
return .number(value)
}
public func != (lhs: JSONValue, rhs: Double) -> Bool {
!(lhs == rhs)
}

static func parseBoolean(string: String) throws -> JSONValue {
switch string.lowercased() {
case "true":
return .boolean(true)
case "false":
return .boolean(false)
default:
throw Error("Invalid boolean")
}
}
public func == (lhs: JSONValue, rhs: Bool) -> Bool {
lhs == JSONValue.boolean(rhs)
}

static func parseNull(string: String) throws -> JSONValue {
switch string.lowercased() {
case "null", "":
return .null
default:
throw Error("Invalid null")
}
}
public func != (lhs: JSONValue, rhs: Bool) -> Bool {
!(lhs == rhs)
}

static func parseAny(string: String) -> JSONValue {
if let object = try? parseObject(string: string) {
return object
} else if let array = try? parseArray(string: string) {
return array
} else if let number = try? parseNumber(string: string) {
return number
} else if let bool = try? parseBoolean(string: string) {
return bool
} else if let null = try? parseNull(string: string) {
return null
} else {
return .string(string)
}
}
public func == (lhs: JSONValue, rhs: some BinaryInteger) -> Bool {
lhs == JSONValue.number(Double(rhs))
}

public extension JSONValue {
static let numberFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US")
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 6
formatter.roundingMode = .halfUp
return formatter
}()
public func != (lhs: JSONValue, rhs: some BinaryInteger) -> Bool {
!(lhs == rhs)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// JSONValuePatternTests.swift
// JSONBodyPatternTests.swift
// FlyingFox
//
// Created by Simon Whitty on 15/08/2024.
Expand Down Expand Up @@ -33,12 +33,12 @@ import FlyingFox
import Foundation
import Testing

struct JSONValuePatternTests {
struct JSONBodyPatternTests {

@Test
func pattern_MatchesJSONPath() async throws {
// given
let pattern = JSONValuePattern { try $0.getValue(for: "$.name") == .string("fish") }
let pattern = JSONBodyPattern { try $0.getValue(for: "$.name") == "fish" }

// when then
#expect(pattern.evaluate(json: #"{"name": "fish"}"#))
Expand All @@ -53,7 +53,7 @@ struct JSONValuePatternTests {
// given
let route = HTTPRoute(
"POST /fish",
jsonBody: { try $0.getValue(for: "$.food") == .string("chips") }
jsonBody: { try $0.getValue(for: "$.food") == "chips" }
)

// when
Expand All @@ -70,14 +70,13 @@ struct JSONValuePatternTests {
}
}

private extension JSONValuePattern {
private extension JSONBodyPattern {

func evaluate(json: String) -> Bool {
self.evaluate(Data(json.utf8))
}
}


private extension HTTPRequest {
static func make(method: HTTPMethod = .POST,
version: HTTPVersion = .http11,
Expand Down
8 changes: 4 additions & 4 deletions FlyingFox/Tests/JSON/JSONPathTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,16 @@ struct JSONPathTests {
"""#)

#expect(
try json.getValue(for: "$.owner.age").asNumber() == 7
try json.getValue(for: "$.owner.age") == 7
)
#expect(
try json.getValue(for: "$.owner.isAdmin").asBool()
try json.getValue(for: "$.owner.isAdmin") == true
)
#expect(
try json.getValue(for: "$.users[1].food").asString() == "chips"
try json.getValue(for: "$.users[1].food") == "chips"
)
#expect(
try json.getValue(for: "$.users[2].age").asNumber() == 9
try json.getValue(for: "$.users[2].age") == 9
)
}

Expand Down
Loading
Loading