From a5d31841673daec443404825d79904e56aee40ea Mon Sep 17 00:00:00 2001 From: Kyle Fang Date: Thu, 27 Jul 2023 10:43:25 +0800 Subject: [PATCH] feat: add support for bodyField and namespaced type --- Sources/SwiftRequest/Macros/ParamMacros.swift | 3 + .../SwiftRequest/Request+toURLRequest.swift | 2 + Sources/SwiftRequest/Request.swift | 4 ++ Sources/SwiftRequestMacros/MethodMacro.swift | 2 +- .../ServiceMethodExpander.swift | 4 ++ Tests/SwiftRequestTests/ServiceTests.swift | 70 ++++++++++++++++++- 6 files changed, 82 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftRequest/Macros/ParamMacros.swift b/Sources/SwiftRequest/Macros/ParamMacros.swift index e893a2c..ca8fc50 100644 --- a/Sources/SwiftRequest/Macros/ParamMacros.swift +++ b/Sources/SwiftRequest/Macros/ParamMacros.swift @@ -10,5 +10,8 @@ public macro Header(_ name: String = "") = #externalMacro(module: "SwiftRequestM @attached(member) public macro FormField(_ name: String = "") = #externalMacro(module: "SwiftRequestMacros", type: "ParamMacro") +@attached(member) +public macro BodyField(_ name: String = "") = #externalMacro(module: "SwiftRequestMacros", type: "ParamMacro") + @attached(member) public macro Body() = #externalMacro(module: "SwiftRequestMacros", type: "BodyMacro") diff --git a/Sources/SwiftRequest/Request+toURLRequest.swift b/Sources/SwiftRequest/Request+toURLRequest.swift index b881b87..7aaa46e 100644 --- a/Sources/SwiftRequest/Request+toURLRequest.swift +++ b/Sources/SwiftRequest/Request+toURLRequest.swift @@ -34,6 +34,8 @@ public extension Request { } else { request.httpBody = try JSONEncoder().encode(body) } + } else if let bodyFields { + request.httpBody = try JSONSerialization.data(withJSONObject: bodyFields) } if let headers { diff --git a/Sources/SwiftRequest/Request.swift b/Sources/SwiftRequest/Request.swift index 013d938..e395a58 100644 --- a/Sources/SwiftRequest/Request.swift +++ b/Sources/SwiftRequest/Request.swift @@ -6,9 +6,11 @@ public struct Request { let queryParams: Params? let headers: Params? let formFields: Params? + let bodyFields: BodyParams? let body: (any Encodable)? public typealias Params = [String: (any CustomStringConvertible)?] + public typealias BodyParams = [String: (any Encodable)?] public init( url: URL, @@ -16,6 +18,7 @@ public struct Request { queryParams: Params? = nil, headers: Params? = nil, formFields: Params? = nil, + bodyFields: BodyParams? = nil, body: (any Encodable)? = nil ) { self.url = url @@ -23,6 +26,7 @@ public struct Request { self.queryParams = queryParams self.headers = headers self.formFields = formFields + self.bodyFields = bodyFields self.body = body } } diff --git a/Sources/SwiftRequestMacros/MethodMacro.swift b/Sources/SwiftRequestMacros/MethodMacro.swift index 7710094..2a1d1d2 100644 --- a/Sources/SwiftRequestMacros/MethodMacro.swift +++ b/Sources/SwiftRequestMacros/MethodMacro.swift @@ -47,7 +47,7 @@ public class MethodMacro: PeerMacro { outputType: TypeSyntax, in context: some MacroExpansionContext ) -> Bool { - if outputType.is(SimpleTypeIdentifierSyntax.self) { + if outputType.is(SimpleTypeIdentifierSyntax.self) || outputType.is(MemberTypeIdentifierSyntax.self) { return true } diff --git a/Sources/SwiftRequestMacros/ServiceMethodExpander.swift b/Sources/SwiftRequestMacros/ServiceMethodExpander.swift index cc82cd0..491be7c 100644 --- a/Sources/SwiftRequestMacros/ServiceMethodExpander.swift +++ b/Sources/SwiftRequestMacros/ServiceMethodExpander.swift @@ -103,6 +103,10 @@ class ServiceMethodExpander { TupleExprElementSyntax(label: "formFields", expression: formFields) } + if let bodyFields = expandParameter("BodyField", of: declaration, in: context) { + TupleExprElementSyntax(label: "bodyFields", expression: bodyFields) + } + if let body = expandBody(of: declaration, in: context) { TupleExprElementSyntax(label: "body", expression: IdentifierExprSyntax(identifier: body)) } diff --git a/Tests/SwiftRequestTests/ServiceTests.swift b/Tests/SwiftRequestTests/ServiceTests.swift index ca88587..8861fec 100644 --- a/Tests/SwiftRequestTests/ServiceTests.swift +++ b/Tests/SwiftRequestTests/ServiceTests.swift @@ -7,6 +7,9 @@ final class ServiceTests: XCTestCase { let testMacros: [String: Macro.Type] = [ "Service": ServiceMacro.self, "GET": MethodMacro.self, + "POST": MethodMacro.self, + "BodyField": ParamMacro.self, + "QueryParam": ParamMacro.self, ] func testMacro() { @@ -32,12 +35,75 @@ final class ServiceTests: XCTestCase { func getRandomQuotes(limit: Int? = nil) async throws -> [Quote] { let request = Request(url: resourceURL.appendingPathComponent("random"), queryParams: ["limit": limit]) - return try await session.execute(request) + return try await executor(request) } func getQuote(by id: String) async throws -> Quote { let request = Request(url: resourceURL.appendingPathComponent("\\(id)")) - return try await session.execute(request) + return try await executor(request) + } + } + """, + macros: testMacros + ) + } + + func testBodyFieldMacro() { + assertMacroExpansion( + """ + @Service(resource: "quotes") + protocol QuoteService { + @POST("random") + func getRandomQuotes(@BodyField limit: Int?) async throws -> [Quote] + } + """, + expandedSource: """ + + protocol QuoteService { + func getRandomQuotes(@BodyField limit: Int?) async throws -> [Quote] + } + class QuoteServiceImpl: Service, QuoteService { + private lazy var resourceURL: URL = baseURL.appendingPathComponent("quotes") + + func getRandomQuotes(limit: Int? = nil) async throws -> [Quote] { + let request = Request(url: resourceURL.appendingPathComponent("random"), method: "POST", bodyFields: ["limit": limit]) + return try await executor(request) + } + } + """, + macros: testMacros + ) + } + + func testNestedTypeMacro() { + assertMacroExpansion( + """ + struct Namespace { + struct Output: Codable { + let hello: String + } + } + @Service(resource: "quotes") + protocol QuoteService { + @POST("random") + func getRandomQuotes(@BodyField limit: Int?) async throws -> Namespace.Output + } + """, + expandedSource: """ + struct Namespace { + struct Output: Codable { + let hello: String + } + } + protocol QuoteService { + func getRandomQuotes(@BodyField limit: Int?) async throws -> Namespace.Output + } + class QuoteServiceImpl: Service, QuoteService { + private lazy var resourceURL: URL = baseURL.appendingPathComponent("quotes") + + func getRandomQuotes(limit: Int? = nil) async throws -> Namespace.Output { + let request = Request(url: resourceURL.appendingPathComponent("random"), method: "POST", bodyFields: ["limit": limit]) + return try await executor(request) } } """,