├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── Async │ ├── Async+NIO.swift │ ├── AsyncError.swift │ ├── Collection+Future.swift │ ├── Deprecated.swift │ ├── Exports.swift │ ├── Future+DoCatch.swift │ ├── Future+Flatten.swift │ ├── Future+Global.swift │ ├── Future+Map.swift │ ├── Future+Transform.swift │ ├── Future+Variadic.swift │ ├── Future+Void.swift │ ├── FutureType.swift │ ├── QueueHandler.swift │ └── Worker.swift ├── Bits │ ├── BitsError.swift │ ├── Byte+Alphabet.swift │ ├── Byte+Control.swift │ ├── Byte+Digit.swift │ ├── ByteBuffer+binaryFloatingPointOperations.swift │ ├── ByteBuffer+peek.swift │ ├── ByteBuffer+require.swift │ ├── ByteBuffer+string.swift │ ├── Bytes.swift │ ├── Data+Bytes.swift │ ├── Data+Strings.swift │ └── Deprecated.swift ├── COperatingSystem │ └── libc.swift ├── Core │ ├── BasicKey.swift │ ├── CaseInsensitiveString.swift │ ├── CodableReflection │ │ ├── Decodable+Reflectable.swift │ │ ├── ReflectionDecodable.swift │ │ └── ReflectionDecoders.swift │ ├── CoreError.swift │ ├── Data+Base64URL.swift │ ├── Data+Hex.swift │ ├── DataCoders.swift │ ├── Deprecated.swift │ ├── DirectoryConfig.swift │ ├── Exports.swift │ ├── File.swift │ ├── Future+Unwrap.swift │ ├── FutureEncoder.swift │ ├── HeaderValue.swift │ ├── LosslessDataConvertible.swift │ ├── MediaType.swift │ ├── NestedData.swift │ ├── NotFound.swift │ ├── OptionalType.swift │ ├── Process+Execute.swift │ ├── Reflectable.swift │ ├── String+Utilities.swift │ └── Thread+Async.swift └── Debugging │ ├── Debuggable.swift │ ├── Demangler.swift │ └── SourceLocation.swift ├── Tests ├── AsyncTests │ └── AsyncTests.swift ├── BitsTests │ ├── ByteBufferPeekTests.swift │ └── ByteBufferRequireTests.swift ├── CoreTests │ ├── CoreTests.swift │ └── ReflectableTests.swift ├── DebuggingTests │ ├── FooError.swift │ ├── FooErrorTests.swift │ ├── GeneralTests.swift │ ├── MinimumError.swift │ ├── TestError.swift │ └── TraceableTests.swift └── LinuxMain.swift └── circle.yml /.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | .build 3 | .DS_Store 4 | *.xcodeproj 5 | Package.pins 6 | Package.resolved 7 | DerivedData/ 8 | .swiftpm 9 | 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Vapor Core 2 | 3 | If you found a mistake or think of a cool new feature, please [create an issue](https://github.com/vapor/core/issues/new) or, if you want to implement it yourself, [fork this repo](https://github.com/vapor/core/fork) and open a Pull Request! 4 | 5 | We'll take a look as soon as we can. 6 | 7 | Thanks! 8 | 9 | ### NOTE: 10 | During the development of Vapor 4, this package will be split up into multiple smaller ones. For example [codable-kit](https://github.com/vapor-community/codable-kit) and [nio-kit](https://github.com/vapor-community/nio-kit). If you create a PR/issue here, please be so kind to also open it in the "new" repo. 11 | Current list of existing repos Core will be split into: 12 | - [codable-kit](https://github.com/vapor-community/codable-kit) (Parts of Core & Bits) 13 | - [nio-kit](https://github.com/vapor-community/nio-kit) (Async module) 14 | 15 | ## Maintainers 16 | 17 | - [@MrLotU](https://github.com/MrLotU) 18 | 19 | See the [Vapor maintainers doc](https://github.com/vapor/vapor/blob/master/Docs/maintainers.md) for more information. 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Qutheory, LLC 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 all 13 | 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 THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "Core", 6 | products: [ 7 | .library(name: "Async", targets: ["Async"]), 8 | .library(name: "Bits", targets: ["Bits"]), 9 | .library(name: "Core", targets: ["Core"]), 10 | .library(name: "COperatingSystem", targets: ["COperatingSystem"]), 11 | .library(name: "Debugging", targets: ["Debugging"]), 12 | ], 13 | dependencies: [ 14 | /// Event-driven network application framework for high performance protocol servers & clients, non-blocking. 15 | .package(url: "https://github.com/apple/swift-nio.git", from: "1.14.1"), 16 | ], 17 | targets: [ 18 | .target(name: "Async", dependencies: ["NIO"]), 19 | .testTarget(name: "AsyncTests", dependencies: ["Async"]), 20 | .target(name: "Bits", dependencies: ["Debugging", "NIO"]), 21 | .testTarget(name: "BitsTests", dependencies: ["Bits", "NIO"]), 22 | .target(name: "Core", dependencies: ["Async", "Bits", "COperatingSystem", "Debugging", "NIOFoundationCompat"]), 23 | .testTarget(name: "CoreTests", dependencies: ["Core"]), 24 | .target(name: "COperatingSystem"), 25 | .target(name: "Debugging"), 26 | .testTarget(name: "DebuggingTests", dependencies: ["Debugging"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

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

21 | -------------------------------------------------------------------------------- /Sources/Async/Async+NIO.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | import NIO 3 | 4 | /// Convenience shorthand for `EventLoopFuture`. 5 | public typealias Future = EventLoopFuture 6 | 7 | /// Convenience shorthand for `EventLoopPromise`. 8 | public typealias Promise = EventLoopPromise 9 | 10 | extension EventLoop { 11 | /// Creates a new promise for the specified type. 12 | public func newPromise(_ type: T.Type, file: StaticString = #file, line: UInt = #line) -> Promise { 13 | return newPromise(file: file, line: line) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Async/AsyncError.swift: -------------------------------------------------------------------------------- 1 | /// Logs verbose debug info if `VERBOSE` compiler flag is enabled. 2 | internal func VERBOSE(_ string: @autoclosure () -> (String)) { 3 | #if VERBOSE 4 | print("[VERBOSE] [Async] \(string())") 5 | #endif 6 | } 7 | 8 | 9 | /// Only includes the supplied closure in non-release builds. 10 | internal func debugOnly(_ body: () -> Void) { 11 | assert({ body(); return true }()) 12 | } 13 | 14 | /// Logs a runtime warning. 15 | internal func WARNING(_ string: @autoclosure () -> String) { 16 | print("[WARNING] [Async] \(string())") 17 | } 18 | 19 | 20 | /// Logs an unhandleable runtime error. 21 | internal func ERROR(_ string: @autoclosure () -> String) { 22 | print("[ERROR] [Async] \(string())") 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Async/Collection+Future.swift: -------------------------------------------------------------------------------- 1 | extension Collection { 2 | /// Maps a collection of same-type `Future`s. 3 | /// 4 | /// See `Future.map` 5 | public func map(to type: T.Type, on worker: Worker, _ callback: @escaping ([S]) throws -> T) -> Future where Element == Future { 6 | return flatten(on: worker).map(to: T.self, callback) 7 | } 8 | 9 | /// Maps a collection of same-type `Future`s. 10 | /// 11 | /// See `Future.flatMap` 12 | public func flatMap(to type: T.Type, on worker: Worker, _ callback: @escaping ([S]) throws -> Future) -> Future where Element == Future { 13 | return flatten(on: worker).flatMap(to: T.self, callback) 14 | } 15 | } 16 | 17 | extension Collection where Element == Future { 18 | /// Maps a collection of void `Future`s. 19 | /// 20 | /// See `Future.map` 21 | public func map(to type: T.Type, on worker: Worker, _ callback: @escaping () throws -> T) -> Future { 22 | return flatten(on: worker).map(to: T.self) { _ in 23 | return try callback() 24 | } 25 | } 26 | 27 | /// Maps a collection of void `Future`s. 28 | /// 29 | /// See `Future.flatMap` 30 | public func flatMap(to type: T.Type, on worker: Worker, _ callback: @escaping () throws -> Future) -> Future { 31 | return flatten(on: worker).flatMap(to: T.self) { _ in 32 | return try callback() 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Async/Deprecated.swift: -------------------------------------------------------------------------------- 1 | extension Future { 2 | /// See `cascade(promise:)`. 3 | @available(*, deprecated, renamed: "cascade(promise:)") 4 | public func chain(to promise: Promise) { 5 | self.cascade(promise: promise) 6 | } 7 | } 8 | 9 | extension Array where Element == Future { 10 | /// See `flatten(on:)`. 11 | @available(*, deprecated) 12 | public func transform(on worker: Worker, to callback: @escaping () throws -> Future) -> Future { 13 | return flatten(on: worker).flatMap(to: T.self, callback) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Async/Exports.swift: -------------------------------------------------------------------------------- 1 | @_exported import NIO 2 | -------------------------------------------------------------------------------- /Sources/Async/Future+DoCatch.swift: -------------------------------------------------------------------------------- 1 | // MARK: Do / Catch 2 | 3 | extension Future { 4 | /// Adds a callback for handling this `Future`'s result when it becomes available. 5 | /// 6 | /// futureString.do { string in 7 | /// print(string) 8 | /// }.catch { error in 9 | /// print("oops: \(error)") 10 | /// } 11 | /// 12 | /// - warning: Don't forget to use `catch` to handle the error case. 13 | public func `do`(_ callback: @escaping (T) -> ()) -> Future { 14 | whenSuccess(callback) 15 | return self 16 | } 17 | 18 | /// Adds a callback for handling this `Future`'s result if an error occurs. 19 | /// 20 | /// futureString.do { string in 21 | /// print(string) 22 | /// }.catch { error in 23 | /// print("oops: \(error)") 24 | /// } 25 | /// 26 | /// - note: Will *only* be executed if an error occurs. Successful results will not call this handler. 27 | @discardableResult 28 | public func `catch`(_ callback: @escaping (Error) -> ()) -> Future { 29 | whenFailure(callback) 30 | return self 31 | } 32 | 33 | /// Adds a handler to be asynchronously executed on completion of this future. 34 | /// 35 | /// futureString.do { string in 36 | /// print(string) 37 | /// }.catch { error in 38 | /// print("oops: \(error)") 39 | /// }.always { 40 | /// print("done") 41 | /// } 42 | /// 43 | /// - note: Will be executed on both success and failure, but will not receive any input. 44 | @discardableResult 45 | public func always(_ callback: @escaping () -> ()) -> Future { 46 | whenComplete(callback) 47 | return self 48 | } 49 | } 50 | 51 | extension Collection { 52 | /// Adds a callback for handling this `[Future]`'s result when it becomes available. 53 | /// 54 | /// futureStrings.do { strings in 55 | /// print(strings) 56 | /// }.catch { error in 57 | /// print("oops: \(error)") 58 | /// } 59 | /// 60 | /// - warning: Don't forget to use `catch` to handle the error case. 61 | public func `do`(on worker: Worker, _ callback: @escaping ([T]) -> ()) -> Future<[T]> where Element == Future { 62 | return self.flatten(on: worker).do(callback) 63 | } 64 | 65 | /// Adds a callback for handling this `[Future]`'s result if an error occurs. 66 | /// 67 | /// futureStrings.do { strings in 68 | /// print(strings) 69 | /// }.catch { error in 70 | /// print("oops: \(error)") 71 | /// } 72 | /// 73 | /// - note: Will *only* be executed if an error occurs. Successful results will not call this handler. 74 | @discardableResult 75 | public func `catch`(on worker: Worker,_ callback: @escaping (Error) -> ()) -> Future<[T]> where Element == Future { 76 | return self.flatten(on: worker).catch(callback) 77 | } 78 | 79 | 80 | /// Adds a handler to be asynchronously executed on completion of these futures. 81 | /// 82 | /// futureStrings.do { strings in 83 | /// print(strings) 84 | /// }.catch { error in 85 | /// print("oops: \(error)") 86 | /// }.always { 87 | /// print("done") 88 | /// } 89 | /// 90 | /// - note: Will be executed on both success and failure, but will not receive any input. 91 | @discardableResult 92 | public func always(on worker: Worker,_ callback: @escaping () -> ()) -> Future<[T]> where Element == Future { 93 | return self.flatten(on: worker).always(callback) 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /Sources/Async/Future+Flatten.swift: -------------------------------------------------------------------------------- 1 | // MARK: Flatten 2 | 3 | /// A closure that returns a future. 4 | public typealias LazyFuture = () throws -> Future 5 | 6 | extension Collection { 7 | /// Flattens an array of lazy futures into a future with an array of results. 8 | /// - note: each subsequent future will wait for the previous to complete before starting. 9 | public func syncFlatten(on worker: Worker) -> Future<[T]> where Element == LazyFuture { 10 | let promise = worker.eventLoop.newPromise([T].self) 11 | 12 | var elements: [T] = [] 13 | elements.reserveCapacity(self.count) 14 | 15 | var iterator = makeIterator() 16 | func handle(_ future: LazyFuture) { 17 | do { 18 | try future().do { res in 19 | elements.append(res) 20 | if let next = iterator.next() { 21 | handle(next) 22 | } else { 23 | promise.succeed(result: elements) 24 | } 25 | }.catch { error in 26 | promise.fail(error: error) 27 | } 28 | } catch { 29 | promise.fail(error: error) 30 | } 31 | } 32 | 33 | if let first = iterator.next() { 34 | handle(first) 35 | } else { 36 | promise.succeed(result: elements) 37 | } 38 | 39 | return promise.futureResult 40 | } 41 | } 42 | 43 | extension Collection where Element == LazyFuture { 44 | /// Flattens an array of lazy void futures into a single void future. 45 | /// - note: each subsequent future will wait for the previous to complete before starting. 46 | public func syncFlatten(on worker: Worker) -> Future { 47 | let flatten: Future<[Void]> = self.syncFlatten(on: worker) 48 | return flatten.transform(to: ()) 49 | } 50 | } 51 | 52 | extension Collection { 53 | /// Flattens an array of futures into a future with an array of results. 54 | /// - note: the order of the results will match the order of the futures in the input array. 55 | public func flatten(on worker: Worker) -> Future<[T]> where Element == Future { 56 | return Future.whenAll(Array(self), eventLoop: worker.eventLoop) 57 | } 58 | } 59 | 60 | extension Collection where Element == Future { 61 | /// Flattens an array of void futures into a single one. 62 | public func flatten(on worker: Worker) -> Future { 63 | return Future.andAll(Array(self), eventLoop: worker.eventLoop) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Async/Future+Global.swift: -------------------------------------------------------------------------------- 1 | // MARK: Global 2 | 3 | extension Future { 4 | /// Statically available method for mimicking behavior of calling `return future.map` where no starting future is available. 5 | /// 6 | /// return Future.map(on: req) { 7 | /// return try someThrowingThing() 8 | /// } 9 | /// 10 | /// This allows you to convert any non-throwing, future-return method into a closure that accepts throwing and returns a future. 11 | public static func map(on worker: Worker, _ callback: @escaping () throws -> Expectation) -> Future { 12 | let promise = worker.eventLoop.newPromise(Expectation.self) 13 | 14 | do { 15 | try promise.succeed(result: callback()) 16 | } catch { 17 | promise.fail(error: error) 18 | } 19 | 20 | return promise.futureResult 21 | } 22 | 23 | /// Statically available method for mimicking behavior of calling `return future.flatMap` where no starting future is available. 24 | /// 25 | /// return Future.flatMap(on: req) { 26 | /// return try someAsyncThrowingThing() 27 | /// } 28 | /// 29 | /// This allows you to convert any non-throwing, future-return method into a closure that accepts throwing and returns a future. 30 | public static func flatMap(on worker: Worker, _ callback: @escaping () throws -> Future) -> Future { 31 | let promise = worker.eventLoop.newPromise(Expectation.self) 32 | 33 | do { 34 | try callback().cascade(promise: promise) 35 | } catch { 36 | promise.fail(error: error) 37 | } 38 | 39 | return promise.futureResult 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Async/Future+Map.swift: -------------------------------------------------------------------------------- 1 | // MARK: Map 2 | 3 | extension Future { 4 | /// Maps a `Future` to a `Future` of a different type. 5 | /// 6 | /// - note: The result returned within should be non-`Future` type. 7 | /// 8 | /// print(futureString) // Future 9 | /// let futureInt = futureString.map(to: Int.self) { string in 10 | /// print(string) // The actual String 11 | /// return Int(string) ?? 0 12 | /// } 13 | /// print(futureInt) // Future 14 | /// 15 | /// See `flatMap(to:_:)` for mapping `Future` results to other `Future` types. 16 | public func map(to type: T.Type = T.self, _ callback: @escaping (Expectation) throws -> T) -> Future { 17 | return self.thenThrowing(callback) 18 | } 19 | 20 | /// Maps a `Future` to a `Future` of a different type. 21 | /// 22 | /// - note: The result returned within the closure should be another `Future`. 23 | /// 24 | /// print(futureURL) // Future 25 | /// let futureRes = futureURL.flatMap(to: Response.self) { url in 26 | /// print(url) // The actual URL 27 | /// return client.get(url: url) // Returns Future 28 | /// } 29 | /// print(futureRes) // Future 30 | /// 31 | /// See `map(to:_:)` for mapping `Future` results to non-`Future` types. 32 | public func flatMap(to type: T.Type = T.self, _ callback: @escaping (Expectation) throws -> Future) -> Future { 33 | return self.then { input in 34 | do { 35 | return try callback(input) 36 | } catch { 37 | return self.eventLoop.newFailedFuture(error: error) 38 | } 39 | } 40 | } 41 | 42 | /// Calls the supplied closure if the chained Future resolves to an Error. 43 | /// 44 | /// The closure gives you a chance to rectify the error (returning the desired expectation) 45 | /// or to re-throw or throw a different error. 46 | /// 47 | /// The callback expects a non-Future return (if not throwing instead). See `catchFlatMap` for a Future return. 48 | public func catchMap(_ callback: @escaping (Error) throws -> (Expectation)) -> Future { 49 | return self.thenIfErrorThrowing(callback) 50 | } 51 | 52 | 53 | /// Calls the supplied closure if the chained Future resolves to an Error. 54 | /// 55 | /// The closure gives you a chance to rectify the error (returning the desired expectation) 56 | /// or to re-throw or throw a different error. 57 | /// 58 | /// The callback expects a Future return (if not throwing instead). See `catchMap` for a non-Future return. 59 | /// 60 | /// return conn.query("BEGIN TRANSACTION").flatMap { 61 | /// return transaction.run(on: connection).flatMap { 62 | /// return conn.query("END TRANSACTION") 63 | /// }.catchFlatMap { error in 64 | /// return conn.query("ROLLBACK").map { 65 | /// throw error 66 | /// } 67 | /// } 68 | /// } 69 | /// 70 | public func catchFlatMap(_ callback: @escaping (Error) throws -> (Future)) -> Future { 71 | return self.thenIfError { inputError in 72 | do { 73 | return try callback(inputError) 74 | } catch { 75 | return self.eventLoop.newFailedFuture(error: error) 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/Async/Future+Transform.swift: -------------------------------------------------------------------------------- 1 | // MARK: Transform 2 | 3 | extension Future { 4 | /// Maps the current future to contain the new type. Errors are carried over, successful (expected) results are transformed into the given instance. 5 | /// 6 | /// user.save(on: req).transform(to: HTTPStatus.created) 7 | /// 8 | public func transform(to instance: @escaping @autoclosure () -> T) -> Future { 9 | return self.map(to: T.self) { _ in 10 | instance() 11 | } 12 | } 13 | 14 | /// Maps the current future to contain the new type. Errors are carried over, successful (expected) results are transformed into the given instance. 15 | /// 16 | /// let user = User.find(id, on: request) 17 | /// posts.save(on: request).transform(to: user) 18 | /// 19 | public func transform(to future: Future) -> Future { 20 | return self.flatMap(to: T.self) { _ in 21 | future 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Async/Future+Variadic.swift: -------------------------------------------------------------------------------- 1 | // MARK: Variadic 2 | 3 | /// Calls the supplied callback when both futures have completed. 4 | /// 5 | /// return map(to: ..., futureA, futureB) { a, b in 6 | /// // ... 7 | /// } 8 | /// 9 | public func map( 10 | to result: Result.Type = Result.self, 11 | _ futureA: Future, 12 | _ futureB: Future, 13 | _ callback: @escaping (A, B) throws -> (Result) 14 | ) -> Future { 15 | return futureA.flatMap(to: Result.self) { a in 16 | return futureB.map(to: Result.self) { b in 17 | return try callback(a, b) 18 | } 19 | } 20 | } 21 | 22 | /// Calls the supplied callback when both futures have completed. 23 | /// 24 | /// return flatMap(to: ..., futureA, futureB) { a, b in 25 | /// // ... 26 | /// } 27 | /// 28 | public func flatMap( 29 | to result: Result.Type = Result.self, 30 | _ futureA: Future, 31 | _ futureB: Future, 32 | _ callback: @escaping (A, B) throws -> Future 33 | ) -> Future { 34 | return futureA.flatMap(to: Result.self) { a in 35 | return futureB.flatMap(to: Result.self) { b in 36 | return try callback(a, b) 37 | } 38 | } 39 | } 40 | 41 | /// Calls the supplied callback when all three futures have completed. 42 | /// 43 | /// return map(to: ..., futureA, futureB, futureC) { a, b, c in 44 | /// // ... 45 | /// } 46 | /// 47 | public func map( 48 | to result: Result.Type = Result.self, 49 | _ futureA: Future, 50 | _ futureB: Future, 51 | _ futureC: Future, 52 | _ callback: @escaping (A, B, C) throws -> Result 53 | ) -> Future { 54 | return futureA.flatMap(to: Result.self) { a in 55 | return futureB.flatMap(to: Result.self) { b in 56 | return futureC.map(to: Result.self) { c in 57 | return try callback(a, b, c) 58 | } 59 | } 60 | } 61 | } 62 | 63 | /// Calls the supplied callback when all three futures have completed. 64 | /// 65 | /// return flatMap(to: ..., futureA, futureB, futureC) { a, b, c in 66 | /// // ... 67 | /// } 68 | /// 69 | public func flatMap( 70 | to result: Result.Type = Result.self, 71 | _ futureA: Future, 72 | _ futureB: Future, 73 | _ futureC: Future, 74 | _ callback: @escaping (A, B, C) throws -> Future 75 | ) -> Future { 76 | return futureA.flatMap(to: Result.self) { a in 77 | return futureB.flatMap(to: Result.self) { b in 78 | return futureC.flatMap(to: Result.self) { c in 79 | return try callback(a, b, c) 80 | } 81 | } 82 | } 83 | } 84 | 85 | /// Calls the supplied callback when all four futures have completed. 86 | /// 87 | /// return map(to: ..., futureA, futureB, futureC, futureD) { a, b, c, d in 88 | /// // ... 89 | /// } 90 | /// 91 | public func map( 92 | to result: Result.Type = Result.self, 93 | _ futureA: Future, 94 | _ futureB: Future, 95 | _ futureC: Future, 96 | _ futureD: Future, 97 | _ callback: @escaping (A, B, C, D) throws -> Result 98 | ) -> Future { 99 | return futureA.flatMap(to: Result.self) { a in 100 | return futureB.flatMap(to: Result.self) { b in 101 | return futureC.flatMap(to: Result.self) { c in 102 | return futureD.map(to: Result.self) { d in 103 | return try callback(a, b, c, d) 104 | } 105 | } 106 | } 107 | } 108 | } 109 | 110 | /// Calls the supplied callback when all four futures have completed. 111 | /// 112 | /// return flatMap(to: ..., futureA, futureB, futureC, futureD) { a, b, c, d in 113 | /// // ... 114 | /// } 115 | /// 116 | public func flatMap( 117 | to result: Result.Type = Result.self, 118 | _ futureA: Future, 119 | _ futureB: Future, 120 | _ futureC: Future, 121 | _ futureD: Future, 122 | _ callback: @escaping (A, B, C, D) throws -> (Future) 123 | ) -> Future { 124 | return futureA.flatMap(to: Result.self) { a in 125 | return futureB.flatMap(to: Result.self) { b in 126 | return futureC.flatMap(to: Result.self) { c in 127 | return futureD.flatMap(to: Result.self) { d in 128 | return try callback(a, b, c, d) 129 | } 130 | } 131 | } 132 | } 133 | } 134 | 135 | /// Calls the supplied callback when all five futures have completed. 136 | /// 137 | /// return map(to: ..., futureA, futureB, futureC, futureD, futureE) { a, b, c, d, e in 138 | /// // ... 139 | /// } 140 | /// 141 | public func map( 142 | to result: Result.Type = Result.self, 143 | _ futureA: Future, 144 | _ futureB: Future, 145 | _ futureC: Future, 146 | _ futureD: Future, 147 | _ futureE: Future, 148 | _ callback: @escaping (A, B, C, D, E) throws -> Result 149 | ) -> Future { 150 | return futureA.flatMap(to: Result.self) { a in 151 | return futureB.flatMap(to: Result.self) { b in 152 | return futureC.flatMap(to: Result.self) { c in 153 | return futureD.flatMap(to: Result.self) { d in 154 | return futureE.map(to: Result.self) { e in 155 | return try callback(a, b, c, d, e) 156 | } 157 | } 158 | } 159 | } 160 | } 161 | } 162 | 163 | /// Calls the supplied callback when all five futures have completed. 164 | /// 165 | /// return flatMap(to: ..., futureA, futureB, futureC, futureD, futureE) { a, b, c, d, e in 166 | /// // ... 167 | /// } 168 | /// 169 | public func flatMap( 170 | to result: Result.Type = Result.self, 171 | _ futureA: Future, 172 | _ futureB: Future, 173 | _ futureC: Future, 174 | _ futureD: Future, 175 | _ futureE: Future, 176 | _ callback: @escaping (A, B, C, D, E) throws -> (Future) 177 | ) -> Future { 178 | return futureA.flatMap(to: Result.self) { a in 179 | return futureB.flatMap(to: Result.self) { b in 180 | return futureC.flatMap(to: Result.self) { c in 181 | return futureD.flatMap(to: Result.self) { d in 182 | return futureE.flatMap(to: Result.self) { e in 183 | return try callback(a, b, c, d, e) 184 | } 185 | } 186 | } 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /Sources/Async/Future+Void.swift: -------------------------------------------------------------------------------- 1 | extension Promise where T == Void { 2 | /// Calls `succeed(result: ())`. 3 | public func succeed() { 4 | self.succeed(result: ()) 5 | } 6 | } 7 | 8 | extension Future where T == Void { 9 | /// A pre-completed `Future`. 10 | public static func done(on worker: Worker) -> Future { 11 | let promise = worker.eventLoop.newPromise(Void.self) 12 | promise.succeed() 13 | return promise.futureResult 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Async/FutureType.swift: -------------------------------------------------------------------------------- 1 | /// Callback for accepting a result. 2 | public typealias FutureResultCallback = (FutureResult) -> () 3 | 4 | /// A future result type. 5 | /// Concretely implemented by `Future` 6 | public protocol FutureType { 7 | /// This future's expectation. 8 | associatedtype Expectation 9 | 10 | /// This future's result type. 11 | typealias Result = FutureResult 12 | 13 | /// The event loop this future is fulfilled on. 14 | var eventLoop: EventLoop { get } 15 | 16 | /// Adds a new awaiter to this `Future` that will be called when the result is ready. 17 | func addAwaiter(callback: @escaping FutureResultCallback) 18 | } 19 | 20 | extension EventLoopFuture: FutureType { 21 | /// See `FutureType`. 22 | public typealias Expectation = T 23 | 24 | /// See `FutureType`. 25 | public func addAwaiter(callback: @escaping (FutureResult) -> ()) { 26 | self.do { result in 27 | callback(.success(result)) 28 | }.catch { error in 29 | callback(.error(error)) 30 | } 31 | } 32 | } 33 | 34 | // Indirect so futures can be nested. 35 | public indirect enum FutureResult { 36 | case error(Error) 37 | case success(T) 38 | 39 | /// Returns the result error or `nil` if the result contains expectation. 40 | public var error: Error? { 41 | switch self { 42 | case .error(let error): 43 | return error 44 | default: 45 | return nil 46 | } 47 | } 48 | 49 | /// Returns the result expectation or `nil` if the result contains an error. 50 | public var result: T? { 51 | switch self { 52 | case .success(let expectation): 53 | return expectation 54 | default: 55 | return nil 56 | } 57 | } 58 | 59 | /// Throws an error if this contains an error, returns the Expectation otherwise 60 | public func unwrap() throws -> T { 61 | switch self { 62 | case .success(let data): 63 | return data 64 | case .error(let error): 65 | throw error 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/Async/QueueHandler.swift: -------------------------------------------------------------------------------- 1 | import NIO 2 | 3 | /// Controls a Swift NIO pipeline of `[In]` -> `[Out]`. 4 | /// 5 | /// One or more `Out` can be enqueued to the handler at a time. When enqueuing output, 6 | /// you must specify an input callback `(In) throws -> (Bool)`. This callback will be used to 7 | /// provide "responses" to your output. When the callback returns `true` (or an error is thrown), 8 | /// the future returned when enqueuing data will be completed. 9 | /// 10 | /// This handler is useful for implementing clients. Requests can be enqueued to the handler and one 11 | /// or more responses can be received. This handler works great with client protocols that support pipelining. 12 | /// 13 | public final class QueueHandler: ChannelInboundHandler { 14 | /// See `ChannelInboundHandler.InboundIn` 15 | public typealias InboundIn = In 16 | 17 | /// See `ChannelInboundHandler.OutboundOut` 18 | public typealias OutboundOut = Out 19 | 20 | /// Queue of input handlers and promises. Oldest (current) handler and promise are at the end of the array. 21 | private var inputQueue: [InputContext] 22 | 23 | /// Queue of output. Oldest objects are at the end of the array (output is dequeued with `popLast()`) 24 | private var outputQueue: [[OutboundOut]] 25 | 26 | /// This handler's event loop. 27 | private let eventLoop: EventLoop 28 | 29 | /// A write-ready context waiting. 30 | private weak var waitingCtx: ChannelHandlerContext? 31 | 32 | /// Handles errors that happen when no input promise is waiting. 33 | private var errorHandler: (Error) -> () 34 | 35 | /// Create a new `QueueHandler` on the supplied worker. 36 | public init(on worker: Worker, onError: @escaping (Error) -> ()) { 37 | VERBOSE("QueueHandler.init(on: \(worker))") 38 | self.inputQueue = [] 39 | self.outputQueue = [] 40 | self.eventLoop = worker.eventLoop 41 | self.errorHandler = onError 42 | } 43 | 44 | /// Enqueue new output to the handler. 45 | /// 46 | /// - parameters: 47 | /// - output: An array of output (can be `0`) that you wish to send. 48 | /// - onInput: A callback that will accept new input (usually responses to the output you enqueued) 49 | /// The callback will continue to be called until you return `true` or an error is thrown. 50 | /// - returns: A future signal. Will be completed when `onInput` returns `true` or throws an error. 51 | public func enqueue(_ output: [OutboundOut], onInput: @escaping (InboundIn) throws -> Bool) -> Future { 52 | guard eventLoop.inEventLoop else { 53 | return eventLoop.submit { 54 | // do nothing 55 | }.flatMap { 56 | // perform this on the event loop 57 | return self.enqueue(output, onInput: onInput) 58 | } 59 | } 60 | 61 | VERBOSE("QueueHandler.enqueue(\(output.count))") 62 | outputQueue.insert(output, at: 0) 63 | let promise = eventLoop.newPromise(Void.self) 64 | let context = InputContext(promise: promise, onInput: onInput) 65 | inputQueue.insert(context, at: 0) 66 | if let ctx = waitingCtx { 67 | ctx.eventLoop.execute { 68 | self.writeOutputIfEnqueued(ctx: ctx) 69 | } 70 | } 71 | return promise.futureResult 72 | } 73 | 74 | /// Triggers a context write if any output is enqueued. 75 | private func writeOutputIfEnqueued(ctx: ChannelHandlerContext) { 76 | VERBOSE("QueueHandler.sendOutput(ctx: \(ctx)) [outputQueue.count=\(outputQueue.count)]") 77 | while let next = outputQueue.popLast() { 78 | for output in next { 79 | ctx.write(wrapOutboundOut(output), promise: nil) 80 | } 81 | ctx.flush() 82 | } 83 | waitingCtx = ctx 84 | } 85 | 86 | /// MARK: ChannelInboundHandler conformance 87 | 88 | /// See `ChannelInboundHandler.channelRead(ctx:data:)` 89 | public func channelRead(ctx: ChannelHandlerContext, data: NIOAny) { 90 | VERBOSE("QueueHandler.channelRead(ctx: \(ctx), data: \(data))") 91 | let input = unwrapInboundIn(data) 92 | guard let current = inputQueue.last else { 93 | debugOnly { 94 | WARNING("[QueueHandler] Read triggered when input queue was empty, ignoring: \(input).") 95 | } 96 | return 97 | } 98 | do { 99 | if try current.onInput(input) { 100 | let popped = inputQueue.popLast() 101 | assert(popped != nil) 102 | 103 | current.promise.succeed() 104 | } 105 | } catch { 106 | let popped = inputQueue.popLast() 107 | assert(popped != nil) 108 | 109 | current.promise.fail(error: error) 110 | } 111 | } 112 | 113 | /// See `ChannelInboundHandler.channelActive(ctx:)` 114 | public func channelActive(ctx: ChannelHandlerContext) { 115 | VERBOSE("QueueHandler.channelActive(ctx: \(ctx))") 116 | writeOutputIfEnqueued(ctx: ctx) 117 | } 118 | 119 | /// See `ChannelInboundHandler.errorCaught(error:)` 120 | public func errorCaught(ctx: ChannelHandlerContext, error: Error) { 121 | VERBOSE("QueueHandler.errorCaught(ctx: \(ctx), error: \(error))") 122 | if let current = inputQueue.last { 123 | current.promise.fail(error: error) 124 | } else { 125 | self.errorHandler(error) 126 | } 127 | } 128 | } 129 | 130 | /// Contains the `onInput` handler and promise created by enqueuing one or more output to a `QueueHandler`. 131 | fileprivate struct InputContext { 132 | /// Should be completed when `onInput` returns `true` or an error is thrown. 133 | var promise: Promise 134 | 135 | /// All incoming input will be passed to this callback when it is the current context. 136 | var onInput: (In) throws -> Bool 137 | } 138 | -------------------------------------------------------------------------------- /Sources/Async/Worker.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | 3 | /// `Worker`s are types that have a reference to an `EventLoop`. 4 | /// 5 | /// You will commonly see `Worker`s required after an `on:` label. 6 | /// 7 | /// return Future.map(on: req) { ... } 8 | /// 9 | /// The most common containers you will interact with in Vapor are: 10 | /// - `Application` 11 | /// - `Request` 12 | /// - `Response` 13 | /// 14 | /// You can also use a SwiftNIO `EventGroup` directly as your `Worker`. 15 | /// 16 | /// let worker = MultiThreadedEventLoopGroup(numThreads: 2) 17 | /// ... 18 | /// let connection = database.makeConnection(on: worker) 19 | /// 20 | public typealias Worker = EventLoopGroup 21 | 22 | /// `Worker`s are types that have a reference to an `EventLoop`. 23 | /// 24 | /// You will commonly see `Worker`s required after an `on:` label. 25 | /// 26 | /// return Future.map(on: req) { ... } 27 | /// 28 | /// The most common containers you will interact with in Vapor are: 29 | /// - `Application` 30 | /// - `Request` 31 | /// - `Response` 32 | /// 33 | /// You can also use a SwiftNIO `EventGroup` directly as your `Worker`. 34 | /// 35 | /// let worker = MultiThreadedEventLoopGroup(numThreads: 2) 36 | /// ... 37 | /// let connection = database.makeConnection(on: worker) 38 | /// 39 | extension Worker { 40 | /// See `BasicWorker`. 41 | public var eventLoop: EventLoop { 42 | return next() 43 | } 44 | 45 | /// Creates a new, succeeded `Future` from the worker's event loop with a `Void` value. 46 | /// 47 | /// let a: Future = req.future() 48 | /// 49 | /// - returns: The succeeded future. 50 | public func future() -> Future { 51 | return self.eventLoop.newSucceededFuture(result: ()) 52 | } 53 | 54 | /// Creates a new, succeeded `Future` from the worker's event loop. 55 | /// 56 | /// let a: Future = req.future("hello") 57 | /// 58 | /// - parameters: 59 | /// - value: The value that the future will wrap. 60 | /// - returns: The succeeded future. 61 | public func future(_ value: T) -> Future { 62 | return self.eventLoop.newSucceededFuture(result: value) 63 | } 64 | 65 | /// Creates a new, failed `Future` from the worker's event loop. 66 | /// 67 | /// let b: Future = req.future(error: Abort(...)) 68 | /// 69 | /// - parameters: 70 | /// - error: The error that the future will wrap. 71 | /// - returns: The failed future. 72 | public func future(error: Error) -> Future { 73 | return self.eventLoop.newFailedFuture(error: error) 74 | } 75 | } 76 | 77 | /// A basic `Worker` type that has a single `EventLoop`. 78 | public protocol BasicWorker: Worker { 79 | /// This worker's event loop. All async work done on this worker _must_ occur on its `EventLoop`. 80 | var eventLoop: EventLoop { get } 81 | } 82 | 83 | extension BasicWorker { 84 | /// See `EventLoopGroup`. 85 | public func next() -> EventLoop { 86 | return self.eventLoop 87 | } 88 | 89 | /// See `EventLoopGroup`. 90 | public func shutdownGracefully(queue: DispatchQueue, _ callback: @escaping (Error?) -> Void) { 91 | eventLoop.shutdownGracefully(queue: queue, callback) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Sources/Bits/BitsError.swift: -------------------------------------------------------------------------------- 1 | import Debugging 2 | 3 | /// Errors that can be thrown while working with Bits. 4 | public struct BitsError: Debuggable { 5 | /// See `Debuggable` 6 | public var identifier: String 7 | 8 | /// See `Debuggable` 9 | public var reason: String 10 | 11 | /// See `Debuggable` 12 | public var possibleCauses: [String] 13 | 14 | /// See `Debuggable` 15 | public var suggestedFixes: [String] 16 | 17 | /// See `Debuggable` 18 | public var stackTrace: [String]? 19 | 20 | /// Creates a new `BitsError`. 21 | /// 22 | /// See `Debuggable` 23 | init(identifier: String, reason: String, suggestedFixes: [String] = [], possibleCauses: [String] = []) { 24 | self.identifier = identifier 25 | self.reason = reason 26 | self.suggestedFixes = suggestedFixes 27 | self.possibleCauses = possibleCauses 28 | self.stackTrace = BitsError.makeStackTrace() 29 | } 30 | } 31 | 32 | /// Logs an unhandleable runtime error. 33 | internal func ERROR(_ string: @autoclosure () -> String) { 34 | print("[ERROR] [Bits] \(string())") 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Bits/Byte+Alphabet.swift: -------------------------------------------------------------------------------- 1 | /// Adds alphabet conveniences to `Byte`. 2 | extension Byte { 3 | /// Returns true if the given byte is between lowercase or uppercase A-Z in UTF8. 4 | public var isLetter: Bool { 5 | return (self >= .a && self <= .z) || (self >= .A && self <= .Z) 6 | } 7 | 8 | /// Returns whether or not a given byte represents a UTF8 digit 0 through 9, or an arabic letter 9 | public var isAlphanumeric: Bool { 10 | return isLetter || isDigit 11 | } 12 | 13 | /// Returns whether a given byte can be interpreted as a hex value in UTF8, ie: 0-9, a-f, A-F. 14 | public var isHexDigit: Bool { 15 | return (self >= .zero && self <= .nine) || (self >= .A && self <= .F) || (self >= .a && self <= .f) 16 | } 17 | 18 | /// A 19 | public static let A: Byte = 0x41 20 | 21 | /// B 22 | public static let B: Byte = 0x42 23 | 24 | /// C 25 | public static let C: Byte = 0x43 26 | 27 | /// D 28 | public static let D: Byte = 0x44 29 | 30 | /// E 31 | public static let E: Byte = 0x45 32 | 33 | /// F 34 | public static let F: Byte = 0x46 35 | 36 | /// G 37 | public static let G: Byte = 0x47 38 | 39 | /// H 40 | public static let H: Byte = 0x48 41 | 42 | /// I 43 | public static let I: Byte = 0x49 44 | 45 | /// J 46 | public static let J: Byte = 0x4A 47 | 48 | /// K 49 | public static let K: Byte = 0x4B 50 | 51 | /// L 52 | public static let L: Byte = 0x4C 53 | 54 | /// M 55 | public static let M: Byte = 0x4D 56 | 57 | /// N 58 | public static let N: Byte = 0x4E 59 | 60 | /// O 61 | public static let O: Byte = 0x4F 62 | 63 | /// P 64 | public static let P: Byte = 0x50 65 | 66 | /// Q 67 | public static let Q: Byte = 0x51 68 | 69 | /// R 70 | public static let R: Byte = 0x52 71 | 72 | /// S 73 | public static let S: Byte = 0x53 74 | 75 | /// T 76 | public static let T: Byte = 0x54 77 | 78 | /// U 79 | public static let U: Byte = 0x55 80 | 81 | /// V 82 | public static let V: Byte = 0x56 83 | 84 | /// W 85 | public static let W: Byte = 0x57 86 | 87 | /// X 88 | public static let X: Byte = 0x58 89 | 90 | /// Y 91 | public static let Y: Byte = 0x59 92 | 93 | /// Z 94 | public static let Z: Byte = 0x5A 95 | } 96 | 97 | extension Byte { 98 | /// a 99 | public static let a: Byte = 0x61 100 | 101 | /// b 102 | public static let b: Byte = 0x62 103 | 104 | /// c 105 | public static let c: Byte = 0x63 106 | 107 | /// d 108 | public static let d: Byte = 0x64 109 | 110 | /// e 111 | public static let e: Byte = 0x65 112 | 113 | /// f 114 | public static let f: Byte = 0x66 115 | 116 | /// g 117 | public static let g: Byte = 0x67 118 | 119 | /// h 120 | public static let h: Byte = 0x68 121 | 122 | /// i 123 | public static let i: Byte = 0x69 124 | 125 | /// j 126 | public static let j: Byte = 0x6A 127 | 128 | /// k 129 | public static let k: Byte = 0x6B 130 | 131 | /// l 132 | public static let l: Byte = 0x6C 133 | 134 | /// m 135 | public static let m: Byte = 0x6D 136 | 137 | /// n 138 | public static let n: Byte = 0x6E 139 | 140 | /// o 141 | public static let o: Byte = 0x6F 142 | 143 | /// p 144 | public static let p: Byte = 0x70 145 | 146 | /// q 147 | public static let q: Byte = 0x71 148 | 149 | /// r 150 | public static let r: Byte = 0x72 151 | 152 | /// s 153 | public static let s: Byte = 0x73 154 | 155 | /// t 156 | public static let t: Byte = 0x74 157 | 158 | /// u 159 | public static let u: Byte = 0x75 160 | 161 | /// v 162 | public static let v: Byte = 0x76 163 | 164 | /// w 165 | public static let w: Byte = 0x77 166 | 167 | /// x 168 | public static let x: Byte = 0x78 169 | 170 | /// y 171 | public static let y: Byte = 0x79 172 | 173 | /// z 174 | public static let z: Byte = 0x7A 175 | } 176 | 177 | -------------------------------------------------------------------------------- /Sources/Bits/Byte+Control.swift: -------------------------------------------------------------------------------- 1 | /// Adds control character conveniences to `Byte`. 2 | extension Byte { 3 | /// Returns whether or not the given byte can be considered UTF8 whitespace 4 | public var isWhitespace: Bool { 5 | return self == .space || self == .newLine || self == .carriageReturn || self == .horizontalTab 6 | } 7 | 8 | /// '\t' 9 | public static let horizontalTab: Byte = 0x9 10 | 11 | /// '\n' 12 | public static let newLine: Byte = 0xA 13 | 14 | /// '\r' 15 | public static let carriageReturn: Byte = 0xD 16 | 17 | /// ' ' 18 | public static let space: Byte = 0x20 19 | 20 | /// ! 21 | public static let exclamation: Byte = 0x21 22 | 23 | /// " 24 | public static let quote: Byte = 0x22 25 | 26 | /// # 27 | public static let numberSign: Byte = 0x23 28 | 29 | /// $ 30 | public static let dollar: Byte = 0x24 31 | 32 | /// % 33 | public static let percent: Byte = 0x25 34 | 35 | /// & 36 | public static let ampersand: Byte = 0x26 37 | 38 | /// ' 39 | public static let apostrophe: Byte = 0x27 40 | 41 | /// ( 42 | public static let leftParenthesis: Byte = 0x28 43 | 44 | /// ) 45 | public static let rightParenthesis: Byte = 0x29 46 | 47 | /// * 48 | public static let asterisk: Byte = 0x2A 49 | 50 | /// + 51 | public static let plus: Byte = 0x2B 52 | 53 | /// , 54 | public static let comma: Byte = 0x2C 55 | 56 | /// - 57 | public static let hyphen: Byte = 0x2D 58 | 59 | /// . 60 | public static let period: Byte = 0x2E 61 | 62 | /// / 63 | public static let forwardSlash: Byte = 0x2F 64 | 65 | /// \ 66 | public static let backSlash: Byte = 0x5C 67 | 68 | /// : 69 | public static let colon: Byte = 0x3A 70 | 71 | /// ; 72 | public static let semicolon: Byte = 0x3B 73 | 74 | /// = 75 | public static let equals: Byte = 0x3D 76 | 77 | /// ? 78 | public static let questionMark: Byte = 0x3F 79 | 80 | /// @ 81 | public static let at: Byte = 0x40 82 | 83 | /// [ 84 | public static let leftSquareBracket: Byte = 0x5B 85 | 86 | /// ] 87 | public static let rightSquareBracket: Byte = 0x5D 88 | 89 | /// ^ 90 | public static let caret: Byte = 0x5E 91 | 92 | /// _ 93 | public static let underscore: Byte = 0x5F 94 | 95 | /// ` 96 | public static let backtick: Byte = 0x60 97 | 98 | /// ~ 99 | public static let tilde: Byte = 0x7E 100 | 101 | /// { 102 | public static let leftCurlyBracket: Byte = 0x7B 103 | 104 | /// } 105 | public static let rightCurlyBracket: Byte = 0x7D 106 | 107 | /// < 108 | public static let lessThan: Byte = 0x3C 109 | 110 | /// > 111 | public static let greaterThan: Byte = 0x3E 112 | 113 | /// | 114 | public static let pipe: Byte = 0x7C 115 | } 116 | 117 | extension Byte { 118 | /// Defines the `crlf` used to denote line breaks in HTTP and many other formatters 119 | public static let crlf: Bytes = [ 120 | .carriageReturn, 121 | .newLine 122 | ] 123 | } 124 | -------------------------------------------------------------------------------- /Sources/Bits/Byte+Digit.swift: -------------------------------------------------------------------------------- 1 | /// Adds digit conveniences to `Byte`. 2 | extension Byte { 3 | /// Returns whether or not a given byte represents a UTF8 digit 0 through 9 4 | public var isDigit: Bool { 5 | return (.zero ... .nine).contains(self) 6 | } 7 | 8 | /// 0 in utf8 9 | public static let zero: Byte = 0x30 10 | 11 | /// 1 in utf8 12 | public static let one: Byte = 0x31 13 | 14 | /// 2 in utf8 15 | public static let two: Byte = 0x32 16 | 17 | /// 3 in utf8 18 | public static let three: Byte = 0x33 19 | 20 | /// 4 in utf8 21 | public static let four: Byte = 0x34 22 | 23 | /// 5 in utf8 24 | public static let five: Byte = 0x35 25 | 26 | /// 6 in utf8 27 | public static let six: Byte = 0x36 28 | 29 | /// 7 in utf8 30 | public static let seven: Byte = 0x37 31 | 32 | /// 8 in utf8 33 | public static let eight: Byte = 0x38 34 | 35 | /// 9 in utf8 36 | public static let nine: Byte = 0x39 37 | } 38 | 39 | extension Int { 40 | public func bytes(reserving: Int = 0) -> [UInt8] { 41 | var data = [UInt8]() 42 | data.reserveCapacity(reserving) 43 | 44 | var i: Int 45 | 46 | if self < 0 { 47 | data.append(.hyphen) 48 | // make positive 49 | i = -self 50 | } else { 51 | i = self 52 | } 53 | 54 | var offset = 0 55 | var testI = i 56 | 57 | repeat { 58 | offset = offset &+ 1 59 | testI = testI / 10 60 | data.append(0) 61 | } while testI > 0 62 | 63 | while offset > 0 { 64 | // subtract first to be before the `data.count` 65 | offset = offset &- 1 66 | data[offset] = 0x30 &+ numericCast(i % 10) 67 | i = i / 10 68 | } 69 | 70 | return data 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/Bits/ByteBuffer+binaryFloatingPointOperations.swift: -------------------------------------------------------------------------------- 1 | extension ByteBuffer { 2 | /// Write `integer` into this `ByteBuffer`, moving the writer index forward appropriately. 3 | /// 4 | /// - parameters: 5 | /// - integer: The integer to serialize. 6 | /// - endianness: The endianness to use, defaults to big endian. 7 | /// - returns: The number of bytes written. 8 | @discardableResult 9 | public mutating func write(floatingPoint: T) -> Int where T: BinaryFloatingPoint { 10 | let bytesWritten = self.set(floatingPoint: floatingPoint, at: self.writerIndex) 11 | self.moveWriterIndex(forwardBy: bytesWritten) 12 | return Int(bytesWritten) 13 | } 14 | 15 | /// Write `integer` into this `ByteBuffer` starting at `index`. This does not alter the writer index. 16 | /// 17 | /// - parameters: 18 | /// - integer: The integer to serialize. 19 | /// - index: The index of the first byte to write. 20 | /// - endianness: The endianness to use, defaults to big endian. 21 | /// - returns: The number of bytes written. 22 | @discardableResult 23 | public mutating func set(floatingPoint: T, at index: Int) -> Int where T: BinaryFloatingPoint { 24 | var value = floatingPoint 25 | return Swift.withUnsafeBytes(of: &value) { ptr in 26 | self.set(bytes: ptr, at: index) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Bits/ByteBuffer+peek.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension ByteBuffer { 4 | /// Peeks into the `ByteBuffer` at the current reader index without changing any state. 5 | /// 6 | /// buffer.peekInteger(as: Int32.self) // Optional(5) 7 | /// 8 | /// - parameters: 9 | /// - skipping: The amount of bytes to skip, defaults to `0`. 10 | /// - type: Optional parameter for specifying the generic `FixedWidthInteger` type. 11 | public func peekInteger(skipping: Int = 0, as type: I.Type = I.self) -> I? where I: FixedWidthInteger { 12 | guard readableBytes >= MemoryLayout.size + skipping else { 13 | return nil 14 | } 15 | return getInteger(at: readerIndex + skipping) 16 | } 17 | 18 | /// Peeks into the `ByteBuffer` at the current reader index without changing any state. 19 | /// 20 | /// buffer.peekString(count: 5) // Optional("hello") 21 | /// 22 | /// - parameters: 23 | /// - length: Number of bytes to peek. 24 | /// - skipping: The amount of bytes to skip, defaults to `0`. 25 | /// - encoding: `String.Encoding` to use when converting the bytes to a `String` 26 | public func peekString(length: Int, skipping: Int = 0, encoding: String.Encoding = .utf8) -> String? { 27 | guard readableBytes >= length + skipping else { return nil } 28 | guard let bytes = getBytes(at: readerIndex + skipping, length: length) else { return nil } 29 | return String(bytes: bytes, encoding: encoding) 30 | } 31 | /// Peeks into the `ByteBuffer` at the current reader index without changing any state. 32 | /// 33 | /// buffer.peekFloat(as: Double.self) // Optional(3.14) 34 | /// 35 | /// - parameters: 36 | /// - skipping: The amount of bytes to skip, defaults to `0`. 37 | /// - type: Optional parameter for specifying the generic `BinaryFloatingPoint` type. 38 | public func peekFloatingPoint(skipping: Int = 0, as: T.Type = T.self) -> T? 39 | where T: BinaryFloatingPoint 40 | { 41 | guard readableBytes >= MemoryLayout.size + skipping else { return nil } 42 | return self.withVeryUnsafeBytes { ptr in 43 | var value: T = 0 44 | withUnsafeMutableBytes(of: &value) { valuePtr in 45 | valuePtr.copyMemory( 46 | from: UnsafeRawBufferPointer( 47 | start: ptr.baseAddress!.advanced(by: skipping + readerIndex), 48 | count: MemoryLayout.size 49 | ) 50 | ) 51 | } 52 | return value 53 | } 54 | } 55 | 56 | /// Peeks into the `ByteBuffer` at the current reader index without changing any state. 57 | /// 58 | /// buffer.peekData(count: 5) // Optional(5 bytes) 59 | /// 60 | /// - parameters: 61 | /// - length: Number of bytes to peek. 62 | /// - skipping: The amount of bytes to skip, defaults to `0`. 63 | public func peekData(length: Int, skipping: Int = 0) -> Data? { 64 | guard readableBytes >= length + skipping else { return nil } 65 | guard let bytes = getBytes(at: readerIndex + skipping, length: length) 66 | else { return nil } 67 | return Data(bytes: bytes) 68 | } 69 | 70 | 71 | /// Peeks into the `ByteBuffer` at the current reader index without changing any state. 72 | /// 73 | /// buffer.peekData(count: 5) // Optional(5 bytes) 74 | /// 75 | /// - parameters: 76 | /// - count: Number of bytes to peek. 77 | /// - skipping: The amount of bytes to skip, defaults to `0`. 78 | public func peekBytes(count: Int, skipping: Int = 0) -> [UInt8]? { 79 | guard readableBytes >= count + skipping else { return nil } 80 | guard let bytes = getBytes(at: readerIndex + skipping, length: count) else { return nil } 81 | return bytes 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Sources/Bits/ByteBuffer+require.swift: -------------------------------------------------------------------------------- 1 | import Debugging 2 | import NIO 3 | import Foundation 4 | 5 | extension ByteBuffer { 6 | /// Reads a `FixedWidthInteger` from this `ByteBuffer` or throws an error. 7 | /// 8 | /// See `readInteger(endianniess:as:)` 9 | public mutating func requireReadInteger(endianness: Endianness = .big, as: I.Type = I.self) throws -> I where I: FixedWidthInteger { 10 | guard let i: I = readInteger(endianness: endianness, as: I.self) else { 11 | throw BitsError(identifier: "requireReadInteger", reason: "Not enough data available in the ByteBuffer.") 12 | } 13 | 14 | return i 15 | } 16 | 17 | /// Reads a `String` from this `ByteBuffer` or throws an error. 18 | /// 19 | /// See `readString(endianniess:as:)` 20 | public mutating func requireReadString(length: Int) throws -> String { 21 | guard let string = readString(length: length) else { 22 | throw BitsError(identifier: "requireReadString", reason: "Not enough data available in the ByteBuffer.") 23 | } 24 | return string 25 | } 26 | 27 | /// Reads a `Data` from this `ByteBuffer` or throws an error. 28 | /// 29 | /// See `readData(endianniess:as:)` 30 | public mutating func requireReadData(length: Int) throws -> Data { 31 | guard let bytes = readBytes(length: length) else { 32 | throw BitsError(identifier: "requireReadData", reason: "Not enough data available in the ByteBuffer.") 33 | } 34 | return Data(bytes: bytes) 35 | } 36 | 37 | /// Reads a `BinaryFloatingPoint` from this `ByteBuffer` or throws an error. 38 | public mutating func requireReadFloatingPoint(as: T.Type = T.self) throws -> T where T: BinaryFloatingPoint { 39 | guard let bytes = self.readBytes(length: MemoryLayout.size) else { 40 | throw BitsError(identifier: "requireReadFloat", reason: "Not enough data available in the ByteBuffer.") 41 | } 42 | var value: T = 0 43 | withUnsafeMutableBytes(of: &value) { valuePtr in 44 | valuePtr.copyBytes(from: bytes) 45 | } 46 | return value 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/Bits/ByteBuffer+string.swift: -------------------------------------------------------------------------------- 1 | extension ByteBuffer { 2 | /// Reads a null-terminated `String` from this `ByteBuffer`. 3 | /// 4 | /// - parameters: 5 | /// - encoding: `String.Encoding` to use when converting the bytes to a `String` 6 | public mutating func readNullTerminatedString(encoding: String.Encoding = .utf8) -> String? { 7 | var bytes: [UInt8] = [] 8 | parse: while true { 9 | guard let byte: Byte = readInteger() else { 10 | return nil 11 | } 12 | switch byte { 13 | case 0: break parse // found null terminator 14 | default: bytes.append(byte) 15 | } 16 | } 17 | return String(bytes: bytes, encoding: encoding) 18 | } 19 | 20 | /// Reads a null-terminated `String` from this `ByteBuffer` or throws an error. 21 | /// 22 | /// See `readNullTerminatedString(encoding:)` 23 | public mutating func requireReadNullTerminatedString(encoding: String.Encoding = .utf8) throws -> String { 24 | guard let string = readNullTerminatedString(encoding: encoding) else { 25 | throw BitsError(identifier: "nullTerminatedString", reason: "This was not available in the buffer") 26 | } 27 | return string 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Bits/Bytes.swift: -------------------------------------------------------------------------------- 1 | import struct NIO.ByteBuffer 2 | 3 | /// A `Byte` is an 8-bit unsigned integer. 4 | public typealias Byte = UInt8 5 | 6 | /// `Bytes` are a Swift array of 8-bit unsigned integers. 7 | public typealias Bytes = [Byte] 8 | 9 | /// `BytesBufferPointer` are a Swift `UnsafeBufferPointer` to 8-bit unsigned integers. 10 | public typealias BytesBufferPointer = UnsafeBufferPointer 11 | 12 | /// `MutableBytesBufferPointer` are a Swift `UnsafeMutableBufferPointer` to 8-bit unsigned integers. 13 | public typealias MutableBytesBufferPointer = UnsafeMutableBufferPointer 14 | 15 | /// `BytesPointer` are a Swift `UnsafePointer` to 8-bit unsigned integers. 16 | public typealias BytesPointer = UnsafePointer 17 | 18 | /// `MutableBytesPointer` are a Swift `UnsafeMutablePointer` to 8-bit unsigned integers. 19 | public typealias MutableBytesPointer = UnsafeMutablePointer 20 | 21 | /// `ByteBuffer` is a typealias to NIO's `ByteBuffer`. 22 | public typealias ByteBuffer = NIO.ByteBuffer 23 | 24 | /// Implements pattern matching for `Byte` to `Byte?`. 25 | public func ~=(pattern: Byte, value: Byte?) -> Bool { 26 | return pattern == value 27 | } 28 | 29 | extension Byte { 30 | /// Returns the `String` representation of this `Byte` (unicode scalar). 31 | public var string: String { 32 | let unicode = Unicode.Scalar(self) 33 | let char = Character(unicode) 34 | return String(char) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Bits/Data+Bytes.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Data { 4 | /// Returns a `0x` prefixed, space-separated, hex-encoded string for this `Data`. 5 | public var hexDebug: String { 6 | return "0x" + map { String(format: "%02X", $0) }.joined(separator: " ") 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Sources/Bits/Data+Strings.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Offset between `a` and `A` in ASCII encoding. 4 | public let asciiCasingOffset = Byte.a - Byte.A 5 | 6 | extension Data { 7 | /// Efficiently converts a `Data`'s uppercased ASCII characters to lowercased. 8 | public func lowercasedASCIIString() -> Data { 9 | var lowercased = Data(repeating: 0, count: self.count) 10 | var writeIndex = 0 11 | 12 | for i in self.startIndex..= .A && self[i] <= .Z { 14 | lowercased[writeIndex] = self[i] &+ asciiCasingOffset 15 | } else { 16 | lowercased[writeIndex] = self[i] 17 | } 18 | 19 | writeIndex = writeIndex &+ 1 20 | } 21 | 22 | return lowercased 23 | } 24 | } 25 | 26 | extension Array where Element == UInt8 { 27 | /// Calculates `djb2` hash for this array of `UInt8`. 28 | public var djb2: Int { 29 | var hash = 5381 30 | 31 | for element in self { 32 | hash = ((hash << 5) &+ hash) &+ numericCast(element) 33 | } 34 | 35 | return hash 36 | } 37 | 38 | /// Efficiently converts an array of bytes uppercased ASCII characters to lowercased. 39 | public func lowercasedASCIIString() -> [UInt8] { 40 | var lowercased = [UInt8](repeating: 0, count: self.count) 41 | var writeIndex = 0 42 | 43 | for i in self.startIndex..= .A && self[i] <= .Z { 45 | lowercased[writeIndex] = self[i] &+ asciiCasingOffset 46 | } else { 47 | lowercased[writeIndex] = self[i] 48 | } 49 | 50 | writeIndex = writeIndex &+ 1 51 | } 52 | 53 | return lowercased 54 | } 55 | 56 | /// Checks if the current bytes are equal to the contents of the provided `BytesBufferPointer`. 57 | public func caseInsensitiveEquals(to data: BytesBufferPointer) -> Bool { 58 | guard self.count == data.count else { return false } 59 | 60 | for i in 0.. Bool { 71 | guard self.count == data.count else { return false } 72 | 73 | for i in 0..(_ closure: (BytesBufferPointer) throws -> T) rethrows -> T { 84 | return try self.withUnsafeBytes { (pointer: BytesPointer) in 85 | let buffer = BytesBufferPointer(start: pointer,count: self.count) 86 | 87 | return try closure(buffer) 88 | } 89 | } 90 | 91 | /// Reads from a `Data` buffer using a `MutableBytesBufferPointer` rather than a normal pointer 92 | public mutating func withMutableByteBuffer(_ closure: (MutableBytesBufferPointer) throws -> T) rethrows -> T { 93 | let count = self.count 94 | return try self.withUnsafeMutableBytes { (pointer: MutableBytesPointer) in 95 | let buffer = MutableBytesBufferPointer(start: pointer,count: count) 96 | return try closure(buffer) 97 | } 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /Sources/Bits/Deprecated.swift: -------------------------------------------------------------------------------- 1 | extension Byte { 2 | /// ~ 3 | @available (*, deprecated, renamed: "tilde") 4 | public static let tilda: Byte = 0x7E 5 | } 6 | -------------------------------------------------------------------------------- /Sources/COperatingSystem/libc.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | @_exported import Glibc 3 | #else 4 | @_exported import Darwin.C 5 | #endif 6 | -------------------------------------------------------------------------------- /Sources/Core/BasicKey.swift: -------------------------------------------------------------------------------- 1 | /// A basic `CodingKey` implementation. 2 | public struct BasicKey: CodingKey { 3 | /// See `CodingKey`. 4 | public var stringValue: String 5 | 6 | /// See `CodingKey`. 7 | public var intValue: Int? 8 | 9 | /// Creates a new `BasicKey` from a `String.` 10 | public init(_ string: String) { 11 | self.stringValue = string 12 | } 13 | 14 | /// Creates a new `BasicKey` from a `Int.` 15 | /// 16 | /// These are usually used to specify array indexes. 17 | public init(_ int: Int) { 18 | self.intValue = int 19 | self.stringValue = int.description 20 | } 21 | 22 | /// See `CodingKey`. 23 | public init?(stringValue: String) { 24 | self.stringValue = stringValue 25 | } 26 | 27 | /// See `CodingKey`. 28 | public init?(intValue: Int) { 29 | self.intValue = intValue 30 | self.stringValue = intValue.description 31 | } 32 | } 33 | 34 | /// Capable of being represented by a `BasicKey`. 35 | public protocol BasicKeyRepresentable { 36 | /// Converts this type to a `BasicKey`. 37 | func makeBasicKey() -> BasicKey 38 | } 39 | 40 | extension String: BasicKeyRepresentable { 41 | /// See `BasicKeyRepresentable` 42 | public func makeBasicKey() -> BasicKey { 43 | return BasicKey(self) 44 | } 45 | } 46 | 47 | extension Int: BasicKeyRepresentable { 48 | /// See `BasicKeyRepresentable` 49 | public func makeBasicKey() -> BasicKey { 50 | return BasicKey(self) 51 | } 52 | } 53 | 54 | extension Array where Element == BasicKeyRepresentable { 55 | /// Converts an array of `BasicKeyRepresentable` to `[BasicKey]` 56 | public func makeBasicKeys() -> [BasicKey] { 57 | return map { $0.makeBasicKey() } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/Core/CaseInsensitiveString.swift: -------------------------------------------------------------------------------- 1 | /// A `CaseInsensitiveString` (for `Comparable`, `Equatable`, and `Hashable`). 2 | /// 3 | /// 4 | /// "HELLO".ci == "hello".ci // true 5 | /// 6 | public struct CaseInsensitiveString: ExpressibleByStringLiteral, Comparable, Equatable, Hashable, CustomStringConvertible { 7 | /// See `Equatable`. 8 | public static func == (lhs: CaseInsensitiveString, rhs: CaseInsensitiveString) -> Bool { 9 | return lhs.storage.lowercased() == rhs.storage.lowercased() 10 | } 11 | 12 | /// See `Comparable`. 13 | public static func < (lhs: CaseInsensitiveString, rhs: CaseInsensitiveString) -> Bool { 14 | return lhs.storage.lowercased() < rhs.storage.lowercased() 15 | } 16 | 17 | /// Internal `String` storage. 18 | private let storage: String 19 | 20 | // #if compiler(>=4.2) 21 | #if swift(>=4.1.50) 22 | /// See `Hashable`. 23 | public func hash(into hasher: inout Hasher) { 24 | hasher.combine(self.storage.lowercased()) 25 | } 26 | #else 27 | /// See `Hashable`. 28 | public var hashValue: Int { 29 | return self.storage.lowercased().hashValue 30 | } 31 | #endif 32 | 33 | /// See `CustomStringConvertible`. 34 | public var description: String { 35 | return storage 36 | } 37 | 38 | /// Creates a new `CaseInsensitiveString`. 39 | /// 40 | /// let ciString = CaseInsensitiveString("HeLlO") 41 | /// 42 | /// - parameters: 43 | /// - string: A case-sensitive `String`. 44 | public init(_ string: String) { 45 | self.storage = string 46 | } 47 | 48 | /// See `ExpressibleByStringLiteral`. 49 | public init(stringLiteral value: String) { 50 | self.storage = value 51 | } 52 | } 53 | 54 | extension String { 55 | /// Creates a `CaseInsensitiveString` from this `String`. 56 | /// 57 | /// "HELLO".ci == "hello".ci // true 58 | /// 59 | public var ci: CaseInsensitiveString { 60 | return .init(self) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Core/CodableReflection/Decodable+Reflectable.swift: -------------------------------------------------------------------------------- 1 | /// Default `Reflectable` implementation for types that are also `Decodable`. 2 | extension Reflectable where Self: Decodable { 3 | /// Default `Reflectable` implementation for types that are also `Decodable`. 4 | /// 5 | /// See `Reflectable.reflectProperties(depth:)` 6 | public static func reflectProperties(depth: Int) throws -> [ReflectedProperty] { 7 | return try decodeProperties(depth: depth) 8 | } 9 | 10 | /// Default `Reflectable` implementation for types that are also `Decodable`. 11 | /// 12 | /// See `AnyReflectable`. 13 | public static func anyReflectProperty(valueType: Any.Type, keyPath: AnyKeyPath) throws -> ReflectedProperty? { 14 | return try anyDecodeProperty(valueType: valueType, keyPath: keyPath) 15 | } 16 | } 17 | 18 | extension Decodable { 19 | /// Decodes all `CodableProperty`s for this type. This requires that all propeties on this type are `ReflectionDecodable`. 20 | /// 21 | /// This is used to provide a default implementation for `reflectProperties(depth:)` on `Reflectable`. 22 | /// 23 | /// - parameters: depth: The level of nesting to use. 24 | /// If `0`, the top-most properties will be returned. 25 | /// If `1`, the first layer of nested properties, and so-on. 26 | /// - throws: Any error decoding this type's properties. 27 | /// - returns: All `ReflectedProperty`s at the specified depth. 28 | public static func decodeProperties(depth: Int) throws -> [ReflectedProperty] { 29 | let context = ReflectionDecoderContext(activeOffset: 0, maxDepth: 42) 30 | let decoder = ReflectionDecoder(codingPath: [], context: context) 31 | _ = try Self(from: decoder) 32 | return context.properties.filter { $0.path.count == depth + 1 } 33 | } 34 | 35 | /// Decodes a `CodableProperty` for the supplied `KeyPath`. This requires that all propeties on this 36 | /// type are `ReflectionDecodable`. 37 | /// 38 | /// This is used to provide a default implementation for `reflectProperty(forKey:)` on `Reflectable`. 39 | /// 40 | /// - parameters: 41 | /// - keyPath: `KeyPath` to decode a property for. 42 | /// - throws: Any error decoding this property. 43 | /// - returns: `ReflectedProperty` if one was found. 44 | public static func decodeProperty(forKey keyPath: KeyPath) throws -> ReflectedProperty? { 45 | return try anyDecodeProperty(valueType: T.self, keyPath: keyPath) 46 | } 47 | 48 | /// Decodes a `CodableProperty` for the supplied `KeyPath`. This requires that all propeties on this 49 | /// type are `ReflectionDecodable`. 50 | /// 51 | /// This is used to provide a default implementation for `reflectProperty(forKey:)` on `Reflectable`. 52 | /// 53 | /// - parameters: 54 | /// - keyPath: `AnyKeyPath` to decode a property for. 55 | /// - throws: Any error decoding this property. 56 | public static func anyDecodeProperty(valueType: Any.Type, keyPath: AnyKeyPath) throws -> ReflectedProperty? { 57 | guard valueType is AnyReflectionDecodable.Type else { 58 | throw CoreError(identifier: "ReflectionDecodable", reason: "`\(valueType)` does not conform to `ReflectionDecodable`.") 59 | } 60 | 61 | if let cached = ReflectedPropertyCache.storage[keyPath] { 62 | return cached 63 | } 64 | 65 | var maxDepth = 0 66 | a: while true { 67 | defer { maxDepth += 1 } 68 | var activeOffset = 0 69 | 70 | if maxDepth > 42 { 71 | return nil 72 | } 73 | 74 | b: while true { 75 | defer { activeOffset += 1 } 76 | let context = ReflectionDecoderContext(activeOffset: activeOffset, maxDepth: maxDepth) 77 | let decoder = ReflectionDecoder(codingPath: [], context: context) 78 | 79 | let decoded = try Self(from: decoder) 80 | guard let codingPath = context.activeCodingPath else { 81 | // no more values are being set at this depth 82 | break b 83 | } 84 | 85 | guard let t = valueType as? AnyReflectionDecodable.Type, let left = decoded[keyPath: keyPath] else { 86 | break b 87 | } 88 | 89 | if try t.anyReflectDecodedIsLeft(left) { 90 | let property = ReflectedProperty(any: valueType, at: codingPath.map { $0.stringValue }) 91 | ReflectedPropertyCache.storage[keyPath] = property 92 | return property 93 | } 94 | } 95 | } 96 | } 97 | } 98 | 99 | /// Caches derived `ReflectedProperty`s so that they only need to be decoded once per thread. 100 | final class ReflectedPropertyCache { 101 | /// Thread-specific shared storage. 102 | static var storage: [AnyKeyPath: ReflectedProperty] { 103 | get { 104 | let cache = ReflectedPropertyCache.thread.currentValue ?? .init() 105 | return cache.storage 106 | } 107 | set { 108 | let cache = ReflectedPropertyCache.thread.currentValue ?? .init() 109 | cache.storage = newValue 110 | ReflectedPropertyCache.thread.currentValue = cache 111 | } 112 | } 113 | 114 | /// Private `ThreadSpecificVariable` powering this cache. 115 | private static let thread: ThreadSpecificVariable = .init() 116 | 117 | /// Instance storage. 118 | private var storage: [AnyKeyPath: ReflectedProperty] 119 | 120 | /// Creates a new `ReflectedPropertyCache`. 121 | init() { 122 | self.storage = [:] 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Sources/Core/CodableReflection/ReflectionDecodable.swift: -------------------------------------------------------------------------------- 1 | /// Types conforming to this protocol can be created dynamically for use in reflecting the structure of a `Decodable` type. 2 | /// 3 | /// `ReflectionDecodable` requires that a type declare two _distinct_ representations of itself. It also requires that the type 4 | /// declare a method for comparing those two representations. If the conforming type is already equatable, this method will 5 | /// not be required. 6 | /// 7 | /// A `Bool` is a simple type that is capable of conforming to `ReflectionDecodable`: 8 | /// 9 | /// extension Bool: ReflectionDecodable { 10 | /// static func reflectDecoded() -> (Bool, Bool) { return (false, true) } 11 | /// } 12 | /// 13 | /// For some types, like an `enum` with only one case, it is impossible to conform to `ReflectionDecodable`. In these situations 14 | /// you must expand the type to have at least two distinct cases, or use a different method of reflection. 15 | /// 16 | /// enum Pet { case cat } // unable to conform 17 | /// 18 | /// Enums with two or more cases can conform. 19 | /// 20 | /// enum Pet { case cat, dog } 21 | /// extension Pet: ReflectionDecodable { 22 | /// static func reflectDecoded() -> (Pet, Pet) { return (.cat, .dog) } 23 | /// } 24 | /// 25 | /// Many types already conform to `ReflectionDecodable` such as `String`, `Int`, `Double`, `UUID`, `Array`, `Dictionary`, and `Optional`. 26 | /// 27 | /// Other types will have free implementation provided when conformance is added, like `RawRepresentable` types. 28 | /// 29 | /// enum Direction: UInt8, ReflectionDecodable { 30 | /// case left, right 31 | /// } 32 | /// 33 | public protocol ReflectionDecodable: AnyReflectionDecodable { 34 | /// Returns a tuple containing two _distinct_ instances for this type. 35 | /// 36 | /// extension Bool: ReflectionDecodable { 37 | /// static func reflectDecoded() -> (Bool, Bool) { return (false, true) } 38 | /// } 39 | /// 40 | /// - throws: Any errors deriving these distinct instances. 41 | /// - returns: Two distinct instances of this type. 42 | static func reflectDecoded() throws -> (Self, Self) 43 | 44 | /// Returns `true` if the supplied instance of this type is equal to the _left_ instance returned 45 | /// by `reflectDecoded()`. 46 | /// 47 | /// extension Pet: ReflectionDecodable { 48 | /// static func reflectDecoded() -> (Pet, Pet) { return (cat, dog) } 49 | /// } 50 | /// 51 | /// In the case of the above example, this method should return `true` if supplied `Pet.cat` and false for anything else. 52 | /// This method is automatically implemented for types that conform to `Equatable. 53 | /// 54 | /// - throws: Any errors comparing instances. 55 | /// - returns: `true` if supplied instance equals left side of `reflectDecoded()`. 56 | static func reflectDecodedIsLeft(_ item: Self) throws -> Bool 57 | } 58 | 59 | extension ReflectionDecodable where Self: Equatable { 60 | /// Default implememntation for `ReflectionDecodable` that are also `Equatable`. 61 | /// 62 | /// See `ReflectionDecodable.reflectDecodedIsLeft(_:)` for more information. 63 | public static func reflectDecodedIsLeft(_ item: Self) throws -> Bool { 64 | return try Self.reflectDecoded().0 == item 65 | } 66 | } 67 | 68 | // MARK: Types 69 | 70 | extension String: ReflectionDecodable { 71 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 72 | public static func reflectDecoded() -> (String, String) { return ("0", "1") } 73 | } 74 | 75 | extension FixedWidthInteger { 76 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 77 | public static func reflectDecoded() -> (Self, Self) { return (0, 1) } 78 | } 79 | 80 | extension UInt: ReflectionDecodable { } 81 | extension UInt8: ReflectionDecodable { } 82 | extension UInt16: ReflectionDecodable { } 83 | extension UInt32: ReflectionDecodable { } 84 | extension UInt64: ReflectionDecodable { } 85 | 86 | extension Int: ReflectionDecodable { } 87 | extension Int8: ReflectionDecodable { } 88 | extension Int16: ReflectionDecodable { } 89 | extension Int32: ReflectionDecodable { } 90 | extension Int64: ReflectionDecodable { } 91 | 92 | extension Bool: ReflectionDecodable { 93 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 94 | public static func reflectDecoded() -> (Bool, Bool) { return (false, true) } 95 | } 96 | 97 | extension BinaryFloatingPoint { 98 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 99 | public static func reflectDecoded() -> (Self, Self) { return (0, 1) } 100 | } 101 | 102 | extension Decimal: ReflectionDecodable { 103 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 104 | public static func reflectDecoded() -> (Decimal, Decimal) { return (0, 1) } 105 | } 106 | 107 | extension Float: ReflectionDecodable { } 108 | extension Double: ReflectionDecodable { } 109 | 110 | extension UUID: ReflectionDecodable { 111 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 112 | public static func reflectDecoded() -> (UUID, UUID) { 113 | let left = UUID(uuid: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)) 114 | let right = UUID(uuid: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2)) 115 | return (left, right) 116 | } 117 | } 118 | 119 | extension Data: ReflectionDecodable { 120 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 121 | public static func reflectDecoded() -> (Data, Data) { 122 | let left = Data([0x00]) 123 | let right = Data([0x01]) 124 | return (left, right) 125 | } 126 | } 127 | 128 | extension File: ReflectionDecodable { 129 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 130 | public static func reflectDecoded() -> (File, File) { 131 | return ( 132 | File(data: Data(count: 1), filename: "foo.pdf"), 133 | File(data: Data(count: 2), filename: "bar.pdf") 134 | ) 135 | } 136 | } 137 | 138 | extension Date: ReflectionDecodable { 139 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 140 | public static func reflectDecoded() -> (Date, Date) { 141 | let left = Date(timeIntervalSince1970: 1) 142 | let right = Date(timeIntervalSince1970: 0) 143 | return (left, right) 144 | } 145 | } 146 | 147 | extension Optional: ReflectionDecodable { 148 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 149 | public static func reflectDecoded() throws -> (Wrapped?, Wrapped?) { 150 | let reflected = try forceCast(Wrapped.self).anyReflectDecoded() 151 | return (reflected.0 as? Wrapped, reflected.1 as? Wrapped) 152 | } 153 | 154 | /// See `ReflectionDecodable.reflectDecodedIsLeft(_:)` for more information. 155 | public static func reflectDecodedIsLeft(_ item: Wrapped?) throws -> Bool { 156 | guard let wrapped = item else { 157 | return false 158 | } 159 | return try forceCast(Wrapped.self).anyReflectDecodedIsLeft(wrapped) 160 | } 161 | } 162 | 163 | extension Array: ReflectionDecodable { 164 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 165 | public static func reflectDecoded() throws -> ([Element], [Element]) { 166 | let reflected = try forceCast(Element.self).anyReflectDecoded() 167 | return ([reflected.0 as! Element], [reflected.1 as! Element]) 168 | } 169 | 170 | /// See `ReflectionDecodable.reflectDecodedIsLeft(_:)` for more information. 171 | public static func reflectDecodedIsLeft(_ item: [Element]) throws -> Bool { 172 | return try forceCast(Element.self).anyReflectDecodedIsLeft(item[0]) 173 | } 174 | } 175 | 176 | extension Dictionary: ReflectionDecodable { 177 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 178 | public static func reflectDecoded() throws -> ([Key: Value], [Key: Value]) { 179 | let reflectedValue = try forceCast(Value.self).anyReflectDecoded() 180 | let reflectedKey = try forceCast(Key.self).anyReflectDecoded() 181 | let key = reflectedKey.0 as! Key 182 | return ([key: reflectedValue.0 as! Value], [key: reflectedValue.1 as! Value]) 183 | } 184 | 185 | /// See `ReflectionDecodable.reflectDecodedIsLeft(_:)` for more information. 186 | public static func reflectDecodedIsLeft(_ item: [Key: Value]) throws -> Bool { 187 | let reflectedKey = try forceCast(Key.self).anyReflectDecoded() 188 | let key = reflectedKey.0 as! Key 189 | return try forceCast(Value.self).anyReflectDecodedIsLeft(item[key]!) 190 | } 191 | } 192 | 193 | extension Set: ReflectionDecodable { 194 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 195 | public static func reflectDecoded() throws -> (Set, Set) { 196 | let reflected = try forceCast(Element.self).anyReflectDecoded() 197 | return ([reflected.0 as! Element], [reflected.1 as! Element]) 198 | } 199 | 200 | /// See `ReflectionDecodable.reflectDecodedIsLeft(_:)` for more information. 201 | public static func reflectDecodedIsLeft(_ item: Set) throws -> Bool { 202 | return try forceCast(Element.self).anyReflectDecodedIsLeft(item.first!) 203 | } 204 | } 205 | 206 | extension URL: ReflectionDecodable { 207 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 208 | public static func reflectDecoded() throws -> (URL, URL) { 209 | let left = URL(string: "https://left.fake.url")! 210 | let right = URL(string: "https://right.fake.url")! 211 | return (left, right) 212 | } 213 | } 214 | 215 | // MARK: Type Erased 216 | 217 | /// Type-erased version of `ReflectionDecodable` 218 | public protocol AnyReflectionDecodable { 219 | /// Type-erased version of `ReflectionDecodable.reflectDecoded()`. 220 | /// 221 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 222 | static func anyReflectDecoded() throws -> (Any, Any) 223 | 224 | /// Type-erased version of `ReflectionDecodable.reflectDecodedIsLeft(_:)`. 225 | /// 226 | /// See `ReflectionDecodable.reflectDecodedIsLeft(_:)` for more information. 227 | static func anyReflectDecodedIsLeft(_ any: Any) throws -> Bool 228 | } 229 | 230 | extension ReflectionDecodable { 231 | /// Type-erased version of `ReflectionDecodable.reflectDecoded()`. 232 | /// 233 | /// See `ReflectionDecodable.reflectDecoded()` for more information. 234 | public static func anyReflectDecoded() throws -> (Any, Any) { 235 | let reflected = try reflectDecoded() 236 | return (reflected.0, reflected.1) 237 | } 238 | 239 | /// Type-erased version of `ReflectionDecodable.reflectDecodedIsLeft(_:)`. 240 | /// 241 | /// See `ReflectionDecodable.reflectDecodedIsLeft(_:)` for more information. 242 | public static func anyReflectDecodedIsLeft(_ any: Any) throws -> Bool { 243 | return try reflectDecodedIsLeft(any as! Self) 244 | } 245 | } 246 | 247 | /// Trys to cast a type to `AnyReflectionDecodable.Type`. This can be removed when conditional conformance supports runtime querying. 248 | func forceCast(_ type: T.Type) throws -> AnyReflectionDecodable.Type { 249 | guard let casted = T.self as? AnyReflectionDecodable.Type else { 250 | throw CoreError( 251 | identifier: "ReflectionDecodable", 252 | reason: "\(T.self) is not `ReflectionDecodable`", 253 | suggestedFixes: [ 254 | "Conform `\(T.self)` to `ReflectionDecodable`: `extension \(T.self): ReflectionDecodable { }`." 255 | ] 256 | ) 257 | } 258 | return casted 259 | } 260 | 261 | #if swift(>=4.1.50) 262 | #else 263 | public protocol CaseIterable { 264 | static var allCases: [Self] { get } 265 | } 266 | #endif 267 | 268 | extension ReflectionDecodable where Self: CaseIterable { 269 | /// Default implementation of `ReflectionDecodable` for enums that are also `CaseIterable`. 270 | /// 271 | /// See `ReflectionDecodable.reflectDecoded(_:)` for more information. 272 | public static func reflectDecoded() throws -> (Self, Self) { 273 | /// enum must have at least 2 unique cases 274 | guard allCases.count > 1, 275 | let first = allCases.first, let last = allCases.suffix(1).first else { 276 | throw CoreError( 277 | identifier: "ReflectionDecodable", 278 | reason: "\(Self.self) enum must have at least 2 cases", 279 | suggestedFixes: [ 280 | "Add at least 2 cases to the enum." 281 | ] 282 | ) 283 | } 284 | return (first, last) 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /Sources/Core/CodableReflection/ReflectionDecoders.swift: -------------------------------------------------------------------------------- 1 | /// Internal types for powering the default implementation of `Reflectable` for `Decodable` types. 2 | /// 3 | /// See `Decodable.decodeProperties(depth:)` and `Decodable.decodeProperty(forKey:)` for more information. 4 | 5 | // MARK: Internal 6 | 7 | /// Reference class for collecting information about `Decodable` types when initializing them. 8 | final class ReflectionDecoderContext { 9 | /// If set, this is the `CodingKey` path to the truthy value in the initialized model. 10 | var activeCodingPath: [CodingKey]? 11 | 12 | /// Sets a maximum depth for decoding nested types like optionals and structs. This value ensures 13 | /// that models with recursive structures can be decoded without looping infinitely. 14 | var maxDepth: Int 15 | 16 | /// An array of all properties seen while initilaizing the `Decodable` type. 17 | var properties: [ReflectedProperty] 18 | 19 | /// If `true`, the property be decoded currently should be set to a truthy value. 20 | /// This property will cycle each time it is called. 21 | var isActive: Bool { 22 | defer { currentOffset += 1 } 23 | return currentOffset == activeOffset 24 | } 25 | 26 | /// This decoder context's curent active offset. This will determine which property gets 27 | /// set to a truthy value while decoding. 28 | private var activeOffset: Int 29 | 30 | /// Current offset. This is equal to the number of times `isActive` has been called so far. 31 | private var currentOffset: Int 32 | 33 | /// Creates a new `ReflectionDecoderContext`. 34 | init(activeOffset: Int, maxDepth: Int) { 35 | self.activeCodingPath = nil 36 | self.maxDepth = maxDepth 37 | self.properties = [] 38 | self.activeOffset = activeOffset 39 | currentOffset = 0 40 | } 41 | 42 | /// Adds a property to this `ReflectionDecoderContext`. 43 | func addProperty(type: T.Type, at path: [CodingKey]) { 44 | let path = path.map { $0.stringValue } 45 | // remove any duplicates, favoring the new type 46 | properties = properties.filter { $0.path != path } 47 | let property = ReflectedProperty.init(T.self, at: path) 48 | properties.append(property) 49 | } 50 | } 51 | 52 | /// Main decoder for codable reflection. 53 | struct ReflectionDecoder: Decoder { 54 | var codingPath: [CodingKey] 55 | var context: ReflectionDecoderContext 56 | var userInfo: [CodingUserInfoKey: Any] { return [:] } 57 | 58 | init(codingPath: [CodingKey], context: ReflectionDecoderContext) { 59 | self.codingPath = codingPath 60 | self.context = context 61 | } 62 | 63 | func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { 64 | return .init(ReflectionKeyedDecoder(codingPath: codingPath, context: context)) 65 | } 66 | 67 | func unkeyedContainer() throws -> UnkeyedDecodingContainer { 68 | return ReflectionUnkeyedDecoder(codingPath: codingPath, context: context) 69 | } 70 | 71 | func singleValueContainer() throws -> SingleValueDecodingContainer { 72 | return ReflectionSingleValueDecoder(codingPath: codingPath, context: context) 73 | } 74 | } 75 | 76 | /// Single value decoder for codable reflection. 77 | struct ReflectionSingleValueDecoder: SingleValueDecodingContainer { 78 | var codingPath: [CodingKey] 79 | var context: ReflectionDecoderContext 80 | 81 | init(codingPath: [CodingKey], context: ReflectionDecoderContext) { 82 | self.codingPath = codingPath 83 | self.context = context 84 | } 85 | 86 | func decodeNil() -> Bool { 87 | return false 88 | } 89 | 90 | func decode(_ type: T.Type) throws -> T where T: Decodable { 91 | context.addProperty(type: T.self, at: codingPath) 92 | let type = try forceCast(T.self) 93 | let reflected = try type.anyReflectDecoded() 94 | if context.isActive { 95 | context.activeCodingPath = codingPath 96 | return reflected.0 as! T 97 | } 98 | return reflected.1 as! T 99 | } 100 | } 101 | 102 | /// Keyed decoder for codable reflection. 103 | final class ReflectionKeyedDecoder: KeyedDecodingContainerProtocol where K: CodingKey { 104 | typealias Key = K 105 | var allKeys: [K] { return [] } 106 | var codingPath: [CodingKey] 107 | var context: ReflectionDecoderContext 108 | var nextIsOptional: Bool 109 | 110 | init(codingPath: [CodingKey], context: ReflectionDecoderContext) { 111 | self.codingPath = codingPath 112 | self.context = context 113 | self.nextIsOptional = false 114 | } 115 | 116 | func contains(_ key: K) -> Bool { 117 | nextIsOptional = true 118 | return true 119 | } 120 | 121 | func decodeNil(forKey key: K) throws -> Bool { 122 | if context.maxDepth > codingPath.count { 123 | return false 124 | } 125 | return true 126 | } 127 | 128 | func nestedContainer(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer where NestedKey: CodingKey { 129 | return .init(ReflectionKeyedDecoder(codingPath: codingPath + [key], context: context)) 130 | } 131 | 132 | func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer { 133 | return ReflectionUnkeyedDecoder(codingPath: codingPath + [key], context: context) 134 | } 135 | 136 | func superDecoder() throws -> Decoder { 137 | return ReflectionDecoder(codingPath: codingPath, context: context) 138 | } 139 | 140 | func superDecoder(forKey key: K) throws -> Decoder { 141 | return ReflectionDecoder(codingPath: codingPath + [key], context: context) 142 | } 143 | 144 | func decode(_ type: T.Type, forKey key: K) throws -> T where T : Decodable { 145 | if nextIsOptional { 146 | context.addProperty(type: T?.self, at: codingPath + [key]) 147 | nextIsOptional = false 148 | } else { 149 | context.addProperty(type: T.self, at: codingPath + [key]) 150 | } 151 | if let type = T.self as? AnyReflectionDecodable.Type, let reflected = try? type.anyReflectDecoded() { 152 | if context.isActive { 153 | context.activeCodingPath = codingPath + [key] 154 | return reflected.0 as! T 155 | } 156 | return reflected.1 as! T 157 | } else { 158 | let decoder = ReflectionDecoder(codingPath: codingPath + [key], context: context) 159 | return try T(from: decoder) 160 | } 161 | } 162 | } 163 | 164 | /// Unkeyed decoder for codable reflection. 165 | fileprivate struct ReflectionUnkeyedDecoder: UnkeyedDecodingContainer { 166 | var count: Int? 167 | var isAtEnd: Bool 168 | var currentIndex: Int 169 | var codingPath: [CodingKey] 170 | var context: ReflectionDecoderContext 171 | 172 | init(codingPath: [CodingKey], context: ReflectionDecoderContext) { 173 | self.codingPath = codingPath 174 | self.context = context 175 | self.currentIndex = 0 176 | if context.isActive { 177 | self.count = 1 178 | self.isAtEnd = false 179 | context.activeCodingPath = codingPath 180 | } else { 181 | self.count = 0 182 | self.isAtEnd = true 183 | } 184 | } 185 | 186 | mutating func decodeNil() throws -> Bool { 187 | isAtEnd = true 188 | return true 189 | } 190 | 191 | mutating func decode(_ type: T.Type) throws -> T where T : Decodable { 192 | context.addProperty(type: [T].self, at: codingPath) 193 | isAtEnd = true 194 | if let type = T.self as? AnyReflectionDecodable.Type, let reflected = try? type.anyReflectDecoded() { 195 | return reflected.0 as! T 196 | } else { 197 | let decoder = ReflectionDecoder(codingPath: codingPath, context: context) 198 | return try T(from: decoder) 199 | } 200 | } 201 | 202 | mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { 203 | return .init(ReflectionKeyedDecoder(codingPath: codingPath, context: context)) 204 | } 205 | 206 | mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { 207 | return ReflectionUnkeyedDecoder(codingPath: codingPath, context: context) 208 | } 209 | 210 | mutating func superDecoder() throws -> Decoder { 211 | return ReflectionDecoder(codingPath: codingPath, context: context) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Sources/Core/CoreError.swift: -------------------------------------------------------------------------------- 1 | /// An error that can be thrown while working with the `Core` module. 2 | public struct CoreError: Debuggable, Error { 3 | /// See `Debuggable` 4 | public var identifier: String 5 | 6 | /// See `Debuggable` 7 | public var reason: String 8 | 9 | /// See `Debuggable` 10 | public var possibleCauses: [String] 11 | 12 | /// See `Debuggable` 13 | public var suggestedFixes: [String] 14 | 15 | /// See `Debuggable` 16 | public var stackTrace: [String]? 17 | 18 | /// Creates a new `CoreError`. 19 | /// 20 | /// See `Debuggable` 21 | public init(identifier: String, reason: String, possibleCauses: [String] = [], suggestedFixes: [String] = []) { 22 | self.identifier = identifier 23 | self.reason = reason 24 | self.suggestedFixes = suggestedFixes 25 | self.possibleCauses = possibleCauses 26 | self.stackTrace = CoreError.makeStackTrace() 27 | } 28 | } 29 | 30 | /// Logs an unhandleable runtime error. 31 | internal func ERROR(_ string: @autoclosure () -> String) { 32 | print("[ERROR] [Core] \(string())") 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Core/Data+Base64URL.swift: -------------------------------------------------------------------------------- 1 | import Bits 2 | 3 | extension Data { 4 | /// Decodes a base64-url encoded string to data. 5 | /// 6 | /// https://tools.ietf.org/html/rfc4648#page-7 7 | public init?(base64URLEncoded: String, options: Data.Base64DecodingOptions = []) { 8 | self.init(base64Encoded: base64URLEncoded.base64URLUnescaped(), options: options) 9 | } 10 | 11 | /// Decodes base64-url encoded data. 12 | /// 13 | /// https://tools.ietf.org/html/rfc4648#page-7 14 | public init?(base64URLEncoded: Data, options: Data.Base64DecodingOptions = []) { 15 | self.init(base64Encoded: base64URLEncoded.base64URLUnescaped(), options: options) 16 | } 17 | 18 | /// Encodes data to a base64-url encoded string. 19 | /// 20 | /// https://tools.ietf.org/html/rfc4648#page-7 21 | /// 22 | /// - parameter options: The options to use for the encoding. Default value is `[]`. 23 | /// - returns: The base64-url encoded string. 24 | public func base64URLEncodedString(options: Data.Base64EncodingOptions = []) -> String { 25 | return base64EncodedString(options: options).base64URLEscaped() 26 | } 27 | 28 | /// Encodes data to base64-url encoded data. 29 | /// 30 | /// https://tools.ietf.org/html/rfc4648#page-7 31 | /// 32 | /// - parameter options: The options to use for the encoding. Default value is `[]`. 33 | /// - returns: The base64-url encoded data. 34 | public func base64URLEncodedData(options: Data.Base64EncodingOptions = []) -> Data { 35 | return base64EncodedData(options: options).base64URLEscaped() 36 | } 37 | } 38 | 39 | /// MARK: String Escape 40 | 41 | extension String { 42 | /// Converts a base64-url encoded string to a base64 encoded string. 43 | /// 44 | /// https://tools.ietf.org/html/rfc4648#page-7 45 | public func base64URLUnescaped() -> String { 46 | let replaced = replacingOccurrences(of: "-", with: "+") 47 | .replacingOccurrences(of: "_", with: "/") 48 | /// https://stackoverflow.com/questions/43499651/decode-base64url-to-base64-swift 49 | let padding = replaced.count % 4 50 | if padding > 0 { 51 | return replaced + String(repeating: "=", count: 4 - padding) 52 | } else { 53 | return replaced 54 | } 55 | } 56 | 57 | /// Converts a base64 encoded string to a base64-url encoded string. 58 | /// 59 | /// https://tools.ietf.org/html/rfc4648#page-7 60 | public func base64URLEscaped() -> String { 61 | return replacingOccurrences(of: "+", with: "-") 62 | .replacingOccurrences(of: "/", with: "_") 63 | .replacingOccurrences(of: "=", with: "") 64 | } 65 | 66 | /// Converts a base64-url encoded string to a base64 encoded string. 67 | /// 68 | /// https://tools.ietf.org/html/rfc4648#page-7 69 | public mutating func base64URLUnescape() { 70 | self = base64URLUnescaped() 71 | } 72 | 73 | /// Converts a base64 encoded string to a base64-url encoded string. 74 | /// 75 | /// https://tools.ietf.org/html/rfc4648#page-7 76 | public mutating func base64URLEscape() { 77 | self = base64URLEscaped() 78 | } 79 | } 80 | 81 | /// MARK: Data Escape 82 | 83 | extension Data { 84 | /// Converts base64-url encoded data to a base64 encoded data. 85 | /// 86 | /// https://tools.ietf.org/html/rfc4648#page-7 87 | public mutating func base64URLUnescape() { 88 | for (i, byte) in enumerated() { 89 | switch byte { 90 | case .hyphen: self[i] = .plus 91 | case .underscore: self[i] = .forwardSlash 92 | default: break 93 | } 94 | } 95 | /// https://stackoverflow.com/questions/43499651/decode-base64url-to-base64-swift 96 | let padding = count % 4 97 | if padding > 0 { 98 | self += Data(repeating: .equals, count: 4 - count % 4) 99 | } 100 | } 101 | 102 | /// Converts base64 encoded data to a base64-url encoded data. 103 | /// 104 | /// https://tools.ietf.org/html/rfc4648#page-7 105 | public mutating func base64URLEscape() { 106 | for (i, byte) in enumerated() { 107 | switch byte { 108 | case .plus: self[i] = .hyphen 109 | case .forwardSlash: self[i] = .underscore 110 | default: break 111 | } 112 | } 113 | self = split(separator: .equals).first ?? .init() 114 | } 115 | 116 | /// Converts base64-url encoded data to a base64 encoded data. 117 | /// 118 | /// https://tools.ietf.org/html/rfc4648#page-7 119 | public func base64URLUnescaped() -> Data { 120 | var data = self 121 | data.base64URLUnescape() 122 | return data 123 | } 124 | 125 | /// Converts base64 encoded data to a base64-url encoded data. 126 | /// 127 | /// https://tools.ietf.org/html/rfc4648#page-7 128 | public func base64URLEscaped() -> Data { 129 | var data = self 130 | data.base64URLEscape() 131 | return data 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Sources/Core/Data+Hex.swift: -------------------------------------------------------------------------------- 1 | import Bits 2 | 3 | extension Data { 4 | /// Converts `Data` to a hex-encoded `String`. 5 | /// 6 | /// Data("hello".utf8).hexEncodedString() // 68656c6c6f 7 | /// 8 | /// - parameters: 9 | /// - uppercase: If `true`, uppercase letters will be used when encoding. 10 | /// Default value is `false`. 11 | public func hexEncodedString(uppercase: Bool = false) -> String { 12 | return String(bytes: hexEncodedData(uppercase: uppercase), encoding: .utf8) ?? "" 13 | } 14 | 15 | 16 | /// Applies hex-encoding to `Data`. 17 | /// 18 | /// Data("hello".utf8).hexEncodedData() // 68656c6c6f 19 | /// 20 | /// - parameters: 21 | /// - uppercase: If `true`, uppercase letters will be used when encoding. 22 | /// Default value is `false`. 23 | public func hexEncodedData(uppercase: Bool = false) -> Data { 24 | var bytes = Data() 25 | bytes.reserveCapacity(count * 2) 26 | 27 | let table: Bytes 28 | if uppercase { 29 | table = radix16table_uppercase 30 | } else { 31 | table = radix16table_lowercase 32 | } 33 | 34 | for byte in self { 35 | bytes.append(table[Int(byte / 16)]) 36 | bytes.append(table[Int(byte % 16)]) 37 | } 38 | 39 | return bytes 40 | } 41 | } 42 | 43 | /// Uppercase radix16 table. 44 | fileprivate let radix16table_uppercase: Bytes = [ 45 | .zero, .one, .two, .three, .four, .five, .six, .seven, .eight, .nine, .A, .B, .C, .D, .E, .F 46 | ] 47 | 48 | /// Lowercase radix16 table. 49 | fileprivate let radix16table_lowercase: Bytes = [ 50 | .zero, .one, .two, .three, .four, .five, .six, .seven, .eight, .nine, .a, .b, .c, .d, .e, .f 51 | ] 52 | -------------------------------------------------------------------------------- /Sources/Core/DataCoders.swift: -------------------------------------------------------------------------------- 1 | /// A type capable of decoding `Decodable` types from `Data`. 2 | /// 3 | /// print(data) /// Data 4 | /// let user = try JSONDecoder().decode(User.self, from: data) 5 | /// print(user) /// User 6 | /// 7 | public protocol DataDecoder { 8 | /// Decodes an instance of the supplied `Decodable` type from `Data`. 9 | /// 10 | /// print(data) /// Data 11 | /// let user = try JSONDecoder().decode(User.self, from: data) 12 | /// print(user) /// User 13 | /// 14 | /// - parameters: 15 | /// - decodable: Generic `Decodable` type (`D`) to decode. 16 | /// - from: `Data` to decode a `D` from. 17 | /// - returns: An instance of the `Decodable` type (`D`). 18 | /// - throws: Any error that may occur while attempting to decode the specified type. 19 | func decode(_ decodable: D.Type, from data: Data) throws -> D where D: Decodable 20 | } 21 | 22 | extension DataDecoder { 23 | /// Convenience method for decoding a `Decodable` type from something `LosslessDataConvertible`. 24 | /// 25 | /// 26 | /// print(data) /// LosslessDataConvertible 27 | /// let user = try JSONDecoder().decode(User.self, from: data) 28 | /// print(user) /// User 29 | /// 30 | /// - parameters: 31 | /// - decodable: Generic `Decodable` type (`D`) to decode. 32 | /// - from: `LosslessDataConvertible` to decode a `D` from. 33 | /// - returns: An instance of the `Decodable` type (`D`). 34 | /// - throws: Any error that may occur while attempting to decode the specified type. 35 | public func decode(_ decodable: D.Type, from data: LosslessDataConvertible) throws -> D where D: Decodable { 36 | return try decode(D.self, from: data.convertToData()) 37 | } 38 | } 39 | 40 | /// A type capable of encoding `Encodable` objects to `Data`. 41 | /// 42 | /// print(user) /// User 43 | /// let data = try JSONEncoder().encode(user) 44 | /// print(data) /// Data 45 | /// 46 | public protocol DataEncoder { 47 | /// Encodes the supplied `Encodable` object to `Data`. 48 | /// 49 | /// print(user) /// User 50 | /// let data = try JSONEncoder().encode(user) 51 | /// print(data) /// Data 52 | /// 53 | /// - parameters: 54 | /// - encodable: Generic `Encodable` object (`E`) to encode. 55 | /// - returns: Encoded `Data` 56 | /// - throws: Any error taht may occur while attempting to encode the specified type. 57 | func encode(_ encodable: E) throws -> Data where E: Encodable 58 | } 59 | 60 | /// MARK: Default Conformances 61 | 62 | extension JSONDecoder: DataDecoder { } 63 | extension JSONEncoder: DataEncoder { } 64 | -------------------------------------------------------------------------------- /Sources/Core/Deprecated.swift: -------------------------------------------------------------------------------- 1 | /// Nothing here yet... 2 | -------------------------------------------------------------------------------- /Sources/Core/DirectoryConfig.swift: -------------------------------------------------------------------------------- 1 | import COperatingSystem 2 | 3 | /// `DirectoryConfig` represents a configured working directory. It can also be used to derive a working directory automatically. 4 | /// 5 | /// let dirConfig = DirectoryConfig.detect() 6 | /// print(dirConfig.workDir) // "/path/to/workdir" 7 | /// 8 | public struct DirectoryConfig { 9 | /// Path to the current working directory. 10 | public let workDir: String 11 | 12 | /// Create a new `DirectoryConfig` with a custom working directory. 13 | /// 14 | /// - parameters: 15 | /// - workDir: Custom working directory path. 16 | public init(workDir: String) { 17 | self.workDir = workDir 18 | } 19 | 20 | /// Creates a `DirectoryConfig` by deriving a working directory using the `#file` variable or `getcwd` method. 21 | /// 22 | /// - returns: The derived `DirectoryConfig` if it could be created, otherwise just "./". 23 | public static func detect() -> DirectoryConfig { 24 | var fileBasedWorkDir: String? = nil 25 | 26 | #if Xcode 27 | // check if we are in Xcode via SPM integration 28 | if !#file.contains("SourcePackages/checkouts") { 29 | // we are NOT in Xcode via SPM integration 30 | // use #file hacks to determine working directory automatically 31 | if #file.contains(".build") { 32 | // most dependencies are in `./.build/` 33 | fileBasedWorkDir = #file.components(separatedBy: "/.build").first 34 | } else if #file.contains("Packages") { 35 | // when editing a dependency, it is in `./Packages/` 36 | fileBasedWorkDir = #file.components(separatedBy: "/Packages").first 37 | } else { 38 | // when dealing with current repository, file is in `./Sources/` 39 | fileBasedWorkDir = #file.components(separatedBy: "/Sources").first 40 | } 41 | } 42 | #endif 43 | 44 | let workDir: String 45 | if let fileBasedWorkDir = fileBasedWorkDir { 46 | workDir = fileBasedWorkDir 47 | } else { 48 | // get actual working directory 49 | let cwd = getcwd(nil, Int(PATH_MAX)) 50 | defer { 51 | if let cwd = cwd { 52 | free(cwd) 53 | } 54 | } 55 | 56 | if let cwd = cwd, let string = String(validatingUTF8: cwd) { 57 | workDir = string 58 | } else { 59 | workDir = "./" 60 | } 61 | } 62 | 63 | return DirectoryConfig( 64 | workDir: workDir.hasSuffix("/") ? workDir : workDir + "/" 65 | ) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/Core/Exports.swift: -------------------------------------------------------------------------------- 1 | @_exported import Async 2 | @_exported import Debugging 3 | @_exported import Foundation 4 | @_exported import NIOFoundationCompat 5 | -------------------------------------------------------------------------------- /Sources/Core/File.swift: -------------------------------------------------------------------------------- 1 | /// Represents a single file. 2 | public struct File: Codable { 3 | /// Name of the file, including extension. 4 | public var filename: String 5 | 6 | /// The file's data. 7 | public var data: Data 8 | 9 | /// Associated `MediaType` for this file's extension, if it has one. 10 | public var contentType: MediaType? { 11 | return ext.flatMap { MediaType.fileExtension($0.lowercased()) } 12 | } 13 | 14 | /// The file extension, if it has one. 15 | public var ext: String? { 16 | let parts = filename.split(separator: ".") 17 | 18 | if parts.count > 1 { 19 | return parts.last.map(String.init) 20 | } else { 21 | return nil 22 | } 23 | } 24 | 25 | /// Creates a new `File`. 26 | /// 27 | /// let file = File(data: "hello", filename: "foo.txt") 28 | /// 29 | /// - parameters: 30 | /// - data: The file's contents. 31 | /// - filename: The name of the file, not including path. 32 | public init(data: LosslessDataConvertible, filename: String) { 33 | self.data = data.convertToData() 34 | self.filename = filename 35 | } 36 | } 37 | 38 | extension File: Equatable { 39 | public static func ==(lhs: File, rhs: File) -> Bool { 40 | return 41 | lhs.data == rhs.data && 42 | lhs.filename == rhs.filename 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/Core/Future+Unwrap.swift: -------------------------------------------------------------------------------- 1 | public extension Future where Expectation: OptionalType { 2 | /// Unwraps an `Optional` value contained inside a Future's expectation. 3 | /// If the optional resolves to `nil` (`.none`), the supplied error will be thrown instead. 4 | /// 5 | /// print(futureString) // Future 6 | /// futureString.unwrap(or: MyError()) // Future 7 | /// 8 | /// - parameters: 9 | /// - error: `Error` to throw if the value is `nil`. This is captured with `@autoclosure` 10 | /// to avoid intiailize the `Error` unless needed. 11 | func unwrap(or error: @autoclosure @escaping () -> Error) -> Future { 12 | return map(to: Expectation.WrappedType.self) { optional in 13 | guard let wrapped = optional.wrapped else { 14 | throw error() 15 | } 16 | return wrapped 17 | } 18 | } 19 | } 20 | 21 | /// Applies `nil` coalescing to a future's optional and a concrete type. 22 | /// 23 | /// print(maybeFutureInt) // Future? 24 | /// let futureInt = maybeFutureInt ?? 0 25 | /// print(futureInt) // Future 26 | /// 27 | public func ??(lhs: Future, rhs: T) -> Future { 28 | return lhs.map(to: T.self) { value in 29 | return value ?? rhs 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Core/FutureEncoder.swift: -------------------------------------------------------------------------------- 1 | /// An encoder that is capable of handling `Future` encodable objects. 2 | public protocol FutureEncoder: class { 3 | /// Encodes a `Future` object. 4 | func encodeFuture(_ future: Future) throws 5 | where E: Encodable 6 | } 7 | 8 | /// Conforms future to `Encodable` where its expectation is also `Encodable`. 9 | extension Future: Encodable where T: Encodable { 10 | /// See `Encodable`. 11 | public func encode(to encoder: Encoder) throws { 12 | if let encoder = encoder as? FutureEncoder { 13 | try encoder.encodeFuture(self) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Core/HeaderValue.swift: -------------------------------------------------------------------------------- 1 | /// Represents a header value with optional parameter metadata. 2 | /// 3 | /// Parses a header string like `application/json; charset="utf8"`, into: 4 | /// 5 | /// - value: `"application/json"` 6 | /// - parameters: ["charset": "utf8"] 7 | /// 8 | /// Simplified format: 9 | /// 10 | /// headervalue := value *(";" parameter) 11 | /// ; Matching of media type and subtype 12 | /// ; is ALWAYS case-insensitive. 13 | /// 14 | /// value := token 15 | /// 16 | /// parameter := attribute "=" value 17 | /// 18 | /// attribute := token 19 | /// ; Matching of attributes 20 | /// ; is ALWAYS case-insensitive. 21 | /// 22 | /// token := 1* 24 | /// 25 | /// value := token 26 | /// ; token MAY be quoted 27 | /// 28 | /// tspecials := "(" / ")" / "<" / ">" / "@" / 29 | /// "," / ";" / ":" / "\" / <"> 30 | /// "/" / "[" / "]" / "?" / "=" 31 | /// ; Must be in quoted-string, 32 | /// ; to use within parameter values 33 | public struct HeaderValue { 34 | /// Internal storage. 35 | internal let _value: Data 36 | 37 | /// The `HeaderValue`'s main value. 38 | /// 39 | /// In the `HeaderValue` `"application/json; charset=utf8"`: 40 | /// 41 | /// - value: `"application/json"` 42 | public var value: String { 43 | return String(data: _value, encoding: .utf8) ?? "" 44 | } 45 | 46 | /// The `HeaderValue`'s metadata. Zero or more key/value pairs. 47 | /// 48 | /// In the `HeaderValue` `"application/json; charset=utf8"`: 49 | /// 50 | /// - parameters: ["charset": "utf8"] 51 | public var parameters: [CaseInsensitiveString: String] 52 | 53 | /// Creates a new `HeaderValue`. 54 | public init(_ value: LosslessDataConvertible, parameters: [CaseInsensitiveString: String] = [:]) { 55 | self._value = value.convertToData() 56 | self.parameters = parameters 57 | } 58 | 59 | /// Serializes this `HeaderValue` to a `String`. 60 | public func serialize() -> String { 61 | var string = "\(value)" 62 | for (key, val) in parameters { 63 | string += "; \(key)=\"\(val)\"" 64 | } 65 | return string 66 | } 67 | 68 | /// Parse a `HeaderValue` from a `String`. 69 | /// 70 | /// guard let headerValue = HeaderValue.parse("application/json; charset=utf8") else { ... } 71 | /// 72 | public static func parse(_ data: LosslessDataConvertible) -> HeaderValue? { 73 | let data = data.convertToData() 74 | 75 | /// separate the zero or more parameters 76 | let parts = data.split(separator: .semicolon, maxSplits: 1) 77 | 78 | /// there must be at least one part, the value 79 | guard let value = parts.first else { 80 | /// should never hit this 81 | return nil 82 | } 83 | 84 | /// get the remaining parameters string 85 | var remaining: Data 86 | 87 | switch parts.count { 88 | case 1: 89 | /// no parameters, early exit 90 | return HeaderValue(value, parameters: [:]) 91 | case 2: remaining = parts[1] 92 | default: return nil 93 | } 94 | 95 | /// collect all of the parameters 96 | var parameters: [CaseInsensitiveString: String] = [:] 97 | 98 | /// loop over all parts after the value 99 | parse: while remaining.count > 0 { 100 | let semicolon = remaining.index(of: .semicolon) 101 | let equals = remaining.index(of: .equals) 102 | 103 | let key: Data 104 | let val: Data 105 | 106 | if equals == nil || (equals != nil && semicolon != nil && semicolon! < equals!) { 107 | /// parsing a single flag, without = 108 | key = remaining[remaining.startIndex..<(semicolon ?? remaining.endIndex)] 109 | val = .init() 110 | if let s = semicolon { 111 | remaining = remaining[remaining.index(after: s)...] 112 | } else { 113 | remaining = .init() 114 | } 115 | } else { 116 | /// parsing a normal key=value pair. 117 | /// parse the parameters by splitting on the `=` 118 | let parameterParts = remaining.split(separator: .equals, maxSplits: 1) 119 | 120 | key = parameterParts[0] 121 | 122 | switch parameterParts.count { 123 | case 1: 124 | val = .init() 125 | remaining = .init() 126 | case 2: 127 | let trailing = parameterParts[1] 128 | 129 | if trailing.first == .quote { 130 | /// find first unescaped quote 131 | var quoteIndex: Data.Index? 132 | var escapedIndexes: [Data.Index] = [] 133 | findQuote: for i in 1.. 0 { 154 | /// go reverse so that we can correctly remove multiple 155 | for escapeLoc in escapedIndexes.reversed() { 156 | valpart.remove(at: escapeLoc) 157 | } 158 | } 159 | 160 | val = valpart 161 | 162 | let rest = trailing[trailing.index(after: trailing.startIndex)...] 163 | if let nextSemicolon = rest.index(of: .semicolon) { 164 | remaining = rest[rest.index(after: nextSemicolon)...] 165 | } else { 166 | remaining = .init() 167 | } 168 | } else { 169 | /// find first semicolon 170 | var semicolonOffset: Data.Index? 171 | findSemicolon: for i in 0.. Data 5 | 6 | /// Losslessly converts `Data` to this type. 7 | static func convertFromData(_ data: Data) -> Self 8 | } 9 | 10 | extension Data { 11 | /// Converts this `Data` to a `LosslessDataConvertible` type. 12 | /// 13 | /// let string = Data([0x68, 0x69]).convert(to: String.self) 14 | /// print(string) // "hi" 15 | /// 16 | /// - parameters: 17 | /// - type: The `LosslessDataConvertible` to convert to. 18 | /// - returns: Instance of the `LosslessDataConvertible` type. 19 | public func convert(to type: T.Type = T.self) -> T where T: LosslessDataConvertible { 20 | return T.convertFromData(self) 21 | } 22 | } 23 | 24 | extension String: LosslessDataConvertible { 25 | /// Converts this `String` to data using `.utf8`. 26 | public func convertToData() -> Data { 27 | return Data(utf8) 28 | } 29 | 30 | /// Converts `Data` to a `utf8` encoded String. 31 | /// 32 | /// - throws: Error if String is not UTF8 encoded. 33 | public static func convertFromData(_ data: Data) -> String { 34 | guard let string = String(data: data, encoding: .utf8) else { 35 | /// FIXME: string convert _from_ data is not actually lossless. 36 | /// this should really only conform to a `LosslessDataRepresentable` protocol. 37 | return "" 38 | } 39 | return string 40 | } 41 | } 42 | 43 | extension Array: LosslessDataConvertible where Element == UInt8 { 44 | /// Converts this `[UInt8]` to `Data`. 45 | public func convertToData() -> Data { 46 | return Data(bytes: self) 47 | } 48 | 49 | /// Converts `Data` to `[UInt8]`. 50 | public static func convertFromData(_ data: Data) -> Array { 51 | return .init(data) 52 | } 53 | } 54 | 55 | extension Data: LosslessDataConvertible { 56 | /// `LosslessDataConvertible` conformance. 57 | public func convertToData() -> Data { 58 | return self 59 | } 60 | 61 | /// `LosslessDataConvertible` conformance. 62 | public static func convertFromData(_ data: Data) -> Data { 63 | return data 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Core/NestedData.swift: -------------------------------------------------------------------------------- 1 | /// A data structure containing arbitrarily nested arrays and dictionaries. 2 | /// 3 | /// Conforming to this protocol adds two methods to the conforming type for getting and 4 | /// setting the nested data. 5 | /// 6 | /// - `NestedData.get(at:)` 7 | /// - `NestedData.set(to:at:)` 8 | /// 9 | public protocol NestedData { 10 | /// Returns a dictionary representation of `self`, or `nil` if not possible. 11 | var dictionary: [String: Self]? { get } 12 | 13 | /// Returns an array representation of self, or `nil` if not possible. 14 | var array: [Self]? { get } 15 | 16 | /// Creates `self` from a dictionary representation. 17 | static func dictionary(_ value: [String: Self]) -> Self 18 | 19 | /// Creates `self` from an array representation. 20 | static func array(_ value: [Self]) -> Self 21 | } 22 | 23 | extension NestedData { 24 | /// Sets self to the supplied value at a given path. 25 | /// 26 | /// data.set(to: "hello", at: ["path", "to", "value"]) 27 | /// 28 | /// - parameters: 29 | /// - value: Value of `Self` to set at the supplied path. 30 | /// - path: `CodingKey` path to update with the supplied value. 31 | public mutating func set(to value: Self, at path: [CodingKey]) { 32 | set(&self, to: value, at: path) 33 | } 34 | 35 | /// Sets self to the supplied value at a given path. 36 | /// 37 | /// data.get(at: ["path", "to", "value"]) 38 | /// 39 | /// - parameters: 40 | /// - path: `CodingKey` path to fetch the supplied value at. 41 | /// - returns: An instance of `Self` if a value exists at the path, otherwise `nil`. 42 | public func get(at path: [CodingKey]) -> Self? { 43 | var child = self 44 | for seg in path { 45 | if let dictionary = child.dictionary, let c = dictionary[seg.stringValue] { 46 | child = c 47 | } else if let array = child.array, let index = seg.intValue { 48 | child = array[index] 49 | } else { 50 | return nil 51 | } 52 | } 53 | return child 54 | } 55 | 56 | /// Recursive backing method to `set(to:at:)`. 57 | private func set(_ context: inout Self, to value: Self, at path: [CodingKey]) { 58 | guard path.count >= 1 else { 59 | context = value 60 | return 61 | } 62 | 63 | let end = path[0] 64 | var child: Self 65 | switch path.count { 66 | case 1: 67 | child = value 68 | case 2...: 69 | if let index = end.intValue { 70 | let array = context.array ?? [] 71 | if array.count > index { 72 | child = array[index] 73 | } else { 74 | child = .array([]) 75 | } 76 | set(&child, to: value, at: Array(path[1...])) 77 | } else { 78 | child = context.dictionary?[end.stringValue] ?? .dictionary([:]) 79 | set(&child, to: value, at: Array(path[1...])) 80 | } 81 | default: fatalError("Unreachable") 82 | } 83 | 84 | if let index = end.intValue { 85 | if var arr = context.array { 86 | if arr.count > index { 87 | arr[index] = child 88 | } else { 89 | arr.append(child) 90 | } 91 | context = .array(arr) 92 | } else { 93 | context = .array([child]) 94 | } 95 | } else { 96 | if var dict = context.dictionary { 97 | dict[end.stringValue] = child 98 | context = .dictionary(dict) 99 | } else { 100 | context = .dictionary([end.stringValue: child]) 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Sources/Core/NotFound.swift: -------------------------------------------------------------------------------- 1 | /// Generic "not found" error with optional root cause. 2 | /// 3 | /// throw NotFound(rootCause: ...) 4 | /// 5 | public struct NotFound: Error { 6 | /// Underlying error that led to the `NotFound` error being thrown. 7 | public let rootCause: Error? 8 | 9 | /// Creates a new `NotFound` error. 10 | /// 11 | /// throw NotFound(rootCause: ...) 12 | /// 13 | /// - parameters: 14 | /// - rootCause: Underlying error that led to the `NotFound` error being thrown. 15 | public init(rootCause: Error? = nil) { 16 | self.rootCause = rootCause 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Core/OptionalType.swift: -------------------------------------------------------------------------------- 1 | /// Capable of being represented by an optional wrapped type. 2 | /// 3 | /// This protocol mostly exists to allow constrained extensions on generic 4 | /// types where an associatedtype is an `Optional`. 5 | public protocol OptionalType: AnyOptionalType { 6 | /// Underlying wrapped type. 7 | associatedtype WrappedType 8 | 9 | /// Returns the wrapped type, if it exists. 10 | var wrapped: WrappedType? { get } 11 | 12 | /// Creates this optional type from an optional wrapped type. 13 | static func makeOptionalType(_ wrapped: WrappedType?) -> Self 14 | } 15 | 16 | /// Conform concrete optional to `OptionalType`. 17 | /// See `OptionalType` for more information. 18 | extension Optional: OptionalType { 19 | /// See `OptionalType.WrappedType` 20 | public typealias WrappedType = Wrapped 21 | 22 | /// See `OptionalType.wrapped` 23 | public var wrapped: Wrapped? { 24 | switch self { 25 | case .none: return nil 26 | case .some(let w): return w 27 | } 28 | } 29 | 30 | /// See `OptionalType.makeOptionalType` 31 | public static func makeOptionalType(_ wrapped: Wrapped?) -> Optional { 32 | return wrapped 33 | } 34 | } 35 | 36 | /// Type-erased `OptionalType` 37 | public protocol AnyOptionalType { 38 | /// Returns the wrapped type, if it exists. 39 | var anyWrapped: Any? { get } 40 | 41 | /// Returns the wrapped type, if it exists. 42 | static var anyWrappedType: Any.Type { get } 43 | } 44 | 45 | extension AnyOptionalType where Self: OptionalType { 46 | /// See `AnyOptionalType.anyWrapped` 47 | public var anyWrapped: Any? { return wrapped } 48 | 49 | /// See `AnyOptionalType.anyWrappedType` 50 | public static var anyWrappedType: Any.Type { return WrappedType.self } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/Core/Process+Execute.swift: -------------------------------------------------------------------------------- 1 | #if !os(iOS) 2 | import NIO 3 | 4 | /// Different types of process output. 5 | public enum ProcessOutput { 6 | /// Standard process output. 7 | case stdout(Data) 8 | 9 | /// Standard process error output. 10 | case stderr(Data) 11 | } 12 | 13 | extension Process { 14 | /// Executes the supplied program in a new process, blocking until the process completes. 15 | /// Any data piped to `stdout` during the process will be returned as a string. 16 | /// If the process exits with a non-zero status code, an error will be thrown containing 17 | /// the contents of `stderr` and `stdout`. 18 | /// 19 | /// let result = try Process.execute("echo", "hi") 20 | /// print(result) /// "hi" 21 | /// 22 | /// - parameters: 23 | /// - program: The name of the program to execute. If it does not begin with a `/`, the full 24 | /// path will be resolved using `/bin/sh -c which ...`. 25 | /// - arguments: An array of arguments to pass to the program. 26 | public static func execute(_ program: String, _ arguments: String...) throws -> String { 27 | return try execute(program, arguments) 28 | } 29 | 30 | /// Executes the supplied program in a new process, blocking until the process completes. 31 | /// Any data piped to `stdout` during the process will be returned as a string. 32 | /// If the process exits with a non-zero status code, an error will be thrown containing 33 | /// the contents of `stderr` and `stdout`. 34 | /// 35 | /// let result = try Process.execute("echo", "hi") 36 | /// print(result) /// "hi" 37 | /// 38 | /// - parameters: 39 | /// - program: The name of the program to execute. If it does not begin with a `/`, the full 40 | /// path will be resolved using `/bin/sh -c which ...`. 41 | /// - arguments: An array of arguments to pass to the program. 42 | public static func execute(_ program: String, _ arguments: [String]) throws -> String { 43 | var stderr: String = "" 44 | var stdout: String = "" 45 | let status = try asyncExecute(program, arguments, on: EmbeddedEventLoop()) { output in 46 | switch output { 47 | case .stderr(let data): 48 | stderr += String(data: data, encoding: .utf8) ?? "" 49 | case .stdout(let data): 50 | stdout += String(data: data, encoding: .utf8) ?? "" 51 | } 52 | }.wait() 53 | if status != 0 { 54 | throw ProcessExecuteError(status: status, stderr: stderr, stdout: stdout) 55 | } 56 | return stdout.trimmingCharacters(in: .whitespacesAndNewlines) 57 | } 58 | 59 | /// Asynchronously the supplied program in a new process. Stderr and stdout will be supplied to the output closure 60 | /// as it is received. The returned future will finish when the process has terminated. 61 | /// 62 | /// let status = try Process.asyncExecute("echo", "hi", on: ...) { output in 63 | /// print(output) 64 | /// }.wait() 65 | /// print(result) // 0 66 | /// 67 | /// - parameters: 68 | /// - program: The name of the program to execute. If it does not begin with a `/`, the full 69 | /// path will be resolved using `/bin/sh -c which ...`. 70 | /// - arguments: An array of arguments to pass to the program. 71 | /// - worker: Worker to perform async task on. 72 | /// - output: Handler for the process output. 73 | /// - returns: A future containing the termination status of the process. 74 | public static func asyncExecute(_ program: String, _ arguments: String..., on worker: Worker, _ output: @escaping (ProcessOutput) -> ()) -> Future { 75 | return asyncExecute(program, arguments, on: worker, output) 76 | } 77 | 78 | /// Asynchronously the supplied program in a new process. Stderr and stdout will be supplied to the output closure 79 | /// as it is received. The returned future will finish when the process has terminated. 80 | /// 81 | /// let status = try Process.asyncExecute("echo", ["hi"], on: ...) { output in 82 | /// print(output) 83 | /// }.wait() 84 | /// print(result) // 0 85 | /// 86 | /// - parameters: 87 | /// - program: The name of the program to execute. If it does not begin with a `/`, the full 88 | /// path will be resolved using `/bin/sh -c which ...`. 89 | /// - arguments: An array of arguments to pass to the program. 90 | /// - worker: Worker to perform async task on. 91 | /// - output: Handler for the process output. 92 | /// - returns: A future containing the termination status of the process. 93 | public static func asyncExecute(_ program: String, _ arguments: [String], on worker: Worker, _ output: @escaping (ProcessOutput) -> ()) -> Future { 94 | if program.hasPrefix("/") { 95 | let stdout = Pipe() 96 | let stderr = Pipe() 97 | 98 | // will be set to false when the program is done 99 | var running = true 100 | 101 | // readabilityHandler doesn't work on linux, so we are left with this hack 102 | DispatchQueue.global().async { 103 | while running { 104 | let stdout = stdout.fileHandleForReading.availableData 105 | if !stdout.isEmpty { 106 | output(.stdout(stdout)) 107 | } 108 | } 109 | } 110 | DispatchQueue.global().async { 111 | while running { 112 | let stderr = stderr.fileHandleForReading.availableData 113 | if !stderr.isEmpty { 114 | output(.stderr(stderr)) 115 | } 116 | } 117 | } 118 | 119 | // stdout.fileHandleForReading.readabilityHandler = { handle in 120 | // let data = handle.availableData 121 | // guard !data.isEmpty else { 122 | // return 123 | // } 124 | // output(.stdout(data)) 125 | // } 126 | // stderr.fileHandleForReading.readabilityHandler = { handle in 127 | // let data = handle.availableData 128 | // guard !data.isEmpty else { 129 | // return 130 | // } 131 | // output(.stderr(data)) 132 | // } 133 | 134 | let promise = worker.eventLoop.newPromise(Int32.self) 135 | DispatchQueue.global().async { 136 | let process = launchProcess(path: program, arguments, stdout: stdout, stderr: stderr) 137 | process.waitUntilExit() 138 | running = false 139 | promise.succeed(result: process.terminationStatus) 140 | } 141 | return promise.futureResult 142 | } else { 143 | var resolvedPath: String? 144 | return asyncExecute("/bin/sh", ["-c", "which \(program)"], on: worker) { o in 145 | switch o { 146 | case .stdout(let data): resolvedPath = String(data: data, encoding: .utf8)? 147 | .trimmingCharacters(in: .whitespacesAndNewlines) 148 | default: break 149 | } 150 | }.flatMap { status in 151 | guard let path = resolvedPath, path.hasPrefix("/") else { 152 | throw CoreError(identifier: "executablePath", reason: "Could not find executable path for program: \(program).") 153 | } 154 | return asyncExecute(path, arguments, on: worker, output) 155 | } 156 | } 157 | } 158 | 159 | /// Powers `Process.execute(_:_:)` methods. Separated so that `/bin/sh -c which` can run as a separate command. 160 | private static func launchProcess(path: String, _ arguments: [String], stdout: Pipe, stderr: Pipe) -> Process { 161 | let process = Process() 162 | process.environment = ProcessInfo.processInfo.environment 163 | process.launchPath = path 164 | process.arguments = arguments 165 | process.standardOutput = stdout 166 | process.standardError = stderr 167 | process.launch() 168 | return process 169 | } 170 | } 171 | 172 | /// An error that can be thrown while using `Process.execute(_:_:)` 173 | public struct ProcessExecuteError: Error { 174 | /// The exit status 175 | public let status: Int32 176 | 177 | /// Contents of `stderr` 178 | public var stderr: String 179 | 180 | /// Contents of `stdout` 181 | public var stdout: String 182 | } 183 | 184 | extension ProcessExecuteError: Debuggable { 185 | /// See `Debuggable.identifier`. 186 | public var identifier: String { 187 | return status.description 188 | } 189 | 190 | /// See `Debuggable.reason` 191 | public var reason: String { 192 | return stderr 193 | } 194 | } 195 | 196 | #endif 197 | -------------------------------------------------------------------------------- /Sources/Core/Reflectable.swift: -------------------------------------------------------------------------------- 1 | /// This protocol allows for reflection of properties on conforming types. 2 | /// 3 | /// Ideally Swift type mirroring would handle this completely. In the interim, this protocol 4 | /// acts to fill in the missing gaps. 5 | /// 6 | /// struct Pet: Decodable { 7 | /// var name: String 8 | /// var age: Int 9 | /// } 10 | /// 11 | /// struct User: Reflectable, Decodable { 12 | /// var id: UUID? 13 | /// var name: String 14 | /// var pet: Pet 15 | /// } 16 | /// 17 | /// try User.reflectProperties(depth: 0) // [id: UUID?, name: String, pet: Pet] 18 | /// try User.reflectProperties(depth: 1) // [pet.name: String, pet.age: Int] 19 | /// try User.reflectProperty(forKey: \.name) // ["name"] String 20 | /// try User.reflectProperty(forKey: \.pet.name) // ["pet", "name"] String 21 | /// 22 | /// Types that conform to this protocol and are also `Decodable` will get the implementations for free 23 | /// using a decoder to discover the type's structure. 24 | /// 25 | /// Any type can conform to `Reflectable` by implementing its two static methods. 26 | /// 27 | /// struct User: Reflectable { 28 | /// var firstName: String 29 | /// var lastName: String 30 | /// 31 | /// static func reflectProperties(depth: Int) throws -> [ReflectedProperty] { 32 | /// guard depth == 0 else { return [] } // this type only has properties at depth 0 33 | /// return [.init(String.self, at: ["first_name"]), .init(String.self, at: ["last_name"])] 34 | /// } 35 | /// 36 | /// static func reflectProperty(forKey keyPath: KeyPath) throws -> ReflectedProperty? { 37 | /// let key: String 38 | /// switch keyPath { 39 | /// case \User.firstName: key = "first_name" 40 | /// case \User.lastName: key = "last_name" 41 | /// default: return nil 42 | /// } 43 | /// return .init(T.self, at: [key]) 44 | /// } 45 | /// } 46 | /// 47 | /// Even if your type gets the default implementation for being `Decodable`, you can still override both 48 | /// the `reflectProperties(depth:)` and `reflectProperty(forKey:)` methods. 49 | public protocol Reflectable: AnyReflectable { 50 | 51 | /// Returns a `ReflectedProperty` for the supplied key path. 52 | /// 53 | /// struct Pet: Decodable { 54 | /// var name: String 55 | /// var age: Int 56 | /// } 57 | /// 58 | /// struct User: Reflectable, Decodable { 59 | /// var id: UUID? 60 | /// var name: String 61 | /// var pet: Pet 62 | /// } 63 | /// 64 | /// try User.reflectProperty(forKey: \.name) // ["name"] String 65 | /// try User.reflectProperty(forKey: \.pet.name) // ["pet", "name"] String 66 | /// 67 | /// - parameters: 68 | /// - keyPath: `KeyPath` to reflect a property for. 69 | /// - throws: Any error reflecting this property. 70 | /// - returns: `ReflectedProperty` if one was found. 71 | static func reflectProperty(forKey keyPath: KeyPath) throws -> ReflectedProperty? 72 | } 73 | 74 | extension Reflectable { 75 | /// Reflects all of this type's `ReflectedProperty`s. 76 | public static func reflectProperties() throws -> [ReflectedProperty] { 77 | return try reflectProperties(depth: 0) 78 | } 79 | 80 | /// See `Reflectable`. 81 | public static func reflectProperty(forKey keyPath: KeyPath) throws -> ReflectedProperty? { 82 | return try anyReflectProperty(valueType: T.self, keyPath: keyPath) 83 | } 84 | } 85 | 86 | /// Type-erased `Reflectable`. 87 | public protocol AnyReflectable { 88 | /// Reflects all of this type's `ReflectedProperty`s. 89 | /// 90 | /// struct Pet: Decodable { 91 | /// var name: String 92 | /// var age: Int 93 | /// } 94 | /// 95 | /// struct User: Reflectable, Decodable { 96 | /// var id: UUID? 97 | /// var name: String 98 | /// var pet: Pet 99 | /// } 100 | /// 101 | /// try User.reflectProperties(depth: 0) // [id: UUID?, name: String, pet: Pet] 102 | /// try User.reflectProperties(depth: 1) // [pet.name: String, pet.age: Int] 103 | /// 104 | /// - parameters: 105 | /// - depth: The level of nesting to use. 106 | /// If `0`, the top-most properties will be returned. 107 | /// If `1`, the first layer of nested properties, and so-on. 108 | /// - throws: Any error reflecting this type's properties. 109 | /// - returns: All `ReflectedProperty`s at the specified depth. 110 | static func reflectProperties(depth: Int) throws -> [ReflectedProperty] 111 | 112 | /// Returns a `ReflectedProperty` for the supplied key path. Use the non-type erased version on 113 | /// `Reflectable` wherever possible. 114 | /// 115 | /// struct Pet: Decodable { 116 | /// var name: String 117 | /// var age: Int 118 | /// } 119 | /// 120 | /// struct User: Reflectable, Decodable { 121 | /// var id: UUID? 122 | /// var name: String 123 | /// var pet: Pet 124 | /// } 125 | /// 126 | /// try User.anyReflectProperty(valueType: String.self, keyPath: \User.name) // ["name"] String 127 | /// try User.anyReflectProperty(valueType: String.self, keyPath: \User.pet.name) // ["pet", "name"] String 128 | /// 129 | /// - parameters: 130 | /// - valueType: Value type of the key path. 131 | /// - keyPath: `AnyKeyPath` to reflect a property for. 132 | /// - throws: Any error reflecting this property. 133 | /// - returns: `ReflectedProperty` if one was found. 134 | static func anyReflectProperty(valueType: Any.Type, keyPath: AnyKeyPath) throws -> ReflectedProperty? 135 | } 136 | 137 | /// Represents a property on a type that has been reflected using the `Reflectable` protocol. 138 | /// 139 | /// let property = try User.reflectProperty(forKey: \.pet.name) 140 | /// print(property) // ["pet", "name"] String 141 | /// 142 | public struct ReflectedProperty { 143 | /// This property's type. 144 | public let type: Any.Type 145 | 146 | /// The path to this property. 147 | public let path: [String] 148 | 149 | /// Creates a new `ReflectedProperty` from a type and path. 150 | public init(_ type: T.Type, at path: [String]) { 151 | self.type = T.self 152 | self.path = path 153 | } 154 | 155 | /// Creates a new `ReflectedProperty` using `Any.Type` and a path. 156 | public init(any type: Any.Type, at path: [String]) { 157 | self.type = type 158 | self.path = path 159 | } 160 | } 161 | 162 | extension Collection where Element == ReflectedProperty { 163 | /// Removes all optional properties from an array of `ReflectedProperty`. 164 | public func optionalsRemoved() -> [ReflectedProperty] { 165 | return filter { !($0.type is AnyOptionalType.Type) } 166 | } 167 | } 168 | 169 | extension ReflectedProperty: CustomStringConvertible { 170 | /// See `CustomStringConvertible.description` 171 | public var description: String { 172 | return "\(path.joined(separator: ".")): \(type)" 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Sources/Core/String+Utilities.swift: -------------------------------------------------------------------------------- 1 | extension String { 2 | /// Converts the string to a `Bool` or returns `nil`. 3 | public var bool: Bool? { 4 | switch self { 5 | case "true", "yes", "1", "y": return true 6 | case "false", "no", "0", "n": return false 7 | default: return nil 8 | } 9 | } 10 | } 11 | 12 | extension String { 13 | /// Ensures a string has a trailing suffix w/o duplicating 14 | /// 15 | /// "hello.jpg".finished(with: ".jpg") // hello.jpg 16 | /// "hello".finished(with: ".jpg") // hello.jpg 17 | /// 18 | public func finished(with end: String) -> String { 19 | guard !self.hasSuffix(end) else { return self } 20 | return self + end 21 | } 22 | } 23 | 24 | extension UUID: LosslessStringConvertible { 25 | /// See `LosslessStringConvertible`. 26 | public init?(_ string: String) { 27 | self.init(uuidString: string) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Core/Thread+Async.swift: -------------------------------------------------------------------------------- 1 | extension Thread { 2 | /// Runs the supplied closure on a new thread. Calls `Thread.detachNewThread(_:)`. 3 | /// 4 | /// Thread.async { 5 | /// sleep(1) 6 | /// print("world!") 7 | /// } 8 | /// print("Hello, ", terminator: "") 9 | /// 10 | /// The above snippet will output: 11 | /// 12 | /// Hello, world! 13 | /// 14 | /// - warning: This method will call `fatalError(_:)` on macOS < 10.12. 15 | /// 16 | /// Once the work inside the closure has completed, the thread will exit automatically. 17 | /// 18 | /// - parameters: 19 | /// - work: Closure to be called on new thread. 20 | public static func async(_ work: @escaping () -> Void) { 21 | if #available(macOS 10.12, iOS 10.0, *) { 22 | Thread.detachNewThread(work) 23 | } else { 24 | fatalError("macOS 10.12/iOS 10.0 or later required to call Thread.async(_:)") 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Debugging/Debuggable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// `Debuggable` provides an interface that allows a type 4 | /// to be more easily debugged in the case of an error. 5 | public protocol Debuggable: CustomDebugStringConvertible, CustomStringConvertible, LocalizedError { 6 | /// A readable name for the error's Type. This is usually 7 | /// similar to the Type name of the error with spaces added. 8 | /// This will normally be printed proceeding the error's reason. 9 | /// - note: For example, an error named `FooError` will have the 10 | /// `readableName` `"Foo Error"`. 11 | static var readableName: String { get } 12 | 13 | /// A unique identifier for the error's Type. 14 | /// - note: This defaults to `ModuleName.TypeName`, 15 | /// and is used to create the `identifier` property. 16 | static var typeIdentifier: String { get } 17 | 18 | /// Some unique identifier for this specific error. 19 | /// This will be used to create the `identifier` property. 20 | /// Do NOT use `String(reflecting: self)` or `String(describing: self)` 21 | /// or there will be infinite recursion 22 | var identifier: String { get } 23 | 24 | /// The reason for the error. Usually one sentence (that should end with a period). 25 | var reason: String { get } 26 | 27 | /// Optional source location for this error 28 | var sourceLocation: SourceLocation? { get } 29 | 30 | /// Stack trace from which this error originated (must set this from the error's init) 31 | var stackTrace: [String]? { get } 32 | 33 | /// A `String` array describing the possible causes of the error. 34 | /// - note: Defaults to an empty array. 35 | /// Provide a custom implementation to give more context. 36 | var possibleCauses: [String] { get } 37 | 38 | /// A `String` array listing some common fixes for the error. 39 | /// - note: Defaults to an empty array. 40 | /// Provide a custom implementation to be more helpful. 41 | var suggestedFixes: [String] { get } 42 | 43 | /// An array of string `URL`s linking to documentation pertaining to the error. 44 | /// - note: Defaults to an empty array. 45 | /// Provide a custom implementation with relevant links. 46 | var documentationLinks: [String] { get } 47 | 48 | /// An array of string `URL`s linking to related Stack Overflow questions. 49 | /// - note: Defaults to an empty array. 50 | /// Provide a custom implementation to link to useful questions. 51 | var stackOverflowQuestions: [String] { get } 52 | 53 | /// An array of string `URL`s linking to related issues on Vapor's GitHub repo. 54 | /// - note: Defaults to an empty array. 55 | /// Provide a custom implementation to a list of pertinent issues. 56 | var gitHubIssues: [String] { get } 57 | } 58 | 59 | 60 | /// MARK: Computed 61 | 62 | extension Debuggable { 63 | /// Generates a stack trace from the call point. Must call this from the error's init. 64 | public static func makeStackTrace() -> [String] { 65 | return Thread.callStackSymbols 66 | } 67 | } 68 | 69 | extension Debuggable { 70 | public var fullIdentifier: String { 71 | return Self.typeIdentifier + "." + identifier 72 | } 73 | } 74 | 75 | // MARK: Defaults 76 | 77 | extension Debuggable { 78 | /// See `Debuggable` 79 | public static var readableName: String { 80 | return typeIdentifier 81 | } 82 | 83 | /// See `Debuggable` 84 | public static var typeIdentifier: String { 85 | let type = "\(self)" 86 | return type.split(separator: ".").last.flatMap(String.init) ?? type 87 | } 88 | 89 | /// See `Debuggable` 90 | public var possibleCauses: [String] { 91 | return [] 92 | } 93 | 94 | /// See `Debuggable` 95 | public var suggestedFixes: [String] { 96 | return [] 97 | } 98 | 99 | /// See `Debuggable` 100 | public var documentationLinks: [String] { 101 | return [] 102 | } 103 | 104 | /// See `Debuggable` 105 | public var stackOverflowQuestions: [String] { 106 | return [] 107 | } 108 | 109 | /// See `Debuggable` 110 | public var gitHubIssues: [String] { 111 | return [] 112 | } 113 | 114 | /// See `Debuggable` 115 | public var sourceLocation: SourceLocation? { 116 | return nil 117 | } 118 | 119 | /// See `Debuggable` 120 | public var stackTrace: [String]? { 121 | return nil 122 | } 123 | } 124 | 125 | /// MARK: Custom...StringConvertible 126 | 127 | extension Debuggable { 128 | /// See `CustomDebugStringConvertible` 129 | public var debugDescription: String { 130 | return debuggableHelp(format: .long) 131 | } 132 | 133 | /// See `CustomStringConvertible` 134 | public var description: String { 135 | return debuggableHelp(format: .short) 136 | } 137 | } 138 | 139 | // MARK: Localized 140 | 141 | extension Debuggable { 142 | /// A localized message describing what error occurred. 143 | public var errorDescription: String? { return description } 144 | 145 | /// A localized message describing the reason for the failure. 146 | public var failureReason: String? { return reason } 147 | 148 | /// A localized message describing how one might recover from the failure. 149 | public var recoverySuggestion: String? { return suggestedFixes.first } 150 | 151 | /// A localized message providing "help" text if the user requests help. 152 | public var helpAnchor: String? { return documentationLinks.first } 153 | } 154 | 155 | 156 | // MARK: Representations 157 | 158 | /// Available formatting options for generating debug info for `Debuggable` errors. 159 | public enum HelpFormat { 160 | case short 161 | case long 162 | } 163 | 164 | extension Debuggable { 165 | /// A computed property returning a `String` that encapsulates why the error occurred, suggestions on how to 166 | /// fix the problem, and resources to consult in debugging (if these are available). 167 | /// - note: This representation is best used with functions like print() 168 | public func debuggableHelp(format: HelpFormat) -> String { 169 | var print: [String] = [] 170 | 171 | switch format { 172 | case .long: 173 | print.append("⚠️ \(Self.readableName): \(reason)\n- id: \(fullIdentifier)") 174 | case .short: 175 | print.append("⚠️ [\(fullIdentifier): \(reason)]") 176 | } 177 | 178 | if let source = sourceLocation { 179 | switch format { 180 | case .long: 181 | var help: [String] = [] 182 | help.append("File: \(source.file)") 183 | help.append(" - func: \(source.function)") 184 | help.append(" - line: \(source.line)") 185 | help.append(" - column: \(source.column)") 186 | if let range = source.range { 187 | help.append("- range: \(range)") 188 | } 189 | print.append(help.joined(separator: "\n")) 190 | case .short: 191 | var string = "[\(source.file):\(source.line):\(source.column)" 192 | if let range = source.range { 193 | string += " (\(range))" 194 | } 195 | string += "]" 196 | print.append(string) 197 | } 198 | } 199 | 200 | switch format { 201 | case .long: 202 | if !possibleCauses.isEmpty { 203 | print.append("Here are some possible causes: \(possibleCauses.bulletedList)") 204 | } 205 | 206 | if !suggestedFixes.isEmpty { 207 | print.append("These suggestions could address the issue: \(suggestedFixes.bulletedList)") 208 | } 209 | 210 | if !documentationLinks.isEmpty { 211 | print.append("Vapor's documentation talks about this: \(documentationLinks.bulletedList)") 212 | } 213 | 214 | if !stackOverflowQuestions.isEmpty { 215 | print.append("These Stack Overflow links might be helpful: \(stackOverflowQuestions.bulletedList)") 216 | } 217 | 218 | if !gitHubIssues.isEmpty { 219 | print.append("See these Github issues for discussion on this topic: \(gitHubIssues.bulletedList)") 220 | } 221 | case .short: 222 | if possibleCauses.count > 0 { 223 | print.append("[Possible causes: \(possibleCauses.joined(separator: " "))]") 224 | } 225 | if suggestedFixes.count > 0 { 226 | print.append("[Suggested fixes: \(suggestedFixes.joined(separator: " "))]") 227 | } 228 | } 229 | 230 | switch format { 231 | case .long: 232 | return print.joined(separator: "\n\n") + "\n" 233 | case .short: 234 | return print.joined(separator: " ") 235 | } 236 | } 237 | } 238 | 239 | 240 | extension Sequence where Iterator.Element == String { 241 | var bulletedList: String { 242 | return map { "\n- \($0)" } .joined() 243 | } 244 | } 245 | 246 | -------------------------------------------------------------------------------- /Sources/Debugging/Demangler.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Here be dragons! _stdlib_demangleImpl is linked into the stdlib. Use at your own risk! 4 | 5 | @_silgen_name("swift_demangle") 6 | public 7 | func _stdlib_demangleImpl( 8 | mangledName: UnsafePointer?, 9 | mangledNameLength: UInt, 10 | outputBuffer: UnsafeMutablePointer?, 11 | outputBufferSize: UnsafeMutablePointer?, 12 | flags: UInt32 13 | ) -> UnsafeMutablePointer? 14 | 15 | func _stdlib_demangleName(_ mangledName: String) -> String { 16 | return mangledName.utf8CString.withUnsafeBufferPointer { 17 | (mangledNameUTF8CStr) in 18 | 19 | let demangledNamePtr = _stdlib_demangleImpl( 20 | mangledName: mangledNameUTF8CStr.baseAddress, 21 | mangledNameLength: UInt(mangledNameUTF8CStr.count - 1), 22 | outputBuffer: nil, 23 | outputBufferSize: nil, 24 | flags: 0) 25 | 26 | if let demangledNamePtr = demangledNamePtr { 27 | let demangledName = String(cString: demangledNamePtr) 28 | free(demangledNamePtr) 29 | return demangledName 30 | } 31 | return mangledName 32 | } 33 | } 34 | 35 | /// backtrace is included on macOS and Linux, with the same ABI. 36 | @_silgen_name("backtrace") 37 | func backtrace(_: UnsafeMutablePointer!, _: UInt32) -> UInt32 38 | 39 | -------------------------------------------------------------------------------- /Sources/Debugging/SourceLocation.swift: -------------------------------------------------------------------------------- 1 | /// A source-code location. 2 | public struct SourceLocation { 3 | /// File in which this location exists. 4 | public var file: String 5 | 6 | /// Function in which this location exists. 7 | public var function: String 8 | 9 | /// Line number this location belongs to. 10 | public var line: UInt 11 | 12 | /// Number of characters into the line this location starts at. 13 | public var column: UInt 14 | 15 | /// Optional start/end range of the source. 16 | public var range: Range? 17 | 18 | /// Creates a new `SourceLocation` 19 | public init(file: String, function: String, line: UInt, column: UInt, range: Range?) { 20 | self.file = file 21 | self.function = function 22 | self.line = line 23 | self.column = column 24 | self.range = range 25 | } 26 | } 27 | 28 | extension SourceLocation { 29 | /// Creates a new `SourceLocation` for the current call site. 30 | public static func capture( 31 | file: String = #file, 32 | function: String = #function, 33 | line: UInt = #line, 34 | column: UInt = #column, 35 | range: Range? = nil 36 | ) -> SourceLocation { 37 | return SourceLocation(file: file, function: function, line: line, column: column, range: range) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/AsyncTests/AsyncTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Async 2 | import XCTest 3 | 4 | final class AsyncTests: XCTestCase { 5 | let worker: Worker = EmbeddedEventLoop() 6 | func testVariadicMap() throws { 7 | let futureA = Future.map(on: worker) { "a" } 8 | let futureB = Future.map(on: worker) { "b" } 9 | let futureAB = map(to: String.self, futureA, futureB) { a, b in 10 | return "\(a)\(b)" 11 | } 12 | try XCTAssertEqual(futureAB.wait(), "ab") 13 | } 14 | 15 | func testFlatten() throws { 16 | let loop = EmbeddedEventLoop() 17 | let a = loop.newPromise(String.self) 18 | let b = loop.newPromise(String.self) 19 | let c = loop.newPromise(String.self) 20 | let arr: [Future] = [a.futureResult, b.futureResult, c.futureResult] 21 | let flat = arr.flatten(on: loop) 22 | b.succeed(result: "b") 23 | a.succeed(result: "a") 24 | c.succeed(result: "c") 25 | try XCTAssertEqual(flat.wait(), ["a", "b", "c"]) 26 | } 27 | 28 | /// Stress test flatten 29 | /// If this test fails, this might indicate a threading issue 30 | func testFlattenStress() throws { 31 | let loopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) 32 | let timeoutLoop = MultiThreadedEventLoopGroup(numberOfThreads: 1) 33 | let futureCount = 1000 * System.coreCount 34 | let expectedResult = (0..]() 38 | for _ in 0..]() 82 | let n = 10 83 | 84 | var completedOrder = [Int]() 85 | let completionQueue = DispatchQueue(label: "testSyncFlattenQueue") 86 | 87 | for i in 0..] = [] 114 | let count = 1<<12 115 | for i in 0..] = [a.futureResult, b.futureResult] 126 | a.succeed(result: "a") 127 | b.fail(error: "b") 128 | XCTAssertThrowsError(try arr.flatten(on: loop).wait()) { XCTAssert($0 is String) } 129 | } 130 | 131 | func testFlattenEmpty() throws { 132 | let loop = EmbeddedEventLoop() 133 | let arr: [Future] = [] 134 | try XCTAssertEqual(arr.flatten(on: loop).wait().count, 0) 135 | } 136 | 137 | func doRecursive(n: Int, eventLoop: EventLoop) -> EventLoopFuture { 138 | guard n != 0 else { 139 | return eventLoop.newSucceededFuture(result: ()) 140 | } 141 | return eventLoop.submit { } 142 | .flatMap { 143 | self.doRecursive(n: n - 1, eventLoop: eventLoop) 144 | } 145 | } 146 | 147 | func testFlatMapDoesNotCauseStackOverflowWhenFinishing() throws { 148 | let loopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) 149 | try doRecursive(n: 10_000, eventLoop: loopGroup.next()).wait() 150 | try loopGroup.syncShutdownGracefully() 151 | } 152 | 153 | func testFlatMapPerformance() throws { 154 | let loopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) 155 | measure { 156 | for _ in 0..<1000 { 157 | try! doRecursive(n: 100, eventLoop: loopGroup.next()).wait() 158 | } 159 | } 160 | try loopGroup.syncShutdownGracefully() 161 | } 162 | 163 | static let allTests = [ 164 | ("testVariadicMap", testVariadicMap), 165 | ("testFlatten", testFlatten), 166 | ("testSyncFlatten", testSyncFlatten), 167 | ("testFlattenStackOverflow", testFlattenStackOverflow), 168 | ("testFlattenFail", testFlattenFail), 169 | ("testFlattenEmpty", testFlattenEmpty), 170 | ("testFlattenStress", testFlattenStress), 171 | ("testFlattenPerformance", testFlattenPerformance), 172 | ("testFlatMapPerformance", testFlatMapPerformance), 173 | ("testFlatMapDoesNotCauseStackOverflowWhenFinishing", testFlatMapDoesNotCauseStackOverflowWhenFinishing) 174 | ] 175 | } 176 | 177 | extension String: Error { } 178 | -------------------------------------------------------------------------------- /Tests/BitsTests/ByteBufferPeekTests.swift: -------------------------------------------------------------------------------- 1 | import NIO 2 | import XCTest 3 | @testable import Bits 4 | 5 | final class ByteBufferPeekTests: XCTestCase { 6 | private let allocator = ByteBufferAllocator() 7 | private var buf: ByteBuffer! = nil 8 | 9 | func testPeekFixedWidthInteger() { 10 | buf = allocator.buffer(capacity: 32) 11 | let first: Int = 1 12 | let second: Int = 3 13 | buf.write(integer: first) 14 | buf.write(integer: second) 15 | XCTAssertEqual(buf.peekInteger(skipping: 0), first) 16 | XCTAssertEqual(buf.peekInteger(skipping: MemoryLayout.size), second) 17 | } 18 | 19 | func testPeekString() { 20 | buf = allocator.buffer(capacity: 256) 21 | let first = "My String" 22 | let second = "My other string" 23 | 24 | buf.write(string: first) 25 | buf.write(string: second) 26 | 27 | XCTAssertEqual(buf.peekString(length: first.count, encoding: .utf8), first) 28 | XCTAssertEqual(buf.peekString(length: second.count, 29 | skipping: first.count, 30 | encoding: .utf8), second) 31 | 32 | _ = buf.readBytes(length: first.count) 33 | XCTAssertEqual(buf.peekString(length: second.count, 34 | encoding: .utf8), second) 35 | } 36 | 37 | func testPeekData() { 38 | buf = allocator.buffer(capacity: 256) 39 | let first = Array("My String".utf8) 40 | let second = Array("My other string".utf8) 41 | 42 | buf.write(bytes: first) 43 | buf.write(bytes: second) 44 | 45 | XCTAssertEqual(buf.peekData(length: first.count), Data(bytes: first)) 46 | XCTAssertEqual(buf.peekData(length: second.count, 47 | skipping: first.count), Data(bytes: second)) 48 | 49 | _ = buf.readBytes(length: first.count) 50 | XCTAssertEqual(buf.peekData(length: second.count), Data(bytes: second)) 51 | } 52 | 53 | func testPeekBinaryFloatingPoint() throws { 54 | buf = allocator.buffer(capacity: 32) 55 | let first: Double = 9.42 56 | let second: Float = 9.43212 57 | 58 | buf.write(floatingPoint: first) 59 | buf.write(floatingPoint: second) 60 | 61 | XCTAssertEqual(buf.peekFloatingPoint(as: Double.self), first) 62 | XCTAssertEqual(buf.peekFloatingPoint(skipping: MemoryLayout.size, 63 | as: Float.self), second) 64 | 65 | 66 | XCTAssertEqual(try buf.requireReadFloatingPoint(as: Double.self), first) 67 | 68 | XCTAssertEqual(buf.peekFloatingPoint(as: Float.self), second) 69 | } 70 | 71 | func testPeekBytes() { 72 | buf = allocator.buffer(capacity: 128) 73 | let byte1 = UInt8(1) 74 | let byte2 = UInt8(2) 75 | buf.write(bytes: [byte1, byte2]) 76 | 77 | let peekedBytes = buf.peekBytes(count: 2) 78 | XCTAssertEqual(peekedBytes?.first, byte1) 79 | XCTAssertEqual(peekedBytes?.last, byte2) 80 | XCTAssertEqual(buf.readBytes(length: 1)?.first, byte1) 81 | XCTAssertEqual(buf.peekBytes(count: 1)?.first, byte2) 82 | XCTAssertEqual(buf.peekBytes(count: 2), nil) 83 | } 84 | 85 | func testPeekFirstByte() { 86 | buf = allocator.buffer(capacity: 128) 87 | let byte1 = UInt8(1) 88 | let byte2 = UInt8(2) 89 | buf.write(bytes: [byte1, byte2]) 90 | 91 | XCTAssertEqual(buf.peekInteger(as: Byte.self), byte1) 92 | XCTAssertEqual(buf.readBytes(length: 1)?.first, byte1) 93 | XCTAssertEqual(buf.peekInteger(as: Byte.self), byte2) 94 | 95 | XCTAssertEqual(buf.readBytes(length: 1)?.first, byte2) 96 | XCTAssertEqual(buf.peekInteger(as: Byte.self), nil) 97 | } 98 | 99 | static let allTests = [ 100 | ("testPeekFixedWidthInteger", testPeekFixedWidthInteger), 101 | ("testPeekString", testPeekString), 102 | ("testPeekData", testPeekData), 103 | ("testPeekBinaryFloatingPoint", testPeekBinaryFloatingPoint), 104 | ] 105 | } 106 | -------------------------------------------------------------------------------- /Tests/BitsTests/ByteBufferRequireTests.swift: -------------------------------------------------------------------------------- 1 | import NIO 2 | import XCTest 3 | @testable import Bits 4 | 5 | final class ByteBufferRequireTests: XCTestCase { 6 | private let allocator = ByteBufferAllocator() 7 | private var buf: ByteBuffer! = nil 8 | 9 | func testRequireFixedWidthInteger() { 10 | buf = allocator.buffer(capacity: 32) 11 | let first: Int = 1 12 | let second: Int = 3 13 | buf.write(integer: first) 14 | buf.write(integer: second) 15 | try XCTAssertEqual(buf.requireReadInteger(), first) 16 | try XCTAssertEqual(buf.requireReadInteger(), second) 17 | 18 | do { 19 | let _: Int = try buf.requireReadInteger() 20 | XCTFail() 21 | } catch _ as BitsError { 22 | XCTAssert(true) 23 | } catch { 24 | XCTFail() 25 | } 26 | } 27 | 28 | func testRequireString() { 29 | buf = allocator.buffer(capacity: 32) 30 | let first = "This String" 31 | let second = "Is Not That String" 32 | buf.write(string: first) 33 | buf.write(string: second) 34 | try XCTAssertEqual(buf.requireReadString(length: first.count), first) 35 | try XCTAssertEqual(buf.requireReadString(length: second.count), second) 36 | 37 | do { 38 | _ = try buf.requireReadString(length: 1) 39 | XCTFail() 40 | } catch _ as BitsError { 41 | XCTAssert(true) 42 | } catch { 43 | XCTFail() 44 | } 45 | } 46 | 47 | func testRequireData() { 48 | buf = allocator.buffer(capacity: 256) 49 | let first = Array("My String".utf8) 50 | let second = Array("My other string".utf8) 51 | 52 | buf.write(bytes: first) 53 | buf.write(bytes: second) 54 | 55 | try XCTAssertEqual(buf.requireReadData(length: first.count), Data(bytes: first)) 56 | try XCTAssertEqual(buf.requireReadData(length: second.count), Data(bytes: second)) 57 | 58 | do { 59 | _ = try buf.requireReadData(length: 1) 60 | XCTFail() 61 | } catch _ as BitsError { 62 | XCTAssert(true) 63 | } catch { 64 | XCTFail() 65 | } 66 | } 67 | 68 | func testRequireBinaryFloatingPoint() { 69 | buf = allocator.buffer(capacity: 256) 70 | let first: Float = 91.235 71 | let second: Double = 32.476 72 | 73 | buf.write(floatingPoint: first) 74 | buf.write(floatingPoint: second) 75 | 76 | try XCTAssertEqual(buf.requireReadFloatingPoint(), first) 77 | try XCTAssertEqual(buf.requireReadFloatingPoint(), second) 78 | 79 | do { 80 | let _: Double = try buf.requireReadFloatingPoint() 81 | XCTFail() 82 | } catch _ as BitsError { 83 | XCTAssert(true) 84 | } catch { 85 | XCTFail() 86 | } 87 | } 88 | 89 | 90 | static let allTests = [ 91 | ("testRequireFixedWidthInteger", testRequireFixedWidthInteger), 92 | ("testRequireString", testRequireString), 93 | ("testRequireData", testRequireData), 94 | ("testRequireBinaryFloatingPoint", testRequireBinaryFloatingPoint), 95 | ] 96 | } 97 | 98 | -------------------------------------------------------------------------------- /Tests/CoreTests/CoreTests.swift: -------------------------------------------------------------------------------- 1 | import Core 2 | import XCTest 3 | 4 | class CoreTests: XCTestCase { 5 | func testProcessExecute() throws { 6 | try XCTAssertEqual(Process.execute("/bin/echo", "hi"), "hi") 7 | } 8 | 9 | func testProcessExecuteCurl() throws { 10 | let res = try Process.execute("/usr/bin/curl", "--verbose", "https://vapor.codes") 11 | XCTAssertEqual(res.contains("Vapor"), true) 12 | } 13 | 14 | func testProcessAsyncExecute() throws { 15 | let eventLoop = MultiThreadedEventLoopGroup(numberOfThreads: 1) 16 | var lastOutput: ProcessOutput? 17 | let status = try Process.asyncExecute("/bin/echo", "hi", on: eventLoop) { output in 18 | lastOutput = output 19 | }.wait() 20 | XCTAssertEqual(status, 0) 21 | if let output = lastOutput { 22 | switch output { 23 | case .stderr: XCTFail("stderr") 24 | case .stdout(let data): XCTAssertEqual(String(data: data, encoding: .utf8), "hi\n") 25 | } 26 | } else { 27 | XCTFail("no output") 28 | } 29 | } 30 | 31 | func testProcessExecuteMissing() throws { 32 | XCTAssertThrowsError(try Process.execute("foo", "hi"), "hi") 33 | } 34 | 35 | func testBase64() { 36 | let original = Data("The quick brown fox jumps over 13 lazy dogs.".utf8) 37 | XCTAssertEqual(original.base64EncodedString(), "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIDEzIGxhenkgZG9ncy4=") 38 | XCTAssertEqual(Data(base64Encoded: original.base64EncodedString()), original) 39 | } 40 | 41 | func testBase64URL() { 42 | let original = Data("The quick brown fox jumps over 13 lazy dogs.".utf8) 43 | XCTAssertEqual(original.base64URLEncodedString(), "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIDEzIGxhenkgZG9ncy4") 44 | XCTAssertEqual(Data(base64URLEncoded: original.base64URLEncodedString()), original) 45 | } 46 | 47 | func testBase64URLEscaping() { 48 | do { 49 | let data = Data(bytes: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x98, 0x99, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9]) 50 | XCTAssertEqual(data.base64EncodedString(), "AAECAwQFBgcICRAREhMUFRYXGBkgISIjJCUmJygpMDEyMzQ1Njc4OUBBQkNERUZHSElQUVJTVFVWV1hZYGFiY2RlZmdoaXBxcnN0dXZ3eHmAgYKDhIWGh4iJkJGSk5SVmJmgoaKjpKWmp6ipsLGys7S1tre4ucDBwsPExcbHyMnQ0dLT1NXW19jZ8PHy8/T19vf4+Q==") 51 | XCTAssertEqual(data.base64URLEncodedString(), "AAECAwQFBgcICRAREhMUFRYXGBkgISIjJCUmJygpMDEyMzQ1Njc4OUBBQkNERUZHSElQUVJTVFVWV1hZYGFiY2RlZmdoaXBxcnN0dXZ3eHmAgYKDhIWGh4iJkJGSk5SVmJmgoaKjpKWmp6ipsLGys7S1tre4ucDBwsPExcbHyMnQ0dLT1NXW19jZ8PHy8_T19vf4-Q") 52 | XCTAssertEqual(data.base64EncodedString(), data.base64EncodedString().base64URLEscaped().base64URLUnescaped()) 53 | XCTAssertEqual(Data(data.base64EncodedString().utf8), Data(data.base64EncodedString().utf8).base64URLEscaped().base64URLUnescaped()) 54 | } 55 | do { 56 | let data = Data(bytes: [0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x98, 0x99, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9]) 57 | XCTAssertEqual(data.base64EncodedString(), "AgMEBQYHCAkQERITFBUWFxgZICEiIyQlJicoKTAxMjM0NTY3ODlAQUJDREVGR0hJUFFSU1RVVldYWWBhYmNkZWZnaGlwcXJzdHV2d3h5gIGCg4SFhoeIiZCRkpOUlZiZoKGio6SlpqeoqbCxsrO0tba3uLnAwcLDxMXGx8jJ0NHS09TV1tfY2fDx8vP09fb3+Pk=") 58 | XCTAssertEqual(data.base64URLEncodedString(), "AgMEBQYHCAkQERITFBUWFxgZICEiIyQlJicoKTAxMjM0NTY3ODlAQUJDREVGR0hJUFFSU1RVVldYWWBhYmNkZWZnaGlwcXJzdHV2d3h5gIGCg4SFhoeIiZCRkpOUlZiZoKGio6SlpqeoqbCxsrO0tba3uLnAwcLDxMXGx8jJ0NHS09TV1tfY2fDx8vP09fb3-Pk") 59 | XCTAssertEqual(data.base64EncodedString(), data.base64EncodedString().base64URLEscaped().base64URLUnescaped()) 60 | XCTAssertEqual(Data(data.base64EncodedString().utf8), Data(data.base64EncodedString().utf8).base64URLEscaped().base64URLUnescaped()) 61 | } 62 | } 63 | 64 | func testHexEncodedString() throws { 65 | XCTAssertEqual(Data("hello".utf8).hexEncodedString(), "68656c6c6f") 66 | XCTAssertEqual(Data("hello".utf8).hexEncodedString(uppercase: true), "68656C6C6F") 67 | } 68 | 69 | func testHeaderValue() throws { 70 | func parse(_ string: String) throws -> HeaderValue { 71 | guard let value = HeaderValue.parse(string) else { 72 | throw CoreError(identifier: "headerValueParse", reason: "Could not parse: \(string)") 73 | } 74 | return value 75 | } 76 | 77 | // content-disposition 78 | do { 79 | let header = try parse(""" 80 | form-data; name="multinamed[]"; filename="" 81 | """) 82 | XCTAssertEqual(header.value, "form-data") 83 | XCTAssertEqual(header.parameters["name"], "multinamed[]") 84 | XCTAssertEqual(header.parameters["filename"], "") 85 | XCTAssertEqual(header.parameters.count, 2) 86 | } 87 | 88 | // content type no charset 89 | do { 90 | let header = try parse(""" 91 | application/json 92 | """) 93 | XCTAssertEqual(header.value, "application/json") 94 | XCTAssertEqual(header.parameters.count, 0) 95 | } 96 | 97 | // content type 98 | do { 99 | let header = try parse(""" 100 | application/json; charset=utf8 101 | """) 102 | XCTAssertEqual(header.value, "application/json") 103 | XCTAssertEqual(header.parameters["charset"], "utf8") 104 | XCTAssertEqual(header.parameters.count, 1) 105 | } 106 | 107 | // quoted content type 108 | do { 109 | let header = try parse(""" 110 | application/json; charset="utf8" 111 | """) 112 | XCTAssertEqual(header.value, "application/json") 113 | XCTAssertEqual(header.parameters["charset"], "utf8") 114 | XCTAssertEqual(header.parameters.count, 1) 115 | } 116 | 117 | // random letters 118 | do { 119 | let header = try parse(""" 120 | af332r92832llgalksdfjsjf 121 | """) 122 | XCTAssertEqual(header.value, "af332r92832llgalksdfjsjf") 123 | XCTAssertEqual(header.parameters.count, 0) 124 | } 125 | 126 | // empty value 127 | do { 128 | let header = try parse(""" 129 | form-data; name=multinamed[]; filename= 130 | """) 131 | XCTAssertEqual(header.value, "form-data") 132 | XCTAssertEqual(header.parameters["name"], "multinamed[]") 133 | XCTAssertEqual(header.parameters["filename"], "") 134 | XCTAssertEqual(header.parameters.count, 2) 135 | } 136 | 137 | // empty value with trailing 138 | do { 139 | let header = try parse(""" 140 | form-data; name=multinamed[]; filename=; foo=bar 141 | """) 142 | XCTAssertEqual(header.value, "form-data") 143 | XCTAssertEqual(header.parameters["name"], "multinamed[]") 144 | XCTAssertEqual(header.parameters["filename"], "") 145 | XCTAssertEqual(header.parameters["foo"], "bar") 146 | XCTAssertEqual(header.parameters.count, 3) 147 | } 148 | 149 | // escaped quote 150 | do { 151 | let header = try parse(""" 152 | application/json; charset="u\\"t\\"f8" 153 | """) 154 | XCTAssertEqual(header.value, "application/json") 155 | XCTAssertEqual(header.parameters["charset"], "u\"t\"f8") 156 | XCTAssertEqual(header.parameters.count, 1) 157 | } 158 | 159 | // flag style 160 | do { 161 | let header = try parse(""" 162 | id=foo; HttpOnly; Secure; foo=bar; Hello 163 | """) 164 | XCTAssertEqual(header.value, "id=foo") 165 | XCTAssertEqual(header.parameters["HTTPONLY"], "") 166 | XCTAssertEqual(header.parameters["secure"], "") 167 | XCTAssertEqual(header.parameters["foo"], "bar") 168 | XCTAssertEqual(header.parameters["hello"], "") 169 | XCTAssertEqual(header.parameters.count, 4) 170 | } 171 | } 172 | 173 | func testDirectoryConfig() throws { 174 | let config = DirectoryConfig.detect() 175 | let license = config.workDir + "LICENSE" 176 | if !FileManager.default.fileExists(atPath: license) { 177 | XCTFail("could not find license using directory config") 178 | } 179 | } 180 | 181 | static let allTests = [ 182 | ("testProcessExecute", testProcessExecute), 183 | ("testProcessAsyncExecute", testProcessAsyncExecute), 184 | ("testProcessExecuteMissing", testProcessExecuteMissing), 185 | ("testBase64", testBase64), 186 | ("testBase64URL", testBase64URL), 187 | ("testBase64URLEscaping", testBase64URLEscaping), 188 | ("testHexEncodedString", testHexEncodedString), 189 | ("testHeaderValue", testHeaderValue), 190 | ("testDirectoryConfig", testDirectoryConfig), 191 | ] 192 | } 193 | -------------------------------------------------------------------------------- /Tests/CoreTests/ReflectableTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Core 2 | import XCTest 3 | 4 | class ReflectableTests: XCTestCase { 5 | func testStruct() throws { 6 | enum Pet: String, ReflectionDecodable, Decodable { 7 | static func reflectDecoded() -> (Pet, Pet) { return (.cat, .dog) } 8 | case cat, dog 9 | } 10 | 11 | enum Direction: UInt8, ReflectionDecodable, Decodable, CaseIterable { 12 | static let allCases: [Direction] = [.left, .right] 13 | case left, right 14 | } 15 | 16 | struct Foo: Reflectable, Decodable { 17 | var bool: Bool 18 | var obool: Bool? 19 | var int: Int 20 | var oint: Int? 21 | var sarr: [String] 22 | var osarr: [String]? 23 | var pet: Pet 24 | var opet: Pet? 25 | var dir: Direction 26 | var odir: Direction? 27 | } 28 | 29 | let properties = try Foo.reflectProperties() 30 | 31 | #if swift(>=4.1.50) 32 | XCTAssertEqual(properties.description, """ 33 | [bool: Bool, obool: Optional<Bool>, int: Int, oint: Optional<Int>, sarr: Array<String>, osarr: Optional<Array<String>>, pet: Pet, opet: Optional<Pet>, dir: Direction, odir: Optional<Direction>] 34 | """) 35 | #else 36 | XCTAssertEqual(properties.description, """ 37 | [bool: Bool, obool: Optional<Bool>, int: Int, oint: Optional<Int>, sarr: Array<String>, osarr: Optional<Array<String>>, pet: Pet #1, opet: Optional<Pet #1>, dir: Direction #1, odir: Optional<Direction #1>] 38 | """) 39 | #endif 40 | 41 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.bool)?.path, ["bool"]) 42 | try XCTAssert(Foo.reflectProperty(forKey: \.bool)?.type is Bool.Type) 43 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.obool)?.path, ["obool"]) 44 | try XCTAssert(Foo.reflectProperty(forKey: \.obool)?.type is Bool?.Type) 45 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.int)?.path, ["int"]) 46 | try XCTAssert(Foo.reflectProperty(forKey: \.int)?.type is Int.Type) 47 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.oint)?.path, ["oint"]) 48 | try XCTAssert(Foo.reflectProperty(forKey: \.oint)?.type is Int?.Type) 49 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.sarr)?.path, ["sarr"]) 50 | try XCTAssert(Foo.reflectProperty(forKey: \.sarr)?.type is [String].Type) 51 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.osarr)?.path, ["osarr"]) 52 | try XCTAssert(Foo.reflectProperty(forKey: \.osarr)?.type is [String]?.Type) 53 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.pet)?.path, ["pet"]) 54 | try XCTAssert(Foo.reflectProperty(forKey: \.pet)?.type is Pet.Type) 55 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.opet)?.path, ["opet"]) 56 | try XCTAssert(Foo.reflectProperty(forKey: \.opet)?.type is Pet?.Type) 57 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.dir)?.path, ["dir"]) 58 | try XCTAssert(Foo.reflectProperty(forKey: \.dir)?.type is Direction.Type) 59 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.odir)?.path, ["odir"]) 60 | try XCTAssert(Foo.reflectProperty(forKey: \.odir)?.type is Direction?.Type) 61 | } 62 | 63 | func testCaseIterableExtension() throws { 64 | #if swift(>=4.2) 65 | // Should throw since there's only 1 case 66 | enum FakePet: String, CaseIterable, ReflectionDecodable, Decodable { 67 | case dragon 68 | } 69 | 70 | enum Pet: String, CaseIterable, ReflectionDecodable, Decodable { 71 | case cat, dog 72 | } 73 | 74 | struct Foo: Reflectable, Decodable { 75 | var bool: Bool 76 | var pet: Pet 77 | } 78 | 79 | let properties = try Foo.reflectProperties() 80 | XCTAssertEqual(properties.description, """ 81 | [bool: Bool, pet: Pet] 82 | """) 83 | 84 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.bool)?.path, ["bool"]) 85 | try XCTAssert(Foo.reflectProperty(forKey: \.bool)?.type is Bool.Type) 86 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.pet)?.path, ["pet"]) 87 | try XCTAssert(Foo.reflectProperty(forKey: \.pet)?.type is Pet.Type) 88 | try XCTAssertThrowsError(FakePet.reflectDecoded(), "FakePet should throw") 89 | #else 90 | XCTAssertTrue(true) 91 | #endif 92 | } 93 | 94 | func testNonOptionalsOnly() throws { 95 | struct Foo: Reflectable, Decodable { 96 | var bool: Bool 97 | var obool: Bool? 98 | var int: Int 99 | var oint: Int? 100 | var sarr: [String] 101 | var osarr: [String]? 102 | } 103 | 104 | let properties = try Foo.reflectProperties().optionalsRemoved() 105 | XCTAssertEqual(properties.description, "[bool: Bool, int: Int, sarr: Array<String>]") 106 | 107 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.bool)?.path, ["bool"]) 108 | try XCTAssert(Foo.reflectProperty(forKey: \.bool)?.type is Bool.Type) 109 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.int)?.path, ["int"]) 110 | try XCTAssert(Foo.reflectProperty(forKey: \.int)?.type is Int.Type) 111 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.sarr)?.path, ["sarr"]) 112 | try XCTAssert(Foo.reflectProperty(forKey: \.sarr)?.type is [String].Type) 113 | } 114 | 115 | func testStructCustomProperties() throws { 116 | struct User: Reflectable { 117 | var firstName: String 118 | var lastName: String 119 | 120 | static func reflectProperties(depth: Int) throws -> [ReflectedProperty] { 121 | switch depth { 122 | case 0: return [.init(String.self, at: ["first_name"]), .init(String.self, at: ["last_name"])] 123 | default: return [] 124 | } 125 | } 126 | 127 | static func anyReflectProperty(valueType: Any.Type, keyPath: AnyKeyPath) throws -> ReflectedProperty? { 128 | let key: String 129 | switch keyPath { 130 | case \User.firstName: key = "first_name" 131 | case \User.lastName: key = "last_name" 132 | default: return nil 133 | } 134 | return .init(any: valueType, at: [key]) 135 | } 136 | } 137 | 138 | let properties = try User.reflectProperties(depth: 0) 139 | XCTAssertEqual(properties.description, "[first_name: String, last_name: String]") 140 | try XCTAssertEqual(User.reflectProperty(forKey: \.firstName)?.path, ["first_name"]) 141 | try XCTAssert(User.reflectProperty(forKey: \.firstName)?.type is String.Type) 142 | try XCTAssertEqual(User.reflectProperty(forKey: \.lastName)?.path, ["last_name"]) 143 | try XCTAssert(User.reflectProperty(forKey: \.lastName)?.type is String.Type) 144 | } 145 | 146 | func testNestedStruct() throws { 147 | struct Foo: Reflectable, Decodable { 148 | var name: String 149 | var age: Double 150 | var luckyNumber: Int 151 | var bar: Bar 152 | } 153 | 154 | struct Bar: Decodable { 155 | var name: String 156 | var age: Double 157 | var luckyNumbers: [Int] 158 | var dict: [String: String] 159 | var set: Set<String> 160 | } 161 | 162 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.name)?.path, ["name"]) 163 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.age)?.path, ["age"]) 164 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.luckyNumber)?.path, ["luckyNumber"]) 165 | XCTAssertThrowsError(try Foo.reflectProperty(forKey: \.bar)) 166 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.bar.name)?.path, ["bar", "name"]) 167 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.bar.age)?.path, ["bar", "age"]) 168 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.bar.luckyNumbers)?.path, ["bar", "luckyNumbers"]) 169 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.bar.dict)?.path, ["bar", "dict"]) 170 | try XCTAssertEqual(Foo.reflectProperty(forKey: \.bar.set)?.path, ["bar", "set"]) 171 | } 172 | 173 | func testProperties() throws { 174 | struct User: Reflectable, Decodable { 175 | var int: Int 176 | var oint: Int? 177 | var int8: Int8 178 | var oint8: Int8? 179 | var int16: Int16 180 | var oint16: Int16? 181 | var int32: Int32 182 | var oint32: Int32? 183 | var int64: Int64 184 | var oint64: Int64? 185 | var uint: UInt 186 | var uoint: UInt? 187 | var uint8: UInt8 188 | var uoint8: UInt8? 189 | var uint16: UInt16 190 | var uoint16: UInt16? 191 | var uint32: UInt32 192 | var uoint32: UInt32? 193 | var uint64: UInt64 194 | var uoint64: UInt64? 195 | 196 | var uuid: UUID 197 | var ouuid: UUID? 198 | 199 | var date: Date 200 | var odate: Date? 201 | 202 | var float: Float 203 | var ofloat: Float? 204 | var double: Double 205 | var odouble: Double? 206 | 207 | var string: String 208 | var ostring: String? 209 | 210 | var bool: Bool 211 | var obool: Bool? 212 | 213 | var array: [String] 214 | var oarray: [String]? 215 | 216 | var dict: [String: String] 217 | var odict: [String: String]? 218 | 219 | var set: Set<String> 220 | var oset: Set<String>? 221 | 222 | var url: URL 223 | var ourl: URL? 224 | } 225 | 226 | let properties = try User.reflectProperties() 227 | XCTAssertEqual(properties.description, "[int: Int, oint: Optional<Int>, int8: Int8, oint8: Optional<Int8>, int16: Int16, oint16: Optional<Int16>, int32: Int32, oint32: Optional<Int32>, int64: Int64, oint64: Optional<Int64>, uint: UInt, uoint: Optional<UInt>, uint8: UInt8, uoint8: Optional<UInt8>, uint16: UInt16, uoint16: Optional<UInt16>, uint32: UInt32, uoint32: Optional<UInt32>, uint64: UInt64, uoint64: Optional<UInt64>, uuid: UUID, ouuid: Optional<UUID>, date: Date, odate: Optional<Date>, float: Float, ofloat: Optional<Float>, double: Double, odouble: Optional<Double>, string: String, ostring: Optional<String>, bool: Bool, obool: Optional<Bool>, array: Array<String>, oarray: Optional<Array<String>>, dict: Dictionary<String, String>, odict: Optional<Dictionary<String, String>>, set: Set<String>, oset: Optional<Set<String>>, url: URL, ourl: Optional<URL>]") 228 | } 229 | 230 | func testPropertyDepth() throws { 231 | struct Pet: Decodable { 232 | var name: String 233 | var age: Int 234 | } 235 | 236 | struct User: Reflectable, Decodable { 237 | var id: UUID? 238 | var pet: Pet 239 | var name: String 240 | var age: Int 241 | } 242 | 243 | #if swift(>=4.1.50) 244 | try XCTAssertEqual(User.reflectProperties(depth: 0).description, "[id: Optional<UUID>, pet: Pet, name: String, age: Int]") 245 | #else 246 | try XCTAssertEqual(User.reflectProperties(depth: 0).description, "[id: Optional<UUID>, pet: Pet #1, name: String, age: Int]") 247 | #endif 248 | try XCTAssertEqual(User.reflectProperties(depth: 1).description, "[pet.name: String, pet.age: Int]") 249 | try XCTAssertEqual(User.reflectProperties(depth: 2).description, "[]") 250 | } 251 | 252 | func testPropertyA() throws { 253 | final class A: Reflectable, Decodable { 254 | public var id: UUID? 255 | public var date: Date 256 | public var length: Double 257 | public var isOpen: Bool 258 | } 259 | try XCTAssertEqual(A.reflectProperties().description, "[id: Optional<UUID>, date: Date, length: Double, isOpen: Bool]") 260 | } 261 | 262 | func testGH112() throws { 263 | /// A single entry of a Todo list. 264 | final class Todo: FooModel, Decodable { 265 | /// The unique identifier for this `Todo`. 266 | var id: Int? 267 | 268 | /// A title describing what this `Todo` entails. 269 | var title: String 270 | 271 | /// Creates a new `Todo`. 272 | init(id: Int? = nil, title: String) { 273 | self.id = id 274 | self.title = title 275 | } 276 | } 277 | 278 | try XCTAssertEqual(Todo.reflectProperties().description, "[id: Optional<Int>, title: String]") 279 | try XCTAssertEqual(Todo.reflectProperty(forKey: \.id)?.path, ["id"]) 280 | try XCTAssertEqual(Todo.reflectProperty(forKey: \.title)?.path, ["title"]) 281 | try XCTAssertEqual(Todo.reflectProperty(forKey: Todo.idKey)?.path, ["id"]) 282 | 283 | } 284 | 285 | func testCustomCodingKeys() throws { 286 | final class Team: Reflectable, Decodable { 287 | var id: Int? 288 | var name: String 289 | enum CodingKeys: String, CodingKey { 290 | case id = "id" 291 | case name = "name_asdf" 292 | } 293 | init() { fatalError() } 294 | } 295 | try XCTAssertEqual(Team.reflectProperty(forKey: \.id)?.path, ["id"]) 296 | try XCTAssertEqual(Team.reflectProperty(forKey: \.name)?.path, ["name_asdf"]) 297 | try XCTAssertEqual(Team.reflectProperties().description, "[id: Optional<Int>, name_asdf: String]") 298 | } 299 | 300 | func testCache() throws { 301 | final class A: Reflectable, Decodable { 302 | public var b: String 303 | } 304 | 305 | for _ in 0..<1_000 { 306 | try XCTAssertEqual(A.reflectProperty(forKey: \.b)?.path, ["b"]) 307 | } 308 | } 309 | 310 | func testArrayNested() throws { 311 | struct Pet: Codable { 312 | var name: String 313 | var type: String 314 | } 315 | 316 | struct Person: Reflectable, Codable { 317 | var id: Int? 318 | var title: String 319 | var pets: [Pet] 320 | } 321 | 322 | #if swift(>=4.1.50) 323 | try XCTAssertEqual(Person.reflectProperties().description, "[id: Optional<Int>, title: String, pets: Array<Pet>]") 324 | #else 325 | try XCTAssertEqual(Person.reflectProperties().description, "[id: Optional<Int>, title: String, pets: Array<Pet #1>]") 326 | #endif 327 | XCTAssertThrowsError(try Person.reflectProperty(forKey: \.pets)) 328 | } 329 | 330 | /// https://github.com/vapor/core/issues/119 331 | func testGH119() throws { 332 | enum PetType: Int, Codable { 333 | case cat, dog 334 | } 335 | struct Pet: Reflectable, Codable { 336 | var name: String 337 | var type: PetType 338 | } 339 | try XCTAssertEqual(Pet.reflectProperties().description, "[name: String, type: Int]") 340 | } 341 | 342 | static let allTests = [ 343 | ("testStruct", testStruct), 344 | ("testCaseIterableExtension", testCaseIterableExtension), 345 | ("testNonOptionalsOnly", testNonOptionalsOnly), 346 | ("testStructCustomProperties", testStructCustomProperties), 347 | ("testNestedStruct", testNestedStruct), 348 | ("testProperties", testProperties), 349 | ("testPropertyDepth", testPropertyDepth), 350 | ("testPropertyA", testPropertyA), 351 | ("testGH112", testGH112), 352 | ("testCustomCodingKeys", testCustomCodingKeys), 353 | ("testCache", testCache), 354 | ("testArrayNested", testArrayNested), 355 | ("testGH119", testGH119), 356 | ] 357 | } 358 | 359 | protocol Model: Reflectable { 360 | associatedtype ID 361 | static var idKey: WritableKeyPath<Self, ID?> { get } 362 | } 363 | 364 | protocol FooModel: Model where ID == Int { 365 | var id: Int? { get set } 366 | } 367 | 368 | extension FooModel { 369 | static var idKey: WritableKeyPath<Self, Int?> { 370 | return \.id 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /Tests/DebuggingTests/FooError.swift: -------------------------------------------------------------------------------- 1 | import Debugging 2 | 3 | enum FooError: String, Error { 4 | case noFoo 5 | } 6 | 7 | extension FooError: Debuggable { 8 | static var readableName: String { 9 | return "Foo Error" 10 | } 11 | 12 | var identifier: String { 13 | return rawValue 14 | } 15 | 16 | var reason: String { 17 | switch self { 18 | case .noFoo: 19 | return "You do not have a `foo`." 20 | } 21 | } 22 | 23 | var possibleCauses: [String] { 24 | switch self { 25 | case .noFoo: 26 | return [ 27 | "You did not set the flongwaffle.", 28 | "The session ended before a `Foo` could be made.", 29 | "The universe conspires against us all.", 30 | "Computers are hard." 31 | ] 32 | } 33 | } 34 | 35 | var suggestedFixes: [String] { 36 | switch self { 37 | case .noFoo: 38 | return [ 39 | "You really want to use a `Bar` here.", 40 | "Take up the guitar and move to the beach." 41 | ] 42 | } 43 | } 44 | 45 | var documentationLinks: [String] { 46 | switch self { 47 | case .noFoo: 48 | return [ 49 | "http://documentation.com/Foo", 50 | "http://documentation.com/foo/noFoo" 51 | ] 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /Tests/DebuggingTests/FooErrorTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Foundation 3 | @testable import Debugging 4 | 5 | class FooErrorTests: XCTestCase { 6 | static let allTests = [ 7 | ("testPrintable", testPrintable), 8 | ("testOmitEmptyFields", testOmitEmptyFields), 9 | ("testReadableName", testReadableName), 10 | ("testIdentifier", testIdentifier), 11 | ("testCausesAndSuggestions", testCausesAndSuggestions), 12 | ] 13 | 14 | let error: FooError = .noFoo 15 | 16 | func testPrintable() throws { 17 | print(error.debugDescription) 18 | XCTAssertEqual( 19 | error.debugDescription, 20 | expectedPrintable, 21 | "`error`'s `debugDescription` should equal `expectedPrintable`." 22 | ) 23 | } 24 | 25 | func testOmitEmptyFields() { 26 | XCTAssertTrue( 27 | error.stackOverflowQuestions.isEmpty, 28 | "There should be no `stackOverflowQuestions`." 29 | ) 30 | 31 | XCTAssertFalse( 32 | error.debugDescription.contains("Stack Overflow"), 33 | "The `debugDescription` should contain no mention of Stack Overflow." 34 | ) 35 | } 36 | 37 | func testReadableName() { 38 | XCTAssertEqual( 39 | FooError.readableName, 40 | "Foo Error", 41 | "`readableName` should be a well-formatted `String`." 42 | ) 43 | } 44 | 45 | func testIdentifier() { 46 | XCTAssertEqual( 47 | error.identifier, 48 | "noFoo", 49 | "`instanceIdentifier` should equal `'noFoo'`." 50 | ) 51 | } 52 | 53 | func testCausesAndSuggestions() { 54 | XCTAssertEqual( 55 | error.possibleCauses, 56 | expectedPossibleCauses, 57 | "`possibleCauses` should match `expectedPossibleCauses`" 58 | ) 59 | 60 | XCTAssertEqual(error.suggestedFixes, 61 | expectedSuggestedFixes, 62 | "`suggestedFixes` should match `expectedSuggestFixes`") 63 | 64 | XCTAssertEqual(error.documentationLinks, 65 | expectedDocumentedLinks, 66 | "`documentationLinks` should match `expectedDocumentedLinks`") 67 | } 68 | } 69 | 70 | // MARK: - Fixtures 71 | 72 | private let expectedPrintable: String = { 73 | var expectation = "⚠️ Foo Error: You do not have a `foo`.\n" 74 | expectation += "- id: FooError.noFoo\n\n" 75 | 76 | expectation += "Here are some possible causes: \n" 77 | expectation += "- You did not set the flongwaffle.\n" 78 | expectation += "- The session ended before a `Foo` could be made.\n" 79 | expectation += "- The universe conspires against us all.\n" 80 | expectation += "- Computers are hard.\n\n" 81 | 82 | expectation += "These suggestions could address the issue: \n" 83 | expectation += "- You really want to use a `Bar` here.\n" 84 | expectation += "- Take up the guitar and move to the beach.\n\n" 85 | 86 | expectation += "Vapor's documentation talks about this: \n" 87 | expectation += "- http://documentation.com/Foo\n" 88 | expectation += "- http://documentation.com/foo/noFoo\n" 89 | return expectation 90 | }() 91 | 92 | private let expectedPossibleCauses = [ 93 | "You did not set the flongwaffle.", 94 | "The session ended before a `Foo` could be made.", 95 | "The universe conspires against us all.", 96 | "Computers are hard." 97 | ] 98 | 99 | private let expectedSuggestedFixes = [ 100 | "You really want to use a `Bar` here.", 101 | "Take up the guitar and move to the beach." 102 | ] 103 | 104 | private let expectedDocumentedLinks = [ 105 | "http://documentation.com/Foo", 106 | "http://documentation.com/foo/noFoo" 107 | ] 108 | -------------------------------------------------------------------------------- /Tests/DebuggingTests/GeneralTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Debugging 3 | 4 | class GeneralTests: XCTestCase { 5 | func testBulletedList() { 6 | let todos = [ 7 | "Get groceries", 8 | "Walk the dog", 9 | "Change oil in car", 10 | "Get haircut" 11 | ] 12 | 13 | let bulleted = todos.bulletedList 14 | let expectation = "\n- Get groceries\n- Walk the dog\n- Change oil in car\n- Get haircut" 15 | XCTAssertEqual(bulleted, expectation) 16 | } 17 | 18 | func testMinimumConformance() { 19 | let minimum = MinimumError.alpha 20 | let description = minimum.debugDescription 21 | let expectation = "⚠️ MinimumError: Not enabled\n- id: MinimumError.alpha\n" 22 | XCTAssertEqual(description, expectation) 23 | } 24 | 25 | static let allTests = [ 26 | ("testBulletedList", testBulletedList), 27 | ("testMinimumConformance", testMinimumConformance), 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /Tests/DebuggingTests/MinimumError.swift: -------------------------------------------------------------------------------- 1 | import Debugging 2 | 3 | enum MinimumError: String { 4 | case alpha, beta, charlie 5 | } 6 | 7 | extension MinimumError: Debuggable { 8 | /// The reason for the error. 9 | /// Typical implementations will switch over `self` 10 | /// and return a friendly `String` describing the error. 11 | /// - note: It is most convenient that `self` be a `Swift.Error`. 12 | /// 13 | /// Here is one way to do this: 14 | /// 15 | /// switch self { 16 | /// case someError: 17 | /// return "A `String` describing what went wrong including the actual error: `Error.someError`." 18 | /// // other cases 19 | /// } 20 | var reason: String { 21 | switch self { 22 | case .alpha: 23 | return "Not enabled" 24 | case .beta: 25 | return "Enabled, but I'm not configured" 26 | case .charlie: 27 | return "Broken beyond repair" 28 | } 29 | } 30 | 31 | var identifier: String { 32 | return rawValue 33 | } 34 | 35 | /// A `String` array describing the possible causes of the error. 36 | /// - note: Defaults to an empty array. 37 | /// Provide a custom implementation to give more context. 38 | var possibleCauses: [String] { 39 | return [] 40 | } 41 | 42 | var suggestedFixes: [String] { 43 | return [] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/DebuggingTests/TestError.swift: -------------------------------------------------------------------------------- 1 | import Debugging 2 | 3 | struct TestError: Error { 4 | enum Kind: String { 5 | case foo 6 | case bar 7 | case baz 8 | } 9 | 10 | var kind: Kind 11 | var reason: String 12 | var file: String 13 | var function: String 14 | var line: UInt 15 | var column: UInt 16 | var stackTrace: [String] 17 | 18 | init(kind: Kind, reason: String, file: String = #file, function: String = #function, line: UInt = #line, column: UInt = #column) { 19 | self.kind = kind 20 | self.reason = reason 21 | self.file = file 22 | self.function = function 23 | self.line = line 24 | self.column = column 25 | self.stackTrace = TestError.makeStackTrace() 26 | } 27 | 28 | static func foo(reason: String, file: String = #file, function: String = #function, line: UInt = #line, column: UInt = #column) -> TestError { 29 | return TestError(kind: .foo, reason: reason, file: file, function: function, line: line, column: column) 30 | } 31 | } 32 | 33 | extension TestError: Debuggable { 34 | var identifier: String { 35 | return kind.rawValue 36 | } 37 | 38 | var possibleCauses: [String] { 39 | switch kind { 40 | case .foo: 41 | return ["What do you expect, you're testing errors."] 42 | default: 43 | return [] 44 | } 45 | } 46 | 47 | var suggestedFixes: [String] { 48 | switch kind { 49 | case .foo: 50 | return ["Get a better keyboard to chair interface."] 51 | default: 52 | return [] 53 | } 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Tests/DebuggingTests/TraceableTests.swift: -------------------------------------------------------------------------------- 1 | import Debugging 2 | import XCTest 3 | 4 | func triggerError() throws { 5 | throw TestError.foo(reason: "So we can test stuff") 6 | } 7 | 8 | 9 | class TraceableTests: XCTestCase { 10 | func testPrintable() throws { 11 | do { 12 | try triggerError() 13 | } catch { 14 | print(error) 15 | print() 16 | print() 17 | debugPrint(error) 18 | } 19 | } 20 | 21 | static let allTests = [ 22 | ("testPrintable", testPrintable), 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | 3 | @testable import AsyncTests 4 | @testable import BitsTests 5 | @testable import CoreTests 6 | @testable import DebuggingTests 7 | import XCTest 8 | 9 | XCTMain([ 10 | /// Async 11 | testCase(AsyncTests.allTests), 12 | 13 | /// Bits 14 | testCase(ByteBufferPeekTests.allTests), 15 | testCase(ByteBufferRequireTests.allTests), 16 | 17 | /// Core 18 | testCase(CoreTests.allTests), 19 | testCase(ReflectableTests.allTests), 20 | 21 | /// Debugging 22 | testCase(FooErrorTests.allTests), 23 | testCase(GeneralTests.allTests), 24 | testCase(TraceableTests.allTests), 25 | ]) 26 | 27 | #endif -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | macos: 5 | macos: 6 | xcode: "9.3" 7 | steps: 8 | - checkout 9 | - run: swift build 10 | - run: swift test 11 | linux: 12 | docker: 13 | - image: codevapor/swift:4.1 14 | steps: 15 | - checkout 16 | - run: 17 | name: Compile code 18 | command: swift build 19 | - run: 20 | name: Run unit tests 21 | command: swift test 22 | 23 | linux-release: 24 | docker: 25 | - image: swift:4.2 26 | steps: 27 | - checkout 28 | - run: 29 | name: Compile code with optimizations 30 | command: swift build -c release 31 | linux-vapor: 32 | docker: 33 | - image: swift:4.2 34 | steps: 35 | - run: 36 | name: Clone Vapor 37 | command: git clone -b 3 https://github.com/vapor/vapor.git 38 | working_directory: ~/ 39 | - run: 40 | name: Switch Vapor to this Core revision 41 | command: swift package edit Core --revision $CIRCLE_SHA1 42 | working_directory: ~/vapor 43 | - run: 44 | name: Run Vapor unit tests 45 | command: swift test 46 | working_directory: ~/vapor 47 | linux-fluent-sqlite: 48 | docker: 49 | - image: swift:4.2 50 | steps: 51 | - run: 52 | name: Clone Fluent SQLite 53 | command: git clone -b 3 https://github.com/vapor/fluent-sqlite.git 54 | working_directory: ~/ 55 | - run: 56 | name: Switch Fluent SQLite to this Core revision 57 | command: swift package edit Core --revision $CIRCLE_SHA1 58 | working_directory: ~/fluent-sqlite 59 | - run: 60 | name: Run Fluent SQLite unit tests 61 | command: swift test 62 | working_directory: ~/fluent-sqlite 63 | linux-fluent-mysql: 64 | docker: 65 | - image: swift:4.2 66 | - image: mysql:5.7 67 | environment: 68 | MYSQL_ALLOW_EMPTY_PASSWORD: true 69 | MYSQL_DATABASE: vapor_database 70 | # MYSQL_ROOT_HOST: % 71 | MYSQL_USER: vapor_username 72 | MYSQL_PASSWORD: vapor_password 73 | steps: 74 | - run: 75 | name: Clone Fluent MySQL 76 | command: git clone -b 3 https://github.com/vapor/fluent-mysql.git 77 | working_directory: ~/ 78 | - run: 79 | name: Switch Fluent MySQL to this Core revision 80 | command: swift package edit Core --revision $CIRCLE_SHA1 81 | working_directory: ~/fluent-mysql 82 | - run: 83 | name: Run Fluent MySQL unit tests 84 | command: swift test 85 | working_directory: ~/fluent-mysql 86 | linux-fluent-postgresql: 87 | docker: 88 | - image: swift:4.2 89 | - image: circleci/postgres:latest 90 | name: psql 91 | environment: 92 | POSTGRES_USER: vapor_username 93 | POSTGRES_DB: vapor_database 94 | POSTGRES_PASSWORD: vapor_password 95 | steps: 96 | - run: 97 | name: Clone Fluent PostgreSQL 98 | command: git clone -b 1 https://github.com/vapor/fluent-postgresql.git 99 | working_directory: ~/ 100 | - run: 101 | name: Switch Fluent PostgreSQL to this Core revision 102 | command: swift package edit Core --revision $CIRCLE_SHA1 103 | working_directory: ~/fluent-postgresql 104 | - run: 105 | name: Run Fluent PostgreSQL unit tests 106 | command: swift test 107 | working_directory: ~/fluent-postgresql 108 | linux-redis: 109 | docker: 110 | - image: swift:4.2 111 | - image: redis:3.2 112 | steps: 113 | - run: 114 | name: Clone Redis 115 | command: git clone -b support-3 https://github.com/vapor/redis.git 116 | working_directory: ~/ 117 | - run: 118 | name: Switch Redis to this Core revision 119 | command: swift package edit Core --revision $CIRCLE_SHA1 120 | working_directory: ~/redis 121 | - run: 122 | name: Run Redis unit tests 123 | command: swift test 124 | working_directory: ~/redis 125 | linux-jwt: 126 | docker: 127 | - image: swift:4.2 128 | steps: 129 | - run: 130 | name: Clone JWT 131 | command: git clone -b 3 https://github.com/vapor/jwt.git 132 | working_directory: ~/ 133 | - run: 134 | name: Switch JWT to this Core revision 135 | command: swift package edit Core --revision $CIRCLE_SHA1 136 | working_directory: ~/jwt 137 | - run: 138 | name: Run JWT unit tests 139 | command: swift test 140 | working_directory: ~/jwt 141 | linux-leaf: 142 | docker: 143 | - image: swift:4.2 144 | steps: 145 | - run: 146 | name: Clone Leaf 147 | command: git clone -b 3 https://github.com/vapor/leaf.git 148 | working_directory: ~/ 149 | - run: 150 | name: Switch Leaf to this Core revision 151 | command: swift package edit Core --revision $CIRCLE_SHA1 152 | working_directory: ~/leaf 153 | - run: 154 | name: Run Leaf unit tests 155 | command: swift test 156 | working_directory: ~/leaf 157 | workflows: 158 | version: 2 159 | tests: 160 | jobs: 161 | - linux 162 | - linux-vapor 163 | - linux-fluent-sqlite 164 | - linux-fluent-mysql 165 | - linux-fluent-postgresql 166 | - linux-redis 167 | - linux-jwt 168 | - linux-leaf 169 | - linux-release 170 | nightly: 171 | triggers: 172 | - schedule: 173 | cron: "0 0 * * *" 174 | filters: 175 | branches: 176 | only: 177 | - master 178 | jobs: 179 | - linux 180 | --------------------------------------------------------------------------------