├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── NodeCocoapods.podspec ├── Package.swift ├── Package@swift-4.swift ├── README.md ├── Sources ├── Node │ ├── Accessors │ │ ├── Getters.swift │ │ └── Setters.swift │ ├── Convertibles │ │ ├── Bool+Convertible.swift │ │ ├── Date+Convertible.swift │ │ ├── FloatingPoint+Convertible.swift │ │ ├── Integer+Convertible.swift │ │ ├── Schema+Convertible.swift │ │ ├── SchemaWrapper+Convertible.swift │ │ ├── String+Convertible.swift │ │ ├── UUID+Convertible.swift │ │ └── UnsignedInteger+Convertible.swift │ ├── Core │ │ ├── Context.swift │ │ ├── Node.swift │ │ ├── NodeConvertible.swift │ │ ├── NodeInitializable.swift │ │ └── NodeRepresentable.swift │ ├── Fuzzy │ │ ├── Array+Convertible.swift │ │ ├── Dictionary+Convertible.swift │ │ ├── Fuzzy+Any.swift │ │ ├── FuzzyConverter.swift │ │ ├── Optional+Convertible.swift │ │ └── Set+Convertible.swift │ ├── Number │ │ └── Number.swift │ ├── StructuredData │ │ ├── StructuredData+Equatable.swift │ │ ├── StructuredData+Init.swift │ │ ├── StructuredData+PathIndexable.swift │ │ ├── StructuredData+Polymorphic.swift │ │ └── StructuredData.swift │ ├── StructuredDataWrapper │ │ ├── StructuredDataWrapper+Cases.swift │ │ ├── StructuredDataWrapper+Convenience.swift │ │ ├── StructuredDataWrapper+Equatable.swift │ │ ├── StructuredDataWrapper+Literals.swift │ │ ├── StructuredDataWrapper+PathIndexable.swift │ │ ├── StructuredDataWrapper+Polymorphic.swift │ │ └── StructuredDataWrapper.swift │ └── Utilities │ │ ├── Errors.swift │ │ ├── Exports.swift │ │ └── Identifier.swift └── PathIndexable │ ├── PathIndexable+Subscripting.swift │ └── PathIndexable.swift ├── Tests ├── Info.plist ├── LinuxMain.swift ├── NodeTests │ ├── BasicConvertibleTests.swift │ ├── DictionaryKeyPathTests.swift │ ├── NodeBackedTests.swift │ ├── NodeDataTypeTests.swift │ ├── NodeGetterTests.swift │ ├── NodeIndexableTests.swift │ ├── NodePolymorphicTests.swift │ ├── NodeTests.swift │ ├── NumberTests.swift │ ├── SequenceConvertibleTests.swift │ └── SettersTests.swift └── PathIndexableTests │ └── PathIndexableTests.swift └── manifesto.md /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | macos: 5 | macos: 6 | xcode: "9.0" 7 | steps: 8 | - run: brew install vapor/tap/vapor 9 | - checkout 10 | - run: swift build 11 | - run: swift test 12 | 13 | linux-3: 14 | docker: 15 | - image: swift:3.1.1 16 | steps: 17 | - run: apt-get install -yq libssl-dev 18 | - checkout 19 | - run: swift build 20 | - run: swift test 21 | 22 | linux-4: 23 | docker: 24 | - image: swift:4.0.3 25 | steps: 26 | - run: apt-get install -yq libssl-dev 27 | - checkout 28 | - run: swift build 29 | - run: swift test 30 | 31 | workflows: 32 | version: 2 33 | tests: 34 | jobs: 35 | - macos 36 | - linux-3 37 | - linux-4 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | Package.pins 6 | Package.resolved 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /NodeCocoapods.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | # Node was taken, so using NodeCocoapods here ... we'll see how that works 3 | # in code do `#if COCOAPODS` to use correct import 4 | spec.name = 'NodeCocoapods' 5 | spec.version = '2.0.3' 6 | spec.license = 'MIT' 7 | spec.homepage = 'https://github.com/vapor/node' 8 | spec.authors = { 'Vapor' => 'contact@vapor.codes' } 9 | spec.summary = 'A formatted data encapsulation meant to facilitate the transformation from one object to another.' 10 | spec.source = { :git => "#{spec.homepage}.git", :tag => "#{spec.version}" } 11 | spec.ios.deployment_target = "8.0" 12 | spec.osx.deployment_target = "10.10" 13 | spec.watchos.deployment_target = "2.0" 14 | spec.tvos.deployment_target = "9.0" 15 | spec.requires_arc = true 16 | spec.social_media_url = 'https://twitter.com/codevapor' 17 | spec.default_subspec = "Default" 18 | 19 | spec.subspec "Default" do |ss| 20 | ss.source_files = 'Sources/**/*.{swift}' 21 | ss.dependency 'Core', '~> 2.0' 22 | ss.dependency 'Bits', '~> 1.0' 23 | ss.dependency 'Debugging', '~> 1.0' 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "Node", 5 | targets: [ 6 | Target(name: "Node", dependencies: ["PathIndexable"]), 7 | Target(name: "PathIndexable") 8 | ], 9 | dependencies: [ 10 | .Package(url: "https://github.com/vapor/core.git", majorVersion: 2), 11 | .Package(url: "https://github.com/vapor/bits.git", majorVersion: 1), 12 | .Package(url: "https://github.com/vapor/debugging.git", majorVersion: 1), 13 | ] 14 | ) 15 | -------------------------------------------------------------------------------- /Package@swift-4.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "Node", 6 | products: [ 7 | .library(name: "Node", targets: ["Node"]), 8 | .library(name: "PathIndexable", targets: ["PathIndexable"]), 9 | ], 10 | dependencies: [ 11 | .package(url: "https://github.com/vapor/core.git", .upToNextMajor(from: "2.0.0")), 12 | .package(url: "https://github.com/vapor/bits.git", .upToNextMajor(from: "1.0.0")), 13 | .package(url: "https://github.com/vapor/debugging.git", .upToNextMajor(from: "1.0.0")), 14 | ], 15 | targets: [ 16 | .target(name: "Node", dependencies: ["Bits", "Core", "PathIndexable"]), 17 | .testTarget(name: "NodeTests", dependencies: ["Node"]), 18 | .target(name: "PathIndexable"), 19 | .testTarget(name: "PathIndexableTests", dependencies: ["PathIndexable"]) 20 | ] 21 | ) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Node 3 |
4 |
5 | 6 | Documentation 7 | 8 | 9 | Slack Team 10 | 11 | 12 | MIT License 13 | 14 | 15 | Continuous Integration 16 | 17 | 18 | Swift 3.1 19 | 20 |

