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

Swagger 2 + Swift 5 #240

Open
wants to merge 2 commits into
base: swagger_2
Choose a base branch
from
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
30 changes: 15 additions & 15 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -6,62 +6,62 @@
"repositoryURL": "https://github.com/yonaskolb/JSONUtilities.git",
"state": {
"branch": null,
"revision": "6403a5455f30add5413095d1b5a70e8a5eb83ba0",
"version": "3.3.8"
"revision": "128d2ffc22467f69569ef8ff971683e2393191a0",
"version": "4.2.0"
}
},
{
"package": "PathKit",
"repositoryURL": "https://github.com/kylef/PathKit.git",
"state": {
"branch": null,
"revision": "891a3fec2699fc43aed18b7649950677c0152a22",
"version": "0.8.0"
"revision": "e2f5be30e4c8f531c9c1e8765aa7b71c0a45d7a0",
"version": "0.9.2"
}
},
{
"package": "Rainbow",
"repositoryURL": "https://github.com/onevcat/Rainbow.git",
"state": {
"branch": null,
"revision": "797a68d0a642609424b08f11eb56974a54d5f6e2",
"version": "3.1.4"
"revision": "9c52c1952e9b2305d4507cf473392ac2d7c9b155",
"version": "3.1.5"
}
},
{
"package": "Spectre",
"repositoryURL": "https://github.com/kylef/Spectre.git",
"state": {
"branch": null,
"revision": "e46b75cf03ad5e563b4b0a5068d3d6f04d77d80b",
"version": "0.7.2"
"revision": "f14ff47f45642aa5703900980b014c2e9394b6e5",
"version": "0.9.0"
}
},
{
"package": "Stencil",
"repositoryURL": "https://github.com/yonaskolb/Stencil.git",
"repositoryURL": "https://github.com/stencilproject/Stencil.git",
"state": {
"branch": null,
"revision": "daf4e41549a534904376794e899ca6aecaf4ff83",
"version": "0.9.3"
"revision": "0e9a78d6584e3812cd9c09494d5c7b483e8f533c",
"version": "0.13.1"
}
},
{
"package": "SwiftCLI",
"repositoryURL": "https://github.com/jakeheis/SwiftCLI",
"state": {
"branch": null,
"revision": "37f4a7f863f6fe76ce44fc0023f331eea0089beb",
"version": "5.2.0"
"revision": "c72c4564f8c0a24700a59824880536aca45a4cae",
"version": "6.0.1"
}
},
{
"package": "Yams",
"repositoryURL": "https://github.com/jpsim/Yams.git",
"state": {
"branch": null,
"revision": "26ab35f50ea891e8edefcc9d975db2f6b67e1d68",
"version": "1.0.1"
"revision": "c947a306d2e80ecb2c0859047b35c73b8e1ca27f",
"version": "2.0.0"
}
}
]
Expand Down
16 changes: 8 additions & 8 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:4.0
// swift-tools-version:5.0
import PackageDescription

