Skip to content

Commit

Permalink
✨ Add header override
Browse files Browse the repository at this point in the history
  • Loading branch information
MadsBogeskov committed Jan 24, 2024
1 parent 3f8bf40 commit 45466a0
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 81 deletions.
2 changes: 1 addition & 1 deletion Sources/SwaggerSwiftCore/API Factory/APIFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,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
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 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.addValue(\(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.addValue(\($0.swiftyName), forHTTPHeaderField: \"\($0.fullHeaderName)\")
}
"""
}
}

var headerStatements = setHeaderValues

var bodyInjection: String = ""
if let body = parameters.first(where: { $0.in == .body }) {
bodyInjection += """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ public struct RequestParameterFactory {
.map { $0.capitalizingFirstLetter() }
.joined()

if let (headerParameter, headerModels) = try resolveInputHeadersToApi(parameters: parameters,
typePrefix: typeName,
isInternalOnly: operation.isInternalOnly,
swagger: swagger,
swaggerFile: swaggerFile) {
if let (headerParameter, headerModels) = try resolveInputHeadersToApi(
parameters: parameters,
typePrefix: typeName,
isInternalOnly: operation.isInternalOnly,
swagger: swagger,
swaggerFile: swaggerFile
) {
resolvedParameters.append(headerParameter)
resolvedModelDefinitions.append(contentsOf: headerModels)
}
Expand Down Expand Up @@ -113,60 +115,91 @@ public struct RequestParameterFactory {
}

private func resolveInputHeadersToApi(parameters: [SwaggerSwiftML.Parameter], typePrefix: String, isInternalOnly: Bool, swagger: Swagger, swaggerFile: SwaggerFile) throws -> (FunctionParameter, [ModelDefinition])? {
let headerParameters = parameters.parameters(of: .header)

let globalHeaderFieldNames = (swaggerFile.globalHeaders ?? []).map(convertApiHeader)
let requestSpecificParameters = parameters.compactMap {
if case let .header(type) = $0.location {
return HeaderParameter(
type: type,
name: $0.name,
required: $0.required,
description: $0.description
)
} else {
return nil
}
}.unique(on: { $0.name }) // this is necessary in the case where copies of the same header are sent in at the same request

let globalHeaderParams = swaggerFile.globalHeaders
.filter { header in requestSpecificParameters.contains(where: { $0.name.lowercased() == header.lowercased() }) == false }
.map {
HeaderParameter(
type: .string(
format: nil,
enumValues: nil,
maxLength: nil,
minLength: nil,
pattern: nil
),
name: $0,
required: true,
description: nil
)
}

var modelDefinitions = [ModelDefinition]()

let headerFields: [ModelField] = try headerParameters.compactMap { param, type, _ in
let (type, inlineModelDefinitions) = try type.toType(
let requestSpecificHeaderFields: [ModelField] = try requestSpecificParameters.map { headerParam in
let (type, inlineModelDefinitions) = try headerParam.type.toType(
typePrefix: typePrefix,
description: param.description,
description: headerParam.description,
swagger: swagger
)

let name = convertApiHeader(param.name)
modelDefinitions.append(contentsOf: inlineModelDefinitions)

// we should not add fields for the global headers
if globalHeaderFieldNames.contains(name) {
return nil
let isRequired: Bool
if swaggerFile.globalHeaders.contains(where: { $0.lowercased() == headerParam.name.lowercased() }) {
isRequired = false // this is overriden by the global headers so it will be set by that, and is therefor not required here
} else {
isRequired = headerParam.required
}

modelDefinitions.append(contentsOf: inlineModelDefinitions)
return ModelField(
description: nil,
type: type,
name: convertApiHeader(headerParam.name),
isRequired: isRequired
)
}

return ModelField(description: nil,
type: type,
name: name,
isRequired: param.required)
}.sorted(by: { $0.argumentLabel < $1.argumentLabel })

if headerFields.count > 0 {
let typeName = "\(typePrefix)Headers"

let model = Model(description: "A collection of the header fields required for the request",
typeName: typeName,
fields: headerFields,
inheritsFrom: [],
isInternalOnly: isInternalOnly,
embeddedDefinitions: [],
isCodable: false)

if model.fields.count > 0 {
let headerModelDefinition = ModelDefinition.object(model)
modelDefinitions.append(headerModelDefinition)
let functionParameter = FunctionParameter(description: nil,
name: "headers",
typeName: .object(typeName: typeName),
required: true,
in: .headers,
isEnum: false)

return (functionParameter, modelDefinitions)
let globalHeaderFields = globalHeaderParams
.map {
ModelField(description: nil, type: .string(defaultValue: nil), name: convertApiHeader($0.name), isRequired: false)
}
}

return nil
let allHeaderFields = (requestSpecificHeaderFields + globalHeaderFields)
.sorted(by: { $0.argumentLabel < $1.argumentLabel })

guard allHeaderFields.count > 0 else { return nil }

let model = Model(
description: "A collection of the header fields required for the request",
typeName: "\(typePrefix)Headers",
fields: allHeaderFields,
isInternalOnly: isInternalOnly,
isCodable: false
)

modelDefinitions.append(ModelDefinition.object(model))

let functionParameter = FunctionParameter(
description: nil,
name: "headers",
typeName: .object(typeName: model.typeName),
required: requestSpecificHeaderFields.count > 0 && requestSpecificHeaderFields.contains(where: { $0.isRequired }),
in: .headers,
isEnum: false
)

return (functionParameter, modelDefinitions)
}

private func resolvePathParameters(parameters: [SwaggerSwiftML.Parameter], typePrefix: String, swagger: Swagger) throws -> ([FunctionParameter], [ModelDefinition]) {
Expand Down Expand Up @@ -357,3 +390,17 @@ public struct RequestParameterFactory {
return (functionParameters, modelDefinitions)
}
}

extension Sequence {
func unique<T: Equatable>(on block: (Iterator.Element) -> T) -> [Self.Element] {
var result = [Iterator.Element]()

for item in self {
if result.contains(where: { r in block(item) == block(r) }) == false {
result.append(item)
}
}

return result
}
}
7 changes: 7 additions & 0 deletions Sources/SwaggerSwiftCore/Extensions/Parameter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ enum ParamLocation {
case body
}

struct HeaderParameter {
let type: ParameterType
let name: String
let required: Bool
let description: String?
}

extension Sequence where Element == SwaggerSwiftML.Parameter {
/// Convenience function that searches a set of SwaggerSwiftML.Parameter's to find those that are used in a specific location, based on `ParamLocation`
/// - Parameter type: the type of api parameter that is needed
Expand Down
18 changes: 14 additions & 4 deletions Sources/SwaggerSwiftCore/Generator/Generator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public struct Generator {

log("Creating Swift Project at \(destination)")

let globalHeadersModel = GlobalHeadersModel(headerFields: swaggerFile.globalHeaders ?? [])
let globalHeadersModel = GlobalHeadersModel(headerFields: swaggerFile.globalHeaders)
let commonLibraryName = "\(swaggerFile.projectName)Shared"

if swaggerFile.createSwiftPackage {
Expand Down Expand Up @@ -226,7 +226,17 @@ public struct Generator {
}
}

private func write(apiDefinition: APIDefinition, modelDefinitions: [ModelDefinition], swaggerFile: SwaggerFile, rootDirectory: String, commonLibraryName: String?, accessControl: APIAccessControl, globalHeadersModel: GlobalHeadersModel, fileManager: FileManager, dummyMode: Bool) throws {
private func write(
apiDefinition: APIDefinition,
modelDefinitions: [ModelDefinition],
swaggerFile: SwaggerFile,
rootDirectory: String,
commonLibraryName: String?,
accessControl: APIAccessControl,
globalHeadersModel: GlobalHeadersModel,
fileManager: FileManager,
dummyMode: Bool
) throws {
log("Parsing contents of Swagger: \(apiDefinition.serviceName)")

let apiDirectory = rootDirectory + "/" + apiDefinition.serviceName
Expand Down Expand Up @@ -300,8 +310,8 @@ public struct Generator {
try globalHeadersDefinitions.write(toFile: globalHeaderExtensionsPath)
}

if let globalHeaderFields = swaggerFile.globalHeaders {
let globalHeadersModel = GlobalHeadersModel(headerFields: globalHeaderFields)
if swaggerFile.globalHeaders.count > 0 {
let globalHeadersModel = GlobalHeadersModel(headerFields: swaggerFile.globalHeaders)
let globalHeadersFileContents = globalHeadersModel.toSwift(swaggerFile: swaggerFile, accessControl: accessControl)
try globalHeadersFileContents.write(toFile: "\(targetPath)/GlobalHeaders.swift")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ struct GlobalHeadersModel {
return model
}

func addToRequestFunction() -> String {
private func addToRequestFunction() -> String {
let fields = headerFields.map {
APIRequestHeaderField(
headerName: $0,
Expand Down Expand Up @@ -83,7 +83,7 @@ if let \($0.swiftyName) = \($0.swiftyName) {
return function
}

func asDictionaryFunction() -> String {
private func asDictionaryFunction() -> String {
let fields = headerFields.map {
APIRequestHeaderField(
headerName: $0,
Expand Down
Loading

0 comments on commit 45466a0

Please sign in to comment.