IOOI
(Lars Sonchocky-Helldorf)
1
Hi there,
getting my feet wet with Vapor 4 I want to serve several JSON files as raw Data or String for that matter using Vapor 4.
I have a database which contains besides other stuff UUIDs which also act as filenames for several JSON files.
Currently my code looks like this:
routes.swift:
func routes(_ app: Application) throws {
let geoJSONController = GeoJSONController()
…
app.get("MapEditorBackend", ":GeoJSONID", use: geoJSONController.get)
…
}
GeoJSONController.swift:
struct GeoJSONController {
…
func get(req: Request) throws -> EventLoopFuture<String> {
return GeoJSON.find(req.parameters.get("GeoJSONID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { String(decoding: Data.fromFile($0.id!.uuidString), as: UTF8.self) }
.transform(to: .ok)
}
func delete(req: Request) throws -> EventLoopFuture<HTTPStatus> {
return GeoJSON.find(req.parameters.get("GeoJSONID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { $0.delete(on: req.db) }
.transform(to: .ok)
}
}
when compiling above code I get those Errors:
SLPN-NB-LSH:MapEditorBackend lars$ ~/toolbox-18.0.0-beta.27/vapor-beta run
/Users/lars/Documents/Projects/MapEditor/MapEditorBackend/MapEditorBackend/Sources/App/Controllers/GeoJSONController.swift:16:14: error: generic parameter 'NewValue' could not be inferred
.unwrap(or: Abort(.notFound))
^
/Users/lars/Documents/Projects/MapEditor/MapEditorBackend/MapEditorBackend/.build/checkouts/swift-nio/Sources/NIO/EventLoopFuture.swift:465:17: note: in call to function 'flatMap(file:line:_:)'
public func flatMap<NewValue>(file: StaticString = #file, line: UInt = #line, _ callback: @escaping (Value) -> EventLoopFuture<NewValue>) -> EventLoopFuture<NewValue> {
^
/Users/lars/Documents/Projects/MapEditor/MapEditorBackend/MapEditorBackend/Sources/App/Controllers/GeoJSONController.swift:17:24: error: cannot convert value of type 'String' to closure result type 'EventLoopFuture<NewValue>'
.flatMap { String(decoding: Data.fromFile($0.id!.uuidString), as: UTF8.self) }
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/lars/Documents/Projects/MapEditor/MapEditorBackend/MapEditorBackend/Sources/App/Controllers/GeoJSONController.swift:18:29: error: type 'EventLoopFuture<String>' has no member 'ok'
.transform(to: .ok)
~^~
[2/3] Compiling App routes.swift
Fatal error: result 1: file /Users/lars/toolbox-18.0.0-beta.27/Sources/VaporToolbox/exec.swift, line 50
Illegal instruction: 4
while the delete Function seems to work fine (I used it as a template for my get function) here EventLoopFuture<String> as a return type doesn't seem to work.
What I am doing wrong here? Do I have to wrap the String I create in the flatMap closure somehow?
Thanks in advance,
Lars
mattpolzin
(Mathew Polzin)
2
At a glance I would guess the problem is actually with your string construction. You may want String(data:encoding:) (which produces an optional you will need to unwrap) or you could go straight from the file to the string with a different constructor skipping data altogether. You also should be able to just pass the data to a Response constructor if you’d like to go that route, but you would need to change the signature of your get function in that case.
bzamayo
(Benjamin Mayo)
3
The String initializer is fine. Again just scanning from this forum thread, I think the problem is using flatMap there, flatMap expects you to return an EventLoopFuture but the String initializer just returns a string. Change flatMap to map in the get(req:) handler and that should clean up your build errors.
1 Like
mattpolzin
(Mathew Polzin)
4
Good catch. I change my vote to this.
mattpolzin
(Mathew Polzin)
5
Is that a valid use of .transform(to:)? You should get an HTTP 200 status just for successfully creating a String here so at best I think it is superfluous.
1 Like
bzamayo
(Benjamin Mayo)
6
Another good point! If you want to return the string, you need to remove the transform. The transform(to: .ok) drops the string completely and only returns the status code.
1 Like
IOOI
(Lars Sonchocky-Helldorf)
7
Thanks to all for your quick help! Changing flatMap to just map and removing the transform almost fixed everything. Now my function looks like this:
func get(req: Request) throws -> EventLoopFuture<String> {
return GeoJSON.find(req.parameters.get("GeoJSONID"), on: req.db)
.unwrap(or: Abort(.notFound))
.map {
if let data = try? Data.fromFile($0.id!.uuidString) {
return String(decoding: data, as: UTF8.self)
} else {
return ""
}
}
}
and works like a charm!
bzamayo
(Benjamin Mayo)
8
One more suggestion, you may want to be more explicit about the error case here, rather than simply swallowing with try?. You can do something like:
func get(req: Request) throws -> EventLoopFuture<String> {
return GeoJSON.find(req.parameters.get("GeoJSONID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMapThrowing { found in
try Data.fromFile(found.id!.uuidString)
}.map { data in
String(decoding: data, as: UTF8.self)
}.flatMapErrorThrowing { error in
throw Abort(.internalServerError) // you could do anything in here, or omit `flatMapErrorThrowing ` and propagate the error from the `fromFile` call directly
}
}
1 Like