Skip to content

Commit

Permalink
yo swiftserver:model User
Browse files Browse the repository at this point in the history
  • Loading branch information
rmg committed Apr 20, 2017
1 parent 21e837b commit 9c7f29e
Show file tree
Hide file tree
Showing 8 changed files with 537 additions and 2 deletions.
3 changes: 3 additions & 0 deletions swift-blog/Sources/Generated/AdapterFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@ public class AdapterFactory {
self.manager = manager
}

public func getUserAdapter() throws -> UserAdapter {
return UserMemoryAdapter()
}

}
2 changes: 2 additions & 0 deletions swift-blog/Sources/Generated/CRUDResources.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ import Kitura
import Configuration

public func initializeCRUDResources(manager: ConfigurationManager, router: Router) throws {
let factory = AdapterFactory(manager: manager)
try UserResource(factory: factory).setupRoutes(router: router)
}
86 changes: 86 additions & 0 deletions swift-blog/Sources/Generated/User.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import SwiftyJSON

public struct User {
public let id: String?
public let name: String
public let email: String

public init(id: String?, name: String, email: String) {
self.id = id
self.name = name
self.email = email
}

public init(json: JSON) throws {
// Required properties
guard json["name"].exists() else {
throw ModelError.requiredPropertyMissing(name: "name")
}
guard let name = json["name"].string else {
throw ModelError.propertyTypeMismatch(name: "name", type: "string", value: json["name"].description, valueType: String(describing: json["name"].type))
}
self.name = name
guard json["email"].exists() else {
throw ModelError.requiredPropertyMissing(name: "email")
}
guard let email = json["email"].string else {
throw ModelError.propertyTypeMismatch(name: "email", type: "string", value: json["email"].description, valueType: String(describing: json["email"].type))
}
self.email = email

// Optional properties
if json["id"].exists() &&
json["id"].type != .string {
throw ModelError.propertyTypeMismatch(name: "id", type: "string", value: json["id"].description, valueType: String(describing: json["id"].type))
}
self.id = json["id"].string

// Check for extraneous properties
if let jsonProperties = json.dictionary?.keys {
let properties: [String] = ["id", "name", "email"]
for jsonPropertyName in jsonProperties {
if !properties.contains(where: { $0 == jsonPropertyName }) {
throw ModelError.extraneousProperty(name: jsonPropertyName)
}
}
}
}

public func settingID(_ newId: String?) -> User {
return User(id: newId, name: name, email: email)
}

public func updatingWith(json: JSON) throws -> User {
if json["id"].exists() &&
json["id"].type != .string {
throw ModelError.propertyTypeMismatch(name: "id", type: "string", value: json["id"].description, valueType: String(describing: json["id"].type))
}
let id = json["id"].string ?? self.id

if json["name"].exists() &&
json["name"].type != .string {
throw ModelError.propertyTypeMismatch(name: "name", type: "string", value: json["name"].description, valueType: String(describing: json["name"].type))
}
let name = json["name"].string ?? self.name

if json["email"].exists() &&
json["email"].type != .string {
throw ModelError.propertyTypeMismatch(name: "email", type: "string", value: json["email"].description, valueType: String(describing: json["email"].type))
}
let email = json["email"].string ?? self.email

return User(id: id, name: name, email: email)
}

public func toJSON() -> JSON {
var result = JSON([
"name": JSON(name),
"email": JSON(email),
])
if let id = id {
result["id"] = JSON(id)
}

return result
}
}
9 changes: 9 additions & 0 deletions swift-blog/Sources/Generated/UserAdapter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public protocol UserAdapter {
func findAll(onCompletion: @escaping ([User], Error?) -> Void)
func create(_ model: User, onCompletion: @escaping (User?, Error?) -> Void)
func deleteAll(onCompletion: @escaping (Error?) -> Void)

func findOne(_ maybeID: String?, onCompletion: @escaping (User?, Error?) -> Void)
func update(_ maybeID: String?, with model: User, onCompletion: @escaping (User?, Error?) -> Void)
func delete(_ maybeID: String?, onCompletion: @escaping (User?, Error?) -> Void)
}
57 changes: 57 additions & 0 deletions swift-blog/Sources/Generated/UserMemoryAdapter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Foundation

