SlideShare uma empresa Scribd logo
1 de 41
Baixar para ler offline
TodoList
Tutorial
Creating a TodoList
→ Creating a project
→ Add dependencies
→ Simple Routing
→ Error Handling
→ Promises
→ Testing
Create a Project
mkdir TodoList
cd TodoList
swift package init
swift package generate-xcodeproj
Add Dependencies
import PackageDescription
let package = Package(
name: "MyTodoList",
dependencies: [
.Package(url: "https://github.com/IBM-Swift/Kitura", majorVersion: 0, minor: 32),
.Package(url: "https://github.com/IBM-Swift/HeliumLogger", majorVersion: 0, minor: 17)
]
)
Simple Route
let router = Router()
router.get("/") { request, response, next in
response.status(.OK).send("Hello World!")
}
Simple Server
Kitura.addHTTPServer(onPort: 8090, with: todoListController.router)
Kitura.run()
Simple Logger
import HeliumLogger
HeliumLogger.use()
Log.info("Hello world!")
Using multiple targets
targets: [
Target(name: "Server", dependencies: [.Target(name: "TodoList")]),
Target(name: "TodoList")
],
Separation project to:
→ Sources/TodoList/TodoList.swift
→ Sources/Server/main.swift
Create a Controller
public final class TodoListController {
public let router = Router()
public init() {
router.get("/v1/tasks", handler: handleGetTasks)
router.post("/v1/tasks", handler: handlerAddTask)
}
}
Add routes
func handleGetTasks(request: RouterRequest,
response: RouterResponse,
next: @escaping () -> Void) throws {
}
func handleAddTask(request: RouterRequest,
response: RouterResponse,
next: @escaping () -> Void) throws {
}
Add basic collection to Controller
let tasks: [String] = []
Get tasks
func handleGetTasks(request: RouterRequest,
response: RouterResponse,
next: @escaping () -> Void) throws {
response.status(.OK).send(json: JSON( tasks ))
}
Add ability to add tasks
Add a Body Parser
router.all("*", middleware: BodyParser())
Simplify getting the JSON back
extension RouterRequest {
var json: JSON? {
guard let body = self.body else {
return nil
}
guard case let .json(json) = body else {
return nil
}
return json
}
}
Get the description back
func handleAddTask(request: RouterRequest,
response: RouterResponse,
next: @escaping () -> Void) throws {
if let json = request.json else {
response.status(.badRequest)
next()
return
}
let description = json["description"].stringValue
tasks.append(description)
}
Protect your array
let queue = DispatchQueue(label: "com.example.tasklist")
queue.sync {
}
Create a more rich Task
struct Task {
let id: UUID
let description: String
let createdAt: Date
let isCompleted: Bool
}
Make it Equatible
extension Task: Equatable { }
func == (lhs: Task, rhs: Task) -> Bool {
if lhs.id == rhs.id,
lhs.description == rhs.description,
lhs.createdAt == rhs.createdAt,
lhs.isCompleted == rhs.isCompleted
{
return true
}
return false
}
Make things tranformable to Dictionary
typealias StringValuePair = [String: Any]
protocol StringValuePairConvertible {
var stringValuePairs: StringValuePair {get}
}
Make collections also tranformable to Dictionary
extension Array where Element : StringValuePairConvertible {
var stringValuePairs: [StringValuePair] {
return self.map { $0.stringValuePairs }
}
}
Make Task a StringValuePairConvertible
extension Task: StringValuePairConvertible {
var stringValuePairs: StringValuePair {
return [
"id": "(self.id)",
"description": self.description,
"createdAt": self.createdAt.timeIntervalSinceReferenceDate,
"isCompleted": self.isCompleted
]
}
}
Change [String] to [Task]
private var tasks: [Task] = []
response.status(.OK).send(json: JSON(task.stringValuePairs))
Add task with Tasks
task.append(Task(id: UUID(),
description: "Do the dishes",
createdAt: Date(),
isCompleted: false))
Factor out the Database
final class TaskDatabase {
private var storage: [Task] = []
let queue = DispatchQueue(label: "com.example.tasklist")
func addTask(oncompletion: (Task) -> Void) {
queue.sync {
self.storage.append(task)
oncompletion(task)
}
}
func getTasks(oncompletion: ([Task]) -> Void) {
queue.sync {
oncompletion(self.storage)
}
}
}
Use asynchronous callbacks
Error Handling
TaskListError
enum TaskListError : LocalizedError {
case descriptionTooShort(String)
case descriptionTooLong(String)
case noJSON
Error Description
var errorDescription: String? {
switch self {
case .descriptionTooShort(let string): return "(string) is too short"
case .descriptionTooLong(let string): return "(string) is too long"
case .noJSON: return "No JSON in payload"
}
}
Make it convertible to JSON
extension TaskListError: StringValuePairConvertible {
var stringValuePairs: StringValuePair {
return ["error": self.errorDescription ?? ""]
}
}
Validating the Request
let maximumLength = 40
let minimumLength = 3
struct AddTaskRequest {
let description: String
}
Validate the request
func validateRequest(request: RouterRequest) throws -> AddTaskRequest {
guard let json = request.json else {
throw TaskListError.noJSON
}
let description = json["description"].stringValue
if description.characters.count > maximumLength {
throw TaskListError.descriptionTooLong(description)
}
if description.characters.count < minimumLength {
throw TaskListError.descriptionTooShort(description)
}
return AddTaskRequest(description: description)
}
Use Promises
Add MiniPromiseKit
.Package(url: "https://github.com/davidungar/miniPromiseKit", majorVersion: 4, minor: 1),
Create Promises
final class TaskDatabase {
private var storage: [Task] = []
let queue = DispatchQueue(label: "com.example.tasklist")
func addTask(task: Task) -> Promise<Task> {
return Promise{ fulfill, reject in
queue.sync {
self.storage.append(task)
fulfill(task)
}
}
}
func getTasks() -> Promise<[Task]> {
return Promise{ fulfill, reject in
queue.sync {
fulfill(self.storage)
}
}
}
}
Use the Promises
_ = firstly {
taskDatabase.getTasks()
}.then (on: self.queue) { tasks in
response.status(.OK).send(json: JSON(tasks.stringValuePairs))
}
.catch (on: self.queue) { error in
if let err = error as? TaskListError {
response.status(.badRequest).send(json: JSON(err.stringValuePairs))
}
}
.always(on: self.queue) {
next()
}
Use the Promises
_ = firstly { () throws -> Promise<Task> in
let addRequest = try validateRequest(request: request)
let task = Task(with: addRequest)
return taskDatabase.addTask(task: task)
}
.then (on: self.queue) { task -> Void in
response.status(.OK).send(json: JSON(task.stringValuePairs))
}
.catch (on: self.queue) { error in
if let err = error as? TaskListError {
response.status(.badRequest).send(json: JSON(err.stringValuePairs))
}
}
.always(on: self.queue) {
next()
}
Testing
Set up Kitura framework
private let queue = DispatchQueue(label: "Kitura runloop", qos: .userInitiated, attributes: .concurrent)
public let defaultSession = URLSession(configuration: .default)
private let todoListController = TodoListController()
override func setUp() {
super.setUp()
Kitura.addHTTPServer(onPort: 8090, with: todoListController.router)
queue.async {
Kitura.run()
}
}
Add a test
func testGetTodos() {
let expectation1 = expectation(description: "Get Todos")
var url: URLRequest = URLRequest(url: URL(string: "http://localhost:8090/v1/tasks")!)
url.addValue("application/json", forHTTPHeaderField: "Content-Type")
url.httpMethod = "GET"
url.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData
let dataTask = defaultSession.dataTask(with: url) {
data, response, error in
XCTAssertNil(error)
switch (response as? HTTPURLResponse)?.statusCode {
case 200?:
guard let data = data else {
XCTAssert(false)
return
}
let json = JSON(data: data)
print(json)
expectation1.fulfill()
case nil: XCTFail("response not HTTPURLResponse")
case let code?: XCTFail("bad status: (code)")
}
}
dataTask.resume()
waitForExpectations(timeout: 10, handler: { _ in })
}
Enable Code coverage
swift package generate-xcodeproj --enable-code-coverage

Mais conteúdo relacionado

Mais procurados

Scala - den smarta kusinen
Scala - den smarta kusinenScala - den smarta kusinen
Scala - den smarta kusinen
Redpill Linpro
 
Jggug 2010 330 Grails 1.3 観察
Jggug 2010 330 Grails 1.3 観察Jggug 2010 330 Grails 1.3 観察
Jggug 2010 330 Grails 1.3 観察
Tsuyoshi Yamamoto
 
Store and Process Big Data with Hadoop and Cassandra
Store and Process Big Data with Hadoop and CassandraStore and Process Big Data with Hadoop and Cassandra
Store and Process Big Data with Hadoop and Cassandra
Deependra Ariyadewa
 

Mais procurados (20)

The Ring programming language version 1.10 book - Part 50 of 212
The Ring programming language version 1.10 book - Part 50 of 212The Ring programming language version 1.10 book - Part 50 of 212
The Ring programming language version 1.10 book - Part 50 of 212
 
The Ring programming language version 1.5.4 book - Part 40 of 185
The Ring programming language version 1.5.4 book - Part 40 of 185The Ring programming language version 1.5.4 book - Part 40 of 185
The Ring programming language version 1.5.4 book - Part 40 of 185
 
The Ring programming language version 1.5.3 book - Part 40 of 184
The Ring programming language version 1.5.3 book - Part 40 of 184The Ring programming language version 1.5.3 book - Part 40 of 184
The Ring programming language version 1.5.3 book - Part 40 of 184
 
Scala - den smarta kusinen
Scala - den smarta kusinenScala - den smarta kusinen
Scala - den smarta kusinen
 
MBL301 Data Persistence to Amazon Dynamodb for Mobile Apps - AWS re: Invent 2012
MBL301 Data Persistence to Amazon Dynamodb for Mobile Apps - AWS re: Invent 2012MBL301 Data Persistence to Amazon Dynamodb for Mobile Apps - AWS re: Invent 2012
MBL301 Data Persistence to Amazon Dynamodb for Mobile Apps - AWS re: Invent 2012
 
Why realm?
Why realm?Why realm?
Why realm?
 
The Ring programming language version 1.7 book - Part 16 of 196
The Ring programming language version 1.7 book - Part 16 of 196The Ring programming language version 1.7 book - Part 16 of 196
The Ring programming language version 1.7 book - Part 16 of 196
 
Jggug 2010 330 Grails 1.3 観察
Jggug 2010 330 Grails 1.3 観察Jggug 2010 330 Grails 1.3 観察
Jggug 2010 330 Grails 1.3 観察
 
Firebase ng2 zurich
Firebase ng2 zurichFirebase ng2 zurich
Firebase ng2 zurich
 
2019-01-29 - Demystifying Kotlin Coroutines
2019-01-29 - Demystifying Kotlin Coroutines2019-01-29 - Demystifying Kotlin Coroutines
2019-01-29 - Demystifying Kotlin Coroutines
 
The Ring programming language version 1.5.1 book - Part 12 of 180
The Ring programming language version 1.5.1 book - Part 12 of 180The Ring programming language version 1.5.1 book - Part 12 of 180
The Ring programming language version 1.5.1 book - Part 12 of 180
 
Async all around us (promises)
Async all around us (promises)Async all around us (promises)
Async all around us (promises)
 
Codable routing
Codable routingCodable routing
Codable routing
 
The Ring programming language version 1.6 book - Part 15 of 189
The Ring programming language version 1.6 book - Part 15 of 189The Ring programming language version 1.6 book - Part 15 of 189
The Ring programming language version 1.6 book - Part 15 of 189
 
Caching a page
Caching a pageCaching a page
Caching a page
 
Store and Process Big Data with Hadoop and Cassandra
Store and Process Big Data with Hadoop and CassandraStore and Process Big Data with Hadoop and Cassandra
Store and Process Big Data with Hadoop and Cassandra
 
XTW_Import
XTW_ImportXTW_Import
XTW_Import
 
Python in the database
Python in the databasePython in the database
Python in the database
 
Finding Clojure
Finding ClojureFinding Clojure
Finding Clojure
 
The Ring programming language version 1.5.2 book - Part 11 of 181
The Ring programming language version 1.5.2 book - Part 11 of 181The Ring programming language version 1.5.2 book - Part 11 of 181
The Ring programming language version 1.5.2 book - Part 11 of 181
 

Semelhante a Kitura Todolist tutorial

Overview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web FrameworkOverview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web Framework
IndicThreads
 
Scala based Lift Framework
Scala based Lift FrameworkScala based Lift Framework
Scala based Lift Framework
vhazrati
 
Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...
Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...
Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...
julien.ponge
 
How and why i roll my own node.js framework
How and why i roll my own node.js frameworkHow and why i roll my own node.js framework
How and why i roll my own node.js framework
Ben Lin
 
Scala4sling
Scala4slingScala4sling
Scala4sling
day
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
Dmitry Buzdin
 

Semelhante a Kitura Todolist tutorial (20)

Overview Of Lift Framework
Overview Of Lift FrameworkOverview Of Lift Framework
Overview Of Lift Framework
 
Overview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web FrameworkOverview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web Framework
 
Scala based Lift Framework
Scala based Lift FrameworkScala based Lift Framework
Scala based Lift Framework
 
GDG Devfest 2019 - Build go kit microservices at kubernetes with ease
GDG Devfest 2019 - Build go kit microservices at kubernetes with easeGDG Devfest 2019 - Build go kit microservices at kubernetes with ease
GDG Devfest 2019 - Build go kit microservices at kubernetes with ease
 
Threads, Queues, and More: Async Programming in iOS
Threads, Queues, and More: Async Programming in iOSThreads, Queues, and More: Async Programming in iOS
Threads, Queues, and More: Async Programming in iOS
 
TypeScript Introduction
TypeScript IntroductionTypeScript Introduction
TypeScript Introduction
 
Server Side Swift: Vapor
Server Side Swift: VaporServer Side Swift: Vapor
Server Side Swift: Vapor
 
Taming Core Data by Arek Holko, Macoscope
Taming Core Data by Arek Holko, MacoscopeTaming Core Data by Arek Holko, Macoscope
Taming Core Data by Arek Holko, Macoscope
 
Spring data iii
Spring data iiiSpring data iii
Spring data iii
 
Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...
Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...
Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...
 
Scala on Your Phone
Scala on Your PhoneScala on Your Phone
Scala on Your Phone
 
Play!ng with scala
Play!ng with scalaPlay!ng with scala
Play!ng with scala
 
Advanced #2 networking
Advanced #2   networkingAdvanced #2   networking
Advanced #2 networking
 
Serverless archtiectures
Serverless archtiecturesServerless archtiectures
Serverless archtiectures
 
Android Automated Testing
Android Automated TestingAndroid Automated Testing
Android Automated Testing
 
How and why i roll my own node.js framework
How and why i roll my own node.js frameworkHow and why i roll my own node.js framework
How and why i roll my own node.js framework
 
Scala4sling
Scala4slingScala4sling
Scala4sling
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
 
G*ワークショップ in 仙台 Grails(とことん)入門
G*ワークショップ in 仙台 Grails(とことん)入門G*ワークショップ in 仙台 Grails(とことん)入門
G*ワークショップ in 仙台 Grails(とことん)入門
 
Nancy + rest mow2012
Nancy + rest   mow2012Nancy + rest   mow2012
Nancy + rest mow2012
 

Último

%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
masabamasaba
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
VictoriaMetrics
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
masabamasaba
 

Último (20)

WSO2CON 2024 Slides - Open Source to SaaS
WSO2CON 2024 Slides - Open Source to SaaSWSO2CON 2024 Slides - Open Source to SaaS
WSO2CON 2024 Slides - Open Source to SaaS
 
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
 
Architecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the pastArchitecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the past
 
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Toronto Psychic Readings, Attraction spells,Brin...
 
What Goes Wrong with Language Definitions and How to Improve the Situation
What Goes Wrong with Language Definitions and How to Improve the SituationWhat Goes Wrong with Language Definitions and How to Improve the Situation
What Goes Wrong with Language Definitions and How to Improve the Situation
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
 
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital TransformationWSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
 
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
 
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
 
WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...
WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...
WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open SourceWSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
 
Artyushina_Guest lecture_YorkU CS May 2024.pptx
Artyushina_Guest lecture_YorkU CS May 2024.pptxArtyushina_Guest lecture_YorkU CS May 2024.pptx
Artyushina_Guest lecture_YorkU CS May 2024.pptx
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand
 
%in Benoni+277-882-255-28 abortion pills for sale in Benoni
%in Benoni+277-882-255-28 abortion pills for sale in Benoni%in Benoni+277-882-255-28 abortion pills for sale in Benoni
%in Benoni+277-882-255-28 abortion pills for sale in Benoni
 
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 

Kitura Todolist tutorial

