Skip to content
Open
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
9 changes: 9 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public struct PathGenerationModel: Encodable {
.map { OperationGenerationModel(operationModel: $0) }
self.name = pathModel.path.pathName
self.pathWithSeparatedParameters = pathModel.path.pathWithSeparatedParameters
self.parameters = operations[0].pathParameters
self.parameters = pathModel.parameters.map { $0.value }
}

}
Expand Down
4 changes: 3 additions & 1 deletion Sources/CodeGenerator/Models/PathModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ import Foundation
public struct PathModel: Encodable {
/// URI template
public let path: String
public let parameters: [Reference<ParameterModel>]
public let operations: [OperationModel]

public init(path: String, operations: [OperationModel]) {
public init(path: String, parameters: [Reference<ParameterModel>], operations: [OperationModel]) {
self.path = path
self.parameters = parameters
self.operations = operations.sorted { $0.httpMethod < $1.httpMethod }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,26 @@ public class SwaggerCorrector {

return path
}

/// Checks that `path` parameters are declared in `Path`, but not in `Operation`
/// If some are declared in `Operation`, gives warning and tries to fix it
/// See `ParameterTests` for details
/// See `SwaggerCorrectorTests` for example
public func correctPathParameters(for pathModel: PathModel) -> [Reference<ParameterModel>] {
let pathParameterNames = pathModel.parameters.map { $0.value.name }

for operation in pathModel.operations {
let operationPathParameters = operation.parameters?.filter { $0.value.location == .path } ?? []
let operationOnlyPathParameters = operationPathParameters.filter { !pathParameterNames.contains($0.value.name) }

guard operationOnlyPathParameters.isEmpty else {
for parameter in operationOnlyPathParameters {
logger?.warning("Parameter \(parameter.value.name) for path \(pathModel.path) is declared inside Operation. Path parameters should be declared inside Path. Trying to fix...")
}
return operationPathParameters
}
}
return pathModel.parameters
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,12 @@ public struct TreeParser {
)
}

let parameters = try service.parameters.map {
try parametersParser.parse(parameter: $0, current: current, other: other)
}
let operations = try service.operations.map(mapper)

return .init(path: service.path, operations: operations)
return .init(path: service.path, parameters: parameters, operations: operations)
}

func parse(operation: OperationNode, current: DependencyWithTree, other: [DependencyWithTree]) throws -> OperationModel {
Expand Down
46 changes: 26 additions & 20 deletions Sources/GASTBuilder/Builders/AnyServiceBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,13 @@ public struct AnyServiceBuilder: ServiceBuilder {
/// Build all item which are under `paths:`
public func build(paths: [Path]) throws -> [PathNode] {
return try paths.map { path in
let parameters = try wrap(buildParameters(path.parameters),
message: "While parsing path paramters for \(path.path)")
let operations = try wrap(self.build(operations: path.operations),
message: "While parsing Path: \(path.path)")
return PathNode(path: path.path, operations: operations)
return PathNode(path: path.path,
parameters: parameters,
operations: operations)
}
}
}
Expand All @@ -59,25 +63,7 @@ extension AnyServiceBuilder {
func build(operations: [Swagger.Operation]) throws -> [OperationNode] {
return try operations.map { operation in

let mapper = { (param: PossibleReference<Parameter>) -> Referenced<ParameterNode> in
switch param {
case .reference(let ref):
return .ref(ref.rawValue)
case .value(let val):
let params = try wrap(
// TODO: - think about how to fix emty string
self.parameterBuilder.build(parameters: [.init(name: "", value: val)]),
message: "While parsing operation's parameter \(val.name)")

guard params.count == 1 else {
throw CommonError(message: "We had sent 1 parameter, and then got \(params.count). It's very strange. Plz contact mainteiners")
}

return .entity(params[0])
}
}

let params = try wrap(operation.parameters.map(mapper),
let params = try wrap(buildParameters(operation.parameters),
message: "While parsing operation \(operation.method.rawValue)")

let requestBody = try wrap(
Expand All @@ -97,6 +83,26 @@ extension AnyServiceBuilder {
}
}

func buildParameters(_ parameters: [PossibleReference<Parameter>]) throws -> [Referenced<ParameterNode>] {
return try parameters.map {
switch $0 {
case .reference(let ref):
return .ref(ref.rawValue)
case .value(let val):
let params = try wrap(
// TODO: - think about how to fix emty string
self.parameterBuilder.build(parameters: [.init(name: "", value: val)]),
message: "While parsing operation's parameter \(val.name)")

guard params.count == 1 else {
throw CommonError(message: "We had sent 1 parameter, and then got \(params.count). It's very strange. Plz contact mainteiners")
}

return .entity(params[0])
}
}
}

func buildBody(body: PossibleReference<RequestBody>?) throws -> Referenced<RequestBodyNode>? {
guard let body = body else { return nil }

Expand Down
4 changes: 3 additions & 1 deletion Sources/GASTTree/Services/PathNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import Foundation

public struct PathNode {
public var path: String
public let parameters: [Referenced<ParameterNode>]
public var operations: [OperationNode]

public init(path: String, operations: [OperationNode]) {
public init(path: String, parameters: [Referenced<ParameterNode>], operations: [OperationNode]) {
self.path = path
self.parameters = parameters
self.operations = operations
}
}
7 changes: 4 additions & 3 deletions Sources/Pipelines/CodeGen/SwaggerCorrectorStage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ class SwaggerCorrectorStage: PipelineStage {
}

private func correctService(_ service: [PathModel]) -> [PathModel] {
return service.map { path in
return PathModel(path: corrector.correctPath(path.path),
operations: path.operations)
return service.map { pathModel in
return PathModel(path: corrector.correctPath(pathModel.path),
parameters: corrector.correctPathParameters(for: pathModel),
operations: pathModel.operations)
}
}
}
35 changes: 34 additions & 1 deletion Tests/CodeGeneratorTests/SwaggerCorrectorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

import Foundation
import XCTest
import CodeGenerator
import Pipelines
import UtilsForTesting

@testable import CodeGenerator

/// Contains cases which check that `SwaggerCorrector` finds expected issues in OpenAPI elements and, if possible, fixes them
class SwaggerCorrectorTests: XCTestCase {
Expand Down Expand Up @@ -44,4 +47,34 @@ class SwaggerCorrectorTests: XCTestCase {

XCTAssertEqual(expectedPath, correctedPath)
}

/// Checks that if path parameters are declared in `Operation` instead of `Path`, corrector moves them to `Path`
func testPathParametersDeclaredInOperationAreMovedToPath() throws {
// Arrange

let pathToRoot = "/path/to/services.yaml"
let fileProvider = FileProviderStub()
fileProvider.isReadableFile = true
fileProvider.files = [pathToRoot: SwaggerCorrectorYamls.yamlWithPathParametersInOperationWontBeParsed]

var factory = StubGASTTreeFactory(fileProvider: fileProvider)
var result = [[PathModel]]()
factory.resultClosure = { (val: [[PathModel]]) throws -> Void in
result = val
}
let pipeline = factory.build()

let corrector = SwaggerCorrector()

// Act

try pipeline.run(with: URL(string: pathToRoot)!)
let fixedParameters = corrector.correctPathParameters(for: result[0][0])

// Assert

XCTAssertEqual(fixedParameters.count, 1)
XCTAssertEqual(fixedParameters.first?.value.name, "id")
}

}
32 changes: 32 additions & 0 deletions Tests/CodeGeneratorTests/SwaggerCorrectorYamls.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// SwaggerCorrectorYamls.swift
//
//
// Created by Дмитрий Демьянов on 08.11.2021.
//

import Foundation

enum SwaggerCorrectorYamls {
/// Contains service `messages` with one operation `get`
/// Service has path parameter `id` with type `string`, it is declared in `Operation`
static var yamlWithPathParametersInOperationWontBeParsed = """
paths:
/messages/{id}:
get:
parameters:
- name: id
required: true
in: path
schema:
type: string
responses:
default:
description: "Все ок"
content:
application/json:
schema:
type: integer

""".data(using: .utf8)!
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import UtilsForTesting
/// Cases:
///
/// - Params definition:
/// - Path params declared in `Path` will be parsed
/// - Params with primitive type with `in place declaration` will be parsed
/// - Params with ref in schema on enum will be parsed
/// - Params with ref in schema on object will be parsed
Expand All @@ -30,6 +31,7 @@ import UtilsForTesting
/// - Params with ref in schema on object with another ref will be parsed
/// - Params with ref on cycled objects will parsed
///
/// - Path params declared in `Operation` won't be parsed
/// - Params with schema definition won't be parsed
/// - Parameters with ref on another parameter won't be parsed
///
Expand All @@ -39,6 +41,68 @@ final class ParametersTests: XCTestCase {

// MARK: - Params definition

/// Path parameters declared inside `Path` will be parsed
func testPathParametersInPathWillBeParsed() throws {
// Arrange

let pathToRoot = "/path/to/services.yaml"
let fileProvider = FileProviderStub()
fileProvider.isReadableFile = true
fileProvider.files = [pathToRoot: ParametersTests.yamlWithPathParametersInPathWillBeParsed]

var factory = StubGASTTreeFactory(fileProvider: fileProvider)

var result = [[PathModel]]()

factory.resultClosure = { (val: [[PathModel]]) throws -> Void in
result = val
}

let pipeline = factory.build()

// Act

try pipeline.run(with: URL(string: pathToRoot)!)
let pathParameters = result[0][0].parameters

// Assert

XCTAssertEqual(pathParameters.count, 1)

let firstParam = pathParameters.first(where: { $0.name == "id" })!

XCTAssertEqual(try firstParam.type().primitiveType(), .string)
}

/// Path parameters declared inside `Operation` won't be parsed
func testPathParametersInOperationWontBeParsed() throws {
// Arrange

let pathToRoot = "/path/to/services.yaml"
let fileProvider = FileProviderStub()
fileProvider.isReadableFile = true
fileProvider.files = [pathToRoot: ParametersTests.yamlWithPathParametersInOperationWontBeParsed]

var factory = StubGASTTreeFactory(fileProvider: fileProvider)

var result = [[PathModel]]()

factory.resultClosure = { (val: [[PathModel]]) throws -> Void in
result = val
}

let pipeline = factory.build()

// Act

try pipeline.run(with: URL(string: pathToRoot)!)
let pathParameters = result[0][0].parameters

// Assert

XCTAssertEqual(pathParameters.count, 0)
}

/// Params with primitive type with `in place declaration` will be parsed
func testWithPrimitiveTypeWillBeParsed() throws {
// Arrange
Expand Down
45 changes: 45 additions & 0 deletions Tests/PipelinesTests/YamlToGenerationModelsTests/Yamls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,51 @@
import Foundation

extension ParametersTests {

/// Contains service `messages` with one operation `get`
/// Service has path parameter `id` with type `string`, it is declared in `Path`
static var yamlWithPathParametersInPathWillBeParsed = """
paths:
/messages/{id}:
parameters:
- name: id
required: true
in: path
schema:
type: string
get:
responses:
default:
description: "Все ок"
content:
application/json:
schema:
type: integer

""".data(using: .utf8)!

/// Contains service `messages` with one operation `get`
/// Service has path parameter `id` with type `string`, it is declared in `Operation`
static var yamlWithPathParametersInOperationWontBeParsed = """
paths:
/messages/{id}:
get:
parameters:
- name: id
required: true
in: path
schema:
type: string
responses:
default:
description: "Все ок"
content:
application/json:
schema:
type: integer

""".data(using: .utf8)!

/// Contains service `messages` with one operation `get`
/// And the operation contains two parameters `id2` and `id3` with `integer` and `string` types
/// Each parameter is declared `in place`
Expand Down