Change response model / shape depending on path












0















I have a networking layer I am using to call multiple endpoints. I'd like to reduce the amount of repeated code and thought perhaps I could pass my response model as part of my endpoint.



The idea would be instead of needing multiple functions that simply differ by response, I could just call my network layer and have this set based on the path.



The current error I see is




Var 'responseType' is not a member type of 'IdentityEndpoint'




I was hoping to achieve something like this



mutating func identity(with endpoint: IdentityEndpoint, completion: @escaping (Either<IdentityEndpoint.responseType>) -> Void)


instead of this



mutating func identity(with endpoint: IdentityEndpoint, completion: @escaping (Either<OAuthToken>) -> Void) 


APIClient



struct APIClient: APIClientProtocol {
var task: URLSessionDataTask = URLSessionDataTask()
var session: SessionProtocol = URLSession.shared
var request: URLRequest?

mutating func identity(with endpoint: IdentityEndpoint, completion: @escaping (Either<IdentityEndpoint.responseType>) -> Void) {
dispatch(endpoint: endpoint, completion: completion)
}
}

extension APIClient {
fileprivate mutating func dispatch<T: Codable>(endpoint: EndpointProtocol, completion: @escaping (Either<T>) -> Void) {
do {
request = try constructRequest(from: endpoint)
guard let request = request else { return }
call(with: request, completion: completion)
} catch {}
}

fileprivate func constructRequest(from route: EndpointProtocol) throws -> URLRequest {
var request = URLRequest(url: route.baseUrl.appendingPathComponent(route.path), cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 10.0)
request.httpMethod = route.httpMethod.rawValue
do {
switch route.task {
case .request(let headers):
addAdditionalHeaders(headers, request: &request)
case .requestParams(let bodyParams, let encoding, let urlParams, let headers):
addAdditionalHeaders(headers, request: &request)
try configureParameters(bodyParams: bodyParams, encoding: encoding, urlParams: urlParams, request: &request)
}
return request
} catch {
throw NSError(domain: "Could not create request task for (route.task)", code: 0, userInfo: nil)
}
}

fileprivate func configureParameters(bodyParams: Parameters?, encoding: ParameterEncoding, urlParams: Parameters?, request: inout URLRequest) throws {
do {
try encoding.encode(urlRequest: &request, bodyParams: bodyParams, urlParams: urlParams)
} catch {
throw NSError(domain: "Could not configure params for request", code: 0, userInfo: nil)
}
}

fileprivate func addAdditionalHeaders(_ additionalHeaders: HTTPHeaders?, request: inout URLRequest) {
guard let headers = additionalHeaders else { return }
for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}
}
}


IdentityEndPoint



protocol EndpointProtocol {
var baseUrl: URL { get }
var path: String { get }
var httpMethod: HTTPMethod { get }
var task: HTTPTask { get }
var headers: HTTPHeaders? { get }
}


public enum IdentityEndpoint {
case accessToken(company: String, code: String)

func getDomain(forService service: String) -> URL {
return URL(string: "https://{SERVICE}.foo.bar".replacingOccurrences(of: "{SERVICE}", with: service))!
}
}

extension IdentityEndpoint: EndpointProtocol {
var baseUrl: URL {
return getDomain(forService: "identity")
}

var responseType: Codable {
switch self {
default:
return OAuthToken.self as! Codable
}
}

var path: String {
switch self {
case .accessToken(let props):
return "/auth/realms/(props.company)/protocol/openid-connect/token"
}
}

var httpMethod: HTTPMethod {
switch self {
case .accessToken:
return .POST
}
}

var headers: HTTPHeaders? {
switch self {
case .accessToken:
return ["Content-Type": "application/x-www-form-urlencoded"]
}
}

var task: HTTPTask {
switch self {
case .accessToken(let props):
return .requestParams(bodyParams: [
"grant_type": "authorization_code", "code": "(props.code)", "redirect_uri": "homedev://oauth-callback", "client_id": "mobile-home"
], encoding: .jsonEncoding, urlParams: nil, headers: headers)
}
}
}









