Created
January 27, 2020 07:59
-
-
Save mattpolzin/57dbfbc33e9e97b50f63b3e280866bca to your computer and use it in GitHub Desktop.
Example OpenAPI tooling script in Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/swift sh | |
import Foundation | |
import ConsoleKit // vapor/console-kit ~> 4.0.0-beta.2.1 | |
import TransformEncoder // @mattpolzin ~> 0.2.0 | |
import OpenAPIKit // @mattpolzin ~> 0.16.0 | |
import Yams // @jpsim ~> 2.0.0 | |
// MARK: - Helpers | |
extension OpenAPI.HttpVerb: LosslessStringConvertible, CustomStringConvertible { | |
public init?(_ operation: String) { | |
guard let value = Self(rawValue: operation) else { | |
return nil | |
} | |
self = value | |
} | |
public var description: String { rawValue } | |
} | |
// MARK: - Commands | |
final class NewDocumentationCommand: Command { | |
struct Signature: CommandSignature { | |
init() {} | |
} | |
var help: String { | |
"Create a new ./openapi.yml documentation file." | |
} | |
func run(using context: CommandContext, signature: Signature) throws { | |
let templateDoc = OpenAPI.Document( | |
info: .init( | |
title: "New API", | |
version: "1.0" | |
), | |
servers: [], | |
paths: [:], | |
components: .noComponents | |
) | |
let output = try YAMLEncoder().encode(templateDoc) | |
try output.data(using: .utf8)!.write(to: URL(fileURLWithPath: "./openapi.yml"), options: .withoutOverwriting) | |
console.success("API documentation created 🎉") | |
} | |
} | |
final class AddEndpointCommand: Command { | |
struct Signature: CommandSignature { | |
@Argument(name: "path", help: "Path for new endpoint.") | |
var path: String | |
@Argument(name: "operation", help: "The operation to use. One of [\(OpenAPI.HttpVerb.allCases.map { $0.rawValue }.joined(separator: ", "))].") | |
var operation: OpenAPI.HttpVerb | |
init() {} | |
} | |
var help: String { | |
"Add a new endpoint to the OpenAPI document at ./openapi.yml" | |
} | |
func run(using context: CommandContext, signature: Signature) throws { | |
let pathComponents = OpenAPI.PathComponents(rawValue: signature.path) | |
let templateOperation = OpenAPI.PathItem.Operation( | |
parameters: [ | |
.parameter( | |
name: "Content-Type", | |
parameterLocation: .header(required: false), | |
schema: .string( | |
allowedValues: [ | |
.init(OpenAPI.ContentType.json.rawValue), | |
.init(OpenAPI.ContentType.html.rawValue) | |
] | |
) | |
) | |
], | |
responses: [ | |
200: .response( | |
description: "Successful Retrieval", | |
content: [ | |
.json: .init( | |
schema: .object( | |
properties: [ | |
"hello": .string | |
] | |
), | |
example: #"{ "hello": "world" }"# | |
) | |
] | |
) | |
] | |
) | |
let document = try YAMLDecoder().decode(OpenAPI.Document.self, from: String(contentsOf: URL(fileURLWithPath: "./openapi.yml"))) | |
let transformer = TransformEncoder() | |
// validate that the endpoint does not already exist | |
transformer.validate { (paths: OpenAPI.PathItem.Map, codingPath) in | |
let existingOperation = paths[pathComponents]?[OpenAPI.PathItem.self]?[signature.operation] | |
if existingOperation != nil, existingOperation != templateOperation { | |
throw ValidationError( | |
reason: "The \(signature.operation) endpoint for \(pathComponents.rawValue) already exists.", | |
at: codingPath | |
) | |
} | |
} | |
// add the new endpoint | |
transformer.transformOnce { (paths: OpenAPI.PathItem.Map, _) -> OpenAPI.PathItem.Map in | |
var paths = paths | |
var currentPathItem = paths[pathComponents]?[OpenAPI.PathItem.self] ?? OpenAPI.PathItem() | |
currentPathItem[signature.operation] = templateOperation | |
paths[pathComponents] = .init(currentPathItem) | |
return paths | |
} | |
do { | |
let transformedDoc = try transformer.encode(document) | |
let output = try YAMLEncoder().encode(transformedDoc) | |
try output.data(using: .utf8)!.write(to: URL(fileURLWithPath: "./openapi.yml")) | |
console.success("Endpoint added 🎉") | |
} catch let error as EncodingError { | |
switch error { | |
case .invalidValue(_, let errorContext): | |
if let underlyingError = errorContext.underlyingError { | |
context.console.error(String(describing: underlyingError)) | |
break | |
} | |
throw error | |
} | |
} | |
} | |
} | |
// MARK: - Create Console, configure, run | |
let console: Console = Terminal() | |
var input = CommandInput(arguments: CommandLine.arguments) | |
var context = CommandContext(console: console, input: input) | |
var commands = Commands() | |
commands.use(AddEndpointCommand(), as: "new-endpoint", isDefault: false) | |
commands.use(NewDocumentationCommand(), as: "new", isDefault: false) | |
do { | |
let group = commands | |
.group(help: "OpenAPI Tooling") | |
try console.run(group, input: input) | |
} catch let error { | |
console.error(error.localizedDescription) | |
exit(1) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Syntax highlighting: