Skip to content

Commit

Permalink
Merge branch 'main' into ms/use-custom-string-coding-key
Browse files Browse the repository at this point in the history
  • Loading branch information
mxsc authored Apr 16, 2024
2 parents 2732cad + 12d57b2 commit 07cfa29
Show file tree
Hide file tree
Showing 15 changed files with 417 additions and 195 deletions.
39 changes: 20 additions & 19 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,34 @@ name: Build and Run Tests

on:
pull_request:
branches: [ main ]
branches:
- main

jobs:
build:
runs-on: macos-latest
runs-on: macos-13
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Set Xcode
run: xcversion select 14.2
- name: Set Xcode
run: xcodes select 15.2

- name: Build
run: swift build -v
- name: Build
run: swift build -v

test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
runs-on: macos-13
steps:
- uses: actions/checkout@v4

- name: Set Xcode
run: xcversion select 14.2
- name: Set Xcode
run: xcodes select 15.2

- name: Run tests
run: swift test -v --parallel --xunit-output test.xml
- name: Run tests
run: swift test -v --parallel --xunit-output test.xml

- name: Publish Unit Test Results
uses: EnricoMi/publish-unit-test-result-action/composite@v1
if: always()
with:
files: "test.xml"
- name: Publish Unit Test Results
uses: EnricoMi/publish-unit-test-result-action/composite@v2
if: always()
with:
files: "test.xml"
46 changes: 22 additions & 24 deletions .github/workflows/build_docker_image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,30 @@ name: Build Docker Image
on:
workflow_dispatch:
push:
branches:
- main
branches:
- main

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Quay login
run: docker login -u="$QUAY_USERNAME" -p="$QUAY_TOKEN" quay.io
env:
QUAY_TOKEN: ${{ secrets.quay_token }}
QUAY_USERNAME: ${{ secrets.quay_username }}

- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: quay.io/lunarway/swaggerswift:latest
- uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Quay login
run: docker login -u="$QUAY_USERNAME" -p="$QUAY_TOKEN" quay.io
env:
QUAY_TOKEN: ${{ secrets.quay_token }}
QUAY_USERNAME: ${{ secrets.quay_username }}

- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: quay.io/lunarway/swaggerswift:latest
13 changes: 8 additions & 5 deletions Sources/SwaggerSwiftCore/API Factory/APIFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ struct APIFactory {
var networkRequestFunctions = [APIRequest]()
var inlineModelDefinitions = [ModelDefinition]()
for swaggerPath in swagger.paths {
let (pathNetworkRequestFunctions, pathCurrentInlineDefinitions) = try apisAndModels(fromPath: swaggerPath.value,
servicePath: swaggerPath.key,
swagger: swagger,
swaggerFile: swaggerFile)
let (pathNetworkRequestFunctions, pathCurrentInlineDefinitions) = try apisAndModels(
fromPath: swaggerPath.value,
servicePath: swaggerPath.key,
swagger: swagger,
swaggerFile: swaggerFile
)

networkRequestFunctions.append(contentsOf: pathNetworkRequestFunctions)
inlineModelDefinitions.append(contentsOf: pathCurrentInlineDefinitions)
Expand Down Expand Up @@ -130,6 +132,7 @@ struct APIFactory {
break
}
}

return allDefinitions
}

Expand Down Expand Up @@ -201,7 +204,7 @@ struct APIFactory {
defaultValue: nil)
]

let hasGlobalHeaders = (swaggerFile.globalHeaders ?? []).count > 0
let hasGlobalHeaders = swaggerFile.globalHeaders.count > 0