21 | -------------------------------------------------------------------------------- /Sources/Node/Accessors/Getters.swift: -------------------------------------------------------------------------------- 1 | // MARK: Transformers 2 | 3 | extension StructuredDataWrapper { 4 | public func get( 5 | _ indexers: PathIndexer..., 6 | transform: (InputType) throws -> T 7 | ) throws -> T { 8 | return try get(path: indexers, transform: transform) 9 | } 10 | 11 | public func get( 12 | path indexers: [PathIndexer], 13 | transform: (InputType) throws -> T 14 | ) throws -> T { 15 | let value = self[indexers] ?? .null 16 | let input = try InputType(node: value, in: context) 17 | return try transform(input) 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /Sources/Node/Accessors/Setters.swift: -------------------------------------------------------------------------------- 1 | extension StructuredDataWrapper { 2 | public mutating func removeKey(_ path: String) { 3 | self.wrapped[path] = nil 4 | } 5 | 6 | public mutating func removeKey(_ indexers: PathIndexer...) { 7 | self.wrapped[indexers] = nil 8 | } 9 | 10 | public mutating func removeKey(_ indexers: [PathIndexer]) { 11 | self.wrapped[indexers] = nil 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Node/Convertibles/Bool+Convertible.swift: -------------------------------------------------------------------------------- 1 | extension Bool: NodeConvertible { 2 | public init(node: Node) throws { 3 | guard let bool = node.bool else { 4 | throw NodeError.unableToConvert(input: node, expectation: "\(Bool.self)", path: []) 5 | } 6 | self = bool 7 | } 8 | 9 | public func makeNode(in context: Context?) -> Node { 10 | return .bool(self, in: context) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Node/Convertibles/Date+Convertible.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Date: NodeConvertible { 4 | internal static let lock = NSLock() 5 | 6 | /** 7 | If a date receives a numbered node, it will use this closure 8 | to convert that number into a Date as a timestamp 9 | 10 | By default, this timestamp uses seconds via timeIntervalSince1970. 11 | 12 | Override for custom implementations 13 | */ 14 | public static var incomingTimestamp: (Node.Number) throws -> Date = { 15 | return Date(timeIntervalSince1970: $0.double) 16 | } 17 | 18 | /** 19 | In default scenarios where a timestamp should be represented as a 20 | Number, this closure will be used. 21 | 22 | By default, uses seconds via timeIntervalSince1970. 23 | 24 | Override for custom implementations. 25 | */ 26 | public static var outgoingTimestamp: (Date) throws -> Node.Number = { 27 | return Node.Number($0.timeIntervalSince1970) 28 | } 29 | 30 | /** 31 | A prioritized list of date formatters to use when attempting 32 | to parse a String into a Date. 33 | 34 | Override for custom implementations, or to remove supported formats 35 | */ 36 | public static var incomingDateFormatters: [DateFormatter] = [ 37 | .iso8601, 38 | .mysql, 39 | .rfc1123 40 | ] 41 | 42 | /** 43 | A default formatter to use when serializing a Date object to 44 | a String. 45 | 46 | Defaults to ISO 8601 47 | 48 | Override for custom implementations. 49 | 50 | For complex scenarios where various string representations must be used, 51 | the user is responsible for handling their date formatting manually. 52 | */ 53 | public static var outgoingDateFormatter: DateFormatter = .iso8601 54 | 55 | /** 56 | Initializes a Date object with another Node.date, a number representing a timestamp, 57 | or a formatted date string corresponding to one of the `incomingDateFormatters`. 58 | */ 59 | public init(node: Node) throws { 60 | switch node.wrapped { 61 | case let .date(date): 62 | self = date 63 | case let .number(number): 64 | self = try Date.incomingTimestamp(number) 65 | case let .string(string): 66 | Date.lock.lock() 67 | defer { Date.lock.unlock() } 68 | guard 69 | let date = Date.incomingDateFormatters 70 | .lazy 71 | .flatMap({ $0.date(from: string) }) 72 | .first 73 | else { fallthrough } 74 | self = date 75 | default: 76 | throw NodeError.unableToConvert( 77 | input: node, 78 | expectation: "\(Date.self), formatted time string, or timestamp", 79 | path: [] 80 | ) 81 | } 82 | } 83 | 84 | /// Creates a node representation of the date 85 | public func makeNode(in context: Context?) throws -> Node { 86 | return .date(self, in: context) 87 | } 88 | } 89 | 90 | extension StructuredData { 91 | public var date: Date? { 92 | return try? Date(node: self, in: nil) 93 | } 94 | } 95 | 96 | extension DateFormatter { 97 | /** 98 | ISO8601 Date Formatter -- preferred in JSON 99 | 100 | http://stackoverflow.com/a/28016692/2611971 101 | */ 102 | @nonobjc public static let iso8601: DateFormatter = { 103 | let formatter = DateFormatter() 104 | formatter.locale = Locale(identifier: "en_US_POSIX") 105 | formatter.timeZone = TimeZone(secondsFromGMT: 0) 106 | formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX" 107 | return formatter 108 | }() 109 | } 110 | 111 | extension DateFormatter { 112 | /** 113 | A date formatter for mysql formatted types 114 | */ 115 | @nonobjc public static let mysql: DateFormatter = { 116 | let formatter = DateFormatter() 117 | formatter.timeZone = TimeZone(secondsFromGMT: 0) 118 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" 119 | return formatter 120 | }() 121 | } 122 | 123 | extension DateFormatter { 124 | /** 125 | A date formatter conforming to RFC 1123 spec 126 | */ 127 | @nonobjc public static let rfc1123: DateFormatter = { 128 | let formatter = DateFormatter() 129 | formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss z" 130 | return formatter 131 | }() 132 | } 133 | -------------------------------------------------------------------------------- /Sources/Node/Convertibles/FloatingPoint+Convertible.swift: -------------------------------------------------------------------------------- 1 | public protocol NodeConvertibleFloatingPointType: NodeConvertible { 2 | var doubleValue: Double { get } 3 | init(_ other: Double) 4 | } 5 | 6 | extension Float: NodeConvertibleFloatingPointType { 7 | public var doubleValue: Double { 8 | return Double(self) 9 | } 10 | } 11 | 12 | extension Double: NodeConvertibleFloatingPointType { 13 | public var doubleValue: Double { 14 | return Double(self) 15 | } 16 | } 17 | 18 | extension NodeConvertibleFloatingPointType { 19 | public init(node: Node) throws { 20 | guard let double = node.double else { 21 | throw NodeError.unableToConvert(input: node, expectation: "\(Self.self)", path: []) 22 | } 23 | self.init(double) 24 | } 25 | 26 | public func makeNode(in context: Context?) -> Node { 27 | return .number(StructuredData.Number(doubleValue), in: context) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Node/Convertibles/Integer+Convertible.swift: -------------------------------------------------------------------------------- 1 | extension Int: NodeConvertible {} 2 | extension Int8: NodeConvertible {} 3 | extension Int16: NodeConvertible {} 4 | extension Int32: NodeConvertible {} 5 | extension Int64: NodeConvertible {} 6 | 7 | extension SignedInteger { 8 | public init(node: Node) throws { 9 | guard let int = node.int else { 10 | throw NodeError.unableToConvert(input: node, expectation: "\(Self.self)", path: []) 11 | } 12 | 13 | #if swift(>=4) 14 | self.init(Int64(int)) 15 | #else 16 | self.init(int.toIntMax()) 17 | #endif 18 | } 19 | 20 | public func makeNode(in context: Context?) -> Node { 21 | #if swift(>=4) 22 | let max = Int64(self) 23 | #else 24 | let max = self.toIntMax() 25 | #endif 26 | let number = StructuredData.Number(max) 27 | return .number(number, in: context) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Node/Convertibles/Schema+Convertible.swift: -------------------------------------------------------------------------------- 1 | extension StructuredData: NodeConvertible { 2 | public init(node: Node) { 3 | self = node.wrapped 4 | } 5 | 6 | public func makeNode(in context: Context?) -> Node { 7 | return Node(self, in: context) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Sources/Node/Convertibles/SchemaWrapper+Convertible.swift: -------------------------------------------------------------------------------- 1 | extension StructuredDataWrapper { 2 | public func makeNode(in context: Context?) -> Node { 3 | let context = context ?? self.context 4 | return Node(wrapped, in: context) 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/Node/Convertibles/String+Convertible.swift: -------------------------------------------------------------------------------- 1 | extension String: NodeConvertible { 2 | public init(node: Node) throws { 3 | guard let string = node.string else { 4 | throw NodeError.unableToConvert(input: node, expectation: "\(String.self)", path: []) 5 | } 6 | self = string 7 | } 8 | 9 | public func makeNode(in context: Context?) -> Node { 10 | return .string(self, in: context) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Node/Convertibles/UUID+Convertible.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension UUID: NodeConvertible { 4 | public init(node: Node) throws { 5 | guard let string = node.string, let uuid = UUID(uuidString: string) else { 6 | throw NodeError.unableToConvert(input: node, expectation: "\(UUID.self)", path: []) 7 | } 8 | self = uuid 9 | } 10 | 11 | public func makeNode(in context: Context?) -> Node { 12 | return uuidString.makeNode(in: context) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Node/Convertibles/UnsignedInteger+Convertible.swift: -------------------------------------------------------------------------------- 1 | extension UInt: NodeConvertible {} 2 | extension UInt8: NodeConvertible {} 3 | extension UInt16: NodeConvertible {} 4 | extension UInt32: NodeConvertible {} 5 | extension UInt64: NodeConvertible {} 6 | 7 | extension UnsignedInteger { 8 | public init(node: Node) throws { 9 | guard let int = node.uint else { 10 | throw NodeError.unableToConvert(input: node, expectation: "\(Self.self)", path: []) 11 | } 12 | 13 | #if swift(>=4) 14 | self.init(UInt64(int)) 15 | #else 16 | self.init(int.toUIntMax()) 17 | #endif 18 | } 19 | 20 | public func makeNode(in context: Context?) -> Node { 21 | #if swift(>=4) 22 | let max = UInt64(self) 23 | #else 24 | let max = self.toUIntMax() 25 | #endif 26 | let number = Node.Number(max) 27 | return .number(number, in: context) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Node/Core/Context.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Sometimes convertible operations require a greater context beyond 3 | just a Node. 4 | 5 | Any object can conform to Context and be included in initialization 6 | */ 7 | public protocol Context {} 8 | 9 | public final class ObjectContext: Context { 10 | public let object: [K: V] 11 | public init(_ object: [K: V]) { 12 | self.object = object 13 | } 14 | } 15 | 16 | public let emptyContext = ObjectContext([:]) 17 | -------------------------------------------------------------------------------- /Sources/Node/Core/Node.swift: -------------------------------------------------------------------------------- 1 | public struct Node: StructuredDataWrapper { 2 | public static let defaultContext = emptyContext 3 | 4 | public var wrapped: StructuredData 5 | public var context: Context 6 | 7 | public init(_ wrapped: StructuredData, in context: Context?) { 8 | self.wrapped = wrapped 9 | self.context = context ?? emptyContext 10 | } 11 | } 12 | 13 | extension Node: FuzzyConverter { 14 | public static func represent(_ any: T, in context: Context) throws -> Node? { 15 | guard let r = any as? NodeRepresentable else { 16 | return nil 17 | } 18 | return try r.makeNode(in: context) 19 | } 20 | 21 | public static func initialize(node: Node) throws -> T? { 22 | guard let type = T.self as? NodeInitializable.Type else { 23 | return nil 24 | } 25 | 26 | return try type.init(node: node) as? T 27 | } 28 | } 29 | 30 | extension Node: NodeConvertible { 31 | public init(node: Node) { 32 | self = node 33 | } 34 | 35 | public func makeNode(in context: Context?) -> Node { 36 | return self 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Node/Core/NodeConvertible.swift: -------------------------------------------------------------------------------- 1 | 2 | /// The underlying protocol used for all conversions. 3 | /// This is the base of all conversions, where both sides of data are NodeConvertible. 4 | /// Any NodeConvertible can be turned into any other NodeConvertible type 5 | /// 6 | /// Json => Node => Object => Node => XML => ... 7 | public typealias NodeConvertible = NodeInitializable & NodeRepresentable 8 | -------------------------------------------------------------------------------- /Sources/Node/Core/NodeInitializable.swift: -------------------------------------------------------------------------------- 1 | public protocol NodeInitializable { 2 | /// Initialize the convertible with a node within a context. 3 | /// 4 | /// Context is an empty protocol to which any type can conform. 5 | /// This allows flexibility. for objects that might require access 6 | /// to a context outside of the node ecosystem 7 | init(node: Node) throws 8 | } 9 | 10 | extension NodeInitializable { 11 | public init(node representable: NodeRepresentable?, in context: Context? = nil) throws { 12 | let node = try representable?.makeNode(in: context) ?? Node(.null, in: context) 13 | try self.init(node: node) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Node/Core/NodeRepresentable.swift: -------------------------------------------------------------------------------- 1 | public protocol NodeRepresentable { 2 | /// Able to be represented as a Node 3 | /// 4 | /// - throws: if convertible can not create a Node 5 | /// - returns: a node if possible 6 | func makeNode(in context: Context?) throws -> Node 7 | } 8 | 9 | extension NodeRepresentable { 10 | /** 11 | Map the node back to a convertible type 12 | 13 | - parameter type: the type to map to -- can be inferred 14 | - throws: if mapping fails 15 | - returns: convertible representation of object 16 | */ 17 | public func converted( 18 | to type: T.Type = T.self, 19 | in context: Context? 20 | ) throws -> T { 21 | let node = try makeNode(in: context) 22 | return try type.init(node: node) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Node/Fuzzy/Array+Convertible.swift: -------------------------------------------------------------------------------- 1 | extension Array: NodeConvertible { 2 | public init(node: Node) throws { 3 | let array = node.array ?? [node] 4 | 5 | self = try array.map { n in 6 | return try Node.fuzzy.initialize(node: n) 7 | } 8 | } 9 | 10 | public func makeNode(in context: Context?) throws -> Node { 11 | let nodes: [Node] = try map { item in 12 | return try Node.fuzzy.represent(item, in: context) 13 | } 14 | return Node(nodes) 15 | } 16 | } 17 | 18 | extension StructuredDataWrapper { 19 | public mutating func set(_ path: String, _ any: [Any?]?) throws { 20 | try set(path, any.makeNode(in: context)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Node/Fuzzy/Dictionary+Convertible.swift: -------------------------------------------------------------------------------- 1 | extension Dictionary: NodeConvertible { 2 | public init(node: Node) throws { 3 | guard Key.self is String.Type else { 4 | throw NodeError.invalidDictionaryKeyType 5 | } 6 | 7 | guard let object = node.object else { 8 | throw NodeError.unableToConvert( 9 | input: node, 10 | expectation: "\([Key: Value].self)", 11 | path: [] 12 | ) 13 | } 14 | 15 | var mapped: [Key: Value] = [:] 16 | try object.forEach { key, node in 17 | let key = key as! Key 18 | let val: Value = try Node.fuzzy.initialize(node: node) 19 | mapped[key] = val 20 | } 21 | self = mapped 22 | } 23 | 24 | public func makeNode(in context: Context?) throws -> Node { 25 | guard Key.self is String.Type else { 26 | throw NodeError.invalidDictionaryKeyType 27 | } 28 | 29 | var nodes: [String: Node] = [:] 30 | try forEach { (key, value) in 31 | nodes[key as! String] = try Node.fuzzy.represent(value, in: context) 32 | } 33 | 34 | return Node(nodes) 35 | } 36 | } 37 | 38 | extension StructuredDataWrapper { 39 | public mutating func set(_ path: String, _ any: [String: Any?]?) throws { 40 | try set(path, any.makeNode(in: context)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Node/Fuzzy/Fuzzy+Any.swift: -------------------------------------------------------------------------------- 1 | extension StructuredDataWrapper { 2 | public mutating func set(_ path: String, _ any: Any?) throws { 3 | let value = try Node.fuzzy.represent(any, in: context) 4 | wrapped[path] = value.wrapped 5 | } 6 | 7 | public func get(_ path: String) throws -> T { 8 | let data = wrapped[path] ?? .null 9 | let node = Node(data, in: context) 10 | return try Node.fuzzy.initialize(node: node) 11 | } 12 | 13 | public func get() throws -> T { 14 | let node = Node(wrapped, in: context) 15 | return try Node.fuzzy.initialize(node: node) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Node/Fuzzy/FuzzyConverter.swift: -------------------------------------------------------------------------------- 1 | public protocol FuzzyConverter { 2 | static func represent( 3 | _ any: T, 4 | in context: Context 5 | ) throws -> Node? 6 | 7 | static func initialize( 8 | node: Node 9 | ) throws -> T? 10 | } 11 | 12 | private var _fuzzyTypes: [FuzzyConverter.Type] = [Node.self] 13 | 14 | extension Node { 15 | public static var fuzzy: [FuzzyConverter.Type] { 16 | get { return _fuzzyTypes } 17 | set { _fuzzyTypes = newValue } 18 | } 19 | } 20 | 21 | extension Array where Iterator.Element == FuzzyConverter.Type { 22 | func initialize(node: Node) throws -> T { 23 | var maybe: T? 24 | for fuzzy in Node.fuzzy { 25 | if let any: T = try fuzzy.initialize(node: node) { 26 | maybe = any 27 | break 28 | } 29 | } 30 | 31 | guard let wrapped = maybe else { 32 | throw NodeError.noFuzzyConverter(item: nil, type: T.self) 33 | } 34 | 35 | return wrapped 36 | } 37 | 38 | func represent(_ any: T, in context: Context?) throws -> Node { 39 | var maybe: Node? 40 | for fuzzy in Node.fuzzy { 41 | if let data = try fuzzy.represent(any, in: context ?? Node.defaultContext) { 42 | maybe = data 43 | break 44 | } 45 | } 46 | 47 | guard let node = maybe else { 48 | throw NodeError.noFuzzyConverter(item: any, type: T.self) 49 | } 50 | 51 | return node 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/Node/Fuzzy/Optional+Convertible.swift: -------------------------------------------------------------------------------- 1 | extension Optional: NodeConvertible { 2 | public init(node: Node) throws { 3 | guard node != .null else { 4 | self = .none 5 | return 6 | } 7 | 8 | let wrapped: Wrapped = try Node.fuzzy.initialize(node: node) 9 | self = .some(wrapped) 10 | } 11 | 12 | public func makeNode(in context: Context?) throws -> Node { 13 | switch self { 14 | case .none: 15 | return .null 16 | case .some(let some): 17 | return try Node.fuzzy.represent(some, in: context) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Node/Fuzzy/Set+Convertible.swift: -------------------------------------------------------------------------------- 1 | extension Set: NodeConvertible { 2 | public init(node: Node) throws { 3 | let array = try [Element](node: node) 4 | self = Set(array) 5 | } 6 | 7 | public func makeNode(in context: Context?) throws -> Node { 8 | let array = Array(self) 9 | return try array.makeNode(in: context) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Node/Number/Number.swift: -------------------------------------------------------------------------------- 1 | extension Node { 2 | public typealias Number = StructuredData.Number 3 | } 4 | 5 | extension StructuredData { 6 | /// A more comprehensive Number encapsulation to allow 7 | /// more nuanced number information to be stored 8 | public enum Number { 9 | case int(Int) 10 | case uint(UInt) 11 | case double(Double) 12 | } 13 | } 14 | 15 | // MARK: Initializers 16 | 17 | extension StructuredData.Number { 18 | #if swift(>=4) 19 | public init(_ value: I) { 20 | let max = Int64(value) 21 | let int = Int(max) 22 | self = .int(int) 23 | } 24 | #else 25 | public init(_ value: I) { 26 | let max = value.toIntMax() 27 | let int = Int(max) 28 | self = .int(int) 29 | } 30 | #endif 31 | 32 | public init(_ value: U) { 33 | #if swift(>=4) 34 | let max = UInt64(value) 35 | #else 36 | let max = value.toUIntMax() 37 | #endif 38 | let uint = UInt(max) 39 | self = .uint(uint) 40 | } 41 | 42 | public init(_ value: Float) { 43 | let double = Double(value) 44 | self = .init(double) 45 | } 46 | 47 | public init(_ value: Double) { 48 | self = .double(value) 49 | } 50 | } 51 | 52 | extension String { 53 | fileprivate var number: StructuredData.Number? { 54 | if self.contains(".") { 55 | return Double(self).flatMap { StructuredData.Number($0) } 56 | } 57 | 58 | guard hasPrefix("-") else { return UInt(self).flatMap { StructuredData.Number($0) } } 59 | return Int(self).flatMap { StructuredData.Number($0) } 60 | } 61 | } 62 | 63 | // MARK: Accessors 64 | 65 | extension UInt { 66 | internal static var intMax = UInt(Int.max) 67 | } 68 | 69 | extension StructuredData.Number { 70 | public var int: Int { 71 | switch self { 72 | case let .int(i): 73 | return i 74 | case let .uint(u): 75 | guard u < UInt.intMax else { return Int.max } 76 | return Int(u) 77 | case let .double(d): 78 | guard d < Double(Int.max) else { return Int.max } 79 | guard d > Double(Int.min) else { return Int.min } 80 | return Int(d) 81 | } 82 | } 83 | 84 | public var uint: UInt { 85 | switch self { 86 | case let .int(i): 87 | guard i > 0 else { return 0 } 88 | return UInt(i) 89 | case let .uint(u): 90 | return u 91 | case let .double(d): 92 | guard d < Double(UInt.max) else { return UInt.max } 93 | return UInt(d) 94 | } 95 | } 96 | 97 | public var double: Double { 98 | switch self { 99 | case let .int(i): 100 | return Double(i) 101 | case let .uint(u): 102 | return Double(u) 103 | case let .double(d): 104 | return Double(d) 105 | } 106 | } 107 | } 108 | 109 | extension StructuredData.Number { 110 | public var bool: Bool? { 111 | switch self { 112 | case let .int(i): 113 | switch i { 114 | case 1: return true 115 | case 0: return false 116 | default: 117 | return nil 118 | } 119 | case let .uint(u): 120 | switch u { 121 | case 1: return true 122 | case 0: return false 123 | default: 124 | return nil 125 | } 126 | case let .double(d): 127 | switch d { 128 | case 1.0: return true 129 | case 0.0: return false 130 | default: 131 | return nil 132 | } 133 | } 134 | } 135 | } 136 | 137 | // MARK: Equatable 138 | 139 | extension StructuredData.Number: Equatable {} 140 | 141 | public func ==(lhs: StructuredData.Number, rhs: StructuredData.Number) -> Bool { 142 | switch (lhs, rhs) { 143 | case let (.int(l), .int(r)): 144 | return l == r 145 | case let (.int(l), .uint(r)): 146 | guard l >= 0 && r <= UInt(Int.max) else { return false } 147 | return l == Int(r) 148 | case let (.int(l), .double(r)): 149 | guard r.truncatingRemainder(dividingBy: 1) == 0.0 else { return false } 150 | return l == Int(r) 151 | case let (.uint(l), .int(r)): 152 | guard l <= UInt(Int.max) && r >= 0 else { return false } 153 | return Int(l) == r 154 | case let (.uint(l), .uint(r)): 155 | return l == r 156 | case let (.uint(l), .double(r)): 157 | guard r >= 0 && r.truncatingRemainder(dividingBy: 1) == 0.0 else { return false } 158 | return l == UInt(r) 159 | case let (.double(l), .int(r)): 160 | guard l.truncatingRemainder(dividingBy: 1) == 0.0 else { return false } 161 | return Int(l) == r 162 | case let (.double(l), .uint(r)): 163 | guard l.truncatingRemainder(dividingBy: 1) == 0.0 else { return false } 164 | return UInt(l) == r 165 | case let (.double(l), .double(r)): 166 | return l == r 167 | } 168 | } 169 | 170 | // MARK: Literals 171 | 172 | extension StructuredData.Number: ExpressibleByIntegerLiteral { 173 | public init(integerLiteral value: IntegerLiteralType) { 174 | self.init(value) 175 | } 176 | } 177 | 178 | extension StructuredData.Number: ExpressibleByFloatLiteral { 179 | public init(floatLiteral value: FloatLiteralType) { 180 | self.init(value) 181 | } 182 | } 183 | 184 | // MARK: String 185 | 186 | extension StructuredData.Number: CustomStringConvertible { 187 | public var description: String { 188 | switch self { 189 | case let .int(i): 190 | return i.description 191 | case let .uint(u): 192 | return u.description 193 | case let .double(d): 194 | return d.description 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /Sources/Node/StructuredData/StructuredData+Equatable.swift: -------------------------------------------------------------------------------- 1 | extension StructuredData: Equatable {} 2 | 3 | public func ==(lhs: StructuredData, rhs: StructuredData) -> Bool { 4 | switch (lhs, rhs) { 5 | case (.null, .null): 6 | return true 7 | case let (.bool(l), .bool(r)): 8 | return l == r 9 | case let (.number(l), .number(r)): 10 | return l == r 11 | case let (.string(l), .string(r)): 12 | return l == r 13 | case let (.array(l), .array(r)): 14 | return l == r 15 | case let (.object(l), .object(r)): 16 | return l == r 17 | case let (.bytes(l), .bytes(r)): 18 | return l == r 19 | case let (.date(l), .date(r)): 20 | return l.timeIntervalSince1970 == r.timeIntervalSince1970 21 | default: 22 | return false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Node/StructuredData/StructuredData+Init.swift: -------------------------------------------------------------------------------- 1 | extension StructuredDataWrapper { 2 | public init(_ value: Bool, in context: Context? = Self.defaultContext) { 3 | self = .bool(value, in: context) 4 | } 5 | 6 | public init(_ value: String, in context: Context? = Self.defaultContext) { 7 | self = .string(value, in: context) 8 | } 9 | 10 | public init(_ int: Int, in context: Context? = Self.defaultContext) { 11 | self = .number(StructuredData.Number(int), in: context) 12 | } 13 | 14 | public init(_ double: Double, in context: Context? = Self.defaultContext) { 15 | self = .number(StructuredData.Number(double), in: context) 16 | } 17 | 18 | public init(_ uint: UInt, in context: Context? = Self.defaultContext) { 19 | self = .number(StructuredData.Number(uint), in: context) 20 | } 21 | 22 | public init(_ number: StructuredData.Number, in context: Context? = Self.defaultContext) { 23 | self = .number(number, in: context) 24 | } 25 | 26 | public init(_ value: [StructuredData], in context: Context? = Self.defaultContext) { 27 | let array = StructuredData.array(value) 28 | self.init(array, in: context) 29 | } 30 | 31 | public init(_ value: [String : StructuredData], in context: Context? = Self.defaultContext) { 32 | self = Self(.object(value), in: context) 33 | } 34 | 35 | public init(bytes: [UInt8], in context: Context? = Self.defaultContext) { 36 | self = .bytes(bytes, in: context) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Node/StructuredData/StructuredData+PathIndexable.swift: -------------------------------------------------------------------------------- 1 | extension StructuredData: PathIndexable { 2 | /// If self is an array representation, return array 3 | public var pathIndexableArray: [StructuredData]? { 4 | return array 5 | } 6 | 7 | /// If self is an object representation, return object 8 | public var pathIndexableObject: [String: StructuredData]? { 9 | return object 10 | } 11 | 12 | public init(_ array: [StructuredData]) { 13 | self = .array(array) 14 | } 15 | 16 | public init(_ object: [String: StructuredData]) { 17 | self = .object(object) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Node/StructuredData/StructuredData+Polymorphic.swift: -------------------------------------------------------------------------------- 1 | import Bits 2 | import Core 3 | 4 | extension StructuredData { 5 | public var string: String? { 6 | switch self { 7 | case .bool(let bool): 8 | return "\(bool)" 9 | case .number(let number): 10 | return "\(number)" 11 | case .string(let string): 12 | return string 13 | case .date(let date): 14 | Date.lock.lock() 15 | let string = Date.outgoingDateFormatter.string(from: date) 16 | Date.lock.unlock() 17 | return string 18 | case .bytes(let bytes): 19 | return bytes.makeString() 20 | default: 21 | return nil 22 | } 23 | } 24 | 25 | public var int: Int? { 26 | switch self { 27 | case .string(let string): 28 | return string.int 29 | case .number(let number): 30 | return number.int 31 | case .bool(let bool): 32 | return bool ? 1 : 0 33 | case .date(let date): 34 | return try? Date.outgoingTimestamp(date).int 35 | default: 36 | return nil 37 | } 38 | } 39 | 40 | public var uint: UInt? { 41 | switch self { 42 | case .string(let string): 43 | return string.uint 44 | case .number(let number): 45 | return number.uint 46 | case .bool(let bool): 47 | return bool ? 1 : 0 48 | case .date(let date): 49 | return try? Date.outgoingTimestamp(date).uint 50 | default: 51 | return nil 52 | } 53 | } 54 | 55 | public var double: Double? { 56 | switch self { 57 | case .number(let number): 58 | return number.double 59 | case .string(let string): 60 | return string.double 61 | case .bool(let bool): 62 | return bool ? 1.0 : 0.0 63 | case .date(let date): 64 | return try? Date.outgoingTimestamp(date).double 65 | default: 66 | return nil 67 | } 68 | } 69 | 70 | public var isNull: Bool { 71 | switch self { 72 | case .null: 73 | return true 74 | case .string(let string): 75 | return string.isNull 76 | default: 77 | return false 78 | } 79 | } 80 | 81 | public var bool: Bool? { 82 | switch self { 83 | case .bool(let bool): 84 | return bool 85 | case .number(let number): 86 | return number.bool 87 | case .string(let string): 88 | return string.bool 89 | case .null: 90 | return false 91 | default: 92 | return nil 93 | } 94 | } 95 | 96 | public var float: Float? { 97 | switch self { 98 | case .number(let number): 99 | return Float(number.double) 100 | case .string(let string): 101 | return string.float 102 | case .bool(let bool): 103 | return bool ? 1.0 : 0.0 104 | case .date(let date): 105 | let double = try? Date.outgoingTimestamp(date).double 106 | return double.flatMap { Float($0) } 107 | default: 108 | return nil 109 | } 110 | } 111 | 112 | public var array: [StructuredData]? { 113 | guard case let .array(array) = self else { return nil } 114 | return array 115 | } 116 | 117 | public var object: [String: StructuredData]? { 118 | guard case let .object(ob) = self else { return nil } 119 | return ob 120 | } 121 | 122 | public var bytes: [UInt8]? { 123 | switch self { 124 | case .bytes(let bytes): 125 | return bytes 126 | case .string(let string): 127 | return string.bytes 128 | default: 129 | return nil 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Sources/Node/StructuredData/StructuredData.swift: -------------------------------------------------------------------------------- 1 | @_exported import struct Foundation.Date 2 | 3 | 4 | /// StructuredData is meant a data structure that can be used to facilitate different 5 | /// structured data formats 6 | public enum StructuredData { 7 | case null 8 | case bool(Bool) 9 | case number(Number) 10 | case string(String) 11 | case array([StructuredData]) 12 | case object([String: StructuredData]) 13 | case bytes([UInt8]) 14 | case date(Date) 15 | } 16 | 17 | extension StructuredData { 18 | public init() { 19 | self.init([:]) 20 | } 21 | } 22 | 23 | extension StructuredData: CustomStringConvertible { 24 | public var description: String { 25 | switch self { 26 | case .null: 27 | return "null" 28 | case .bool(let bool): 29 | return bool.description 30 | case .number(let number): 31 | return number.description 32 | case .string(let string): 33 | return string.description 34 | case .array(let array): 35 | let string = array.map { $0.description } .joined(separator: ", ") 36 | return "[\(string)]" 37 | case .object(let ob): 38 | let string = ob.map { key, value in "\(key): \(value)" } .joined(separator: ", ") 39 | return "[\(string)]" 40 | case .bytes(let bytes): 41 | return "\(bytes)" 42 | case .date(let date): 43 | return "\(date)" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/Node/StructuredDataWrapper/StructuredDataWrapper+Cases.swift: -------------------------------------------------------------------------------- 1 | import Bits 2 | 3 | /// This allows schema wrappers to function a bit more like StructuredData 4 | /// slightly like their enum counterparts passing things like 5 | /// `.null` or `.object(foo)` 6 | extension StructuredDataWrapper { 7 | public static var null: Self { 8 | return Self(.null, in: nil) 9 | } 10 | 11 | public static func bool(_ val: Bool, in context: Context? = Self.defaultContext) -> Self { 12 | return Self(.bool(val), in: context) 13 | } 14 | 15 | public static func date(_ val: Date, in context: Context? = Self.defaultContext) -> Self { 16 | return Self(.date(val), in: context) 17 | } 18 | 19 | public static func number(_ val: StructuredData.Number, in context: Context? = Self.defaultContext) -> Self { 20 | return Self(.number(val), in: context) 21 | } 22 | 23 | public static func string(_ val: String, in context: Context? = Self.defaultContext) -> Self { 24 | return Self(.string(val), in: context) 25 | } 26 | 27 | public static func bytes(_ val: Bytes, in context: Context? = Self.defaultContext) -> Self { 28 | return Self(.bytes(val), in: context) 29 | } 30 | 31 | public static func object(_ val: [String: S], in context: Context? = Self.defaultContext) -> Self { 32 | var new = [String: StructuredData]() 33 | val.forEach { key, value in 34 | new[key] = value.wrapped 35 | } 36 | 37 | // context should be same for all 38 | return Self(.object(new), in: context ?? val.values.first?.context) 39 | } 40 | 41 | public static func object(_ val: [String: Self], in context: Context? = Self.defaultContext) -> Self { 42 | var new = [String: StructuredData]() 43 | val.forEach { key, value in 44 | new[key] = value.wrapped 45 | } 46 | 47 | // context should be same for all 48 | return Self(.object(new), in: context ?? val.values.first?.context) 49 | } 50 | 51 | public static func array(_ val: [S], in context: Context? = Self.defaultContext) -> Self { 52 | let new = val.map { $0.wrapped } 53 | // context should be same for all 54 | return Self(.array(new), in: context ?? val.first?.context) 55 | } 56 | 57 | public static func array(_ val: [Self], in context: Context? = Self.defaultContext) -> Self { 58 | let new = val.map { $0.wrapped } 59 | // context should be same for all 60 | return Self(.array(new), in: context ?? val.first?.context) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Node/StructuredDataWrapper/StructuredDataWrapper+Convenience.swift: -------------------------------------------------------------------------------- 1 | extension StructuredDataWrapper { 2 | public init(_ wrapper: S) { 3 | self.init(wrapper.wrapped, in: wrapper.context) 4 | } 5 | 6 | public init(node: StructuredData, in context: Context? = Self.defaultContext) { 7 | self.init(node, in: context) 8 | } 9 | 10 | public init(node: NodeRepresentable, in context: Context? = Self.defaultContext) throws { 11 | let node = try node.makeNode(in: context) 12 | self.init(node) 13 | } 14 | 15 | public init(_ context: Context?) { 16 | self.init(.object([:]), in: context) 17 | } 18 | 19 | public init(_ wrapped: StructuredData, _ context: Context) { 20 | self.init(wrapped, in: context) 21 | } 22 | 23 | public init(_ wrapped: StructuredData) { 24 | self.init(wrapped, in: Self.defaultContext) 25 | } 26 | 27 | public func converted(to type: T.Type = T.self) -> T { 28 | return T(wrapped, in: context) 29 | } 30 | 31 | public func converted(to type: T.Type = T.self) throws -> T { 32 | return try T.init(node: self) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Node/StructuredDataWrapper/StructuredDataWrapper+Equatable.swift: -------------------------------------------------------------------------------- 1 | extension StructuredDataWrapper { 2 | public static func == (lhs: Self, rhs: Self) -> Bool { 3 | return lhs.wrapped == rhs.wrapped 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Sources/Node/StructuredDataWrapper/StructuredDataWrapper+Literals.swift: -------------------------------------------------------------------------------- 1 | 2 | extension StructuredDataWrapper { // : ExpressibleByNilLiteral { 3 | public init(nilLiteral value: Void) { 4 | self = Self(.null, in: Self.defaultContext) 5 | } 6 | } 7 | 8 | extension StructuredDataWrapper { // : ExpressibleByBooleanLiteral { 9 | public init(booleanLiteral value: Bool) { 10 | self = .bool(value, in: Self.defaultContext) 11 | } 12 | } 13 | 14 | extension StructuredDataWrapper { // : ExpressibleByIntegerLiteral { 15 | public init(integerLiteral value: Int) { 16 | self = .number(.init(value), in: Self.defaultContext) 17 | } 18 | } 19 | 20 | extension StructuredDataWrapper { // : ExpressibleByFloatLiteral { 21 | public init(floatLiteral value: Double) { 22 | self = .number(.init(value), in: Self.defaultContext) 23 | } 24 | } 25 | 26 | extension StructuredDataWrapper { // : ExpressibleByStringLiteral { 27 | public init(unicodeScalarLiteral value: String) { 28 | self = .string(value, in: Self.defaultContext) 29 | } 30 | 31 | public init(extendedGraphemeClusterLiteral value: String) { 32 | self = .string(value, in: Self.defaultContext) 33 | } 34 | 35 | public init(stringLiteral value: String) { 36 | self = .string(value, in: Self.defaultContext) 37 | } 38 | } 39 | 40 | extension StructuredDataWrapper { // : ExpressibleByArrayLiteral { 41 | public init(arrayLiteral elements: Self...) { 42 | self = .array(elements, in: Self.defaultContext) 43 | } 44 | } 45 | 46 | extension StructuredDataWrapper { // : ExpressibleByDictionaryLiteral { 47 | public init(dictionaryLiteral elements: (String, Self)...) { 48 | var new = [String: Self]() 49 | elements.forEach { key, value in 50 | new[key] = value 51 | } 52 | self = .object(new, in: Self.defaultContext) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/Node/StructuredDataWrapper/StructuredDataWrapper+PathIndexable.swift: -------------------------------------------------------------------------------- 1 | extension StructuredDataWrapper { 2 | 3 | /** 4 | If self is an array representation, return array 5 | */ 6 | public var pathIndexableArray: [Self]? { 7 | return wrapped.array?.map { Self($0, in: context) } 8 | } 9 | 10 | /** 11 | If self is an object representation, return object 12 | */ 13 | public var pathIndexableObject: [String: Self]? { 14 | guard let o = wrapped.object else { return nil } 15 | var object: [String: Self] = [:] 16 | o.forEach { key, val in 17 | object[key] = Self(val, in: context) 18 | } 19 | return object 20 | } 21 | 22 | /** 23 | Initialize json w/ array 24 | */ 25 | public init(_ array: [Self]) { 26 | let schema = array.map { $0.wrapped } 27 | let node = StructuredData.array(schema) 28 | 29 | // take first context to attempt inference, should all be same 30 | let context = array.lazy.flatMap { $0.context } .first 31 | self.init(node, in: context) 32 | } 33 | 34 | /** 35 | Initialize json w/ object 36 | */ 37 | public init(_ o: [String: Self]) { 38 | var object: [String: StructuredData] = [:] 39 | for (key, val) in o { 40 | object[key] = val.wrapped 41 | } 42 | let schema = StructuredData.object(object) 43 | 44 | // take first context to attempt inference, should all be same 45 | #if swift(>=4.1) 46 | let context = o.values.lazy.compactMap { $0.context } .first 47 | #else 48 | let context = o.values.lazy.flatMap { $0.context } .first 49 | #endif 50 | self.init(schema, in: context) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/Node/StructuredDataWrapper/StructuredDataWrapper+Polymorphic.swift: -------------------------------------------------------------------------------- 1 | extension StructuredDataWrapper { 2 | public var isNull: Bool { 3 | return wrapped.isNull 4 | } 5 | 6 | public var bool: Bool? { 7 | return wrapped.bool 8 | } 9 | 10 | public var double: Double? { 11 | return wrapped.double 12 | } 13 | 14 | public var float: Float? { 15 | return wrapped.float 16 | } 17 | 18 | public var int: Int? { 19 | return wrapped.int 20 | } 21 | 22 | public var uint: UInt? { 23 | return wrapped.uint 24 | } 25 | 26 | public var string: String? { 27 | return wrapped.string 28 | } 29 | 30 | public var bytes: [UInt8]? { 31 | return wrapped.bytes 32 | } 33 | 34 | public var date: Date? { 35 | return wrapped.date 36 | } 37 | 38 | public var array: [Self]? { 39 | return wrapped.array?.map { item in 40 | Self(item, context) 41 | } 42 | } 43 | 44 | public var object: [String: Self]? { 45 | guard let object = wrapped.object else { return nil } 46 | var mutable: [String: Self] = [:] 47 | object.forEach { k, v in 48 | mutable[k] = Self(v, context) 49 | } 50 | return mutable 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/Node/StructuredDataWrapper/StructuredDataWrapper.swift: -------------------------------------------------------------------------------- 1 | public protocol StructuredDataWrapper: 2 | NodeConvertible, 3 | PathIndexable, 4 | Equatable, 5 | ExpressibleByNilLiteral, 6 | ExpressibleByBooleanLiteral, 7 | ExpressibleByIntegerLiteral, 8 | ExpressibleByFloatLiteral, 9 | ExpressibleByStringLiteral, 10 | ExpressibleByArrayLiteral, 11 | ExpressibleByDictionaryLiteral 12 | { 13 | static var defaultContext: Context? { get } 14 | 15 | var wrapped: StructuredData { get set } 16 | var context: Context { get } 17 | init(_ wrapped: StructuredData, in context: Context?) 18 | } 19 | 20 | extension StructuredDataWrapper { 21 | public static var defaultContext: Context? { return nil } 22 | public init(node: Node) { 23 | self.init(node.wrapped, in: node.context) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Node/Utilities/Errors.swift: -------------------------------------------------------------------------------- 1 | @_exported import Debugging 2 | 3 | public enum NodeError: Debuggable { 4 | case noFuzzyConverter(item: Any?, type: Any.Type) 5 | case invalidDictionaryKeyType 6 | case unableToConvert(input: Node?, expectation: String, path: [PathIndexer]) 7 | } 8 | 9 | extension NodeError { 10 | func appendPath(_ path: [PathIndexer]) -> NodeError { 11 | switch self { 12 | case .unableToConvert( 13 | input: let input, 14 | expectation: let expectation, 15 | path: let existing 16 | ) where existing.isEmpty: 17 | return .unableToConvert(input: input, expectation: expectation, path: path) 18 | default: 19 | return self 20 | } 21 | } 22 | } 23 | 24 | extension NodeError { 25 | public var identifier: String { 26 | switch self { 27 | case .noFuzzyConverter: 28 | return "noFuzzyConverter" 29 | case .invalidDictionaryKeyType: 30 | return "invalidDictionaryKeyType" 31 | case .unableToConvert: 32 | return "unableToConvert" 33 | } 34 | } 35 | 36 | public var reason: String { 37 | switch self { 38 | case .noFuzzyConverter(let item, let type): 39 | let reason: String 40 | if let item = item { 41 | reason = "No converters found for \(item) of type \(type)" 42 | } else { 43 | reason = "No converters found for type \(type)" 44 | } 45 | return reason 46 | case .invalidDictionaryKeyType: 47 | return "Dictionary must have String keys." 48 | case .unableToConvert(let node, let expectation, let path): 49 | let path = path.map { "\($0)" } .joined(separator: ".") 50 | if let node = node, node != .null { 51 | return "Unable to convert '\(node)' to '\(expectation)' for path '\(path)'" 52 | } else { 53 | return "No value found at path '\(path)', expected '\(expectation)'" 54 | } 55 | } 56 | } 57 | 58 | public var possibleCauses: [String] { 59 | switch self { 60 | case .invalidDictionaryKeyType: 61 | return [ 62 | "You attempted to parse/serialize an object using a dictionary with non-String keys" 63 | ] 64 | case .noFuzzyConverter: 65 | return [ 66 | "You have not properly set the Node.fuzzy array" 67 | ] 68 | case .unableToConvert: 69 | return [ 70 | "typo in key path", 71 | "underlying type is not convertible", 72 | "unexpected '.' being interpreted as path instead of key", 73 | ] 74 | } 75 | } 76 | 77 | public var suggestedFixes: [String] { 78 | switch self { 79 | case .invalidDictionaryKeyType: 80 | return [ 81 | "Change dictionary to [String: *] or Dictionary" 82 | ] 83 | case .noFuzzyConverter(let item, let type): 84 | var fixes: [String] = [] 85 | 86 | Node.fuzzy.forEach { fuzzy in 87 | if let item = item { 88 | fixes.append("Conform \(item) of type \(type) to \(fuzzy)") 89 | } else { 90 | fixes.append("Conform \(type) to \(fuzzy)") 91 | } 92 | } 93 | 94 | return fixes 95 | case .unableToConvert: 96 | return [ 97 | "called `get(...)` on a key or key path that does not exist in the data", 98 | "the data being parsed is missing required values or is incorrectly formatted", 99 | "found unconvertible data, e.g., got a string of letters when an integer is required", 100 | "if you have keys containing a '.' that shouldn't be interpreted as a path, use 'DotKey(\"actual.key\")'", 101 | ] 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Sources/Node/Utilities/Exports.swift: -------------------------------------------------------------------------------- 1 | #if !COCOAPODS 2 | // These are critical components to Node and are being exported 3 | @_exported import PathIndexable 4 | #endif 5 | -------------------------------------------------------------------------------- /Sources/Node/Utilities/Identifier.swift: -------------------------------------------------------------------------------- 1 | /// Represents a convenience around various identifier types. 2 | public struct Identifier: StructuredDataWrapper { 3 | public var wrapped: StructuredData 4 | public let context: Context 5 | 6 | public init(_ wrapped: StructuredData, in context: Context?) { 7 | self.wrapped = wrapped 8 | self.context = context ?? emptyContext 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/PathIndexable/PathIndexable+Subscripting.swift: -------------------------------------------------------------------------------- 1 | /// Indexable 2 | /// ["a": [["foo":"bar"]]] 3 | /// Indexer list 4 | /// ["a", 0, "foo"] 5 | /// 6 | /// "a" 7 | /// [["foo":"bar"]] 8 | /// [0, "foo"] 9 | /// 10 | /// 0 11 | /// ["foo":"bar"] 12 | /// ["foo"] 13 | /// 14 | /// "foo" 15 | /// "bar" 16 | /// [] 17 | /// 18 | /// ret bar 19 | extension PathIndexable { 20 | /// Access via comma separated list of indexers, for example 21 | /// ["key", 0, "path", "here" 22 | public subscript(indexers: PathIndexer...) -> Self? { 23 | get { 24 | return self[indexers] 25 | } 26 | set { 27 | self[indexers] = newValue 28 | } 29 | } 30 | 31 | /// Sometimes we prefer (or require) arrays of indexers 32 | /// those are accepted here 33 | public subscript(indexers: [PathIndexer]) -> Self? { 34 | get { 35 | let indexers = indexers.unwrap() 36 | 37 | /// if there's a next item, then the corresponding index 38 | /// for that item needs to access it. 39 | /// 40 | /// once nil is found, keep returning nil, indexers don't matter at that point 41 | return indexers.reduce(self) { nextIndexable, nextIndexer in 42 | return nextIndexable.flatMap(nextIndexer.get) 43 | } 44 | } 45 | set { 46 | let indexers = indexers.unwrap() 47 | 48 | guard let currentIndexer = indexers.first else { return } 49 | var indexersRemaining = indexers 50 | indexersRemaining.removeFirst() 51 | 52 | if indexersRemaining.isEmpty { 53 | currentIndexer.set(newValue, to: &self) 54 | } else { 55 | var next = self[currentIndexer] ?? currentIndexer.makeEmptyStructureForIndexing() as Self 56 | next[indexersRemaining] = newValue 57 | self[currentIndexer] = next 58 | } 59 | } 60 | } 61 | } 62 | 63 | extension Sequence where Iterator.Element == PathIndexer { 64 | /// This is how we allow strings to unwrap themselves into larger keys 65 | /// if you need to preserve `.` in your keys, use the `DotKey` type 66 | internal func unwrap() -> [PathIndexer] { 67 | return flatMap { indexer in indexer.unwrapComponents() } 68 | } 69 | 70 | public func path() -> String { 71 | return map { $0.description } .joined(separator: ", ") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/PathIndexable/PathIndexable.swift: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | Objects wishing to inherit complex subscripting should implement 4 | this protocol 5 | */ 6 | public protocol PathIndexable { 7 | /// If self is an array representation, return array 8 | var pathIndexableArray: [Self]? { get } 9 | 10 | /// If self is an object representation, return object 11 | var pathIndexableObject: [String: Self]? { get } 12 | 13 | /** 14 | Initialize a new object encapsulating an array of Self 15 | 16 | - parameter array: value to encapsulate 17 | */ 18 | init(_ array: [Self]) 19 | 20 | /** 21 | Initialize a new object encapsulating an object of type [String: Self] 22 | 23 | - parameter object: value to encapsulate 24 | */ 25 | init(_ object: [String: Self]) 26 | } 27 | 28 | // MARK: Indexable 29 | 30 | /** 31 | Anything that can be used as subscript access for a Node. 32 | 33 | Int and String are supported natively, additional Indexable types 34 | should only be added after very careful consideration. 35 | */ 36 | public protocol PathIndexer: CustomStringConvertible { 37 | /** 38 | Access for 'self' within the given node, 39 | ie: inverse ov `= node[self]` 40 | 41 | - parameter node: the node to access 42 | 43 | - returns: a value for index of 'self' if exists 44 | */ 45 | func get(from indexable: T) -> T? 46 | 47 | /** 48 | Set given input to a given node for 'self' if possible. 49 | ie: inverse of `node[0] =` 50 | 51 | - parameter input: value to set in parent, or `nil` if should remove 52 | - parameter parent: node to set input in 53 | */ 54 | func set(_ input: T?, to parent: inout T) 55 | 56 | /** 57 | Create an empty structure that can be set with the given type. 58 | 59 | ie: 60 | - a string will create an empty dictionary to add itself as a value 61 | - an Int will create an empty array to add itself as a value 62 | 63 | - returns: an empty structure that can be set by Self 64 | */ 65 | func makeEmptyStructureForIndexing() -> T 66 | 67 | /// Used to allow turning one component into many when desirable 68 | func unwrapComponents() -> [PathIndexer] 69 | } 70 | 71 | extension PathIndexer { 72 | public func unwrapComponents() -> [PathIndexer] { return [self] } 73 | } 74 | 75 | extension Int: PathIndexer { 76 | /** 77 | - see: PathIndex 78 | */ 79 | public func get(from indexable: T) -> T? { 80 | guard let array = indexable.pathIndexableArray else { return nil } 81 | guard self < array.count else { return nil } 82 | return array[self] 83 | } 84 | 85 | /** 86 | - see: PathIndex 87 | */ 88 | public func set(_ input: T?, to parent: inout T) { 89 | guard let array = parent.pathIndexableArray else { return } 90 | guard self < array.count else { return } 91 | 92 | var mutable = array 93 | if let new = input { 94 | mutable[self] = new 95 | } else { 96 | mutable.remove(at: self) 97 | } 98 | parent = type(of: parent).init(mutable) 99 | } 100 | 101 | public func makeEmptyStructureForIndexing() -> T { 102 | return T([]) 103 | } 104 | } 105 | 106 | extension String: PathIndexer { 107 | /** 108 | - see: PathIndex 109 | */ 110 | public func get(from indexable: T) -> T? { 111 | if let object = indexable.pathIndexableObject?[self] { 112 | return object 113 | } else if let array = indexable.pathIndexableArray { 114 | // Index takes precedence 115 | if let idx = Int(self), idx < array.count { 116 | return array[idx] 117 | } 118 | 119 | #if swift(>=4.1) 120 | let value = array.compactMap(self.get) 121 | #else 122 | let value = array.flatMap(self.get) 123 | #endif 124 | guard !value.isEmpty else { return nil } 125 | return type(of: indexable).init(value) 126 | } 127 | 128 | return nil 129 | } 130 | 131 | /** 132 | - see: PathIndex 133 | */ 134 | public func set(_ input: T?, to parent: inout T) { 135 | if let object = parent.pathIndexableObject { 136 | var mutable = object 137 | mutable[self] = input 138 | parent = type(of: parent).init(mutable) 139 | } else if let array = parent.pathIndexableArray { 140 | let mapped: [T] = array.map { val in 141 | var mutable = val 142 | self.set(input, to: &mutable) 143 | return mutable 144 | } 145 | parent = type(of: parent).init(mapped) 146 | } 147 | } 148 | 149 | 150 | public func makeEmptyStructureForIndexing() -> T { 151 | return T([:]) 152 | } 153 | 154 | public func unwrapComponents() -> [PathIndexer] { 155 | return toCharacterSequence() 156 | .split(separator: ".") 157 | .map(String.init) 158 | } 159 | } 160 | 161 | extension String { 162 | internal func keyPathComponents() -> [String] { 163 | return toCharacterSequence() 164 | .split(separator: ".") 165 | .map(String.init) 166 | } 167 | 168 | #if swift(>=4.0) 169 | private func toCharacterSequence() -> String { 170 | return self 171 | } 172 | #else 173 | fileprivate func toCharacterSequence() -> CharacterView { 174 | return self.characters 175 | } 176 | #endif 177 | } 178 | 179 | /// Everything in indexable will explode keypaths, 180 | /// for example, "foo.bar" will become "foo", "bar" 181 | /// should you have . nested in your JSON keys, use this class 182 | /// 183 | /// ["foo.bar": 2] 184 | /// 185 | /// would be accessed 186 | /// data[DotKey("foo.bar")] 187 | /// this will preserve the `.` 188 | public struct DotKey: PathIndexer { 189 | public let key: String 190 | public init(_ key: String) { 191 | self.key = key 192 | } 193 | 194 | public func get(from indexable: T) -> T? { 195 | return key.get(from: indexable) 196 | } 197 | 198 | public func set(_ input: T?, to parent: inout T) { 199 | key.set(input, to: &parent) 200 | } 201 | 202 | public func makeEmptyStructureForIndexing() -> T { 203 | return key.makeEmptyStructureForIndexing() 204 | } 205 | } 206 | 207 | extension DotKey { 208 | public var description: String { 209 | return key 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import XCTest 3 | @testable import NodeTests 4 | @testable import PathIndexableTests 5 | 6 | XCTMain([ 7 | testCase(BasicConvertibleTests.allTests), 8 | testCase(DictionaryKeyPathTests.allTests), 9 | testCase(NodeDataTypeTests.allTests), 10 | testCase(NodeGetterTests.allTests), 11 | testCase(NodeIndexableTests.allTests), 12 | testCase(NodePolymorphicTests.allTests), 13 | testCase(NodeTests.allTests), 14 | testCase(SequenceConvertibleTests.allTests), 15 | testCase(NumberTests.allTests), 16 | testCase(NodeBackedTests.allTests), 17 | testCase(SettersTests.allTests), 18 | testCase(DictionaryKeyPathTests.allTests), 19 | testCase(PathIndexableTests.allTests), 20 | ]) 21 | #endif 22 | -------------------------------------------------------------------------------- /Tests/NodeTests/BasicConvertibleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConvertibleTests.swift 3 | // Node 4 | // 5 | // Created by Logan Wright on 7/20/16. 6 | // 7 | // 8 | 9 | import XCTest 10 | import Node 11 | import Foundation 12 | 13 | class BasicConvertibleTests: XCTestCase { 14 | static let allTests = [ 15 | ("testBoolInit", testBoolInit), 16 | ("testBoolRepresent", testBoolRepresent), 17 | ("testIntegerInit", testIntegerInit), 18 | ("testIntegerRepresent", testIntegerRepresent), 19 | ("testDoubleInit", testDoubleInit), 20 | ("testDoubleRepresent", testDoubleRepresent), 21 | 22 | ("testFloatInit", testFloatInit), 23 | ("testFloatRepresent", testFloatRepresent), 24 | ("testUnsignedIntegerInit", testUnsignedIntegerInit), 25 | ("testUnsignedIntegerRepresent", testUnsignedIntegerRepresent), 26 | ("testStringInit", testStringInit), 27 | ("testStringRepresent", testStringRepresent), 28 | ("testNodeConvertible", testNodeConvertible), 29 | ("testUUIDConvertible", testUUIDConvertible), 30 | ("testUUIDConvertibleThrows", testUUIDConvertibleThrows), 31 | ] 32 | 33 | func testBoolInit() throws { 34 | let truths: [Node] = [ 35 | "true", "t", "yes", "y", 1, 1.0, "1" 36 | ] 37 | try truths.forEach { truth in try XCTAssert(Bool(node: truth)) } 38 | 39 | let falsehoods: [Node] = [ 40 | "false", "f", "no", "n", 0, 0.0, "0" 41 | ] 42 | try falsehoods.forEach { falsehood in try XCTAssert(!Bool(node: falsehood)) } 43 | 44 | let fails: [Node] = [ 45 | [1,2,3], ["key": "value"], .null 46 | ] 47 | try assert(Bool.self, fails: fails) 48 | } 49 | 50 | func testBoolRepresent() { 51 | let truthy = true.makeNode(in: nil) 52 | let falsy = false.makeNode(in: nil) 53 | XCTAssert(truthy == .bool(true, in: nil)) 54 | XCTAssert(falsy == .bool(false, in: nil)) 55 | } 56 | 57 | func testIntegerInit() throws { 58 | let string = Node("400") 59 | let int = Node(-42) 60 | let double = Node(55.6) 61 | let bool = Node(true) 62 | 63 | try XCTAssert(Int(node: string) == 400) 64 | try XCTAssert(Int(node: int) == -42) 65 | try XCTAssert(Int(node: double) == 55) 66 | try XCTAssert(Int(node: bool) == 1) 67 | 68 | let fails: [Node] = [ 69 | [1,2,3], ["key": "value"], .null 70 | ] 71 | try assert(Int.self, fails: fails) 72 | } 73 | 74 | func testIntegerRepresent() throws { 75 | let node = 124.makeNode(in: nil) 76 | XCTAssert(node == .number(124, in: nil)) 77 | } 78 | 79 | func testDoubleInit() throws { 80 | let string = Node("433.1029") 81 | let int = Node(-42) 82 | let double = Node(55.6) 83 | let bool = Node(true) 84 | 85 | try XCTAssert(Double(node: string) == 433.1029) 86 | try XCTAssert(Double(node: int) == -42.0) 87 | try XCTAssert(Double(node: double) == 55.6) 88 | try XCTAssert(Double(node: bool) == 1.0) 89 | 90 | let fails: [Node] = [ 91 | [1,2,3], ["key": "value"], .null 92 | ] 93 | try assert(Double.self, fails: fails) 94 | } 95 | 96 | func testDoubleRepresent() { 97 | let node = 124.534.makeNode(in: nil) 98 | XCTAssert(node == .number(124.534, in: nil)) 99 | } 100 | 101 | func testFloatInit() throws { 102 | let string = Node("433.1029") 103 | let int = Node(-42) 104 | let double = Node(55.6) 105 | let bool = Node(true) 106 | 107 | try XCTAssert(Float(node: string) == 433.1029) 108 | try XCTAssert(Float(node: int) == -42.0) 109 | try XCTAssert(Float(node: double) == 55.6) 110 | try XCTAssert(Float(node: bool) == 1.0) 111 | 112 | let fails: [Node] = [ 113 | [1,2,3], ["key": "value"], .null 114 | ] 115 | try assert(Float.self, fails: fails) 116 | } 117 | 118 | func testFloatRepresent() { 119 | let float = Float(123.0) 120 | let node = float.makeNode(in: nil) 121 | XCTAssert(node == .number(123.0, in: nil)) 122 | } 123 | 124 | func testUnsignedIntegerInit() throws { 125 | let string = Node("400") 126 | let int = Node(42) 127 | let double = Node(55.6) 128 | let bool = Node(true) 129 | 130 | try XCTAssert(UInt(node: string) == 400) 131 | try XCTAssert(UInt(node: int) == 42) 132 | try XCTAssert(UInt(node: double) == 55) 133 | try XCTAssert(UInt(node: bool) == 1) 134 | 135 | let fails: [Node] = [ 136 | [1,2,3], ["key": "value"], .null 137 | ] 138 | try assert(UInt.self, fails: fails) 139 | } 140 | 141 | func testUnsignedIntegerRepresent() throws { 142 | let uint = UInt(124) 143 | let node = uint.makeNode(in: nil) 144 | XCTAssert(node == .number(124, in: nil)) 145 | } 146 | 147 | func testStringInit() throws { 148 | let string = Node("hello :)") 149 | let int = Node(42) 150 | let double = Node(55.6) 151 | let bool = Node(true) 152 | 153 | try XCTAssert(String(node: string) == "hello :)") 154 | try XCTAssert(String(node: int) == "42") 155 | try XCTAssert(String(node: double) == "55.6") 156 | try XCTAssert(String(node: bool) == "true") 157 | 158 | let fails: [Node] = [ 159 | [1,2,3], ["key": "value"], .null 160 | ] 161 | try assert(String.self, fails: fails) 162 | } 163 | 164 | func testStringRepresent() { 165 | let node = "hello :)".makeNode(in: nil) 166 | XCTAssert(node == .string("hello :)", in: nil)) 167 | } 168 | 169 | func testNodeConvertible() throws { 170 | let node = Node("hello node") 171 | let initted = Node(node: node) 172 | let made = node.makeNode(in: nil) 173 | XCTAssert(initted == made) 174 | } 175 | 176 | func testUUIDConvertible() throws { 177 | let expectation = UUID() 178 | let node = expectation.makeNode(in: nil) 179 | XCTAssertEqual(expectation.uuidString, node.string) 180 | 181 | let inverse = try node.converted(to: UUID.self, in: nil) 182 | XCTAssertEqual(inverse, expectation) 183 | } 184 | 185 | func testUUIDConvertibleThrows() throws { 186 | let node = Node("I'm not a uuid :)") 187 | do { 188 | _ = try node.converted(to: UUID.self, in: nil) 189 | XCTFail("Should fail") 190 | } catch is NodeError { 191 | // ok, expected to fail 192 | } 193 | 194 | } 195 | 196 | private func assert(_ n: N.Type, fails cases: [Node]) throws { 197 | try cases.forEach { fail in 198 | do { 199 | _ = try N(node: fail) 200 | } catch is NodeError {} 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /Tests/NodeTests/DictionaryKeyPathTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DictionaryKeyPathTests.swift 3 | // Genome 4 | // 5 | // Created by Logan Wright on 7/2/15. 6 | // Copyright © 2015 lowriDevs. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Node 11 | 12 | class DictionaryKeyPathTests: XCTestCase { 13 | static let allTests = [ 14 | ("testPaths", testPaths), 15 | ("testGarbage", testGarbage), 16 | ] 17 | 18 | func testPaths() { 19 | let inner = Node(["two" : .string("Found me!")]) 20 | var test = Node([ 21 | "one" : inner 22 | ]) 23 | 24 | guard let node = test["one", "two"] else { 25 | XCTFail() 26 | return 27 | } 28 | 29 | guard let str = node.string else { 30 | XCTFail() 31 | return 32 | } 33 | XCTAssert(str == "Found me!") 34 | 35 | test["path", "to", "new", "value"] = .string("Hello!") 36 | guard let setVal = test["path", "to", "new", "value"] else { 37 | XCTFail() 38 | return 39 | } 40 | guard let setStr = setVal.string else { 41 | XCTFail() 42 | return 43 | } 44 | 45 | XCTAssert(setStr == "Hello!") 46 | } 47 | 48 | func testGarbage() throws { 49 | let node = try! [1, 2, "3", "4", ["hello": "world"]].converted(to: Node.self, in: nil) 50 | XCTAssertEqual(node[0], 1) 51 | XCTAssertEqual(node[1], 2) 52 | XCTAssertEqual(node[2], "3") 53 | XCTAssertEqual(node[3], "4") 54 | XCTAssertEqual(node[4, "hello"], "world") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/NodeTests/NodeBackedTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Node 3 | 4 | struct JSON: StructuredDataWrapper { 5 | var wrapped: StructuredData 6 | var context: Context 7 | init(_ wrapped: StructuredData, in context: Context?) { 8 | self.wrapped = wrapped 9 | self.context = context ?? emptyContext 10 | } 11 | } 12 | 13 | class NodeBackedTests: XCTestCase { 14 | static let allTests = [ 15 | ("testSubscripts", testSubscripts), 16 | ("testPolymorphic", testPolymorphic), 17 | ] 18 | 19 | func testSubscripts() throws { 20 | let json = try JSON(node: [ 21 | "names": [ 22 | "", 23 | "", 24 | "World" 25 | ] 26 | ] 27 | ) 28 | 29 | XCTAssertEqual(json["names", 2]?.string, "World") 30 | } 31 | 32 | func testPolymorphic() throws { 33 | let node = try JSON( 34 | node: [ 35 | "string": "Hello!", 36 | "int": 3, 37 | "bool": true, 38 | "ob": [ 39 | "name": "World" 40 | ], 41 | "arr": [ 42 | 0, 43 | 1, 44 | 2 45 | ], 46 | "null": "null", 47 | "double": 3.14 48 | ] 49 | ) 50 | 51 | XCTAssertEqual(node["string"]?.string, "Hello!") 52 | XCTAssertEqual(node["int"]?.int, 3) 53 | XCTAssertEqual(node["bool"]?.bool, true) 54 | XCTAssertEqual(node["ob", "name"]?.string, "World") 55 | XCTAssertEqual(node["arr", 2]?.int, 2) 56 | XCTAssertEqual(node["null"]?.isNull, true) 57 | XCTAssertEqual(node["double"]?.double, 3.14) 58 | #if swift(>=4.1) 59 | let arr = node["arr"]?.array?.compactMap { $0.int } ?? [] 60 | #else 61 | let arr = node["arr"]?.array?.flatMap { $0.int } ?? [] 62 | #endif 63 | XCTAssertEqual(arr, [0, 1, 2]) 64 | let ob = node["ob"]?.object 65 | XCTAssertEqual(ob?["name"]?.string, "World") 66 | XCTAssertNil(node["int", "foo"]?.object) 67 | 68 | let jsArr: [JSON] = try [0, 1].map { try $0.converted(in: nil) } 69 | _ = JSON(jsArr) 70 | let jsOb: [String: JSON] = ["key": JSON(.string("val"), in: nil)] 71 | _ = JSON(jsOb) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Tests/NodeTests/NodeDataTypeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NodeDataTypeTest.swift 3 | // Genome 4 | // 5 | // Created by Logan Wright on 12/6/15. 6 | // Copyright © 2015 lowriDevs. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import Node 12 | 13 | class NodeDataTypeTests: XCTestCase { 14 | static let allTests = [ 15 | ("testIntegers", testIntegers), 16 | ("testUnsignedIntegers", testUnsignedIntegers), 17 | ] 18 | 19 | // 127 is Int8 max, unless you want to change the way this test is setup, 20 | // the value must be somewhere between 0 and 127 21 | let integerValue: Int = 127 22 | lazy var integerNodeValue: Node = .number(Node.Number(self.integerValue), in: nil) 23 | 24 | func testIntegers() throws { 25 | let int = try Int(node: integerNodeValue) 26 | XCTAssert(int == integerValue) 27 | 28 | let int8 = try Int8(node: integerNodeValue) 29 | XCTAssert(int8 == Int8(integerValue)) 30 | 31 | let int16 = try Int16(node: integerNodeValue) 32 | XCTAssert(int16 == Int16(integerValue)) 33 | 34 | let int32 = try Int32(node: integerNodeValue) 35 | XCTAssert(int32 == Int32(integerValue)) 36 | 37 | let int64 = try Int64(node: integerNodeValue) 38 | XCTAssert(int64 == Int64(integerValue)) 39 | } 40 | 41 | func testUnsignedIntegers() throws { 42 | let uint = try UInt(node: integerNodeValue) 43 | XCTAssert(uint == UInt(integerValue)) 44 | 45 | let uint8 = try UInt8(node: integerNodeValue) 46 | XCTAssert(uint8 == UInt8(integerValue)) 47 | 48 | let uint16 = try UInt16(node: integerNodeValue) 49 | XCTAssert(uint16 == UInt16(integerValue)) 50 | 51 | let uint32 = try UInt32(node: integerNodeValue) 52 | XCTAssert(uint32 == UInt32(integerValue)) 53 | 54 | let uint64 = try UInt64(node: integerNodeValue) 55 | XCTAssert(uint64 == UInt64(integerValue)) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/NodeTests/NodeGetterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BasicTypes.swift 3 | // Genome 4 | // 5 | // Created by Logan Wright on 9/19/15. 6 | // Copyright © 2015 lowriDevs. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Foundation 11 | @testable import Node 12 | 13 | struct NoNull: NodeInitializable, Hashable { 14 | let node: Node 15 | 16 | var hashValue: Int { 17 | return "\(node)".hashValue 18 | } 19 | 20 | init(node: Node) throws { 21 | guard node != .null else { 22 | throw NodeError.unableToConvert(input: node, expectation: "something not null", path: []) 23 | } 24 | 25 | self.node = node 26 | } 27 | } 28 | 29 | func == (l: NoNull, r: NoNull) -> Bool { 30 | return l.node == r.node 31 | } 32 | 33 | class NodeGetterTests: XCTestCase { 34 | override func setUp() { 35 | Node.fuzzy = [Node.self] 36 | } 37 | 38 | static let allTests = [ 39 | ("testGetNoPath", testGetNoPath), 40 | ("testgetTransform", testgetTransform), 41 | ("testgetTransformThrows", testgetTransformThrows), 42 | ("testgetTransformOptionalValue", testgetTransformOptionalValue), 43 | ("testgetTransformOptionalNil", testgetTransformOptionalNil), 44 | ("testgetSingle", testgetSingle), 45 | ("testgetSingleOptional", testgetSingleOptional), 46 | ("testgetSingleThrows", testgetSingleThrows), 47 | ("testgetArray", testgetArray), 48 | ("testgetArrayOptional", testgetArrayOptional), 49 | ("testgetArrayThrows", testgetArrayThrows), 50 | ("testgetArrayOfArrays", testgetArrayOfArrays), 51 | ("testgetArrayOfArraysOptional", testgetArrayOfArraysOptional), 52 | ("testgetArrayOfArraysThrows", testgetArrayOfArraysThrows), 53 | ("testgetObject", testgetObject), 54 | ("testgetObjectOptional", testgetObjectOptional), 55 | ("testgetObjectThrows", testgetObjectThrows), 56 | ("testgetObjectOfArrays", testgetObjectOfArrays), 57 | ("testgetObjectOfArraysOptional", testgetObjectOfArraysOptional), 58 | ("testgetObjectOfArraysThrows", testgetObjectOfArraysThrows), 59 | ("testgetSet", testgetSet), 60 | ("testgetSetOptional", testgetSetOptional), 61 | ("testgetSetThrows", testgetSetThrows), 62 | ("testgetDateRFC1123", testgetDateRFC1123), 63 | ("testgetDateMySQLDATETIME", testgetDateMySQLDATETIME), 64 | ("testBadObject", testBadObject), 65 | ] 66 | 67 | func testGetNoPath() throws { 68 | let node = "foo" as Node 69 | let foo = try node.get() as String 70 | XCTAssertEqual(foo, "foo") 71 | } 72 | 73 | func testgetTransform() throws { 74 | let dict = ["date": 250] 75 | let node = try Node(node: dict, in: nil) 76 | let geted = try node.get("date", transform: Date.fromTimestamp) 77 | XCTAssert(geted.timeIntervalSince1970 == 250) 78 | } 79 | 80 | func testgetTransformThrows() throws { 81 | let node = Node() 82 | do { 83 | _ = try node.get("date", transform: Date.fromTimestamp) 84 | XCTFail("should throw error") 85 | } catch is NodeError {} 86 | } 87 | 88 | func testgetTransformOptionalValue() throws { 89 | let node = try Node(node: ["date": 250], in: nil) 90 | let geted = try node.get("date", transform: Date.optionalFromTimestamp) 91 | XCTAssert(geted?.timeIntervalSince1970 == 250) 92 | } 93 | 94 | func testgetTransformOptionalNil() throws { 95 | let node = Node() 96 | let geted = try node.get("date", transform: Date.optionalFromTimestamp) 97 | XCTAssertNil(geted) 98 | } 99 | 100 | func testgetSingle() throws { 101 | let node = try Node(node: ["nest": [ "ed": ["hello": "world", "pi": 3.14159]]]) 102 | let geted = try node.get("nest.ed.hello") as NoNull 103 | XCTAssert(geted.node.string == "world") 104 | } 105 | 106 | func testgetSingleOptional() throws { 107 | let node = try Node(node: ["nest": [ "ed": ["hello": "world", "pi": 3.14159]]]) 108 | let geted: NoNull? = try node.get("nest.ed.hello") 109 | XCTAssert(geted?.node.string == "world") 110 | } 111 | 112 | func testgetSingleThrows() throws { 113 | let node = Node() 114 | do { 115 | _ = try node.get("nest.ed.hello") as NoNull 116 | XCTFail("should throw node error unable to convert") 117 | } catch is NodeError {} 118 | } 119 | 120 | func testgetArray() throws { 121 | let node = try Node(node: ["nest": [ "ed": ["array": [1, 2, 3, 4]]]]) 122 | let geted = try! node.get("nest.ed.array") as [NoNull] 123 | #if swift(>=4.1) 124 | let numbers = geted.compactMap { $0.node.int } 125 | #else 126 | let numbers = geted.flatMap { $0.node.int } 127 | #endif 128 | XCTAssert(numbers == [1,2,3,4]) 129 | } 130 | 131 | func testgetArrayOptional() throws { 132 | let node = try Node(node: ["nest": [ "ed": ["array": [1, 2, 3, 4]]]]) 133 | let geted: [NoNull]? = try node.get("nest.ed.array") 134 | #if swift(>=4.1) 135 | let numbers = geted?.compactMap { $0.node.int } ?? [] 136 | #else 137 | let numbers = geted?.flatMap { $0.node.int } ?? [] 138 | #endif 139 | XCTAssert(numbers == [1,2,3,4]) 140 | } 141 | 142 | func testgetArrayThrows() throws { 143 | let node = Node() 144 | do { 145 | _ = try node.get("nest.ed.hello") as [NoNull] 146 | XCTFail("should throw node error unable to convert") 147 | } catch is NodeError {} 148 | } 149 | 150 | func testgetArrayOfArrays() throws { 151 | let node = try Node(node: ["nest": [ "ed": ["array": [[1], [2], [3], [4]]]]]) 152 | let geted = try node.get("nest.ed.array") as [[NoNull]] 153 | #if swift(>=4.1) 154 | let numbers = geted.map { innerArray in 155 | innerArray.compactMap { $0.node.int } 156 | } 157 | #else 158 | let numbers = geted.map { innerArray in 159 | innerArray.flatMap { $0.node.int } 160 | } 161 | #endif 162 | 163 | guard numbers.count == 4 else { 164 | XCTFail("failed array of arrays") 165 | return 166 | } 167 | XCTAssert(numbers[0] == [1]) 168 | XCTAssert(numbers[1] == [2]) 169 | XCTAssert(numbers[2] == [3]) 170 | XCTAssert(numbers[3] == [4]) 171 | } 172 | 173 | func testgetArrayOfArraysOptional() throws { 174 | let node = try Node(node: ["nest": [ "ed": ["array": [[1], [2], [3], [4]]]]]) 175 | let geted: [[NoNull]]? = try node.get("nest.ed.array") 176 | #if swift(>=4.1) 177 | let numbers = geted?.map { innerArray in 178 | innerArray.compactMap { $0.node.int } 179 | } ?? [] 180 | #else 181 | let numbers = geted?.map { innerArray in 182 | innerArray.flatMap { $0.node.int } 183 | } ?? [] 184 | #endif 185 | 186 | guard numbers.count == 4 else { 187 | XCTFail("failed array of arrays optional") 188 | return 189 | } 190 | XCTAssert(numbers[0] == [1]) 191 | XCTAssert(numbers[1] == [2]) 192 | XCTAssert(numbers[2] == [3]) 193 | XCTAssert(numbers[3] == [4]) 194 | } 195 | 196 | func testgetArrayOfArraysThrows() throws { 197 | do { 198 | let node = Node() 199 | _ = try node.get("nest.ed.array") as [[NoNull]] 200 | XCTFail("should throw node error unable to convert") 201 | } catch is NodeError {} 202 | } 203 | 204 | func testgetObject() throws { 205 | let node = try Node(node: ["nest": [ "ed": ["object": ["hello": "world"]]]]) 206 | let geted = try node.get("nest.ed.object") as [String: NoNull] 207 | XCTAssert(geted["hello"]?.node.string == "world") 208 | } 209 | 210 | func testgetObjectOptional() throws { 211 | let node = try Node(node: ["nest": [ "ed": ["object": ["hello": "world"]]]]) 212 | let geted: [String: NoNull]? = try node.get("nest.ed.object") 213 | XCTAssert(geted?["hello"]?.node.string == "world") 214 | } 215 | 216 | func testgetObjectThrows() throws { 217 | let node = Node() 218 | do { 219 | _ = try node.get("dont.exist.0") as [String: NoNull] 220 | XCTFail("should throw node error unable to convert") 221 | } catch {} 222 | } 223 | 224 | func testgetObjectOfArrays() throws { 225 | let node = try Node(node: ["nest": [ "ed": ["object": ["hello": [1,2,3,4]]]]]) 226 | let geted = try node.get("nest.ed.object") as [String: [NoNull]] 227 | #if swift(>=4.1) 228 | let ints = geted["hello"]?.compactMap({ $0.node.int }) ?? [] 229 | #else 230 | let ints = geted["hello"]?.flatMap({ $0.node.int }) ?? [] 231 | #endif 232 | XCTAssert(ints == [1,2,3,4]) 233 | } 234 | 235 | func testgetObjectOfArraysOptional() throws { 236 | let node = try Node(node: ["nest": [ "ed": ["object": ["hello": [1,2,3,4]]]]]) 237 | let geted: [String: [NoNull]]? = try node.get("nest.ed.object") 238 | #if swift(>=4.1) 239 | let ints = geted?["hello"]?.compactMap({ $0.node.int }) ?? [] 240 | #else 241 | let ints = geted?["hello"]?.flatMap({ $0.node.int }) ?? [] 242 | #endif 243 | XCTAssert(ints == [1,2,3,4]) 244 | } 245 | 246 | func testgetObjectOfArraysThrows() throws { 247 | let node = Node() 248 | do { 249 | _ = try node.get("dont.exist.0") as [String: [NoNull]] 250 | XCTFail("should throw node error unable to convert") 251 | } catch {} 252 | } 253 | 254 | func testgetSet() throws { 255 | let node = try Node(node: ["nest": [ "ed": ["array": [1, 2, 3, 4]]]]) 256 | let geted = try node.get("nest.ed.array") as Set 257 | let ints = [1,2,3,4] 258 | let compare = try ints.converted(to: Set.self, in: nil) 259 | XCTAssert(geted == compare) 260 | } 261 | 262 | func testgetSetOptional() throws { 263 | let node = try Node(node: ["nest": [ "ed": ["array": [1, 2, 3, 4]]]]) 264 | let geted: Set? = try node.get("nest.ed.array") 265 | let ints = [1,2,3,4] 266 | let compare = try ints.converted(to: Set.self, in: nil) 267 | XCTAssert(geted == compare) 268 | } 269 | 270 | func testgetSetThrows() throws { 271 | let node = Node() 272 | do { 273 | _ = try node.get("dont.exist.0") as Set 274 | XCTFail("should throw node error unable to convert") 275 | } catch is NodeError {} 276 | } 277 | 278 | func testgetDateRFC1123() throws { 279 | let node = try Node(node: ["time": "Sun, 16 May 2010 15:20:00 GMT"]) 280 | let date: Date = try node.get("time") 281 | XCTAssertEqual(date.timeIntervalSince1970, 1274023200.0) 282 | } 283 | 284 | func testgetDateMySQLDATETIME() throws { 285 | let node = try Node(node: ["time": "2010-05-16 15:20:00"]) 286 | let date: Date = try node.get("time") 287 | XCTAssertEqual(date.timeIntervalSince1970, 1274023200.0) 288 | } 289 | 290 | func testBadObject() throws { 291 | // there was an issue where calling keys on an array of 292 | // string objects would stack overflow, 293 | // this test asserts that doesn't happen 294 | let arrayNotObject = Node(["key", "typo"]) 295 | XCTAssertNotNil(arrayNotObject.array) 296 | XCTAssertNil(arrayNotObject.object) 297 | // assert this doesn't stack overflow 298 | _ = arrayNotObject["key"] 299 | } 300 | } 301 | 302 | extension Date { 303 | static func fromTimestamp(_ timestamp: Int) -> Date { 304 | return Date(timeIntervalSince1970: TimeInterval(timestamp)) 305 | } 306 | 307 | static func optionalFromTimestamp(_ timestamp: Int?) -> Date? { 308 | guard let stamp = timestamp else { return nil } 309 | return fromTimestamp(stamp) 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /Tests/NodeTests/NodeIndexableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BasicTypes.swift 3 | // Genome 4 | // 5 | // Created by Logan Wright on 9/19/15. 6 | // Copyright © 2015 lowriDevs. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Node 11 | 12 | class NodeIndexableTests: XCTestCase { 13 | static let allTests = [ 14 | ("testInt", testInt), 15 | ("testString", testString), 16 | ("testStringSequenceObject", testStringSequenceObject), 17 | ("testStringSequenceArray", testStringSequenceArray), 18 | ("testIntSequence", testIntSequence), 19 | ("testMixed", testMixed), 20 | ] 21 | 22 | func testInt() { 23 | let array: Node = ["one", 24 | "two", 25 | "three"] 26 | let path = [1] 27 | XCTAssert(array[path] == "two") 28 | } 29 | 30 | func testString() { 31 | let object: Node = ["a" : 1] 32 | XCTAssert(object["a"] == 1) 33 | } 34 | 35 | func testStringSequenceObject() { 36 | let ob: Node = ["key" : ["path" : "found me!"]] 37 | XCTAssert(ob["key", "path"] == "found me!") 38 | } 39 | 40 | func testStringSequenceArray() { 41 | let obArray: Node = [["a" : 0], 42 | ["a" : 1], 43 | ["a" : 2], 44 | ["a" : 3]] 45 | let collection = obArray["a"] 46 | XCTAssert(collection == [0,1,2,3]) 47 | } 48 | 49 | func testIntSequence() { 50 | let inner: Node = ["...", 51 | "found me!"] 52 | let outer: Node = [inner] 53 | XCTAssert(outer[0, 1] == "found me!") 54 | } 55 | 56 | func testMixed() { 57 | let mixed: Node = ["one" : ["a", "b", "c"]] 58 | XCTAssert(mixed["one", 1] == "b") 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Tests/NodeTests/NodePolymorphicTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NodeEquatableTests.swift 3 | // Node 4 | // 5 | // Created by Logan Wright on 7/20/16. 6 | // 7 | // 8 | 9 | import XCTest 10 | @testable import Node 11 | 12 | class NodePolymorphicTests: XCTestCase { 13 | static let allTests = [ 14 | ("testPolymorphicString", testPolymorphicString), 15 | ("testPolymorphicInt", testPolymorphicInt), 16 | ("testPolymorphicUInt", testPolymorphicUInt), 17 | ("testPolymorphicFloat", testPolymorphicFloat), 18 | ("testPolymorphicDouble", testPolymorphicDouble), 19 | ("testPolymorphicNull", testPolymorphicNull), 20 | ("testPolymorphicBool", testPolymorphicBool), 21 | ("testPolymorphicArray", testPolymorphicArray), 22 | ("testPolymorphicObject", testPolymorphicObject) 23 | ] 24 | 25 | func testPolymorphicString() { 26 | let bool: Node = true 27 | let int: Node = 1 28 | let double: Node = 3.14 29 | let string: Node = "hi" 30 | let ob: Node = .object(["key": "value"], in: nil) 31 | let arr: Node = .array([1,2,3], in: nil) 32 | let bytes: Node = .bytes("foo.bar".makeBytes(), in: nil) 33 | 34 | XCTAssert(bool.string == "true") 35 | XCTAssert(int.string == "1") 36 | XCTAssert(double.string == "3.14") 37 | XCTAssert(string.string == "hi") 38 | XCTAssertNil(ob.string) 39 | XCTAssertNil(arr.string) 40 | XCTAssertEqual(bytes.string, "foo.bar") 41 | } 42 | 43 | func testPolymorphicInt() { 44 | let boolTrue: Node = true 45 | let boolFalse: Node = false 46 | let int: Node = 42 47 | let double: Node = 3.14 48 | let intString: Node = "123" 49 | 50 | let histring: Node = "hi" 51 | let ob: Node = .object(["key": "value"], in: nil) 52 | let arr: Node = .array([1,2,3], in: nil) 53 | let bytes: Node = .bytes([10, 20, 30, 40], in: nil) 54 | 55 | XCTAssert(boolTrue.int == 1) 56 | XCTAssert(boolFalse.int == 0) 57 | XCTAssert(int.int == 42) 58 | XCTAssert(double.int == 3) 59 | XCTAssert(intString.int == 123) 60 | XCTAssertNil(histring.int) 61 | XCTAssertNil(ob.int) 62 | XCTAssertNil(arr.int) 63 | XCTAssertNil(bytes.int) 64 | } 65 | 66 | func testPolymorphicUInt() { 67 | let boolTrue: Node = true 68 | let boolFalse: Node = false 69 | let int: Node = 42 70 | let double: Node = 3.14 71 | let intString: Node = "123" 72 | 73 | let histring: Node = "hi" 74 | let ob: Node = .object(["key": "value"], in: nil) 75 | let arr: Node = .array([1,2,3], in: nil) 76 | let bytes: Node = .bytes([10, 20, 30, 40], in: nil) 77 | 78 | XCTAssert(boolTrue.uint == 1) 79 | XCTAssert(boolFalse.uint == 0) 80 | XCTAssert(int.uint == 42) 81 | XCTAssert(double.uint == 3) 82 | XCTAssert(intString.uint == 123) 83 | XCTAssertNil(histring.uint) 84 | XCTAssertNil(ob.uint) 85 | XCTAssertNil(arr.uint) 86 | XCTAssertNil(bytes.uint) 87 | } 88 | 89 | func testPolymorphicFloat() { 90 | let boolTrue: Node = true 91 | let boolFalse: Node = false 92 | let int: Node = 42 93 | let double: Node = 3.14 94 | let intString: Node = "123" 95 | let doubleString: Node = "42.5997" 96 | 97 | let histring: Node = "hi" 98 | let ob: Node = .object(["key": "value"], in: nil) 99 | let arr: Node = .array([1,2,3], in: nil) 100 | let bytes: Node = .bytes([10, 20, 30, 40], in: nil) 101 | 102 | XCTAssert(boolTrue.float == 1) 103 | XCTAssert(boolFalse.float == 0) 104 | XCTAssert(int.float == 42) 105 | XCTAssert(double.float == 3.14) 106 | XCTAssert(intString.float == 123) 107 | XCTAssert(doubleString.float == 42.5997) 108 | XCTAssertNil(histring.float) 109 | XCTAssertNil(ob.float) 110 | XCTAssertNil(arr.float) 111 | XCTAssertNil(bytes.float) 112 | } 113 | 114 | func testPolymorphicDouble() { 115 | let boolTrue: Node = true 116 | let boolFalse: Node = false 117 | let int: Node = 42 118 | let double: Node = 3.14 119 | let intString: Node = "123" 120 | let doubleString: Node = "42.5997" 121 | 122 | let histring: Node = "hi" 123 | let ob: Node = .object(["key": "value"], in: nil) 124 | let arr: Node = .array([1,2,3], in: nil) 125 | let bytes: Node = .bytes([10, 20, 30, 40], in: nil) 126 | 127 | XCTAssert(boolTrue.double == 1) 128 | XCTAssert(boolFalse.double == 0) 129 | XCTAssert(int.double == 42) 130 | XCTAssert(double.double == 3.14) 131 | XCTAssert(intString.double == 123) 132 | XCTAssert(doubleString.double == 42.5997) 133 | XCTAssertNil(histring.double) 134 | XCTAssertNil(ob.double) 135 | XCTAssertNil(arr.double) 136 | XCTAssertNil(bytes.double) 137 | } 138 | 139 | func testPolymorphicNull() { 140 | let null: Node = .null 141 | let lowerNullString: Node = "null" 142 | let upperNullString: Node = "NULL" 143 | 144 | let bool: Node = true 145 | let int: Node = 42 146 | let double: Node = 3.14 147 | let string: Node = "hi" 148 | let ob: Node = .object(["key": "value"], in: nil) 149 | let arr: Node = .array([1,2,3], in: nil) 150 | let bytes: Node = .bytes([10, 20, 30, 40], in: nil) 151 | 152 | XCTAssertTrue(null.isNull) 153 | XCTAssertTrue(lowerNullString.isNull) 154 | XCTAssertTrue(upperNullString.isNull) 155 | 156 | XCTAssertFalse(bool.isNull) 157 | XCTAssertFalse(int.isNull) 158 | XCTAssertFalse(double.isNull) 159 | XCTAssertFalse(string.isNull) 160 | XCTAssertFalse(ob.isNull) 161 | XCTAssertFalse(arr.isNull) 162 | XCTAssertFalse(bytes.isNull) 163 | } 164 | 165 | func testPolymorphicBool() { 166 | let null: Node = .null 167 | let bool: Node = true 168 | let int: Node = 42 169 | let boolInt: Node = 1 170 | let double: Node = 3.14 171 | let boolDouble: Node = 1.0 172 | let string: Node = "hi" 173 | let boolString: Node = "true" 174 | let ob: Node = .object(["key": "value"], in: nil) 175 | let arr: Node = .array([1,2,3], in: nil) 176 | let bytes: Node = .bytes([10, 20, 30, 40], in: nil) 177 | 178 | XCTAssert(null.bool == false) 179 | XCTAssert(bool.bool == true) 180 | XCTAssertNil(int.bool) 181 | XCTAssert(boolInt.bool == true) 182 | XCTAssertNil(double.bool) 183 | XCTAssert(boolDouble.bool == true) 184 | XCTAssertNil(string.bool) 185 | XCTAssert(boolString.bool == true) 186 | XCTAssertNil(ob.bool) 187 | XCTAssertNil(arr.bool) 188 | XCTAssertNil(bytes.bool) 189 | } 190 | 191 | func testPolymorphicArray() { 192 | let null: Node = .null 193 | let bool: Node = true 194 | let int: Node = 42 195 | let double: Node = 3.14 196 | let string: Node = "hi" 197 | let arrayString: Node = "hi, there, array" 198 | let ob: Node = .object(["key": "value"], in: nil) 199 | let arr: Node = .array([1,2,3], in: nil) 200 | let bytes: Node = .bytes([10, 20, 30, 40], in: nil) 201 | 202 | XCTAssertNil(null.array) 203 | XCTAssertNil(bool.array) 204 | XCTAssertNil(int.array) 205 | XCTAssertNil(double.array) 206 | 207 | #if swift(>=4.1) 208 | let array = arr.array?.compactMap { $0.int } ?? [] 209 | #else 210 | let array = arr.array?.flatMap { $0.int } ?? [] 211 | #endif 212 | XCTAssert(array == [1, 2, 3]) 213 | 214 | XCTAssertNil(string.array) 215 | XCTAssertNil(arrayString.array) 216 | XCTAssertNil(ob.array) 217 | XCTAssertNil(bytes.array) 218 | } 219 | 220 | func testPolymorphicObject() { 221 | let null: Node = .null 222 | let bool: Node = true 223 | let int: Node = 42 224 | let double: Node = 3.14 225 | let string: Node = "hi" 226 | let ob: Node = .object(["key": "value"], in: nil) 227 | let arr: Node = .array([1,2,3], in: nil) 228 | let bytes: Node = .bytes([10, 20, 30, 40], in: nil) 229 | 230 | XCTAssertNotNil(ob.object) 231 | XCTAssert(ob.object?["key"]?.string == "value") 232 | 233 | XCTAssertNil(null.object) 234 | XCTAssertNil(bool.object) 235 | XCTAssertNil(int.object) 236 | XCTAssertNil(double.object) 237 | XCTAssertNil(string.object) 238 | XCTAssertNil(arr.object) 239 | XCTAssertNil(bytes.object) 240 | 241 | } 242 | 243 | } 244 | -------------------------------------------------------------------------------- /Tests/NodeTests/NodeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NodeEquatableTests.swift 3 | // Node 4 | // 5 | // Created by Logan Wright on 7/20/16. 6 | // 7 | // 8 | 9 | import XCTest 10 | @testable import Node 11 | 12 | class NodeTests: XCTestCase { 13 | static let allTests = [ 14 | ("testInits", testInits), 15 | ("testArrayInits", testArrayInits), 16 | ("testObjectInits", testObjectInits), 17 | ("testNonHomogenousArrayInits", testNonHomogenousArrayInits), 18 | ("testNonHomogenousObjectInits", testNonHomogenousObjectInits), 19 | ("testLiterals", testLiterals), 20 | ("testEquatable", testEquatable), 21 | ] 22 | 23 | func testInits() { 24 | // these are mostly here to ensure compilation errors don't occur 25 | XCTAssert(Node(true) == .bool(true)) 26 | XCTAssert(Node("hi") == .string("hi")) 27 | XCTAssert(Node(1) == .number(1)) 28 | XCTAssert(Node(3.14) == .number(3.14)) 29 | 30 | let uint = UInt(42) 31 | XCTAssert(Node(uint) == .number(Node.Number(uint))) 32 | 33 | let number = Node.Number(2345) 34 | XCTAssert(Node(number) == .number(number)) 35 | 36 | let array = [Node(1), Node(2), Node(3)] 37 | XCTAssert(Node(array) == .array([1,2,3])) 38 | 39 | let object = ["key": Node("value")] 40 | XCTAssert(Node(object) == .object(object)) 41 | 42 | XCTAssert(Node(bytes: [1,2,3,4]) == .bytes([1,2,3,4])) 43 | } 44 | 45 | func testArrayInits() throws { 46 | let array: [Int] = [1,2,3,4,5] 47 | let node = try Node.init(node: array) 48 | XCTAssertEqual(node, [1,2,3,4,5]) 49 | 50 | let optionalArray: [String?] = ["a", "b", "c", nil, "d", nil] 51 | let optionalNode = try Node.init(node: optionalArray) 52 | XCTAssertEqual(optionalNode, ["a", "b", "c", .null, "d", .null]) 53 | } 54 | 55 | func testObjectInits() throws { 56 | let dict: [String: String] = [ 57 | "hello": "world", 58 | "goodbye": "moon" 59 | ] 60 | let node = try Node(node: dict) 61 | XCTAssert(node == ["hello": "world", "goodbye": "moon"]) 62 | 63 | let optionalDict: [String: String?] = [ 64 | "hello": "world", 65 | "goodbye": nil 66 | ] 67 | let optionalNode = try Node(node: optionalDict) 68 | XCTAssertEqual(optionalNode, ["hello": "world", "goodbye": .null]) 69 | } 70 | 71 | func testNonHomogenousArrayInits() throws { 72 | let array: [NodeRepresentable] = [1, "hiya", Node.object(["a": "b"]), false] 73 | let node = try Node(node: array) 74 | XCTAssertEqual(node, [1, "hiya", Node.object(["a": "b"]), false]) 75 | 76 | 77 | let optionalArray: [NodeRepresentable?] = [42, "bye", Node.array([1,2,3]), true, nil] 78 | let optionalNode = try Node(node: optionalArray) 79 | XCTAssertEqual(optionalNode, [42, "bye", Node.array([1,2,3]), true, .null]) 80 | } 81 | 82 | func testNonHomogenousObjectInits() throws { 83 | let dict: [String: NodeRepresentable] = [ 84 | "hello": "world", 85 | "goodbye": 1 86 | ] 87 | let node = try Node(node: dict) 88 | XCTAssertEqual(node, ["hello": "world", "goodbye": 1]) 89 | 90 | let optionalDict: [String: NodeRepresentable?] = [ 91 | "hello": "world", 92 | "goodbye": nil, 93 | "ok": 1 94 | ] 95 | let optionalNode = try Node(node: optionalDict) 96 | XCTAssertEqual(optionalNode, ["hello": "world", "goodbye": .null, "ok": 1]) 97 | } 98 | 99 | func testLiterals() { 100 | XCTAssert(Node.null == nil) 101 | XCTAssert(Node.bool(false) == false) 102 | XCTAssert(Node.number(1) == 1) 103 | XCTAssert(Node.number(42.3) == 42.3) 104 | 105 | XCTAssert(Node.string("test") == "test") 106 | let unicode = Node(unicodeScalarLiteral: "test") 107 | XCTAssert(Node.string("test") == unicode) 108 | let grapheme = Node(extendedGraphemeClusterLiteral: "test") 109 | XCTAssert(Node.string("test") == grapheme) 110 | 111 | XCTAssert(Node.array([1,2,3]) == [1,2,3]) 112 | XCTAssert(Node.object(["key": "value"]) == ["key": "value"]) 113 | } 114 | 115 | func testEquatable() { 116 | let truthyPairs: [(Node, Node)] = [ 117 | (nil, nil), 118 | (1, 1.0), 119 | (true, true), 120 | (false, false), 121 | ("hello", "hello"), 122 | ([1,2,3], [1,2,3]), 123 | (["key": "value"], ["key": "value"]) 124 | ] 125 | 126 | truthyPairs.forEach { lhs, rhs in XCTAssert(lhs == rhs, "\(lhs) should equal \(rhs)") } 127 | 128 | let falsyPairs: [(Node, Node)] = [ 129 | (nil, 42), 130 | (1, "hello"), 131 | (true, ["key": "value"]), 132 | ([1,2,3], false), 133 | ("hello", "goodbye"), 134 | ([1,2,3], [1,2,3,4]), 135 | (["key": "value"], ["array", "of", "strings"]) 136 | ] 137 | 138 | falsyPairs.forEach { lhs, rhs in XCTAssert(lhs != rhs, "\(lhs) should equal \(rhs)") } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Tests/NodeTests/NumberTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumberTests.swift 3 | // Node 4 | // 5 | // Created by Logan Wright on 7/20/16. 6 | // 7 | // 8 | 9 | import XCTest 10 | @testable import Node 11 | @testable import Node 12 | 13 | class NumberTests: XCTestCase { 14 | static let allTests = [ 15 | ("testSignedInit", testSignedInit), 16 | ("testUnsignedInit", testUnsignedInit), 17 | ("testFloatingPoint", testFloatingPoint), 18 | ("testAccessors", testAccessors), 19 | ("testBoolAccessors", testBoolAccessors), 20 | ("testIntMax", testIntMax), 21 | ("testDescriptions", testDescriptions), 22 | ("testEquatableTrue", testEquatableTrue), 23 | ("testEquatableFalse", testEquatableFalse), 24 | ] 25 | 26 | func testSignedInit() { 27 | let a = Node.Number(Int8(1)) 28 | let b = Node.Number(Int16(-2)) 29 | let c = Node.Number(Int32(3)) 30 | let d = Node.Number(Int(-4)) 31 | 32 | XCTAssert([a, b, c, d] == [1, -2, 3, -4]) 33 | } 34 | 35 | func testUnsignedInit() { 36 | let a = Node.Number(UInt8(1)) 37 | let b = Node.Number(UInt16(2)) 38 | let c = Node.Number(UInt32(3)) 39 | let d = Node.Number(UInt(4)) 40 | 41 | XCTAssert([a, b, c, d] == [1, 2, 3, 4]) 42 | } 43 | 44 | func testFloatingPoint() { 45 | let double = Double(52.899) 46 | let float = Float(10.5) 47 | 48 | XCTAssert(Node.Number(double) == 52.899) 49 | XCTAssert(Node.Number(float) == 10.5) 50 | } 51 | 52 | func testAccessors() { 53 | let intRaw = Int(-42) 54 | let doubleRaw = Double(52.8) 55 | let uintRaw = UInt(3000) 56 | 57 | let int = Node.Number(intRaw) 58 | XCTAssert(int.int == -42) 59 | XCTAssert(int.double == -42.0) 60 | XCTAssert(int.uint == 0) 61 | 62 | let double = Node.Number(doubleRaw) 63 | XCTAssert(double.int == 52) 64 | XCTAssert(double.double == 52.8) 65 | XCTAssert(double.uint == 52) 66 | 67 | let uint = Node.Number(uintRaw) 68 | XCTAssert(uint.int == 3000) 69 | XCTAssert(uint.double == 3000.0) 70 | XCTAssert(uint.uint == 3000) 71 | } 72 | 73 | func testBoolAccessors() { 74 | let intTrue = Int(1) 75 | let doubleTrue = Double(1.0) 76 | let uintTrue = UInt(1) 77 | XCTAssert(Node.Number(intTrue).bool == true) 78 | XCTAssert(Node.Number(doubleTrue).bool == true) 79 | XCTAssert(Node.Number(uintTrue).bool == true) 80 | 81 | let intFalse = Int(0) 82 | let doubleFalse = Double(0.0) 83 | let uintFalse = UInt(0) 84 | XCTAssert(Node.Number(intFalse).bool == false) 85 | XCTAssert(Node.Number(doubleFalse).bool == false) 86 | XCTAssert(Node.Number(uintFalse).bool == false) 87 | 88 | let intNil = Int(-6) 89 | let doubleNil = Double(9.98) 90 | let uintNil = UInt(899999) 91 | XCTAssertNil(Node.Number(intNil).bool) 92 | XCTAssertNil(Node.Number(doubleNil).bool) 93 | XCTAssertNil(Node.Number(uintNil).bool) 94 | } 95 | 96 | func testIntMax() { 97 | let exceed = UInt.intMax + 50 98 | let number = Node.Number(exceed) 99 | XCTAssert(number.int == Int.max) 100 | XCTAssert(number.uint == exceed) 101 | } 102 | 103 | func testDescriptions() { 104 | let int = Int(-6) 105 | let double = Double(9.98) 106 | let uint = UInt(899999) 107 | 108 | XCTAssert(Node.Number(int).description == "-6") 109 | XCTAssert(Node.Number(double).description == "9.98") 110 | XCTAssert(Node.Number(uint).description == "899999") 111 | } 112 | 113 | func testEquatableTrue() { 114 | let int = Node.Number(Int(88)) 115 | let double = Node.Number(Double(88)) 116 | let uint = Node.Number(UInt(88)) 117 | 118 | XCTAssert(int == int) 119 | XCTAssert(int == double) 120 | XCTAssert(int == uint) 121 | 122 | XCTAssert(double == int) 123 | XCTAssert(double == double) 124 | XCTAssert(double == uint) 125 | 126 | XCTAssert(uint == int) 127 | XCTAssert(uint == double) 128 | XCTAssert(uint == uint) 129 | } 130 | 131 | func testEquatableFalse() { 132 | let int = Node.Number(Int(-1)) 133 | let double = Node.Number(Double(99.8)) 134 | let uint = Node.Number(UInt(9632)) 135 | 136 | XCTAssert(int != double) 137 | XCTAssert(int != uint) 138 | 139 | XCTAssert(double != int) 140 | XCTAssert(double != uint) 141 | 142 | XCTAssert(uint != int) 143 | XCTAssert(uint != double) 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /Tests/NodeTests/SequenceConvertibleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SequenceConvertibleTests.swift 3 | // Node 4 | // 5 | // Created by Logan Wright on 7/20/16. 6 | // 7 | // 8 | 9 | import XCTest 10 | import Node 11 | 12 | class TestInitializable: NodeInitializable { 13 | let node: Node 14 | 15 | required init(node: Node) { 16 | self.node = node 17 | } 18 | } 19 | 20 | final class Foo: NodeConvertible { 21 | var node: Node 22 | var contextMakeNode: Context? 23 | 24 | init(node: Node) throws { 25 | self.node = node 26 | } 27 | 28 | func makeNode(in context: Context? = nil) throws -> Node { 29 | self.contextMakeNode = context 30 | return node 31 | } 32 | } 33 | 34 | class SequenceConvertibleTests: XCTestCase { 35 | static let allTests = [ 36 | ("testSequence", testSequence), 37 | ("testDictionary", testDictionary), 38 | ("testArrayConvert", testArrayConvert), 39 | ("testSetConvert", testSetConvert), 40 | ] 41 | 42 | func testSequence() throws { 43 | let ints: [Int] = [1,2,3,4,5] 44 | let node = try ints.makeNode(in: nil) 45 | XCTAssert(node == .array([1,2,3,4,5], in: nil)) 46 | 47 | let representables = ints.map { $0 as NodeRepresentable } 48 | let node2 = try representables.makeNode(in: nil) 49 | XCTAssert(node2 == .array([1,2,3,4,5], in: nil)) 50 | 51 | let models = try ints.converted(to: [TestInitializable].self, in: nil) 52 | #if swift(>=4.1) 53 | let backInts = models.map { $0.node } .compactMap { $0.int } 54 | #else 55 | let backInts = models.map { $0.node } .flatMap { $0.int } 56 | #endif 57 | XCTAssert(backInts == ints) 58 | 59 | let models2 = try representables.converted(to: [TestInitializable].self, in: nil) 60 | #if swift(>=4.1) 61 | let backInts2 = models2.map { $0.node } .compactMap { $0.int } 62 | #else 63 | let backInts2 = models2.map { $0.node } .flatMap { $0.int } 64 | #endif 65 | XCTAssert(backInts2 == ints) 66 | 67 | 68 | // This tests whether the context is passed to the sequence 69 | let foo1 = try Foo(node: [ 70 | "hello" 71 | ]) 72 | let foo2 = try Foo(node: [ 73 | "goodbye" 74 | ]) 75 | 76 | XCTAssertNil(foo1.contextMakeNode) 77 | XCTAssertNil(foo2.contextMakeNode) 78 | 79 | let context = ObjectContext(["isContext": true]) 80 | 81 | let _ = try [foo1, foo2].makeNode(in: context) 82 | 83 | guard let foo1Context = foo1.contextMakeNode as? ObjectContext, 84 | let foo2Context = foo1.contextMakeNode as? ObjectContext else { 85 | XCTFail() 86 | return 87 | } 88 | 89 | XCTAssert(foo1Context === context) 90 | XCTAssert(foo2Context === context) 91 | 92 | } 93 | 94 | func testDictionary() throws { 95 | let dict: [String: String] = [ 96 | "key": "val", 97 | "hi": "world" 98 | ] 99 | let node = try dict.makeNode(in: nil) 100 | XCTAssert(node == ["key": "val", "hi": "world"]) 101 | 102 | let model = try dict.converted(to: TestInitializable.self, in: nil) 103 | XCTAssert(model.node["key"]?.string == "val") 104 | XCTAssert(model.node["hi"]?.string == "world") 105 | } 106 | 107 | func testArrayConvert() throws { 108 | let ints = try [Int](node: Node.array([1,2,3,4,"5"])) 109 | XCTAssert(ints == [1,2,3,4,5]) 110 | 111 | let one = try [Int](node: 1) 112 | XCTAssert(one == [1]) 113 | 114 | let strings = ["1", "2", "3", "4", "5"] 115 | let collected = try strings.converted(to: [Int].self, in: nil) 116 | XCTAssert(collected == [1,2,3,4,5]) 117 | 118 | let collectedMixed = try [1, 2, "3", "4", 5].converted(to: [Int].self, in: nil) 119 | XCTAssert(collectedMixed == [1,2,3,4,5]) 120 | } 121 | 122 | func testSetConvert() throws { 123 | let ints = try Set(node: Node.array([1,2,3,4,"5"])) 124 | XCTAssert(ints == [1,2,3,4,5]) 125 | 126 | let one = try Set(node: 1) 127 | XCTAssert(one == [1]) 128 | 129 | let strings = ["1", "2", "3", "4", "5"] 130 | let collected = try Set(node: Node(node: strings)) 131 | XCTAssert(collected == [1,2,3,4,5]) 132 | 133 | let collectedMixed = try [1, 2, "3", "4", 5].converted(to: Set.self, in: nil) 134 | XCTAssert(collectedMixed == [1,2,3,4,5]) 135 | } 136 | 137 | func testRepresentableDictionary() throws { 138 | let node = try Node(node: [ 139 | "hello": 52, 140 | ]) 141 | XCTAssertEqual(node, .object(["hello": 52])) 142 | 143 | let foo = try Foo(node: [ 144 | "hello": 52 145 | ]) 146 | XCTAssertEqual(foo.node, .object(["hello": 52])) 147 | 148 | let empty: Node? = nil 149 | let fooWithNil = try Foo.init(node: [ 150 | "hello": empty 151 | ]) 152 | XCTAssertEqual(fooWithNil.node, .object(["hello": .null])) 153 | } 154 | 155 | func testRepresentableArray() throws { 156 | let node = try Node(node: [ 157 | "hello", 158 | ]) 159 | XCTAssertEqual(node, .array(["hello"])) 160 | 161 | 162 | let foo = try Foo(node: [ 163 | "hello" 164 | ]) 165 | XCTAssertEqual(foo.node, .array(["hello"])) 166 | 167 | let empty: Node? = nil 168 | let fooWithNil = try Foo(node: [ 169 | empty 170 | ]) 171 | XCTAssertEqual(fooWithNil.node, .array([.null])) 172 | 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Tests/NodeTests/SettersTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | import Node 4 | 5 | extension Node { 6 | internal init() { 7 | self.init([:], in: nil) 8 | } 9 | } 10 | 11 | class SettersTests: XCTestCase { 12 | static let allTests = [ 13 | ("testSetters", testSetters) 14 | ] 15 | 16 | func testSetters() throws { 17 | var node = Node() 18 | 19 | let singular = 1 20 | try node.set("singular", singular) 21 | 22 | let array = ["foo", "bar"] 23 | try node.set("array", array) 24 | 25 | let nestedArray = [[0], [1], [2]] 26 | try node.set("nestedArray", nestedArray) 27 | 28 | let arrayOfObjects = [["name": "a"], ["name": "b"]] 29 | try node.set("arrayOfObjects", arrayOfObjects) 30 | 31 | let dictionary = ["hello": "world"] 32 | try node.set("dictionary", dictionary) 33 | 34 | let dictionaryWithArray = ["hello": ["a", "b", "c"]] 35 | try node.set("dictionaryWithArray", dictionaryWithArray) 36 | 37 | let path = "path" 38 | try node.set("I.Live.Down.The.Road.At.0.Index", path) 39 | 40 | let dictionaryWithDictionary = [ 41 | "person": [ 42 | "age": 13 43 | ] 44 | ] 45 | try node.set("person", dictionaryWithDictionary) 46 | // ASSERTIONS 47 | 48 | try node.assert("singular", expectation: singular) 49 | 50 | try node.assert("array", expectation: array.makeNode(in: nil)) 51 | 52 | let na = try nestedArray.map { try $0.makeNode(in: nil) } 53 | try node.assert("nestedArray", expectation: Node(na)) 54 | 55 | let ao = try arrayOfObjects.map { try $0.makeNode(in: nil) } 56 | try node.assert("arrayOfObjects", expectation: Node(ao)) 57 | 58 | try node.assert("dictionary", expectation: Node(node: dictionary, in: nil)) 59 | 60 | let da = ["hello": ["a", "b", "c"]] as Node 61 | try node.assert("dictionaryWithArray", expectation: da) 62 | 63 | try node.assert("I.Live.Down.The.Road.At.0.Index", expectation: "path") 64 | 65 | try node.assert("person", expectation: try Node(node: dictionaryWithDictionary)) 66 | } 67 | } 68 | 69 | extension Node { 70 | fileprivate func assert(_ key: String, expectation: NodeRepresentable?) throws { 71 | let expectation = try expectation?.makeNode(in: nil) 72 | let value = self[key] 73 | XCTAssertEqual(value, expectation) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Tests/PathIndexableTests/PathIndexableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BasicTypes.swift 3 | // Genome 4 | // 5 | // Created by Logan Wright on 9/19/15. 6 | // Copyright © 2015 lowriDevs. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import PathIndexable 11 | 12 | enum Node { 13 | case null 14 | case bool(Bool) 15 | case number(Double) 16 | case string(String) 17 | case array([Node]) 18 | case object([String:Node]) 19 | } 20 | 21 | extension Node: PathIndexable { 22 | var pathIndexableArray: [Node]? { 23 | guard case let .array(arr) = self else { 24 | return nil 25 | } 26 | return arr 27 | } 28 | 29 | var pathIndexableObject: [String: Node]? { 30 | guard case let .object(ob) = self else { 31 | return nil 32 | } 33 | return ob 34 | } 35 | 36 | init(_ array: [Node]) { 37 | self = .array(array) 38 | } 39 | 40 | init(_ object: [String: Node]) { 41 | self = .object(object) 42 | } 43 | } 44 | 45 | class PathIndexableTests: XCTestCase { 46 | static var allTests = [ 47 | ("testInt", testInt), 48 | ("testString", testString), 49 | ("testStringSequenceObject", testStringSequenceObject), 50 | ("testStringSequenceArray", testStringSequenceArray), 51 | ("testIntSequence", testIntSequence), 52 | ("testMixed", testMixed), 53 | ("testAccessNil", testAccessNil), 54 | ] 55 | 56 | func testInt() { 57 | let array: Node = .array(["one", 58 | "two", 59 | "three"].map(Node.string)) 60 | guard let node = array[1] else { 61 | XCTFail() 62 | return 63 | } 64 | guard case let .string(val) = node else { 65 | XCTFail() 66 | return 67 | } 68 | 69 | XCTAssert(val == "two") 70 | } 71 | 72 | func testString() { 73 | let object = Node(["a" : .number(1)]) 74 | guard let node = object["a"] else { 75 | XCTFail() 76 | return 77 | } 78 | guard case let .number(val) = node else { 79 | XCTFail() 80 | return 81 | } 82 | 83 | XCTAssert(val == 1) 84 | } 85 | 86 | func testStringSequenceObject() { 87 | let sub = Node(["path" : .string("found me!")]) 88 | let ob = Node(["key" : sub]) 89 | guard let node = ob["key", "path"] else { 90 | XCTFail() 91 | return 92 | } 93 | guard case let .string(val) = node else { 94 | XCTFail() 95 | return 96 | } 97 | 98 | XCTAssert(val == "found me!") 99 | } 100 | 101 | func testStringSequenceArray() { 102 | let zero = Node(["a" : .number(0)]) 103 | let one = Node(["a" : .number(1)]) 104 | let two = Node(["a" : .number(2)]) 105 | let three = Node(["a" : .number(3)]) 106 | let obArray = Node([zero, one, two, three]) 107 | 108 | guard let collection = obArray["a"] else { 109 | XCTFail() 110 | return 111 | } 112 | guard case let .array(value) = collection else { 113 | XCTFail() 114 | return 115 | } 116 | 117 | #if swift(>=4.1) 118 | let mapped: [Double] = value.compactMap { node in 119 | guard case let .number(val) = node else { 120 | return nil 121 | } 122 | return val 123 | } 124 | #else 125 | let mapped: [Double] = value.flatMap { node in 126 | guard case let .number(val) = node else { 127 | return nil 128 | } 129 | return val 130 | } 131 | #endif 132 | XCTAssert(mapped == [0,1,2,3]) 133 | } 134 | 135 | func testIntSequence() { 136 | let inner = Node([.string("..."), 137 | .string("found me!")]) 138 | let outer = Node([inner]) 139 | 140 | guard let node = outer[0, 1] else { 141 | XCTFail() 142 | return 143 | } 144 | guard case let .string(value) = node else { 145 | XCTFail() 146 | return 147 | } 148 | 149 | XCTAssert(value == "found me!") 150 | } 151 | 152 | func testMixed() { 153 | let array = Node([.string("a"), .string("b"), .string("c")]) 154 | let mixed = Node(["one" : array]) 155 | 156 | guard let node = mixed["one", 1] else { 157 | XCTFail() 158 | return 159 | } 160 | guard case let .string(value) = node else { 161 | XCTFail() 162 | return 163 | } 164 | 165 | XCTAssert(value == "b") 166 | } 167 | 168 | func testOutOfBounds() { 169 | var array = Node([.number(1.0), .number(2.0), .number(3.0)]) 170 | XCTAssertNil(array[3]) 171 | array[3] = .number(4.0) 172 | XCTAssertNil(array[3]) 173 | } 174 | 175 | func testSetArray() { 176 | var array = Node([.number(1.0), .number(2.0), .number(3.0)]) 177 | XCTAssertEqual(array[1], .number(2.0)) 178 | array[1] = .number(4.0) 179 | XCTAssertEqual(array[1], .number(4.0)) 180 | array[1] = nil 181 | XCTAssertEqual(array[1], .number(3.0)) 182 | } 183 | 184 | func testMakeEmpty() { 185 | let int: Int = 5 186 | let node: Node = int.makeEmptyStructureForIndexing() 187 | XCTAssertEqual(node, .array([])) 188 | } 189 | 190 | func testAccessNil() { 191 | let array = Node([.object(["test": .number(42)]), .number(5)]) 192 | XCTAssertNil(array["foo"]) 193 | 194 | if let keyValResult = array["test"], case let .array(array) = keyValResult { 195 | XCTAssertEqual(array.count, 1) 196 | XCTAssertEqual(array.first, .number(42)) 197 | } else { 198 | XCTFail("Expected array result from array key val") 199 | } 200 | 201 | let number = Node.number(5) 202 | XCTAssertNil(number["test"]) 203 | } 204 | 205 | func testSetObject() { 206 | var object = Node([ 207 | "one": .number(1.0), 208 | "two": .number(2.0), 209 | "three": .number(3.0) 210 | ]) 211 | XCTAssertEqual(object["two"], .number(2.0)) 212 | object["two"] = .number(4.0) 213 | XCTAssertEqual(object["two"], .number(4.0)) 214 | object["two"] = nil 215 | XCTAssertEqual(object["two"], nil) 216 | 217 | var array = Node([object, object]) 218 | array["two"] = .number(5.0) 219 | } 220 | 221 | func testPath() { 222 | var object = Node([ 223 | "one": Node([ 224 | "two": .number(42) 225 | ]) 226 | ]) 227 | XCTAssertEqual(object["one.two"], .number(42)) 228 | 229 | object["one.two"] = .number(5) 230 | XCTAssertEqual(object["one.two"], .number(5)) 231 | 232 | let comps = "one.two.5.&".keyPathComponents() 233 | XCTAssertEqual(comps, ["one", "two", "5", "&"]) 234 | } 235 | 236 | func testStringPathIndex() { 237 | let path = ["hello", "3"] 238 | let node = Node( 239 | [ 240 | "hello": .array([ 241 | .string("a"), 242 | .string("b"), 243 | .string("c"), 244 | .string("d") 245 | ]) 246 | ] 247 | ) 248 | 249 | if let n = node[path], case let .string(result) = n { 250 | print(result) 251 | XCTAssert(result == "d") 252 | } else { 253 | XCTFail("Expected result") 254 | } 255 | } 256 | 257 | func testDotKey() { 258 | let node = Node( 259 | [ 260 | "foo.bar": .array([ 261 | .string("a"), 262 | .string("b"), 263 | .string("c"), 264 | .string("d") 265 | ]) 266 | ] 267 | ) 268 | 269 | if let n = node[DotKey("foo.bar"), 3], case let .string(result) = n { 270 | print(result) 271 | XCTAssert(result == "d") 272 | } else { 273 | XCTFail("Expected result") 274 | } 275 | } 276 | } 277 | 278 | extension Node: Equatable { 279 | 280 | } 281 | 282 | func ==(lhs: Node, rhs: Node) -> Bool { 283 | switch (lhs, rhs) { 284 | case (.number(let l), .number(let r)): 285 | return l == r 286 | case (.array(let l), .array(let r)): 287 | return l == r 288 | default: 289 | return false 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /manifesto.md: -------------------------------------------------------------------------------- 1 | # Node Manifesto 2 | 3 | Node is a data encapsulation enum that facilities transformations from one type to another in Swift. 4 | 5 | ## Reasoning 6 | 7 | If you are working in an environment that requires converting between data types, especially where those conversions may required intermediate types, Node will be invaluable. Working on the web, for instance, commonly requires conversions from formats like JSON or XML to database formats like MySQL or Mongo. Instead of creating explicit conversions for each permutation of your supported types, you just conform each data type to Node once. Any type that conforms to Node's protocols can be converted to any other type that also conforms. Visually, this looks like: 8 | 9 | ### Without Node 10 | 11 | ``` 12 | XML --> MySQL 13 | Form --> MySQL 14 | MySQL --> JSON 15 | XML --> Mongo 16 | Form --> Mongo 17 | Mongo --> JSON 18 | ``` 19 | 20 | Six conformances conversions required for six conversion use cases. 21 | 22 | ### With Node 23 | 24 | ``` 25 | --- --- 26 | XML | | XML 27 | JSON | | JSON 28 | Form > - Node - < Form 29 | MySQL | | MySQL 30 | Mongo | | Mongo 31 | --- --- 32 | ``` 33 | 34 | Five conversions required for 25 conversion use cases. 35 | 36 | ## Usage 37 | 38 | In this simple example, you can see one conformance to `NodeConvertible` for the class `Log` provides compatibility with the MySQL database and JSON REST API. 39 | 40 | ```swift 41 | final class Log: Model { 42 | let message: String 43 | 44 | // MARK: NodeConvertible 45 | 46 | init(node: Node, in context: Context) throws { 47 | message = try node.get("message") 48 | } 49 | 50 | func makeNode(in context: Context) throws { 51 | var node = Node() 52 | try node.set("message", message) 53 | return node 54 | } 55 | } 56 | 57 | let log = try Log.find(1) 58 | let json = try log.makeJSON() 59 | ``` 60 | 61 | ### Advanced 62 | 63 | In cases where the serialization or parsing for a given conformance varies, Node is still a powerful tool. 64 | 65 | In the following example, the User conforms manually to the `RowConvertible` and `JSONConvertible` types. 66 | However, since both `Row` and `JSON` are `NodeConvertible` types, Node conveniences of `.get` and `.set` are available. 67 | 68 | ```swift 69 | final class User: Model { 70 | let name: Name 71 | let age: Int 72 | let organizationId: Node 73 | 74 | var organization: Parent { 75 | return parent(id: organizationId) 76 | } 77 | 78 | init(row: Row) throws { 79 | name = try row.get() // uses whole node to init Name 80 | age = try row.get("age") // converts row to Int 81 | organizationId = try row.get(Organization.foreignIdKey) // converts row to ID 82 | } 83 | 84 | func makeRow() throws -> Row { 85 | var row = Row() 86 | try row.set(name) // merges Name row into current row 87 | try row.set("age", age) // converts Int to Row 88 | try row.set(Organization.foreignIdKey, organizationId) // converts ID to Row 89 | return row 90 | } 91 | } 92 | 93 | // MARK: JSON 94 | 95 | extension User: JSONConvertible { 96 | init(json: JSON) throws { 97 | name = try json.get("name") // uses Node at key `"name"` to init Name with json init 98 | age = try json.get("age") // converts JSON to Int 99 | organizationId = try json.get("organization.id") // converts JSON to Id 100 | } 101 | 102 | func makeJSON() throws -> JSON { 103 | var json = JSON() 104 | try json.set("id", id) // converts ID to JSON 105 | try json.set("name", name) // calls `makeJSON` on name and sets to key `"name"` 106 | try json.set("age", age) // converts Int to JSON 107 | try json.set("organization", organization) // automatically calls `.get()` and converts organization to JSON 108 | return json 109 | } 110 | } 111 | ``` 112 | 113 | ### Custom Types 114 | 115 | Users can declare their own `NodeConvertible` types to take advantage of the `Initializable` + `Representable` = `Convertible` pattern. 116 | 117 | ```swift 118 | struct InternalFormat { 119 | // proprietary stuff here 120 | } 121 | 122 | extension InternalFormat: NodeConvertible { 123 | // only need to conform once 124 | } 125 | ``` 126 | 127 | ```swift 128 | protocol InternalFormatInitializable { 129 | init(internalFormat: InternalFormat) throws 130 | } 131 | 132 | protocol InternalFormatRepresentable { 133 | func makeInternalFormat() throws -> InternalFormat 134 | } 135 | 136 | protocol InternalFormatConvertible: InternalFormatInitializable, InternalFormatRepresentable { } 137 | ``` 138 | 139 | ```swift 140 | extension User: InternalFormatConvertible { 141 | init(internalFormat: InternalFormat) throws { 142 | name = try internalFormat.get("name") // calls `Name.init(internalFormat: ...)` if exists, or `Name.init(..., context: InternalFormatContext())` 143 | age = try internalFormat.get("age") // automatically converts InternalFormat to Int 144 | organizationId = try internalFormat.get("orgId") // automatically converts InternalFormat to ID 145 | } 146 | 147 | func makeInternalFormat() throws -> InternalFormat { 148 | let i = InternalFormat() 149 | try i.set("name", name) // calls `Name.makeInternalFormat()` if exists, or `Name.makeNode(in: InternalFormatContext())` 150 | try i.set("age", age) // automatically converts Int to InternalFormat 151 | try i.set("orgId", organizationId) // automatically converts ID to InternalFormat 152 | return i 153 | } 154 | } 155 | ``` 156 | --------------------------------------------------------------------------------