share|improve this question



























    0















    I have a networking layer I am using to call multiple endpoints. I'd like to reduce the amount of repeated code and thought perhaps I could pass my response model as part of my endpoint.



    The idea would be instead of needing multiple functions that simply differ by response, I could just call my network layer and have this set based on the path.



    The current error I see is




    Var 'responseType' is not a member type of 'IdentityEndpoint'




    I was hoping to achieve something like this



    mutating func identity(with endpoint: IdentityEndpoint, completion: @escaping (Either<IdentityEndpoint.responseType>) -> Void)


    instead of this



    mutating func identity(with endpoint: IdentityEndpoint, completion: @escaping (Either<OAuthToken>) -> Void) 


    APIClient



    struct APIClient: APIClientProtocol {
    var task: URLSessionDataTask = URLSessionDataTask()
    var session: SessionProtocol = URLSession.shared
    var request: URLRequest?

    mutating func identity(with endpoint: IdentityEndpoint, completion: @escaping (Either<IdentityEndpoint.responseType>) -> Void) {
    dispatch(endpoint: endpoint, completion: completion)
    }
    }

    extension APIClient {
    fileprivate mutating func dispatch<T: Codable>(endpoint: EndpointProtocol, completion: @escaping (Either<T>) -> Void) {
    do {
    request = try constructRequest(from: endpoint)
    guard let request = request else { return }
    call(with: request, completion: completion)
    } catch {}
    }

    fileprivate func constructRequest(from route: EndpointProtocol) throws -> URLRequest {
    var request = URLRequest(url: route.baseUrl.appendingPathComponent(route.path), cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 10.0)
    request.httpMethod = route.httpMethod.rawValue
    do {
    switch route.task {
    case .request(let headers):
    addAdditionalHeaders(headers, request: &request)
    case .requestParams(let bodyParams, let encoding, let urlParams, let headers):
    addAdditionalHeaders(headers, request: &request)
    try configureParameters(bodyParams: bodyParams, encoding: encoding, urlParams: urlParams, request: &request)
    }
    return request
    } catch {
    throw NSError(domain: "Could not create request task for (route.task)", code: 0, userInfo: nil)
    }
    }

    fileprivate func configureParameters(bodyParams: Parameters?, encoding: ParameterEncoding, urlParams: Parameters?, request: inout URLRequest) throws {
    do {
    try encoding.encode(urlRequest: &request, bodyParams: bodyParams, urlParams: urlParams)
    } catch {
    throw NSError(domain: "Could not configure params for request", code: 0, userInfo: nil)
    }
    }

    fileprivate func addAdditionalHeaders(_ additionalHeaders: HTTPHeaders?, request: inout URLRequest) {
    guard let headers = additionalHeaders else { return }
    for (key, value) in headers {
    request.setValue(value, forHTTPHeaderField: key)
    }
    }
    }


    IdentityEndPoint



    protocol EndpointProtocol {
    var baseUrl: URL { get }
    var path: String { get }
    var httpMethod: HTTPMethod { get }
    var task: HTTPTask { get }
    var headers: HTTPHeaders? { get }
    }


    public enum IdentityEndpoint {
    case accessToken(company: String, code: String)

    func getDomain(forService service: String) -> URL {
    return URL(string: "https://{SERVICE}.foo.bar".replacingOccurrences(of: "{SERVICE}", with: service))!
    }
    }

    extension IdentityEndpoint: EndpointProtocol {
    var baseUrl: URL {
    return getDomain(forService: "identity")
    }

    var responseType: Codable {
    switch self {
    default:
    return OAuthToken.self as! Codable
    }
    }

    var path: String {
    switch self {
    case .accessToken(let props):
    return "/auth/realms/(props.company)/protocol/openid-connect/token"
    }
    }

    var httpMethod: HTTPMethod {
    switch self {
    case .accessToken:
    return .POST
    }
    }

    var headers: HTTPHeaders? {
    switch self {
    case .accessToken:
    return ["Content-Type": "application/x-www-form-urlencoded"]
    }
    }

    var task: HTTPTask {
    switch self {
    case .accessToken(let props):
    return .requestParams(bodyParams: [
    "grant_type": "authorization_code", "code": "(props.code)", "redirect_uri": "homedev://oauth-callback", "client_id": "mobile-home"
    ], encoding: .jsonEncoding, urlParams: nil, headers: headers)
    }
    }
    }









    share|improve this question

























      0












      0








      0








      I have a networking layer I am using to call multiple endpoints. I'd like to reduce the amount of repeated code and thought perhaps I could pass my response model as part of my endpoint.



      The idea would be instead of needing multiple functions that simply differ by response, I could just call my network layer and have this set based on the path.



      The current error I see is




      Var 'responseType' is not a member type of 'IdentityEndpoint'




      I was hoping to achieve something like this



      mutating func identity(with endpoint: IdentityEndpoint, completion: @escaping (Either<IdentityEndpoint.responseType>) -> Void)


      instead of this



      mutating func identity(with endpoint: IdentityEndpoint, completion: @escaping (Either<OAuthToken>) -> Void) 


      APIClient



      struct APIClient: APIClientProtocol {
      var task: URLSessionDataTask = URLSessionDataTask()
      var session: SessionProtocol = URLSession.shared
      var request: URLRequest?

      mutating func identity(with endpoint: IdentityEndpoint, completion: @escaping (Either<IdentityEndpoint.responseType>) -> Void) {
      dispatch(endpoint: endpoint, completion: completion)
      }
      }

      extension APIClient {
      fileprivate mutating func dispatch<T: Codable>(endpoint: EndpointProtocol, completion: @escaping (Either<T>) -> Void) {
      do {
      request = try constructRequest(from: endpoint)
      guard let request = request else { return }
      call(with: request, completion: completion)
      } catch {}
      }

      fileprivate func constructRequest(from route: EndpointProtocol) throws -> URLRequest {
      var request = URLRequest(url: route.baseUrl.appendingPathComponent(route.path), cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 10.0)
      request.httpMethod = route.httpMethod.rawValue
      do {
      switch route.task {
      case .request(let headers):
      addAdditionalHeaders(headers, request: &request)
      case .requestParams(let bodyParams, let encoding, let urlParams, let headers):
      addAdditionalHeaders(headers, request: &request)
      try configureParameters(bodyParams: bodyParams, encoding: encoding, urlParams: urlParams, request: &request)
      }
      return request
      } catch {
      throw NSError(domain: "Could not create request task for (route.task)", code: 0, userInfo: nil)
      }
      }

      fileprivate func configureParameters(bodyParams: Parameters?, encoding: ParameterEncoding, urlParams: Parameters?, request: inout URLRequest) throws {
      do {
      try encoding.encode(urlRequest: &request, bodyParams: bodyParams, urlParams: urlParams)
      } catch {
      throw NSError(domain: "Could not configure params for request", code: 0, userInfo: nil)
      }
      }

      fileprivate func addAdditionalHeaders(_ additionalHeaders: HTTPHeaders?, request: inout URLRequest) {
      guard let headers = additionalHeaders else { return }
      for (key, value) in headers {
      request.setValue(value, forHTTPHeaderField: key)
      }
      }
      }


      IdentityEndPoint



      protocol EndpointProtocol {
      var baseUrl: URL { get }
      var path: String { get }
      var httpMethod: HTTPMethod { get }
      var task: HTTPTask { get }
      var headers: HTTPHeaders? { get }
      }


      public enum IdentityEndpoint {
      case accessToken(company: String, code: String)

      func getDomain(forService service: String) -> URL {
      return URL(string: "https://{SERVICE}.foo.bar".replacingOccurrences(of: "{SERVICE}", with: service))!
      }
      }

      extension IdentityEndpoint: EndpointProtocol {
      var baseUrl: URL {
      return getDomain(forService: "identity")
      }

      var responseType: Codable {
      switch self {
      default:
      return OAuthToken.self as! Codable
      }
      }

      var path: String {
      switch self {
      case .accessToken(let props):
      return "/auth/realms/(props.company)/protocol/openid-connect/token"
      }
      }

      var httpMethod: HTTPMethod {
      switch self {
      case .accessToken:
      return .POST
      }
      }

      var headers: HTTPHeaders? {
      switch self {
      case .accessToken:
      return ["Content-Type": "application/x-www-form-urlencoded"]
      }
      }

      var task: HTTPTask {
      switch self {
      case .accessToken(let props):
      return .requestParams(bodyParams: [
      "grant_type": "authorization_code", "code": "(props.code)", "redirect_uri": "homedev://oauth-callback", "client_id": "mobile-home"
      ], encoding: .jsonEncoding, urlParams: nil, headers: headers)
      }
      }
      }









      share|improve this question














      I have a networking layer I am using to call multiple endpoints. I'd like to reduce the amount of repeated code and thought perhaps I could pass my response model as part of my endpoint.



      The idea would be instead of needing multiple functions that simply differ by response, I could just call my network layer and have this set based on the path.



      The current error I see is




      Var 'responseType' is not a member type of 'IdentityEndpoint'




      I was hoping to achieve something like this



      mutating func identity(with endpoint: IdentityEndpoint, completion: @escaping (Either<IdentityEndpoint.responseType>) -> Void)


      instead of this



      mutating func identity(with endpoint: IdentityEndpoint, completion: @escaping (Either<OAuthToken>) -> Void) 


      APIClient



      struct APIClient: APIClientProtocol {
      var task: URLSessionDataTask = URLSessionDataTask()
      var session: SessionProtocol = URLSession.shared
      var request: URLRequest?

      mutating func identity(with endpoint: IdentityEndpoint, completion: @escaping (Either<IdentityEndpoint.responseType>) -> Void) {
      dispatch(endpoint: endpoint, completion: completion)
      }
      }

      extension APIClient {
      fileprivate mutating func dispatch<T: Codable>(endpoint: EndpointProtocol, completion: @escaping (Either<T>) -> Void) {
      do {
      request = try constructRequest(from: endpoint)
      guard let request = request else { return }
      call(with: request, completion: completion)
      } catch {}
      }

      fileprivate func constructRequest(from route: EndpointProtocol) throws -> URLRequest {
      var request = URLRequest(url: route.baseUrl.appendingPathComponent(route.path), cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 10.0)
      request.httpMethod = route.httpMethod.rawValue
      do {
      switch route.task {
      case .request(let headers):
      addAdditionalHeaders(headers, request: &request)
      case .requestParams(let bodyParams, let encoding, let urlParams, let headers):
      addAdditionalHeaders(headers, request: &request)
      try configureParameters(bodyParams: bodyParams, encoding: encoding, urlParams: urlParams, request: &request)
      }
      return request
      } catch {
      throw NSError(domain: "Could not create request task for (route.task)", code: 0, userInfo: nil)
      }
      }

      fileprivate func configureParameters(bodyParams: Parameters?, encoding: ParameterEncoding, urlParams: Parameters?, request: inout URLRequest) throws {
      do {
      try encoding.encode(urlRequest: &request, bodyParams: bodyParams, urlParams: urlParams)
      } catch {
      throw NSError(domain: "Could not configure params for request", code: 0, userInfo: nil)
      }
      }

      fileprivate func addAdditionalHeaders(_ additionalHeaders: HTTPHeaders?, request: inout URLRequest) {
      guard let headers = additionalHeaders else { return }
      for (key, value) in headers {
      request.setValue(value, forHTTPHeaderField: key)
      }
      }
      }


      IdentityEndPoint



      protocol EndpointProtocol {
      var baseUrl: URL { get }
      var path: String { get }
      var httpMethod: HTTPMethod { get }
      var task: HTTPTask { get }
      var headers: HTTPHeaders? { get }
      }


      public enum IdentityEndpoint {
      case accessToken(company: String, code: String)

      func getDomain(forService service: String) -> URL {
      return URL(string: "https://{SERVICE}.foo.bar".replacingOccurrences(of: "{SERVICE}", with: service))!
      }
      }

      extension IdentityEndpoint: EndpointProtocol {
      var baseUrl: URL {
      return getDomain(forService: "identity")
      }

      var responseType: Codable {
      switch self {
      default:
      return OAuthToken.self as! Codable
      }
      }

      var path: String {
      switch self {
      case .accessToken(let props):
      return "/auth/realms/(props.company)/protocol/openid-connect/token"
      }
      }

      var httpMethod: HTTPMethod {
      switch self {
      case .accessToken:
      return .POST
      }
      }

      var headers: HTTPHeaders? {
      switch self {
      case .accessToken:
      return ["Content-Type": "application/x-www-form-urlencoded"]
      }
      }

      var task: HTTPTask {
      switch self {
      case .accessToken(let props):
      return .requestParams(bodyParams: [
      "grant_type": "authorization_code", "code": "(props.code)", "redirect_uri": "homedev://oauth-callback", "client_id": "mobile-home"
      ], encoding: .jsonEncoding, urlParams: nil, headers: headers)
      }
      }
      }






      swift generics codable urlsession






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Dec 28 '18 at 16:23









      Harry BlueHarry Blue

      691522




      691522
























          1 Answer
          1






          active

          oldest

          votes


















          0














          Add an associatedtype to your EndpointProtocol. Then specify it in IdentityEndpoint like this



          protocol EndpointProtocol {
          associatedtype ResponseType

          ...
          }

          extension IdentityEndpoint: EndpointProtocol {
          typealias ResponseType = OAuthToken

          ...
          }


          Now you will be able to write



          mutating func identity(
          with endpoint: IdentityEndpoint,
          completion: @escaping (Either<IdentityEndpoint.ResponseType>) -> Void
          )





          share|improve this answer























            Your Answer






            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "1"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53961409%2fchange-response-model-shape-depending-on-path%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            0














            Add an associatedtype to your EndpointProtocol. Then specify it in IdentityEndpoint like this



            protocol EndpointProtocol {
            associatedtype ResponseType

            ...
            }

            extension IdentityEndpoint: EndpointProtocol {
            typealias ResponseType = OAuthToken

            ...
            }


            Now you will be able to write



            mutating func identity(
            with endpoint: IdentityEndpoint,
            completion: @escaping (Either<IdentityEndpoint.ResponseType>) -> Void
            )





            share|improve this answer




























              0














              Add an associatedtype to your EndpointProtocol. Then specify it in IdentityEndpoint like this



              protocol EndpointProtocol {
              associatedtype ResponseType

              ...
              }

              extension IdentityEndpoint: EndpointProtocol {
              typealias ResponseType = OAuthToken

              ...
              }


              Now you will be able to write



              mutating func identity(
              with endpoint: IdentityEndpoint,
              completion: @escaping (Either<IdentityEndpoint.ResponseType>) -> Void
              )





              share|improve this answer


























                0












                0








                0







                Add an associatedtype to your EndpointProtocol. Then specify it in IdentityEndpoint like this



                protocol EndpointProtocol {
                associatedtype ResponseType

                ...
                }

                extension IdentityEndpoint: EndpointProtocol {
                typealias ResponseType = OAuthToken

                ...
                }


                Now you will be able to write



                mutating func identity(
                with endpoint: IdentityEndpoint,
                completion: @escaping (Either<IdentityEndpoint.ResponseType>) -> Void
                )





                share|improve this answer













                Add an associatedtype to your EndpointProtocol. Then specify it in IdentityEndpoint like this



                protocol EndpointProtocol {
                associatedtype ResponseType

                ...
                }

                extension IdentityEndpoint: EndpointProtocol {
                typealias ResponseType = OAuthToken

                ...
                }


                Now you will be able to write



                mutating func identity(
                with endpoint: IdentityEndpoint,
                completion: @escaping (Either<IdentityEndpoint.ResponseType>) -> Void
                )






                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered Jan 8 at 13:01









                Damiaan DufauxDamiaan Dufaux

                2,10111021




                2,10111021






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Stack Overflow!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53961409%2fchange-response-model-shape-depending-on-path%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Monofisismo

                    Angular Downloading a file using contenturl with Basic Authentication

                    Olmecas