-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat: Add/Retrieve Codable objects in a session #54
Changes from 4 commits
01d3cb5
a280bc7
f02f80e
a3315ab
3832a3d
4cf59db
68e4a0b
6ff9bf2
0886acc
0b11b7f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/** | ||
* 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. | ||
**/ | ||
|
||
// MARK StoreError | ||
|
||
/// An error indicating the failure of an operation to encode/decode into/from the session `Store`. | ||
public enum SessionCodingError: Swift.Error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please make this a struct instead of an enum so we can add to it without doing a major release. |
||
|
||
//Thrown when the provided Key is not found in the session. | ||
case keyNotFound(key: String) | ||
|
||
//Thrown when a primative Decodable or array of primative Decodables fails to be cast to the provided type. | ||
case failedPrimativeCast() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Primative -> Primitive (throughout this PR) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
//Throw when the provided Encodable fails to be serialized to JSON. | ||
case failedToSerializeJSON() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -110,4 +110,118 @@ public class SessionState { | |
isDirty = true | ||
} | ||
} | ||
|
||
// Check if the provided value is a primative JSON type. | ||
private func isPrimative(value: Any) -> Bool { | ||
if value is [Any] { | ||
return value is [String] || | ||
value is [Int] || | ||
value is [Double] || | ||
value is [Bool] | ||
} else { | ||
return value is String || | ||
value is Int || | ||
value is Double || | ||
value is Bool | ||
} | ||
} | ||
// MARK: Codable session | ||
|
||
/** | ||
Encode an encodable value as JSON and store it in the session for the provided key. | ||
### Usage Example: ### | ||
The example below defines a `User` struct. | ||
It decodes a `User` instance from the request body | ||
and stores it in the request session using the user's id as the key. | ||
```swift | ||
public struct User: Codable { | ||
let id: String | ||
let name: String | ||
} | ||
let router = Router() | ||
router.all(middleware: Session(secret: "secret")) | ||
router.post("/user") { request, response, next in | ||
let user = try request.read(as: User.self) | ||
try request.session?.add(user, forKey: user.id) | ||
response.status(.created) | ||
response.send(user) | ||
next() | ||
} | ||
``` | ||
- Parameter value: The Encodable object which will be added to the session. | ||
- Parameter forKey: The key that the Encodable object will be stored under. | ||
- Throws: `EncodingError` if value to be stored fails to be encoded as JSON. | ||
- Throws: `SessionCodingError.failedToSerializeJSON` if value to be stored fails to be serialized as JSON. | ||
*/ | ||
public func add<T: Encodable>(_ value: T, forKey key: String) throws { | ||
let json: Any | ||
if isPrimative(value: value) { | ||
json = value | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this special casing for primitives? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JSONEncoder and JSONDecoder do not have an .allowFragments option and will throw an error if you try and encode/decode a primitive type using them. This has been raised as a bug in SR-6163. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK - I think we should consider not allowing primitives and throwing if the serialization fails. What do you think? |
||
} else { | ||
let data = try JSONEncoder().encode(value) | ||
let mirror = Mirror(reflecting: value) | ||
if mirror.displayStyle == .collection { | ||
guard let array = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [Any] else { | ||
throw SessionCodingError.failedToSerializeJSON() | ||
} | ||
json = array | ||
} else { | ||
guard let dict = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else { | ||
throw SessionCodingError.failedToSerializeJSON() | ||
} | ||
json = dict | ||
} | ||
|
||
} | ||
state[key] = json | ||
isDirty = true | ||
} | ||
|
||
/** | ||
Decode the JSON value that is stored in the session for the provided key as a Decodable object. | ||
### Usage Example: ### | ||
The example below defines a `User` struct. | ||
It then reads a user id from the query parameters | ||
and decodes the instance of `User` that is stored in the session for that id. | ||
```swift | ||
public struct User: Codable { | ||
let id: String | ||
let name: String | ||
} | ||
let router = Router() | ||
router.all(middleware: Session(secret: "secret")) | ||
router.get("/user") { request, response, next in | ||
guard let userID = request.queryParameters["userid"] else { | ||
return response.status(.notFound).end() | ||
} | ||
let user = try request.session?.read(as: User.self, forKey: userID) | ||
response.status(.OK) | ||
response.send(user) | ||
next() | ||
} | ||
``` | ||
- Parameter as: The Decodable object type which the session will be decoded as. | ||
- Parameter forKey: The key that the Decodable object was stored under. | ||
- Throws: `SessionCodingError.keyNotFound` if a value is not found for the provided key. | ||
- Throws: `SessionCodingError.failedPrimativeCast` if value stored for the key fails to be decoded as a primative JSON type. | ||
- Throws: `DecodingError` if value stored for the key fails to be decoded as the provided type. | ||
- Returns: The instantiated Decodable object | ||
*/ | ||
public func read<T: Decodable>(as type: T.Type, forKey key: String) throws -> T { | ||
guard let dict = state[key] else { | ||
throw SessionCodingError.keyNotFound(key: key) | ||
} | ||
if isPrimative(value: dict) { | ||
guard let primative = dict as? T else { | ||
throw SessionCodingError.failedPrimativeCast() | ||
} | ||
return primative | ||
} | ||
let data = try JSONSerialization.data(withJSONObject: dict) | ||
return try JSONDecoder().decode(type, from: data) | ||
} | ||
} | ||
|
||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does response.send with an optional Codable do what you expect?