Initial commit

This commit is contained in:
oscarz
2024-08-12 10:49:20 +08:00
parent 3002510aaf
commit 00fd0adf89
331 changed files with 53210 additions and 130 deletions

201
Pods/KituraContracts/LICENSE generated Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

87
Pods/KituraContracts/README.md generated Normal file
View File

@ -0,0 +1,87 @@
<p align="center">
<a href="https://www.kitura.io/">
<img src="https://raw.githubusercontent.com/IBM-Swift/Kitura/master/Sources/Kitura/resources/kitura-bird.svg?sanitize=true" height="100" alt="Kitura">
</a>
</p>
<p align="center">
<a href="https://ibm-swift.github.io/KituraContracts/index.html">
<img src="https://img.shields.io/badge/apidoc-KituraContracts-1FBCE4.svg?style=flat" alt="APIDoc">
</a>
<a href="https://travis-ci.org/IBM-Swift/KituraContracts">
<img src="https://travis-ci.org/IBM-Swift/KituraContracts.svg?branch=master" alt="Build Status - Master">
</a>
<img src="https://img.shields.io/badge/os-macOS-green.svg?style=flat" alt="macOS">
<img src="https://img.shields.io/badge/os-linux-green.svg?style=flat" alt="Linux">
<img src="https://img.shields.io/badge/license-Apache2-blue.svg?style=flat" alt="Apache 2">
<a href="http://swift-at-ibm-slack.mybluemix.net/">
<img src="http://swift-at-ibm-slack.mybluemix.net/badge.svg" alt="Slack Status">
</a>
</p>
# KituraContracts
## Summary
KituraContracts is a library containing type definitions shared by client (e.g. [KituraKit](https://ibm-swift.github.io/KituraKit/)) and server (e.g. [Kitura](https://ibm-swift.github.io/Kitura)) code. These shared type definitions include [Codable Closure Aliases](https://ibm-swift.github.io/KituraContracts/Typealiases.html), [RequestError](https://ibm-swift.github.io/KituraContracts/Structs/RequestError.html), [QueryEncoder](https://ibm-swift.github.io/KituraContracts/Classes/QueryEncoder.html), [QueryDecoder](https://ibm-swift.github.io/KituraContracts/Classes/QueryDecoder.html), [Coder](https://ibm-swift.github.io/KituraContracts/Classes/Coder.html), [Identifier Protocol](https://ibm-swift.github.io/KituraContracts/Protocols/Identifier.html#/s:15KituraContracts10IdentifierP5valueSSv) and [Extensions](https://ibm-swift.github.io/KituraContracts/Extensions.html#/s:SS) to String and Int, which add conformity to the Identifier protocol.
## Usage
KituraContracts represents the types and protocols that are common to both the [Kitura](https://github.com/IBM-Swift/Kitura) server and [KituraKit](https://github.com/IBM-Swift/KituraKit) client side library. If you are using Kitura or KituraKit, your project does not need to depend on KituraContracts explicitly.
#### Add dependencies
Add the `KituraContracts` package to the dependencies within your applications `Package.swift` file. Substitute `"x.x.x"` with the latest `KituraContracts` [release](https://github.com/IBM-Swift/KituraContracts/releases).
```swift
.package(url: "https://github.com/IBM-Swift/KituraContracts.git", from: "x.x.x")
```
Add `KituraContracts` to your target's dependencies:
```swift
.target(name: "example", dependencies: ["KituraContracts"]),
```
#### Import package
```swift
import KituraContracts
```
## Example
This example, shows how to use a shared type definition for `RequestError` within a router POST method on `users`. If no errors occurred and you have a `User` you can respond with the user and pass nil as the `RequestError?` value. If there has been an error you can respond with an appropriate error and pass nil for the `User?`.
````swift
public struct User: Codable {
...
}
router.post("/users") { (user: User, respondWith: (User?, RequestError?) -> Void) in
if databaseConnectionIsOk {
...
respondWith(user, nil)
} else {
...
respondWith(nil, .internalServerError)
}
}
````
## Swift version
The 1.x.x releases were tested on macOS and Linux using the Swift 4.1 binaries. Please note that this is the default version of Swift that is included in [Xcode 9.3](https://developer.apple.com/xcode/).
## API Documentation
For more information visit our [API reference](https://ibm-swift.github.io/KituraContracts/index.html).
## Community
We love to talk server-side Swift and Kitura. Join our [Slack](http://swift-at-ibm-slack.mybluemix.net/) to meet the team!
## License
This library is licensed under Apache 2.0. Full license text is available in [LICENSE](https://github.com/IBM-Swift/KituraContracts/blob/master/LICENSE).

View File

@ -0,0 +1,28 @@
/**
* Copyright IBM Corporation 2018
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
import Foundation
/**
A class that conforms to `BodyDecoder` must be able to decode from `Data` into a `Codable` type.
This class can then be used to produce input objects for a Codable route.
*/
public protocol BodyDecoder: AnyObject {
/// Decode a `Decodable` type from a `Data`, using this `BodyDecoder`.
func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T
}
extension JSONDecoder: BodyDecoder {}

View File

@ -0,0 +1,28 @@
/**
* Copyright IBM Corporation 2018
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
import Foundation
/**
A class that conforms to `BodyEncoder` must be able to encode a `Codable` type into `Data`.
This class can then be used to produce output objects for a Codable route.
*/
public protocol BodyEncoder: AnyObject {
/// Encode an `Encodable` type to a `Data`, using this `BodyEncoder`.
func encode<T : Encodable>(_ value: T) throws -> Data
}
extension JSONEncoder: BodyEncoder {}

View File

@ -0,0 +1,78 @@
/**
* Copyright IBM Corporation 2018
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
import Foundation
/**
A set of values representing the format of a response body.
This struct is intended to be "enum-like" and values should be
accessed via the public static stored properties.
- Note: An enum was not used here because currently enums are
always exhaustive. This means adding a case to an enum
is a breaking change. In order to keep such additions
non-breaking we have used an "enum-like" struct instead.
This means code using `BodyFormat` should always handle
unrecognised `BodyFormat` values (eg in a default case
of a switch). `UnsupportedBodyFormatError` may be used
in this situation.
### Usage Example: ###
````swift
let format = BodyFormat.json
````
*/
public struct BodyFormat: Equatable {
/**
A String value of the type that the body format will be represented in, which is used to ensure that both the left-hand side and the right-hand side are of the same type in the response body.
*/
public let type: String
private init(_ type: String) {
self.type = type
}
/**
This function checks that both the left-hand side and the right-hand side of the response body are of the same type.
*/
public static func == (_ lhs: BodyFormat, _ rhs: BodyFormat) -> Bool {
return lhs.type == rhs.type
}
/**
The JSON representation of the response body.
*/
public static let json = BodyFormat("application/json")
}
/**
An error that may be thrown when a particular instance of `BodyFormat`
is not supported.
*/
public struct UnsupportedBodyFormatError: Error {
/**
The format of the body.
*/
public let bodyFormat: BodyFormat
/**
Initialize `UnsupportedBodyFormatError` with the format of the body.
*/
public init(_ bodyFormat: BodyFormat) {
self.bodyFormat = bodyFormat
}
}

View File

@ -0,0 +1,260 @@
/**
* Copyright IBM Corporation 2017
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
// MARK: Codable Type Aliases
/**
The `ResultClosure` is used by other `Codable` aliases when responding with only a `RequestError` is needed.
The following two typealiases make use of `ResultClosure`:
````swift
public typealias NonCodableClosure = (@escaping ResultClosure) -> Void
public typealias IdentifierNonCodableClosure<Id: Identifier> = (Id, @escaping ResultClosure) -> Void
````
*/
public typealias ResultClosure = (RequestError?) -> Void
/**
The `CodableResultClosure` is used by other `Codable` aliases when responding with an object which conforms to `Codable`, or a `RequestError` is needed.
The following two typealiases make use of `CodableResultClosure`:
````swift
public typealias IdentifierCodableClosure<Id: Identifier, I: Codable, O: Codable> = (Id, I, @escaping CodableResultClosure<O>) -> Void
public typealias CodableClosure<I: Codable, O: Codable> = (I, @escaping CodableResultClosure<O>) -> Void
````
*/
public typealias CodableResultClosure<O: Codable> = (O?, RequestError?) -> Void
/**
The `CodableArrayResultClosure` is used by other `Codable` aliases when responding with an array of objects which conform to `Codable`, or a `RequestError` is needed.
The following typealias makes use of `CodableArrayResultClosure`:
````swift
public typealias CodableArrayClosure<O: Codable> = (@escaping CodableArrayResultClosure<O>) -> Void
````
*/
public typealias CodableArrayResultClosure<O: Codable> = ([O]?, RequestError?) -> Void
/**
The `IdentifierCodableArrayResultClosure` is used by other `Codable` aliases when responding with an array of tuples containing an identifier and a `Codable` object, or a `RequestError`.
The following typealias makes use of `IdentifierCodableArrayResultClosure`:
````swift
public typealias CodableIdentifierClosure<I: Codable, Id: Identifier, O: Codable> = (I, @escaping IdentifierCodableResultClosure<[Id, O]?>) -> Void
````
*/
public typealias IdentifierCodableArrayResultClosure<Id: Identifier, O: Codable> = ([(Id, O)]?, RequestError?) -> Void
/**
This is used to perform a series of actions which use an object conforming to `Identifier` and an object conforming to `Codable`. After which you want to respond with an object which conforms to `Codable`, which is of the same type as the object passed as a parameter, or respond with an `Identifier` or `RequestError`.
The following typealias makes use of `IdentifierCodableResultClosure`:
````swift
public typealias CodableIdentifierClosure<I: Codable, Id: Identifier, O: Codable> = (I, @escaping IdentifierCodableResultClosure<Id, O>) -> Void
````
*/
public typealias IdentifierCodableResultClosure<Id: Identifier, O: Codable> = (Id?, O?, RequestError?) -> Void
/**
The `IdentifierCodableClosure` is used to perform a series of actions utilising an object conforming to `Identifier` and an object conforming to 'Codable', then respond with an object which conforms to `Codable`, which is of the same type as the object passed as a parameter, or responding with a `RequestError` in the form of a `CodableResultClosure`.
By default `Int` has conformity to `Identifier`. In this example, if there has been an error you can use the `respondWith` call to respond with an appropriate error, passing nil for the `User?`. If no errors occurred and you have a `User`, you can just respond with the user, passing nil as the `RequestError?` value.
### Usage Example: ###
````swift
public struct User: Codable {
...
}
var userStore: [Int, User] = [...]
router.put("/users") { (id: Int, user: User, respondWith: (User?, RequestError?) -> Void) in
guard let oldUser = self.userStore[id] else {
respondWith(nil, .notFound)
return
}
...
respondWith(user, nil)
}
````
*/
public typealias IdentifierCodableClosure<Id: Identifier, I: Codable, O: Codable> = (Id, I, @escaping CodableResultClosure<O>) -> Void
/**
The `CodableClosure` is used to perform a series of actions utilising an object conforming to `Identifier`, then respond with an object which conforms to `Codable`, which is of the same type as the object passed as a parameter, or responding with a `RequestError` in the form of a `CodableResultClosure`.
If no errors occurred and you have a `User`, you can just respond with the user by passing nil as the `RequestError?` value. In this example, if there has been an error you can use the `respondWith` call to respond with an appropriate error and passing nil for the `User?`.
### Usage Example: ###
````swift
public struct User: Codable {
...
}
router.post("/users") { (user: User, respondWith: (User?, RequestError?) -> Void) in
if databaseConnectionIsOk {
...
respondWith(user, nil)
} else {
...
respondWith(nil, .internalServerError)
}
}
````
*/
public typealias CodableClosure<I: Codable, O: Codable> = (I, @escaping CodableResultClosure<O>) -> Void
/**
The `CodableIdentifierClosure` is used to perform a series of actions utilising an object conforming to `Identifier`, then respond with an object which conforms to `Codable`, and/or an object conforming to `Identifier` or responding with a `RequestError` in the form of a `IdentifierCodableResultClosure`.
If no errors occurred and you have a `User` and the corresponding identifier, you can just respond with the identifier and user, and pass nil as the `RequestError?` value. In this example, if there has been an error you can use the `respondWith` call to respond with an appropriate error and passing nil for `Int?` and nil for `User?`.
### Usage Example: ###
````swift
public struct User: Codable {
...
}
router.post("/users") { (user: User, respondWith: (Int?, User?, RequestError?) -> Void) in
if databaseConnectionIsOk {
...
respondWith(id, user, nil)
} else {
...
respondWith(nil, nil, .internalServerError)
}
}
````
*/
public typealias CodableIdentifierClosure<I: Codable, Id: Identifier, O: Codable> = (I, @escaping IdentifierCodableResultClosure<Id, O>) -> Void
/**
The `NonCodableClosure` is used to perform a series of actions then respond with a `RequestError` in the form of a `ResultClosure`.
If no errors occurred you can just pass nil as the `RequestError?` value. In this example, if there has been an error you can use the `respondWith` call to respond with an appropriate error.
### Usage Example: ###
````swift
router.delete("/users") { (respondWith: (RequestError?) -> Void) in
if databaseConnectionIsOk {
...
respondWith(nil)
} else {
respondWith(.internalServerError)
...
}
}
````
*/
public typealias NonCodableClosure = (@escaping ResultClosure) -> Void
/**
The `IdentifierNonCodableClosure` is used to perform a series of actions utilising an object which conforms to `Identifier`, then respond with a `RequestError` in the form of a `ResultClosure`.
If no errors occurred you can just pass nil as the `RequestError?` value. In this example, if there has been an error you can use the `respondWith` call to respond with an appropriate error.
### Usage Example: ###
````swift
router.delete("/users") { (id: Int, respondWith: (RequestError?) -> Void) in
if databaseConnectionIsOk {
...
respondWith(nil)
} else {
...
respondWith(.internalServerError)
}
}
````
*/
public typealias IdentifierNonCodableClosure<Id: Identifier> = (Id, @escaping ResultClosure) -> Void
/**
The `CodableArrayClosure` is used to perform a series of actions then respond with an array of objects conforming to `Codable` or a `RequestError` in the form of a `CodableArrayResultClosure`.
If no errors occurred and you have an array of `Users` you can just respond with the users by passing nil as the `RequestError?` value. In this example, if there has been an error you can use the `respondWith` call to respond with an appropriate error and passing nil for the `[User]?`.
### Usage Example: ###
````swift
router.get("/users") { (respondWith: ([User]?, RequestError?) -> Void) in
if databaseConnectionIsOk {
...
respondWith(users, nil)
} else {
...
respondWith(nil, .internalServerError)
}
}
````
*/
public typealias CodableArrayClosure<O: Codable> = (@escaping CodableArrayResultClosure<O>) -> Void
/**
The `IdentifierCodableArrayClosure` is used to perform a series of actions then respond with an array of tuples containing an identifier and a Codable object, or a `RequestError`, in the form of a `IdentifierCodableArrayResultClosure`.
If no errors occurred and you have an array of `Users` you can just respond with the users by passing nil as the `RequestError?` value. In this example, if there has been an error you can use the `respondWith` call to respond with an appropriate error and passing nil for the `[User]?`.
### Usage Example: ###
````swift
router.get("/users") { (respondWith: ([(Int, User)]?, RequestError?) -> Void) in
if databaseConnectionIsOk {
...
respondWith([(Int, User)], nil)
} else {
...
respondWith(nil, .internalServerError)
}
}
````
*/
public typealias IdentifierCodableArrayClosure<Id: Identifier, O: Codable> = (@escaping IdentifierCodableArrayResultClosure<Id, O>) -> Void
/**
The `SimpleCodableClosure` is used to perform a series of actions, then respond with an object conforming to `Codable` or a `RequestError` in the form of a `CodableResultClosure`.
### Usage Example: ###
````swift
public struct Status: Codable {
...
}
router.get("/status") { (respondWith: (Status?, RequestError?) -> Void) in
...
respondWith(status, nil)
}
````
*/
public typealias SimpleCodableClosure<O: Codable> = (@escaping CodableResultClosure<O>) -> Void
/**
The `IdentifierSimpleCodableClosure` is used to perform a series of actions utilising an object which conforms to `Identifier`, then respond with an object conforming to `Codable` or a `RequestError` in the form of a `CodableResultClosure`.
If there has been an error you can use the `respondWith` call to respond with an appropriate error and passing nil for the `User?`. In this example, if no errors occurred and you have a `User` you can just respond with the user by passing nil as the `RequestError?` value.
### Usage Example: ###
````swift
public struct User: Codable {
...
}
var userStore: [Int, User] = (...)
router.get("/users") { (id: Int, respondWith: (User?, RequestError?) -> Void) in
guard let user = self.userStore[id] else {
respondWith(nil, .notFound)
return
}
...
respondWith(user, nil)
}
````
*/
public typealias IdentifierSimpleCodableClosure<Id: Identifier, O: Codable> = (Id, @escaping CodableResultClosure<O>) -> Void

View File

@ -0,0 +1,68 @@
/*
* Copyright IBM Corporation 2017
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/**
Class defining shared resources for the [QueryDecoder](https://github.com/IBM-Swift/KituraContracts/blob/master/Sources/KituraContracts/CodableQuery/QueryDecoder.swift) and [QueryEncoder](https://github.com/IBM-Swift/KituraContracts/blob/master/Sources/KituraContracts/CodableQuery/QueryEncoder.swift).
### Usage Example: ###
````swift
let date = Coder.defaultDateFormatter.date(from: "2017-10-31T16:15:56+0000")!
````
*/
public class Coder {
@available(*, deprecated, message: "Use Coder.defaultDateFormatter instead")
public let dateFormatter: DateFormatter = Coder.defaultDateFormatter
/**
The default [DateFormatter](https://developer.apple.com/documentation/foundation/dateformatter) used for encoding and decoding query parameters. It uses the "UTC" timezone and "yyyy-MM-dd'T'HH:mm:ssZ" date format.
### Usage Example: ###
````swift
let date = Coder.defaultDateFormatter.date(from: "2017-10-31T16:15:56+0000")
````
*/
public static let defaultDateFormatter: DateFormatter = {
let value = DateFormatter()
value.timeZone = TimeZone(identifier: "UTC")
value.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
return value
}()
/**
Initializes a `Coder` instance with a `DateFormatter`
using the "UTC" timezone and "yyyy-MM-dd'T'HH:mm:ssZ" date format.
*/
public init() {}
/**
Helper method to extract the field name from a `CodingKey` array.
### Usage Example: ###
````swift
let fieldName = Coder.getFieldName(from: codingPath)
````
*/
public static func getFieldName(from codingPath: [CodingKey]) -> String {
#if swift(>=4.1)
return codingPath.compactMap({$0.stringValue}).joined(separator: ".")
#else
return codingPath.flatMap({$0.stringValue}).joined(separator: ".")
#endif
}
}

View File

@ -0,0 +1,353 @@
/*
* Copyright IBM Corporation 2017
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
import LoggerAPI
/// Codable String Conversion Extension.
extension String {
/// Converts the given String to an Int?.
public var int: Int? {
return Int(self)
}
/// Converts the given String to a Int8?.
public var int8: Int8? {
return Int8(self)
}
/// Converts the given String to a Int16?.
public var int16: Int16? {
return Int16(self)
}
/// Converts the given String to a Int32?.
public var int32: Int32? {
return Int32(self)
}
/// Converts the given String to a Int64?.
public var int64: Int64? {
return Int64(self)
}
/// Converts the given String to a UInt?.
public var uInt: UInt? {
return UInt(self)
}
/// Converts the given String to a UInt8?.
public var uInt8: UInt8? {
return UInt8(self)
}
/// Converts the given String to a UInt16?.
public var uInt16: UInt16? {
return UInt16(self)
}
/// Converts the given String to a UInt32?.
public var uInt32: UInt32? {
return UInt32(self)
}
/// Converts the given String to a UInt64?.
public var uInt64: UInt64? {
return UInt64(self)
}
/// Converts the given String to a Float?.
public var float: Float? {
return Float(self)
}
/// Converts the given String to a Double?.
public var double: Double? {
return Double(self)
}
/// Converts the given String to a Bool?.
public var boolean: Bool? {
return !self.isEmpty ? Bool(self) : false
}
/// Converts the given String to a String.
public var string: String {
return self
}
/// Converts the given String to an [Int]?.
public var intArray: [Int]? {
return decodeArray(Int.self)
}
/// Converts the given String to an [Int8]?.
public var int8Array: [Int8]? {
return decodeArray(Int8.self)
}
/// Converts the given String to an [Int16]?.
public var int16Array: [Int16]? {
return decodeArray(Int16.self)
}
/// Converts the given String to an [Int32]?.
public var int32Array: [Int32]? {
return decodeArray(Int32.self)
}
/// Converts the given String to an [Int64]?.
public var int64Array: [Int64]? {
return decodeArray(Int64.self)
}
/// Converts the given String to an [UInt]?.
public var uIntArray: [UInt]? {
return decodeArray(UInt.self)
}
/// Converts the given String to an [UInt8]?.
public var uInt8Array: [UInt8]? {
return decodeArray(UInt8.self)
}
/// Converts the given String to an [UInt16]?.
public var uInt16Array: [UInt16]? {
return decodeArray(UInt16.self)
}
/// Converts the given String to an [UInt32]?.
public var uInt32Array: [UInt32]? {
return decodeArray(UInt32.self)
}
/// Converts the given String to an [UInt64]?.
public var uInt64Array: [UInt64]? {
return decodeArray(UInt64.self)
}
/// Converts the given String to a [Float]?.
public var floatArray: [Float]? {
return decodeArray(Float.self)
}
/// Converts the given String to a [Double]?.
public var doubleArray: [Double]? {
return decodeArray(Double.self)
}
/// Converts the given String to a [Bool]?.
public var booleanArray: [Bool]? {
return decodeArray(Bool.self)
}
/// Converts the given String to a [String].
public var stringArray: [String] {
let strs: [String] = self.components(separatedBy: ",")
return strs
}
/**
Method used to decode a string into the given type T.
- Parameter type: The Decodable type to convert the string into.
- Returns: The Date? object. Some on success / nil on failure.
*/
public func decodable<T: Decodable>(_ type: T.Type) -> T? {
guard let data = self.data(using: .utf8) else {
return nil
}
let obj: T? = try? JSONDecoder().decode(type, from: data)
return obj
}
/**
Converts the given String to a Date?.
- Parameter formatter: The designated DateFormatter to convert the string with.
- Returns: The Date? object. Some on success / nil on failure.
*/
public func date(_ formatter: DateFormatter) -> Date? {
return formatter.date(from: self)
}
/**
Converts the given String to a [Date]?.
- Parameter formatter: The designated DateFormatter to convert the string with.
- Returns: The [Date]? object. Some on success / nil on failure.
*/
public func dateArray(_ formatter: DateFormatter) -> [Date]? {
let strs: [String] = self.components(separatedBy: ",")
let dates = strs.map { formatter.date(from: $0) }.filter { $0 != nil }.map { $0! }
if dates.count == strs.count {
return dates
}
return nil
}
/**
Converts the given String to a [Date]? object using the dateDecodingStrategy supplied.
- Parameter formatter: The designated `DateFormatter` to convert the string with.
- Parameter decoderStrategy: The `JSON.dateDecodingStrategy` that should be used to decode the specifed Date. Default is set to .formatted with default dateFormatter.
- Parameter decoder: The `Decoder` parameter is only used for the custom strategy.
- Returns: The [Date]? object. Some on success / nil on failure.
*/
public func dateArray(decoderStrategy: JSONDecoder.DateDecodingStrategy = .formatted(Coder.defaultDateFormatter), decoder: Decoder?=nil) -> [Date]? {
switch decoderStrategy {
case .formatted(let formatter):
let strs: [String] = self.components(separatedBy: ",")
let dates = strs.map { formatter.date(from: $0) }.filter { $0 != nil }.map { $0! }
if dates.count == strs.count {
return dates
}
return nil
case .deferredToDate:
let strs: [String] = self.components(separatedBy: ",")
#if swift(>=4.1)
let dbs = strs.compactMap(Double.init)
#else
let dbs = strs.flatMap(Double.init)
#endif
let dates = dbs.map { Date(timeIntervalSinceReferenceDate: $0) }
if dates.count == dbs.count {
return dates
}
return nil
case .secondsSince1970:
let strs: [String] = self.components(separatedBy: ",")
#if swift(>=4.1)
let dbs = strs.compactMap(Double.init)
#else
let dbs = strs.flatMap(Double.init)
#endif
let dates = dbs.map { Date(timeIntervalSince1970: $0) }
if dates.count == dbs.count {
return dates
}
return nil
case .millisecondsSince1970:
let strs: [String] = self.components(separatedBy: ",")
#if swift(>=4.1)
let dbs = strs.compactMap(Double.init)
#else
let dbs = strs.flatMap(Double.init)
#endif
let dates = dbs.map { Date(timeIntervalSince1970: ($0)/1000) }
if dates.count == dbs.count {
return dates
}
return nil
case .iso8601:
let strs: [String] = self.components(separatedBy: ",")
if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
let dates = strs.map { _iso8601Formatter.date(from: $0) }
if dates.count == strs.count {
return dates as? [Date]
}
return nil
} else {
fatalError("ISO8601DateFormatter is unavailable on this platform.")
}
case .custom(let closure):
var dateArray: [Date] = []
guard let decoder = decoder else {return dateArray}
var fieldValueArray = self.split(separator: ",")
for _ in fieldValueArray {
// Call closure to decode value
guard let date = try? closure(decoder) else {
return nil
}
dateArray.append(date)
// Delete from array after use
fieldValueArray.removeFirst()
}
return dateArray
#if swift(>=5) && !os(Linux)
@unknown default:
Log.error("Decoding strategy not found")
fatalError()
#endif
}
}
/// Helper Method to decode a string to an LosslessStringConvertible array types.
private func decodeArray<T: LosslessStringConvertible>(_ type: T.Type) -> [T]? {
let strs: [String] = self.components(separatedBy: ",")
let values: [T] = strs.map { T($0) }.filter { $0 != nil }.map { $0! }
return values.count == strs.count ? values : nil
}
/// Parses percent encoded string into query parameters with comma-separated
/// values.
var urlDecodedFieldValuePairs: [String: String] {
var result: [String: String] = [:]
for item in self.components(separatedBy: "&") {
let (key, value) = item.keyAndDecodedValue
if let value = value {
// If value already exists for this key, append it
if let existingValue = result[key] {
result[key] = "\(existingValue),\(value)"
}
else {
result[key] = value
}
}
}
return result
}
/// Splits a URL-encoded key and value pair (e.g. "foo=bar") into a tuple
/// with corresponding "key" and "value" values, with the value being URL
/// unencoded.
var keyAndDecodedValue: (key: String, value: String?) {
guard let range = self.range(of: "=") else {
return (key: self, value: nil)
}
let key = String(self[..<range.lowerBound])
let value = String(self[range.upperBound...])
let valueReplacingPlus = value.replacingOccurrences(of: "+", with: " ")
let decodedValue = valueReplacingPlus.removingPercentEncoding
if decodedValue == nil {
Log.warning("Unable to decode query parameter \(key) (coded value: \(valueReplacingPlus)")
}
return (key: key, value: decodedValue ?? valueReplacingPlus)
}
}
// ISO8601 Formatter used for formatting ISO8601 dates.
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
var _iso8601Formatter: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = .withInternetDateTime
return formatter
}()
enum DateError: Error {
case unknownStrategy
}
extension DateError: LocalizedError {
public var errorDescription: String? {
switch self {
case .unknownStrategy:
return("Date encoding or decoding strategy not known.")
}
}
}

View File

@ -0,0 +1,408 @@
/*
* Copyright IBM Corporation 2017
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
import LoggerAPI
/**
Query Parameter Decoder decodes a `[String: String]` object to a `Decodable` object instance. The decode function takes the `Decodable` object as a parameter to decode the dictionary into.
### Usage Example: ###
````swift
let dict = ["intField": "23", "stringField": "a string", "intArray": "1,2,3", "dateField": "2017-10-31T16:15:56+0000", "optionalDateField": "2017-10-31T16:15:56+0000", "nested": "{\"nestedIntField\":333,\"nestedStringField\":\"nested string\"}" ]
guard let query = try? QueryDecoder(dictionary: dict).decode(MyQuery.self) else {
print("Failed to decode query to MyQuery Object")
return
}
````
### Decoding Empty Values:
When an HTML form is sent with an empty or unchecked field, the corresponding key/value pair is sent with an empty value (i.e. `&key1=&key2=`).
The corresponding mapping to Swift types performed by `QueryDecoder` is as follows:
- Any Optional type (including `String?`) defaults to `nil`
- Non-optional `String` successfully decodes to `""`
- Non-optional `Bool` decodes to `false`
- All other non-optional types throw a decoding error
*/
public class QueryDecoder: Coder, Decoder, BodyDecoder {
/**
The coding key path.
### Usage Example: ###
````swift
let fieldName = Coder.getFieldName(from: codingPath)
````
*/
public var codingPath: [CodingKey] = []
/**
The coding user info key.
*/
public var userInfo: [CodingUserInfoKey : Any] = [:]
/**
A `[String: String]` dictionary.
*/
public var dictionary: [String : String]
// A `JSONDecoder.DateDecodingStrategy` date decoder used to determine what strategy
// to use when decoding the specific date.
private var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy
/**
Initializer with an empty dictionary for decoding from Data.
*/
public override init () {
self.dateDecodingStrategy = .formatted(Coder.defaultDateFormatter)
self.dictionary = [:]
super.init()
}
/**
Initializer with a `[String : String]` dictionary.
*/
public init(dictionary: [String : String]) {
self.dateDecodingStrategy = .formatted(Coder.defaultDateFormatter)
self.dictionary = dictionary
super.init()
}
/**
Decode URL encoded data by mapping to its Decodable object representation.
- Parameter type: The Decodable type to the Data will be decoded as.
- Parameter from: The Data to be decoded as the Decodable type.
### Usage Example: ###
````swift
guard let query = try? QueryDecoder().decode(MyQuery.self, from queryData) else {
print("Failed to decode query to MyQuery Object")
return
}
````
*/
public func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
guard let urlString = String(data: data, encoding: .utf8) else {
throw RequestError.unprocessableEntity
}
let decoder = QueryDecoder(dictionary: urlString.urlDecodedFieldValuePairs)
decoder.dateDecodingStrategy = dateDecodingStrategy
if let Q = T.self as? QueryParams.Type {
decoder.dateDecodingStrategy = Q.dateDecodingStrategy
}
return try T(from: decoder)
}
/**
Decodes a `[String: String]` mapping to its Decodable object representation.
- Parameter value: The Decodable object to decode the dictionary into.
### Usage Example: ###
````swift
guard let query = try? QueryDecoder(dictionary: expectedDict).decode(MyQuery.self) else {
print("Failed to decode query to MyQuery Object")
return
}
````
*/
public func decode<T: Decodable>(_ type: T.Type) throws -> T {
if let Q = T.self as? QueryParams.Type {
dateDecodingStrategy = Q.dateDecodingStrategy
}
let fieldName = Coder.getFieldName(from: codingPath)
let fieldValue = dictionary[fieldName]
Log.verbose("fieldName: \(fieldName), fieldValue: \(String(describing: fieldValue))")
switch type {
/// Bool
case is Bool.Type:
return try decodeType(fieldValue?.boolean, to: T.self)
/// Ints
case is Int.Type:
return try decodeType(fieldValue?.int, to: T.self)
case is Int8.Type:
return try decodeType(fieldValue?.int8, to: T.self)
case is Int16.Type:
return try decodeType(fieldValue?.int16, to: T.self)
case is Int32.Type:
return try decodeType(fieldValue?.int32, to: T.self)
case is Int64.Type:
return try decodeType(fieldValue?.int64, to: T.self)
/// Int Arrays
case is [Int].Type:
return try decodeType(fieldValue?.intArray, to: T.self)
case is [Int8].Type:
return try decodeType(fieldValue?.int8Array, to: T.self)
case is [Int16].Type:
return try decodeType(fieldValue?.int16Array, to: T.self)
case is [Int32].Type:
return try decodeType(fieldValue?.int32Array, to: T.self)
case is [Int64].Type:
return try decodeType(fieldValue?.int64Array, to: T.self)
/// UInts
case is UInt.Type:
return try decodeType(fieldValue?.uInt, to: T.self)
case is UInt8.Type:
return try decodeType(fieldValue?.uInt8, to: T.self)
case is UInt16.Type:
return try decodeType(fieldValue?.uInt16, to: T.self)
case is UInt32.Type:
return try decodeType(fieldValue?.uInt32, to: T.self)
case is UInt64.Type:
return try decodeType(fieldValue?.uInt64, to: T.self)
/// UInt Arrays
case is [UInt].Type:
return try decodeType(fieldValue?.uIntArray, to: T.self)
case is [UInt8].Type:
return try decodeType(fieldValue?.uInt8Array, to: T.self)
case is [UInt16].Type:
return try decodeType(fieldValue?.uInt16Array, to: T.self)
case is [UInt32].Type:
return try decodeType(fieldValue?.uInt32Array, to: T.self)
case is [UInt64].Type:
return try decodeType(fieldValue?.uInt64Array, to: T.self)
/// Floats
case is Float.Type:
return try decodeType(fieldValue?.float, to: T.self)
case is [Float].Type:
return try decodeType(fieldValue?.floatArray, to: T.self)
/// Doubles
case is Double.Type:
return try decodeType(fieldValue?.double, to: T.self)
case is [Double].Type:
return try decodeType(fieldValue?.doubleArray, to: T.self)
/// Dates
case is Date.Type:
switch dateDecodingStrategy {
case .deferredToDate:
guard let doubleValue = fieldValue?.double else {return try decodeType(fieldValue, to: T.self)}
return try decodeType(Date(timeIntervalSinceReferenceDate: (doubleValue)), to: T.self)
case .secondsSince1970:
guard let doubleValue = fieldValue?.double else {return try decodeType(fieldValue, to: T.self)}
return try decodeType(Date(timeIntervalSince1970: (doubleValue)), to: T.self)
case .millisecondsSince1970:
guard let doubleValue = fieldValue?.double else {return try decodeType(fieldValue, to: T.self)}
return try decodeType(Date(timeIntervalSince1970: (doubleValue)), to: T.self)
case .iso8601:
if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
guard let stringValue = fieldValue?.string else {return try decodeType(fieldValue, to: T.self)}
guard let date = _iso8601Formatter.date(from: stringValue) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Expected date string to be ISO8601-formatted."))
}
return try decodeType(date, to: T.self)
} else {
fatalError("ISO8601DateFormatter is unavailable on this platform.")
}
case .formatted(let formatted):
return try decodeType(fieldValue?.date(formatted), to: T.self)
case .custom(let closure):
return try decodeType(closure(self), to: T.self)
#if swift(>=5) && !os(Linux)
@unknown default:
throw DateError.unknownStrategy
#endif
}
case is [Date].Type:
switch dateDecodingStrategy {
case .deferredToDate:
return try decodeType(fieldValue?.dateArray(decoderStrategy: .deferredToDate), to: T.self)
case .secondsSince1970:
return try decodeType(fieldValue?.dateArray(decoderStrategy: .secondsSince1970), to: T.self)
case .millisecondsSince1970:
return try decodeType(fieldValue?.dateArray(decoderStrategy: .millisecondsSince1970), to: T.self)
case .iso8601:
if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
return try decodeType(fieldValue?.dateArray(decoderStrategy: .iso8601), to: T.self)
} else {
fatalError("ISO8601DateFormatter is unavailable on this platform.")
}
case .formatted(let formatter):
return try decodeType(fieldValue?.dateArray(formatter), to: T.self)
case .custom(let closure):
return try decodeType(fieldValue?.dateArray(decoderStrategy: .custom(closure), decoder: self), to: T.self)
#if swift(>=5) && !os(Linux)
@unknown default:
throw DateError.unknownStrategy
#endif
}
/// Strings
case is String.Type:
return try decodeType(fieldValue?.string, to: T.self)
case is [String].Type:
return try decodeType(fieldValue?.stringArray, to: T.self)
case is Operation.Type:
if let oType = type as? Operation.Type,
let value = fieldValue?.string {
let result = try oType.init(string: value)
if let castedValue = result as? T {
return castedValue
}
}
return try decodeType(fieldValue?.decodable(T.self), to: T.self)
case is Ordering.Type:
if let oType = type as? Ordering.Type,
let value = fieldValue?.string {
let result = try oType.init(string: value)
if let castedValue = result as? T {
return castedValue
}
}
return try decodeType(fieldValue?.decodable(T.self), to: T.self)
case is Pagination.Type:
if let oType = type as? Pagination.Type,
let value = fieldValue?.string {
let result = try oType.init(string: value)
if let castedValue = result as? T {
return castedValue
}
}
return try decodeType(fieldValue?.decodable(T.self), to: T.self)
default:
Log.verbose("Decoding Custom Type: \(T.Type.self)")
if fieldName.isEmpty {
return try T(from: self)
} else {
// Processing an instance member of the class/struct
return try decodeType(fieldValue?.decodable(T.self), to: T.self)
}
}
}
/**
Returns a keyed decoding container based on the key type.
### Usage Example: ###
````swift
decoder.container(keyedBy: keyType)
````
*/
public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
return KeyedDecodingContainer(KeyedContainer<Key>(decoder: self))
}
/**
Returns an unkeyed decoding container.
### Usage Example: ###
````swift
decoder.unkeyedContainer()
````
*/
public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
return UnkeyedContainer(decoder: self)
}
/**
Returns a single value decoding container based on the key type.
### Usage Example: ###
````swift
decoder.singleValueContainer()
````
*/
public func singleValueContainer() throws -> SingleValueDecodingContainer {
return UnkeyedContainer(decoder: self)
}
private func decodeType<S: Decodable, T: Decodable>(_ object: S, to type: T.Type) throws -> T {
if let values = object as? T {
return values
} else {
throw decodingError()
}
}
private func decodingError() -> DecodingError {
let fieldName = Coder.getFieldName(from: codingPath)
let errorMsg = "Could not process field named '\(fieldName)'."
Log.error(errorMsg)
let errorCtx = DecodingError.Context(codingPath: codingPath, debugDescription: errorMsg)
return DecodingError.dataCorrupted(errorCtx)
}
private struct KeyedContainer<Key: CodingKey>: KeyedDecodingContainerProtocol {
var decoder: QueryDecoder
var codingPath: [CodingKey] { return [] }
var allKeys: [Key] { return [] }
func contains(_ key: Key) -> Bool {
return decoder.dictionary[key.stringValue] != nil
}
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable {
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
return try decoder.decode(T.self)
}
// If it is not in the dictionary or it is a empty string it should be nil
func decodeNil(forKey key: Key) throws -> Bool {
return decoder.dictionary[key.stringValue]?.isEmpty ?? true
}
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
return try decoder.container(keyedBy: type)
}
func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
return try decoder.unkeyedContainer()
}
func superDecoder() throws -> Decoder {
return decoder
}
func superDecoder(forKey key: Key) throws -> Decoder {
return decoder
}
}
private struct UnkeyedContainer: UnkeyedDecodingContainer, SingleValueDecodingContainer {
var decoder: QueryDecoder
var codingPath: [CodingKey] { return [] }
var count: Int? { return nil }
var currentIndex: Int { return 0 }
var isAtEnd: Bool { return false }
func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
return try decoder.decode(type)
}
func decodeNil() -> Bool {
return true
}
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
return try decoder.container(keyedBy: type)
}
func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
return self
}
func superDecoder() throws -> Decoder {
return decoder
}
}
}