let package = Package(
Expand All @@ -9,13 +9,13 @@ let package = Package(
.library(name: "Swagger", targets: ["Swagger"]),
],
dependencies: [
.package(url: "https://github.com/kylef/PathKit.git", from: "0.8.0"),
.package(url: "https://github.com/jakeheis/SwiftCLI", from: "5.0.0"),
.package(url: "https://github.com/yonaskolb/Stencil.git", from: "0.9.0"),
.package(url: "https://github.com/jpsim/Yams.git", from: "1.0.0"),
.package(url: "https://github.com/yonaskolb/JSONUtilities.git", from: "3.3.0"),
.package(url: "https://github.com/kylef/Spectre.git", from: "0.7.0"),
.package(url: "https://github.com/onevcat/Rainbow.git", from: "3.1.0"),
.package(url: "https://github.com/kylef/PathKit.git", from: "0.9.0"),
.package(url: "https://github.com/jakeheis/SwiftCLI", from: "6.0.1"),
.package(url: "https://github.com/stencilproject/Stencil.git", from: "0.13.0"),
.package(url: "https://github.com/jpsim/Yams.git", from: "2.0.0"),
.package(url: "https://github.com/yonaskolb/JSONUtilities.git", from: "4.2.0"),
.package(url: "https://github.com/kylef/Spectre.git", from: "0.9.0"),
.package(url: "https://github.com/onevcat/Rainbow.git", from: "3.0.0"),
],
targets: [
.target(name: "SwagGen", dependencies: [
Expand Down
35 changes: 26 additions & 9 deletions Sources/SwagGen/GenerateCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class GenerateCommand: Command {
let name = "generate"
let shortDescription = "Generates code for a Swagger spec"

let spec = SwiftCLI.Parameter()
let spec = SwiftCLI.Param<String>()

let clean = Key<Generator.Clean>("--clean", "-c", description: "How the destination directory will be cleaned of non generated files:\n\(String(repeating: " ", count: 31)) - none: no files will be removed\n\(String(repeating: " ", count: 31)) - leave.files: all other files will be removed except if starting with . in the destination directory\n\(String(repeating: " ", count: 31)) - all: all other files will be removed")

Expand All @@ -22,10 +22,15 @@ class GenerateCommand: Command {

let language = Key<String>("--language", "-l", description: "The language of the template that will be generated. This defaults to swift")

let options = VariadicKey<String>("--option", "-o", description: "An option that will be merged with template options, and overwrite any options of the same name.\n\(String(repeating: " ", count: 31))Can be repeated multiple times and must in the format --option \"name:value\"")
let optionsKey = VariadicKey<String>("--option", "-o", description: """
An option that will be merged with template options, and overwrite any options of the same name.
Can be repeated multiple times and must be in the format --option "name:value".
The key can have multiple parts separated by dots to set nested properties:
for example, --option "typeAliases.ID:String" would change the type alias of ID to String.
""")

let verbose = Flag("--verbose", "-v", description: "Show verbose output", defaultValue: false)
let silent = Flag("--silent", "-s", description: "Silence standard output", defaultValue: false)
let verbose = Flag("--verbose", "-v", description: "Show verbose output")
let silent = Flag("--silent", "-s", description: "Silence standard output")

func execute() throws {
let clean = self.clean.value ?? .none
Expand All @@ -45,16 +50,28 @@ class GenerateCommand: Command {
exitWithError("Must pass valid spec. It can be a path or a url")
}

/// Assign a value by key list to a multiply nested dictionary that might not exist yet.
func deepAssign(dict: inout [String: Any], keys: [String], value: Any) {
guard let key = keys.first else { return }
if keys.count == 1 {
dict[key] = value
} else {
var subdict: [String: Any] = dict[key].flatMap { $0 as? [String: Any] } ?? [:]
deepAssign(dict: &subdict, keys: Array(keys.dropFirst()), value: value)
dict[key] = subdict
}
}

var options: [String: Any] = [:]
for option in self.options.values {
for option in self.optionsKey.value {
guard option.contains(":") else {
exitWithError("Options arguement '\(option)' must be comma delimited and the name and value must be seperated by a colon")
exitWithError("Options argument '\(option)' must have its name and value separated with a colon")
}
let parts = option.components(separatedBy: ":").map { $0.trimmingCharacters(in: .whitespaces) }
if parts.count >= 2 {
let key = parts.first!
let keys = parts.first!.split(separator: ".").map { String($0) }
let value = Array(parts.dropFirst()).joined(separator: ":")
options[key] = value
deepAssign(dict: &options, keys: Array(keys), value: value)
}
}

Expand Down Expand Up @@ -210,7 +227,7 @@ extension Generator.Clean: ConvertibleFromString {
public static func convert(from: String) -> Generator.Clean? {
switch from {
case "true", "yes", "all": return .all
case "false", "no", "none": return .none
case "false", "no", "none": return Generator.Clean.none
case "leave-dot-files", "leaveDotFiles", "leave.files": return .leaveDotFiles
default: return nil
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/SwagGenKit/CodeFormatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ public class CodeFormatter {

let responseSchemas: [Context] = operation.responses.compactMap { response in
guard let schema = response.response.value.schema else { return nil }
return getInlineSchemaContext(schema, name: response.name.lowerCamelCased())
return getInlineSchemaContext(schema.value, name: response.name.lowerCamelCased())
}
context["responseSchemas"] = responseSchemas
context["hasResponseModels"] = !operation.responses.filter { $0.response.value.schema != nil }.isEmpty
Expand All @@ -277,9 +277,9 @@ public class CodeFormatter {
context["name"] = response.name.lowerCamelCased()
context["statusCode"] = response.statusCode
context["success"] = response.successful
context["schema"] = response.response.value.schema.flatMap(getSchemaContext)
context["schema"] = response.response.value.schema.flatMap { getSchemaContext($0.value) }
context["description"] = response.response.value.description.description
context["type"] = response.response.value.schema.flatMap { getSchemaType(name: response.name, schema: $0) }
context["type"] = response.response.value.schema.flatMap { getSchemaType(name: response.name, schema: $0.value) }

return context
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwagGenKit/SwaggerExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ extension OperationResponse {
}

var enumValue: Enum? {
return response.value.schema?.getEnum(name: name, description: response.value.description)
return response.value.schema?.value.getEnum(name: name, description: response.value.description)
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Swagger/Response.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public struct OperationResponse {
public struct Response {

public let description: String
public let schema: Schema?
public let schema: PossibleReference<Schema>?
public let headers: [String: Item]
}

Expand Down
7 changes: 6 additions & 1 deletion Sources/Swagger/SwaggerSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,12 @@ extension SwaggerSpec: JSONObjectConvertible {
func resolveResponse(_ response: OperationResponse) {
resolveResponseReference(response.response)
if let schema = response.response.value.schema {
resolveSchema(schema)
switch schema {
case .reference(let schemaReference):
resolveDefinitionReference(schemaReference)
case .value(let schema):
resolveSchema(schema)
}
}
}

Expand Down
50 changes: 27 additions & 23 deletions Tests/SwagGenKitTests/FixtureTests.swift
Original file line number Diff line number Diff line change
@@ -1,41 +1,45 @@
import Foundation
import PathKit
import Spectre
import XCTest
@testable import SwagGenKit
@testable import Swagger

public func testFixtures() {
class FixturesTests: XCTestCase {

let specsPath = Path(#file) + "../../../Specs"
let specs = (try? specsPath.children().filter { $0.isDirectory && !$0.lastComponent.hasPrefix(".") }) ?? []
func testFixtures() {

describe("SwagGen") {
for specFolder in specs {
let specsPath = Path(#file) + "../../../Specs"
let specs = (try? specsPath.children().filter { $0.isDirectory && !$0.lastComponent.hasPrefix(".") }) ?? []

let specName = specFolder.lastComponent
describe("SwagGen") {
for specFolder in specs {

$0.it("generate \(specName)") {
let specName = specFolder.lastComponent

let possibleExtensions = ["yml", "yaml", "json"]
guard let specPath = possibleExtensions.map({ specFolder + "spec.\($0)" }).filter({ $0.exists }).first else {
throw failure("couldn't find spec")
}
$0.it("generate \(specName)") {

let possibleExtensions = ["yml", "yaml", "json"]
guard let specPath = possibleExtensions.map({ specFolder + "spec.\($0)" }).filter({ $0.exists }).first else {
throw failure("couldn't find spec")
}

let spec = try SwaggerSpec(path: specPath)
let spec = try SwaggerSpec(path: specPath)

let templateType = "Swift"
let templatePath = Path(#file) + "../../../Templates/\(templateType)"
let templateConfig = try TemplateConfig(path: templatePath, options: ["name": specName])
let templateType = "Swift"
let templatePath = Path(#file) + "../../../Templates/\(templateType)"
let templateConfig = try TemplateConfig(path: templatePath, options: ["name": specName])

let codeFormatter = SwiftFormatter(spec: spec, templateConfig: templateConfig)
let context = codeFormatter.getContext()
let codeFormatter = SwiftFormatter(spec: spec, templateConfig: templateConfig)
let context = codeFormatter.getContext()

let destinationPath = specFolder + "generated/\(templateType)"
try destinationPath.mkpath()
let generator = Generator(context: context, destination: destinationPath.normalize(), templateConfig: templateConfig)
let result = try generator.generate(clean: .all, fileChanged: { _ in })
if result.hasChanged {
throw failure("Generated spec has changed: \(result.description)\n\n\(result.changedFilesDescription(includeModifiedContent: true))")
let destinationPath = specFolder + "generated/\(templateType)"
try destinationPath.mkpath()
let generator = Generator(context: context, destination: destinationPath.normalize(), templateConfig: templateConfig)
let result = try generator.generate(clean: .all, fileChanged: { _ in })
if result.hasChanged {
throw failure("Generated spec has changed: \(result.description)\n\n\(result.changedFilesDescription(includeModifiedContent: true))")
}
}
}
}
Expand Down
63 changes: 33 additions & 30 deletions Tests/SwagGenKitTests/GeneratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,40 @@ import Spectre
@testable import SwagGenKit
import XCTest

public func testGenerator() {
class GeneratorTests: XCTestCase {

describe("name generator") {
$0.it("lower camelcases") {
try expect("CamelCase".lowerCamelCased()) == "camelCase"
try expect("camelCase".lowerCamelCased()) == "camelCase"
try expect("NSW".lowerCamelCased()) == "nsw"
try expect("UserURL".lowerCamelCased()) == "userURL"
try expect("user_url".lowerCamelCased()) == "userURL"
try expect("url".lowerCamelCased()) == "url"
try expect("event-type".lowerCamelCased()) == "eventType"
try expect("a-z".lowerCamelCased()) == "az"
try expect("A-Z".lowerCamelCased()) == "aZ"
try expect("SNAKE_CASE".lowerCamelCased()) == "snakeCase"
try expect("snake_case".lowerCamelCased()) == "snakeCase"
try expect("UserServiceDeviceHasAccessTo".lowerCamelCased()) == "userServiceDeviceHasAccessTo"
try expect("UserService.getCustomerDevices".lowerCamelCased()) == "userServiceGetCustomerDevices"
}
$0.it("upper camelcases") {
try expect("CamelCase".upperCamelCased()) == "CamelCase"
try expect("camelCase".upperCamelCased()) == "CamelCase"
try expect("nsw".upperCamelCased()) == "Nsw"
try expect("NSW".upperCamelCased()) == "NSW"
try expect("userURL".upperCamelCased()) == "UserURL"
try expect("url".upperCamelCased()) == "URL"
try expect("a-z".upperCamelCased()) == "Az"
try expect("A-Z".upperCamelCased()) == "AZ"
try expect("user_url".upperCamelCased()) == "UserURL"
try expect("snake_case".upperCamelCased()) == "SnakeCase"
try expect("SNAKE_CASE".upperCamelCased()) == "SnakeCase"
try expect("UserServiceDeviceHasAccessTo".upperCamelCased()) == "UserServiceDeviceHasAccessTo"
public func testGenerator() {

describe("name generator") {
$0.it("lower camelcases") {
try expect("CamelCase".lowerCamelCased()) == "camelCase"
try expect("camelCase".lowerCamelCased()) == "camelCase"
try expect("NSW".lowerCamelCased()) == "nsw"
try expect("UserURL".lowerCamelCased()) == "userURL"
try expect("user_url".lowerCamelCased()) == "userURL"
try expect("url".lowerCamelCased()) == "url"
try expect("event-type".lowerCamelCased()) == "eventType"
try expect("a-z".lowerCamelCased()) == "az"
try expect("A-Z".lowerCamelCased()) == "aZ"
try expect("SNAKE_CASE".lowerCamelCased()) == "snakeCase"
try expect("snake_case".lowerCamelCased()) == "snakeCase"
try expect("UserServiceDeviceHasAccessTo".lowerCamelCased()) == "userServiceDeviceHasAccessTo"
try expect("UserService.getCustomerDevices".lowerCamelCased()) == "userServiceGetCustomerDevices"
}
$0.it("upper camelcases") {
try expect("CamelCase".upperCamelCased()) == "CamelCase"
try expect("camelCase".upperCamelCased()) == "CamelCase"
try expect("nsw".upperCamelCased()) == "Nsw"
try expect("NSW".upperCamelCased()) == "NSW"
try expect("userURL".upperCamelCased()) == "UserURL"
try expect("url".upperCamelCased()) == "URL"
try expect("a-z".upperCamelCased()) == "Az"
try expect("A-Z".upperCamelCased()) == "AZ"
try expect("user_url".upperCamelCased()) == "UserURL"
try expect("snake_case".upperCamelCased()) == "SnakeCase"
try expect("SNAKE_CASE".upperCamelCased()) == "SnakeCase"
try expect("UserServiceDeviceHasAccessTo".upperCamelCased()) == "UserServiceDeviceHasAccessTo"
}
}
}
}
8 changes: 0 additions & 8 deletions Tests/SwagGenKitTests/XCTest.swift

This file was deleted.

Loading