if hasGlobalHeaders {
fields.append(APIDefinitionField(name: "headerProvider",
Expand Down
107 changes: 68 additions & 39 deletions Sources/SwaggerSwiftCore/API Request Factory/APIRequestFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ public struct APIRequestFactory {
}

func generateRequest(for operation: SwaggerSwiftML.Operation, httpMethod: HTTPMethod, servicePath: String, swagger: Swagger, swaggerFile: SwaggerFile, pathParameters: [Parameter]) throws -> (APIRequest, [ModelDefinition]) {
let functionName = resolveFunctionName(httpMethod: httpMethod.rawValue,
servicePath: servicePath,
operationId: operation.operationId)
let functionName = resolveFunctionName(
httpMethod: httpMethod.rawValue,
servicePath: servicePath,
operationId: operation.operationId
)

var inlineResponseModels = [ModelDefinition]()

Expand Down Expand Up @@ -66,35 +68,55 @@ public struct APIRequestFactory {
swagger.findParameter(node: $0)
} + pathParameters

let headers = allParameters.compactMap { param -> APIRequestHeaderField? in
if case ParameterLocation.header = param.location {
return APIRequestHeaderField(headerName: param.name,
isRequired: param.required)
} else {
return nil
let requestSpecificHeaders = allParameters
.compactMap { param -> APIRequestHeaderField? in
if case ParameterLocation.header = param.location {

let isRequired: Bool
if swaggerFile.globalHeaders.contains(param.name) {
isRequired = false // the header will be provided by global headers
} else {
isRequired = param.required
}

return APIRequestHeaderField(
headerName: param.name,
isRequired: isRequired
)
} else {
return nil
}
}
}

let headers = requestSpecificHeaders

let queryItems = resolveQueries(parameters: allParameters, swagger: swagger)

let apiResponseTypes = apiResponseTypeFactory.make(forResponses: responses,
forHTTPMethod: httpMethod,
at: servicePath,
swagger: swagger)
let apiResponseTypes = apiResponseTypeFactory.make(
forResponses: responses,
forHTTPMethod: httpMethod,
at: servicePath,
swagger: swagger
)

inlineResponseModels.append(contentsOf: inlineModels)

let apiRequest = APIRequest(
description: operation.description,
functionName: functionName,
parameters: functionParameters,
consumes: try consumeMimeType(forOperation: operation, swagger: swagger, httpMethod: httpMethod.rawValue, servicePath: servicePath),
consumes: try consumeMimeType(
forOperation: operation,
swagger: swagger,
httpMethod: httpMethod.rawValue,
servicePath: servicePath
),
isInternalOnly: operation.isInternalOnly,
isDeprecated: operation.deprecated,
httpMethod: httpMethod,
servicePath: servicePath,
queries: queryItems,
headers: headers,
headers: headers.unique(on: \.swiftyName),
responseTypes: apiResponseTypes,
returnType: returnType
)
Expand All @@ -119,12 +141,8 @@ public struct APIRequestFactory {
}
}

let queries: [QueryElement] = queryParameters.compactMap {
guard case ParameterLocation.query = $0.location else {
return nil
}

switch $0.location {
let queries: [QueryElement] = queryParameters.compactMap { parameter in
switch parameter.location {
case .query(let type, _):
switch type {
case .string(let format, let enumValues, _, _, _):
Expand All @@ -145,43 +163,54 @@ public struct APIRequestFactory {
case .email: fallthrough
case .unsupported:
return QueryElement(
fieldName: $0.name,
fieldValue: $0.name.camelized,
isOptional: $0.required == false,
fieldName: parameter.name,
fieldValue: parameter.name.camelized,
isOptional: parameter.required == false,
valueType: valueType
)
case .date: fallthrough
case .dateTime:
return QueryElement(
fieldName: $0.name,
fieldValue: $0.name.camelized,
isOptional: $0.required == false,
fieldName: parameter.name,
fieldValue: parameter.name.camelized,
isOptional: parameter.required == false,
valueType: .date
)
}
} else {
return QueryElement(
fieldName: $0.name,
fieldValue: $0.name.camelized,
isOptional: $0.required == false,
fieldName: parameter.name,
fieldValue: parameter.name.camelized,
isOptional: parameter.required == false,
valueType: valueType
)
}
case .array:
case .array(let items, let collectionFormat, maxItems: _, minItems: _, uniqueItems: _):
if case .string(_, let enumValues, _, _, _) = items.type {
if enumValues != nil {
return QueryElement(
fieldName: parameter.name,
fieldValue: parameter.name.camelized,
isOptional: parameter.required == false,
valueType: .array(isEnum: true, collectionFormat: collectionFormat)
)
}
}

return QueryElement(
fieldName: $0.name,
fieldValue: $0.name.camelized,
isOptional: $0.required == false,
valueType: .array
fieldName: parameter.name,
fieldValue: parameter.name.camelized,
isOptional: parameter.required == false,
valueType: .array(isEnum: false, collectionFormat: collectionFormat)
)
case .number: fallthrough
case .integer: fallthrough
case .boolean: fallthrough
case .file:
return QueryElement(
fieldName: $0.name,
fieldValue: $0.name.camelized,
isOptional: $0.required == false,
fieldName: parameter.name,
fieldValue: parameter.name.camelized,
isOptional: parameter.required == false,
valueType: .default
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,17 @@ extension APIRequest {
/// The arguments of the function, e.g. "myValue: String, myOtherValue: Int"
private var functionArguments: String {
parameters.map {
"\($0.name.variableNameFormatted): \($0.typeName.toString(required: $0.required))\($0.required ? "" : " = nil")"
let paramName = $0.name.variableNameFormatted
let typeName = $0.typeName.toString(required: $0.required)

let defaultValue: String
if !$0.required {
defaultValue = " = nil"
} else {
defaultValue = ""
}

return "\(paramName): \(typeName)\(defaultValue)"
}.joined(separator: ", ")
}

Expand All @@ -28,26 +38,35 @@ extension APIRequest {
}.joined(separator: "/")

var globalHeaders = [String]()
if let globalHeaderFields = swaggerFile.globalHeaders, globalHeaderFields.count > 0 {
if swaggerFile.globalHeaders.count > 0 {
globalHeaders.append("let globalHeaders = self.headerProvider()")
globalHeaders.append("globalHeaders.add(to: &request)")
}

var headerStatements: [String] = headers
let uniquelyGlobalHeaders = swaggerFile.globalHeaders.filter { globalHeaderName in headers.contains(where: { $0.fullHeaderName == globalHeaderName }) == false }

let allHeaders = headers + uniquelyGlobalHeaders.map {
APIRequestHeaderField(headerName: $0, isRequired: false) // not required as they are default from the global header provider
}

let headersName = headers.filter { $0.isRequired }.count > 0 ? "headers" : "headers?"

let setHeaderValues: [String] = allHeaders
.sorted(by: { $0.swiftyName < $1.swiftyName })
.filter { !(swaggerFile.globalHeaders ?? []).map { $0.lowercased() }.contains($0.fullHeaderName.lowercased()) }
.map {
if $0.isRequired {
return "request.addValue(headers.\($0.swiftyName), forHTTPHeaderField: \"\($0.fullHeaderName)\")"
return "request.setValue(\(headersName).\($0.swiftyName), forHTTPHeaderField: \"\($0.fullHeaderName)\")"
} else {
return """
if let \(($0.swiftyName)) = headers.\($0.swiftyName) {
request.addValue(\($0.swiftyName), forHTTPHeaderField: \"\($0.fullHeaderName)\")
}
if let \(($0.swiftyName)) = \(headersName).\($0.swiftyName) {
request.setValue(\($0.swiftyName), forHTTPHeaderField: \"\($0.fullHeaderName)\")
}
"""
}
}

var headerStatements = setHeaderValues

var bodyInjection: String = ""
if let body = parameters.first(where: { $0.in == .body }) {
bodyInjection += """
Expand Down Expand Up @@ -121,7 +140,7 @@ if let \(($0.swiftyName)) = headers.\($0.swiftyName) {
switch consumes {
case .json:
urlSessionMethodName = "data(for: request)"
headerStatements.append("request.addValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")")
headerStatements.append("request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")")

case .formUrlEncoded: fallthrough
case .multiPartFormData:
Expand Down Expand Up @@ -219,7 +238,7 @@ private func _\(functionName)(\(functionArguments)) async -> \(functionReturnTyp
body += "\n"

body += """
\(accessControl) func \(functionName)(\(functionArguments)\(functionArguments.isEmpty ? "" : ", ")completion: @escaping (\(functionReturnType)) -> Void = { _ in }) {
\(accessControl) func \(functionName)(\(functionArguments)\(functionArguments.isEmpty ? "" : ", ")completion: @Sendable @escaping (\(functionReturnType)) -> Void = { _ in }) {
_Concurrency.Task {
let result = await _\(functionName)(\(parameters.map { "\($0.name.variableNameFormatted): \($0.name.variableNameFormatted)" }.joined(separator: ", ")))
completion(result)
Expand Down
Loading

0 comments on commit 07cfa29

Please sign in to comment.