Vapor 3 - send a HTTPRequest
Your problem is .map(to: HTTPResponse.self)
. Map needs to transform its result into a new result regularly, like you would map
an array. However, the result of your map-closure returns an EventLoopFuture<HTTPResponse>
. This results in your map
function returning an EventLoopFuture<EventLoopFuture<HTTPResponse>>
.
To avoid this complexity, use flatMap
.
var headers: HTTPHeaders = .init()
let body = HTTPBody(string: a)
let httpReq = HTTPRequest(
method: .POST,
url: URL(string: "/post")!,
headers: headers,
body: body)
let client = HTTPClient.connect(hostname: "httpbin.org", on: req)
let httpRes = client.flatMap(to: HTTPResponse.self) { client in
return client.send(httpReq)
}
EDIT:
If you want to use the Content APIs you can do so like this:
let data = httpRes.flatMap(to: ExampleData.self) { httpResponse in
let response = Response(http: httpResponse, using: req)
return try response.content.decode(ExampleData.self)
}
Vapor 3 Routing
I don't know if I understand but to register the route and then handle the request you should write something like this:
class CountryController: RouteCollection {
// Register routes for country
func boot(router: Router) throws {
let group = router.grouped("api", "country")
group.post(Country.self, at: "new", use: newCountryHandler)
}
}
private extension CountryController {
func newCountryHandler(_ request: Request, newCountry: Country) throws -> Future<HTTPResponseStatus> {
// Handle your new Country object
}
}
How to upload files for a model using vapor 3 for an iOS app
Server-side
Model
final class AppObject: Codable {
var id: Int?
var ipaFile: String // relative path to file in Public dir
var plistFile: String // relative path to file in Public dir
var imageFile: String // relative path to file in Public dir
var notes: String
var name: String
init(ipaFile: String, plistFile: String, imageFile: String, notes: String, name: String) {
self.ipaFile = ipaFile
self.plistFile = plistFile
self.imageFile = imageFile
self.notes = notes
self.name = name
}
}
extension AppObject: MySQLModel {}
extension AppObject: Content {}
extension AppObject: Migration {}
extension AppObject: Parameter {}
Controller
struct AppObjectsController: RouteCollection {
func boot(router: Router) throws {
let appObjectsRoute = router.grouped("api", "apps")
appObjectsRoute.get(use: getAllHandler)
appObjectsRoute.post(PostData.self, use: createHandler)
}
func getAllHandler(_ req: Request) throws -> Future<[AppObject]> {
return AppObject.query(on: req).all()
}
}
extension AppObjectsController {
struct PostData: Content {
let ipaFile, plistFile, imageFile: File
let name, notes: String
}
func createHandler(_ req: Request, payload: PostData) throws -> Future<AppObject> {
let ipaFile = ServerFile(ext: "ipa", folder: .ipa)
let plistFile = ServerFile(ext: "plist", folder: .plist)
let imageFile = ServerFile(ext: "jpg", folder: .image)
let appObject = AppObject(ipaFile: ipaFile.relativePath, plistFile: plistFile.relativePath, imageFile: imageFile.relativePath, notes: payload.notes, name: payload.name)
/// we have to wrap it in transaction
/// to rollback object creating
/// in case if file saving fails
return req.transaction(on: .mysql) { conn in
return appObject.create(on: conn).map { appObject in
try ipaFile.save(with: payload.ipaFile.data)
try plistFile.save(with: payload.plistFile.data)
try imageFile.save(with: payload.imageFile.data)
}
}
}
}
ServerFile struct
struct ServerFile {
enum Folder: String {
case ipa = "ipa"
case plist = "plists"
case image = "images"
case root = ""
}
let file, ext: String
let folder: Folder
init (file: String? = UUID().uuidString, ext: String, folder: Folder? = .root) {
self.file = file
self.ext = ext
self.folder = folder
}
var relativePath: String {
guard folder != .root else { return fileWithExt }
return folder.rawValue + "/" + fileWithExt
}
var fileWithExt: String { return file + "." + ext }
func save(with data: Data) throws {
/// Get path to project's dir
let workDir = DirectoryConfig.detect().workDir
/// Build path to Public folder
let publicDir = workDir.appending("Public")
/// Build path to file folder
let fileFolder = publicDir + "/" + folder.rawValue
/// Create file folder if needed
var isDir : ObjCBool = true
if !FileManager.default.fileExists(atPath: fileFolder, isDirectory: &isDir) {
try FileManager.default.createDirectory(atPath: fileFolder, withIntermediateDirectories: true)
}
let filePath = publicDir + "/" + relativePath
/// Save data into file
try data.write(to: URL(fileURLWithPath: filePath))
}
}
iOS
Declare AppObject
model
struct AppObject: Codable {
var id: Int
var ipaFile, plistFile, imageFile: String
var name, notes: String
}
With CodyFire library multipart requests are really easy
Declare you endpoint
import CodyFire
struct AppController: EndpointController {
static var server: ServerURL? = nil
static var endpoint: String = "apps"
}
/// Usually separate file like App+Create.swift
extension AppController {
struct CreateAppRequest: MultipartPayload {
var ipaFile, plistFile, imageFile: Attachment
var name, note: String
public init (ipaFile: Attachment, plistFile: Attachment, imageFile: Attachment, name: String, note: String) {
self.ipaFile = ipaFile
self.plistFile = plistFile
self.imageFile = imageFile
self.name = name
self.note = note
}
}
static func create(_ payload: CreateAppRequest) -> APIRequest<AppObject> {
return request(payload: payload).method(.post)
}
}
Then in some view controller try to create an app on the server
/// Replace _ with file data
let ipaFile = Attachment(data: _, fileName: "", mimeType: "ipa")
let plistFile = Attachment(data: _, fileName: "", mimeType: "plist")
let imageFile = Attachment(data: _, fileName: "", mimeType: .jpg)
let payload = AppController.CreateAppRequest(ipaFile: ipaFile,
plistFile: plistFile,
imageFile: imageFile,
name: "something",
note: "something")
AppController.create(payload).onRequestStarted {
/// it calls only if request started properly
/// start showing loading bar
}.onError { error in
let alert = UIAlertController(title: nil, message: error.description, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel))
self.present(alert, animated: true)
}.onProgress { progress in
/// show progress
}.onSuccess { appObject in
/// show success
/// here you received just created `appObject`
}
And that's it, it just works :)
Next example for getting list of AppObject
s
/// Separate file like App+List.swift
extension AppController {
static func list() -> APIRequest<[AppObject]> {
return request()
}
}
then somewhere in view controller
AppController.list().onSuccess { appObjects in
/// `appObjects` is `[AppObject]`
}
Hope it helps.
Transactions with Vapor 4/Fluent
It's req.db.transaction
to create a new transaction for use. The tests are the best place to look to find examples - e.g. here
(Also an issue on GH at vapor/docs would be great to ensure the docs get written!)
Related Topics
Swift Optional Escaping Closure
Add Links to Swift Classes in the Quick Help Documentation Comments
Swift Uifont Ibinspectable - Is It Possible
In Swiftui How to Set the Environment Variable of Editmode in an Xcodepreview
How to Disable the Show Tab Bar Menu Option in Swiftui
How to Get the Edited Image from Uiimagepickercontroller in Swift
Trying to Use Keychainitemwrapper by Apple "Translated" to Swift
Vapor 3 Beta Example Endpoint Request
Add Shadow Above Swiftui's Tabview
Can/How to Replace My Kvo Stuff with Rc3
Check If Variable Is a Block/Function/Callable in Swift
How to Get Motion Events with the Apple Tv Remote
Convincing Swift That a Function Will Never Return, Due to a Thrown Exception
Detecting iOS Dark Mode Change