Skip to content

Commit

Permalink
swift: add response interfaces
Browse files Browse the repository at this point in the history
Adds Swift interfaces for responses within Envoy. These should be at parity with the interfaces being added for Kotlin in #260.

Resolves #118.

Doc for reference: https://docs.google.com/document/d/1N0ZFJktK8m01uqqgfDRVB9mpC1iEn9dqkQaa_yMn_kE/edit#heading=h.i6ky65xaa9va

Signed-off-by: Michael Rebello <[email protected]>
  • Loading branch information
rebello95 committed Jul 16, 2019
1 parent 75701c3 commit 7dd5ff2
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 4 deletions.
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",
"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 {
/// 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()
}
}

0 comments on commit 7dd5ff2

Please sign in to comment.