Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

swift: add response interfaces #261

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions library/swift/src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ swift_static_framework(
name = "ios_framework",
srcs = [
"Envoy.swift",
"EnvoyResult.swift",
buildbreaker marked this conversation as resolved.
Show resolved Hide resolved
"LogLevel.swift",
"NetworkError.swift",
"Request.swift",
"RequestBuilder.swift",
"RequestMethod.swift",
"Response.swift",
"ResponseBuilder.swift",
"RetryPolicy.swift",
],
module_name = "Envoy",
Expand Down
18 changes: 18 additions & 0 deletions library/swift/src/EnvoyResult.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Foundation

/// A result returned from Envoy.
@objcMembers
public final class Result: NSObject {
/// The response returned from the server.
public let response: Response?
/// An error that was encountered on the network (i.e., going offline).
public let error: NetworkError?

/// Designated initializer.
public init(response: Response?,
error: NetworkError?)
{
self.response = response
self.error = error
}
}
14 changes: 14 additions & 0 deletions library/swift/src/NetworkError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Foundation

/// Error representing cases when no response was received from the server.
/// I.e., the client went offline or became disconnected.
@objcMembers
public final class NetworkError: NSObject, Swift.Error {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, more of a platform question:
It is a swift convention to return an error and let the caller handle the error that way, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a swift convention to return an error and let the caller handle the error that way, correct?

Are you referring to doing this instead of throwing?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The benefit of returning an error is that it can be typed. Swift doesn’t allow you to specify which errors can be thrown by a function, so consumers would have to catch all errors and cast. Also throwing wouldn’t work for async

/// Message describing this error.
public let message: String?

/// Designated initializer.
public init(message: String?) {
self.message = message
}
}
4 changes: 2 additions & 2 deletions library/swift/src/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ public final class Request: NSObject {
/// Trailers to send with the request.
/// Multiple values for a given name are valid, and will be sent as comma-separated values.
public let trailers: [String: [String]]
// Serialized data to send as the body of the request.
/// Serialized data to send as the body of the request.
public let body: Data?
// Retry policy to use for this request.
/// Retry policy to use for this request.
public let retryPolicy: RetryPolicy?

/// Converts the request back to a builder so that it can be modified (i.e., by a filter).
Expand Down
4 changes: 2 additions & 2 deletions library/swift/src/RequestBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ public final class RequestBuilder: NSObject {
/// Trailers to send with the request.
/// Multiple values for a given name are valid, and will be sent as comma-separated values.
public private(set) var trailers: [String: [String]] = [:]
// Serialized data to send as the body of the request.
/// Serialized data to send as the body of the request.
public private(set) var body: Data?
// Retry policy to use for this request.
/// Retry policy to use for this request.
public private(set) var retryPolicy: RetryPolicy?

// MARK: - Initializers
Expand Down
35 changes: 35 additions & 0 deletions library/swift/src/Response.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Foundation

/// Represents an Envoy HTTP response. Use `ResponseBuilder` to construct new instances.
@objcMembers
public final class Response: NSObject {
/// Status code returned with the response.
public let statusCode: Int
/// Headers returned with the response.
/// Multiple values for a given name are valid, and will be sent as comma-separated values.
public let headers: [String: [String]]
/// Trailers returned with the response.
/// Multiple values for a given name are valid, and will be sent as comma-separated values.
public let trailers: [String: [String]]
/// Serialized data returned as the body of the response.
public let body: Data?

/// Converts the response back to a builder so that it can be modified (i.e., by a filter).
///
/// - returns: A new builder including all the properties of this response.
public func newBuilder() -> ResponseBuilder {
return ResponseBuilder(response: self)
}

/// Internal initializer called from the builder to create a new response.
init(statusCode: Int,
headers: [String: [String]] = [:],
trailers: [String: [String]] = [:],
body: Data?)
{
self.statusCode = statusCode
self.headers = headers
self.trailers = trailers
self.body = body
}
}
112 changes: 112 additions & 0 deletions library/swift/src/ResponseBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import Foundation

/// Builder used for constructing instances of `Response` types.
@objcMembers
public final class ResponseBuilder: NSObject {
/// Status code returned with the response.
public private(set) var statusCode: Int = 200
/// Headers returned with the response.
/// Multiple values for a given name are valid, and will be sent as comma-separated values.
public private(set) var headers: [String: [String]] = [:]
/// Trailers returned with the response.
/// Multiple values for a given name are valid, and will be sent as comma-separated values.
public private(set) var trailers: [String: [String]] = [:]
/// Serialized data returned as the body of the response.
public private(set) var body: Data?

// MARK: - Initializers

/// Internal initializer used for converting a response back to a builder.
convenience init(response: Response) {
self.init()
self.statusCode = response.statusCode
self.headers = response.headers
self.trailers = response.trailers
self.body = response.body
}

// MARK: - Builder functions

@discardableResult
public func addStatusCode(_ statusCode: Int) -> ResponseBuilder {
self.statusCode = statusCode
return self
}

@discardableResult
public func addHeader(name: String, value: String) -> ResponseBuilder {
self.headers[name, default: []].append(value)
return self
}

@discardableResult
public func removeHeaders(name: String) -> ResponseBuilder {
self.headers.removeValue(forKey: name)
return self
}

@discardableResult
public func removeHeader(name: String, value: String) -> ResponseBuilder {
self.headers[name]?.removeAll(where: { $0 == value })
if self.headers[name]?.isEmpty == true {
self.headers.removeValue(forKey: name)
}

return self
}

@discardableResult
public func addTrailer(name: String, value: String) -> ResponseBuilder {
self.trailers[name, default: []].append(value)
return self
}

@discardableResult
public func removeTrailer(name: String) -> ResponseBuilder {
self.trailers.removeValue(forKey: name)
return self
}

@discardableResult
public func removeTrailers(named name: String, value: String) -> ResponseBuilder {
self.trailers[name]?.removeAll(where: { $0 == value })
if self.trailers[name]?.isEmpty == true {
self.trailers.removeValue(forKey: name)
}

return self
}

@discardableResult
public func addBody(_ body: Data?) -> ResponseBuilder {
self.body = body
return self
}

public func build() -> Response {
return Response(statusCode: self.statusCode,
headers: self.headers,
trailers: self.trailers,
body: self.body)
}
}

// MARK: - Objective-C helpers

extension Response {
/// Convenience builder function to allow for cleaner Objective-C syntax.
///
/// For example:
///
/// Response *res = [Response withBuild:^(ResponseBuilder *builder) {
/// [builder addBody:bodyData];
/// [builder addHeaderWithName:@"x-some-header" value:@"foo"];
/// [builder addTrailerWithName:@"x-some-trailer" value:@"foo"];
/// }];
@objc
public static func with(build: (ResponseBuilder) -> Void) -> Response {
let builder = ResponseBuilder()
build(builder)
return builder.build()
}
}