How can computation flow branch out of a sequence of multiple Future
actions, in a single Vapor route, to return a simple String
Response
which indicates which stage was exited?
Future
methods catch(_:)
, catchMap(on:_:)
, and catchFlatMap(_:)
can execute if a error is thrown; however, my experiments so far with any catch approach have not been able to branch out the sequence of Future
actions. (see API & Docs)
Note: Since Vapor 3 Async core is built on top of swift-nio, a SwiftNIO solution would also be of interest.
Example
For example, consider a Future
sequence which will create
a db entry, update
the same db entry, query
(read) the db entry, and then return some String
response.
Structure for Posting
{
"number": 0
}
public struct ExamplePipe: Codable {
public var id: UUID?
public var number: Int
init(number: Int) {
self.number = number
}
public func description() -> String {
return """
UUID: \(id?.uuidString ?? "nil.....-....-....-....-............")
{number:\(number)}
"""
}
}
// Database model for fetching and saving data via Fluent.
extension ExamplePipe: SQLiteUUIDModel {}
// Content convertable to/from HTTP message.
extension ExamplePipe: Content {}
// Database migration
extension ExamplePipe: Migration {}
// Dynamic HTTP routing parameter: `id`
extension ExamplePipe: Parameter {}
struct ExamplePipeController: RouteCollection {
func boot(router: Router) throws {
let pipelineRoutes = router.grouped("api", "pipeline")
// POST http://localhost:8080/api/pipeline/linear
pipelineRoutes.post(ExamplePipe.self, at: "linear", use: linearPost)
// POST http://localhost:8080/api/pipeline/nested
pipelineRoutes.post(ExamplePipe.self, at: "nested", use: nestedPost)
}
// …
}
Scenario: map
Linear Sequence
// POST http://localhost:8080/api/example/pipeline/basic
func linearPost(_ request: Request, _ pipelineData: ExamplePipe)
throws -> Future<String> {
var s = "##### Linear Pipeline Data #####\n"
let mutableA = pipelineData
s += "## STAGE_A \(mutableA.description())\n"
let futureA: Future<ExamplePipe> = mutableA.create(on: request)
let futureB: Future<ExamplePipe> = futureA.flatMap(to: ExamplePipe.self) {
(nonmutableB: ExamplePipe) -> Future<ExamplePipe> in
var mutableB = nonmutableB
mutableB.number += 1
if mutableB.number == 2 {
print("POSSIBLE TO EXIT SEQUENCE AT STAGE B??")
}
s += "## STAGE_B \(mutableB.description())\n"
let futureB: Future<ExamplePipe> = mutableB.update(on: request)
return futureB
}
let futureC: Future<ExamplePipe?> = futureB.flatMap {
(nonmutableC: ExamplePipe) -> Future<ExamplePipe?> in
s += "## STAGE_C \(nonmutableC.description())\n"
if nonmutableC.id == nil {
print("POSSIBLE TO EXIT SEQUENCE AT STAGE C??")
}
let uuid = nonmutableC.id!
let futureC: Future<ExamplePipe?> = ExamplePipe
.query(on: request)
.filter(\ExamplePipe.id==uuid)
.first()
return futureC
}
let futureD: Future<String> = futureC.map(to: String.self) {
(nonmutableD: ExamplePipe?) -> String in
guard var mutableD = nonmutableD else {
s += "## STAGE_D ExamplePipe is NIL\n"
s += "#################################\n"
print(s)
return s
}
mutableD.number += 1
s += "## STAGE_D \(mutableD.description())\n"
s += "#################################\n"
print(s)
return s
}
return futureD
}
Scenario: map
Nested Sequence
// POST http://localhost:8080/api/example/pipeline/nested
func nestedPost(_ request: Request, _ pipelineData: ExamplePipe)
throws -> Future<String> {
var s = "##### Nested Pipeline Data #####\n"
let mutableA = pipelineData
s += "## STAGE:A \(mutableA.description())\n"
let futureA: Future<ExamplePipe> = mutableA.create(on: request)
let futureD: Future<String> = futureA.flatMap {
(nonmutableB: ExamplePipe) -> Future<String> in
var mutableB = nonmutableB
mutableB.number += 1
if mutableB.number == 2 {
print("POSSIBLE TO EXIT SEQUENCE AT STAGE B??")
}
s += "## STAGE:B \(mutableB.description())\n"
let futureB: Future<ExamplePipe> = mutableB.update(on: request)
let futureDD: Future<String> = futureB.flatMap {
(nonmutableC: ExamplePipe) -> Future<String> in
s += "## STAGE:C \(nonmutableC.description())\n"
if nonmutableC.id == nil {
print("POSSIBLE TO EXIT SEQUENCE AT STAGE C??")
}
let uuid = nonmutableC.id!
let futureC: Future<ExamplePipe?> = ExamplePipe
.query(on: request)
.filter(\ExamplePipe.id==uuid)
.first()
let futureDDD: Future<String> = futureC.map(to: String.self) {
(nonmutableD: ExamplePipe?) -> String in
guard var mutableD = nonmutableD else {
s += "## STAGE:D ExamplePipe is `nil`\n"
s += "#################################\n"
print(s)
return s
}
mutableD.number += 1
s += "## STAGE:D \(mutableD.description())\n"
s += "#################################\n"
print(s)
return s
}
return futureDDD
}
return futureDD
}
return futureD
}