How to add a field to the Moya.Response JSON that wasn't in the real payload from the http response - swift

If I have:
import Moya
import RxSwift
import ObjectMapper
import Moya_ObjectMapper
provider.request(.callApi(id: id))
.mapObject(Thing.self)
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.observeOn(MainScheduler.instance)
...
struct Thing: Mappable, Equatable {
var id: String?
init?(map: Map) {
}
mutating func mapping(map: Map) {
id <- map["id"]
}
Making an http api call and getting back json like {"id: "123"} and it's all working great. A new Thing struct is made with the right id. But what if I want to add "flavor" to Thing and hard code {"id: "123", "flavor": "something"}.
i.e. let's just modify the actual http response body and add "flavor": "something" before it gets to the .mapObject method. Where is the right place to tap into that?
And it's not just adding it to the mapping func in Thing because "something" is different for each id. Might be flavor: "something1" and then flavor: "something2". I have this value in the same scope as callApi(id: id) so something like:
provider.request(.callApi(id: id))
.addJSON("flavor", flavor)
.mapObject(Thing.self)
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.observeOn(MainScheduler.instance)
But .addJSON is something I just made up. It doesn't exist. But there must be some simple solution for this?

Trying to modify the actual JSON feels dirty and at two low-level. But I've been there so no judging from me if it works. :)
I'd approach it by creating a special version of the Moya.Response extension methods available from Moya_ObjectMapper.
public extension Response {
/// Maps data received from the signal into an object which implements the Mappable protocol.
/// If the conversion fails, the signal errors.
public func mapObject<T: BaseMappable>(_ type: T.Type, context: MapContext? = nil) throws -> T {
guard let object = Mapper<T>(context: context).map(JSONObject: try mapJSON()) else {
throw MoyaError.jsonMapping(self)
}
return object
}
I'd add a similar method but with an additional parameter closure (T) -> (T). So it would essentially return the mapped object after doing another map which would add any necessary information you need into it.

Related

Is it possible for a Swift type to be inferred by "pulling out" a Type value from a generic function's parameter?

Introduction
(Apologies if the title is confusing, but I explain the question better here!)
I'm building a networking library that can perform JSON decoding on its responses.
Host apps adopting this library will create enums conforming to NetLibRoute. All that currently does is enforce the presence of asURL:
public protocol NetLibRoute {
var asURL: URL { get throws }
}
In a host app, I have a routing system that enforces API structure at the compiler-level (via enums and associated values) for each endpoint, like this:
enum Routes: NetLibRoute {
case people(Int?)
// Other routes go here, e.g.:
// case user(Int)
// case search(query: String, limit: Int?)
var asURL: URL {
let host = "https://swapi.dev/"
let urlString: String
switch self {
case let .people(personID):
if let personID {
urlString = host + "api/people/\(personID)"
} else {
urlString = host + "api/people/"
}
// Build other URLs from associated values
}
return URL(string: urlString)!
}
}
I also want each enum to be associated with a certain Codable type. I can do that, of course, by modifying the Route protocol declaration to also require a type conforming to Decodable:
protocol NetLibRoute {
var asURL: URL { get throws }
var decodedType: Decodable.Type { get } // This
}
And a matching computed property in my Routes enum:
var decodedType: Decodable.Type {
switch self {
case .people(_):
return Person.self
// And so on
}
}
The Problem
Currently, my networking code has a declaration something like this:
public static func get<T>(route: NetLibRoute,
type: T.Type) async throws -> T where T: Decodable {
// performing request on route.asURL
// decoding from JSON as T or throwing error
// returning decoded T
}
Which lets me call it like this:
let person = try await NetLib.get(route: Routes.people(1), type: Person.self)
However, this redundancy (and potential human error from mismatching route and type) really irks me. I really want to be able to only pass in a route, and have the resulting type be inferred from there.
Is there some way to get the compiler to somehow check the NetLibRoute enum and check its decodedType property, in order to know what type to use?
Ultimately, I want this networking function to take one parameter (a route) and infer the return type of that route (at compile-time, not with fragile runtime hacks or !s), and return an instance of the type.
Is this possible?
Potential Alternatives?
I'm also open to alternative solutions that may involve moving where the get function is called from.
For example, calling this get function on a route itself to return the type:
let person = try await Routes.people(1).get(type: Person.self) // Works, but not optimal
let person = try await Routes.people(1).get() // What I want
Or even on the type itself, by creating a new protocol in the library, and then extending Decodable to conform to it:
public protocol NetLibFetchable {
static var route: NetLibRoute { get }
}
extension Decodable where Self: NetLibFetchable {
public static func get<T>() async throws -> T where Self == T, T: Decodable {
// Call normal get function using inferred properties
return try await NetLib.get(route: route,
type: T.self)
}
Which indeed lets me call like this:
let person = try await Person.get() // I can't figure out a clean way to pass in properties that the API may want, at least not without once again passing in Routes.people(1), defeating the goal of having Person and Routes.people inherently linked.
While this eliminates the issue of type inference, the route can no longer be customized at call-time, and instead is stuck like this:
extension Person: NetLibFetchable {
public static var route: NetLibRoute {
Routes.people(1) // Can't customize to different ID Ints anymore!
}
}
Which makes this particular example a no-go, and leaves me at a loss.
Appreciation
Anyway, thank you so much for reading, for your time, and for your help.
I really want this library to be as clean as possible for host apps interacting with it, and your help will make that possible.
Are you wedded to the idea of using an enum? If not, you can do pretty much what you want by giving each enum value its own type and using an associated type to do what you want.
public protocol NetLibRoute
{
var asURL: URL { get throws }
associatedtype Decoded: Decodable
}
struct Person: Decodable
{
var name: String
}
struct Login: Decodable
{
var id: String
}
struct People: NetLibRoute
{
typealias Decoded = Person
var id: Int
var asURL: URL { return URL(filePath: "/") }
}
struct User: NetLibRoute
{
typealias Decoded = Login
var id: String
var asURL: URL { return URL(filePath: "/") }
}
func get<N: NetLibRoute>(item: N) throws -> N.Decoded
{
let data = try Data(contentsOf: item.asURL)
return try JSONDecoder().decode(N.Decoded.self, from: data)
}
let thing1 = try get(item: People(id: 1))
let thing2 = try get(item: User(id: "foo"))
Where you might have had a switch before to do different things with different Routes you would now use a function with overloaded arguments.
func doSomething(thing: Person)
{
// do something for a Person
}
func doSomething(thing: Login)
{
// do something else for a Login
}
doSomething(thing: thing1)
doSomething(thing: thing2)
I think the problem lays in this function.
public static func get<T>(route: Route,
type: T.Type) async throws -> T where T: Decodable {
// performing request on route.asURL
// decoding from JSON as T or throwing error
// returning decoded T
}
On the first hand, it uses concretions instead of abstractions. You shouldn't pass a Route here, it should use your protocol NetLibRoute instead.
On the other hand, I think that the type param is not needed. Afaik you can get the Type to Decode with the var:
NetLibRoute.decodedType
Am I missing something on this matter?
Apart from that, I'd rather go with struct instead of enum when trying to implement the Routes (concretions). Enums cannot be extended. So you won't be allowing the creation of new requests in client side, only in the library.
I hope I've helped.
PS: Some time ago I made this repo. Maybe that could help you (specially this class). I used Combine instead of async/await, but it's not relevant to what you need.

MVVM Design with struct and async response

I'm trying to implement the MVVM approach to my app and have some trouble with the required struct as Model. In a struct it is hard to manipulate its own data but I need two because its holds the view data.
I request data from a firebase database and after receiving, assigning the data to an array.
As I found out, I can not manipulate the array inside a response closure. When I try to call another function as callback I get the following error:
Partial application of 'mutating' method is not allowed
Here is my code:
mutating func searchLocations(name: String, onlyOwnList: Bool) {
if onlyOwnList{
onlineDataManager.getOwnLocations(searchName: name, userId: appViewModel.currentUser, completionHandler: getLocationsCallback)
}
else{
onlineDataManager.getAvailableLocations(searchName: name, userId: appViewModel.currentUser, completionHandler: getLocationsCallback)
}
}
mutating private func getLocationsCallback(_ locations: BarLocations){
self.locationList.locations.removeAll()
for location in locations.locations{
self.locationList.locations.append(location)
}
}
I'm used to Java so im struggling with the sense of structs.
I hope somebody can help me or tell me, how to do it better
I believe the issue you're facing is that the mutation of the struct needs to happen in the same scope of the first mutating function since structs are pass-by-values.
mutating func searchLocations(name: String, onlyOwnList: Bool) {
if onlyOwnList{
onlineDataManager.getOwnLocations(searchName: name, userId: appViewModel.currentUser) { [weak self] locations in
self?.locationList.locations = locations
}
} else{
onlineDataManager.getAvailableLocations(searchName: name, userId: appViewModel.currentUser) { [weak self] locations in
self?.locationList.locations = locations
}
}
}
To be honest, if your model is handling the loading of its own information, I'd change your model to be a class and not a struct. Either that, or handle the data load outside of the model (in your view controller and then create a simple struct with the information).

enums with Associated Values + generics + protocol with associatedtype

I'm trying to make my API Service as generic as possible:
API Service Class
class ApiService {
func send<T>(request: RestRequest) -> T {
return request.parse()
}
}
So that the compiler can infer the response type from the request categories .auth and .data:
let apiService = ApiService()
// String
let stringResponse = apiService.send(request: .auth(.signupWithFacebook(token: "9999999999999")))
// Int
let intResponse = apiService.send(request: .data(.content(id: "123")))
I tried to come up with a solution using generics and a protocol with associated type to handle the parsing in a clean way. However I'm having trouble associating the request cases with the different response types in a way that it's simple and type-safe:
protocol Parseable {
associatedtype ResponseType
func parse() -> ResponseType
}
Endpoints
enum RestRequest {
case auth(_ request: AuthRequest)
case data(_ request: DataRequest)
// COMPILER ERROR HERE: Generic parameter 'T' is not used in function signature
func parse<T: Parseable>() -> T.ResponseType {
switch self {
case .auth(let request): return (request as T).parse()
case .data(let request): return (request as T).parse()
}
}
enum AuthRequest: Parseable {
case login(email: String, password: String)
case signupWithFacebook(token: String)
typealias ResponseType = String
func parse() -> ResponseType {
return "String!!!"
}
}
enum DataRequest: Parseable {
case content(id: String?)
case package(id: String?)
typealias ResponseType = Int
func parse() -> ResponseType {
return 16
}
}
}
How is T not used in function signature even though I'm using T.ResponseType as function return?
Is there a better still clean way to achieve this?
I'm trying to make my API Service as generic as possible:
First, and most importantly, this should never be a goal. Instead, you should start with use cases, and make sure that your API Service meets them. "As generic as possible" doesn't mean anything, and only will get you into type nightmares as you add "generic features" to things, which is not the same thing as being generally useful to many use cases. What callers require this flexibility? Start with the callers, and the protocols will follow.
func send<T>(request: RestRequest) -> T
Next, this is a very bad signature. You don't want type inference on return types. It's a nightmare to manage. Instead, the standard way to do this in Swift is:
func send<ResultType>(request: RestRequest, returning: ResultType.type) -> ResultType
By passing the expected result type as a parameter, you get rid of the type inference headaches. The headache looks like this:
let stringResponse = apiService.send(request: .auth(.signupWithFacebook(token: "9999999999999")))
How is the compiler to know that stringResponse is supposed to be a String? Nothing here says "String." So instead you have to do this:
let stringResponse: String = ...
And that's very ugly Swift. Instead you probably want (but not really):
let stringResponse = apiService.send(request: .auth(.signupWithFacebook(token: "9999999999999")),
returning: String.self)
"But not really" because there's no way to implement this well. How can send know how to translate "whatever response I get" into "an unknown type that happens to be called String?" What would that do?
protocol Parseable {
associatedtype ResponseType
func parse() -> ResponseType
}
This PAT (protocol w/ associated type) doesn't really make sense. It says something is parseable if an instance of it can return a ResponseType. But that would be a parser not "something that can be parsed."
For something that can be parsed, you want an init that can take some input and create itself. The best for that is Codable usually, but you could make your own, such as:
protocol Parseable {
init(parsing data: Data) throws
}
But I'd lean towards Codable, or just passing the parsing function (see below).
enum RestRequest {}
This is probably a bad use of enum, especially if what you're looking for is general usability. Every new RestRequest will require updating parse, which is the wrong place for this kind of code. Enums make it easy to add new "things that all instances implement" but hard to add "new kinds of instances." Structs (+ protocols) are the opposite. They make it easy to add new kinds of the protocol, but hard to add new protocol requirements. Requests, especially in a generic system, are the latter kind. You want to add new requests all the time. Enums make that hard.
Is there a better still clean way to achieve this?
It depends on what "this" is. What does your calling code look like? Where does your current system create code duplication that you want to eliminate? What are your use cases? There is no such thing as "as generic as possible." There are just systems that can adapt to use cases along axes they were prepared to handle. Different configuration axes lead to different kinds of polymorphism, and have different trade-offs.
What do you want your calling code to look like?
Just to provide an example of what this might look like, though, it'd be something like this.
final class ApiService {
let urlSession: URLSession
init(urlSession: URLSession = .shared) {
self.urlSession = urlSession
}
func send<Response: Decodable>(request: URLRequest,
returning: Response.Type,
completion: #escaping (Response?) -> Void) {
urlSession.dataTask(with: request) { (data, response, error) in
if let error = error {
// Log your error
completion(nil)
return
}
if let data = data {
let result = try? JSONDecoder().decode(Response.self, from: data)
// Probably check for nil here and log an error
completion(result)
return
}
// Probably log an error
completion(nil)
}
}
}
This is very generic, and can apply to numerous kinds of use cases (though this particular form is very primitive). You may find it doesn't apply to all your use cases, so you'd begin to expand on it. For example, maybe you don't like using Decodable here. You want a more generic parser. That's fine, make the parser configurable:
func send<Response>(request: URLRequest,
returning: Response.Type,
parsedBy: #escaping (Data) -> Response?,
completion: #escaping (Response?) -> Void) {
urlSession.dataTask(with: request) { (data, response, error) in
if let error = error {
// Log your error
completion(nil)
return
}
if let data = data {
let result = parsedBy(data)
// Probably check for nil here and log an error
completion(result)
return
}
// Probably log an error
completion(nil)
}
}
Maybe you want both approaches. That's fine, build one on top of the other:
func send<Response: Decodable>(request: URLRequest,
returning: Response.Type,
completion: #escaping (Response?) -> Void) {
send(request: request,
returning: returning,
parsedBy: { try? JSONDecoder().decode(Response.self, from: $0) },
completion: completion)
}
If you're looking for even more on this topic, you may be interested in "Beyond Crusty" which includes a worked-out example of tying together parsers of the kind you're discussing. It's a bit dated, and Swift protocols are more powerful now, but the basic message is unchanged and the foundation of things like parsedBy in this example.

Vapor 3 API: embed Future<Model> in response object

Suppose I have a model called Estimate. I have a Vapor 3 API that I want to return a list of these models, filtered by query parameters. Doing so currently returns a Future<[Estimate]>, which results in the API returning JSON that looks like this:
[{estimate object}, {estimate object}, ...]
Instead, I'd like make it return something this:
{"estimates": [{estimate object}, {estimate object}, ...]}
So, the same thing as before, but wrapped in a JSON object with a single key, "estimates".
According to the documentation, any time I want to return something non-default, I should make a new type for it; this suggests to me I should create a type like:
final class EstimatesResponse: Codable {
var estimates: [Estimate]?
}
However, after filtering I get a Future<[Estimate]> and NOT a pure [Estimate] array, meaning that I couldn't assign it to my EstimatesResponse estimates property. It seems weird to make the type of estimates be Future<[Estimate]>, and I'm not sure how that'd turn out.
How, then, can I return JSON of the correct format?
First, you need to create Codable object, I prefer struct as below. Must implement protocol Content for routing.
struct EstimatesResponse: Codable {
var estimates: [Estimate]
}
extension EstimatesResponse: Content { }
I assumed that you are using a controller and inside the controller, you can use the following pseudo-code. Adjust your code so that val is Future<[Estimate]>, then use flatmap/map to get [Estimate].
func getEstimates(_ req: Request) throws -> Future<EstimatesResponse> {
let val = Estimate.query(on: req).all()
return val.flatMap { model in
let all = EstimatesResponse(estimates: model)
return Future.map(on: req) {return all }
}
}

Swift: Protocol Based Type Construction

I'm trying to create a protocol in Swift I can use for object construction. The problem I'm running into is that I need to store the type information so the type can be constructed later and returned in a callback. I can't seem to find a way to store it without either crashing the compiler or creating build errors. Here's the basics (a contrived, but working example):
protocol Model {
init(values: [String])
func printValues()
}
struct Request<T:Model> {
let returnType:T.Type
let callback:T -> ()
}
We have a simple protocol that declares a init (for construction) and another func printValues() (for testing). We also define a struct we can use to store the type information and a callback to return the new type when its constructed.
Next we create a constructor:
class Constructor {
var callbacks: [Request<Model>] = []
func construct<T:Model>(type:T.Type, callback: T -> ()) {
callback(type(values: ["value1", "value2"]))
}
func queueRequest<T:Model>(request: Request<T>) {
callbacks.append(request)
}
func next() {
if let request = callbacks.first {
let model = request.returnType(values: ["value1", "value2"])
request.callback(model)
}
}
}
A couple things to note: This causes a compiler crash. It can't figure this out for some reason. The problem appears to be var callbacks: [Request<Model>] = []. If I comment out everything else, the compiler still crashes. Commenting out the var callbacks and the compiler stops crashing.
Also, the func construct works fine. But it doesn't store the type information so it's not so useful to me. I put in there for demonstration.
I found I could prevent the compiler from crashing if I remove the protocol requirement from the Request struct: struct Request<T>. In this case everything works and compiles but I still need to comment out let model = request.returnType(values: ["value1", "value2"]) in func next(). That is also causing a compiler crash.
Here's a usage example:
func construct() {
let constructor = Constructor()
let request = Request(returnType: TypeA.self) { req in req.printValues() }
//This works fine
constructor.construct(TypeA.self) { a in
a.printValues()
}
//This is what I want
constructor.queueRequest(request)
constructor.next() //The callback in the request object should be called and the values should print
}
Does anyone know how I can store type information restricted to a specific protocol to the type can later be constructed dynamically and returned in a callback?
If you want the exact same behavior of next I would suggest to do this:
class Constructor {
// store closures
var callbacks: [[String] -> ()] = []
func construct<T:Model>(type:T.Type, callback: T -> ()) {
callback(type(values: ["value1", "value2"]))
}
func queueRequest<T:Model>(request: Request<T>) {
// some code from the next function so you don't need to store the generic type itself
// **EDIT** changed closure to type [String] -> () in order to call it with different values
callbacks.append({ values in
let model = request.returnType(values: values)
request.callback(model)
})
}
func next(values: [String]) {
callbacks.first?(values)
}
}
Now you can call next with your values. Hopefully this works for you.
EDIT: Made some changes to the closure type and the next function
Unfortunately there is no way to save specific generic types in an array and dynamically call their methods because Swift is a static typed language (and Array has to have unambiguous types).
But hopefully we can express something like this in the future like so:
var callbacks: [Request<T: Model>] = []
Where T could be anything but has to conform to Model for example.
Your queueRequest method shouldn't have to know the generic type the Request it's being passed. Since callbacks is an array of Request<Model> types, the method just needs to know that the request being queued is of the type Request<Model>. It doesn't matter what the generic type is.
This code builds for me in a Playground:
class Constructor {
var callbacks: [Request<Model>] = []
func construct<T:Model>(type:T.Type, callback: T -> ()) {
callback(type(values: ["value1", "value2"]))
}
func queueRequest(request: Request<Model>) {
callbacks.append(request)
}
func next() {
if let request = callbacks.first {
let model = request.returnType(values: ["value1", "value2"])
request.callback(model)
}
}
}
So I found an answer that seems to do exactly what I want. I haven't confirmed this works yet in live code, but it does compile without any errors. Turns out, I needed to add one more level of redirection:
I create another protocol explicitly for object construction:
protocol ModelConstructor {
func constructWith(values:[String])
}
In my Request struct, I conform to this protocol:
struct Request<T:Model> : ModelConstructor {
let returnType:T.Type
let callback:T -> ()
func constructWith(values:[String]) {
let model = returnType(values: values)
callback(model)
}
}
Notice the actual construction is moved into the Request struct. Technically, the Constructor is no longer constructing, but for now I leave its name alone. I can now store the Request struct as ModelConstructor and correctly queue Requests:
class Constructor {
var callbacks: [ModelConstructor] = []
func queueRequest(request: Request<Model>) {
queueRequest(request)
}
func queueRequest(request: ModelConstructor) {
callbacks.append(request)
}
func next() {
if let request = callbacks.first {
request.constructWith(["value1", "value2"])
callbacks.removeAtIndex(0)
}
}
}
Note something special here: I can now successfully "queue" (or store in an array) Request<Model>, but I must do so indirectly by calling queueRequest(request: ModelConstructor). In this case, I'm overloading but that's not necessary. What matters here is that if I try to call callbacks.append(request) in the queueRequest(request: Request<Model>) function, the Swift compiler crashes. Apparently we need to hold the compiler's hand here a little so it can understand what exactly we want.
What I've found is that you cannot separate Type information from Type Construction. It needs to be all in the same place (in this case it's the Request struct). But so long as you keep construction coupled with the Type information, you're free to delay/store the construction until you have the information you need to actually construct the object.