Concise method of updating a model to match a request body with Swift? - swift

Given a controller method which accepts a request body which conforms to some or all of the properties in an entity in Vapor, is there a means of updating the entity without manually assigning all of it's properties? Currently, I'm having to do this:
func update(_ req: Request) throws -> Future<Mission> {
let mission = try req.parameters.next(Mission.self)
let content = try req.content.decode(Mission.self)
return flatMap(to: Mission.self, mission, content) { (mission, content) in
mission.propertyA = content.propertyA
mission.propB = content.propB
mission.propC = content.propC
return mission.save(on: req)
}
}
This isn't very scalable as it requires me to manually assign each property. What I'm looking for is something like this:
func update(_ req: Request) throws -> Future<Mission> {
let mission = try req.parameters.next(Mission.self)
let content = try req.content.decode(Mission.self)
return mission.save(on: content)
}
However this yields the error Argument type 'EventLoopFuture<Mission>' does not conform to expected type 'DatabaseConnectable'.
What is a good solution here?

With Submissions you should be able to do:
func create(req: Request) throws -> Future<Either<Mission, SubmissionValidationError>> {
return try req.content.decode(Mission.Submission.self)
.updateValid(on: req)
.save(on: req)
.promoteErrors()
}
It takes some setting up but it's flexible and allows you to validate your input. The promoteErrors function + Either in the result help create useful error response but you could do without them.

That error that you're receiving is because you're trying to save(on: content), you need to save on the request:
return mission.save(on: req)
That being said, what you really want is this:
func update(_ req: Request) throws -> Future<Mission> {
let updatedMission = try req.content.decode(Mission.self)
return updatedMission.update(on: req)
}
This decodes a Mission object that is in the request body and then updates the Mission with the corresponding id in the database. So make sure when you're sending Mission JSON in the body that it has an id.

Related

Returning parsed JSON data using Alamofire?

Hello new to Swift and Alamofire,
The issue i'm having is when I call this fetchAllUsers() the code will return the empty users array and after it's done executing it will go inside the AF.request closure and execute the rest.
I've done some research and I was wondering is this is caused by Alamofire being an Async function.
Any suggestions?
func fetchAllUsers() -> [User] {
var users = [User]()
let allUsersUrl = baseUrl + "users/"
if let url = URL(string: allUsersUrl) {
AF.request(url).response { response in
if let data = response.data {
users = self.parse(json: data)
}
}
}
return users
}
You need to handle the asynchrony in some way. This this means passing a completion handler for the types you need. Other times it means you wrap it in other async structures, like promises or a publisher (which Alamofire also provides).
In you case, I'd suggest making your User type Decodable and allow Alamofire to do the decoding for you.
func fetchAllUsers(completionHandler: #escaping ([User]) -> Void) {
let allUsersUrl = baseUrl + "users/"
if let url = URL(string: allUsersUrl) {
AF.request(url).responseDecodable(of: [User].self) { response in
if let users = response.value {
completionHandler(users)
}
}
}
}
However, I would suggest returning the full Result from the response rather than just the [User] value, otherwise you'll miss any errors that occur.

Removing Swift RxAlamofire dependency

I'm trying to remove my dependency on RxAlamofire.
I currently have this function:
func requestData(_ urlRequest: URLRequestConvertible) -> Observable<(HTTPURLResponse, Data)> {
RxAlamofire.request(urlRequest).responseData()
}
How can I refactor this and use Alamofire directly to build and return an RxSwift Observable?
I suggest you look at the way the library wraps URLRequest to get an idea on how to do it...
Below is an abbreviated example from the library. In essence, you need to use Observable.create, make the network call passing in a closure that knows how to use the observer that create gives you.
Make sure you send a completed when done and make sure the disposable knows how to cancel the request.
Your Base will be something in Alamofire (I don't use Alamofire so I'm not sure what that might be.)
extension Reactive where Base: URLSession {
/**
Observable sequence of responses for URL request.
Performing of request starts after observer is subscribed and not after invoking this method.
**URL requests will be performed per subscribed observer.**
Any error during fetching of the response will cause observed sequence to terminate with error.
- parameter request: URL request.
- returns: Observable sequence of URL responses.
*/
public func response(request: URLRequest) -> Observable<(response: HTTPURLResponse, data: Data)> {
return Observable.create { observer in
let task = self.base.dataTask(with: request) { data, response, error in
guard let response = response, let data = data else {
observer.on(.error(error ?? RxCocoaURLError.unknown))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
observer.on(.error(RxCocoaURLError.nonHTTPResponse(response: response)))
return
}
observer.on(.next((httpResponse, data)))
observer.on(.completed)
}
task.resume()
return Disposables.create(with: task.cancel)
}
}
}

