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

feat: bypass body serialization if body type is data #9

Merged
merged 2 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
17 changes: 15 additions & 2 deletions Sources/NClient/Endpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public protocol Endpoint {
/// The HTTP method of the endpoint.
var method: HTTP.Method { get }

var contentType: HTTP.MIMEType { get }

/// Constructs the URL components for the endpoint given the parameters.
func url(parameters: Parameters) -> URLComponents

Expand All @@ -43,6 +45,9 @@ public protocol Endpoint {
public extension Endpoint {
/// The default HTTP method for the endpoint is `GET`.
var method: HTTP.Method { .GET }

/// The default Content-Type for the endpoint is `application/json`.
var contentType: HTTP.MIMEType { .json }
}

// MARK: Request creation
Expand Down Expand Up @@ -73,7 +78,7 @@ public extension Endpoint {

var request = URLRequest(url: url.absoluteURL)
request.httpMethod = method.rawValue
request.setHeader(.accept, value: HTTP.MIMEType.json)
request.setHeader(.accept, value: HTTP.MIMEType.json.rawValue)
try serializeBody(requestBody, into: &request)

return request
Expand All @@ -90,12 +95,20 @@ public extension Endpoint where RequestBody == Empty {
public extension Endpoint where RequestBody: Encodable {
/// Serializes an encodable request body as JSON.
func serializeBody(_ body: RequestBody, into request: inout URLRequest) throws {
request.setHeader(.contentType, value: HTTP.MIMEType.json)
request.setHeader(.contentType, value: contentType.rawValue)
let data = try JSONEncoder().encode(body)
request.httpBody = data
}
}

public extension Endpoint where RequestBody == Data {
/// Bypass serialization if request body is already of Data type
func serializeBody(_ body: RequestBody, into request: inout URLRequest) throws {
request.setHeader(.contentType, value: contentType.rawValue)
request.httpBody = body
}
}

// MARK: Deserialize

public extension Endpoint where ResponseBody == Empty {
Expand Down
25 changes: 17 additions & 8 deletions Sources/NClient/HTTP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,14 @@ public enum HTTP {
struct HeaderName: RawRepresentable {
let rawValue: String
}
}

extension HTTP {
/// Represents common MIME types used in HTTP requests and responses.
enum MIMEType {
/// JSON data.
static let json = "application/json"
/// Supported MIME types
public struct MIMEType: RawRepresentable {
public let rawValue: String

/// URL-encoded form data.
static let formUrlEncoded = "application/x-www-form-urlencoded"
public init(rawValue: String) {
self.rawValue = rawValue
}
}
}

Expand All @@ -45,6 +43,17 @@ extension HTTP.HeaderName {
static let contentType = HTTP.HeaderName("Content-Type")
}

public extension HTTP.MIMEType {
/// JSON data.
static let json = HTTP.MIMEType(rawValue: "application/json")

/// URL-encoded form data.
static let formUrlEncoded = HTTP.MIMEType(rawValue: "application/x-www-form-urlencoded")

/// MP4
static let mp4 = HTTP.MIMEType(rawValue: "audio/mp4")
}

extension URLRequest {
mutating func setHeader(_ name: HTTP.HeaderName, value: String) {
setValue(value, forHTTPHeaderField: name.rawValue)
Expand Down
2 changes: 1 addition & 1 deletion Tests/NClientTests/Unit/APIClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ final class APIClientTests: XCTestCase {
result: .success(
.init(
statusCode: 200,
headers: [HTTP.HeaderName.contentType.rawValue: HTTP.MIMEType.json],
headers: [HTTP.HeaderName.contentType.rawValue: HTTP.MIMEType.json.rawValue],
data: mockResponseData
)
)
Expand Down
40 changes: 37 additions & 3 deletions Tests/NClientTests/Unit/EndpointTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class EndpointTests: XCTestCase {
XCTAssertEqual(request.url?.absoluteString, "https://example.com/path")
XCTAssertEqual(request.httpMethod, HTTP.Method.GET.rawValue)

XCTAssertEqual(request.value(forHTTPHeaderField: HTTP.HeaderName.accept.rawValue), HTTP.MIMEType.json)
XCTAssertEqual(request.value(forHTTPHeaderField: HTTP.HeaderName.accept.rawValue), HTTP.MIMEType.json.rawValue)
XCTAssertNil(request.value(forHTTPHeaderField: HTTP.HeaderName.contentType.rawValue))

XCTAssertNil(request.httpBody)
Expand Down Expand Up @@ -48,14 +48,32 @@ final class EndpointTests: XCTestCase {
XCTAssertEqual(request.url?.absoluteString, "https://example.com/path")
XCTAssertEqual(request.httpMethod, HTTP.Method.POST.rawValue)

XCTAssertEqual(request.value(forHTTPHeaderField: HTTP.HeaderName.accept.rawValue), HTTP.MIMEType.json)
XCTAssertEqual(request.value(forHTTPHeaderField: HTTP.HeaderName.contentType.rawValue), HTTP.MIMEType.json)
XCTAssertEqual(request.value(forHTTPHeaderField: HTTP.HeaderName.accept.rawValue), HTTP.MIMEType.json.rawValue)
XCTAssertEqual(request.value(forHTTPHeaderField: HTTP.HeaderName.contentType.rawValue), HTTP.MIMEType.json.rawValue)

let bodyData = try JSONEncoder().encode(mockRequestBody)
XCTAssertNotNil(request.httpBody)
XCTAssertEqual(request.httpBody, bodyData)
}

func testEndpointWithRawRequestBody() throws {
let endpoint = MockEndpointWithRawResponseBody()
let request = try endpoint.request(
baseUrl: mockBaseUrl,
parameters: .empty,
requestBody: try XCTUnwrap(mockMessage.data(using: .utf8))
)

XCTAssertEqual(request.url?.absoluteString, "https://example.com/path")
XCTAssertEqual(request.httpMethod, HTTP.Method.POST.rawValue)

XCTAssertEqual(request.value(forHTTPHeaderField: HTTP.HeaderName.accept.rawValue), HTTP.MIMEType.json.rawValue)
XCTAssertEqual(request.value(forHTTPHeaderField: HTTP.HeaderName.contentType.rawValue), HTTP.MIMEType.mp4.rawValue)

let requestBody = try XCTUnwrap(request.httpBody)
XCTAssertEqual(String(data: requestBody, encoding: .utf8), mockMessage)
}

// MARK: Deserialize

func testDeserializeEmptyBody() throws {
Expand Down Expand Up @@ -140,3 +158,19 @@ private struct MockEndpointWithResponseBody: Endpoint {
.init(path: mockPath)
}
}

private struct MockEndpointWithRawResponseBody: Endpoint {
typealias RequestBody = Data

var method: HTTP.Method {
.POST
}

var contentType: HTTP.MIMEType {
.mp4
}

func url(parameters: Parameters) -> URLComponents {
.init(path: mockPath)
}
}