├── .project
├── Kitura
├── Proposals
│ ├── .gitkeep
│ ├── 0001-codable-routing.md
│ ├── 0002-query-parameters-enhancement.md
│ └── 0003-codable-query-parameters.md
├── README.md
├── Releases
│ └── .gitkeep
└── status.md
├── Other
├── Proposals
│ └── .gitkeep
├── README.md
├── Releases
│ └── .gitkeep
└── status.md
├── README.md
└── template.md
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | evolution
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Kitura/Proposals/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IBM-Swift/evolution/0f004e6ba9fa05515fdad5d55dd04b8e097a3471/Kitura/Proposals/.gitkeep
--------------------------------------------------------------------------------
/Kitura/Proposals/0001-codable-routing.md:
--------------------------------------------------------------------------------
1 | ## Codable Routing
2 | * Proposal: KIT-0001
3 | * Authors: [Chris Bailey](https://github.com/seabaylea), [Mike Tunnicliffe](https://github.com/tunniclm), [Ricardo Olivieri](https://github.com/rolivieri)
4 | * Review Manager: [Lloyd Roseblade](https://github.com/lroseblade)
5 | * Status: DRAFT
6 | * Implementation: Link to branch
7 | * Previous Revision: 1
8 | * Previous Proposal: NNNN
9 |
10 | ### Introduction
11 | The Kitura router currently requires you to interact with `RouterRequest` and `RouterResponse` types. This means inspecting and setting the correct HTTP headers, and working with the enclosed request and response body data directly, including carrying out data validation, error checking, JSON parsing, and setting the correct HTTP response codes.
12 |
13 | This proposal adds a new set of convenience APIs that provides an abstraction layer for the developer, where the framework deals with data validation and processing, and provides on requested concrete Swift types to the application code.
14 |
15 | ### Motivation
16 | The current API provided by the Kitura Router provides the developer with significant amounts of flexibility and low level control. This, however, also means that the developer must write relatively complex code to carry out simple tasks. For example, the following is a "best practice" implementation of a RESTful API for a `POST` request on `/` that expects to receive a `Name` object as a JSON payload, defined as follows:
17 |
18 | ```
19 | {
20 | "name": String
21 | }
22 | ```
23 |
24 | and that returns the same object (once its successfully stored on the server):
25 |
26 | ```swift
27 | router.all("/*", middleware: BodyParser())
28 | router.post("/", handler: handlePost)
29 |
30 | func handlePost(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) {
31 | // Check that the HTTP "Content-Type" header is correctly set to "application/json"
32 | // If not return `.unsupportedMediaType`
33 | guard let contentType = request.headers["Content-Type"], contentType.hasPrefix("application/json") else {
34 | response.status(.unsupportedMediaType)
35 | response.send(json: JSON([ "error": "Request Content-Type must be application/json" ]))
36 | return next()
37 | }
38 |
39 | // Check that the body data could be parsed as JSON or return `.badRequest`
40 | guard case .json(let json)? = request.body else {
41 | response.status(.badRequest)
42 | response.send(json: JSON([ "error": "Request body could not be parsed as JSON" ]))
43 | return next()
44 | }
45 |
46 | // Validate the JSON objects matches the expected fields and types
47 | // Return `.unprocessableEntity` for any errors
48 | guard json["name"].exists() else {
49 | response.status(.unprocessableEntity)
50 | response.send(json: JSON([ "error": "Missing reqired property `name`" ]))
51 | next()
52 | }
53 | guard let name = json["name"].string else {
54 | response.status(.unprocessableEntity)
55 | response.send(json: JSON([ "error": "Property type mismatch, `name` is a json["name"].description but expected String" ]))
56 | next()
57 | }
58 |
59 | // Finally have our single field from the JSON object
60 | let receivedName = name
61 |
62 | //
63 | // Insert the application logic code here
64 | //
65 |
66 | // Return the Name object as JSON
67 | response.send(json: ["name": receivedname])
68 | }
69 | ```
70 |
71 | ### Proposed solution
72 | This proposal covers an additional layer of API that provides abstraction and convenience over the existing lower level API. This new layer additionally leverages the new `Codable` capabilities delivered in Swift 4, allowing the developer to work solely with types that conform to `Codable`.
73 |
74 | Utilizing the new API layer, the above code becomes:
75 |
76 | ```swift
77 | struct Name: Codable {
78 | let name: String
79 | }
80 |
81 | router.post("/", handler: handlePost)
82 |
83 | func handlePost(name: Name, completion: (Name?, Error?) -> Void) {
84 |
85 | //
86 | // Insert the application logic code here
87 | //
88 |
89 | completion(name, error)
90 | }
91 | ```
92 |
93 | Here the developer has specified the `Codable` types that the handler expects and that it will return via a completion handler, while the framework handles HTTP header checks, encoding/decoding, data validation, and the setting of correct HTTP response codes.
94 |
95 | ### Detailed design
96 |
97 | #### Codable Routing
98 | The new API adds the ability to specify `Codable` route handlers that denote the types the handler expects to receive and that it will respond with. The framework is therefore expected to validate that the incoming request body can be converted to this type and reject non-conforming requests with the correct error codes.
99 |
100 | Below is the new API specification for Codable routes:
101 |
102 | ```swift
103 | extension Router {
104 |
105 | // POST: handler receives body data as a Codable and responds with a Codable via the completion handler
106 | post(_ route: String, handler: @escaping (Codable, completion: @escaping (Codable?, Error?) -> Void) -> Void)
107 |
108 | // GET: handler receives no body data and responds with an array of Codable via the completion handler
109 | get(_ route: String, handler: @escaping (completion: @escaping ([Codable]?, Error?) -> Void) -> Void)
110 |
111 | // DELETE handler receives no body data and responds by calling the completion handler with no data
112 | delete(_ route: String, handler: @escaping (completion: @escaping (Error?) -> Void) -> Void)
113 |
114 | }
115 | ```
116 |
117 | This however is limited, as most RESTful APIs also use URL encoded data. For example:
118 | ```
119 | GET /user/1
120 | ```
121 | would typically be used to retrieve data associated with a user uniquely identified by ID 1.
122 |
123 | #### Identifier Routing
124 | These use cases are handled by providing the additional ability for the developer to request that a type that conforms to `Identifier` is also passed to the handler, for example:
125 |
126 | ```swift
127 | struct UserId: Identifier {
128 | public let id: Int
129 | public init(value: String) throws {
130 | if let id = Int(value) {
131 | self.id = id
132 | } else {
133 | throw TypeError.unidentifiable
134 | }
135 | }
136 | }
137 |
138 | router.get("/user", handler: handleGetUser)
139 |
140 | func handleGetUser(id: UserID, completion: (User?, Error?) -> Void) {
141 | completion(userStore[id.id], nil)
142 | }
143 | ```
144 |
145 | Here the `Identifier` struct provides a constructor that accepts a String value. This is called by the Router with the value encoded in the root URL.
146 |
147 | Below is the new API specification for additionally `Identifier` routes:
148 |
149 | ```swift
150 | // GET: handler receives an Identifier and responds with a Codable via the completion handler
151 | get(_ route: String, handler: @escaping (Identifier, completion: @escaping (Codable?, Error?) -> Void) -> Void)
152 |
153 | // PUT: handler receives an Identifier and responds with a Codable via the completion handler
154 | put(_ route: String, handler: @escaping (Identifier, completion: @escaping (Codable?, Error?) -> Void) -> Void)
155 |
156 | // PATCH: handler receives an Identifier and responds with a Codable via the completion handler
157 | patch(_ route: String, handler: @escaping (Identifier, completion: @escaping (Codable?, Error?) -> Void) -> Void)
158 |
159 | // DELETE: handler receives an Identifier and responds by calling the completion handler with no data
160 | delete(_ route: String, handler: @escaping (Identifier, completion: @escaping (Error?) -> Void) -> Void)
161 | ```
162 | **Note:** This is a simplification of how `Identifier` is intended to work. It will be covered in more detail in a subsequent proposal.
163 |
164 | #### Error Handling
165 | For each of the Codable and Identifier routes, the handler has the ability to return an error. This provides the handler with a mechanism to report an error which affects the headers and return codes used in the underlying HTTP response. The following error values are available, which matches the possible HTTP status codes:
166 |
167 | ```swift
168 | public enum RouteHandlerError: Int, Error {
169 | case accepted = 202, badGateway = 502, badRequest = 400, conflict = 409, `continue` = 100, created = 201
170 | case expectationFailed = 417, failedDependency = 424, forbidden = 403, gatewayTimeout = 504, gone = 410
171 | case httpVersionNotSupported = 505, insufficientSpaceOnResource = 419, insufficientStorage = 507
172 | case internalServerError = 500, lengthRequired = 411, methodFailure = 420, methodNotAllowed = 405
173 | case movedPermanently = 301, movedTemporarily = 302, multiStatus = 207, multipleChoices = 300
174 | case networkAuthenticationRequired = 511, noContent = 204, nonAuthoritativeInformation = 203
175 | case notAcceptable = 406, notFound = 404, notImplemented = 501, notModified = 304, OK = 200
176 | case partialContent = 206, paymentRequired = 402, preconditionFailed = 412, preconditionRequired = 428
177 | case proxyAuthenticationRequired = 407, processing = 102, requestHeaderFieldsTooLarge = 431
178 | case requestTimeout = 408, requestTooLong = 413, requestURITooLong = 414, requestedRangeNotSatisfiable = 416
179 | case resetContent = 205, seeOther = 303, serviceUnavailable = 503, switchingProtocols = 101
180 | case temporaryRedirect = 307, tooManyRequests = 429, unauthorized = 401, unprocessableEntity = 422
181 | case unsupportedMediaType = 415, useProxy = 305, misdirectedRequest = 421, unknown = -1
182 | }
183 | ```
184 | **To Do:** Not all of these need to be supported. For example the scenarios where `.httpVersionNotSupported` would be relevant should be handled by the Codable router itself.
185 |
186 | ### Alternatives considered
187 |
188 | #### Blocking/Synchronous API
189 | Whilst a radical simplification over the work that a developer previously needed to do in order to implement a RESTful API, the new Codable Router APIs look, at least initially, to be convoluted and non-intuative. This is largely driven by the specification of two closures:
190 |
191 | 1. The handler closure, which provides the application logic to be called.
192 | 2. The completion closure, which the application logic uses to provide an asynchronous response.
193 |
194 | The API could therefore be simplified by removing the completion closure (#2) and having the handler (#1) return the required result, e.g.:
195 |
196 | ```swift
197 | // POST: handler receives body data as a Codable and responds with a Codable via the completion handler
198 | post(_ route: String, handler: @escaping (Codable, completion: @escaping (Codable?, Error?) -> Void) -> Void)
199 | ```
200 |
201 | becomes:
202 |
203 | ```swift
204 | // POST: handler receives body data as a Codable and responds with a Codable via the completion handler
205 | post(_ route: String, handler: @escaping (Codable) throws -> Codable)
206 | ```
207 | This, however, comes as the cost of being able to do asynchronous processing.
208 |
209 | It is conceivable to support both types of API, however doing so adds an additional level of complexity for the developer, who now has to understand the differences between the two APIs and choose which to use. This could however be partially mitigated by using the approach that is common in Node.js, which is to add a `sync` identifier to the function name, eg:
210 |
211 | ```swift
212 | // POST: handler receives body data as a Codable and responds with a Codable via the completion handler
213 | postSync(_ route: String, handler: @escaping (Codable) throws -> Codable)
214 | ```
215 |
216 | Whilst taking this approach is an option, this proposal does not cover adding these `sync` APIs.
217 |
218 | #### Promises
219 | The callback-based API could be simplified using Promises so that the handlers return a Promise instead of calling the callback:
220 |
221 | ```swift
222 | // POST: handler receives body data as a Codable and responds with a Codable via the completion handler
223 | post(_ route: String, handler: @escaping (Codable, completion: @escaping (Codable?, Error?) -> Void) -> Void)
224 | ```
225 |
226 | becomes:
227 |
228 | ```swift
229 | // POST: handler receives body data as a Codable and responds with a Codable via the completion handler
230 | post(_ route: String) -> Promise
231 | ```
232 |
233 | This would simplify the method signature and error handling.
234 |
235 | However, Swift does not have built-in promises and whilst there are A+ promises compliant libraries (like PromiseKit) there is no abstraction available over the different implementations. We would have to be opinionated and choose a particular library which limits the options of users. Swift is likely to add async-await to the language (and perhaps Promises to Foundation as well) in the near future so it is prudent to continue with the existing callback-based approach we have been using so far so we do not introduce an API that could be incompatible with future Swift updates.
236 |
237 | #### Result enum instead of optionals in callback
238 | The callback-based API has a callback that takes an optional Codable and optional Error as parameters. This introduces 4 combinations where one or the other of these is nil/present. Only 2 of these combinations is valid (we never want an Error *and* and Codable, and we never want both to be nil).
239 |
240 | In order to remove these cases we could use a generic Result enum, like:
241 |
242 | ```swift
243 | enum Result {
244 | case success(T)
245 | case failure(Error)
246 | }
247 | ```
248 |
249 | giving a POST implementation like this:
250 |
251 | ```swift
252 | // POST: handler receives body data as a Codable and responds with a Codable via the completion handler
253 | post(_ route: String, handler: @escaping (Codable, completion: @escaping (Result) -> Void) -> Void)
254 | ```
255 |
256 | This would enforce that you either have a Codable or you have an Error.
257 |
258 | Whilst this pattern is used in a some libraries for implementing callbacks, it is more common to use the 2 optionals approach and looking at the current proposals for async-await it looks likely there will be a migration path provided to make this pattern compatible with async-await (much as `PromiseKit.wrap()` provides).
259 |
260 | ### Further Work
261 | As noted above, a further proposal is required to cover the implementation of the `Identifier` protocol in-depth. Additionally, Route handlers typically use two other types of data that are not covered by this proposal:
262 |
263 | 1. Authenticated user information.
264 | 2. URL encoded parameters.
265 |
266 | These will also be covered in subsequent proposals, however the expectation is that these will take a similar approach, with additional parameter types requested by the developer being passed to their route handlers.
267 |
268 | ### Feedback
269 | Feedback should be via either (or both) of the following routes:
270 |
271 | 1. The following GitHub issue:
272 | https://github.com/IBM-Swift/evolution/issues/2
273 | 2. The "open-playback" channel in the Swift@IBM Slack:
274 | https://swift-at-ibm-slack.mybluemix.net/
275 |
--------------------------------------------------------------------------------
/Kitura/Proposals/0002-query-parameters-enhancement.md:
--------------------------------------------------------------------------------
1 | ## API Improvements for Query Parameters
2 | * Proposal: KIT-0002
3 | * Authors: [Ricardo Olivieri](https://github.com/rolivieri), [Chris Bailey](https://github.com/seabaylea)
4 | * Review Manager: [Lloyd Roseblade](https://github.com/lroseblade)
5 | * Status: DRAFT
6 | * Previous Revision: 1
7 | * Previous Proposal: N/A
8 |
9 | ### Introduction
10 | Applications that leverage the traditional Raw Routing APIs in the Kitura framework can obtain URL encoded [query parameters](https://en.wikipedia.org/wiki/Query_string) provided in an HTTP request by accessing the `queryParameters` field in the `RouterRequest` type. The `queryParameters` field is a dictionary that is of the `[String : String]` type.
11 |
12 | This proposal seeks to improve the current API so that applications can extract, without much effort, the values from the `queryParameters` field in the desired type (e.g. `Int`, `Float`, `Array`, etc.).
13 |
14 | ### Motivation
15 | With the current API, application developers implement code similar to what you see below in order to process query parameters provided to their applications:
16 |
17 | ```swift
18 | router.get("/employees") { (request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) in
19 | let params = request.queryParameters
20 | if let value: String = params["countries"] as? String {
21 | let countries: [String] = value.components(separatedBy: ",")
22 | ...
23 | }
24 |
25 | if let value: String = params["level"] as? String, let level = Int(value) {
26 | ...
27 | }
28 |
29 | if let value: String = params["ratings"] as? String {
30 | let strs: [String] = value.components(separatedBy: ",")
31 | let floats: [Float] = strs.map { Float($0) }.filter { $0 != nil }.map { $0! }
32 | if floats.count == strs.count {
33 | ...
34 | }
35 | }
36 |
37 | ...
38 | }
39 | ```
40 |
41 | Though the code shown above is not complex, there is a considerable amount of boilerplate code that developers need to write, test, and maintain.
42 |
43 | ### Proposed solution
44 | This proposal covers augmenting the API in Kitura `2.x` for extracting query parameter values. With the new proposed API, the above code becomes:
45 |
46 | ```swift
47 | router.get("/employees") { (request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) in
48 | let params = request.queryParameters
49 | if let countries: [String] = params["countries"]?.stringArray {
50 | ...
51 | }
52 |
53 | if let level: Int = params["level"]?.int {
54 | ...
55 | }
56 |
57 | if let ratings: [Float] = params["ratings"]?.floatArray {
58 | ...
59 | }
60 |
61 | ...
62 | }
63 | ```
64 |
65 | Here the developer can obtain a query parameter in the desired type by simply invoking the corresponding computed property. The amount of boilerplate code is significantly reduced with this new API and it is less error prone. The proposed API adds convenience for developers and, at the same time, allows the framework do the heavy lifting, on behalf of developers, for processing the query parameter values out of the `[String : String]` dictionary.
66 |
67 | ### Detailed design
68 |
69 | An internal extension to the `String` type will be added to the Kitura framework that provides the capabilities described in previous sections of this proposal. Hence, this will be a non-breaking API change. A possible implementation for the extension is shown next:
70 |
71 | ```swift
72 | extension String {
73 |
74 | public var int: Int? {
75 | return Int(self)
76 | }
77 |
78 | public var uInt: UInt? {
79 | return UInt(self)
80 | }
81 |
82 | public var float: Float? {
83 | return Float(self)
84 | }
85 |
86 | public var double: Double? {
87 | return Double(self)
88 | }
89 |
90 | public var boolean: Bool? {
91 | return Bool(self)
92 | }
93 |
94 | public var string: String {
95 | get { return self }
96 | }
97 |
98 | public var intArray: [Int]? {
99 | let strs: [String] = self.components(separatedBy: ",")
100 | let ints: [Int] = strs.map { Int($0) }.filter { $0 != nil }.map { $0! }
101 | if ints.count == strs.count {
102 | return ints
103 | }
104 | return nil
105 | }
106 |
107 | public var uIntArray: [Int]? {
108 | let strs: [String] = self.components(separatedBy: ",")
109 | let uInts: [Int] = strs.map { Int($0) }.filter { $0 != nil }.map { $0! }
110 | if uInts.count == strs.count {
111 | return uInts
112 | }
113 | return nil
114 | }
115 |
116 | public var floatArray: [Float]? {
117 | let strs: [String] = self.components(separatedBy: ",")
118 | let floats: [Float] = strs.map { Float($0) }.filter { $0 != nil }.map { $0! }
119 | if floats.count == strs.count {
120 | return floats
121 | }
122 | return nil
123 | }
124 |
125 | public var doubleArray: [Double]? {
126 | let strs: [String] = self.components(separatedBy: ",")
127 | let doubles: [Double] = strs.map { Double($0) }.filter { $0 != nil }.map { $0! }
128 | if doubles.count == strs.count {
129 | return doubles
130 | }
131 | return nil
132 | }
133 |
134 | public var stringArray: [String] {
135 | let strs: [String] = self.components(separatedBy: ",")
136 | return strs
137 | }
138 |
139 | public func decodable(_ type: T.Type) -> T? {
140 | guard let data = self.data(using: .utf8) else {
141 | return nil
142 | }
143 | let obj: T? = try? JSONDecoder().decode(type, from: data)
144 | return obj
145 | }
146 |
147 | public func date(_ formatter: DateFormatter) -> Date? {
148 | return formatter.date(from: self)
149 | }
150 |
151 | public func dateArray(_ formatter: DateFormatter) -> [Date]? {
152 | let strs: [String] = self.components(separatedBy: ",")
153 | let dates = strs.map { formatter.date(from: $0) }.filter { $0 != nil }.map { $0! }
154 | if dates.count == strs.count {
155 | return dates
156 | }
157 | return nil
158 | }
159 | ```
160 |
161 | ### Feedback
162 | Feedback should be via either (or both) of the following routes:
163 |
164 | 1. The following GitHub issue:
165 | https://github.com/IBM-Swift/evolution/issues/6
166 | 2. The "open-playback" channel in the Swift@IBM Slack:
167 | https://swift-at-ibm-slack.mybluemix.net/
168 |
--------------------------------------------------------------------------------
/Kitura/Proposals/0003-codable-query-parameters.md:
--------------------------------------------------------------------------------
1 | ## Query Parameters for Codable Routing
2 | * Proposal: KIT-0003
3 | * Authors: [Ricardo Olivieri](https://github.com/rolivieri), [Chris Bailey](https://github.com/seabaylea)
4 | * Review Manager: [Lloyd Roseblade](https://github.com/lroseblade)
5 | * Status: DRAFT
6 | * Previous Revision: 1
7 | * Previous Proposal: N/A
8 |
9 | ### Introduction
10 | The latest version of [Kitura](https://github.com/IBM-Swift/Kitura) (which at the time of writing is `2.0.2`) provides a new set of "Codable Routing" APIs that developers can leverage for implementing route handlers that take advantage of the new [`Codable`](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types) protocol that was made available with the release of [Swift 4](https://swift.org/blog/swift-4-0-released/). This new set of Kitura APIs provides developers with an abstraction layer, where the framework hides the complexity of processing the HTTP request and HTTP response objects and makes available the requested concrete Swift types to the application code.
11 |
12 | This proposal seeks to augment the Codable Routing APIs so that [query parameters](https://en.wikipedia.org/wiki/Query_string) can be provided to the application code in a type-safe manner, without requiring the developer to understand the underlying pinnings of HTTP requests and the composition of query strings.
13 |
14 | ### Motivation
15 | Though the Codable Routing APIs recently introduced in Kitura did simplify greatly the effort for processing incoming HTTP requests, these APIs currently do not provide to the application code the values that make up a query string included in an HTTP request.
16 |
17 | For example, it is common to provide a REST API that allows the client to make a request that includes a set of filters as query parameters. In the case below the client application is making an HTTP `GET` request for a list of employees, but also requesting that the result is filtered by countries, position, and level using a URL encoded query string:
18 |
19 | ```
20 | GET http://localhost:8080/employees?countries=US,UK&position=developer&level=55
21 | ```
22 |
23 | The current Codable Routing APIs however do not provide a mechanism for accessing the query parameters. For example, let's take a look at the following snippet of code:
24 |
25 | ```swift
26 | router.get("/employees") { (respondWith: ([Employee]?, RequestError?) -> Void) in
27 | // Get employee objects (no filtering)
28 | let employees = employeeStore.map({ $0.value })
29 | respondWith(employees, nil)
30 | }
31 | ```
32 |
33 | This only allows a client to submit an HTTP `GET` request against the `http://localhost:8080/employees` URL and for the server to return a list of `Employee` entities.
34 |
35 | Currently the only approach available for implementing a route with URL encoded query parameters is to fall back to the traditional "Raw Routing" APIs, requiring the interaction with `RouterRequest` and `RouterResponse` objects in your application handlers. This removes all of the ease of use and types safety advantages of using Codable Routing.
36 |
37 | ### Proposed solution
38 | This proposal covers augmenting the new Codable Routing APIs in Kitura `2.x` to make the keys and values contained in query strings available to the application code in a type-safe manner. With the new proposed addition to the Codable Routing APIs, the above code becomes:
39 |
40 | ```swift
41 | router.get("/employees") { (query: EmployeeQuery, respondWith: ([Employee]?, RequestError?) -> Void) in
42 | // Filter data using query parameters provided to the application
43 | let employees = employeeStore.map({ $0.value }).filter( {
44 | ( $0.level == query.level &&
45 | $0.position == query.position &&
46 | query.countries.index(of: $0.country) != nil )
47 | })
48 |
49 | // Return list of employees
50 | respondWith(employees, nil)
51 | }
52 | ```
53 |
54 | Here the developer has specified the Swift type (e.g. `EmployeeQuery`) that the handler closure expects to encapsulate the query parameters provided in an incoming HTTP request. Using the new proposed APIs, developers define a Swift type that conforms to the `Codable` protocol and encapsulates the fields that make up the query parameters for a corresponding route (e.g. `/employees`). In the sample above, the Swift type named `EmployeeQuery` encapsulates the different fields (i.e. `countries`, `position`, and `level`) that are part of the query string sent from a client application (e.g. `?countries=US,UK&position=developer&level=55`).
55 |
56 | ```swift
57 | public struct EmployeeQuery: Codable {
58 | public let countries: [String]
59 | public let position: String
60 | public let level: Int
61 | }
62 | ```
63 |
64 | Note that the fields that make up the Swift type can be optionals (see [`Detailed design`](#detailed-design) for further details).
65 |
66 | Using a concrete Swift type as the embodiment for query parameters provides type safety to developers and, at the same time, allows the framework do the heavy lifting, on behalf of developers, for parsing the query string, extracting the key-value pairs, and transforming these into the expected data types.
67 |
68 | Though covering changes to [KituraKit](https://github.com/IBM-Swift/KituraKit) is beyond the scope of this proposal, it is worth mentioning that in addition to the proposed APIs described here for Kitura, we also propose augmenting the Codable Routing APIs in KituraKit. These changes will allow developers denote, on the client side, the Swift type that enscapsulates the query parameters that should be included in an HTTP `GET` and HTTP `DELETE` request that comes out from KituraKit. Consequently, developers will be able share the definition of this Swift type between the client and the server. The enchancements to the APIs in KituraKit also include capabilities for encoding the Swift type into the corresponding query string that will appear in the outbound HTTP request:
69 |
70 | ```swift
71 | func get(_ route: String, query: Q, respondWith: @escaping CodableArrayResultClosure)
72 | func delete(_ route: String, query: Q, respondWith: @escaping ResultClosure)
73 | ```
74 |
75 | ### Detailed design
76 |
77 | #### Prerequisites
78 | This proposal requires the implementation of [KIT-0002](./0002-query-parameters-enhancement).
79 |
80 | #### Codable routing and query parameters
81 | The new addition to the Codable Routing APIs in Kitura adds the ability to specify Codable route handlers that denote the concrete Swift type the handler expects to receive as the embodiment for query parameters. Therefore, the framework is expected to validate that the elements found in the query string of the incoming HTTP request can be converted to their corresponding types (e.g. `String`, `Int`, `Float`, `Double`, etc.) and reject non-conforming requests.
82 |
83 | Below is the new API specifications for augmenting the Codable Routing API routes in Kitura (note that we intend to augment the APIs for the HTTP `GET` and HTTP `DELETE` methods with an additional Swift type for wrapping query parameters):
84 |
85 | ```swift
86 | extension Router {
87 |
88 | // GET: handler receives query object, receives no body data, and responds with an array of Codable entities
89 | func get(_ route: String, handler: @escaping (Q, ([O]?, RequestError?) -> Void) -> Void)
90 |
91 | // DELETE: handler receives query object, receives no body data, and responds with no data
92 | func delete(_ route: String, handler: @escaping (Q, (RequestError?) -> Void) -> Void)
93 | }
94 | ```
95 |
96 | #### Decoding Swift types at runtime
97 | As described above, for a developer to take advantage of the new proposed APIs, he/she first defines the Swift type that encapsultates the fields that make up the query parameters for a given route and then defines the handler that conforms to either one of the function signatures defined above. This implies that the framework needs a mechanism for decoding the query string found in an incoming HTTP `GET` or HTTP `DELETE` request into the corresponding Swift type. Initially, we planned on using the [Reflection API](https://developer.apple.com/documentation/swift/mirror) in Swift but determined that a few limitations would arise from doing so. For instance, all fields in the Swift type would need to be [optionals](https://developer.apple.com/documentation/swift/optional) since the Reflection API requires an instance of the type before we can do anything meaningful such as figuring out the names and types of the instance fields. Hence, instead of using the reflection API, a custom [`Decoder`](https://developer.apple.com/documentation/swift/decoder) will determine the internal composition (i.e. properties and types) of the Swift type. This will allow developers to define Swift types, for their query entities, that can include optional and non-optional values. We refer to this custom `Decoder` as `QueryDecoder`.
98 |
99 | The following list includes the types that we intend to initialy support in `QueryDecoder`:
100 |
101 | - `Array`
102 | - `Int`
103 | - `Array`
104 | - `UInt`
105 | - `Float`
106 | - `Array`
107 | - `Double`
108 | - `Array`
109 | - `Bool`
110 | - `String`
111 | - `Array`
112 | - `Date`
113 | - `Array`
114 | - `Codable`
115 |
116 | Hence, to encapsulate query parameters, developers can define a Swift type with fields that are of any of the types listed above (optional and non-optional).
117 |
118 | ##### Date type
119 | The `Date` type is of special interest since date values specified in a query string will need to conform to the `yyyy-MM-dd'T'HH:mm:ssZ` format and use `UTC` as the time zone. Since this format and time zone are widely used for specifying date values in JSON payloads, we expect most applications can utilize `QueryDecoder` as is. However, there could be applications that require a different date format and/or time zone in their JSON payloads. To satisfy this need, `QueryDecoder` exposes a `DateFormatter` static variable that developers can modify to their needs:
120 |
121 | ```swift
122 | QueryDecoder.dateFormatter.dateFormat = ...
123 | QueryDecoder.dateFormatter.timeZone = ...
124 | ```
125 |
126 | If it becomes critical, at a later point, we can introduce a new proposal for providing more fine-grained control for decoding date strings. For instance, the `DateFormatter` instance field in `QueryDecoder` can be made non-static, which would allow different decoding formats and time zones for date strings within the same Swift application.
127 |
128 | ##### Nested types that conform to Codable
129 | It is also worth noting that developers can also take advantage of the decoding capabilities in `QueryDecoder` for nested `Codable` types, as shown below:
130 |
131 | ```swift
132 | public struct UserQuery: Codable {
133 | public let level: Int?
134 | public let gender: String
135 | public let roles: [String]
136 | public let nested: Nested
137 | }
138 |
139 | public struct Nested: Codable {
140 | public let nestedInt: Int
141 | public let nestedString: String
142 | }
143 | ```
144 |
145 | As part of the query string for filtering `User` entities, a `nested` key can be provided that contains the following as its value:
146 |
147 | ```json
148 | {
149 | "nestedInt": 1234,
150 | "nestedString": "string"
151 | }
152 | ```
153 |
154 | Here's a sample query string that can be decoded into a `UserQuery` instance that includes a `nested` key:
155 |
156 | ```
157 | ?level=25&gender=female&roles=developer,tester,manager&nested={"nestedInt": 1234, "nestedString": "string"}
158 | ```
159 |
160 | As part of the decoding process, `QueryDecoder` will decode the JSON string value mapped to the `nested` key and then create a corresponding `Nested` instance.
161 |
162 | ### Feedback
163 | Feedback should be via either (or both) of the following routes:
164 |
165 | 1. The following GitHub issue:
166 | https://github.com/IBM-Swift/evolution/issues/7
167 | 2. The "open-playback" channel in the Swift@IBM Slack:
168 | https://swift-at-ibm-slack.mybluemix.net/
169 |
--------------------------------------------------------------------------------
/Kitura/README.md:
--------------------------------------------------------------------------------
1 | # Kitura.next Manifesto
2 |
3 | A long stated goal of the Kitura framework has been to depended on and adopt the Swift standard and core libraries (Dispatch and Foundation) where possible in order to make the framework feel familiar to the developer, remove the need to convert types in order to reuse existing modules, and maximise the portability of application code.
4 |
5 | With the upcoming release of Swift 4, and the progress being made on the [Swift Server APIs project](https://swift.org/server-apis/), there are now additional standard capabilities that can be adopted. In particular, the introduction of Codable enables the removal of the dependency on SwiftyJSON and the move to a standard approach to JSON parsing.
6 |
7 | The side effect of adopting these standard capabilities is that Kitura's API surface needs to be updated, which means the release of a Kitura.next. This also provides us with an opportunity to deliver a number of additional goals, features and capabilities.
8 |
9 | ## Proposed new features in Kitura.next
10 |
11 | **1. Optional typing and data validation of HTTP request parameters**
12 |
13 | The Kitura router currently has the following API specification:
14 |
15 | ```swift
16 | public func get(_ path: String?=nil, handler: @escaping RouterHandler...) -> Router
17 | ```
18 |
19 | This allows you to define the route path as a String, and respond with a `RouterHandler` callback which is defined as:
20 |
21 | ```swift
22 | typealias RouterHandler = (RouterRequest, RouterResponse, @escaping () -> Void) throws -> Void
23 | ```
24 |
25 | Here you can access a Dictionary of URL Encoded HTTP request parameters using `RouterRequest.queryParameters`, or use the BodyParser middleware to access the body converted to a type such as JSON using `RouterRequest.body`.
26 |
27 | These approaches directly reflect the REST API exposed by the server. Whilst this might be the expectation for a pure server developer, it doesn't facilitate what a full stack developer wants to do: have code hosted on a client interact with code hosted on the server. Additionally it presents String based data - which is a limitation of the transport - and puts the emphasis on the developer to validate the data and convert to concrete Swift types.
28 |
29 | _Proposed additional capabilities:_
30 | * Add the ability to optionally request the incoming data is converted into a struct or class.
31 | * Add the ability to optionally request that incoming data is validated using a provided function.
32 | * Add the ability to optionally specify a struct or class as the return type.
33 | * Add the ability to optionally request that returned data is validated using a provided function.
34 |
35 | **2. Data streaming**
36 |
37 | The ability to read and write stream data over HTTP via asynchronous callbacks is one of the valued features of Node.js, contributing to its ability to respond efficiently to a large number of requests with a low memory overhead.
38 |
39 | Whilst this is possible in Swift, the Kitura router however currently only processes complete sets of data: data associated with incoming requests is read fully and provided via the body field in the RouterRequest that can be accessed as a parsed data type (eg. JSON) or as a Data buffer or String.
40 |
41 | This means that data being uploaded or downloaded needs to be fully read and then stored as a complete entity in memory, affecting the performance and memory usage of applications that stream data such as static files, video or audio.
42 |
43 | _Proposed additional capabilities:_
44 | * Add the ability to read from and write to the body of a request or response as a stream using asynchronous callbacks.
45 | * Add the ability for the request or response types to include an embedded stream as part of the defined struct or class.
46 |
47 | **3. Integrated support for OpenAPI (Swagger)**
48 |
49 | The [OpenAPI specification format](https://www.openapis.org) (aka [Swagger](https://swagger.io)) is the standard definition format used to describe RESTful APIs. The OpenAPI specification for a server is typically used by:
50 | * Developers
51 | To understand what APIs are available and what parameters are accepted and returned.
52 | * Client SDK Code generation tools
53 | To create connector SDKs to make calls to the API as a convenience layer that implements the data types and API calls.
54 | * Automated Testing tools
55 | To provide automated test generation and execute against the API.
56 | * API Gateways
57 | To allow the severs APIs to be registered with it for security and management of calls to the APIs.
58 |
59 | The [Swift Server Generator](http://www.kitura.io/en/starter/generator.html) provided by Kitura will already use a provided OpenAPI specification to:
60 | 1. Create a the Kitura application that implements the described RESTful API.
61 | 2. Host the OpenAPI specification as part of the application so that it can be registered with API Gateways.
62 | 3. Create an iOS client SDK to make it easier to make calls to the Kitura server.
63 |
64 | However this process is one way: the Kitura server and the iOS client can be created from the OpenAPI specification, but if the API is modified by adding, removing or updating the routes in the Kitura application, the OpenAPI spec becomes out of sync and must be updated manually.
65 |
66 | _Proposed additional capabilities:_
67 | * Closed loop development process whereby an OpenAPI specification can be generated from the application.
68 | * Ability to verify the OpenAPI specification from the application against a provided specification to provide an initial functional verification test.
69 |
70 | **4. Support for alternative, pluggable, transports**
71 |
72 | The current de-facto standard for sending and receiving data via RESTful APIs is to format the data as JavaScript Object Notation (JSON) objects. This has been one of the drivers for full-stack development of web applications with Node.js on the backend, as the client and server are serializing and transmitting data in their native format.
73 |
74 | Whilst a particularly appropriate transport for full-stack web applications, it is less so for full-stack Swift applications: JSON is a text based format that includes field names and therefore is not memory efficient, and the values stored in the format are not typed. The first means that data payloads being sent between an iOS client and a server are significantly larger than they could be, and the second means that type information is lost and complex serialization and deserialization conversions need to be carried out.
75 |
76 | Alternative transports that are emerging that are both data optimized and strongly typed making it possible to share exact data models between iOS client and server and to minimize data requirements. The most popular of these is [gRPC](https://grpc.io) which uses [protobufs](https://developers.google.com/protocol-buffers/), but others are available including [Thrift](https://thrift.apache.org).
77 |
78 | Adoption alternative transports provides a huge benefit for full-stack Swift developers, however at the cost of interoperability: every client has to use the same transport - unless the Server framework is able to handle different transports concurrently without the application having to be modified.
79 |
80 | _Proposed additional capabilities:_
81 | * Add built-in support for alternative transports, eg. gRPC and Thrift based on configuration.
82 | * Enable handling of different transports dynamically.
83 |
84 | **5. Portability with Serverless Swift**
85 |
86 | "Serverless" frameworks like [AWS Lambda](https://aws.amazon.com/lambda/) and [Apache OpenWhisk](https://developer.ibm.com/code/open/apache-openwhisk/) make it possible to create and run simple asynchronous actions, making it easy to add simple backend components to an application, without the complexity of running and managing a server framework.
87 |
88 | Today, the programming models for server and serverless applications are different. For example, an OpenWhisk action is implemented as:
89 |
90 | ```swift
91 | func run(args: [String:Any]) -> [String:Any] { }
92 | ```
93 |
94 | whereas a server framework receives a request object that contains a body which could be optionally formatted JSON, etc.
95 |
96 | This means that a potentially significant amount of refactoring is required if you wanted to migrate a set of serverless actions into a full server application, or decomposing an existing server application into a set of serverless actions.
97 |
98 | _Proposed additional capabilities:_
99 | * Enable serverless actions to be registered to run inside the Kitura router.
100 | * Enable the Kitura router to be registered as a set of serverless actions.
101 |
102 | **6. More intuitive middlewares**
103 |
104 | The current approach to middlewares implements a single API call which is called twice: one during processing of the request and once during processing of the response:
105 |
106 | ```swift
107 | func handle(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void)
108 | ```
109 |
110 | As a single API is used, the middleware code needs to be able to determine whether a request or response processing and currently occurring, and both the request and response need to be mutable. Additionally there is no clear order in which middlewares execute, and middlewares must call `next()` in order to avoid causing a hang.
111 |
112 | This makes implementation of middlewares unnecessarily complex and prone to errors, so would benefit from simplification of the programming model.
113 |
114 | _Proposed additional capabilities:_
115 | * Separate API calls for request and response processing.
116 | * Predictable ordering of middleware execution.
117 | * Use of mutable and immutable types to remove risk of incorrect modification.
118 | * Removal of the requirement to call `next()`.
119 |
120 | ## Summary
121 | The proposed new features are designed to provide a more intuitive programming model for Swift developers, to make it easier to develop full-stack Swift applications, sharing models and code between client and server, and to make it easier to deploy Kitura as a highly available and scalable production framework.
122 |
123 | Feedback is encouraged on whether these items achieve this, whether other features should be considered, and what the relative priorities are for each of the features.
124 |
--------------------------------------------------------------------------------
/Kitura/Releases/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IBM-Swift/evolution/0f004e6ba9fa05515fdad5d55dd04b8e097a3471/Kitura/Releases/.gitkeep
--------------------------------------------------------------------------------
/Kitura/status.md:
--------------------------------------------------------------------------------
1 | # Kitura Proposal Status
2 |
3 | | Proposal # | Proposal Name | Author(s) | Review Manager | Status |
4 | | ---------- | ------------- | --------- | -------------- | ------ |
5 | | [KIT-0001](https://github.com/IBM-Swift/evolution/tree/master/Kitura/Proposals/0001-codable-routing.md) | [Codable Routing](https://github.com/IBM-Swift/evolution/tree/master/Kitura/Proposals/0001-codable-routing.md) | [Chris Bailey](https://github.com/seabaylea), [Mike Tunnicliffe](https://github.com/tunniclm), [Ricardo Olivieri](https://github.com/rolivieri) | [Lloyd Roseblade](https://github.com/lroseblade) | Draft
--------------------------------------------------------------------------------
/Other/Proposals/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IBM-Swift/evolution/0f004e6ba9fa05515fdad5d55dd04b8e097a3471/Other/Proposals/.gitkeep
--------------------------------------------------------------------------------
/Other/README.md:
--------------------------------------------------------------------------------
1 | # Other projects
2 |
3 | This area should be used to raise proposals for new projects or against existing projects not covered in other areas.
--------------------------------------------------------------------------------
/Other/Releases/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IBM-Swift/evolution/0f004e6ba9fa05515fdad5d55dd04b8e097a3471/Other/Releases/.gitkeep
--------------------------------------------------------------------------------
/Other/status.md:
--------------------------------------------------------------------------------
1 | # Other Project Proposal Status
2 |
3 | | Proposal # | Proposal Name | Author(s) | Review Manager | Status |
4 | | ---------- | ------------- | --------- | -------------- | ------ |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Swift@IBM Evolution Process
2 |
3 | This repository tracks the ongoing evolution of the frameworks and capabilities provided by the Swift@IBM team. It contains:
4 | * **Manifestos:** describing the proposed roadmaps and goals for major projects, eg. Kitura.
5 | * **Proposals:** describing the design proposals for new features or changes to existing capabilities.
6 | * **Status:** describing the status of the current proposals against that major project area.
7 | * **Releases:** describing which past proposals were delivered in which release of a functional area.
8 |
9 | These are provided under subdirectories for each of the major project areas, currently:
10 | * [Kitura](https://github.com/IBM-Swift/evolution/tree/master/Kitura): for the Kitura framework itself
11 | * [Other](https://github.com/IBM-Swift/evolution/tree/master/Other): for all other projects
12 |
13 | This will shortly be extended to provide specific areas for other major projects including: Monitoring, Swift Server Generator, etc.
14 |
15 | ## Evolution Process
16 | The Swift@IBM Evolution process is modeled on the [Swift Evolution process](https://github.com/apple/swift-evolution/blob/master/process.md), but covers multiple functional areas, including all of the projects in the Swift@IBM organisation in GitHub.
17 |
18 | ### Goals
19 | The Swift@IBM Evolution process aims to more publicly document the requirements, goals, architecture and design of capabilities and features being added to the Swift@IBM projects, and to leverage the feedback, ideas and experiences of the Swift community in order to provide capabilities that the community wants, in the way that they want them.
20 |
21 | ### Participation
22 | Everyone is welcome to propose, discuss, and review ideas to improve the Swift@IBM projects. Unlike the Swift Evolution process, there is no mailing list for discussion: discussions and reviews will be carried out using Slack and via GitHub pull requests and issues.
23 |
24 | ### How to propose a change
25 | * Consider any documented goals in that functional area:
26 | Each of the major functional areas will eventually have a published manifesto describing the function areas being considered for the next release(s). Proposals that are within the scope of the relevant manifesto are more likely to be considered. It is recommended that proposals outside of the scope of the relevant manifesto are well socialised in order to get initial feedback.
27 | * Socialize the idea:
28 | Before creating a proposal, the idea should be discussed via Slack, stating the use case, and the problems it solves, along with an idea of what the solution might look like in order to gain interest from the community.
29 | * Develop the proposal:
30 | Proposals should be developed using the proposal [template](https://github.com/IBM-Swift/evolution/blob/master/template.md). Prototyping an implementation and its uses along with the proposal is encouraged, because it helps ensure both technical feasibility of the proposal as well as validating that the proposal solves the problems it is meant to solve.
31 | * Request a review:
32 | Submitting the proposal via a pull request to the [ibm-swift/evolution](https://github.com/IBM-Swift/evolution) repository starts a review of the proposal submission, ensuring that it is sufficiently detailed and clear. Once accepted as a proposal, a proposal number and a Swift@IBM team member will be assigned to the proposal. A GitHub issue will then be opened in order to review the proposal itself.
33 | * Address feedback:
34 | Feedback provided during the review should be responded to and addressed.
35 |
36 | ### Review process
37 | Once the proposal is accepted and a GitHub issue created, the review manager will work with the proposal authors generate and address feedback. The review period will typically be one week, but may run longer if there is still ongoing discussion that needs to be addressed.
38 |
39 | After the review has completed, the review manager is responsible for determining consensus and reporting the decision to the proposal authors. The review manager will then update the proposal's state in the [ibm-swift/evolution](https://github.com/IBM-Swift/evolution) repository to reflect that decision.
40 |
41 | ### Proposal states
42 | A given proposal can be in one of several states:
43 | * Under review
44 | * Returned for revision
45 | * Withdrawn
46 | * Deferred
47 | * Accepted
48 | * Accepted with revisions
49 | * Rejected
50 | * Implemented (release version)
51 |
52 | ### Implementation of proposals
53 | Once a proposal has been accepted, an issue will be created in the relevant project to implement the proposal. Participation from the community to implement proposals is encouraged. It is recommended that you contact any people assigned to the issue in order to collaborate with them, or discuss with the review manager how to get started implementing the proposal for any unassigned proposals.
54 |
--------------------------------------------------------------------------------
/template.md:
--------------------------------------------------------------------------------
1 | # Swift@IBM Evolution Template
2 |
3 | ## Feature name
4 | * Proposal: NNNN
5 | * Authors: Author 1, Author 2
6 | * Review Manager: TBD
7 | * Status: Under review
8 | * Decision Notes: Rationale, Additional Commentary
9 | * Previous Revision: 1
10 | * Previous Proposal: NNNN
11 |
12 | ### Introduction
13 | A short description of what the feature is. Try to keep it to a single-paragraph "elevator pitch" so the reader understands what problem this proposal is addressing.
14 |
15 | ### Motivation
16 | Describe the use cases and problems that this proposal seeks to address. The focus should be on why this feature is valuable to users or what pain point you are looking to address.
17 |
18 | ### Proposed solution
19 | Describe your solution to the problem. Provide examples and describe how they work. Show how your solution is better than current workarounds: is it cleaner, safer, or more efficient?
20 |
21 | ### Detailed design
22 | Describe the design of the solution in detail. If it involves new or modified APIs, show the full API and its documentation comments detailing what it does. The detail in this section should be sufficient for someone who is not one of the authors to be able to reasonably implement the feature.
23 |
24 | ### Alternatives considered
25 | Describe alternative approaches to addressing the same problem, and why you chose this approach instead.
--------------------------------------------------------------------------------