From 05c809a9b7bcf448456b648d05d19e70404c1b8d Mon Sep 17 00:00:00 2001 From: dankinsoid <30962149+dankinsoid@users.noreply.github.com> Date: Sat, 4 May 2024 23:46:27 +0400 Subject: [PATCH] stoplight --- Example/Package.swift | 2 +- Example/README.md | 9 ++- .../App/Controllers/OpenAPIController.swift | 4 +- Example/Sources/App/Models/Address.swift | 2 +- Example/Sources/App/Models/ApiResponse.swift | 2 +- Example/Sources/App/Models/Category.swift | 2 +- Example/Sources/App/Models/Customer.swift | 2 +- Example/Sources/App/Models/LoginQuery.swift | 2 +- Example/Sources/App/Models/Order.swift | 2 +- .../Sources/App/Models/UpdatePetQuery.swift | 2 +- .../Sources/App/Models/UploadImageQuery.swift | 2 +- Example/Sources/App/Models/User.swift | 2 +- README.md | 4 +- Sources/VaporToOpenAPI/Elements.swift | 69 ++++++++++++++++++- 14 files changed, 88 insertions(+), 18 deletions(-) diff --git a/Example/Package.swift b/Example/Package.swift index e6767a9a..c15eef22 100644 --- a/Example/Package.swift +++ b/Example/Package.swift @@ -12,7 +12,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/vapor/vapor.git", from: "4.70.0"), - .package(url: "https://github.com/dankinsoid/VaporToOpenAPI.git", from: "4.4.1"), + .package(path: "../") ], targets: [ .target( diff --git a/Example/README.md b/Example/README.md index cb14e54e..d58c1d90 100644 --- a/Example/README.md +++ b/Example/README.md @@ -9,9 +9,14 @@ run project swift run ``` -and open `http://127.0.0.1:8080/swagger/` (don't miss the last `/`) +if you run project through XCode you need to set the working directory to the project root directory. +Edit Scheme -> Options -> Use custom working directory + +and open: +- Swagger: `http://127.0.0.1:8080/swagger/` (don't miss the trailing `/`) +- Stoplight: `http://127.0.0.1:8080/doc` ## Requirements 📝 -- Language: Swift 5.7+ +- Language: Swift 5.9+ - Toolkit: [Vapor](https://docs.vapor.codes/) 4.70.0 diff --git a/Example/Sources/App/Controllers/OpenAPIController.swift b/Example/Sources/App/Controllers/OpenAPIController.swift index 666917a1..4838ec9b 100644 --- a/Example/Sources/App/Controllers/OpenAPIController.swift +++ b/Example/Sources/App/Controllers/OpenAPIController.swift @@ -31,7 +31,7 @@ struct OpenAPIController: RouteCollection { ) } .excludeFromOpenAPI() - - routes.grouped("docs").registerOpenAPIElements(path: "/swagger/swagger.json") + + routes.stoplightDocumentation("docs", openAPIPath: "/swagger/swagger.json") } } diff --git a/Example/Sources/App/Models/Address.swift b/Example/Sources/App/Models/Address.swift index a9ba4384..a9d98b8f 100644 --- a/Example/Sources/App/Models/Address.swift +++ b/Example/Sources/App/Models/Address.swift @@ -3,7 +3,7 @@ import SwiftOpenAPI import VaporToOpenAPI /// Address -@OpenAPIAutoDescriptable +@OpenAPIDescriptable public struct Address: Codable, WithExample { /// Street address diff --git a/Example/Sources/App/Models/ApiResponse.swift b/Example/Sources/App/Models/ApiResponse.swift index 48d4769a..bd4dc30a 100644 --- a/Example/Sources/App/Models/ApiResponse.swift +++ b/Example/Sources/App/Models/ApiResponse.swift @@ -4,7 +4,7 @@ import Vapor import VaporToOpenAPI /// Api response body -@OpenAPIAutoDescriptable +@OpenAPIDescriptable public struct ApiResponse: Codable, Content, WithExample { /// Response code diff --git a/Example/Sources/App/Models/Category.swift b/Example/Sources/App/Models/Category.swift index 68bd85e9..345356b9 100644 --- a/Example/Sources/App/Models/Category.swift +++ b/Example/Sources/App/Models/Category.swift @@ -2,7 +2,7 @@ import Foundation import SwiftOpenAPI import VaporToOpenAPI -@OpenAPIAutoDescriptable +@OpenAPIDescriptable /// Category public struct Category: Codable, Identifiable, WithExample { diff --git a/Example/Sources/App/Models/Customer.swift b/Example/Sources/App/Models/Customer.swift index ebe4ce95..502b7c29 100644 --- a/Example/Sources/App/Models/Customer.swift +++ b/Example/Sources/App/Models/Customer.swift @@ -2,7 +2,7 @@ import Foundation import SwiftOpenAPI import VaporToOpenAPI -@OpenAPIAutoDescriptable +@OpenAPIDescriptable /// Customer public struct Customer: Codable, Identifiable, WithExample { diff --git a/Example/Sources/App/Models/LoginQuery.swift b/Example/Sources/App/Models/LoginQuery.swift index 0690a052..8135c8d6 100644 --- a/Example/Sources/App/Models/LoginQuery.swift +++ b/Example/Sources/App/Models/LoginQuery.swift @@ -2,7 +2,7 @@ import Foundation import SwiftOpenAPI import VaporToOpenAPI -@OpenAPIAutoDescriptable +@OpenAPIDescriptable /// Login query public struct LoginQuery: Codable, WithExample { diff --git a/Example/Sources/App/Models/Order.swift b/Example/Sources/App/Models/Order.swift index c8acd412..b267ade1 100644 --- a/Example/Sources/App/Models/Order.swift +++ b/Example/Sources/App/Models/Order.swift @@ -3,7 +3,7 @@ import SwiftOpenAPI import Vapor import VaporToOpenAPI -@OpenAPIAutoDescriptable +@OpenAPIDescriptable /// Order public struct Order: Codable, Equatable, Content, WithExample { diff --git a/Example/Sources/App/Models/UpdatePetQuery.swift b/Example/Sources/App/Models/UpdatePetQuery.swift index d4d5158b..e0d5a0eb 100644 --- a/Example/Sources/App/Models/UpdatePetQuery.swift +++ b/Example/Sources/App/Models/UpdatePetQuery.swift @@ -2,7 +2,7 @@ import Foundation import SwiftOpenAPI import VaporToOpenAPI -@OpenAPIAutoDescriptable +@OpenAPIDescriptable public struct UpdatePetQuery: Codable, Equatable, WithExample { /// Updated name of the pet diff --git a/Example/Sources/App/Models/UploadImageQuery.swift b/Example/Sources/App/Models/UploadImageQuery.swift index 66df3274..bf8d9fc0 100644 --- a/Example/Sources/App/Models/UploadImageQuery.swift +++ b/Example/Sources/App/Models/UploadImageQuery.swift @@ -2,7 +2,7 @@ import Foundation import SwiftOpenAPI import VaporToOpenAPI -@OpenAPIAutoDescriptable +@OpenAPIDescriptable public struct UploadImageQuery: Codable, Equatable, WithExample { /// Additional data to pass to server diff --git a/Example/Sources/App/Models/User.swift b/Example/Sources/App/Models/User.swift index ce7d74d2..2e020eac 100644 --- a/Example/Sources/App/Models/User.swift +++ b/Example/Sources/App/Models/User.swift @@ -3,7 +3,7 @@ import SwiftOpenAPI import Vapor import VaporToOpenAPI -@OpenAPIAutoDescriptable +@OpenAPIDescriptable /// User model public struct User: Codable, Content, Identifiable, WithExample { diff --git a/README.md b/README.md index 60fce75c..c0950f95 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,9 @@ let routes = app.routes.groupedOpenAPI(auth: .apiKey()) - `openAPINoAuth`: This method is used to specify that an operation does not require any authentication. - `openAPI(custom:)`: These methods are used to customize a specific aspect of the OpenAPI metadata for a Vapor route, such as a specific security scheme or callback. - + +- `stoplightDocumentation(openAPI:)`: These methods are used to generate a [Stoplight documentation](https://stoplight.io/) from your Vapor routes. + - `operationID` and `operationRef`: These properties are used to generate unique identifiers for OpenAPI operations and to create references to them in other parts of the specification. #### Customizing OpenAPI schemas and parameters diff --git a/Sources/VaporToOpenAPI/Elements.swift b/Sources/VaporToOpenAPI/Elements.swift index d482923e..d8ba274d 100644 --- a/Sources/VaporToOpenAPI/Elements.swift +++ b/Sources/VaporToOpenAPI/Elements.swift @@ -1,8 +1,71 @@ import Vapor extension RoutesBuilder { - public func registerOpenAPIElements(path: String, title: String = "OpenAPI Documentation") { - get("") { req in + + /// Registers an OpenAPI documentation page using the [Stoplight Elements API](https://stoplight.io/). + /// - Parameters: + /// - path: The path to the stoplight documentation. + /// - title: The title of the OpenAPI documentation page. + /// - openAPI: A closure that returns the OpenAPI documentation. + @discardableResult + public func stoplightDocumentation( + _ path: PathComponent..., + title: String = "OpenAPI Documentation", + openAPI: @escaping (Routes) throws -> OpenAPIObject + ) -> Route { + stoplightDocumentation(path, title: title, openAPI: openAPI) + } + + /// Registers an OpenAPI documentation page using the [Stoplight Elements API](https://stoplight.io/). + /// - Parameters: + /// - path: The path to the stoplight documentation. + /// - title: The title of the OpenAPI documentation page. + /// - openAPI: A closure that returns the OpenAPI documentation. + @discardableResult + public func stoplightDocumentation( + _ path: [PathComponent], + title: String = "OpenAPI Documentation", + openAPI: @escaping (Routes) throws -> OpenAPIObject + ) -> Route { + let jsonPath = path + ["openapi.json"] + get(jsonPath) { req in + try openAPI(req.application.routes) + } + .excludeFromOpenAPI() + + return stoplightDocumentation( + path, + openAPIPath: "/" + jsonPath.string, + title: title + ) + } + + /// Registers an OpenAPI documentation page using the [Stoplight Elements API](https://stoplight.io/). + /// - Parameters: + /// - path: The path to the stoplight documentation. + /// - openAPIPath: The path to the OpenAPI documentation. + /// - title: The title of the OpenAPI documentation page. + @discardableResult + public func stoplightDocumentation( + _ path: PathComponent..., + openAPIPath: String, + title: String = "OpenAPI Documentation" + ) -> Route { + stoplightDocumentation(path, openAPIPath: openAPIPath, title: title) + } + + /// Registers an OpenAPI documentation page using the [Stoplight Elements API](https://stoplight.io/). + /// - Parameters: + /// - path: The path to the stoplight documentation. + /// - openAPIPath: The path to the OpenAPI documentation. + /// - title: The title of the OpenAPI documentation page. + @discardableResult + public func stoplightDocumentation( + _ path: [PathComponent], + openAPIPath: String, + title: String = "OpenAPI Documentation" + ) -> Route { + get(path) { req in var headers = HTTPHeaders() headers.contentType = .html @@ -23,7 +86,7 @@ extension RoutesBuilder { - +