Vapor: how to not receive a particular upload?

In Vapor, how does one setup to check and decline an upload request prior to any part of such file being uploaded to the server?
My current attempt in Vapor 3 is with a route handler structured like:
func imagesUploadOneHandler(_ request: Request) throws -> EventLoopFuture<HTTPResponseStatus> {
let headers = request.http.headers
let headersUploadToken: [String] = headers["Upload-Token"]
if headersUploadToken.count != 1 || headersUploadToken[0] != aValidToken {
return HTTPResponseStatus.notAcceptable
}
// http body content type: 'application/octet-stream'
let dataFuture: EventLoopFuture<Data> = request.http.body.consumeData(max: 50_000_000, on: request)
let futureHTTPResponseStatus = dataFuture.map(to: HTTPResponseStatus.self, {
(data: Data) -> HTTPResponseStatus in
// ... other code
return HTTPResponseStatus.ok
})
return futureHTTPResponseStatus
}
Firstly, the above will not compile. The line return HTTPResponseStatus.notAcceptable has a compile time error "return HTTPResponseStatus.notAcceptable". How to convert HTTPResponseStatus to EventLoopFuture<HTTPResponseStatus> has been elusive.
Secondly, can some code prior to request.http.body.consumeData(...) in a route handler prevent an upload of the file content? Or, is some middleware needed instead to avoid uploading the data content (e.g. some large file) from the http.body?

Swift Response template without additional casting

Do somebody know how to achieve Template on Response but without extra casting? Now if I do so Xcode returns me error that I can't override T dynamically. But I really believe I on right way but missed something. No?
Now it's looks like: func didReciveResponse(request: Request, response: Response<Any>)
enum Response<T> {
case success(response: T)
case failured(error: Error)
}
func pseudoResponse() {
let time: Timeinterval = 3
// somehow cast T (compiler shows error that I can't do this)
let response = .success<Timeinterval>(time)
didReciveResponse(.time, response)
}
// delegate method
func didReciveResponse(request: Request, response: Response) {
switch request {
case .time:
switch response {
// response without additional casting (as?)
case .success(let value): time = value
}
}
}
As the compiler cannot infer the generic type you have to annotate the type in this case
func pseudoResponse() {
let time: TimeInterval = 3.0
let response : Response<TimeInterval> = .success(response: time)
didReciveResponse(request: .time, response: response)
}
and you have to specify the Response type in the delegate method
func didReciveResponse(request: Request, response: Response<TimeInterval>) { ...
However if you want to make didReciveResponse (actually didReceiveResponse) also generic you need to write
func didReciveResponse<T>(request: Request, response: Response<T>) {

Alamofire retry request - reactive way

I was looking at those two:
http://sapandiwakar.in/refresh-oauth-tokens-using-moya-rxswift/
Using retryWhen to update tokens based on http error code
And trying to create similiar thing, but without Moya, using Alamofire + RxSwift.
First of all is obviously where should I stick this, since my implementation is divided into a couple smaller parts.
First of all I have my custom method for generating reactive requests:
static func rx_request<T>(requestConvertible: URLRequestConvertible, completion: (Request) -> Observable<T> ) -> Observable<T> {
let manager: Manager = Manager.sharedInstance
return manager
.rx_request { manager -> Request in
return Alamofire.request(requestConvertible)
}
.flatMap { request -> Observable<T> in
return completion(request)
}
.shareReplay(1)
}
Which is later used by specific Requests convenience classes. For example my UserRequests has this private extension to extract some common code from it's methods:
private extension Request {
func rx_userRequest() -> Observable<User> {
return self
.validate()
.rx_responseJSON()
.flatMap{ (request, json) -> Observable<User> in
guard
let dict = json as? [ String: AnyObject ],
let parsedUser: User = try? Unbox(dict) else {
return Observable.error(RequestError.ParsingError)
}
return Observable.just(parsedUser)
}
.rx_storeCredentials()
}
}
Because of how things looks like I wonder whare's the right place to put a retry method and also how to implement it? Because depending on the location I can get different input arguments.
The retry code has to go after the first try, which is rx_responseJSON so the way you have things setup now, it must go between that and the flatMap after it.