  • 2. Creating a TodoList → Creating a project → Add dependencies → Simple Routing → Error Handling → Promises → Testing
  • 3. Create a Project mkdir TodoList cd TodoList swift package init swift package generate-xcodeproj
  • 4. Add Dependencies import PackageDescription let package = Package( name: "MyTodoList", dependencies: [ .Package(url: "https://github.com/IBM-Swift/Kitura", majorVersion: 0, minor: 32), .Package(url: "https://github.com/IBM-Swift/HeliumLogger", majorVersion: 0, minor: 17) ] )
  • 5. Simple Route let router = Router() router.get("/") { request, response, next in response.status(.OK).send("Hello World!") }
  • 6. Simple Server Kitura.addHTTPServer(onPort: 8090, with: todoListController.router) Kitura.run()
  • 8. Using multiple targets targets: [ Target(name: "Server", dependencies: [.Target(name: "TodoList")]), Target(name: "TodoList") ], Separation project to: → Sources/TodoList/TodoList.swift → Sources/Server/main.swift
  • 9. Create a Controller public final class TodoListController { public let router = Router() public init() { router.get("/v1/tasks", handler: handleGetTasks) router.post("/v1/tasks", handler: handlerAddTask) } }
  • 10. Add routes func handleGetTasks(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) throws { } func handleAddTask(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) throws { }
  • 11. Add basic collection to Controller let tasks: [String] = []
  • 12. Get tasks func handleGetTasks(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) throws { response.status(.OK).send(json: JSON( tasks )) }
  • 13. Add ability to add tasks
  • 14. Add a Body Parser router.all("*", middleware: BodyParser())
  • 15. Simplify getting the JSON back extension RouterRequest { var json: JSON? { guard let body = self.body else { return nil } guard case let .json(json) = body else { return nil } return json } }
  • 16. Get the description back func handleAddTask(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) throws { if let json = request.json else { response.status(.badRequest) next() return } let description = json["description"].stringValue tasks.append(description) }
  • 17. Protect your array let queue = DispatchQueue(label: "com.example.tasklist") queue.sync { }
  • 18. Create a more rich Task struct Task { let id: UUID let description: String let createdAt: Date let isCompleted: Bool }
  • 19. Make it Equatible extension Task: Equatable { } func == (lhs: Task, rhs: Task) -> Bool { if lhs.id == rhs.id, lhs.description == rhs.description, lhs.createdAt == rhs.createdAt, lhs.isCompleted == rhs.isCompleted { return true } return false }
  • 20. Make things tranformable to Dictionary typealias StringValuePair = [String: Any] protocol StringValuePairConvertible { var stringValuePairs: StringValuePair {get} }
  • 21. Make collections also tranformable to Dictionary extension Array where Element : StringValuePairConvertible { var stringValuePairs: [StringValuePair] { return self.map { $0.stringValuePairs } } }
  • 22. Make Task a StringValuePairConvertible extension Task: StringValuePairConvertible { var stringValuePairs: StringValuePair { return [ "id": "(self.id)", "description": self.description, "createdAt": self.createdAt.timeIntervalSinceReferenceDate, "isCompleted": self.isCompleted ] } }
  • 23. Change [String] to [Task] private var tasks: [Task] = [] response.status(.OK).send(json: JSON(task.stringValuePairs))
  • 24. Add task with Tasks task.append(Task(id: UUID(), description: "Do the dishes", createdAt: Date(), isCompleted: false))
  • 25. Factor out the Database final class TaskDatabase { private var storage: [Task] = [] let queue = DispatchQueue(label: "com.example.tasklist") func addTask(oncompletion: (Task) -> Void) { queue.sync { self.storage.append(task) oncompletion(task) } } func getTasks(oncompletion: ([Task]) -> Void) { queue.sync { oncompletion(self.storage) } } }
  • 28. TaskListError enum TaskListError : LocalizedError { case descriptionTooShort(String) case descriptionTooLong(String) case noJSON
  • 29. Error Description var errorDescription: String? { switch self { case .descriptionTooShort(let string): return "(string) is too short" case .descriptionTooLong(let string): return "(string) is too long" case .noJSON: return "No JSON in payload" } }
  • 30. Make it convertible to JSON extension TaskListError: StringValuePairConvertible { var stringValuePairs: StringValuePair { return ["error": self.errorDescription ?? ""] } }
  • 31. Validating the Request let maximumLength = 40 let minimumLength = 3 struct AddTaskRequest { let description: String }
  • 32. Validate the request func validateRequest(request: RouterRequest) throws -> AddTaskRequest { guard let json = request.json else { throw TaskListError.noJSON } let description = json["description"].stringValue if description.characters.count > maximumLength { throw TaskListError.descriptionTooLong(description) } if description.characters.count < minimumLength { throw TaskListError.descriptionTooShort(description) } return AddTaskRequest(description: description) }
  • 35. Create Promises final class TaskDatabase { private var storage: [Task] = [] let queue = DispatchQueue(label: "com.example.tasklist") func addTask(task: Task) -> Promise<Task> { return Promise{ fulfill, reject in queue.sync { self.storage.append(task) fulfill(task) } } } func getTasks() -> Promise<[Task]> { return Promise{ fulfill, reject in queue.sync { fulfill(self.storage) } } } }
  • 36. Use the Promises _ = firstly { taskDatabase.getTasks() }.then (on: self.queue) { tasks in response.status(.OK).send(json: JSON(tasks.stringValuePairs)) } .catch (on: self.queue) { error in if let err = error as? TaskListError { response.status(.badRequest).send(json: JSON(err.stringValuePairs)) } } .always(on: self.queue) { next() }
  • 37. Use the Promises _ = firstly { () throws -> Promise<Task> in let addRequest = try validateRequest(request: request) let task = Task(with: addRequest) return taskDatabase.addTask(task: task) } .then (on: self.queue) { task -> Void in response.status(.OK).send(json: JSON(task.stringValuePairs)) } .catch (on: self.queue) { error in if let err = error as? TaskListError { response.status(.badRequest).send(json: JSON(err.stringValuePairs)) } } .always(on: self.queue) { next() }
  • 39. Set up Kitura framework private let queue = DispatchQueue(label: "Kitura runloop", qos: .userInitiated, attributes: .concurrent) public let defaultSession = URLSession(configuration: .default) private let todoListController = TodoListController() override func setUp() { super.setUp() Kitura.addHTTPServer(onPort: 8090, with: todoListController.router) queue.async { Kitura.run() } }
  • 40. Add a test func testGetTodos() { let expectation1 = expectation(description: "Get Todos") var url: URLRequest = URLRequest(url: URL(string: "http://localhost:8090/v1/tasks")!) url.addValue("application/json", forHTTPHeaderField: "Content-Type") url.httpMethod = "GET" url.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData let dataTask = defaultSession.dataTask(with: url) { data, response, error in XCTAssertNil(error) switch (response as? HTTPURLResponse)?.statusCode { case 200?: guard let data = data else { XCTAssert(false) return } let json = JSON(data: data) print(json) expectation1.fulfill() case nil: XCTFail("response not HTTPURLResponse") case let code?: XCTFail("bad status: (code)") } } dataTask.resume() waitForExpectations(timeout: 10, handler: { _ in }) }
  • 41. Enable Code coverage swift package generate-xcodeproj --enable-code-coverage