public class UserMemoryAdapter: UserAdapter {
var items: [String:User] = [:]

public func findAll(onCompletion: @escaping ([User], Error?) -> Void) {
onCompletion(items.map { $1 }, nil)
}

public func create(_ model: User, onCompletion: @escaping (User?, Error?) -> Void) {
let id = model.id ?? UUID().uuidString
// TODO: Don't overwrite if id already exists
let storedModel = model.settingID(id)
items[id] = storedModel
onCompletion(storedModel, nil)
}

public func deleteAll(onCompletion: @escaping (Error?) -> Void) {
items.removeAll()
onCompletion(nil)
}

public func findOne(_ maybeID: String?, onCompletion: @escaping (User?, Error?) -> Void) {
guard let id = maybeID else {
return onCompletion(nil, AdapterError.invalidId(maybeID))
}
guard let retrievedModel = items[id] else {
return onCompletion(nil, AdapterError.notFound(id))
}
onCompletion(retrievedModel, nil)
}

public func update(_ maybeID: String?, with model: User, onCompletion: @escaping (User?, Error?) -> Void) {
delete(maybeID) { _, error in
if let error = error {
onCompletion(nil, error)
} else {
// NOTE: delete() guarantees maybeID non-nil if error is nil
let id = maybeID!
let model = (model.id == nil) ? model.settingID(id) : model
self.create(model) { storedModel, error in
onCompletion(storedModel, error)
}
}
}
}

public func delete(_ maybeID: String?, onCompletion: @escaping (User?, Error?) -> Void) {
guard let id = maybeID else {
return onCompletion(nil, AdapterError.invalidId(maybeID))
}
guard let removedModel = items.removeValue(forKey: id) else {
return onCompletion(nil, AdapterError.notFound(id))
}
onCompletion(removedModel, nil)
}
}
221 changes: 221 additions & 0 deletions swift-blog/Sources/Generated/UserResource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import Kitura
import LoggerAPI
import SwiftyJSON

public class UserResource {
private let adapter: UserAdapter
private let path = "/api/Users"
private let pathWithID = "/api/Users/:id"

init(factory: AdapterFactory) throws {
adapter = try factory.getUserAdapter()
}

func setupRoutes(router: Router) {
router.all("/*", middleware: BodyParser())

router.get(path, handler: handleIndex)
router.post(path, handler: handleCreate)
router.delete(path, handler: handleDeleteAll)

router.get(pathWithID, handler: handleRead)
router.put(pathWithID, handler: handleReplace)
router.patch(pathWithID, handler: handleUpdate)
router.delete(pathWithID, handler: handleDelete)
}

private func handleIndex(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) {
Log.debug("GET \(path)")
// TODO: offset and limit
adapter.findAll() { models, error in
if let _ = error {
// TODO: Send error object?
Log.error("InternalServerError during handleIndex: \(error)")
response.status(.internalServerError)
} else {
response.send(json: JSON(models.map { $0.toJSON() }))
}
next()
}
}

private func handleCreate(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) {
Log.debug("POST \(path)")
guard let contentType = request.headers["Content-Type"],
contentType.hasPrefix("application/json") else {
response.status(.unsupportedMediaType)
response.send(json: JSON([ "error": "Request Content-Type must be application/json" ]))
return next()
}
guard case .json(let json)? = request.body else {
response.status(.badRequest)
response.send(json: JSON([ "error": "Request body could not be parsed as JSON" ]))
return next()
}
do {
let model = try User(json: json)
adapter.create(model) { storedModel, error in
if let _ = error {
// TODO: Handle model errors (eg id conflict)
Log.error("InternalServerError during handleCreate: \(error)")
response.status(.internalServerError)
} else {
response.send(json: storedModel!.toJSON())
}
next()
}
} catch let error as ModelError {
response.status(.unprocessableEntity)
response.send(json: JSON([ "error": error.defaultMessage() ]))
next()
} catch {
Log.error("InternalServerError during handleCreate: \(error)")
response.status(.internalServerError)
next()
}
}

private func handleDeleteAll(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) {
Log.debug("DELETE \(path)")
adapter.deleteAll() { error in
if let _ = error {
response.status(.internalServerError)
} else {
let result = JSON([])
response.send(json: result)
}
next()
}
}

private func handleRead(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) {
Log.debug("GET \(pathWithID)")
adapter.findOne(request.parameters["id"]) { model, error in
if let error = error {
switch error {
case AdapterError.notFound:
response.status(.notFound)
default:
response.status(.internalServerError)
}
} else {
response.send(json: model!.toJSON())
}
next()
}
}

private func handleReplace(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) {
Log.debug("PUT \(pathWithID)")
guard let contentType = request.headers["Content-Type"],
contentType.hasPrefix("application/json") else {
response.status(.unsupportedMediaType)
response.send(json: JSON([ "error": "Request Content-Type must be application/json" ]))
return next()
}
guard case .json(let json)? = request.body else {
response.status(.badRequest)
response.send(json: JSON([ "error": "Request body could not be parsed as JSON" ]))
return next()
}
do {
let model = try User(json: json)
adapter.update(request.parameters["id"], with: model) { storedModel, error in
if let error = error {
switch error {
case AdapterError.notFound:
response.status(.notFound)
case AdapterError.idConflict(let id):
response.status(.conflict)
response.send(json: JSON([ "error": "Cannot update id to a value that already exists (\(id))" ]))
default:
Log.error("InternalServerError during handleCreate: \(error)")
response.status(.internalServerError)
}
} else {
response.send(json: storedModel!.toJSON())
}
next()
}
} catch let error as ModelError {
response.status(.unprocessableEntity)
response.send(json: JSON([ "error": error.defaultMessage() ]))
next()
} catch {
Log.error("InternalServerError during handleReplace: \(error)")
response.status(.internalServerError)
next()
}
}

private func handleUpdate(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) {
Log.debug("PATCH \(pathWithID)")
guard let contentType = request.headers["Content-Type"],
contentType.hasPrefix("application/json") else {
response.status(.unsupportedMediaType)
response.send(json: JSON([ "error": "Request Content-Type must be application/json" ]))
return next()
}
guard case .json(let json)? = request.body else {
response.status(.badRequest)
response.send(json: JSON([ "error": "Request body could not be parsed as JSON" ]))
return next()
}
adapter.findOne(request.parameters["id"]) { model, error in
if let error = error {
switch error {
case AdapterError.notFound:
response.status(.notFound)
default:
response.status(.internalServerError)
}
return next()
}
do {
let updatedModel = try model!.updatingWith(json: json)
self.adapter.update(request.parameters["id"], with: updatedModel) { storedModel, error in
if let error = error {
switch error {
case AdapterError.notFound:
response.status(.notFound)
case AdapterError.idConflict(let id):
response.status(.conflict)
response.send(json: JSON([ "error": "Cannot update id to a value that already exists (\(id))" ]))
default:
Log.error("InternalServerError during handleUpdate: \(error)")
response.status(.internalServerError)
}
} else {
response.send(json: storedModel!.toJSON())
}
next()
}
} catch let error as ModelError {
response.status(.unprocessableEntity)
response.send(json: JSON([ "error": error.defaultMessage() ]))
next()
} catch {
Log.error("InternalServerError during handleUpdate: \(error)")
response.status(.internalServerError)
next()
}
}
}

private func handleDelete(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) {
Log.debug("DELETE \(pathWithID)")
adapter.delete(request.parameters["id"]) { model, error in
if let error = error {
switch error {
case AdapterError.notFound:
response.send(json: JSON([ "count": 0 ]))
default:
response.status(.internalServerError)
}
} else {
response.send(json: JSON([ "count": 1] ))
}
next()
}
}
}
Loading

0 comments on commit 9c7f29e

Please sign in to comment.