Change response model / shape depending on path
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
add a comment |
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
add a comment |
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
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
swift generics codable urlsession
asked Dec 28 '18 at 16:23
Harry BlueHarry Blue
691522
691522
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
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
)
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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
)
add a comment |
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
)
add a comment |
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
)
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
)
answered Jan 8 at 13:01
Damiaan DufauxDamiaan Dufaux
2,10111021
2,10111021
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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