0
votes

My server side always requires a client to send a token. As a developer, I can forget it. Now, I want to come up with some solution that forces me to not forget sending such a required parameters (they can grow in the future, for instance, server can require device's language). The solution seems pretty easy: I should have some ServerManager that gets a parameters (for instance, ["user": "John"]) and URL path. And it also should append required parameters, for example, ["token": "abscsdcds"]. The pseudocode would be something like following:

struct ServerManager {
     func request(params: [String: Any], path: String, completion: (ResponseModel) -> Void) {
          /// appending required default params 
          var paramsToSend = params
          paramsToSend["token"] = Token.token 

          /// making request here               

     }
}

Whenever I make network call, I can use that ServerManager with any doubt that sends all required params and return response.

But things gets complicated because I use Moya for networking. It is made by enumerations that should implement TargetType protocol. I can have dozens of enums like RateAPI, MovieAPI and etc... It means that my ServerManager should accept TargetType and HTTP request using MoyaProvider. Here it is:

 func request<T>(type: TargetType, completion: (ResponseModel<T>) -> Void) {
              /// appending required default params 
              moyaProvider.request(MultiTarget(type)) {  response in 
                     /// parse it 
              }              



         }

I can simple use above function passing TargetType and it returns me a response. I can use above function as following:

ServerManager.shared.request(target: MovieApi.list(params)) { (response) in

}

But params variable above should always contain token. It means that I should write params["token"] = "myToken" whenever I make network request. It is code duplication. I thought to create some base RequestModel that contains my required parameters. Then I can have different subclasses of RequestModel that appends its fields to required params. But this solution can be easily mislead (for instance, subclass can send its own parameters forgetting about parent class params).

It seems my problem in design. Are there any design pattern that solves problem above? Or do we have built in solution in Moya/Alamofire that sends some parameters by default to every request?

1

1 Answers

1
votes

Usually server api's get token from TTPHeaderFields and with Moya you can easily change your defaultEndpointMapping in Some class like ApiGenerator with some method like:

static func request<T : Decodable, E: TargetType>(targetApi : E, responseModel : T.Type, success successCallBack: @escaping (Response<T>) -> Void, error errorCallBack: @escaping (Error) -> Void) -> Disposable {
    ...

    }

And inside this method:

let endpointClosure = { (target: E ) -> Endpoint in
            let defaultEndpoint = MoyaProvider.defaultEndpointMapping(for: target)
            let cookie = String(format: "JSESSIONID=%@;SPRING_SECURITY_REMEMBER_ME_COOKIE=%@", AppSettings.shared.setting.sessionId, AppSettings.shared.setting.rememberMeCookie )


            return defaultEndpoint.adding(newHTTPHeaderFields:
                [
                    "X-Client": "ios",
                    "Cookie" : cookie
                ]
            )
        }

and add it to:

let provider = MoyaProvider<E>(endpointClosure: endpointClosure, plugins: [
            NetworkLoggerPlugin(configuration: .init(logOptions: .verbose))])

And do the rest. But in your case I recommend to create your ApiGenerator class and customize your TargetApi.Task in it like this:

switch defaultEndpoint.task {
   case .requestParameters(parameters: /*Append new params here*/ , encoding: JSONEncoding.default):
       ...
    default:
       ...
 }