├── .editorconfig
├── .github
└── workflows
│ ├── checks.yml
│ ├── publish.yml
│ └── publish_docs.yml
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle.kts
├── detekt
└── detekt.yml
├── docs
├── assets
│ ├── icons
│ │ └── redoc.svg
│ ├── images
│ │ ├── redoc.png
│ │ └── swagger.png
│ └── stylesheets
│ │ └── extra.css
├── changelog.md
├── examples
│ ├── authentication.md
│ ├── basics.md
│ ├── complete-configuration.md
│ ├── customized-schema-generator.md
│ ├── external-openapi-spec.md
│ ├── file-upload.md
│ ├── index.md
│ ├── kotlinx-serialization.md
│ ├── multiple-specs.md
│ ├── openapi-examples.md
│ ├── petstore.md
│ ├── request-response.md
│ ├── schemas.md
│ └── webhooks.md
├── index.md
├── openapi
│ ├── documenting_routes.md
│ ├── example_encoding.md
│ ├── getting_started.md
│ ├── handling_types_schemas_examples.md
│ ├── index.md
│ ├── multiple_specs.md
│ ├── plugin_configuration.md
│ ├── schema_generation.md
│ └── typesafe_routing.md
├── redoc
│ ├── getting_started.md
│ └── index.md
└── swaggerui
│ ├── getting_started.md
│ └── index.md
├── examples
├── build.gradle.kts
└── src
│ └── main
│ ├── kotlin
│ └── io
│ │ └── github
│ │ └── smiley4
│ │ └── ktoropenapi
│ │ └── examples
│ │ ├── Authentication.kt
│ │ ├── Basics.kt
│ │ ├── CompleteConfig.kt
│ │ ├── CustomizedSchemaGenerator.kt
│ │ ├── Examples.kt
│ │ ├── ExternalOpenApiSpec.kt
│ │ ├── FileUpload.kt
│ │ ├── KotlinxSerialization.kt
│ │ ├── Minimal.kt
│ │ ├── MultipleSpecs.kt
│ │ ├── Petstore.kt
│ │ ├── RequestResponse.kt
│ │ ├── Schemas.kt
│ │ ├── TypesafeRouting.kt
│ │ └── Webhooks.kt
│ └── resources
│ └── logback.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── ktor-openapi
├── build.gradle.kts
└── src
│ ├── main
│ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── smiley4
│ │ └── ktoropenapi
│ │ ├── DocumentedRouteSelector.kt
│ │ ├── OpenApiPlugin.kt
│ │ ├── builder
│ │ ├── OpenApiSpecBuilder.kt
│ │ ├── example
│ │ │ ├── ExampleContext.kt
│ │ │ └── ExampleContextImpl.kt
│ │ ├── openapi
│ │ │ ├── ComponentsBuilder.kt
│ │ │ ├── ContactBuilder.kt
│ │ │ ├── ContentBuilder.kt
│ │ │ ├── ExternalDocumentationBuilder.kt
│ │ │ ├── HeaderBuilder.kt
│ │ │ ├── InfoBuilder.kt
│ │ │ ├── LicenseBuilder.kt
│ │ │ ├── OAuthFlowsBuilder.kt
│ │ │ ├── OpenApiBuilder.kt
│ │ │ ├── OperationBuilder.kt
│ │ │ ├── OperationTagsBuilder.kt
│ │ │ ├── ParameterBuilder.kt
│ │ │ ├── PathBuilder.kt
│ │ │ ├── PathsBuilder.kt
│ │ │ ├── RequestBodyBuilder.kt
│ │ │ ├── ResponseBuilder.kt
│ │ │ ├── ResponsesBuilder.kt
│ │ │ ├── SecurityRequirementsBuilder.kt
│ │ │ ├── SecuritySchemesBuilder.kt
│ │ │ ├── ServerBuilder.kt
│ │ │ ├── TagBuilder.kt
│ │ │ ├── TagExternalDocumentationBuilder.kt
│ │ │ └── WebhooksBuilder.kt
│ │ ├── route
│ │ │ ├── RouteCollector.kt
│ │ │ ├── RouteDocumentationMerger.kt
│ │ │ └── RouteMeta.kt
│ │ └── schema
│ │ │ ├── SchemaContext.kt
│ │ │ └── SchemaContextImpl.kt
│ │ ├── config
│ │ ├── AuthKeyLocation.kt
│ │ ├── AuthScheme.kt
│ │ ├── AuthType.kt
│ │ ├── BaseBodyConfig.kt
│ │ ├── ContactConfig.kt
│ │ ├── ExampleConfig.kt
│ │ ├── ExampleEncoder.kt
│ │ ├── ExternalDocsConfig.kt
│ │ ├── HeaderConfig.kt
│ │ ├── InfoConfig.kt
│ │ ├── LicenseConfig.kt
│ │ ├── MultipartBodyConfig.kt
│ │ ├── MultipartPartConfig.kt
│ │ ├── OpenApiDslMarker.kt
│ │ ├── OpenApiPluginConfig.kt
│ │ ├── OpenIdOAuthFlowConfig.kt
│ │ ├── OpenIdOAuthFlowsConfig.kt
│ │ ├── OutputFormat.kt
│ │ ├── ParameterLocation.kt
│ │ ├── PathFilter.kt
│ │ ├── PostBuild.kt
│ │ ├── RequestConfig.kt
│ │ ├── RequestParameterConfig.kt
│ │ ├── ResponseConfig.kt
│ │ ├── ResponsesConfig.kt
│ │ ├── RouteConfig.kt
│ │ ├── SchemaConfig.kt
│ │ ├── SchemaGenerator.kt
│ │ ├── SchemaOverwriteModule.kt
│ │ ├── SecurityConfig.kt
│ │ ├── SecuritySchemeConfig.kt
│ │ ├── ServerConfig.kt
│ │ ├── ServerVariableConfig.kt
│ │ ├── SimpleBodyConfig.kt
│ │ ├── SpecAssigner.kt
│ │ ├── TagConfig.kt
│ │ ├── TagGenerator.kt
│ │ ├── TagsConfig.kt
│ │ ├── ValueExampleDescriptorConfig.kt
│ │ └── descriptors
│ │ │ ├── ExampleDescriptor.kt
│ │ │ └── TypeDescriptor.kt
│ │ ├── data
│ │ ├── BaseBodyData.kt
│ │ ├── ContactData.kt
│ │ ├── DataUtils.kt
│ │ ├── ExampleConfigData.kt
│ │ ├── ExternalDocsData.kt
│ │ ├── HeaderData.kt
│ │ ├── InfoData.kt
│ │ ├── LicenseData.kt
│ │ ├── MultipartPartData.kt
│ │ ├── OpenApiPluginData.kt
│ │ ├── OpenIdOAuthFlowData.kt
│ │ ├── OpenIdOAuthFlowsData.kt
│ │ ├── RequestData.kt
│ │ ├── RequestParameterData.kt
│ │ ├── ResponseData.kt
│ │ ├── RouteData.kt
│ │ ├── SchemaConfigData.kt
│ │ ├── SecurityData.kt
│ │ ├── SecuritySchemeData.kt
│ │ ├── ServerData.kt
│ │ ├── ServerVariableData.kt
│ │ ├── TagData.kt
│ │ └── TagsData.kt
│ │ └── resources
│ │ ├── DocumentedResourceRoutes.kt
│ │ └── documentationExtractor.kt
│ └── test
│ ├── kotlin
│ └── io
│ │ └── github
│ │ └── smiley4
│ │ └── ktorswaggerui
│ │ ├── builder
│ │ ├── ExternalDocsBuilderTest.kt
│ │ ├── InfoBuilderTest.kt
│ │ ├── OpenApiBuilderTest.kt
│ │ ├── OperationBuilderTest.kt
│ │ ├── PathsBuilderTest.kt
│ │ ├── SecuritySchemesBuilderTest.kt
│ │ ├── ServersBuilderTest.kt
│ │ └── TagsBuilderTest.kt
│ │ └── misc
│ │ ├── RouteDocumentationMergerTest.kt
│ │ └── RoutingTests.kt
│ └── resources
│ └── logback.xml
├── ktor-redoc
├── build.gradle.kts
└── src
│ ├── main
│ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── smiley4
│ │ └── ktorredoc
│ │ ├── PropertyBuilder.kt
│ │ ├── Redoc.kt
│ │ ├── RedocRouteSelector.kt
│ │ ├── ResourceContent.kt
│ │ └── config
│ │ └── RedocConfig.kt
│ └── test
│ └── kotlin
│ └── io
│ └── github
│ └── smiley4
│ └── ktorredoc
│ └── RoutingTests.kt
├── ktor-swagger-ui
├── build.gradle.kts
└── src
│ ├── main
│ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── smiley4
│ │ └── ktorswaggerui
│ │ ├── PropertyBuilder.kt
│ │ ├── ResourceContent.kt
│ │ ├── SwaggerUI.kt
│ │ ├── SwaggerUIRouteSelector.kt
│ │ └── config
│ │ ├── OperationsSort.kt
│ │ ├── SwaggerUIConfig.kt
│ │ ├── SwaggerUISyntaxHighlight.kt
│ │ └── TagSort.kt
│ └── test
│ └── kotlin
│ └── io
│ └── github
│ └── smiley4
│ └── ktorswaggerui
│ └── RoutingTests.kt
├── mkdocs.yml
└── settings.gradle.kts
/.github/workflows/checks.yml:
--------------------------------------------------------------------------------
1 | name: Checks
2 |
3 | on: [ push, pull_request, workflow_dispatch ]
4 |
5 | permissions:
6 | contents: read
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v3
13 | - name: Set up JDK 11
14 | uses: actions/setup-java@v3
15 | with:
16 | java-version: '11'
17 | distribution: 'temurin'
18 | cache: 'gradle'
19 | - name: Build with Gradle
20 | uses: gradle/gradle-build-action@v2.7.0
21 | with:
22 | arguments: build
23 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on: workflow_dispatch
4 |
5 | permissions:
6 | contents: read
7 |
8 | jobs:
9 | build:
10 |
11 | runs-on: ubuntu-latest
12 | if: github.repository == 'SMILEY4/ktor-openapi-tools'
13 |
14 | steps:
15 | - uses: actions/checkout@v3
16 |
17 | - name: Set up JDK 11
18 | uses: actions/setup-java@v3
19 | with:
20 | java-version: '11'
21 | distribution: 'temurin'
22 | cache: 'gradle'
23 |
24 | - name: Build
25 | uses: gradle/gradle-build-action@v2.7.0
26 | with:
27 | arguments: build
28 |
29 | - name: Publish
30 | run: |
31 | ./gradlew publishAllPublicationsToMavenCentral --no-configuration-cache
32 | ./gradlew releaseRepository
33 | env:
34 | SONATYPE_CONNECT_TIMEOUT_SECONDS: 180
35 | SONATYPE_CLOSE_TIMEOUT_SECONDS: 900
36 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
37 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
38 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_SECRET_KEY }}
39 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
40 |
--------------------------------------------------------------------------------
/.github/workflows/publish_docs.yml:
--------------------------------------------------------------------------------
1 | name: Publish Docs
2 |
3 | on: workflow_dispatch
4 |
5 | permissions: write-all
6 |
7 | jobs:
8 | deployment:
9 |
10 | runs-on: ubuntu-latest
11 | if: github.repository == 'SMILEY4/ktor-openapi-tools'
12 |
13 | steps:
14 |
15 | - uses: actions/checkout@v3
16 |
17 | - name: Set up JDK 11
18 | uses: actions/setup-java@v3
19 | with:
20 | java-version: '11'
21 | distribution: 'temurin'
22 | cache: 'gradle'
23 |
24 | - name: Cache Python
25 | uses: actions/cache@v4
26 | env:
27 | cache-name: "cache-python"
28 | with:
29 | path: '.gradle/python'
30 | key: 'python'
31 |
32 | - name: Dokka
33 | run: ./gradlew dokkaHtml -Dorg.gradle.jvmargs=-XX:MaxMetaspaceSize=512m
34 |
35 | - name: Publish
36 | run: ./gradlew mkdocsPublish -Porg.ajoberstar.grgit.auth.username=${{ secrets.GRGIT_USER }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Jetbrains
2 | .idea/
3 |
4 | ### Kotlin
5 | .kotlin
6 | *.class
7 | *.log
8 | *.jar
9 |
10 | # Dokka
11 | docs/dokka/
12 |
13 | ### Visual Studio Code
14 | .vscode/
15 |
16 | # Local History for Visual Studio Code
17 | .history/
18 |
19 | ### Linux
20 | *~
21 |
22 | # temporary files which can be created if a process still has a handle open of a deleted file
23 | .fuse_hidden*
24 |
25 | # KDE directory preferences
26 | .directory
27 |
28 | # Linux trash folder which might appear on any partition or disk
29 | .Trash-*
30 |
31 | # .nfs files are created when an open file is removed but is still being accessed
32 | .nfs*
33 |
34 | ### Windows
35 | # Windows thumbnail cache files
36 | Thumbs.db
37 | Thumbs.db:encryptable
38 | ehthumbs.db
39 | ehthumbs_vista.db
40 |
41 | # Dump file
42 | *.stackdump
43 |
44 | # Folder config file
45 | [Dd]esktop.ini
46 |
47 | # Recycle Bin used on file shares
48 | $RECYCLE.BIN/
49 |
50 | # Windows Installer files
51 | *.cab
52 | *.msi
53 | *.msix
54 | *.msm
55 | *.msp
56 |
57 | # Windows shortcuts
58 | *.lnk
59 |
60 | ### Gradle
61 | .gradle/
62 | build/
63 |
64 | # Ignore Gradle GUI config
65 | gradle-app.setting
66 |
67 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
68 | !gradle-wrapper.jar
69 |
70 | # Avoid ignore Gradle wrappper properties
71 | !gradle-wrapper.properties
72 |
73 | # Cache of project
74 | .gradletasknamecache
75 |
76 | # Eclipse Gradle plugin generated files
77 | # Eclipse Core
78 | .project
79 | # JDT-specific (Eclipse Java Development Tools)
80 | .classpath
81 |
82 | ### macOS
83 | # General
84 | .DS_Store
85 | .AppleDouble
86 | .LSOverride
87 |
88 | # Icon must end with two \r
89 | Icon
90 |
91 | # Thumbnails
92 | ._*
93 |
94 | # Files that might appear in the root of a volume
95 | .DocumentRevisions-V100
96 | .fseventsd
97 | .Spotlight-V100
98 | .TemporaryItems
99 | .Trashes
100 | .VolumeIcon.icns
101 | .com.apple.timemachine.donotpresent
102 |
103 | # Directories potentially created on remote AFP share
104 | .AppleDB
105 | .AppleDesktop
106 | Network Trash Folder
107 | Temporary Items
108 | .apdisk
109 |
110 |
111 | ### Python
112 | # Byte-compiled / optimized / DLL files
113 | __pycache__/
114 | *.py[cod]
115 | *$py.class
116 |
117 | # Installer logs
118 | pip-log.txt
119 | pip-delete-this-directory.txt
120 |
121 | # pyenv
122 | .python-version
123 |
124 | # Environments
125 | .env
126 | .venv
127 | env/
128 | venv/
129 | ENV/
130 | env.bak/
131 | venv.bak/
132 |
133 | # mkdocs documentation
134 | /site
135 | .cache
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm") version "2.0.21"
3 | id("org.jetbrains.dokka") version "1.9.20" apply false
4 | id("org.owasp.dependencycheck") version "8.2.1" apply false
5 | id("io.gitlab.arturbosch.detekt") version "1.23.0" apply false
6 | id("com.vanniktech.maven.publish") version "0.28.0" apply false
7 | id("com.github.ben-manes.versions") version "0.51.0" apply false
8 | id("ru.vyarus.mkdocs") version "4.0.1"
9 | }
10 |
11 | repositories {
12 | mavenCentral()
13 | }
14 |
15 | mkdocs {
16 | sourcesDir = "."
17 | buildDir = "./build/mkdocs"
18 | updateSiteUrl = true
19 | publish {
20 | branch = "gh-pages"
21 | version = "5.x"
22 | rootRedirect = true
23 | rootRedirectTo = "latest"
24 | setVersionAliases("latest")
25 | generateVersionsFile = true
26 | }
27 | python {
28 | minPythonVersion = "3.12"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/docs/assets/icons/redoc.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/assets/images/redoc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SMILEY4/ktor-openapi-tools/a65a2c92d72654145dfe4a413bf94d52d516b17c/docs/assets/images/redoc.png
--------------------------------------------------------------------------------
/docs/assets/images/swagger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SMILEY4/ktor-openapi-tools/a65a2c92d72654145dfe4a413bf94d52d516b17c/docs/assets/images/swagger.png
--------------------------------------------------------------------------------
/docs/assets/stylesheets/extra.css:
--------------------------------------------------------------------------------
1 | /*Make code in "details" a bit larger for better readability*/
2 | details code {
3 | font-size: 1em !important;
4 | }
5 |
6 | /*Draw a light border around tab panels*/
7 | @media screen and (max-width: 44.9844em) {
8 | .tabbed-set {
9 | border-bottom: 1px solid var(--md-default-fg-color--lightest);
10 | }
11 | }
12 |
13 | @media not screen and (max-width: 44.9844em) {
14 | .tabbed-set {
15 | border: 1px solid var(--md-default-fg-color--lightest);
16 | border-radius: .2rem !important;
17 | padding: 0 .3rem .3rem .3rem !important;
18 | }
19 | }
--------------------------------------------------------------------------------
/docs/examples/authentication.md:
--------------------------------------------------------------------------------
1 | ---
2 | search:
3 | exclude: true
4 | ---
5 |
6 | # Authentication
7 |
8 | An example showing the handling of protected routes.
9 |
10 | ```kotlin title="Authentication.kt" linenums="1"
11 | ---8<--- "Authentication.kt"
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/examples/basics.md:
--------------------------------------------------------------------------------
1 | ---
2 | search:
3 | exclude: true
4 | ---
5 |
6 | # Basic
7 |
8 | This example shows a basic usage of the plugin
9 |
10 | ```kotlin title="Basics.kt" linenums="1"
11 | --8<---- "Basics.kt"
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/examples/complete-configuration.md:
--------------------------------------------------------------------------------
1 | ---
2 | search:
3 | exclude: true
4 | ---
5 |
6 | # Complete Configuration
7 |
8 | This example showcases a *nearly* complete - and mostly nonsensical - plugin configuration
9 |
10 | ```kotlin title="CompleteConfig.kt" linenums="1"
11 | --8<-- "CompleteConfig.kt"
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/examples/customized-schema-generator.md:
--------------------------------------------------------------------------------
1 | ---
2 | search:
3 | exclude: true
4 | ---
5 |
6 | # Schema Generation
7 |
8 | Example showcasing possible configurations for customizing the automatic schema generation.
9 |
10 | ```kotlin linenums="1"
11 | ---8<--- "CustomizedSchemaGenerator.kt"
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/examples/external-openapi-spec.md:
--------------------------------------------------------------------------------
1 | ---
2 | search:
3 | exclude: true
4 | ---
5 |
6 | # External OpenAPI Spec
7 |
8 | Example showcasing the usage of an external OpenAPI specification with Swagger UI and ReDoc.
9 |
10 | ```kotlin linenums="1"
11 | ---8<--- "ExternalOpenApiSpec.kt"
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/examples/file-upload.md:
--------------------------------------------------------------------------------
1 | ---
2 | search:
3 | exclude: true
4 | ---
5 |
6 | # File Upload
7 |
8 | An example showcasing the documentation of file upload routes and multipart bodies.
9 |
10 | ```kotlin linenums="1"
11 | ---8<--- "FileUpload.kt"
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/examples/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | search:
3 | exclude: true
4 | ---
5 |
6 | # Examples
7 |
8 | Runnable examples showcasing `ktor-openapi`, `ktor-swagger-ui` and `ktor-redoc` functionalities.
--------------------------------------------------------------------------------
/docs/examples/kotlinx-serialization.md:
--------------------------------------------------------------------------------
1 | ---
2 | search:
3 | exclude: true
4 | ---
5 |
6 | # Kotlinx Serialization
7 |
8 | Example focusing on the usage of [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) with OpenAPI specification generation.
9 |
10 | ```kotlin linenums="1"
11 | ---8<--- "KotlinxSerialization.kt"
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/examples/multiple-specs.md:
--------------------------------------------------------------------------------
1 | ---
2 | search:
3 | exclude: true
4 | ---
5 |
6 | # Multiple Specs
7 |
8 | An example showing the setup with multiple OpenAPI specification in a single application.
9 |
10 | ```kotlin linenums="1"
11 | ---8<--- "MultipleSpecs.kt"
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/examples/openapi-examples.md:
--------------------------------------------------------------------------------
1 | ---
2 | search:
3 | exclude: true
4 | ---
5 |
6 | # Examples
7 |
8 | An example showing different ways to handle examples.
9 |
10 | ```kotlin linenums="1"
11 | ---8<--- "Examples.kt"
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/examples/petstore.md:
--------------------------------------------------------------------------------
1 | ---
2 | search:
3 | exclude: true
4 | ---
5 |
6 | # Petstore
7 |
8 | An implementation of the "[Petstore](https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v2.0/json/petstore-simple.json)" sample project.
9 |
10 | ```kotlin linenums="1"
11 | ---8<--- "Petstore.kt"
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/examples/request-response.md:
--------------------------------------------------------------------------------
1 | ---
2 | search:
3 | exclude: true
4 | ---
5 |
6 | # Requests and Responses
7 |
8 | An example focusing on documenting requests and responses.
9 |
10 | ```kotlin linenums="1"
11 | ---8<--- "RequestResponse.kt"
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/examples/schemas.md:
--------------------------------------------------------------------------------
1 | ---
2 | search:
3 | exclude: true
4 | ---
5 |
6 | # Schemas
7 |
8 | An example showing different ways to handle schemas.
9 |
10 | ```kotlin linenums="1"
11 | ---8<--- "Schemas.kt"
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/examples/webhooks.md:
--------------------------------------------------------------------------------
1 | ---
2 | search:
3 | exclude: true
4 | ---
5 |
6 | # Webhooks
7 |
8 | This example showcases the documentation of webhooks.
9 |
10 | ```kotlin title="Webhooks.kt" linenums="1"
11 | --8<---- "Webhooks.kt"
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | hide:
3 | - title
4 | - navigation
5 | - toc
6 | - footer
7 | ---
8 |
9 | # Ktor OpenAPI Tools
10 |
11 | A collection of libraries to simplify API documentation and exploration for [Ktor](https://ktor.io/) applications.
12 | Designed to be non-invasive, they integrate seamlessly with applications without requiring immediate change to existing code while being highly customizable to fit every use case.
13 |
14 |
15 |
16 | - :simple-openapiinitiative:{ .lg .middle } __OpenAPI__
17 |
18 | ---
19 |
20 | `ktor-openapi`
21 |
22 | Add documentation to Ktor routes and automatically generate [OpenAPI](https://www.openapis.org/) specifications.
23 |
24 | [:octicons-arrow-right-24: Get Started](openapi/index.md)
25 |
26 |
27 | - :simple-swagger:{ .lg .middle } __Swagger UI__
28 |
29 | ---
30 |
31 | `ktor-swagger-ui`
32 |
33 | Serve an interactive [Swagger UI](https://swagger.io/tools/swagger-ui/) for easy API exploration and testing
34 |
35 | [:octicons-arrow-right-24: Get Started](swaggerui/index.md)
36 |
37 |
38 | - :redoc:{ .lg .middle } __Redoc__
39 |
40 | ---
41 |
42 | `ktor-redoc`
43 |
44 | Serve an interactive [ReDoc](https://github.com/Redocly/redoc) page for easy API exploration and testing
45 |
46 | [:octicons-arrow-right-24: Get Started](redoc/index.md)
47 |
48 |
--------------------------------------------------------------------------------
/docs/openapi/example_encoding.md:
--------------------------------------------------------------------------------
1 | # Example Encoding
2 |
3 | Example objects are automatically serialized (as json) and added to the OpenAPI specification.
4 | The encoding of example values can be customized in the plugin configuration.
5 | If no encoder is specified, the example value will be encoded by swagger.
6 |
7 | ## Pre-Defined Example Encoders
8 |
9 | === "Swagger"
10 | Explicitly use the internal swagger example encoder.
11 |
12 | ```kotlin
13 | install(OpenApi) {
14 | examples {
15 | encoder = ExampleEncoder.internal()
16 | }
17 | }
18 | ```
19 |
20 | ??? info "More Information"
21 |
22 | [:octicons-arrow-right-24: API Reference](../dokka/ktor-openapi/ktor-openapi/io.github.smiley4.ktoropenapi.config/-example-encoder/internal.html)
23 |
24 | === "Kotlinx.Serialization"
25 | Use [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) to encode example values.
26 |
27 | ```kotlin
28 | install(OpenApi) {
29 | examples {
30 | encoder = ExampleEncoder.kotlinx()
31 | }
32 | }
33 | ```
34 |
35 | ??? info "More Information"
36 |
37 | [:octicons-arrow-right-24: API Reference](../dokka/ktor-openapi/ktor-openapi/io.github.smiley4.ktoropenapi.config/-example-encoder/kotlinx.html)
38 |
39 |
40 | ## Custom Example Encoder
41 |
42 | The example encoder can be completely replaced by an own implementation.
43 |
44 | ```kotlin
45 | install(OpenApi) {
46 | examples {
47 | encoder { type, example -> //(1)!
48 | TOOD() //(2)!
49 | }
50 | }
51 | }
52 | ```
53 |
54 | 1. Input of the example encoding function is a `io.github.smiley4.ktoropenapi.config.TypeDescriptor` with information about the type/schema and the actual value of the example.
55 | 2. Encode/Transform the example value and return the result.
56 |
57 | ???+ example "Custom "toString()" Example Encoder"
58 |
59 | ```kotlin
60 | install(OpenApi) {
61 | examples {
62 | encoder { type, example ->
63 | example.toString() //(1)!
64 | }
65 | }
66 | }
67 | ```
68 |
69 | 1. Always encode and embed the example value as a raw string.
--------------------------------------------------------------------------------
/docs/openapi/getting_started.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | ## Add Dependency
4 |
5 | To generate OpenAPI specifications, you need to include the `ktor-openapi` artifact in the build script.
6 | All artifacts are published to Maven Central.
7 |
8 | === "Gradle (Kotlin)"
9 | ```kotlin
10 | implementation("io.github.smiley4:ktor-openapi:$version")
11 | ```
12 |
13 | === "Gradle"
14 | ```groovy
15 | implementation 'io.github.smiley4:ktor-openapi:$version'
16 | ```
17 |
18 | === "Maven"
19 | ```xml
20 |
21 | io.github.smiley4
22 | ktor-openapi
23 | ${version}
24 |
25 | ```
26 |
27 | ??? tip "Ktor Compatibility and Previous Versions"
28 |
29 | This project as been split into multiple projects starting with version 5.0.Versions up to 5.0 are called `ktor-swagger-ui` instead of `ktor-openapi`.
30 |
31 | | Ktor | Plugin Version | Project Name |
32 | |------|----------------|-------------------|
33 | | 2.x | up to 3.x | `ktor-swagger-ui` |
34 | | 3.x | 4.x | `ktor-swagger-ui` |
35 | | 3.x | 5.x | `ktor-openapi` |
36 |
37 | ## Install OpenAPI
38 |
39 | ```kotlin
40 | install(OpenApi) { //(1)!
41 | //...(2)
42 | }
43 | ```
44 |
45 | 1. Install the "OpenAPI" plugin to the application.
46 | 2. Add additional plugin configuration here.
47 |
48 | ??? info "More Information"
49 |
50 | [:octicons-arrow-right-24: Plugin Configuration](plugin_configuration.md)
51 |
52 | [:octicons-arrow-right-24: API Reference](../dokka/ktor-openapi/ktor-openapi/io.github.smiley4.ktoropenapi.config/-open-api-plugin-config/index.html)
53 |
54 |
55 | ## Exposing OpenAPI Specification
56 |
57 | ```kotlin
58 | routing {
59 | route("api.json") { //(1)!
60 | openApi() //(2)!
61 | }
62 | }
63 | ```
64 |
65 | 1. Create a new route to expose the OpenAPI specification file at `api.json`.
66 | 2. Expose the OpenAPI specification.
67 |
68 | ??? info "More Information"
69 |
70 | [:octicons-arrow-right-24: Multiple OpenAPI Specifications](multiple_specs.md)
71 |
72 | [:octicons-arrow-right-24: API Reference](../dokka/ktor-openapi/ktor-openapi/io.github.smiley4.ktoropenapi/open-api.html)
73 |
74 |
75 | ## Documenting Routes
76 |
77 | ```kotlin
78 | import io.github.smiley4.ktoropenapi.get //(1)!
79 |
80 | get("hello", { //(2)!
81 | description = "A Hello-World route" //(3)!
82 | response {
83 | HttpStatusCode.OK to { //(4)!
84 | description = "A success response"
85 | body() //(5)!
86 | }
87 | }
88 | //...
89 | }) {
90 | call.respondText("Hello World!") //(6)!
91 | }
92 | ```
93 |
94 | 1. Replace `io.ktor.server.routing.get` with `io.github.smiley4.ktoropenapi.get`. Same for other http methods.
95 | 2. Enrich `/hello` route with additional information.
96 | 3. Add a description to the route.
97 | 4. Document the different possible responses.
98 | 5. Specify the response body type. The schema for the type is generated automatically.
99 | 6. Handle requests as usual.
100 |
101 | ??? info "More Information"
102 |
103 | [:octicons-arrow-right-24: Documenting Routes](documenting_routes.md)
104 |
105 | [:octicons-arrow-right-24: Documentation with Type-safe Routing](typesafe_routing.md)
106 |
107 | [:octicons-arrow-right-24: API Reference](../dokka/ktor-openapi/ktor-openapi/io.github.smiley4.ktoropenapi.config/-route-config/index.html)
108 |
--------------------------------------------------------------------------------
/docs/openapi/index.md:
--------------------------------------------------------------------------------
1 | # OpenAPI
2 |
3 | [Ktor](https://ktor.io/) plugin to automatically generate [OpenAPI](https://www.openapis.org/) specifications from routes. Additional information can be gradually added to existing routes without requiring major changes to existing code.
4 |
5 |
6 | ## Features
7 |
8 | - Extends existing Ktor DSL
9 | - No immediate change to code required
10 | - Support for [Type-safe routing](https://ktor.io/docs/server-resources.html) / Resources plugin
11 | - Document webhooks and (limited) options for server-sent events
12 | - Covers (almost) complete [OpenAPI 3.1.0 Specification](https://swagger.io/specification/)
13 | - Automatically generates json schemas from kotlin types
14 | - Out-of-the-box support for type parameters, inheritance, collections, etc
15 | - Usable with reflection or [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization)
16 | - Supports [Jackson](https://github.com/FasterXML/jackson), [Swagger](https://github.com/swagger-api/swagger-core), [Javax](https://mvnrepository.com/artifact/javax.validation/validation-api)
17 | and [Jakarta](https://github.com/jakartaee/validation/tree/main) annotations
18 | - Highly configurable and customizable
19 |
20 |
21 | ## Example
22 |
23 | ```kotlin
24 | install(OpenApi) //(1)!
25 |
26 | routing {
27 |
28 | route("api.json") {
29 | openApi() //(2)!
30 | }
31 |
32 | get("example", {
33 | description = "An example route" //(3)!
34 | response {
35 | HttpStatusCode.OK to {
36 | description = "A success response"
37 | body()
38 | }
39 | }
40 | }) {
41 | call.respondText("Hello World!") //(4)!
42 | }
43 | }
44 | ```
45 |
46 | 1. Install and configure the OpenAPI plugin.
47 | 2. Create a route to expose the OpenAPI specification file at `/api.json`.
48 | 3. Add (optional) information to the route, e.g. a description and responses and response bodies.
49 | 4. Handle requests as usual.
--------------------------------------------------------------------------------
/docs/openapi/plugin_configuration.md:
--------------------------------------------------------------------------------
1 | # Plugin Configuration
2 |
3 | ???+ info "More Information"
4 |
5 | The complete list of all available plugin configuration options can be viewed in the [API Reference](../dokka/ktor-openapi/ktor-openapi/io.github.smiley4.ktoropenapi.config/-open-api-plugin-config/index.html).
6 |
7 |
8 | ## Automatically Assigning Tags
9 |
10 | Tags can either be assigned at the route documentation directly or via a mapping function in the plugin configuration.
11 | The specified function is called for each route and the returned tags are added to the route's documentation.
12 |
13 | ```kotlin
14 | install(OpenApi) {
15 | tags {
16 | tagGenerator = { url -> TODO() }
17 | }
18 | }
19 | ```
20 |
21 |
22 | ## Ignoring Route Selectors
23 |
24 | Other Ktor plugins may add additional route selectors to routes that show up the OpenAPI specification as unwanted parts in the urls.
25 | These can be ignored by adding them to the `ignoredRouteSelectors` or `ignoredRouteSelectorClassNames` configuration.
26 |
27 | ???+ example "Ignoring Route Selector"
28 |
29 | ```kotlin
30 | install(OpenApi) {
31 | tags {
32 | ignoredRouteSelectors = ignoredRouteSelectors + RateLimitRouteSelector::class//(1)!
33 | ignoredRouteSelectorClassNames = ignoredRouteSelectorClassNames + "io.ktor.server.plugins.ratelimit.RateLimitRouteSelector"//(2)!
34 | }
35 | }
36 | ```
37 |
38 | 1. Ignore the route selector from the Ktor [Rate Limiting Plugin](https://ktor.io/docs/server-rate-limit.html) by adding its class to the ignored selectors.
39 | 2. Ignore the route selector from the Ktor [Rate Limiting Plugin](https://ktor.io/docs/server-rate-limit.html) by adding its qualified name to the ignored selector names. This can be useful when the actual class is internal and cannot be accessed.
40 |
41 | ## Excluding Routes
42 |
43 | Routes can be completely excluded from the generated OpenAPI specification via the `hidden`-flag at the route documentation or the `pathFilter`-function in the plugin configuration.
44 |
45 | ???+ example "Excluding Routes"
46 |
47 | === "Route Documentation"
48 | ```kotlin
49 | routing {
50 | get({
51 | hidden = true //(1)!
52 | }) {
53 | call.respond(HttpStatusCode.NotImplemented, Unit)
54 | }
55 | }
56 | ```
57 |
58 | 1. Mark this route as "hidden" and exclude it from the generated OpenAPI specification.
59 |
60 | === "Plugin Configuration"
61 |
62 | ```kotlin
63 | install(OpenApi) {
64 | pathFilter = { method, url -> url.firstOrNull() != "internal" }//(1)!
65 | }
66 | ```
67 |
68 | 1. Filter out and exclude all routes with urls starting with "internal". The function receives the http method and url as inputs and returns `false` for excluded routes.
69 |
70 | ## Choosing the output format - JSON or YAML
71 |
72 | The output format of the generated OpenAPI specification can be changed from `json` to `yaml` with the `outputFormat` option.
73 |
74 | ???+ example "Choosing the Output Format"
75 |
76 | === "JSON"
77 | ```kotlin
78 | install(OpenApi) {
79 | outputFormat = OutputFormat.JSON
80 | }
81 | ```
82 |
83 | === "YAML"
84 |
85 | ```kotlin
86 | install(OpenApi) {
87 | outputFormat = OutputFormat.YAML
88 | }
89 | ```
90 |
91 | ## Customizing Schemas
92 |
93 | [:octicons-arrow-right-24: Schema Generation](schema_generation.md)
94 |
95 | ## Customizing Examples
96 |
97 | [:octicons-arrow-right-24: Example Encoding](example_encoding.md)
98 |
99 | ## Configuring Multiple OpenAPI Specifications
100 |
101 | [:octicons-arrow-right-24: Multiple API Specifications](multiple_specs.md)
102 |
--------------------------------------------------------------------------------
/docs/openapi/typesafe_routing.md:
--------------------------------------------------------------------------------
1 | # Type-safe Routing
2 |
3 | This project supports documenting Ktor routes defined using [Type-safe Routing](https://ktor.io/docs/server-resources.html).
4 |
5 | ???+ example "Documenting Routes"
6 |
7 | ```kotlin
8 | @Resource("{id}")
9 | class Entity( //(1)!
10 | val id: String
11 | )
12 | ```
13 |
14 | 1. Define resources classes as usual.
15 |
16 | ```kotlin
17 | import io.github.smiley4.ktoropenapi.resources.get //(1)!
18 |
19 | routing {
20 | get({ //(2)!
21 | description = "Returns the entity with the requested id."
22 | }) {
23 | call.respond(HttpStatusCode.NotImplemented, Unit) //(3)!
24 | }
25 | }
26 | ```
27 |
28 | 1. Replace `io.ktor.server.resources.get` with `io.github.smiley4.ktoropenapi.resources.get`. Same for other http methods.
29 | 2. Define the route using the resource class and add additional documentation.
30 | 3. Handle requests as usual.
31 |
32 | ??? info "More Information"
33 |
34 | See [Documenting Routes](documenting_routes.md) for more general information.
35 |
36 | ??? tip "Extracting Information from Resource Classes"
37 |
38 | When using type-safe routing, information about the routes can be extracted from the resources classes.
39 | This includes available query and path parameters with their names, types and whether they are optional.
40 | All other information still needs to be added manually and at the routes.
41 |
42 | To enable automatic extraction, set the `autoDocumentResourcesRoutes` option in the plugin configuration.
43 |
44 | ```kotlin
45 | install(OpenApi) {
46 | autoDocumentResourcesRoutes = true
47 | }
48 | ```
49 |
50 | ??? warning "Schema Generation"
51 |
52 | When using Type-safe routing and `autoDocumentResourcesRoutes` is enabled, schemas must be generated using **kotlinx.serialization**.
53 | See [Schema Generation](schema_generation.md) for more information on how to change the default generator.
54 |
55 | ```kotlin
56 | install(OpenApi) {
57 | // enable automatically extracting documentation from resources-routes
58 | autoDocumentResourcesRoutes = true
59 | // schema-generator must use kotlinx-serialization to be compatible
60 | schemas {
61 | generator = SchemaGenerator.kotlinx()
62 | }
63 | }
64 | ```
65 |
66 |
--------------------------------------------------------------------------------
/docs/redoc/getting_started.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | ## Add Dependency
4 |
5 | To serve ReDoc, you need to include the `ktor-redoc` artifact in the build script.
6 | All artifacts are published to Maven Central.
7 |
8 | === "Gradle (Kotlin)"
9 | ```kotlin
10 | implementation("io.github.smiley4:ktor-redoc:$version")
11 | ```
12 |
13 | === "Gradle"
14 | ```groovy
15 | implementation 'io.github.smiley4:ktor-redoc:$version'
16 | ```
17 |
18 | === "Maven"
19 | ```xml
20 |
21 | io.github.smiley4
22 | ktor-redoc
23 | ${version}
24 |
25 | ```
26 |
27 | ## Usage
28 |
29 | ```kotlin
30 | routing {
31 |
32 | route("redoc") { //(1)!
33 | redoc("/api.json") { //(2)!
34 | //...(3)
35 | }
36 | }
37 |
38 | }
39 | ```
40 |
41 | 1. Specify route to serve ReDoc at `/redoc`.
42 | 2. Expose ReDoc showing the OpenAPI specification at `/api.json`. The url can be relative pointing to specification provided by this application or absolute pointing to an external resource.
43 | 3. Add configuration for this ReDoc "instance" here.
44 |
45 | ??? tip "Using ReDoc with [auto-generated](../openapi/index.md) OpenAPI specification"
46 |
47 | ```kotlin
48 | routing {
49 |
50 | route("api.json") {
51 | openApi() //(1)!
52 | }
53 | route("redoc") {
54 | redoc("/api.json") //(2)!
55 | }
56 |
57 | }
58 | ```
59 |
60 | 1. Serve auto-generated OpenAPI specification at `/api.json`.
61 | 2. Expose ReDoc using auto-generated specification at `/api.json`.
62 |
63 | ??? info "Configuration Options"
64 |
65 | For more information on available configuration options, please see the [api reference](../dokka/ktor-redoc/ktor-redoc/io.github.smiley4.ktorredoc.config/-redoc-config/index.html).
--------------------------------------------------------------------------------
/docs/redoc/index.md:
--------------------------------------------------------------------------------
1 | # ReDoc
2 |
3 | Library for [Ktor](https://ktor.io/) applications to serve [ReDoc](https://github.com/Redocly/redoc) - visualize and interact with generated OpenAPI specifications.
4 |
5 | ## Features
6 |
7 | - Explore and interact with OpenAPI specifications generated by [`ktor-openapi`](../openapi/index.md) or external specifications
8 | - Serve bundled ReDoc page
9 | - Expose multiple "instances" of ReDoc (e.g. for different OpenAPI specifications)
10 | - All ReDoc configuration options available
11 |
12 | ## Example
13 |
14 | ```kotlin
15 | routing {
16 |
17 | route("redoc") { //(1)!
18 | redoc("/api.json") { //(2)!
19 | //...(3)
20 | }
21 | }
22 |
23 | }
24 | ```
25 |
26 | 1. Specify route to serve ReDoc at `/redoc`.
27 | 2. Expose ReDoc showing OpenAPI specification at `/api.json`. Path can be relative pointing to specification provided by this application or absolute pointing to an external resource.
28 | 3. Add configuration for this ReDoc "instance" here.
--------------------------------------------------------------------------------
/docs/swaggerui/getting_started.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | ## Add Dependency
4 |
5 | To serve Swagger UI, you need to include the `ktor-swagger-ui` artifact in the build script.
6 | All artifacts are published to Maven Central.
7 |
8 | === "Gradle (Kotlin)"
9 | ```kotlin
10 | implementation("io.github.smiley4:ktor-swagger-ui:$version")
11 | ```
12 |
13 | === "Gradle"
14 | ```groovy
15 | implementation 'io.github.smiley4:ktor-swagger-ui:$version'
16 | ```
17 |
18 | === "Maven"
19 | ```xml
20 |
21 | io.github.smiley4
22 | ktor-swagger-ui
23 | ${version}
24 |
25 | ```
26 |
27 | ## Usage
28 |
29 | ```kotlin
30 | routing {
31 |
32 | route("swagger") { //(1)!
33 | swaggerUI("/api.json") { //(2)!
34 | //...(3)
35 | }
36 | }
37 |
38 | }
39 | ```
40 |
41 | 1. Specify route to serve Swagger UI at `/swagger`.
42 | 2. Expose Swagger UI showing OpenAPI specification at `/api.json`. The url can be relative pointing to specification provided by this application or absolute pointing to an external resource. Specify a map of urls to interact with multiple different specifications in the same Swagger UI "instance".
43 | 3. Add configuration for this Swagger UI "instance" here.
44 |
45 | ??? tip "Using Swagger UI with [auto-generated](../openapi/index.md) OpenAPI specification"
46 |
47 | ```kotlin
48 | routing {
49 |
50 | route("api.json") {
51 | openApi() //(1)!
52 | }
53 | route("swagger") {
54 | swaggerUI("/api.json") //(2)!
55 | }
56 |
57 | }
58 | ```
59 |
60 | 1. Serve auto-generated OpenAPI specification at `/api.json`.
61 | 2. Expose Swagger UI using auto-generated specification at `/api.json`.
62 |
63 |
64 | ??? info "Configuration Options"
65 |
66 | For more information on available configuration options, please see the [api reference](../dokka/ktor-swagger-ui/ktor-swagger-ui/io.github.smiley4.ktorswaggerui.config/-swagger-u-i-config/index.html).
--------------------------------------------------------------------------------
/docs/swaggerui/index.md:
--------------------------------------------------------------------------------
1 | # Swagger UI
2 |
3 | Library for [Ktor](https://ktor.io/) applications to serve [Swagger UI](https://swagger.io/tools/swagger-ui/) - visualize and interact with generated OpenAPI specifications.
4 |
5 | ## Features
6 |
7 | - Explore and interact with OpenAPI specifications generated by [`ktor-openapi`](../openapi/index.md) or external specifications
8 | - Serve bundled Swagger UI
9 | - Expose multiple "instances" of Swagger UI (e.g. for different OpenAPI specifications)
10 | - All Swagger UI configuration options available
11 |
12 | ## Example
13 |
14 | ```kotlin
15 | routing {
16 |
17 | route("swagger") { //(1)!
18 | swaggerUI("/api.json") { //(2)!
19 | //...(3)
20 | }
21 | }
22 |
23 | }
24 | ```
25 |
26 | 1. Specify route to serve Swagger UI at `/swagger`.
27 | 2. Expose Swagger UI using OpenAPI specification at `/api.json`. Path can be relative pointing to specification provided by this application or absolute pointing to an external resource.
28 | 3. Add configuration for this Swagger UI "instance" here.
--------------------------------------------------------------------------------
/examples/build.gradle.kts:
--------------------------------------------------------------------------------
1 | val projectGroupId: String by project
2 | val projectVersion: String by project
3 | group = projectGroupId
4 | version = projectVersion
5 |
6 | plugins {
7 | kotlin("jvm")
8 | kotlin("plugin.serialization") version "2.0.21"
9 | }
10 |
11 | repositories {
12 | mavenCentral()
13 | }
14 |
15 | dependencies {
16 | implementation(project(":ktor-openapi"))
17 | implementation(project(":ktor-swagger-ui"))
18 | implementation(project(":ktor-redoc"))
19 |
20 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
21 |
22 | val versionKtor: String by project
23 | implementation("io.ktor:ktor-server-netty-jvm:$versionKtor")
24 | implementation("io.ktor:ktor-server-content-negotiation:$versionKtor")
25 | implementation("io.ktor:ktor-serialization-jackson:$versionKtor")
26 | implementation("io.ktor:ktor-serialization-kotlinx-json:$versionKtor")
27 |
28 |
29 | implementation("io.ktor:ktor-server-auth:$versionKtor")
30 | implementation("io.ktor:ktor-server-call-logging:$versionKtor")
31 | implementation("io.ktor:ktor-server-test-host:$versionKtor")
32 | implementation("io.ktor:ktor-server-resources:$versionKtor")
33 |
34 | val versionSchemaKenerator: String by project
35 | implementation("io.github.smiley4:schema-kenerator-core:$versionSchemaKenerator")
36 | implementation("io.github.smiley4:schema-kenerator-reflection:$versionSchemaKenerator")
37 | implementation("io.github.smiley4:schema-kenerator-serialization:$versionSchemaKenerator")
38 | implementation("io.github.smiley4:schema-kenerator-swagger:$versionSchemaKenerator")
39 | implementation("io.github.smiley4:schema-kenerator-jackson:$versionSchemaKenerator")
40 |
41 | val versionSwaggerParser: String by project
42 | implementation("io.swagger.parser.v3:swagger-parser:$versionSwaggerParser")
43 |
44 | val versionKotlinLogging: String by project
45 | implementation("io.github.oshai:kotlin-logging-jvm:$versionKotlinLogging")
46 |
47 | val versionLogback: String by project
48 | implementation("ch.qos.logback:logback-classic:$versionLogback")
49 | }
50 |
51 | kotlin {
52 | jvmToolchain(11)
53 | }
54 |
--------------------------------------------------------------------------------
/examples/src/main/kotlin/io/github/smiley4/ktoropenapi/examples/Basics.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.examples
2 |
3 | import io.github.smiley4.ktoropenapi.OpenApi
4 | import io.github.smiley4.ktoropenapi.config.OutputFormat
5 | import io.github.smiley4.ktoropenapi.get
6 | import io.github.smiley4.ktoropenapi.openApi
7 | import io.github.smiley4.ktorredoc.redoc
8 | import io.github.smiley4.ktorswaggerui.swaggerUI
9 | import io.ktor.http.HttpStatusCode
10 | import io.ktor.server.application.Application
11 | import io.ktor.server.application.install
12 | import io.ktor.server.engine.embeddedServer
13 | import io.ktor.server.netty.Netty
14 | import io.ktor.server.response.respondText
15 | import io.ktor.server.routing.route
16 | import io.ktor.server.routing.routing
17 |
18 | fun main() {
19 | embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true)
20 | }
21 |
22 | private fun Application.myModule() {
23 |
24 | // Install and configure the "OpenApi" Plugin
25 | install(OpenApi) {
26 |
27 | outputFormat = OutputFormat.JSON
28 |
29 | // configure basic information about the api
30 | info {
31 | title = "Example API"
32 | description = "An example api to showcase basic swagger-ui functionality."
33 | }
34 | // provide a reference to an external documentation
35 | externalDocs {
36 | url = "https://github.com/SMILEY4/ktor-swagger-ui/wiki"
37 | description = "Sample external documentation"
38 | }
39 | // configure the servers from where the api is being served
40 | server {
41 | url = "http://localhost:8080"
42 | description = "Development Server"
43 | }
44 | server {
45 | url = "https://www.example.com"
46 | description = "Production Server"
47 | }
48 | }
49 |
50 | routing {
51 |
52 | // Create a route for the openapi spec file.
53 | // This route will not be included in the spec.
54 | route("api.json") {
55 | openApi()
56 | }
57 | // Create a route for the Swagger UI using the openapi spec at "/api.json".
58 | // This route will not be included in the spec.
59 | route("swagger") {
60 | swaggerUI("/api.json")
61 | }
62 | // Create a route for redoc using the OpenAPI spec at "/api.json".
63 | // This route will not be included in the spec.
64 | route("redoc") {
65 | redoc("/api.json")
66 | }
67 |
68 | // a documented route
69 | get("hello", {
70 | // description of the route
71 | description = "A Hello-World route"
72 | // information about the request
73 | request {
74 | // information about the query parameter "name" of type "string"
75 | queryParameter("name") {
76 | description = "the name to greet"
77 | }
78 | }
79 | // information about possible responses
80 | response {
81 | // information about a "200 OK" response
82 | code(HttpStatusCode.OK) {
83 | // a description of the response
84 | description = "successful request - always returns 'Hello World!'"
85 | }
86 | }
87 | }) {
88 | call.respondText("Hello ${call.request.queryParameters["name"]}")
89 | }
90 |
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/examples/src/main/kotlin/io/github/smiley4/ktoropenapi/examples/CustomizedSchemaGenerator.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.examples
2 |
3 | import io.github.smiley4.ktoropenapi.OpenApi
4 | import io.github.smiley4.ktoropenapi.config.SchemaGenerator
5 | import io.github.smiley4.ktoropenapi.get
6 | import io.github.smiley4.ktoropenapi.openApi
7 | import io.github.smiley4.ktorredoc.redoc
8 | import io.github.smiley4.ktorswaggerui.swaggerUI
9 | import io.github.smiley4.schemakenerator.serialization.SerializationSteps.analyzeTypeUsingKotlinxSerialization
10 | import io.github.smiley4.schemakenerator.swagger.SwaggerSteps.RequiredHandling
11 | import io.github.smiley4.schemakenerator.swagger.SwaggerSteps.compileReferencingRoot
12 | import io.github.smiley4.schemakenerator.swagger.SwaggerSteps.generateSwaggerSchema
13 | import io.github.smiley4.schemakenerator.swagger.SwaggerSteps.withTitle
14 | import io.github.smiley4.schemakenerator.swagger.data.TitleType
15 | import io.ktor.http.HttpStatusCode
16 | import io.ktor.server.application.Application
17 | import io.ktor.server.application.install
18 | import io.ktor.server.engine.embeddedServer
19 | import io.ktor.server.netty.Netty
20 | import io.ktor.server.response.respond
21 | import io.ktor.server.routing.route
22 | import io.ktor.server.routing.routing
23 | import kotlinx.serialization.Serializable
24 |
25 | fun main() {
26 | embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true)
27 | }
28 |
29 | private fun Application.myModule() {
30 |
31 | // Install and configure the OpenApi plugin
32 | install(OpenApi) {
33 | schemas {
34 | // replace default schema-generator with configurable pre-defined generator, or ...
35 | generator = SchemaGenerator.kotlinx {
36 | nullables = RequiredHandling.NON_REQUIRED
37 | optionals = RequiredHandling.REQUIRED
38 | title = TitleType.SIMPLE
39 | explicitNullTypes = false
40 | }
41 | // ... replace default schema-generator with completely custom generator
42 | generator = { type ->
43 | type
44 | .analyzeTypeUsingKotlinxSerialization()
45 | .generateSwaggerSchema {
46 | nullables = RequiredHandling.NON_REQUIRED
47 | optionals = RequiredHandling.REQUIRED
48 | }
49 | .withTitle(TitleType.SIMPLE)
50 | .compileReferencingRoot(
51 | explicitNullTypes = false
52 | )
53 | }
54 | }
55 | }
56 |
57 | routing {
58 |
59 | // add the routes for the OpenAPI spec, Swagger UI and ReDoc
60 | route("swagger") {
61 | swaggerUI("/api.json")
62 | }
63 | route("api.json") {
64 | openApi()
65 | }
66 | route("redoc") {
67 | redoc("/api.json")
68 | }
69 |
70 | // a documented route
71 | get("hello", {
72 | // information about the request
73 | response {
74 | // information about a "200 OK" response
75 | code(HttpStatusCode.OK) {
76 | // body of the response
77 | body()
78 | }
79 | }
80 | }) {
81 | call.respond(HttpStatusCode.NotImplemented, "...")
82 | }
83 |
84 | }
85 |
86 | }
87 |
88 | @Serializable
89 | private class MyResponseBody(
90 | val name: String,
91 | )
92 |
--------------------------------------------------------------------------------
/examples/src/main/kotlin/io/github/smiley4/ktoropenapi/examples/ExternalOpenApiSpec.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.examples
2 |
3 | import io.github.smiley4.ktorredoc.redoc
4 | import io.github.smiley4.ktorswaggerui.swaggerUI
5 | import io.ktor.server.application.Application
6 | import io.ktor.server.engine.embeddedServer
7 | import io.ktor.server.netty.Netty
8 | import io.ktor.server.routing.route
9 | import io.ktor.server.routing.routing
10 |
11 | fun main() {
12 | embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true)
13 | }
14 |
15 | private fun Application.myModule() {
16 |
17 | routing {
18 |
19 | // Create a route for the swagger ui using an external OpenAPI spec.
20 | route("swagger") {
21 | swaggerUI("https://petstore3.swagger.io/api/v3/openapi.json")
22 | }
23 |
24 | // Create a route for Redoc using an external OpenAPI spec.
25 | route("redoc") {
26 | redoc("https://petstore3.swagger.io/api/v3/openapi.json")
27 | }
28 |
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/examples/src/main/kotlin/io/github/smiley4/ktoropenapi/examples/FileUpload.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.examples
2 |
3 | import io.github.smiley4.ktoropenapi.OpenApi
4 | import io.github.smiley4.ktoropenapi.config.SchemaGenerator
5 | import io.github.smiley4.ktoropenapi.post
6 | import io.github.smiley4.ktoropenapi.openApi
7 | import io.github.smiley4.ktorredoc.redoc
8 | import io.github.smiley4.ktorswaggerui.swaggerUI
9 | import io.ktor.http.ContentType
10 | import io.ktor.http.HttpStatusCode
11 | import io.ktor.server.application.Application
12 | import io.ktor.server.application.install
13 | import io.ktor.server.engine.embeddedServer
14 | import io.ktor.server.netty.Netty
15 | import io.ktor.server.response.respond
16 | import io.ktor.server.routing.route
17 | import io.ktor.server.routing.routing
18 | import java.io.File
19 |
20 | fun main() {
21 | embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true)
22 | }
23 |
24 | private fun Application.myModule() {
25 |
26 | // Install the OpenAPI plugin and use the default configuration
27 | install(OpenApi) {
28 | schemas {
29 | // overwrite type "File" with custom schema for binary data
30 | generator = SchemaGenerator.reflection {
31 | overwrite(SchemaGenerator.TypeOverwrites.File())
32 | }
33 | }
34 | }
35 |
36 | routing {
37 |
38 | // add the routes for the OpenAPI spec, Swagger UI and ReDoc
39 | route("swagger") {
40 | swaggerUI("/api.json")
41 | }
42 | route("api.json") {
43 | openApi()
44 | }
45 | route("redoc") {
46 | redoc("/api.json")
47 | }
48 |
49 | // upload a single file, either as png, jpeg or svg
50 | post("single", {
51 | request {
52 | body {
53 | mediaTypes(
54 | ContentType.Image.PNG,
55 | ContentType.Image.JPEG,
56 | ContentType.Image.SVG,
57 | )
58 | }
59 | }
60 | }) {
61 | call.respond(HttpStatusCode.NotImplemented, "...")
62 | }
63 |
64 | // upload multiple files
65 | post("multipart", {
66 | request {
67 | multipartBody {
68 | mediaTypes(ContentType.MultiPart.FormData)
69 | part("first-image") {
70 | mediaTypes(
71 | ContentType.Image.PNG,
72 | ContentType.Image.JPEG,
73 | ContentType.Image.SVG
74 | )
75 | }
76 | part("second-image") {
77 | mediaTypes(
78 | ContentType.Image.PNG,
79 | ContentType.Image.JPEG,
80 | ContentType.Image.SVG
81 | )
82 | }
83 | }
84 | }
85 | }) {
86 | call.respond(HttpStatusCode.NotImplemented, "...")
87 | }
88 |
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/examples/src/main/kotlin/io/github/smiley4/ktoropenapi/examples/KotlinxSerialization.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalSerializationApi::class)
2 |
3 | package io.github.smiley4.ktoropenapi.examples
4 |
5 | import io.github.smiley4.ktoropenapi.OpenApi
6 | import io.github.smiley4.ktoropenapi.config.ExampleEncoder
7 | import io.github.smiley4.ktoropenapi.config.SchemaGenerator
8 | import io.github.smiley4.ktoropenapi.get
9 | import io.github.smiley4.ktoropenapi.openApi
10 | import io.github.smiley4.ktorredoc.redoc
11 | import io.github.smiley4.ktorswaggerui.swaggerUI
12 | import io.ktor.http.HttpStatusCode
13 | import io.ktor.server.application.Application
14 | import io.ktor.server.application.install
15 | import io.ktor.server.engine.embeddedServer
16 | import io.ktor.server.netty.Netty
17 | import io.ktor.server.response.respondText
18 | import io.ktor.server.routing.route
19 | import io.ktor.server.routing.routing
20 | import kotlinx.serialization.ExperimentalSerializationApi
21 | import kotlinx.serialization.Serializable
22 | import kotlinx.serialization.json.Json
23 | import kotlinx.serialization.json.JsonNamingStrategy
24 |
25 | fun main() {
26 | embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true)
27 | }
28 |
29 | private fun Application.myModule() {
30 |
31 | val json = Json {
32 | prettyPrint = true
33 | encodeDefaults = true
34 | explicitNulls = false
35 | namingStrategy = JsonNamingStrategy.SnakeCase
36 | }
37 |
38 | install(OpenApi) {
39 | schemas {
40 | // configure the schema generator to use the default kotlinx-serializer
41 | generator = SchemaGenerator.kotlinx(json)
42 | }
43 | examples {
44 | // configure the example encoder to encode kotlin objects using kotlinx-serializer
45 | exampleEncoder = ExampleEncoder.kotlinx(json)
46 | }
47 | }
48 |
49 | routing {
50 |
51 | // add the routes for the OpenAPI spec, Swagger UI and ReDoc
52 | route("swagger") {
53 | swaggerUI("/api.json")
54 | }
55 | route("api.json") {
56 | openApi()
57 | }
58 | route("redoc") {
59 | redoc("/api.json")
60 | }
61 |
62 | // a documented route
63 | get("hello", {
64 | description = "A Hello-World route"
65 | request {
66 | queryParameter("name") {
67 | description = "the name to greet"
68 | example("Name Parameter") {
69 | value = "Mr. Example"
70 | }
71 | }
72 | }
73 | response {
74 | code(HttpStatusCode.OK) {
75 | description = "successful request - always returns 'Hello World!'"
76 | body {
77 | example("Success Response") {
78 | value = TestResponse(
79 | name = "Mr. Example",
80 | length = 11
81 | )
82 | }
83 | }
84 | }
85 | }
86 | }) {
87 | call.respondText("Hello ${call.request.queryParameters["name"]}")
88 | }
89 |
90 | }
91 |
92 | }
93 |
94 |
95 | @Serializable
96 | data class TestResponse(
97 | val name: String,
98 | val length: Int,
99 | )
100 |
--------------------------------------------------------------------------------
/examples/src/main/kotlin/io/github/smiley4/ktoropenapi/examples/Minimal.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.examples
2 |
3 | import io.github.smiley4.ktoropenapi.OpenApi
4 | import io.github.smiley4.ktoropenapi.get
5 | import io.github.smiley4.ktoropenapi.openApi
6 | import io.github.smiley4.ktorredoc.redoc
7 | import io.github.smiley4.ktorswaggerui.swaggerUI
8 | import io.ktor.http.HttpStatusCode
9 | import io.ktor.server.application.Application
10 | import io.ktor.server.application.install
11 | import io.ktor.server.engine.embeddedServer
12 | import io.ktor.server.netty.Netty
13 | import io.ktor.server.response.respondText
14 | import io.ktor.server.routing.route
15 | import io.ktor.server.routing.routing
16 |
17 | fun main() {
18 | embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true)
19 | }
20 |
21 | private fun Application.myModule() {
22 |
23 | // Install the OpenAPI plugin and use the default configuration
24 | install(OpenApi)
25 |
26 | routing {
27 |
28 | // Create a route for the OpenAPI spec file.
29 | // This route will not be included in the spec.
30 | route("api.json") {
31 | openApi()
32 | }
33 | // Create a route for the Swagger UI using the OpenAPI spec at "/api.json".
34 | // This route will not be included in the spec.
35 | route("swagger") {
36 | swaggerUI("/api.json") {
37 |
38 | }
39 | }
40 | // Create a route for ReDoc using the OpenAPI spec at "/api.json".
41 | // This route will not be included in the spec.
42 | route("redoc") {
43 | redoc("/api.json")
44 | }
45 |
46 | // a documented route
47 | get("hello", {
48 | // description of this route
49 | description = "A Hello-World route"
50 | response {
51 | HttpStatusCode.OK to {
52 | description = "A successful response"
53 | body()
54 | }
55 | }
56 | }) {
57 | call.respondText("Hello World!")
58 | }
59 |
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/examples/src/main/kotlin/io/github/smiley4/ktoropenapi/examples/MultipleSpecs.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.examples
2 |
3 | import io.github.smiley4.ktoropenapi.OpenApi
4 | import io.github.smiley4.ktoropenapi.get
5 | import io.github.smiley4.ktoropenapi.openApi
6 | import io.github.smiley4.ktoropenapi.route
7 | import io.github.smiley4.ktorredoc.redoc
8 | import io.github.smiley4.ktorswaggerui.swaggerUI
9 | import io.ktor.server.application.Application
10 | import io.ktor.server.application.install
11 | import io.ktor.server.engine.embeddedServer
12 | import io.ktor.server.netty.Netty
13 | import io.ktor.server.response.respondText
14 | import io.ktor.server.routing.route
15 | import io.ktor.server.routing.routing
16 |
17 | fun main() {
18 | embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true)
19 | }
20 |
21 | private fun Application.myModule() {
22 |
23 | // Install and configure the OpenAPI plugin
24 | install(OpenApi) {
25 | // "global" configuration for all specs
26 | info {
27 | title = "Example API"
28 | }
29 | // configuration specific for spec "version1", overwrites global config
30 | spec("version1") {
31 | info {
32 | version = "1.0"
33 | }
34 | }
35 | // configuration specific for spec "version2", overwrites global config
36 | spec("version2") {
37 | info {
38 | version = "2.0"
39 | }
40 | }
41 | // assign all unassigned routes to spec "version2" (here only route '/greet')
42 | specAssigner = { _, _ -> "version2" }
43 | }
44 |
45 | routing {
46 |
47 | // add routes for "version1" spec, Swagger UI and ReDoc
48 | route("v1") {
49 | // OpenAPI spec containing all routes assigned to "version1"
50 | route("api.json") {
51 | openApi("version1")
52 | }
53 | // Swagger UI using '/v1/api.json'
54 | route("swagger") {
55 | swaggerUI("/v1/api.json")
56 | }
57 | // ReDoc using '/v1/api.json'
58 | route("redoc") {
59 | redoc("/v1/api.json")
60 | }
61 | }
62 |
63 | // add routes for "version2" spec, Swagger UI and ReDoc
64 | route("v2") {
65 | // OpenAPI spec containing all routes assigned to "version2"
66 | route("api.json") {
67 | openApi("version2")
68 | }
69 | // Swagger UI using '/v2/api.json'
70 | route("swagger") {
71 | swaggerUI("/v2/api.json")
72 | }
73 | // ReDoc using '/v2/api.json'
74 | route("redoc") {
75 | redoc("/v2/api.json")
76 | }
77 | }
78 |
79 | // version 1.0 routes
80 | route("v1", {
81 | specName = "version1"
82 | }) {
83 |
84 | // "hello" route in version 1.0
85 | get("hello", {
86 | description = "Version 1 'Hello World'"
87 | }) {
88 | call.respondText("Hello World!")
89 | }
90 |
91 | }
92 |
93 | // version 2.0 routes
94 | route("v2", {
95 | specName = "version2"
96 | }) {
97 |
98 | // "hello" route in version 2.0
99 | get("hello", {
100 | description = "Version 2 'Hello World'"
101 | }) {
102 | call.respondText("Hello World! (improved)")
103 | }
104 |
105 | }
106 |
107 | // unassigned route
108 | get("greet", {
109 | description = "Alternative route not manually assigned to any spec."
110 | }) {
111 | call.respondText("Alternative Hello World!")
112 | }
113 |
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/examples/src/main/kotlin/io/github/smiley4/ktoropenapi/examples/Webhooks.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.examples
2 |
3 | import io.github.smiley4.ktoropenapi.OpenApi
4 | import io.github.smiley4.ktoropenapi.openApi
5 | import io.github.smiley4.ktoropenapi.post
6 | import io.github.smiley4.ktoropenapi.webhook
7 | import io.github.smiley4.ktorredoc.redoc
8 | import io.github.smiley4.ktorswaggerui.swaggerUI
9 | import io.ktor.http.ContentType
10 | import io.ktor.http.HttpMethod
11 | import io.ktor.http.HttpStatusCode
12 | import io.ktor.server.application.Application
13 | import io.ktor.server.application.install
14 | import io.ktor.server.engine.embeddedServer
15 | import io.ktor.server.netty.Netty
16 | import io.ktor.server.response.respond
17 | import io.ktor.server.routing.route
18 | import io.ktor.server.routing.routing
19 |
20 | fun main() {
21 | embeddedServer(Netty, port = 8080, host = "localhost", module = Application::myModule).start(wait = true)
22 | }
23 |
24 | private fun Application.myModule() {
25 |
26 | // Install the "OpenApi"-Plugin
27 | install(OpenApi)
28 |
29 | routing {
30 |
31 | // add the routes for the api-spec, swagger-ui and redoc
32 | route("api.json") {
33 | openApi()
34 | }
35 | route("swagger") {
36 | swaggerUI("/api.json")
37 | }
38 | route("redoc") {
39 | redoc("/api.json")
40 | }
41 |
42 | // a "normal" documented route to register for notifications
43 | post("registerForAlert", {
44 | description = "Register an URL to be called when new concerts are scheduled."
45 | request {
46 | body {
47 | description = "The URL to be notified about approaching concerts."
48 | }
49 | }
50 | }) {
51 | call.respond(HttpStatusCode.NotImplemented, Unit)
52 | }
53 |
54 | // documentation of the webhook to notify
55 | webhook(HttpMethod.Post, "concertAlert") {
56 | description = "Notify the registered URL with details of an upcoming concert"
57 | request {
58 | body {
59 | mediaTypes(ContentType.Text.Plain)
60 | required = true
61 | }
62 | }
63 | }
64 |
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/examples/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %cyan(%d{yyyy-MM-dd HH:mm:ss.SSS}) | %highlight(%-5.5level{5}) | %gray(%-16.16thread{16}) | %magenta(%-25.25logger{25}) | %m%n
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 |
3 | # project id
4 | projectGroupId=io.github.smiley4
5 | projectVersion=5.1.0
6 |
7 | # common publishing information
8 | projectBaseScmUrl=https://github.com/SMILEY4/
9 | projectBaseScmConnection=scm:git:git://github.com/SMILEY4/
10 | projectLicenseName=The Apache License, Version 2.0
11 | projectLicenseUrl=https://www.apache.org/licenses/LICENSE-2.0.txt
12 | projectDeveloperName=smiley4
13 | projectDeveloperUrl=https://github.com/SMILEY4
14 |
15 | # dependency versions
16 | versionKtor=3.1.1
17 | versionSwaggerUI=5.17.14
18 | versionSwaggerParser=2.1.24
19 | versionSchemaKenerator=2.1.2
20 | versionKotlinLogging=7.0.0
21 | versionKotest=5.8.0
22 | versionKotlinTest=2.0.21
23 | versionMockk=1.13.12
24 | versionLogback=1.5.6
25 | versionJackson=2.18.1
26 | versionRedoc=2.1.5
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SMILEY4/ktor-openapi-tools/a65a2c92d72654145dfe4a413bf94d52d516b17c/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/example/ExampleContext.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.example
2 |
3 | import io.github.smiley4.ktoropenapi.config.descriptors.ExampleDescriptor
4 | import io.swagger.v3.oas.models.examples.Example
5 |
6 | /**
7 | * Provides examples for an openapi-spec
8 | */
9 | internal interface ExampleContext {
10 |
11 | /**
12 | * Get an [Example] (or a ref to an example) by its [ExampleDescriptor].
13 | */
14 | fun getExample(descriptor: ExampleDescriptor): Example
15 |
16 |
17 | /**
18 | * Get all examples placed in the components-section of the spec.
19 | */
20 | fun getComponentSection(): Map
21 | }
22 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/ComponentsBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.data.OpenApiPluginData
4 | import io.swagger.v3.oas.models.Components
5 | import io.swagger.v3.oas.models.examples.Example
6 | import io.swagger.v3.oas.models.media.Schema
7 |
8 | /**
9 | * Builds the openapi [Components]-object containing shared reusable schemas and examples.
10 | * See [OpenAPI Specification - Components Object](https://swagger.io/specification/#components-object).
11 | */
12 | internal class ComponentsBuilder(
13 | private val config: OpenApiPluginData,
14 | private val securitySchemesBuilder: SecuritySchemesBuilder
15 | ) {
16 |
17 | fun build(schemas: Map>, examples: Map): Components {
18 | return Components().also {
19 | it.schemas = schemas
20 | it.examples = examples
21 | if (config.securityConfig.securitySchemes.isNotEmpty()) {
22 | it.securitySchemes = securitySchemesBuilder.build(config.securityConfig.securitySchemes)
23 | }
24 | }
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/ContactBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.data.ContactData
4 | import io.swagger.v3.oas.models.info.Contact
5 |
6 | /**
7 | * Builds the openapi [Contact]-object. Holds Contact information for the exposed API.
8 | * See [OpenAPI Specification - Contact Object](https://swagger.io/specification/#contact-object).
9 | */
10 | internal class ContactBuilder {
11 |
12 | fun build(contact: ContactData): Contact =
13 | Contact().also {
14 | it.name = contact.name
15 | it.email = contact.email
16 | it.url = contact.url
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/ExternalDocumentationBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.data.ExternalDocsData
4 | import io.swagger.v3.oas.models.ExternalDocumentation
5 |
6 | /**
7 | * Build the openapi [ExternalDocumentation]-object. Allows referencing an external resource for extended documentation.
8 | * See [OpenAPI Specification - External Documentation Object](https://swagger.io/specification/#external-documentation-object).
9 | */
10 | internal class ExternalDocumentationBuilder {
11 |
12 | fun build(externalDocs: ExternalDocsData): ExternalDocumentation =
13 | ExternalDocumentation().also {
14 | it.url = externalDocs.url
15 | it.description = externalDocs.description
16 | }
17 |
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/HeaderBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.builder.schema.SchemaContext
4 | import io.github.smiley4.ktoropenapi.data.HeaderData
5 | import io.swagger.v3.oas.models.headers.Header
6 |
7 | /**
8 | * Build the openapi [Header]-object.
9 | * See [OpenAPI Specification - Header Object](https://swagger.io/specification/#header-object).
10 | */
11 | internal class HeaderBuilder(
12 | private val schemaContext: SchemaContext
13 | ) {
14 |
15 | fun build(header: HeaderData): Header =
16 | Header().also {
17 | it.description = header.description
18 | it.required = header.required
19 | it.deprecated = header.deprecated
20 | it.schema = header.type?.let { t -> schemaContext.getSchema(t) }
21 | it.explode = header.explode
22 | // it.example = TODO()
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/InfoBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.data.InfoData
4 | import io.swagger.v3.oas.models.info.Info
5 |
6 | /**
7 | * Build the openapi [Info]-object. Holds metadata about the API.
8 | * See [OpenAPI Specification - Info Object](https://swagger.io/specification/#info-object).
9 | */
10 | internal class InfoBuilder(
11 | private val contactBuilder: ContactBuilder,
12 | private val licenseBuilder: LicenseBuilder
13 | ) {
14 |
15 | fun build(info: InfoData): Info =
16 | Info().also {
17 | it.title = info.title
18 | it.version = info.version
19 | it.description = info.description
20 | it.termsOfService = info.termsOfService
21 | info.contact?.also { contact ->
22 | it.contact = contactBuilder.build(contact)
23 | }
24 | info.license?.also { license ->
25 | it.license = licenseBuilder.build(license)
26 | }
27 | it.summary = info.summary
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/LicenseBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.data.LicenseData
4 | import io.swagger.v3.oas.models.info.License
5 |
6 | /**
7 | * Build the openapi [License]-object. Holds license information for the exposed API.
8 | * See [OpenAPI Specification - License Object](https://swagger.io/specification/#license-object).
9 | */
10 | internal class LicenseBuilder {
11 |
12 | fun build(license: LicenseData): License =
13 | License().also {
14 | it.name = license.name
15 | it.url = license.url
16 | it.identifier = license.identifier
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/OAuthFlowsBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.data.OpenIdOAuthFlowData
4 | import io.github.smiley4.ktoropenapi.data.OpenIdOAuthFlowsData
5 | import io.swagger.v3.oas.models.security.OAuthFlow
6 | import io.swagger.v3.oas.models.security.OAuthFlows
7 | import io.swagger.v3.oas.models.security.Scopes
8 |
9 | /**
10 | * Build the openapi [OAuthFlows]-object. Holds configuration of the supported OAuth Flows.
11 | * See [OpenAPI Specification - OAuth Flows Object](https://swagger.io/specification/#oauth-flows-object).
12 | */
13 | internal class OAuthFlowsBuilder {
14 |
15 | fun build(flows: OpenIdOAuthFlowsData): OAuthFlows {
16 | return OAuthFlows().apply {
17 | implicit = flows.implicit?.let { build(it) }
18 | password = flows.password?.let { build(it) }
19 | clientCredentials = flows.clientCredentials?.let { build(it) }
20 | authorizationCode = flows.authorizationCode?.let { build(it) }
21 | }
22 | }
23 |
24 | private fun build(flow: OpenIdOAuthFlowData): OAuthFlow {
25 | return OAuthFlow().apply {
26 | authorizationUrl = flow.authorizationUrl
27 | tokenUrl = flow.tokenUrl
28 | refreshUrl = flow.refreshUrl
29 | scopes = flow.scopes?.let { buildScopes(it) }
30 | }
31 | }
32 |
33 | private fun buildScopes(scopes: Map): Scopes {
34 | return Scopes().apply {
35 | scopes.forEach { (k, v) -> addString(k, v) }
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/OpenApiBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.builder.example.ExampleContext
4 | import io.github.smiley4.ktoropenapi.data.OpenApiPluginData
5 | import io.github.smiley4.ktoropenapi.builder.route.RouteMeta
6 | import io.github.smiley4.ktoropenapi.builder.schema.SchemaContext
7 | import io.swagger.v3.oas.models.OpenAPI
8 | import io.swagger.v3.oas.models.SpecVersion
9 |
10 | /**
11 | * Build the openapi [OpenAPI]-object. Is the root of the openapi document.
12 | * See [OpenAPI Specification - OpenAPI Object](https://swagger.io/specification/#openapi-object).
13 | */
14 | internal class OpenApiBuilder(
15 | private val config: OpenApiPluginData,
16 | private val schemaContext: SchemaContext,
17 | private val exampleContext: ExampleContext,
18 | private val infoBuilder: InfoBuilder,
19 | private val externalDocumentationBuilder: ExternalDocumentationBuilder,
20 | private val serverBuilder: ServerBuilder,
21 | private val tagBuilder: TagBuilder,
22 | private val pathsBuilder: PathsBuilder,
23 | private val webhooksBuilder: WebhooksBuilder,
24 | private val componentsBuilder: ComponentsBuilder,
25 | ) {
26 |
27 | fun build(routes: Collection): OpenAPI {
28 | return OpenAPI().also {
29 | it.specVersion = SpecVersion.V31
30 | it.openapi = "3.1.0"
31 | it.info = infoBuilder.build(config.info)
32 | it.externalDocs = externalDocumentationBuilder.build(config.externalDocs)
33 | it.servers = config.servers.map { server -> serverBuilder.build(server) }
34 | it.tags = config.tagsConfig.tags.map { tag -> tagBuilder.build(tag) }
35 | it.paths = pathsBuilder.build(routes.filter { r -> !r.isWebhook})
36 | it.webhooks = webhooksBuilder.build(routes.filter { r -> r.isWebhook})
37 | it.components = componentsBuilder.build(schemaContext.getComponentSection(), exampleContext.getComponentSection())
38 | }
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/OperationBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.builder.route.RouteMeta
4 | import io.swagger.v3.oas.models.Operation
5 |
6 | /**
7 | * Build the openapi [Operation]-object. Holds information describing a single API operation on a path.
8 | * See [OpenAPI Specification - Operation Object](https://swagger.io/specification/#operation-object).
9 | */
10 | internal class OperationBuilder(
11 | private val operationTagsBuilder: OperationTagsBuilder,
12 | private val parameterBuilder: ParameterBuilder,
13 | private val requestBodyBuilder: RequestBodyBuilder,
14 | private val responsesBuilder: ResponsesBuilder,
15 | private val securityRequirementsBuilder: SecurityRequirementsBuilder,
16 | private val externalDocumentationBuilder: ExternalDocumentationBuilder,
17 | private val serverBuilder: ServerBuilder
18 | ) {
19 |
20 | fun build(route: RouteMeta): Operation =
21 | Operation().also {
22 | it.summary = route.documentation.summary
23 | it.description = route.documentation.description
24 | it.operationId = route.documentation.operationId
25 | it.deprecated = route.documentation.deprecated
26 | it.tags = operationTagsBuilder.build(route)
27 | it.parameters = route.documentation.request.parameters
28 | .filter { param -> !param.hidden }
29 | .map { param -> parameterBuilder.build(param) }
30 | route.documentation.request.body?.let { body ->
31 | it.requestBody = requestBodyBuilder.build(body)
32 | }
33 | it.responses = responsesBuilder.build(route.documentation.responses, route.protected)
34 | if (route.protected) {
35 | securityRequirementsBuilder.build(route).also { securityRequirements ->
36 | if (securityRequirements.isNotEmpty()) {
37 | it.security = securityRequirements
38 | }
39 | }
40 | }
41 | it.externalDocs = route.documentation.externalDocs?.let { docs -> externalDocumentationBuilder.build(docs) }
42 | if (route.documentation.servers.isNotEmpty()) {
43 | it.servers = route.documentation.servers.map { server -> serverBuilder.build(server) }
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/OperationTagsBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.data.OpenApiPluginData
4 | import io.github.smiley4.ktoropenapi.builder.route.RouteMeta
5 |
6 | /**
7 | * Builds the list of tags for a single route.
8 | */
9 | internal class OperationTagsBuilder(
10 | private val config: OpenApiPluginData
11 | ) {
12 |
13 | fun build(route: RouteMeta): List {
14 | return mutableSetOf().also { tags ->
15 | tags.addAll(getGeneratedTags(route))
16 | tags.addAll(getRouteTags(route))
17 | }.filterNotNull()
18 | }
19 |
20 | private fun getRouteTags(route: RouteMeta) = route.documentation.tags
21 |
22 | private fun getGeneratedTags(route: RouteMeta) = config.tagsConfig.generator(route.path.split("/").filter { it.isNotEmpty() })
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/ParameterBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.builder.example.ExampleContext
4 | import io.github.smiley4.ktoropenapi.builder.schema.SchemaContext
5 | import io.github.smiley4.ktoropenapi.data.RequestParameterData
6 | import io.github.smiley4.ktoropenapi.config.ParameterLocation
7 | import io.swagger.v3.oas.models.parameters.Parameter
8 |
9 | /**
10 | * Build the openapi [Parameter]-object. Holds information describing a single operation (query, path or header) parameter.
11 | * See [OpenAPI Specification - Parameter Object](https://swagger.io/specification/#parameter-object).
12 | */
13 | internal class ParameterBuilder(
14 | private val schemaContext: SchemaContext,
15 | private val exampleContext: ExampleContext
16 | ) {
17 |
18 | fun build(parameter: RequestParameterData): Parameter =
19 | Parameter().also {
20 | it.`in` = when (parameter.location) {
21 | ParameterLocation.QUERY -> "query"
22 | ParameterLocation.HEADER -> "header"
23 | ParameterLocation.PATH -> "path"
24 | ParameterLocation.COOKIE -> "cookie"
25 | }
26 | it.name = parameter.name
27 | it.description = parameter.description
28 | it.required = parameter.required
29 | it.deprecated = parameter.deprecated
30 | it.allowEmptyValue = parameter.allowEmptyValue
31 | it.explode = parameter.explode
32 | it.example = parameter.example?.let { e -> exampleContext.getExample(e).value }
33 | it.allowReserved = parameter.allowReserved
34 | it.schema = schemaContext.getSchema(parameter.type)
35 | it.style = parameter.style
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/PathBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.builder.route.RouteMeta
4 | import io.ktor.http.HttpMethod
5 | import io.swagger.v3.oas.models.PathItem
6 |
7 | /**
8 | * Build the openapi [PathItem]-object. Holds information describing the operations available on a single path.
9 | * See [OpenAPI Specification - Path Item Object](https://swagger.io/specification/#path-item-object).
10 | */
11 | internal class PathBuilder(
12 | private val operationBuilder: OperationBuilder
13 | ) {
14 |
15 | fun build(route: RouteMeta): PathItem =
16 | PathItem().also {
17 | val operation = operationBuilder.build(route)
18 | when (route.method) {
19 | HttpMethod.Get -> it.get = operation
20 | HttpMethod.Post -> it.post = operation
21 | HttpMethod.Put -> it.put = operation
22 | HttpMethod.Patch -> it.patch = operation
23 | HttpMethod.Delete -> it.delete = operation
24 | HttpMethod.Head -> it.head = operation
25 | HttpMethod.Options -> it.options = operation
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/PathsBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.builder.route.RouteMeta
4 | import io.github.smiley4.ktoropenapi.data.OpenApiPluginData
5 | import io.swagger.v3.oas.models.PathItem
6 | import io.swagger.v3.oas.models.Paths
7 |
8 | /**
9 | * Build the openapi [Paths]-object. Holds the relative paths to the individual endpoints and their operations.
10 | * See [OpenAPI Specification - Paths Object](https://swagger.io/specification/#paths-object).
11 | */
12 | internal class PathsBuilder(
13 | private val config: OpenApiPluginData,
14 | private val pathBuilder: PathBuilder
15 | ) {
16 |
17 | fun build(routes: Collection): Paths =
18 | Paths().also {
19 | routes.forEach { route ->
20 | val url = createUrl(route)
21 | val existingPath = it[url]
22 | if (existingPath != null) {
23 | addToExistingPath(existingPath, route)
24 | } else {
25 | addAsNewPath(url, it, route)
26 | }
27 | }
28 | }
29 |
30 | private fun createUrl(route: RouteMeta): String {
31 | return "${config.rootPath ?: ""}${route.path}"
32 | }
33 |
34 | private fun addAsNewPath(url: String, paths: Paths, route: RouteMeta) {
35 | paths.addPathItem(url, pathBuilder.build(route))
36 | }
37 |
38 | private fun addToExistingPath(existing: PathItem, route: RouteMeta) {
39 | val path = pathBuilder.build(route)
40 | existing.get = path.get ?: existing.get
41 | existing.put = path.put ?: existing.put
42 | existing.post = path.post ?: existing.post
43 | existing.delete = path.delete ?: existing.delete
44 | existing.options = path.options ?: existing.options
45 | existing.head = path.head ?: existing.head
46 | existing.patch = path.patch ?: existing.patch
47 | existing.trace = path.trace ?: existing.trace
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/RequestBodyBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.data.BaseBodyData
4 | import io.swagger.v3.oas.models.parameters.RequestBody
5 |
6 | /**
7 | * Build the openapi [RequestBody]-object. Holds information describing a single request body.
8 | * See [OpenAPI Specification - Request Body Object](https://swagger.io/specification/#request-body-object).
9 | */
10 | internal class RequestBodyBuilder(
11 | private val contentBuilder: ContentBuilder
12 | ) {
13 |
14 | fun build(body: BaseBodyData): RequestBody =
15 | RequestBody().also {
16 | it.description = body.description
17 | it.required = body.required
18 | it.content = contentBuilder.build(body)
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/ResponseBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.data.ResponseData
4 | import io.swagger.v3.oas.models.responses.ApiResponse
5 |
6 | /**
7 | * Build the openapi [ApiResponse]-objects by status-code. Holds information describing status-codes and responses from an API Operation.
8 | * See [OpenAPI Specification - Response Object](https://swagger.io/specification/#response-object).
9 | */
10 | internal class ResponseBuilder(
11 | private val headerBuilder: HeaderBuilder,
12 | private val contentBuilder: ContentBuilder
13 | ) {
14 |
15 | fun build(response: ResponseData): Pair =
16 | response.statusCode to ApiResponse().also {
17 | it.description = response.description
18 | it.headers = response.headers.mapValues { header -> headerBuilder.build(header.value) }
19 | response.body?.let { body ->
20 | it.content = contentBuilder.build(body)
21 | }
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/ResponsesBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.data.ResponseData
4 | import io.github.smiley4.ktoropenapi.data.OpenApiPluginData
5 | import io.ktor.http.HttpStatusCode
6 | import io.swagger.v3.oas.models.responses.ApiResponses
7 |
8 | /**
9 | * Build the openapi [ApiResponses]-object. A container for the expected responses of an operation.
10 | * See [OpenAPI Specification - Responses Object](https://swagger.io/specification/#responses-object).
11 | */
12 | internal class ResponsesBuilder(
13 | private val responseBuilder: ResponseBuilder,
14 | private val config: OpenApiPluginData
15 | ) {
16 |
17 | fun build(responses: List, isProtected: Boolean): ApiResponses =
18 | ApiResponses().also {
19 | responses
20 | .filter { !it.hidden }
21 | .map { response -> responseBuilder.build(response) }
22 | .forEach { (name, response) -> it.addApiResponse(name, response) }
23 | if (shouldAddUnauthorized(responses, isProtected)) {
24 | config.securityConfig.defaultUnauthorizedResponse
25 | ?.let { response -> responseBuilder.build(response) }
26 | ?.also { (name, response) -> it.addApiResponse(name, response) }
27 | }
28 | }
29 |
30 | private fun shouldAddUnauthorized(responses: List, isProtected: Boolean): Boolean {
31 | val unauthorizedCode = HttpStatusCode.Unauthorized.value.toString()
32 | return config.securityConfig.defaultUnauthorizedResponse != null
33 | && !config.securityConfig.defaultUnauthorizedResponse.hidden
34 | && isProtected
35 | && responses.count { it.statusCode == unauthorizedCode } == 0
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/SecurityRequirementsBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.builder.route.RouteMeta
4 | import io.github.smiley4.ktoropenapi.data.OpenApiPluginData
5 | import io.swagger.v3.oas.models.security.SecurityRequirement
6 |
7 | /**
8 | * Build the openapi [SecurityRequirement]-objects.
9 | * See [OpenAPI Specification - Security Requirement Object](https://swagger.io/specification/#security-requirement-object).
10 | */
11 | internal class SecurityRequirementsBuilder(
12 | private val config: OpenApiPluginData
13 | ) {
14 |
15 | fun build(route: RouteMeta): List {
16 | return buildSet {
17 | addAll(route.documentation.securitySchemeNames)
18 | if(route.documentation.securitySchemeNames.isEmpty()) {
19 | addAll(config.securityConfig.defaultSecuritySchemeNames)
20 | }
21 | }.map {
22 | SecurityRequirement().apply {
23 | addList(it, emptyList())
24 | }
25 | }
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/SecuritySchemesBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.config.AuthType
4 | import io.github.smiley4.ktoropenapi.data.SecuritySchemeData
5 | import io.swagger.v3.oas.models.security.SecurityScheme
6 |
7 | /**
8 | * Build the openapi [SecurityScheme]-objects with their names. Holds information defining security schemes that can be used by operations.
9 | * See [OpenAPI Specification - Security Scheme Object](https://swagger.io/specification/#security-scheme-object).
10 | */
11 | internal class SecuritySchemesBuilder(
12 | private val oAuthFlowsBuilder: OAuthFlowsBuilder
13 | ) {
14 |
15 | fun build(securitySchemes: List): Map {
16 | return mutableMapOf().apply {
17 | securitySchemes.forEach {
18 | put(it.schemeName, build(it))
19 | }
20 | }
21 | }
22 |
23 | private fun build(securityScheme: SecuritySchemeData): SecurityScheme {
24 | return SecurityScheme().apply {
25 | description = securityScheme.description
26 | name = if(securityScheme.type == AuthType.API_KEY) securityScheme.name ?: securityScheme.schemeName else null
27 | type = securityScheme.type?.swaggerType
28 | `in` = securityScheme.location?.swaggerType
29 | scheme = securityScheme.scheme?.swaggerType
30 | bearerFormat = securityScheme.bearerFormat
31 | flows = securityScheme.flows?.let { f -> oAuthFlowsBuilder.build(f) }
32 | openIdConnectUrl = securityScheme.openIdConnectUrl
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/ServerBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.data.ServerData
4 | import io.swagger.v3.oas.models.servers.Server
5 | import io.swagger.v3.oas.models.servers.ServerVariable
6 | import io.swagger.v3.oas.models.servers.ServerVariables
7 |
8 | /**
9 | * Build the openapi [Server]-object. Holds information representing a Server.
10 | * See [OpenAPI Specification - Server Object](https://swagger.io/specification/#server-object).
11 | */
12 | internal class ServerBuilder {
13 |
14 | fun build(server: ServerData): Server =
15 | Server().also {
16 | it.url = server.url
17 | it.description = server.description
18 | if (server.variables.isNotEmpty()) {
19 | it.variables = ServerVariables().also { variables ->
20 | server.variables.forEach { entry ->
21 | variables.addServerVariable(entry.name, ServerVariable().also { variable ->
22 | variable.enum = entry.enum.toList()
23 | variable.default = entry.default
24 | variable.description = entry.description
25 | })
26 | }
27 | }
28 | }
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/TagBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.data.TagData
4 | import io.swagger.v3.oas.models.tags.Tag
5 |
6 | /**
7 | * Build the openapi [Tag]-object. Holds metadata of a single tag.
8 | * See [OpenAPI Specification - Tag Object](https://swagger.io/specification/#tag-object).
9 | */
10 | internal class TagBuilder(
11 | private val tagExternalDocumentationBuilder: TagExternalDocumentationBuilder
12 | ) {
13 |
14 | fun build(tag: TagData): Tag =
15 | Tag().also {
16 | it.name = tag.name
17 | it.description = tag.description
18 | if(tag.externalDocUrl != null && tag.externalDocDescription != null) {
19 | it.externalDocs = tagExternalDocumentationBuilder.build(tag.externalDocUrl, tag.externalDocDescription)
20 | }
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/TagExternalDocumentationBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.swagger.v3.oas.models.ExternalDocumentation
4 |
5 | /**
6 | * Build the openapi [ExternalDocumentation]-object for a tag.
7 | * See [OpenAPI Specification - External Documentation Object](https://swagger.io/specification/#external-documentation-object).
8 | */
9 | internal class TagExternalDocumentationBuilder {
10 |
11 | fun build(url: String, description: String): ExternalDocumentation =
12 | ExternalDocumentation().also {
13 | it.url = url
14 | it.description = description
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/openapi/WebhooksBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.openapi
2 |
3 | import io.github.smiley4.ktoropenapi.builder.route.RouteMeta
4 | import io.swagger.v3.oas.models.PathItem
5 |
6 | /**
7 | * Build the openapi "webhooks" section.
8 | * See [OpenAPI Specification - Webhooks](https://spec.openapis.org/oas/v3.1.0.html#oasWebhooks)
9 | */
10 | internal class WebhooksBuilder(
11 | private val pathBuilder: PathBuilder
12 | ) {
13 |
14 | fun build(routes: Collection): Map {
15 | return routes.associate { route ->
16 | route.path to pathBuilder.build(route)
17 | }
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/route/RouteDocumentationMerger.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.route
2 |
3 | import io.github.smiley4.ktoropenapi.config.RouteConfig
4 |
5 | internal class RouteDocumentationMerger {
6 |
7 | /**
8 | * Merges "a" with "b" and returns the result as a new [RouteConfig]. "a" has priority over "b".
9 | */
10 | @Suppress("CyclomaticComplexMethod")
11 | fun merge(a: RouteConfig, b: RouteConfig): RouteConfig {
12 | return RouteConfig().apply {
13 | specName = a.specName ?: b.specName
14 | tags = mutableSetOf().also {
15 | it.addAll(a.tags)
16 | it.addAll(b.tags)
17 | }
18 | summary = a.summary ?: b.summary
19 | description = a.description ?: b.description
20 | operationId = a.operationId ?: b.operationId
21 | securitySchemeNames = mutableSetOf().also { merged ->
22 | a.securitySchemeNames?.let { merged.addAll(it) }
23 | b.securitySchemeNames?.let { merged.addAll(it) }
24 | }
25 | deprecated = a.deprecated || b.deprecated
26 | hidden = a.hidden || b.hidden
27 | protected = a.protected ?: b.protected
28 | request {
29 | buildMap {
30 | b.getRequest().parameters.forEach { this[it.name] = it }
31 | a.getRequest().parameters.forEach { this[it.name] = it }
32 | }.values.forEach { parameters.add(it) }
33 | setBody(a.getRequest().getBody() ?: b.getRequest().getBody())
34 | }
35 | response {
36 | buildMap {
37 | b.getResponses().getResponses().forEach { this[it.statusCode] = it }
38 | a.getResponses().getResponses().forEach { this[it.statusCode] = it }
39 | }.values.forEach { addResponse(it) }
40 | }
41 | }
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/route/RouteMeta.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.route
2 |
3 | import io.github.smiley4.ktoropenapi.data.RouteData
4 | import io.ktor.http.HttpMethod
5 |
6 | /**
7 | * Information about a route
8 | */
9 | internal data class RouteMeta(
10 | val path: String,
11 | val method: HttpMethod,
12 | val documentation: RouteData,
13 | val protected: Boolean,
14 | val isWebhook: Boolean,
15 | )
16 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/builder/schema/SchemaContext.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.builder.schema
2 |
3 | import io.github.smiley4.ktoropenapi.config.descriptors.TypeDescriptor
4 | import io.swagger.v3.oas.models.media.Schema
5 |
6 | /**
7 | * Provides schemas for an openapi-spec
8 | */
9 | internal interface SchemaContext {
10 |
11 | /**
12 | * Get a [Schema] (or a ref to a schema) by its [TypeDescriptor]
13 | */
14 | fun getSchema(typeDescriptor: TypeDescriptor): Schema<*>
15 |
16 | /**
17 | * Get all schemas placed in the components-section of the spec.
18 | */
19 | fun getComponentSection(): Map>
20 | }
21 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/AuthKeyLocation.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.swagger.v3.oas.models.security.SecurityScheme
4 |
5 | /**
6 | * The locations of the API key.
7 | */
8 | enum class AuthKeyLocation(val swaggerType: SecurityScheme.In) {
9 | QUERY(SecurityScheme.In.QUERY),
10 | HEADER(SecurityScheme.In.HEADER),
11 | COOKIE(SecurityScheme.In.COOKIE)
12 | }
13 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/AuthScheme.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | /**
4 | * The authentication scheme.
5 | * https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml
6 | */
7 | enum class AuthScheme(val swaggerType: String) {
8 | BASIC("Basic"),
9 | BEARER("bearer"),
10 | DIGEST("Digest"),
11 | HOBA("HOBA"),
12 | MUTUAL("Mutual"),
13 | OAUTH("OAuth"),
14 | SCRAM_SHA_1("SCRAM-SHA-1"),
15 | SCRAM_SHA_256("SCRAM-SHA-256"),
16 | VAPID("vapid")
17 | }
18 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/AuthType.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.swagger.v3.oas.models.security.SecurityScheme
4 |
5 | /**
6 | * The type of security schemes
7 | */
8 | enum class AuthType(val swaggerType: SecurityScheme.Type) {
9 | API_KEY(SecurityScheme.Type.APIKEY),
10 | HTTP(SecurityScheme.Type.HTTP),
11 | OAUTH2(SecurityScheme.Type.OAUTH2),
12 | OPENID_CONNECT(SecurityScheme.Type.OPENIDCONNECT),
13 | MUTUAL_TLS(SecurityScheme.Type.MUTUALTLS)
14 | }
15 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/BaseBodyConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.data.BaseBodyData
4 | import io.ktor.http.ContentType
5 |
6 | /**
7 | * Describes a single request/response body with a single schema.
8 | */
9 | @OpenApiDslMarker
10 | sealed class BaseBodyConfig {
11 |
12 | /**
13 | * A brief description of the request body
14 | */
15 | var description: String? = null
16 |
17 | /**
18 | * Determines if the request body is required in the request
19 | */
20 | var required: Boolean? = null
21 |
22 | /**
23 | * Allowed Media Types for this body. If none specified, a media type will be chosen automatically based on the provided schema
24 | */
25 | var mediaTypes: Collection = emptySet()
26 |
27 | /**
28 | * Set the allowed Media Types for this body. If none specified, a media type will be chosen automatically based on the provided schema
29 | */
30 | fun mediaTypes(mediaTypes: Collection) {
31 | this.mediaTypes = mediaTypes
32 | }
33 |
34 | /**
35 | * Set the allowed Media Types for this body. If none specified, a media type will be chosen automatically based on the provided schema
36 | */
37 | fun mediaTypes(vararg mediaTypes: ContentType) {
38 | this.mediaTypes = mediaTypes.toList()
39 | }
40 |
41 | /**
42 | * Build the data object for this config.
43 | */
44 | internal abstract fun build(): BaseBodyData
45 | }
46 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/ContactConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.data.ContactData
4 | import io.github.smiley4.ktoropenapi.data.DataUtils.merge
5 |
6 | /**
7 | * Contact information for the exposed API.
8 | */
9 | @OpenApiDslMarker
10 | class ContactConfig internal constructor() {
11 |
12 | /**
13 | * The identifying name of the contact person/organization.
14 | */
15 | var name: String? = ContactData.DEFAULT.name
16 |
17 |
18 | /**
19 | * The URL pointing to the contact information. MUST be in the format of a URL.
20 | */
21 | var url: String? = ContactData.DEFAULT.url
22 |
23 |
24 | /**
25 | * The email address of the contact person/organization. MUST be in the format of an email address.
26 | */
27 | var email: String? = ContactData.DEFAULT.email
28 |
29 |
30 | /**
31 | * Build the data object for this config.
32 | * @param base the base config to "inherit" from. Values from the base should be copied, replaced or merged together.
33 | */
34 | internal fun build(base: ContactData) = ContactData(
35 | name = merge(base.name, name),
36 | url = merge(base.url, url),
37 | email = merge(base.email, email)
38 | )
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/ExampleConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.config.descriptors.ExampleDescriptor
4 | import io.github.smiley4.ktoropenapi.config.descriptors.SwaggerExampleDescriptor
5 | import io.github.smiley4.ktoropenapi.config.descriptors.ValueExampleDescriptor
6 | import io.github.smiley4.ktoropenapi.data.ExampleConfigData
7 | import io.github.smiley4.ktoropenapi.data.MultipartBodyData
8 | import io.github.smiley4.ktoropenapi.data.SecurityData
9 | import io.github.smiley4.ktoropenapi.data.SimpleBodyData
10 | import io.swagger.v3.oas.models.examples.Example
11 |
12 |
13 | /**
14 | * Configuration for examples
15 | */
16 | @OpenApiDslMarker
17 | class ExampleConfig internal constructor() {
18 |
19 | /**
20 | * The list of global / shared examples.
21 | */
22 | private val sharedExamples = mutableMapOf()
23 |
24 |
25 | /**
26 | * Add a shared example that can be referenced by all routes.
27 | * The name of the example has to be unique among all shared examples and acts as its id.
28 | * @param example the example data.
29 | */
30 | fun example(example: ExampleDescriptor) {
31 | sharedExamples[example.name] = example
32 | }
33 |
34 |
35 | /**
36 | * Add a shared example that can be referenced by all routes by the given name.
37 | * The provided name has to be unique among all shared examples and acts as its id.
38 | */
39 | fun example(name: String, example: Example) = example(SwaggerExampleDescriptor(name, example))
40 |
41 |
42 | /**
43 | * Add a shared example that can be referenced by all routes by the given name.
44 | * The provided name has to be unique among all shared examples and acts as its id.
45 | */
46 | fun example(name: String, example: ValueExampleDescriptorConfig.() -> Unit) = example(
47 | ValueExampleDescriptorConfig()
48 | .apply(example)
49 | .let { result ->
50 | ValueExampleDescriptor(
51 | name = name,
52 | value = result.value,
53 | summary = result.summary,
54 | description = result.description
55 | )
56 | }
57 | )
58 |
59 |
60 | /**
61 | * The [GenericExampleEncoder] responsible for encoding all example values.
62 | */
63 | var exampleEncoder: GenericExampleEncoder = ExampleConfigData.DEFAULT.exampleEncoder
64 |
65 |
66 | /**
67 | * Specify a custom encoder for example objects
68 | */
69 | fun encoder(exampleEncoder: GenericExampleEncoder) {
70 | this.exampleEncoder = exampleEncoder
71 | }
72 |
73 |
74 | /**
75 | * Build the data object for this config.
76 | * @param securityConfig the data for security config that might contain additional examples
77 | */
78 | internal fun build(securityConfig: SecurityData) = ExampleConfigData(
79 | sharedExamples = sharedExamples,
80 | securityExamples = securityConfig.defaultUnauthorizedResponse?.body?.let {
81 | when (it) {
82 | is SimpleBodyData -> it
83 | is MultipartBodyData -> null
84 | }
85 | },
86 | exampleEncoder = exampleEncoder
87 | )
88 | }
89 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/ExampleEncoder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import com.fasterxml.jackson.core.type.TypeReference
4 | import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
5 | import io.github.smiley4.ktoropenapi.config.descriptors.KTypeDescriptor
6 | import io.github.smiley4.ktoropenapi.config.descriptors.TypeDescriptor
7 | import kotlinx.serialization.json.Json
8 | import kotlinx.serialization.serializer
9 |
10 | /**
11 | * Encoder to produce the final example value.
12 | * Return the unmodified example to fall back to the default encoder.
13 | */
14 | typealias GenericExampleEncoder = (type: TypeDescriptor?, example: Any?) -> Any?
15 |
16 |
17 | object ExampleEncoder {
18 |
19 | /**
20 | * Default [GenericExampleEncoder] using internal swagger serializer to encode example object.
21 | */
22 | fun internal(): GenericExampleEncoder = { _, example ->
23 | example
24 | }
25 |
26 | /**
27 | * [GenericExampleEncoder] using kotlinx-serialization to encode example objects.
28 | * @param json the kotlinx json serializer to use for encoding objects to json. Set `null` to use default kotlinx json serializer.
29 | */
30 | fun kotlinx(json: Json? = null): GenericExampleEncoder = { type, example ->
31 | when(type) {
32 | is KTypeDescriptor -> {
33 | val jsonEncoder = json ?: Json
34 | val jsonString = jsonEncoder.encodeToString(serializer(type.type), example)
35 | val jsonObj = jacksonObjectMapper().readValue(jsonString, object : TypeReference() {})
36 | jsonObj
37 | }
38 | else -> example
39 | }
40 | }
41 |
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/ExternalDocsConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.data.DataUtils
4 | import io.github.smiley4.ktoropenapi.data.ExternalDocsData
5 |
6 | /**
7 | * An object representing external documentation.
8 | */
9 | @OpenApiDslMarker
10 | class ExternalDocsConfig internal constructor() {
11 |
12 | /**
13 | * A short description of the external documentation
14 | */
15 | var description: String? = null
16 |
17 |
18 | /**
19 | * A URL to the external documentation
20 | */
21 | var url: String = "/"
22 |
23 | /**
24 | * Build the data object for this config.
25 | * @param base the base config to "inherit" from. Values from the base should be copied, replaced or merged together.
26 | */
27 | internal fun build(base: ExternalDocsData) = ExternalDocsData(
28 | url = DataUtils.mergeDefault(base.url, url, ExternalDocsData.DEFAULT.url),
29 | description = DataUtils.merge(base.description, description)
30 | )
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/HeaderConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.config.descriptors.KTypeDescriptor
4 | import io.github.smiley4.ktoropenapi.config.descriptors.SwaggerTypeDescriptor
5 | import io.github.smiley4.ktoropenapi.config.descriptors.TypeDescriptor
6 | import io.github.smiley4.ktoropenapi.data.HeaderData
7 | import io.swagger.v3.oas.models.media.Schema
8 | import kotlin.reflect.KType
9 | import kotlin.reflect.typeOf
10 |
11 | /**
12 | * Describes a single header.
13 | */
14 | @OpenApiDslMarker
15 | class HeaderConfig internal constructor() {
16 |
17 | /**
18 | * A description of the header
19 | */
20 | var description: String? = null
21 |
22 |
23 | /**
24 | * The schema of the header
25 | */
26 | internal var type: TypeDescriptor? = null
27 |
28 |
29 | /**
30 | * The schema of the header
31 | */
32 | fun type(type: TypeDescriptor) {
33 | this.type = type
34 | }
35 |
36 |
37 | /**
38 | * The schema of the header
39 | */
40 | fun type(type: Schema<*>) = type(SwaggerTypeDescriptor(type))
41 |
42 |
43 | /**
44 | * The schema of the header
45 | */
46 | fun type(type: KType) = type(KTypeDescriptor(type))
47 |
48 |
49 | /**
50 | * The schema of the header
51 | */
52 | inline fun type() = type(KTypeDescriptor(typeOf()))
53 |
54 |
55 | /**
56 | * Determines whether this header is mandatory
57 | */
58 | var required: Boolean? = null
59 |
60 |
61 | /**
62 | * Specifies that a header is deprecated and SHOULD be transitioned out of usage
63 | */
64 | var deprecated: Boolean? = null
65 |
66 |
67 | /**
68 | * Specifies whether arrays and objects should generate separate parameters for each array item or object property.
69 | */
70 | var explode: Boolean? = null
71 |
72 | /**
73 | * Build the data object for this config.
74 | */
75 | internal fun build() = HeaderData(
76 | description = description,
77 | type = type,
78 | required = required ?: false,
79 | deprecated = deprecated ?: false,
80 | explode = explode,
81 | )
82 | }
83 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/InfoConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.data.ContactData
4 | import io.github.smiley4.ktoropenapi.data.DataUtils.merge
5 | import io.github.smiley4.ktoropenapi.data.DataUtils.mergeDefault
6 | import io.github.smiley4.ktoropenapi.data.InfoData
7 | import io.github.smiley4.ktoropenapi.data.LicenseData
8 |
9 | /**
10 | * Basic information for the exposed API.
11 | */
12 | @OpenApiDslMarker
13 | class InfoConfig internal constructor() {
14 |
15 | /**
16 | * The title of the api
17 | */
18 | var title: String = "API"
19 |
20 |
21 | /**
22 | * The version of the OpenAPI document
23 | */
24 | var version: String? = "latest"
25 |
26 |
27 | /**
28 | * A short description of the API
29 | */
30 | var description: String? = null
31 |
32 | /**
33 | * A short summary of the API
34 | */
35 | var summary: String? = null
36 |
37 |
38 | /**
39 | * A URL to the Terms of Service for the API. MUST be in the format of a URL.
40 | */
41 | var termsOfService: String? = null
42 |
43 | private var contact: ContactConfig? = null
44 |
45 |
46 | /**
47 | * The contact information for the exposed API.
48 | */
49 | fun contact(block: ContactConfig.() -> Unit) {
50 | contact = ContactConfig().apply(block)
51 | }
52 |
53 |
54 | private var license: LicenseConfig? = null
55 |
56 |
57 | /**
58 | * The license information for the exposed API.
59 | */
60 | fun license(block: LicenseConfig.() -> Unit) {
61 | license = LicenseConfig().apply(block)
62 | }
63 |
64 | /**
65 | * Build the data object for this config.
66 | * @param base the base config to "inherit" from. Values from the base should be copied, replaced or merged together.
67 | */
68 | internal fun build(base: InfoData): InfoData {
69 | return InfoData(
70 | title = mergeDefault(base.title, this.title, InfoData.DEFAULT.title),
71 | version = merge(base.version, this.version),
72 | description = merge(base.description, this.description),
73 | termsOfService = merge(base.termsOfService, this.termsOfService),
74 | contact = contact?.build(base.contact ?: ContactData.DEFAULT) ?: base.contact,
75 | license = license?.build(base.license ?: LicenseData.DEFAULT) ?: base.license,
76 | summary = merge(base.summary, this.summary)
77 | )
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/LicenseConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.data.DataUtils
4 | import io.github.smiley4.ktoropenapi.data.LicenseData
5 |
6 | /**
7 | * License information for the exposed API.
8 | */
9 | @OpenApiDslMarker
10 | class LicenseConfig internal constructor() {
11 |
12 | /**
13 | * The license name used for the API
14 | */
15 | var name: String? = LicenseData.DEFAULT.name
16 |
17 |
18 | /**
19 | * A URL to the license used for the API. MUST be in the format of a URL.
20 | */
21 | var url: String? = LicenseData.DEFAULT.url
22 |
23 |
24 | /**
25 | * An SPDX (https://spdx.org/licenses/) license expression for the API. The identifier field is mutually exclusive of the url field.
26 | */
27 | var identifier: String? = LicenseData.DEFAULT.identifier
28 |
29 | /**
30 | * Build the data object for this config.
31 | * @param base the base config to "inherit" from. Values from the base should be copied, replaced or merged together.
32 | */
33 | internal fun build(base: LicenseData) = LicenseData(
34 | name = DataUtils.merge(base.name, name),
35 | url = DataUtils.merge(base.url, url),
36 | identifier = DataUtils.merge(base.identifier, identifier)
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/MultipartBodyConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.config.descriptors.KTypeDescriptor
4 | import io.github.smiley4.ktoropenapi.config.descriptors.SwaggerTypeDescriptor
5 | import io.github.smiley4.ktoropenapi.config.descriptors.TypeDescriptor
6 | import io.github.smiley4.ktoropenapi.data.MultipartBodyData
7 | import io.swagger.v3.oas.models.media.Schema
8 | import kotlin.reflect.KType
9 | import kotlin.reflect.typeOf
10 |
11 |
12 | /**
13 | * Describes a single request/response body with multipart content.
14 | * See https://swagger.io/docs/specification/describing-request-body/multipart-requests/ for more info
15 | */
16 | @OpenApiDslMarker
17 | class MultipartBodyConfig : BaseBodyConfig() {
18 |
19 | private val parts = mutableListOf()
20 |
21 |
22 | /**
23 | * One part of a multipart-body
24 | */
25 | fun part(name: String, type: TypeDescriptor, block: MultipartPartConfig.() -> Unit = {}) {
26 | parts.add(MultipartPartConfig(name, type).apply(block))
27 | }
28 |
29 |
30 | /**
31 | * One part of a multipart-body
32 | */
33 | fun part(name: String, type: Schema<*>, block: MultipartPartConfig.() -> Unit = {}) = part(name, SwaggerTypeDescriptor(type), block)
34 |
35 |
36 | /**
37 | * One part of a multipart-body
38 | */
39 | fun part(name: String, type: KType, block: MultipartPartConfig.() -> Unit = {}) = part(name, KTypeDescriptor(type), block)
40 |
41 |
42 | /**
43 | * One part of a multipart-body
44 | */
45 | inline fun part(name: String, noinline block: MultipartPartConfig.() -> Unit = {}) =
46 | part(name, KTypeDescriptor(typeOf()), block)
47 |
48 |
49 | override fun build() = MultipartBodyData(
50 | description = description,
51 | required = required ?: false,
52 | mediaTypes = mediaTypes.toSet(),
53 | parts = parts.map { it.build() }
54 | )
55 | }
56 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/MultipartPartConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.config.descriptors.KTypeDescriptor
4 | import io.github.smiley4.ktoropenapi.config.descriptors.SwaggerTypeDescriptor
5 | import io.github.smiley4.ktoropenapi.config.descriptors.TypeDescriptor
6 | import io.github.smiley4.ktoropenapi.data.MultipartPartData
7 | import io.ktor.http.ContentType
8 | import io.swagger.v3.oas.models.media.Schema
9 | import kotlin.reflect.KType
10 | import kotlin.reflect.typeOf
11 |
12 | /**
13 | * Describes one section of a multipart-body.
14 | * See https://swagger.io/docs/specification/describing-request-body/multipart-requests/ for more info
15 | */
16 | @OpenApiDslMarker
17 | class MultipartPartConfig internal constructor(
18 | /**
19 | * The name of this part
20 | */
21 | val name: String,
22 |
23 | val type: TypeDescriptor
24 | ) {
25 |
26 | /**
27 | * Whether this part is required
28 | */
29 | var required: Boolean = false
30 |
31 | /**
32 | * Specific content types for this part
33 | */
34 | var mediaTypes: Collection = setOf()
35 |
36 | /**
37 | * Set specific content types for this part
38 | */
39 | fun mediaTypes(types: Collection) {
40 | this.mediaTypes = types
41 | }
42 |
43 | /**
44 | * Set specific content types for this part
45 | */
46 | fun mediaTypes(vararg types: ContentType) {
47 | this.mediaTypes = types.toList()
48 | }
49 |
50 | /**
51 | * List of headers of this part
52 | */
53 | val headers = mutableMapOf()
54 |
55 |
56 | /**
57 | * Possible headers for this part
58 | */
59 | fun header(name: String, type: TypeDescriptor, block: HeaderConfig.() -> Unit = {}) {
60 | headers[name] = HeaderConfig().apply(block).apply {
61 | this.type = type
62 | }
63 | }
64 |
65 |
66 | /**
67 | * Possible headers for this part
68 | */
69 | fun header(name: String, type: Schema<*>, block: HeaderConfig.() -> Unit = {}) = header(name, SwaggerTypeDescriptor(type), block)
70 |
71 |
72 | /**
73 | * Possible headers for this part
74 | */
75 | fun header(name: String, type: KType, block: HeaderConfig.() -> Unit = {}) = header(name, KTypeDescriptor(type), block)
76 |
77 |
78 | /**
79 | * Possible headers for this part
80 | */
81 | inline fun header(name: String, noinline block: HeaderConfig.() -> Unit = {}) =
82 | header(name, KTypeDescriptor(typeOf()), block)
83 |
84 | /**
85 | * Build the data object for this config.
86 | */
87 | internal fun build() = MultipartPartData(
88 | name = name,
89 | type = type,
90 | required = required,
91 | mediaTypes = mediaTypes.toSet(),
92 | headers = headers.mapValues { it.value.build() }
93 | )
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/OpenApiDslMarker.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | @DslMarker
4 | annotation class OpenApiDslMarker
5 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/OpenIdOAuthFlowConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.data.DataUtils.merge
4 | import io.github.smiley4.ktoropenapi.data.OpenIdOAuthFlowData
5 |
6 | /**
7 | * Configuration details for a supported OAuth Flow
8 | */
9 | @OpenApiDslMarker
10 | class OpenIdOAuthFlowConfig internal constructor() {
11 |
12 | /**
13 | * The authorization URL to be used for this flow
14 | */
15 | var authorizationUrl: String? = null
16 |
17 | /**
18 | * The token URL to be used for this flow
19 | */
20 | var tokenUrl: String? = null
21 |
22 | /**
23 | * The URL to be used for obtaining refresh tokens
24 | */
25 | var refreshUrl: String? = null
26 |
27 | /**
28 | * The available scopes for the OAuth2 security scheme. A map between the scope name and a short description for it
29 | */
30 | var scopes: Map? = null
31 |
32 | /**
33 | * Build the data object for this config.
34 | * @param base the base config to "inherit" from. Values from the base should be copied, replaced or merged together.
35 | */
36 | internal fun build(base: OpenIdOAuthFlowData) = OpenIdOAuthFlowData(
37 | authorizationUrl = merge(base.authorizationUrl, authorizationUrl),
38 | tokenUrl = merge(base.tokenUrl, tokenUrl),
39 | refreshUrl = merge(base.refreshUrl, refreshUrl),
40 | scopes = merge(base.scopes, scopes),
41 | )
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/OpenIdOAuthFlowsConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.data.OpenIdOAuthFlowData
4 | import io.github.smiley4.ktoropenapi.data.OpenIdOAuthFlowsData
5 |
6 | /**
7 | * An object containing configuration information for the oauth flow types supported
8 | */
9 | @OpenApiDslMarker
10 | class OpenIdOAuthFlowsConfig internal constructor() {
11 |
12 | private var implicit: OpenIdOAuthFlowConfig? = null
13 |
14 |
15 | /**
16 | * Configuration for the OAuth Implicit flow
17 | */
18 | fun implicit(block: OpenIdOAuthFlowConfig.() -> Unit) {
19 | implicit = OpenIdOAuthFlowConfig().apply(block)
20 | }
21 |
22 |
23 | private var password: OpenIdOAuthFlowConfig? = null
24 |
25 |
26 | /**
27 | * Configuration for the OAuth Resource Owner Password flow
28 | */
29 | fun password(block: OpenIdOAuthFlowConfig.() -> Unit) {
30 | password = OpenIdOAuthFlowConfig().apply(block)
31 | }
32 |
33 |
34 | private var clientCredentials: OpenIdOAuthFlowConfig? = null
35 |
36 |
37 | /**
38 | * Configuration for the OAuth Client Credentials flow.
39 | */
40 | fun clientCredentials(block: OpenIdOAuthFlowConfig.() -> Unit) {
41 | clientCredentials = OpenIdOAuthFlowConfig().apply(block)
42 | }
43 |
44 |
45 | private var authorizationCode: OpenIdOAuthFlowConfig? = null
46 |
47 |
48 | /**
49 | * Configuration for the OAuth Authorization Code flow.
50 | */
51 | fun authorizationCode(block: OpenIdOAuthFlowConfig.() -> Unit) {
52 | authorizationCode = OpenIdOAuthFlowConfig().apply(block)
53 | }
54 |
55 | /**
56 | * Build the data object for this config.
57 | * @param base the base config to "inherit" from. Values from the base should be copied, replaced or merged together.
58 | */
59 | internal fun build(base: OpenIdOAuthFlowsData) = OpenIdOAuthFlowsData(
60 | implicit = implicit?.build(base.implicit ?: OpenIdOAuthFlowData.DEFAULT) ?: base.implicit,
61 | password = password?.build(base.password ?: OpenIdOAuthFlowData.DEFAULT) ?: base.password,
62 | clientCredentials = clientCredentials?.build(base.clientCredentials ?: OpenIdOAuthFlowData.DEFAULT) ?: base.clientCredentials,
63 | authorizationCode = authorizationCode?.build(base.authorizationCode ?: OpenIdOAuthFlowData.DEFAULT) ?: base.authorizationCode,
64 | )
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/OutputFormat.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | enum class OutputFormat(val empty: String) {
4 | JSON("{}"),
5 | YAML("")
6 | }
7 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/ParameterLocation.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | /**
4 | * Locations for request parameters.
5 | */
6 | enum class ParameterLocation {
7 | QUERY, HEADER, PATH, COOKIE
8 | }
9 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/PathFilter.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.ktor.http.HttpMethod
4 |
5 | /**
6 | * Filters paths to determine which to include (return 'true') in the spec and which to hide (return 'true').
7 | */
8 | typealias PathFilter = (method: HttpMethod, url: List) -> Boolean
9 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/PostBuild.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.swagger.v3.oas.models.OpenAPI
4 |
5 | /**
6 | * Function executed after building the openapi-spec.
7 | */
8 | typealias PostBuild = (openApi: OpenAPI, specName: String) -> Unit
9 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/RequestParameterConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.config.descriptors.ExampleDescriptor
4 | import io.github.smiley4.ktoropenapi.config.descriptors.SwaggerExampleDescriptor
5 | import io.github.smiley4.ktoropenapi.config.descriptors.ValueExampleDescriptor
6 | import io.github.smiley4.ktoropenapi.config.descriptors.TypeDescriptor
7 | import io.github.smiley4.ktoropenapi.data.RequestParameterData
8 | import io.swagger.v3.oas.models.examples.Example
9 | import io.swagger.v3.oas.models.parameters.Parameter
10 |
11 | /**
12 | * Describes a single request parameter.
13 | */
14 | @OpenApiDslMarker
15 | class RequestParameterConfig internal constructor(
16 | /**
17 | * The name (case-sensitive) of the parameter
18 | */
19 | val name: String,
20 | /**
21 | * The type defining the schema used for the parameter.
22 | */
23 | val type: TypeDescriptor,
24 | /**
25 | * Location of the parameter
26 | */
27 | val location: ParameterLocation
28 | ) {
29 |
30 | /**
31 | * A brief description of the parameter
32 | */
33 | var description: String? = null
34 |
35 |
36 | /**
37 | * An example value for this parameter
38 | */
39 | internal var example: ExampleDescriptor? = null
40 |
41 |
42 | /**
43 | * An example value for this parameter
44 | */
45 | fun example(example: ExampleDescriptor) {
46 | this.example = example
47 | }
48 |
49 |
50 | /**
51 | * An example value for this parameter
52 | */
53 | fun example(name: String, example: Example) = example(SwaggerExampleDescriptor(name, example))
54 |
55 |
56 | /**
57 | * An example value for this parameter
58 | */
59 | fun example(name: String, example: ValueExampleDescriptorConfig.() -> Unit) = example(
60 | ValueExampleDescriptorConfig()
61 | .apply(example)
62 | .let { result ->
63 | ValueExampleDescriptor(
64 | name = name,
65 | value = result.value,
66 | summary = result.summary,
67 | description = result.description
68 | )
69 | }
70 | )
71 |
72 |
73 | /**
74 | * Determines whether this parameter is mandatory
75 | */
76 | var required: Boolean? = null
77 |
78 |
79 | /**
80 | * Specifies that a parameter is deprecated and SHOULD be transitioned out of usage
81 | */
82 | var deprecated: Boolean? = null
83 |
84 |
85 | /**
86 | * Sets the ability to pass empty-valued parameters.
87 | * This is valid only for query parameters and allows sending a parameter with an empty value.
88 | */
89 | var allowEmptyValue: Boolean? = null
90 |
91 |
92 | /**
93 | * When this is true, parameter values of type array or object generate separate parameters for each value of the array or key-value
94 | * pair of the map. For other types of parameters this property has no effect
95 | */
96 | var explode: Boolean? = null
97 |
98 |
99 | /**
100 | * Determines whether the parameter value SHOULD allow reserved characters, as defined by RFC3986 :/?#[]@!$&'()*+,;= to be included
101 | * without percent-encoding. This property only applies to parameters with an in value of query
102 | */
103 | var allowReserved: Boolean? = null
104 |
105 |
106 | /**
107 | * Describes how the parameter value will be serialized depending on the type of the parameter value.
108 | */
109 | var style: Parameter.StyleEnum? = null
110 |
111 |
112 | /**
113 | * Don't include this parameter in the openapi-spec
114 | */
115 | var hidden: Boolean = false
116 |
117 |
118 | /**
119 | * Build the data object for this config.
120 | */
121 | internal fun build() = RequestParameterData(
122 | name = name,
123 | type = type,
124 | location = location,
125 | description = description,
126 | example = example,
127 | required = required ?: (location == ParameterLocation.PATH),
128 | deprecated = deprecated ?: false,
129 | allowEmptyValue = allowEmptyValue,
130 | explode = explode ?: false,
131 | allowReserved = allowReserved,
132 | style = style,
133 | hidden = hidden
134 | )
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/ResponseConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.config.descriptors.KTypeDescriptor
4 | import io.github.smiley4.ktoropenapi.config.descriptors.SwaggerTypeDescriptor
5 | import io.github.smiley4.ktoropenapi.config.descriptors.TypeDescriptor
6 | import io.github.smiley4.ktoropenapi.data.ResponseData
7 | import io.swagger.v3.oas.models.media.Schema
8 | import kotlin.reflect.KType
9 | import kotlin.reflect.typeOf
10 |
11 | /**
12 | * A container for the expected responses of an operation. The container maps an HTTP response code to the expected response.
13 | * A response code can only have one response object.
14 | */
15 | @OpenApiDslMarker
16 | class ResponseConfig internal constructor(val statusCode: String) {
17 |
18 | /**
19 | * A short description of the response
20 | */
21 | var description: String? = null
22 |
23 | private val headers = mutableMapOf()
24 |
25 |
26 | /**
27 | * Possible headers returned with this response
28 | */
29 | fun header(name: String, type: TypeDescriptor, block: HeaderConfig.() -> Unit = {}) {
30 | headers[name] = HeaderConfig().apply(block).apply {
31 | this.type = type
32 | }
33 | }
34 |
35 |
36 | /**
37 | * Possible headers returned with this response
38 | */
39 | fun header(name: String, type: Schema<*>, block: HeaderConfig.() -> Unit = {}) = header(name, SwaggerTypeDescriptor(type), block)
40 |
41 |
42 | /**
43 | * Possible headers returned with this response
44 | */
45 | fun header(name: String, type: KType, block: HeaderConfig.() -> Unit = {}) = header(name, KTypeDescriptor(type), block)
46 |
47 |
48 | /**
49 | * Possible headers returned with this response
50 | */
51 | inline fun header(name: String, noinline block: HeaderConfig.() -> Unit = {}) =
52 | header(name, KTypeDescriptor(typeOf()), block)
53 |
54 |
55 | private var body: BaseBodyConfig? = null
56 |
57 |
58 | /**
59 | * The body returned with this response
60 | */
61 | fun body(type: TypeDescriptor, block: SimpleBodyConfig.() -> Unit = {}) {
62 | body = SimpleBodyConfig(type).apply(block)
63 | }
64 |
65 |
66 | /**
67 | * The body returned with this response
68 | */
69 | fun body(type: Schema<*>, block: SimpleBodyConfig.() -> Unit = {}) = body(SwaggerTypeDescriptor(type), block)
70 |
71 |
72 | /**
73 | * The body returned with this response
74 | */
75 | fun body(type: KType, block: SimpleBodyConfig.() -> Unit = {}) = body(KTypeDescriptor(type), block)
76 |
77 |
78 | /**
79 | * The body returned with this response
80 | */
81 | inline fun body(noinline block: SimpleBodyConfig.() -> Unit = {}) = body(KTypeDescriptor(typeOf()), block)
82 |
83 |
84 | /**
85 | * The multipart-body returned with this response
86 | */
87 | fun multipartBody(block: MultipartBodyConfig.() -> Unit) {
88 | body = MultipartBodyConfig().apply(block)
89 | }
90 |
91 |
92 | /**
93 | * Don't include this response in the openapi-spec
94 | */
95 | var hidden: Boolean = false
96 |
97 |
98 | /**
99 | * Build the data object for this config.
100 | */
101 | internal fun build() = ResponseData(
102 | statusCode = statusCode,
103 | description = description,
104 | hidden = hidden,
105 | headers = headers.mapValues { it.value.build() },
106 | body = body?.build()
107 | )
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/ResponsesConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.ktor.http.HttpStatusCode
4 |
5 | /**
6 | * All possible responses of an operation
7 | */
8 | @OpenApiDslMarker
9 | class ResponsesConfig internal constructor() {
10 |
11 | private val responses = mutableMapOf()
12 |
13 |
14 | /**
15 | * Information of response for a given http status code
16 | */
17 | infix fun String.to(block: ResponseConfig.() -> Unit) {
18 | responses[this] = ResponseConfig(this).apply(block)
19 | }
20 |
21 |
22 | /**
23 | * Information of response for a given http status code
24 | */
25 | infix fun HttpStatusCode.to(block: ResponseConfig.() -> Unit) = this.value.toString() to block
26 |
27 | /**
28 | * Information of response for a given http status code
29 | */
30 | fun code(statusCode: String, block: ResponseConfig.() -> Unit) {
31 | responses[statusCode] = ResponseConfig(statusCode).apply(block)
32 | }
33 |
34 | /**
35 | * Information of response for a given http status code
36 | */
37 | fun code(statusCode: HttpStatusCode, block: ResponseConfig.() -> Unit) = code(statusCode.value.toString(), block)
38 |
39 |
40 | /**
41 | * Information of the default response
42 | */
43 | fun default(block: ResponseConfig.() -> Unit) = "default" to block
44 |
45 |
46 | /**
47 | * Add the given response.
48 | */
49 | internal fun addResponse(response: ResponseConfig) {
50 | responses[response.statusCode] = response
51 | }
52 |
53 |
54 | fun getResponses() = responses.values.toList()
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/SchemaConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.config.descriptors.KTypeDescriptor
4 | import io.github.smiley4.ktoropenapi.config.descriptors.SwaggerTypeDescriptor
5 | import io.github.smiley4.ktoropenapi.config.descriptors.TypeDescriptor
6 | import io.github.smiley4.ktoropenapi.data.*
7 | import io.swagger.v3.oas.models.media.Schema
8 | import kotlin.reflect.KType
9 | import kotlin.reflect.typeOf
10 |
11 | /**
12 | * Configuration for schemas
13 | */
14 | @OpenApiDslMarker
15 | class SchemaConfig internal constructor() {
16 |
17 | /**
18 | * The json-schema generator for all schemas. See https://github.com/SMILEY4/schema-kenerator/wiki for more information.
19 | */
20 | var generator: GenericSchemaGenerator = SchemaConfigData.DEFAULT.generator
21 |
22 | private val schemas = mutableMapOf()
23 |
24 | /**
25 | * Add a shared schema that can be referenced by all routes by the given id.
26 | */
27 | fun schema(schemaId: String, descriptor: TypeDescriptor) {
28 | schemas[schemaId] = descriptor
29 | }
30 |
31 | /**
32 | * Add a shared schema that can be referenced by all routes by the given id.
33 | */
34 | fun schema(schemaId: String, schema: Schema<*>) = schema(schemaId, SwaggerTypeDescriptor(schema))
35 |
36 | /**
37 | * Add a shared schema that can be referenced by all routes by the given id.
38 | */
39 | fun schema(schemaId: String, schema: KType) = schema(schemaId, KTypeDescriptor(schema))
40 |
41 | /**
42 | * Add a shared schema that can be referenced by all routes by the given id.
43 | */
44 | inline fun schema(schemaId: String) = schema(schemaId, KTypeDescriptor(typeOf()))
45 |
46 | /**
47 | * Build the data object for this config.
48 | * @param securityConfig configuration that might contain additional schemas
49 | */
50 | internal fun build(securityConfig: SecurityData) = SchemaConfigData(
51 | generator = generator,
52 | schemas = schemas,
53 | securitySchemas = securityConfig.defaultUnauthorizedResponse?.body?.let { body ->
54 | when (body) {
55 | is SimpleBodyData -> listOf(body.type)
56 | is MultipartBodyData -> body.parts.map { it.type }
57 | }
58 | } ?: emptyList()
59 | )
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/SecurityConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.data.DataUtils.merge
4 | import io.github.smiley4.ktoropenapi.data.SecurityData
5 | import io.github.smiley4.ktoropenapi.data.SecuritySchemeData
6 | import io.ktor.http.HttpStatusCode
7 |
8 | /**
9 | * Configuration for security and authentication.
10 | */
11 | @OpenApiDslMarker
12 | class SecurityConfig internal constructor() {
13 |
14 | /**
15 | * Default response to automatically add to each protected route for the "Unauthorized"-Response-Code.
16 | * Generated response can be overwritten with custom response.
17 | */
18 | fun defaultUnauthorizedResponse(block: ResponseConfig.() -> Unit) {
19 | defaultUnauthorizedResponse = ResponseConfig(HttpStatusCode.Unauthorized.value.toString()).apply(block)
20 | }
21 |
22 | private var defaultUnauthorizedResponse: ResponseConfig? = null
23 |
24 |
25 | /**
26 | * The names of the security schemes available for use for the protected paths
27 | */
28 | var defaultSecuritySchemeNames: Collection? = SecurityData.DEFAULT.defaultSecuritySchemeNames
29 |
30 | /**
31 | * Set the names of the security schemes available for use for the protected paths
32 | */
33 | fun defaultSecuritySchemeNames(names: Collection) {
34 | this.defaultSecuritySchemeNames = names
35 | }
36 |
37 | /**
38 | * Set the names of the security schemes available for use for the protected paths
39 | */
40 | fun defaultSecuritySchemeNames(vararg names: String) {
41 | this.defaultSecuritySchemeNames = names.toList()
42 | }
43 |
44 | /**
45 | * Defines security schemes that can be used by operations
46 | */
47 | fun securityScheme(name: String, block: SecuritySchemeConfig.() -> Unit) {
48 | securitySchemes.add(SecuritySchemeConfig(name).apply(block))
49 | }
50 |
51 | private val securitySchemes = mutableListOf()
52 |
53 | /**
54 | * Build the data object for this config.
55 | * @param base the base config to "inherit" from. Values from the base should be copied, replaced or merged together.
56 | */
57 | internal fun build(base: SecurityData) = SecurityData(
58 | defaultUnauthorizedResponse = merge(base.defaultUnauthorizedResponse, defaultUnauthorizedResponse?.build()),
59 | defaultSecuritySchemeNames = buildSet {
60 | addAll(base.defaultSecuritySchemeNames)
61 | defaultSecuritySchemeNames?.also { addAll(it) }
62 | },
63 | securitySchemes = buildList {
64 | addAll(base.securitySchemes)
65 | addAll(securitySchemes.map { it.build(SecuritySchemeData.DEFAULT) })
66 | }
67 | )
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/SecuritySchemeConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.data.DataUtils.merge
4 | import io.github.smiley4.ktoropenapi.data.OpenIdOAuthFlowsData
5 | import io.github.smiley4.ktoropenapi.data.SecuritySchemeData
6 |
7 |
8 | /**
9 | * Defines a security scheme that can be used by the operations. Supported schemes are HTTP authentication, an API key (either as a header,
10 | * a cookie parameter or as a query parameter), OAuth2's common flows (implicit, password, client credentials and authorization code)
11 | */
12 | @OpenApiDslMarker
13 | class SecuritySchemeConfig internal constructor(
14 | /**
15 | * The name of the security scheme.
16 | */
17 | private val schemeName: String
18 | ) {
19 |
20 | /**
21 | * The type of the security scheme
22 | */
23 | var type: AuthType? = null
24 |
25 | /**
26 | * The name scheme and of the header, query or cookie parameter to be used.
27 | */
28 | var name: String? = null
29 |
30 | /**
31 | * The location of the API key (OpenAPI 'in').
32 | * Required for type [AuthType.API_KEY]
33 | */
34 | var location: AuthKeyLocation? = null
35 |
36 |
37 | /**
38 | * The name of the HTTP Authorization scheme to be used.
39 | * Required for type [AuthType.HTTP]
40 | */
41 | var scheme: AuthScheme? = null
42 |
43 |
44 | /**
45 | * A hint to the client to identify how the bearer token is formatted.
46 | * Used for type [AuthType.HTTP] and schema [AuthScheme.BEARER]
47 | */
48 | var bearerFormat: String? = null
49 |
50 | private var flows: OpenIdOAuthFlowsConfig? = null
51 |
52 |
53 | /**
54 | * Information for the oauth flow types supported.
55 | * Required for type [AuthType.OAUTH2]
56 | */
57 | fun flows(block: OpenIdOAuthFlowsConfig.() -> Unit) {
58 | flows = OpenIdOAuthFlowsConfig().apply(block)
59 | }
60 |
61 |
62 | /**
63 | * OpenId Connect URL to discover OAuth2 configuration values.
64 | * Required for type [AuthType.OPENID_CONNECT]
65 | */
66 | var openIdConnectUrl: String? = null
67 |
68 |
69 | /**
70 | * A short description of the security scheme.
71 | */
72 | var description: String? = null
73 |
74 | /**
75 | * Build the data object for this config.
76 | * @param base the base config to "inherit" from. Values from the base should be copied, replaced or merged together.
77 | */
78 | internal fun build(base: SecuritySchemeData) = SecuritySchemeData(
79 | schemeName = schemeName,
80 | type = merge(base.type, type),
81 | name = merge(base.name, name),
82 | location = merge(base.location, location),
83 | scheme = merge(base.scheme, scheme),
84 | bearerFormat = merge(base.bearerFormat, bearerFormat),
85 | flows = flows?.build(base.flows ?: OpenIdOAuthFlowsData.DEFAULT) ?: base.flows,
86 | openIdConnectUrl = merge(base.openIdConnectUrl, openIdConnectUrl),
87 | description = merge(base.description, description),
88 | )
89 | }
90 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/ServerConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.data.DataUtils.merge
4 | import io.github.smiley4.ktoropenapi.data.DataUtils.mergeDefault
5 | import io.github.smiley4.ktoropenapi.data.ServerData
6 |
7 | /**
8 | * An object representing a Server.
9 | */
10 | @OpenApiDslMarker
11 | class ServerConfig internal constructor() {
12 |
13 | /**
14 | * A URL to the target host. This URL supports Server Variables and MAY be relative, to indicate that the host location is relative to
15 | * the location where the OpenAPI document is being served
16 | */
17 | var url: String = ServerData.DEFAULT.url
18 |
19 | /**
20 | * An optional string describing the host designated by the URL
21 | */
22 | var description: String? = ServerData.DEFAULT.description
23 |
24 | private val variables = mutableMapOf()
25 |
26 |
27 | /**
28 | * Adds a new server variable with the given name
29 | */
30 | fun variable(name: String, block: ServerVariableConfig.() -> Unit) {
31 | variables[name] = ServerVariableConfig(name).apply(block)
32 | }
33 |
34 | /**
35 | * Build the data object for this config.
36 | * @param base the base config to "inherit" from. Values from the base should be copied, replaced or merged together.
37 | */
38 | internal fun build(base: ServerData) = ServerData(
39 | url = mergeDefault(base.url, url, ServerData.DEFAULT.url),
40 | description = merge(base.description, description),
41 | variables = buildMap {
42 | base.variables.forEach { this[it.name] = it }
43 | variables.values.map { it.build() }.forEach { this[it.name] = it }
44 | }.values.toList()
45 | )
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/ServerVariableConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.data.ServerVariableData
4 |
5 | /**
6 | * An object representing a Server Variable for server URL template substitution.
7 | */
8 | @OpenApiDslMarker
9 | class ServerVariableConfig internal constructor(
10 | /**
11 | * The name of this variable
12 | */
13 | private val name: String
14 | ) {
15 |
16 | /**
17 | * An enumeration of string values to be used if the substitution options are from a limited set. Must not be empty.
18 | */
19 | var enum: Collection = emptyList()
20 |
21 |
22 | /**
23 | * The default value to use for substitution. Must be in the list of enums.
24 | */
25 | var default: String? = null
26 |
27 |
28 | /**
29 | * An optional description for this server variable.
30 | */
31 | var description: String? = null
32 |
33 | /**
34 | * Build the data object for this config.
35 | */
36 | internal fun build() = ServerVariableData(
37 | name = name,
38 | enum = enum.toSet(),
39 | default = (default ?: enum.firstOrNull()) ?: "",
40 | description = description
41 | )
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/SimpleBodyConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.config.descriptors.ExampleDescriptor
4 | import io.github.smiley4.ktoropenapi.config.descriptors.RefExampleDescriptor
5 | import io.github.smiley4.ktoropenapi.config.descriptors.SwaggerExampleDescriptor
6 | import io.github.smiley4.ktoropenapi.config.descriptors.ValueExampleDescriptor
7 | import io.github.smiley4.ktoropenapi.config.descriptors.TypeDescriptor
8 | import io.github.smiley4.ktoropenapi.data.SimpleBodyData
9 | import io.swagger.v3.oas.models.examples.Example
10 |
11 |
12 | /**
13 | * Describes the base of a single request/response body.
14 | */
15 | @OpenApiDslMarker
16 | class SimpleBodyConfig internal constructor(
17 | /**
18 | * The type defining the schema used for the body.
19 | */
20 | val type: TypeDescriptor,
21 | ) : BaseBodyConfig() {
22 |
23 | /**
24 | * Examples for this body
25 | */
26 | private val examples = mutableListOf()
27 |
28 | /**
29 | * Add the given example as an example to this body
30 | */
31 | fun example(example: ExampleDescriptor) {
32 | examples.add(example)
33 | }
34 |
35 | /**
36 | * Add the given example as an example to this body
37 | */
38 | fun example(name: String, example: Example) = example(SwaggerExampleDescriptor(name, example))
39 |
40 | /**
41 | * Add the given example as an example to this body
42 | */
43 | fun example(name: String, example: ValueExampleDescriptorConfig.() -> Unit) = example(
44 | ValueExampleDescriptorConfig()
45 | .apply(example)
46 | .let { result ->
47 | ValueExampleDescriptor(
48 | name = name,
49 | value = result.value,
50 | summary = result.summary,
51 | description = result.description
52 | )
53 | }
54 | )
55 |
56 |
57 | /**
58 | * Add the given example as an example to this body
59 | * @param name the name of the example to display at this body
60 | * @param refName the name of the referenced example
61 | */
62 | fun exampleRef(name: String, refName: String) = example(RefExampleDescriptor(name, refName))
63 |
64 | /**
65 | * Add the given example as an example to this body
66 | * @param name the name of the example
67 | */
68 | fun exampleRef(name: String) = example(RefExampleDescriptor(name, name))
69 |
70 | override fun build() = SimpleBodyData(
71 | description = description,
72 | required = required ?: false,
73 | mediaTypes = mediaTypes.toSet(),
74 | type = type,
75 | examples = examples,
76 | )
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/SpecAssigner.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | /**
4 | * Assigns (unassigned) routes to api-specs.
5 | * url - the parts of the route-url split at all `/`.
6 | * tags - the tags assigned to the route
7 | */
8 | typealias SpecAssigner = (url: String, tags: List) -> String
9 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/TagConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.data.DataUtils.merge
4 | import io.github.smiley4.ktoropenapi.data.TagData
5 |
6 | /**
7 | * Adds metadata to a single tag.
8 | */
9 | @OpenApiDslMarker
10 | class TagConfig internal constructor(
11 | /**
12 | * The name of the tag.
13 | */
14 | var name: String
15 | ) {
16 |
17 | /**
18 | * A short description for the tag.
19 | */
20 | var description: String? = null
21 |
22 | /**
23 | * A short description of additional external documentation for this tag.
24 | */
25 | var externalDocDescription: String? = null
26 |
27 | /**
28 | * The URL for additional external documentation for this tag.
29 | */
30 | var externalDocUrl: String? = null
31 |
32 |
33 | internal fun build(base: TagData) = TagData(
34 | name = name,
35 | description = merge(base.description, description),
36 | externalDocDescription = merge(base.externalDocDescription, externalDocDescription),
37 | externalDocUrl = merge(base.externalDocUrl, externalDocUrl)
38 | )
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/TagGenerator.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | /**
4 | * Generates additional tags for routes.
5 | * url - the parts of the route-url split at all `/`.
6 | * return a collection of tags. "Null"-entries will be ignored.
7 | */
8 | typealias TagGenerator = (url: List) -> Collection
9 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/TagsConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | import io.github.smiley4.ktoropenapi.data.DataUtils.merge
4 | import io.github.smiley4.ktoropenapi.data.TagData
5 | import io.github.smiley4.ktoropenapi.data.TagsData
6 |
7 | /**
8 | * Configuration for tags
9 | */
10 | @OpenApiDslMarker
11 | class TagsConfig internal constructor() {
12 |
13 | private val tags = mutableListOf()
14 |
15 |
16 | /**
17 | * Tags used by the specification with additional metadata. Not all tags that are used must be declared
18 | */
19 | fun tag(name: String, block: TagConfig.() -> Unit) {
20 | tags.add(TagConfig(name).apply(block))
21 | }
22 |
23 |
24 | /**
25 | * Automatically add tags to the route with the given url.
26 | * The returned (non-null) tags will be added to the tags specified in the route-specific documentation.
27 | */
28 | var tagGenerator: TagGenerator = TagsData.DEFAULT.generator
29 |
30 | /**
31 | * Build the data object for this config.
32 | * @param base the base config to "inherit" from. Values from the base should be copied, replaced or merged together.
33 | */
34 | internal fun build(base: TagsData) = TagsData(
35 | tags = buildList {
36 | addAll(base.tags)
37 | addAll(tags.map { it.build(TagData.DEFAULT) })
38 | },
39 | generator = merge(base.generator, tagGenerator) ?: TagsData.DEFAULT.generator,
40 | )
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/ValueExampleDescriptorConfig.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config
2 |
3 | class ValueExampleDescriptorConfig {
4 |
5 | /**
6 | * the example value
7 | */
8 | var value: Any? = null
9 |
10 |
11 | /**
12 | * a short summary of the example
13 | */
14 | var summary: String? = null
15 |
16 |
17 | /**
18 | * a description of the example
19 | */
20 | var description: String? = null
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/descriptors/ExampleDescriptor.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config.descriptors
2 |
3 | import io.swagger.v3.oas.models.examples.Example
4 |
5 | /**
6 | * Identifier and description of an example
7 | */
8 | sealed class ExampleDescriptor(
9 | val name: String,
10 | )
11 |
12 |
13 | /**
14 | * Describes an example as an object.
15 | */
16 | class ValueExampleDescriptor(
17 | name: String,
18 | val value: Any?,
19 | val summary: String? = null,
20 | val description: String? = null,
21 | ) : ExampleDescriptor(name)
22 |
23 |
24 | /**
25 | * Describes a reference to a shared example placed in the components section
26 | * @param name the name of the example in the operation
27 | * @param refName the name/id of the example to reference in the components section
28 | */
29 | class RefExampleDescriptor(name: String, val refName: String) : ExampleDescriptor(name)
30 |
31 |
32 | /**
33 | * Describes an example as a swagger [Example]-object
34 | */
35 | class SwaggerExampleDescriptor(name: String, val example: Example) : ExampleDescriptor(name)
36 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/config/descriptors/TypeDescriptor.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.config.descriptors
2 |
3 | import io.swagger.v3.oas.models.media.Schema
4 | import kotlinx.serialization.descriptors.SerialDescriptor
5 | import kotlin.reflect.KType
6 | import kotlin.reflect.typeOf
7 |
8 | /**
9 | * Describes and identifies types and schemas.
10 | */
11 | sealed interface TypeDescriptor
12 |
13 |
14 | /**
15 | * Describes a type from a swagger [Schema]
16 | */
17 | class SwaggerTypeDescriptor(val schema: Schema<*>) : TypeDescriptor
18 |
19 | /**
20 | * Describes a type from a kotlin [KType]
21 | */
22 | class KTypeDescriptor(val type: KType) : TypeDescriptor
23 |
24 |
25 | /**
26 | * Describes a type from a kotlinx-serialization [SerialDescriptor]
27 | */
28 | class SerialTypeDescriptor(val descriptor: SerialDescriptor) : TypeDescriptor
29 |
30 |
31 | /**
32 | * Describes an array of types.
33 | */
34 | class ArrayTypeDescriptor(val type: TypeDescriptor) : TypeDescriptor
35 |
36 |
37 | /**
38 | * Describes an object matching any of the given types.
39 | */
40 | class AnyOfTypeDescriptor(val types: List) : TypeDescriptor
41 |
42 |
43 | /**
44 | * Describes an empty type/schema.
45 | */
46 | class EmptyTypeDescriptor : TypeDescriptor
47 |
48 |
49 | /**
50 | * Describes a reference to a schema in the component section.
51 | */
52 | class RefTypeDescriptor(val schemaId: String) : TypeDescriptor
53 |
54 |
55 | /**
56 | * Create a schema describing the given type parameter.
57 | */
58 | inline fun type() = KTypeDescriptor(typeOf())
59 |
60 |
61 | /**
62 | * Describe an empty / "any" schema.
63 | */
64 | fun empty() = EmptyTypeDescriptor()
65 |
66 |
67 | /**
68 | * Describe a schema referenced by the given id.
69 | */
70 | fun ref(schemaId: String) = RefTypeDescriptor(schemaId)
71 |
72 |
73 | /**
74 | * Describe an array with the given item type.
75 | */
76 | fun array(type: TypeDescriptor) = ArrayTypeDescriptor(type)
77 |
78 |
79 | /**
80 | * Describe an array with the given item type.
81 | */
82 | fun array(type: Schema<*>) = ArrayTypeDescriptor(SwaggerTypeDescriptor(type))
83 |
84 |
85 | /**
86 | * Describe an array with the given item type.
87 | */
88 | fun array(type: KType) = ArrayTypeDescriptor(KTypeDescriptor(type))
89 |
90 |
91 | /**
92 | * Describe an array with the given item type.
93 | */
94 | inline fun array() = ArrayTypeDescriptor(KTypeDescriptor(typeOf()))
95 |
96 |
97 | /**
98 | * Describe any of the given types.
99 | */
100 | fun anyOf(vararg types: TypeDescriptor) = AnyOfTypeDescriptor(types.toList())
101 |
102 |
103 | /**
104 | * Describe any of the given types.
105 | */
106 | fun anyOf(types: Collection) = AnyOfTypeDescriptor(types.toList())
107 |
108 |
109 | /**
110 | * Describe any of the given types.
111 | */
112 | fun anyOf(vararg types: Schema<*>) = AnyOfTypeDescriptor(types.map { SwaggerTypeDescriptor(it) })
113 |
114 |
115 | /**
116 | * Describe any of the given types.
117 | */
118 | @JvmName("anyOfSwagger")
119 | fun anyOf(types: Collection>) = AnyOfTypeDescriptor(types.map { SwaggerTypeDescriptor(it) })
120 |
121 |
122 | /**
123 | * Describe any of the given types.
124 | */
125 | fun anyOf(vararg types: KType) = AnyOfTypeDescriptor(types.map { KTypeDescriptor(it) })
126 |
127 |
128 | /**
129 | * Describe any of the given types.
130 | */
131 | @JvmName("anyOfKType")
132 | fun anyOf(types: Collection) = AnyOfTypeDescriptor(types.map { KTypeDescriptor(it) })
133 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/BaseBodyData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | import io.github.smiley4.ktoropenapi.config.descriptors.ExampleDescriptor
4 | import io.github.smiley4.ktoropenapi.config.descriptors.TypeDescriptor
5 | import io.ktor.http.ContentType
6 |
7 | /**
8 | * The common information for request and response bodies.
9 | */
10 | internal sealed class BaseBodyData(
11 | val description: String?,
12 | val required: Boolean,
13 | val mediaTypes: Set,
14 | )
15 |
16 |
17 | /**
18 | * Information for a "simple" request or response body.
19 | */
20 | internal class SimpleBodyData(
21 | description: String?,
22 | required: Boolean,
23 | mediaTypes: Set,
24 | val type: TypeDescriptor,
25 | val examples: List
26 | ) : BaseBodyData(description, required, mediaTypes)
27 |
28 |
29 | /**
30 | * Information for a multipart request or response body.
31 | */
32 | internal class MultipartBodyData(
33 | description: String?,
34 | required: Boolean,
35 | mediaTypes: Set,
36 | val parts: List
37 | ) : BaseBodyData(description, required, mediaTypes)
38 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/ContactData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | /**
4 | * See [OpenAPI Specification - Contact Object](https://swagger.io/specification/#contact-object).
5 | */
6 | internal data class ContactData(
7 | val name: String?,
8 | val url: String?,
9 | val email: String?
10 | ) {
11 | companion object {
12 | val DEFAULT = ContactData(
13 | name = null,
14 | url = null,
15 | email = null
16 | )
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/DataUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | internal object DataUtils {
4 |
5 | /**
6 | * Merges the two boolean values.
7 | * @return true if "value" is true, value of "base" otherwise
8 | */
9 | fun mergeBoolean(base: Boolean, value: Boolean) = if (value) true else base
10 |
11 | /**
12 | * Merges the two values.
13 | * @return "value" if "value" is different from the given default value, "base" otherwise
14 | */
15 | fun mergeDefault(base: T, value: T, default: T) = if (value != default) value else base
16 |
17 | /**
18 | * Merges the two values.
19 | * @return "value" if "value" is not null, "base" otherwise
20 | */
21 | fun merge(base: T?, value: T?) = value ?: base
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/ExampleConfigData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | import io.github.smiley4.ktoropenapi.config.descriptors.ExampleDescriptor
4 | import io.github.smiley4.ktoropenapi.config.ExampleEncoder
5 | import io.github.smiley4.ktoropenapi.config.GenericExampleEncoder
6 |
7 | internal class ExampleConfigData(
8 | val sharedExamples: Map,
9 | val securityExamples: SimpleBodyData?,
10 | val exampleEncoder: GenericExampleEncoder
11 | ) {
12 |
13 | companion object {
14 | val DEFAULT = ExampleConfigData(
15 | sharedExamples = emptyMap(),
16 | securityExamples = null,
17 | exampleEncoder = ExampleEncoder.internal()
18 | )
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/ExternalDocsData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | /**
4 | * See [OpenAPI Specification - External Documentation Object](https://swagger.io/specification/#external-documentation-object).
5 | */
6 | internal data class ExternalDocsData(
7 | val url: String,
8 | val description: String?,
9 | ) {
10 |
11 | companion object {
12 | val DEFAULT = ExternalDocsData(
13 | url = "/",
14 | description = null
15 | )
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/HeaderData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | import io.github.smiley4.ktoropenapi.config.descriptors.TypeDescriptor
4 |
5 | /**
6 | * See [OpenAPI Specification - Header Object](https://swagger.io/specification/#header-object).
7 | */
8 | internal data class HeaderData(
9 | val description: String?,
10 | val type: TypeDescriptor?,
11 | val required: Boolean,
12 | val deprecated: Boolean,
13 | val explode: Boolean?,
14 | )
15 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/InfoData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | /**
4 | * See [OpenAPI Specification - Info Object](https://swagger.io/specification/#info-object).
5 | */
6 | internal data class InfoData(
7 | val title: String,
8 | val version: String?,
9 | val description: String?,
10 | val summary: String?,
11 | val termsOfService: String?,
12 | val contact: ContactData?,
13 | val license: LicenseData?,
14 | ) {
15 | companion object {
16 | val DEFAULT = InfoData(
17 | title = "API",
18 | version = null,
19 | description = null,
20 | summary = null,
21 | termsOfService = null,
22 | contact = null,
23 | license = null,
24 | )
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/LicenseData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | /**
4 | * See [OpenAPI Specification - License Object](https://swagger.io/specification/#license-object).
5 | */
6 | internal data class LicenseData(
7 | val name: String?,
8 | val url: String?,
9 | val identifier: String?
10 | ) {
11 | companion object {
12 | val DEFAULT = LicenseData(
13 | name = null,
14 | url = null,
15 | identifier = null
16 | )
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/MultipartPartData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | import io.github.smiley4.ktoropenapi.config.descriptors.TypeDescriptor
4 | import io.ktor.http.ContentType
5 |
6 | /**
7 | * Information about a part of a multipart request or response body.
8 | */
9 | internal data class MultipartPartData(
10 | val name: String,
11 | val type: TypeDescriptor,
12 | val required: Boolean,
13 | val mediaTypes: Set,
14 | val headers: Map,
15 | )
16 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/OpenApiPluginData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | import io.github.smiley4.ktoropenapi.config.OpenApiPluginConfig
4 | import io.github.smiley4.ktoropenapi.config.OutputFormat
5 | import io.github.smiley4.ktoropenapi.config.PathFilter
6 | import io.github.smiley4.ktoropenapi.config.PostBuild
7 | import io.github.smiley4.ktoropenapi.config.SpecAssigner
8 | import kotlin.reflect.KClass
9 |
10 | /**
11 | * Complete plugin configuration
12 | */
13 | internal data class OpenApiPluginData(
14 | val specAssigner: SpecAssigner,
15 | val pathFilter: PathFilter,
16 | val ignoredRouteSelectors: Set>,
17 | val ignoredRouteSelectorClassNames: Set,
18 | val info: InfoData,
19 | val servers: List,
20 | val externalDocs: ExternalDocsData,
21 | val specConfigs: MutableMap,
22 | val postBuild: PostBuild?,
23 | val schemaConfig: SchemaConfigData,
24 | val exampleConfig: ExampleConfigData,
25 | val securityConfig: SecurityData,
26 | val tagsConfig: TagsData,
27 | val outputFormat: OutputFormat,
28 | val rootPath: String?,
29 | val autoDocumentResourcesRoutes: Boolean,
30 | ) {
31 |
32 | companion object {
33 | val DEFAULT = OpenApiPluginData(
34 | specAssigner = { _, _ -> OpenApiPluginConfig.DEFAULT_SPEC_ID },
35 | pathFilter = { _, _ -> true },
36 | ignoredRouteSelectors = emptySet(),
37 | ignoredRouteSelectorClassNames = emptySet(),
38 | info = InfoData.DEFAULT,
39 | servers = emptyList(),
40 | externalDocs = ExternalDocsData.DEFAULT,
41 | specConfigs = mutableMapOf(),
42 | postBuild = null,
43 | schemaConfig = SchemaConfigData.DEFAULT,
44 | exampleConfig = ExampleConfigData.DEFAULT,
45 | securityConfig = SecurityData.DEFAULT,
46 | tagsConfig = TagsData.DEFAULT,
47 | outputFormat = OutputFormat.JSON,
48 | rootPath = null,
49 | autoDocumentResourcesRoutes = false,
50 | )
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/OpenIdOAuthFlowData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | /**
4 | * See [OpenAPI Specification - OAuth Flow Object](https://swagger.io/specification/#oauth-flow-object).
5 | */
6 | internal data class OpenIdOAuthFlowData(
7 | val authorizationUrl: String? = null,
8 | val tokenUrl: String? = null,
9 | val refreshUrl: String? = null,
10 | val scopes: Map? = null,
11 | ) {
12 |
13 | companion object {
14 | val DEFAULT = OpenIdOAuthFlowData(
15 | authorizationUrl = null,
16 | tokenUrl = null,
17 | refreshUrl = null,
18 | scopes = null,
19 | )
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/OpenIdOAuthFlowsData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | /**
4 | * See [OpenAPI Specification - OAuth Flows Object](https://swagger.io/specification/#oauth-flows-object).
5 | */
6 | internal data class OpenIdOAuthFlowsData(
7 | val implicit: OpenIdOAuthFlowData?,
8 | val password: OpenIdOAuthFlowData?,
9 | val clientCredentials: OpenIdOAuthFlowData?,
10 | val authorizationCode: OpenIdOAuthFlowData?,
11 | ) {
12 |
13 | companion object {
14 | val DEFAULT = OpenIdOAuthFlowsData(
15 | implicit = null,
16 | password = null,
17 | clientCredentials = null,
18 | authorizationCode = null,
19 | )
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/RequestData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | /**
4 | * Information about a request
5 | */
6 | internal data class RequestData(
7 | val parameters: List,
8 | val body: BaseBodyData?,
9 | )
10 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/RequestParameterData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | import io.github.smiley4.ktoropenapi.config.descriptors.ExampleDescriptor
4 | import io.github.smiley4.ktoropenapi.config.ParameterLocation
5 | import io.github.smiley4.ktoropenapi.config.descriptors.TypeDescriptor
6 | import io.swagger.v3.oas.models.parameters.Parameter
7 |
8 | /**
9 | * Information about a request (query, path or header) parameter.
10 | */
11 | internal data class RequestParameterData(
12 | val name: String,
13 | val type: TypeDescriptor,
14 | val location: ParameterLocation,
15 | val description: String?,
16 | val example: ExampleDescriptor?,
17 | val required: Boolean,
18 | val deprecated: Boolean,
19 | val allowEmptyValue: Boolean?,
20 | val explode: Boolean,
21 | val allowReserved: Boolean?,
22 | val style: Parameter.StyleEnum?,
23 | val hidden: Boolean,
24 | )
25 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/ResponseData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | /**
4 | * Information about a response for a status-code.
5 | */
6 | internal data class ResponseData(
7 | val statusCode: String,
8 | val description: String?,
9 | val hidden: Boolean,
10 | val headers: Map,
11 | val body: BaseBodyData?,
12 | )
13 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/RouteData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | /**
4 | * Information about a single route.
5 | */
6 | internal data class RouteData(
7 | val specName: String?,
8 | val tags: Set,
9 | val summary: String?,
10 | val description: String?,
11 | val operationId: String?,
12 | val deprecated: Boolean,
13 | val hidden: Boolean,
14 | val securitySchemeNames: List,
15 | val protected: Boolean?,
16 | val request: RequestData,
17 | val responses: List,
18 | val externalDocs: ExternalDocsData?,
19 | val servers: List,
20 | )
21 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/SchemaConfigData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | import io.github.smiley4.ktoropenapi.config.GenericSchemaGenerator
4 | import io.github.smiley4.ktoropenapi.config.SchemaGenerator
5 | import io.github.smiley4.ktoropenapi.config.descriptors.TypeDescriptor
6 |
7 | /**
8 | * Common configuration for schemas.
9 | */
10 | internal data class SchemaConfigData(
11 | val schemas: Map,
12 | val generator: GenericSchemaGenerator,
13 | val securitySchemas: List
14 | ) {
15 | companion object {
16 | val DEFAULT = SchemaConfigData(
17 | schemas = emptyMap(),
18 | generator = SchemaGenerator.reflection(),
19 | securitySchemas = emptyList()
20 | )
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/SecurityData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | /**
4 | * Common security configuration information.
5 | */
6 | internal data class SecurityData(
7 | val defaultUnauthorizedResponse: ResponseData?,
8 | val defaultSecuritySchemeNames: Set,
9 | val securitySchemes: List,
10 | ) {
11 | companion object {
12 | val DEFAULT = SecurityData(
13 | defaultUnauthorizedResponse = null,
14 | defaultSecuritySchemeNames = emptySet(),
15 | securitySchemes = emptyList()
16 | )
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/SecuritySchemeData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | import io.github.smiley4.ktoropenapi.config.AuthKeyLocation
4 | import io.github.smiley4.ktoropenapi.config.AuthScheme
5 | import io.github.smiley4.ktoropenapi.config.AuthType
6 |
7 | /**
8 | * See [OpenAPI Specification - Security Scheme Object](https://swagger.io/specification/#security-scheme-object).
9 | */
10 | internal data class SecuritySchemeData(
11 | val schemeName: String,
12 | val type: AuthType?,
13 | val name: String?,
14 | val location: AuthKeyLocation?,
15 | val scheme: AuthScheme?,
16 | val bearerFormat: String?,
17 | val flows: OpenIdOAuthFlowsData?,
18 | val openIdConnectUrl: String?,
19 | val description: String?
20 | ) {
21 |
22 | companion object {
23 | val DEFAULT = SecuritySchemeData(
24 | schemeName = "",
25 | type = null,
26 | name = null,
27 | location = null,
28 | scheme = null,
29 | bearerFormat = null,
30 | flows = null,
31 | openIdConnectUrl = null,
32 | description = null,
33 | )
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/ServerData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | /**
4 | * See [OpenAPI Specification - Server Object](https://swagger.io/specification/#server-object).
5 | */
6 | internal data class ServerData(
7 | val url: String,
8 | val description: String?,
9 | val variables: List
10 | ) {
11 |
12 | companion object {
13 | val DEFAULT = ServerData(
14 | url = "/",
15 | description = null,
16 | variables = emptyList()
17 | )
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/ServerVariableData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 |
4 | /**
5 | * See [OpenAPI Specification - Server Variable Object](https://swagger.io/specification/#server-variable-object).
6 | */
7 | internal data class ServerVariableData(
8 | val name: String,
9 | val enum: Set,
10 | val default: String,
11 | val description: String?
12 | )
13 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/TagData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | /**
4 | * See [OpenAPI Specification - Tag Object](https://swagger.io/specification/#tag-object).
5 | */
6 | internal data class TagData(
7 | val name: String,
8 | val description: String?,
9 | val externalDocDescription: String?,
10 | val externalDocUrl: String?
11 | ) {
12 |
13 | companion object {
14 | val DEFAULT = TagData(
15 | name = "",
16 | description = null,
17 | externalDocDescription = null,
18 | externalDocUrl = null
19 | )
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/data/TagsData.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktoropenapi.data
2 |
3 | import io.github.smiley4.ktoropenapi.config.TagGenerator
4 |
5 | /**
6 | * Common configuration for tags.
7 | */
8 | internal data class TagsData(
9 | val tags: List,
10 | val generator: TagGenerator,
11 | ) {
12 |
13 | companion object {
14 | val DEFAULT = TagsData(
15 | tags = emptyList(),
16 | generator = { emptyList() }
17 | )
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/ktor-openapi/src/main/kotlin/io/github/smiley4/ktoropenapi/resources/documentationExtractor.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalSerializationApi::class)
2 |
3 | package io.github.smiley4.ktoropenapi.resources
4 |
5 | import io.github.smiley4.ktoropenapi.OpenApiPlugin
6 | import io.github.smiley4.ktoropenapi.config.ParameterLocation
7 | import io.github.smiley4.ktoropenapi.config.RouteConfig
8 | import io.github.smiley4.ktoropenapi.config.descriptors.SerialTypeDescriptor
9 | import io.ktor.resources.serialization.ResourcesFormat
10 | import kotlinx.serialization.ExperimentalSerializationApi
11 | import kotlinx.serialization.KSerializer
12 | import kotlinx.serialization.descriptors.SerialDescriptor
13 | import kotlinx.serialization.descriptors.StructureKind
14 | import kotlinx.serialization.descriptors.elementNames
15 |
16 | private data class ParameterData(
17 | val name: String,
18 | val descriptor: SerialDescriptor,
19 | val optional: Boolean,
20 | val location: ParameterLocation
21 | )
22 |
23 |
24 | /**
25 | * Intended for internal use.
26 | * Automatically extract route configuration from the given resource class.
27 | */
28 | fun extractTypesafeDocumentation(serializer: KSerializer, resourcesFormat: ResourcesFormat): RouteConfig.() -> Unit {
29 | if(!OpenApiPlugin.config.autoDocumentResourcesRoutes) {
30 | return {}
31 | }
32 | // Note: typesafe routing only defines information about path & query parameters - no other information is available
33 | val path = resourcesFormat.encodeToPathPattern(serializer)
34 | return {
35 | request {
36 | collectParameters(serializer.descriptor, path).forEach { parameter ->
37 | parameter(parameter.location, parameter.name, SerialTypeDescriptor(parameter.descriptor)) {
38 | required = !parameter.optional
39 | }
40 | }
41 | }
42 | }
43 | }
44 |
45 |
46 |
47 | private fun collectParameters(descriptor: SerialDescriptor, path: String): List {
48 | val parameters = mutableListOf()
49 |
50 | descriptor.elementNames.forEach { name ->
51 | val index = descriptor.getElementIndex(name)
52 | val elementDescriptor = descriptor.getElementDescriptor(index)
53 | if (!elementDescriptor.isInline && elementDescriptor.kind is StructureKind.CLASS) {
54 | parameters.addAll(collectParameters(elementDescriptor, path))
55 | } else {
56 | val location = getLocation(name, path)
57 | parameters.add(
58 | ParameterData(
59 | name = name,
60 | descriptor = elementDescriptor,
61 | optional = when(location) {
62 | ParameterLocation.PATH -> path.contains("{$name?}")
63 | ParameterLocation.QUERY -> elementDescriptor.isNullable || descriptor.isElementOptional(index)
64 | else -> false
65 | },
66 | location = location
67 | )
68 | )
69 | }
70 | }
71 |
72 | return parameters
73 | }
74 |
75 |
76 | /**
77 | * path contains parameter with given name -> "PATH"
78 | * path does not contain parameter with given name -> "QUERY"
79 | */
80 | private fun getLocation(name: String, path: String): ParameterLocation {
81 | return if (path.contains("{$name}") || path.contains("{$name?}") || path.contains("{$name...}")) {
82 | ParameterLocation.PATH
83 | } else {
84 | ParameterLocation.QUERY
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/ktor-openapi/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ExternalDocsBuilderTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktorswaggerui.builder
2 |
3 | import io.github.smiley4.ktoropenapi.builder.openapi.ExternalDocumentationBuilder
4 | import io.github.smiley4.ktoropenapi.data.ExternalDocsData
5 | import io.github.smiley4.ktoropenapi.config.ExternalDocsConfig
6 | import io.kotest.core.spec.style.StringSpec
7 | import io.kotest.matchers.shouldBe
8 | import io.swagger.v3.oas.models.ExternalDocumentation
9 |
10 | class ExternalDocsBuilderTest : StringSpec({
11 |
12 | "default external docs object" {
13 | buildExternalDocsObject {}.also { docs ->
14 | docs.url shouldBe "/"
15 | docs.description shouldBe null
16 | }
17 | }
18 |
19 | "complete server object" {
20 | buildExternalDocsObject {
21 | url = "Test URL"
22 | description = "Test Description"
23 | }.also { docs ->
24 | docs.url shouldBe "Test URL"
25 | docs.description shouldBe "Test Description"
26 | }
27 | }
28 |
29 | }) {
30 |
31 | companion object {
32 |
33 | private fun buildExternalDocsObject(builder: ExternalDocsConfig.() -> Unit): ExternalDocumentation {
34 | return ExternalDocumentationBuilder().build(ExternalDocsConfig().apply(builder).build(ExternalDocsData.DEFAULT))
35 | }
36 |
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/ktor-openapi/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/InfoBuilderTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktorswaggerui.builder
2 | import io.github.smiley4.ktoropenapi.builder.openapi.ContactBuilder
3 | import io.github.smiley4.ktoropenapi.builder.openapi.InfoBuilder
4 | import io.github.smiley4.ktoropenapi.builder.openapi.LicenseBuilder
5 | import io.github.smiley4.ktoropenapi.data.InfoData
6 | import io.github.smiley4.ktoropenapi.config.InfoConfig
7 | import io.kotest.core.spec.style.StringSpec
8 | import io.kotest.matchers.nulls.shouldNotBeNull
9 | import io.kotest.matchers.shouldBe
10 | import io.swagger.v3.oas.models.info.Info
11 |
12 |
13 | class InfoBuilderTest : StringSpec({
14 |
15 | "empty info object" {
16 | buildInfoObject {}.also { info ->
17 | info.title shouldBe "API"
18 | info.version shouldBe "latest"
19 | info.description shouldBe null
20 | info.termsOfService shouldBe null
21 | info.contact shouldBe null
22 | info.license shouldBe null
23 | info.extensions shouldBe null
24 | info.summary shouldBe null
25 | }
26 | }
27 |
28 | "full info object" {
29 | buildInfoObject {
30 | title = "Test Api"
31 | version = "1.0"
32 | summary = "testing api"
33 | description = "Api for testing"
34 | termsOfService = "test-tos"
35 | contact {
36 | name = "Test Person"
37 | url = "example.com"
38 | email = "test.mail"
39 |
40 | }
41 | license {
42 | name = "Test License"
43 | url = "example.com"
44 | identifier = "Example"
45 | }
46 | }.also { info ->
47 | info.title shouldBe "Test Api"
48 | info.version shouldBe "1.0"
49 | info.summary shouldBe "testing api"
50 | info.description shouldBe "Api for testing"
51 | info.termsOfService shouldBe "test-tos"
52 | info.contact
53 | .also { contact -> contact.shouldNotBeNull() }
54 | ?.also { contact ->
55 | contact.name shouldBe "Test Person"
56 | contact.url shouldBe "example.com"
57 | contact.email shouldBe "test.mail"
58 | }
59 | info.license
60 | .also { license -> license.shouldNotBeNull() }
61 | ?.also { license ->
62 | license.name shouldBe "Test License"
63 | license.url shouldBe "example.com"
64 | license.identifier shouldBe "Example"
65 | }
66 | info.extensions shouldBe null
67 | }
68 | }
69 |
70 | }) {
71 |
72 | companion object {
73 |
74 | private fun buildInfoObject(builder: InfoConfig.() -> Unit): Info {
75 | return InfoBuilder(
76 | contactBuilder = ContactBuilder(),
77 | licenseBuilder = LicenseBuilder()
78 | ).build(InfoConfig().apply(builder).build(InfoData.DEFAULT))
79 | }
80 |
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/ktor-openapi/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/ServersBuilderTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktorswaggerui.builder
2 |
3 | import io.github.smiley4.ktoropenapi.builder.openapi.ServerBuilder
4 | import io.github.smiley4.ktoropenapi.data.ServerData
5 | import io.github.smiley4.ktoropenapi.config.ServerConfig
6 | import io.kotest.core.spec.style.StringSpec
7 | import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
8 | import io.kotest.matchers.shouldBe
9 | import io.swagger.v3.oas.models.servers.Server
10 |
11 | class ServersBuilderTest : StringSpec({
12 |
13 | "default server object" {
14 | buildServerObject {}.also { server ->
15 | server.url shouldBe "/"
16 | server.description shouldBe null
17 | server.variables shouldBe null
18 | server.extensions shouldBe null
19 | }
20 | }
21 |
22 | "complete server object" {
23 | buildServerObject {
24 | url = "Test URL"
25 | description = "Test Description"
26 | variable("version") {
27 | description = "the version of the api"
28 | default = "2"
29 | enum = setOf("1", "2", "3")
30 | }
31 | variable("region") {
32 | description = "the region of the api"
33 | default = "somewhere"
34 | enum = setOf("somewhere", "else")
35 | }
36 | }.also { server ->
37 | server.url shouldBe "Test URL"
38 | server.description shouldBe "Test Description"
39 | server.variables.keys shouldContainExactlyInAnyOrder listOf("version", "region")
40 | server.variables["version"]!!.also { variable ->
41 | variable.description shouldBe "the version of the api"
42 | variable.default shouldBe "2"
43 | variable.enum shouldContainExactlyInAnyOrder listOf("1", "2", "3")
44 | }
45 | server.variables["region"]!!.also { variable ->
46 | variable.description shouldBe "the region of the api"
47 | variable.default shouldBe "somewhere"
48 | variable.enum shouldContainExactlyInAnyOrder listOf("somewhere", "else")
49 | }
50 | server.extensions shouldBe null
51 | }
52 | }
53 |
54 | }) {
55 |
56 | companion object {
57 |
58 | private fun buildServerObject(builder: ServerConfig.() -> Unit): Server {
59 | return ServerBuilder().build(ServerConfig().apply(builder).build(ServerData.DEFAULT))
60 | }
61 |
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/ktor-openapi/src/test/kotlin/io/github/smiley4/ktorswaggerui/builder/TagsBuilderTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktorswaggerui.builder
2 |
3 | import io.github.smiley4.ktoropenapi.builder.openapi.TagBuilder
4 | import io.github.smiley4.ktoropenapi.builder.openapi.TagExternalDocumentationBuilder
5 | import io.github.smiley4.ktoropenapi.data.TagData
6 | import io.github.smiley4.ktoropenapi.config.TagConfig
7 | import io.kotest.core.spec.style.StringSpec
8 | import io.kotest.matchers.nulls.shouldNotBeNull
9 | import io.kotest.matchers.shouldBe
10 | import io.swagger.v3.oas.models.tags.Tag
11 |
12 |
13 | class TagsBuilderTest : StringSpec({
14 |
15 | "empty tag object" {
16 | buildTagObject("test-tag") {}.also { tag ->
17 | tag.name shouldBe "test-tag"
18 | tag.description shouldBe null
19 | tag.externalDocs shouldBe null
20 | tag.extensions shouldBe null
21 | }
22 | }
23 |
24 | "full tag object" {
25 | buildTagObject("test-tag") {
26 | description = "Description of tag"
27 | externalDocDescription = "Description of external docs"
28 | externalDocUrl = "example.com"
29 | }.also { tag ->
30 | tag.name shouldBe "test-tag"
31 | tag.description shouldBe "Description of tag"
32 | tag.externalDocs
33 | .also { docs -> docs.shouldNotBeNull() }
34 | ?.also { docs ->
35 | docs.description shouldBe "Description of external docs"
36 | docs.url shouldBe "example.com"
37 | docs.extensions shouldBe null
38 | }
39 | tag.extensions shouldBe null
40 | }
41 | }
42 |
43 | }) {
44 |
45 | companion object {
46 |
47 | private fun buildTagObject(name: String, builder: TagConfig.() -> Unit): Tag {
48 | return TagBuilder(
49 | tagExternalDocumentationBuilder = TagExternalDocumentationBuilder()
50 | ).build(TagConfig(name).apply(builder).build(TagData.DEFAULT))
51 | }
52 |
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/ktor-openapi/src/test/kotlin/io/github/smiley4/ktorswaggerui/misc/RoutingTests.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktorswaggerui.misc
2 |
3 | import io.github.smiley4.ktoropenapi.OpenApi
4 | import io.github.smiley4.ktoropenapi.config.OutputFormat
5 | import io.github.smiley4.ktoropenapi.openApi
6 | import io.kotest.matchers.shouldBe
7 | import io.kotest.matchers.string.shouldNotBeEmpty
8 | import io.kotest.matchers.string.shouldStartWith
9 | import io.ktor.client.HttpClient
10 | import io.ktor.client.request.get
11 | import io.ktor.client.statement.bodyAsText
12 | import io.ktor.http.ContentType
13 | import io.ktor.http.HttpStatusCode
14 | import io.ktor.http.contentType
15 | import io.ktor.server.response.respondText
16 | import io.ktor.server.routing.get
17 | import io.ktor.server.routing.route
18 | import io.ktor.server.testing.testApplication
19 | import kotlin.test.Test
20 |
21 | class RoutingTests {
22 |
23 | @Test
24 | fun jsonSpec() = openApiTestApplication {
25 | get("hello").also {
26 | it.status shouldBe HttpStatusCode.OK
27 | it.body shouldBe "Hello Test"
28 | }
29 | get("/").also {
30 | it.status shouldBe HttpStatusCode.NotFound
31 | }
32 | get("/api.json").also {
33 | it.status shouldBe HttpStatusCode.OK
34 | it.contentType shouldBe ContentType.Application.Json
35 | it.body.shouldNotBeEmpty()
36 | it.body shouldStartWith "{\n \"openapi\" : \"3.1.0\","
37 | }
38 | }
39 |
40 |
41 | @Test
42 | fun yamlSpec() = openApiTestApplication(OutputFormat.YAML) {
43 | get("hello").also {
44 | it.status shouldBe HttpStatusCode.OK
45 | it.body shouldBe "Hello Test"
46 | }
47 | get("/").also {
48 | it.status shouldBe HttpStatusCode.NotFound
49 | }
50 | get("/api.yml").also {
51 | it.status shouldBe HttpStatusCode.OK
52 | it.contentType shouldBe ContentType.Text.Plain.withParameter("charset", "utf-8")
53 | it.body.shouldNotBeEmpty()
54 | it.body shouldStartWith "openapi: 3.1.0\n"
55 | }
56 | }
57 |
58 | private fun openApiTestApplication(format: OutputFormat = OutputFormat.JSON, block: suspend TestContext.() -> Unit) {
59 | testApplication {
60 | val client = createClient {
61 | this.followRedirects = false
62 | }
63 | install(OpenApi) {
64 | outputFormat = format
65 | }
66 | routing {
67 | val routeSuffix = when(format) {
68 | OutputFormat.JSON -> "json"
69 | OutputFormat.YAML -> "yml"
70 | }
71 | route("api.$routeSuffix") {
72 | openApi()
73 | }
74 | get("hello") {
75 | call.respondText("Hello Test")
76 | }
77 | }
78 | TestContext(client).apply { block() }
79 | }
80 | }
81 |
82 | class TestContext(private val client: HttpClient) {
83 |
84 | suspend fun get(path: String): GetResult {
85 | return client.get(path)
86 | .let {
87 | GetResult(
88 | path = path,
89 | status = it.status,
90 | contentType = it.contentType(),
91 | body = it.bodyAsText(),
92 | redirect = it.headers["Location"]
93 | )
94 | }
95 | .also { it.print() }
96 | }
97 |
98 |
99 | private fun GetResult.print() {
100 | println("GET ${this.path} => ${this.status} (${this.contentType}): ${this.body}")
101 | }
102 | }
103 |
104 | data class GetResult(
105 | val path: String,
106 | val status: HttpStatusCode,
107 | val contentType: ContentType?,
108 | val body: String,
109 | val redirect: String?
110 | )
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/ktor-openapi/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %cyan(%d{yyyy-MM-dd HH:mm:ss.SSS}) | %highlight(%-5.5level{5}) | %gray(%-16.16thread{16}) | %magenta(%-25.25logger{25}) | %m%n
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/ktor-redoc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.vanniktech.maven.publish.JavadocJar
2 | import com.vanniktech.maven.publish.KotlinJvm
3 | import com.vanniktech.maven.publish.SonatypeHost
4 | import io.gitlab.arturbosch.detekt.Detekt
5 | import org.jetbrains.dokka.gradle.DokkaTask
6 |
7 | val projectGroupId: String by project
8 | val projectVersion: String by project
9 | group = projectGroupId
10 | version = projectVersion
11 |
12 | plugins {
13 | kotlin("jvm")
14 | id("org.owasp.dependencycheck")
15 | id("com.github.ben-manes.versions")
16 | id("io.gitlab.arturbosch.detekt")
17 | id("com.vanniktech.maven.publish")
18 | id("org.jetbrains.dokka")
19 | }
20 |
21 | repositories {
22 | mavenCentral()
23 | }
24 |
25 | dependencies {
26 | val versionKtor: String by project
27 | implementation("io.ktor:ktor-server-core-jvm:$versionKtor")
28 | implementation("io.ktor:ktor-server-content-negotiation:$versionKtor")
29 | testImplementation("io.ktor:ktor-server-netty-jvm:$versionKtor")
30 | testImplementation("io.ktor:ktor-server-content-negotiation:$versionKtor")
31 | testImplementation("io.ktor:ktor-serialization-jackson:$versionKtor")
32 | testImplementation("io.ktor:ktor-server-test-host:$versionKtor")
33 |
34 | val versionRedoc: String by project
35 | implementation("org.webjars:redoc:$versionRedoc")
36 |
37 | val versionKotest: String by project
38 | testImplementation("io.kotest:kotest-runner-junit5:$versionKotest")
39 | testImplementation("io.kotest:kotest-assertions-core:$versionKotest")
40 |
41 | val versionKotlinTest: String by project
42 | testImplementation("org.jetbrains.kotlin:kotlin-test:$versionKotlinTest")
43 | }
44 |
45 | kotlin {
46 | jvmToolchain(11)
47 | }
48 |
49 | tasks.withType().configureEach {
50 | useJUnitPlatform()
51 | }
52 |
53 | detekt {
54 | ignoreFailures = false
55 | buildUponDefaultConfig = true
56 | allRules = false
57 | config.setFrom("$projectDir/../detekt/detekt.yml")
58 | }
59 | tasks.withType().configureEach {
60 | reports {
61 | html.required.set(true)
62 | md.required.set(true)
63 | xml.required.set(false)
64 | txt.required.set(false)
65 | sarif.required.set(false)
66 | }
67 | }
68 |
69 | tasks.withType().configureEach {
70 | outputDirectory.set(file("$rootDir/docs/dokka/ktor-redoc"))
71 | }
72 |
73 | mavenPublishing {
74 | val projectGroupId: String by project
75 | val projectVersion: String by project
76 | val projectBaseScmUrl: String by project
77 | val projectBaseScmConnection: String by project
78 | val projectLicenseName: String by project
79 | val projectLicenseUrl: String by project
80 | val projectDeveloperName: String by project
81 | val projectDeveloperUrl: String by project
82 |
83 | configure(KotlinJvm(JavadocJar.Dokka("dokkaHtml"), true))
84 | publishToMavenCentral(SonatypeHost.S01)
85 | signAllPublications()
86 | coordinates(projectGroupId, "ktor-redoc", projectVersion)
87 | pom {
88 | name.set("Ktor Redoc")
89 | description.set("Ktor plugin to provide Redoc")
90 | url.set(projectBaseScmUrl +"ktor-redoc")
91 | licenses {
92 | license {
93 | name.set(projectLicenseName)
94 | url.set(projectLicenseUrl)
95 | distribution.set(projectLicenseUrl)
96 | }
97 | }
98 | scm {
99 | url.set(projectBaseScmUrl + "ktor-redoc")
100 | connection.set(projectBaseScmConnection + "ktor-redoc.git")
101 | }
102 | developers {
103 | developer {
104 | id.set(projectDeveloperName)
105 | name.set(projectDeveloperName)
106 | url.set(projectDeveloperUrl)
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/ktor-redoc/src/main/kotlin/io/github/smiley4/ktorredoc/PropertyBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktorredoc
2 |
3 |
4 |
5 | internal class PropertyBuilder {
6 |
7 | data class Raw(val value: String)
8 |
9 | private val entries = mutableMapOf()
10 |
11 | operator fun set(key: String, value: Any?) {
12 | entries[key] = value
13 | }
14 |
15 |
16 | /**
17 | * @param prefix string to add before each line
18 | * @param separator the separator string to join each line with
19 | * @param nullBehavior how to handle null values. Valid values are "include", "remove", "to_undefined"
20 | */
21 | fun render(prefix: String, separator: String, nullBehavior: String): String {
22 | return entries
23 | .mapValues { (_, value) ->
24 | when (value) {
25 | null -> when(nullBehavior) {
26 | "include" -> "null"
27 | "remove" -> null
28 | "to_undefined" -> "undefined"
29 | else -> throw IllegalArgumentException("Invalid value for null behaviour: '$nullBehavior'")
30 | }
31 | is Raw -> value.value
32 | is Number -> value.toString()
33 | is Boolean -> value.toString()
34 | is String -> "'$value'"
35 | else -> "'$value'"
36 | }
37 | }
38 | .filterValues { it != null }
39 | .mapValues { (_, value) -> value!! }
40 | .map { (key, value) -> "$prefix$key=$value" }
41 | .joinToString(separator)
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/ktor-redoc/src/main/kotlin/io/github/smiley4/ktorredoc/RedocRouteSelector.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktorredoc
2 |
3 | import io.ktor.server.routing.Route
4 | import io.ktor.server.routing.RouteSelector
5 | import io.ktor.server.routing.RouteSelectorEvaluation
6 | import io.ktor.server.routing.RoutingResolveContext
7 |
8 | internal class RedocRouteSelector : RouteSelector() {
9 | override suspend fun evaluate(context: RoutingResolveContext, segmentIndex: Int) = RouteSelectorEvaluation.Transparent
10 | }
11 |
12 | internal fun Route.markedRedoc(build: Route.() -> Unit): Route {
13 | return createChild(RedocRouteSelector()).also(build)
14 | }
15 |
--------------------------------------------------------------------------------
/ktor-redoc/src/main/kotlin/io/github/smiley4/ktorredoc/ResourceContent.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktorredoc
2 |
3 | import io.ktor.http.ContentType
4 | import io.ktor.http.content.OutgoingContent
5 | import java.net.URL
6 |
7 | internal class ResourceContent(private val resource: URL) : OutgoingContent.ByteArrayContent() {
8 |
9 | private val contentTypes = mapOf(
10 | "html" to ContentType.Text.Html,
11 | "css" to ContentType.Text.CSS,
12 | "js" to ContentType.Application.JavaScript,
13 | "json" to ContentType.Application.Json,
14 | "png" to ContentType.Image.PNG
15 | )
16 |
17 | private val bytes by lazy { resource.readBytes() }
18 |
19 | override val contentType: ContentType? by lazy {
20 | val extension = resource.file.substring(resource.file.lastIndexOf('.') + 1)
21 | contentTypes[extension] ?: ContentType.Text.Html
22 | }
23 |
24 | override val contentLength: Long? by lazy {
25 | bytes.size.toLong()
26 | }
27 |
28 | override fun bytes(): ByteArray = bytes
29 |
30 | override fun toString() = "ResourceContent \"$resource\""
31 | }
32 |
--------------------------------------------------------------------------------
/ktor-redoc/src/test/kotlin/io/github/smiley4/ktorredoc/RoutingTests.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktorredoc
2 |
3 | import io.github.smiley4.ktorredoc.config.RedocConfig
4 | import io.kotest.matchers.shouldBe
5 | import io.kotest.matchers.string.shouldNotBeEmpty
6 | import io.ktor.client.HttpClient
7 | import io.ktor.client.request.*
8 | import io.ktor.client.statement.bodyAsText
9 | import io.ktor.http.ContentType
10 | import io.ktor.http.HttpStatusCode
11 | import io.ktor.http.contentType
12 | import io.ktor.http.withCharset
13 | import io.ktor.server.response.respondText
14 | import io.ktor.server.routing.get
15 | import io.ktor.server.routing.route
16 | import io.ktor.server.testing.testApplication
17 | import kotlin.test.Test
18 |
19 | class RoutingTests {
20 |
21 | @Test
22 | fun defaultConfig() = redocTestApplication {
23 | get("hello").also {
24 | it.status shouldBe HttpStatusCode.OK
25 | it.body shouldBe "Hello Test"
26 | }
27 | get("/redoc").also {
28 | it.status shouldBe HttpStatusCode.Found
29 | it.redirect shouldBe "/redoc/index.html"
30 | }
31 | get("/redoc/index.html").also {
32 | it.status shouldBe HttpStatusCode.OK
33 | it.contentType shouldBe ContentType.Text.Html.withCharset(java.nio.charset.Charset.forName("UTF-8"))
34 | it.body.shouldNotBeEmpty()
35 | }
36 | }
37 |
38 | private fun redocTestApplication(config: RedocConfig.() -> Unit = {}, block: suspend TestContext.() -> Unit = {}) {
39 | testApplication {
40 | val client = createClient {
41 | this.followRedirects = false
42 | }
43 | routing {
44 | route("redoc") {
45 | redoc("api.json", config)
46 | }
47 | get("hello") {
48 | call.respondText("Hello Test")
49 | }
50 | }
51 | TestContext(client).apply { block() }
52 | }
53 | }
54 |
55 | class TestContext(private val client: HttpClient) {
56 |
57 | suspend fun get(path: String): GetResult {
58 | return client.get(path)
59 | .let {
60 | GetResult(
61 | path = path,
62 | status = it.status,
63 | contentType = it.contentType(),
64 | body = it.bodyAsText(),
65 | redirect = it.headers["Location"]
66 | )
67 | }
68 | .also { it.print() }
69 | }
70 |
71 |
72 | private fun GetResult.print() {
73 | println("GET ${this.path} => ${this.status} (${this.contentType}): ${this.body}")
74 | }
75 | }
76 |
77 | data class GetResult(
78 | val path: String,
79 | val status: HttpStatusCode,
80 | val contentType: ContentType?,
81 | val body: String,
82 | val redirect: String?
83 | )
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/ktor-swagger-ui/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.vanniktech.maven.publish.JavadocJar
2 | import com.vanniktech.maven.publish.KotlinJvm
3 | import com.vanniktech.maven.publish.SonatypeHost
4 | import io.gitlab.arturbosch.detekt.Detekt
5 | import org.jetbrains.dokka.gradle.DokkaTask
6 |
7 | val projectGroupId: String by project
8 | val projectVersion: String by project
9 | group = projectGroupId
10 | version = projectVersion
11 |
12 | plugins {
13 | kotlin("jvm")
14 | id("org.owasp.dependencycheck")
15 | id("com.github.ben-manes.versions")
16 | id("io.gitlab.arturbosch.detekt")
17 | id("com.vanniktech.maven.publish")
18 | id("org.jetbrains.dokka")
19 | }
20 |
21 | repositories {
22 | mavenCentral()
23 | }
24 |
25 | dependencies {
26 | val versionKtor: String by project
27 | implementation("io.ktor:ktor-server-core-jvm:$versionKtor")
28 | implementation("io.ktor:ktor-server-content-negotiation:$versionKtor")
29 | testImplementation("io.ktor:ktor-server-netty-jvm:$versionKtor")
30 | testImplementation("io.ktor:ktor-server-content-negotiation:$versionKtor")
31 | testImplementation("io.ktor:ktor-serialization-jackson:$versionKtor")
32 | testImplementation("io.ktor:ktor-server-test-host:$versionKtor")
33 |
34 | val versionSwaggerUI: String by project
35 | implementation("org.webjars:swagger-ui:$versionSwaggerUI")
36 |
37 | val versionKotest: String by project
38 | testImplementation("io.kotest:kotest-runner-junit5:$versionKotest")
39 | testImplementation("io.kotest:kotest-assertions-core:$versionKotest")
40 |
41 | val versionKotlinTest: String by project
42 | testImplementation("org.jetbrains.kotlin:kotlin-test:$versionKotlinTest")
43 | }
44 |
45 | kotlin {
46 | jvmToolchain(11)
47 | }
48 |
49 | tasks.withType().configureEach {
50 | useJUnitPlatform()
51 | }
52 |
53 | detekt {
54 | ignoreFailures = false
55 | buildUponDefaultConfig = true
56 | allRules = false
57 | config.setFrom("$projectDir/../detekt/detekt.yml")
58 | }
59 | tasks.withType().configureEach {
60 | reports {
61 | html.required.set(true)
62 | md.required.set(true)
63 | xml.required.set(false)
64 | txt.required.set(false)
65 | sarif.required.set(false)
66 | }
67 | }
68 |
69 | tasks.withType().configureEach {
70 | outputDirectory.set(file("$rootDir/docs/dokka/ktor-swagger-ui"))
71 | }
72 |
73 | mavenPublishing {
74 | val projectGroupId: String by project
75 | val projectVersion: String by project
76 | val projectBaseScmUrl: String by project
77 | val projectBaseScmConnection: String by project
78 | val projectLicenseName: String by project
79 | val projectLicenseUrl: String by project
80 | val projectDeveloperName: String by project
81 | val projectDeveloperUrl: String by project
82 |
83 | configure(KotlinJvm(JavadocJar.Dokka("dokkaHtml"), true))
84 | publishToMavenCentral(SonatypeHost.S01)
85 | signAllPublications()
86 | coordinates(projectGroupId, "ktor-swagger-ui", projectVersion)
87 | pom {
88 | name.set("Ktor Swagger-UI")
89 | description.set("Ktor plugin to provide a Swagger-UI")
90 | url.set(projectBaseScmUrl + "ktor-swagger-ui")
91 | licenses {
92 | license {
93 | name.set(projectLicenseName)
94 | url.set(projectLicenseUrl)
95 | distribution.set(projectLicenseUrl)
96 | }
97 | }
98 | scm {
99 | url.set(projectBaseScmUrl + "ktor-swagger-ui")
100 | connection.set(projectBaseScmConnection + "ktor-swagger-ui.git")
101 | }
102 | developers {
103 | developer {
104 | id.set(projectDeveloperName)
105 | name.set(projectDeveloperName)
106 | url.set(projectDeveloperUrl)
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/PropertyBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktorswaggerui
2 |
3 |
4 |
5 | internal class PropertyBuilder {
6 |
7 | data class Raw(val value: String)
8 |
9 | private val entries = mutableMapOf()
10 |
11 | operator fun set(key: String, value: Any?) {
12 | entries[key] = value
13 | }
14 |
15 |
16 | /**
17 | * @param linePrefix a string to add before each line
18 | * @param prefixFirst whether to add the prefix to the first line
19 | * @param separator the separator string to join each line with
20 | * @param nullBehavior how to handle null values. Valid values are "include", "remove", "to_undefined"
21 | */
22 | fun render(linePrefix: String, prefixFirst: Boolean, separator: String, nullBehavior: String): String {
23 | return entries
24 | .mapValues { (_, value) ->
25 | when (value) {
26 | null -> when(nullBehavior) {
27 | "include" -> "null"
28 | "remove" -> null
29 | "to_undefined" -> "undefined"
30 | else -> throw IllegalArgumentException("Invalid value for null behaviour: '$nullBehavior'")
31 | }
32 | is Raw -> value.value
33 | is Number -> value.toString()
34 | is Boolean -> value.toString()
35 | is String -> "'$value'"
36 | else -> "'$value'"
37 | }
38 | }
39 | .filterValues { it != null }
40 | .mapValues { (_, value) -> value!! }
41 | .map { (key, value) -> "$key: $value" }
42 | .mapIndexed { index, line ->
43 | if(!prefixFirst && index == 0) {
44 | line
45 | } else {
46 | linePrefix + line
47 | }
48 | }
49 | .joinToString(separator)
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/ResourceContent.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktorswaggerui
2 |
3 | import io.ktor.http.ContentType
4 | import io.ktor.http.content.OutgoingContent
5 | import java.net.URL
6 |
7 | internal class ResourceContent(private val resource: URL) : OutgoingContent.ByteArrayContent() {
8 |
9 | private val contentTypes = mapOf(
10 | "html" to ContentType.Text.Html,
11 | "css" to ContentType.Text.CSS,
12 | "js" to ContentType.Application.JavaScript,
13 | "json" to ContentType.Application.Json,
14 | "png" to ContentType.Image.PNG
15 | )
16 |
17 | private val bytes by lazy { resource.readBytes() }
18 |
19 | override val contentType: ContentType? by lazy {
20 | val extension = resource.file.substring(resource.file.lastIndexOf('.') + 1)
21 | contentTypes[extension] ?: ContentType.Text.Html
22 | }
23 |
24 | override val contentLength: Long? by lazy {
25 | bytes.size.toLong()
26 | }
27 |
28 | override fun bytes(): ByteArray = bytes
29 |
30 | override fun toString() = "ResourceContent \"$resource\""
31 | }
32 |
--------------------------------------------------------------------------------
/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/SwaggerUIRouteSelector.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktorswaggerui
2 |
3 | import io.ktor.server.routing.Route
4 | import io.ktor.server.routing.RouteSelector
5 | import io.ktor.server.routing.RouteSelectorEvaluation
6 | import io.ktor.server.routing.RoutingResolveContext
7 |
8 | internal class SwaggerUIRouteSelector : RouteSelector() {
9 | override suspend fun evaluate(context: RoutingResolveContext, segmentIndex: Int) = RouteSelectorEvaluation.Transparent
10 | }
11 |
12 | internal fun Route.markedSwaggerUI(build: Route.() -> Unit): Route {
13 | return createChild(SwaggerUIRouteSelector()).also(build)
14 | }
15 |
--------------------------------------------------------------------------------
/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/config/OperationsSort.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktorswaggerui.config
2 |
3 | /**
4 | * Determines the order to sort the operations in the swagger-ui.
5 | */
6 | enum class OperationsSort(val value: String) {
7 | /**
8 | * The order returned by the server unchanged
9 | */
10 | NONE("undefined"),
11 | /**
12 | * sort by paths alphanumerically
13 | */
14 | ALPHANUMERICALLY("alpha"),
15 | /**
16 | * sort by HTTP method
17 | */
18 | HTTP_METHOD("method")
19 | }
20 |
--------------------------------------------------------------------------------
/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/config/SwaggerUISyntaxHighlight.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktorswaggerui.config
2 |
3 | /**
4 | * The theme to use for syntax highlighting code-blocks in swagger-ui.
5 | */
6 | enum class SwaggerUISyntaxHighlight(val value: String) {
7 | DISABLED("disabled"),
8 | AGATE("agate"),
9 | ARTA("arta"),
10 | MONOKAI("monokai"),
11 | NORD("nord"),
12 | OBSIDIAN("obsidian"),
13 | TOMORROW_NIGHT("tomorrow-night"),
14 | IDEA("idea")
15 | }
16 |
--------------------------------------------------------------------------------
/ktor-swagger-ui/src/main/kotlin/io/github/smiley4/ktorswaggerui/config/TagSort.kt:
--------------------------------------------------------------------------------
1 | package io.github.smiley4.ktorswaggerui.config
2 |
3 | /**
4 | * Determines the order to sort the tags in the swagger-ui.
5 | */
6 | enum class TagSort(val value: String) {
7 | /**
8 | * The order returned by the server unchanged
9 | */
10 | NONE("undefined"),
11 | /**
12 | * sort by paths alphanumerically
13 | */
14 | ALPHANUMERICALLY("alpha"),
15 | }
16 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "ktor-openapi"
2 |
3 | include("ktor-openapi")
4 | include("ktor-swagger-ui")
5 | include("ktor-redoc")
6 |
7 | include("examples")
8 |
9 | pluginManagement {
10 | repositories {
11 | mavenCentral()
12 | gradlePluginPortal()
13 | }
14 | }
15 |
16 | plugins {
17 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
18 | }
--------------------------------------------------------------------------------