View File

@ -0,0 +1,522 @@
/*
* Copyright IBM Corporation 2017
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
import LoggerAPI
extension CharacterSet {
static let customURLQueryAllowed = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~=:&")
}
/**
Query Parameter Encoder.
Encodes an `Encodable` object to a query parameter string, a `URLQueryItemArray`, or to a `[String: String]` dictionary. The encode function takes the `Encodable` object to encode as the parameter.
### Usage Example: ###
````swift
let date = Coder().dateFormatter.date(from: "2017-10-31T16:15:56+0000")
let query = MyQuery(intField: -1, optionalIntField: 282, stringField: "a string", intArray: [1, -1, 3], dateField: date, optionalDateField: date, nested: Nested(nestedIntField: 333, nestedStringField: "nested string"))
guard let myQueryDict: [String: String] = try? QueryEncoder().encode(query) else {
print("Failed to encode query to [String: String]")
return
}
````
*/
public class QueryEncoder: Coder, Encoder, BodyEncoder {
/**
A `[String: String]` dictionary.
*/
internal var dictionary: [String: String]
internal var anyDictionary: [String: Any]
/**
The coding key path.
### Usage Example: ###
````swift
let fieldName = Coder.getFieldName(from: codingPath)
````
*/
public var codingPath: [CodingKey] = []
/**
The coding user info key.
*/
public var userInfo: [CodingUserInfoKey: Any] = [:]
// A `JSONDecoder.DateEncodingStrategy` date encoder used to determine what strategy
// to use when encoding the specific date.
private var dateEncodingStrategy: JSONEncoder.DateEncodingStrategy
/**
Initializer for the dictionary, which initializes an empty `[String: String]` dictionary.
*/
public override init() {
self.dateEncodingStrategy = .formatted(Coder.defaultDateFormatter)
self.dictionary = [:]
self.anyDictionary = [:]
super.init()
}
/**
Encodes an Encodable object to a query parameter string.
- Parameter value: The Encodable object to encode to its String representation.
### Usage Example: ###
````swift
guard let myQueryStr: String = try? QueryEncoder().encode(query) else {
print("Failed to encode query to String")
return
}
````
*/
public func encode<T: Encodable>(_ value: T) throws -> String {
let dict: [String : String] = try encode(value)
let desc: String = dict.map { key, value in "\(key)=\(value)" }
.reduce("") {pair1, pair2 in "\(pair1)&\(pair2)"}
.addingPercentEncoding(withAllowedCharacters: CharacterSet.customURLQueryAllowed)!
return "?" + String(desc.dropFirst())
}
/**
Encodes an Encodable object to Data.
- Parameter value: The Encodable object to encode to its Data representation.
### Usage Example: ###
````swift
guard let myQueryStr: Data = try? QueryEncoder().encode(query) else {
print("Failed to encode query to Data")
return
}
````
*/
public func encode<T : Encodable>(_ value: T) throws -> Data {
let dict: [String : String] = try encode(value)
let desc: String? = dict.map { key, value in "\(key)=\(value)" }
.reduce("") {pair1, pair2 in "\(pair1)&\(pair2)"}
.addingPercentEncoding(withAllowedCharacters: CharacterSet.customURLQueryAllowed)
guard let data = desc?.data(using: .utf8) else {
throw RequestError.unprocessableEntity
}
return data
}
/**
Encodes an Encodable object to a URLQueryItem array.
- Parameter value: The Encodable object to encode to its [URLQueryItem] representation.
### Usage Example: ###
````swift
guard let myQueryArray: [URLQueryItem] = try? QueryEncoder().encode(query) else {
print("Failed to encode query to [URLQueryItem]")
return
}
````
*/
public func encode<T: Encodable>(_ value: T) throws -> [URLQueryItem] {
if let Q = T.self as? QueryParams.Type {
dateEncodingStrategy = Q.dateEncodingStrategy
}
let dict: [String : String] = try encode(value)
return dict.reduce([URLQueryItem]()) { array, element in
var array = array
array.append(URLQueryItem(name: element.key, value: element.value))
return array
}
}
/**
Encodes an Encodable object to a `[String: String]` dictionary.
- Parameter value: The Encodable object to encode to its `[String: String]` representation.
### Usage Example: ###
````swift
guard let myQueryDict: [String: String] = try? QueryEncoder().encode(query) else {
print("Failed to encode query to [String: String]")
return
}
````
*/
public func encode<T: Encodable>(_ value: T) throws -> [String : String] {
let encoder = QueryEncoder()
encoder.dateEncodingStrategy = self.dateEncodingStrategy
if let Q = T.self as? QueryParams.Type {
encoder.dateEncodingStrategy = Q.dateEncodingStrategy
}
try value.encode(to: encoder)
return encoder.dictionary
}
/// Encodes an Encodable object to a String -> String dictionary
///
/// - Parameter _ value: The Encodable object to encode to its [String: String] representation
public func encode<T: Encodable>(_ value: T) throws -> [String : Any] {
let encoder = QueryEncoder()
encoder.dateEncodingStrategy = self.dateEncodingStrategy
if let Q = T.self as? QueryParams.Type {
encoder.dateEncodingStrategy = Q.dateEncodingStrategy
}
try value.encode(to: encoder)
return encoder.anyDictionary
}
/**
Returns a keyed encoding container based on the key type.
### Usage Example: ###
````swift
encoder.container(keyedBy: keyType)
````
*/
public func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
return KeyedEncodingContainer(KeyedContainer<Key>(encoder: self))
}
/**
Returns an unkeyed encoding container.
### Usage Example: ###
````swift
encoder.unkeyedContainer()
````
*/
public func unkeyedContainer() -> UnkeyedEncodingContainer {
return UnkeyedContainer(encoder: self)
}
/**
Returns an single value encoding container based on the key type.
### Usage Example: ###
````swift
encoder.singleValueContainer()
````
*/
public func singleValueContainer() -> SingleValueEncodingContainer {
return UnkeyedContainer(encoder: self)
}
/// Decode a value for the current field, determined by this encoder's state (codingPath). Some
/// paths through this function are recursive (for handling custom Date encodings).
///
/// Both the keyed and unkeyed containers call this function. The keyed container first sets the
/// encoder's codingPath, which determines the field name we encode.
///
/// If a custom encoding is defined for Date, the custom closure will call this encoder back. It
/// is expected that any such custom encoding produces a single value, calling back via the
/// unkeyed container.
///
/// When custom encoding Date arrays, this function will be invoked multiple times for the same
/// key. The =+= operator is used to build a comma-separated list of values for a key.
internal func _encode<T: Encodable>(value: T) throws {
let encoder = self
let fieldName = Coder.getFieldName(from: encoder.codingPath)
switch value {
/// Ints
case let fieldValue as Int:
encoder.dictionary[fieldName] =+= String(fieldValue)
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as Int8:
encoder.dictionary[fieldName] =+= String(fieldValue)
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as Int16:
encoder.dictionary[fieldName] =+= String(fieldValue)
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as Int32:
encoder.dictionary[fieldName] =+= String(fieldValue)
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as Int64:
encoder.dictionary[fieldName] =+= String(fieldValue)
encoder.anyDictionary[fieldName] = fieldValue
/// Int Arrays
case let fieldValue as [Int]:
let strs: [String] = fieldValue.map { String($0) }
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as [Int8]:
let strs: [String] = fieldValue.map { String($0) }
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as [Int16]:
let strs: [String] = fieldValue.map { String($0) }
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as [Int32]:
let strs: [String] = fieldValue.map { String($0) }
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as [Int64]:
let strs: [String] = fieldValue.map { String($0) }
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
/// UInts
case let fieldValue as UInt:
encoder.dictionary[fieldName] =+= String(fieldValue)
encoder.anyDictionary[fieldName] = fieldValue
/// Int Arrays
case let fieldValue as UInt8:
encoder.dictionary[fieldName] =+= String(fieldValue)
encoder.anyDictionary[fieldName] = fieldValue
/// Int Arrays
case let fieldValue as UInt16:
encoder.dictionary[fieldName] =+= String(fieldValue)
encoder.anyDictionary[fieldName] = fieldValue
/// Int Arrays
case let fieldValue as UInt32:
encoder.dictionary[fieldName] =+= String(fieldValue)
encoder.anyDictionary[fieldName] = fieldValue
/// Int Arrays
case let fieldValue as UInt64:
encoder.dictionary[fieldName] =+= String(fieldValue)
encoder.anyDictionary[fieldName] = fieldValue
/// Int Arrays
/// UInt Arrays
case let fieldValue as [UInt]:
let strs: [String] = fieldValue.map { String($0) }
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
/// Int Arrays
case let fieldValue as [UInt8]:
let strs: [String] = fieldValue.map { String($0) }
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as [UInt16]:
let strs: [String] = fieldValue.map { String($0) }
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as [UInt32]:
let strs: [String] = fieldValue.map { String($0) }
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as [UInt64]:
let strs: [String] = fieldValue.map { String($0) }
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
/// Floats
case let fieldValue as Float:
encoder.dictionary[fieldName] =+= String(fieldValue)
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as [Float]:
let strs: [String] = fieldValue.map { String($0) }
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
/// Doubles
case let fieldValue as Double:
encoder.dictionary[fieldName] =+= String(fieldValue)
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as [Double]:
let strs: [String] = fieldValue.map { String($0) }
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
/// Bools
case let fieldValue as Bool:
encoder.dictionary[fieldName] = String(fieldValue)
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as [Bool]:
let strs: [String] = fieldValue.map { String($0) }
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
/// Strings
case let fieldValue as String:
encoder.dictionary[fieldName] =+= fieldValue
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as [String]:
encoder.dictionary[fieldName] = fieldValue.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
/// Dates
case let fieldValue as Date:
switch encoder.dateEncodingStrategy {
case .formatted(let formatter):
encoder.dictionary[fieldName] = formatter.string(from: fieldValue)
encoder.anyDictionary[fieldName] = fieldValue
case .deferredToDate:
let date = NSNumber(value: fieldValue.timeIntervalSinceReferenceDate)
encoder.dictionary[fieldName] = date.stringValue
encoder.anyDictionary[fieldName] = fieldValue
case .secondsSince1970:
let date = NSNumber(value: fieldValue.timeIntervalSince1970)
encoder.dictionary[fieldName] = date.stringValue
encoder.anyDictionary[fieldName] = fieldValue
case .millisecondsSince1970:
let date = NSNumber(value: 1000 * fieldValue.timeIntervalSince1970)
encoder.dictionary[fieldName] = date.stringValue
encoder.anyDictionary[fieldName] = fieldValue
case .iso8601:
if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
encoder.dictionary[fieldName] = _iso8601Formatter.string(from: fieldValue)
encoder.anyDictionary[fieldName] = fieldValue
} else {
fatalError("ISO8601DateFormatter is unavailable on this platform.")
}
case .custom(let closure):
try closure(fieldValue, encoder)
#if swift(>=5) && !os(Linux)
@unknown default:
throw DateError.unknownStrategy
#endif
}
case let fieldValue as [Date]:
switch encoder.dateEncodingStrategy {
case .deferredToDate:
let dbs: [NSNumber] = fieldValue.map { NSNumber(value: $0.timeIntervalSinceReferenceDate) }
let strs: [String] = dbs.map { ($0).stringValue}
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
case .secondsSince1970:
let dbs: [NSNumber] = fieldValue.map { NSNumber(value: $0.timeIntervalSince1970) }
let strs: [String] = dbs.map { ($0).stringValue}
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
case .millisecondsSince1970:
let dbs: [NSNumber] = fieldValue.map { NSNumber(value: ($0.timeIntervalSince1970)/1000) }
let strs: [String] = dbs.map { ($0).stringValue}
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
case .iso8601:
if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
let strs: [String] = fieldValue.map { _iso8601Formatter.string(from: $0) }
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
} else {
fatalError("ISO8601DateFormatter is unavailable on this platform.")
}
case .formatted(let formatter):
let strs: [String] = fieldValue.map { formatter.string(from: $0) }
encoder.dictionary[fieldName] = strs.joined(separator: ",")
encoder.anyDictionary[fieldName] = fieldValue
// This calls us back with each serialized element individually, with the same fieldName key,
// which builds a comma-separated list using the '=+=' operator.
case .custom(let closure):
for element in fieldValue {
try closure(element, encoder)
}
#if swift(>=5) && !os(Linux)
@unknown default:
throw DateError.unknownStrategy
#endif
}
case let fieldValue as Operation:
encoder.dictionary[fieldName] = fieldValue.getStringValue()
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as Ordering:
encoder.dictionary[fieldName] = fieldValue.getStringValue()
encoder.anyDictionary[fieldName] = fieldValue
case let fieldValue as Pagination:
encoder.dictionary[fieldName] = fieldValue.getStringValue()
encoder.anyDictionary[fieldName] = fieldValue
default:
if fieldName.isEmpty {
encoder.dictionary = [:] // Make encoder instance reusable
encoder.anyDictionary = [:] // Make encoder instance reusable
try value.encode(to: encoder)
} else {
do {
let jsonData = try JSONEncoder().encode(value)
encoder.dictionary[fieldName] = String(data: jsonData, encoding: .utf8)
encoder.anyDictionary[fieldName] = jsonData
} catch let error {
throw encoder.encodingError(value, underlyingError: error)
}
}
}
}
internal func encodingError(_ value: Any, underlyingError: Swift.Error?) -> EncodingError {
let fieldName = Coder.getFieldName(from: codingPath)
let errorCtx = EncodingError.Context(codingPath: codingPath, debugDescription: "Could not process field named '\(fieldName)'.", underlyingError: underlyingError)
return EncodingError.invalidValue(value, errorCtx)
}
private struct KeyedContainer<Key: CodingKey>: KeyedEncodingContainerProtocol {
var encoder: QueryEncoder
var codingPath: [CodingKey] { return [] }
/// The typical path for encoding a QueryParams (keyed) type. This encode will be called
/// for each field in turn.
func encode<T>(_ value: T, forKey key: Key) throws where T : Encodable {
self.encoder.codingPath.append(key)
defer { self.encoder.codingPath.removeLast() }
try encoder._encode(value: value)
}
func encodeNil(forKey: Key) throws {}
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
return encoder.container(keyedBy: keyType)
}
func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
return encoder.unkeyedContainer()
}
func superEncoder() -> Encoder {
return encoder
}
func superEncoder(forKey key: Key) -> Encoder {
return encoder
}
}
private struct UnkeyedContainer: UnkeyedEncodingContainer, SingleValueEncodingContainer {
var encoder: QueryEncoder
var codingPath: [CodingKey] { return [] }
var count: Int { return 0 }
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
return encoder.container(keyedBy: keyType)
}
func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
return self
}
func superEncoder() -> Encoder {
return encoder
}
func encodeNil() throws {}
/// This unkeyed encode will be called by a custom Date encoder. The correct key (field
/// name) will already have been set by a call to the KeyedEncodingContainer.
func encode<T>(_ value: T) throws where T : Encodable {
try encoder._encode(value: value)
}
}
}
// The '=+=' operator builds a comma-separated list of values for a given fieldName when encoding a [Date] that uses a custom formatting.
infix operator =+=
func =+= (lhs: inout String?, rhs: String) {
if let lhsValue = lhs {
lhs = lhsValue + "," + rhs
} else {
lhs = rhs
}
}

File diff suppressed because it is too large Load Diff