├── .github └── workflows │ └── Build.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts ├── src ├── main │ └── kotlin │ │ └── com │ │ └── papsign │ │ └── ktor │ │ └── openapigen │ │ ├── APITag.kt │ │ ├── OpenAPIGen.kt │ │ ├── OpenAPIGenExtension.kt │ │ ├── OpenAPIGenModuleExtension.kt │ │ ├── SchemaMap.kt │ │ ├── SwaggerUI.kt │ │ ├── annotations │ │ ├── Path.kt │ │ ├── Request.kt │ │ ├── Response.kt │ │ ├── encodings │ │ │ ├── APIRequestFormat.kt │ │ │ └── APIResponseFormat.kt │ │ ├── mapping │ │ │ └── OpenAPIName.kt │ │ ├── parameters │ │ │ ├── APIParam.kt │ │ │ ├── HeaderParam.kt │ │ │ ├── ParamAnnotationUtil.kt │ │ │ ├── PathParam.kt │ │ │ └── QueryParam.kt │ │ ├── properties │ │ │ └── description │ │ │ │ ├── Description.kt │ │ │ │ └── DescriptionProcessor.kt │ │ └── type │ │ │ ├── SingleTypeValidator.kt │ │ │ ├── common │ │ │ └── ConstraintViolation.kt │ │ │ ├── number │ │ │ ├── NumberConstraintProcessor.kt │ │ │ ├── floating │ │ │ │ ├── FloatingNumberConstraintProcessor.kt │ │ │ │ ├── clamp │ │ │ │ │ ├── FClamp.kt │ │ │ │ │ └── FClampProcessor.kt │ │ │ │ ├── max │ │ │ │ │ ├── FMax.kt │ │ │ │ │ └── FMaxProcessor.kt │ │ │ │ └── min │ │ │ │ │ ├── FMin.kt │ │ │ │ │ └── FMinProcessor.kt │ │ │ └── integer │ │ │ │ ├── IntegerNumberConstraintProcessor.kt │ │ │ │ ├── clamp │ │ │ │ ├── Clamp.kt │ │ │ │ └── ClampProcessor.kt │ │ │ │ ├── max │ │ │ │ ├── Max.kt │ │ │ │ └── MaxProcessor.kt │ │ │ │ └── min │ │ │ │ ├── Min.kt │ │ │ │ └── MinProcessor.kt │ │ │ ├── object │ │ │ └── example │ │ │ │ ├── ExampleProcessor.kt │ │ │ │ ├── ExampleProvider.kt │ │ │ │ ├── NoExampleProvider.kt │ │ │ │ └── WithExample.kt │ │ │ └── string │ │ │ ├── NotAStringViolation.kt │ │ │ ├── discriminator │ │ │ └── LegacyDiscriminatorProcessor.kt │ │ │ ├── example │ │ │ ├── StringExample.kt │ │ │ └── StringExampleProcessor.kt │ │ │ ├── length │ │ │ ├── Length.kt │ │ │ ├── LengthConstraintProcessor.kt │ │ │ ├── LengthProcessor.kt │ │ │ ├── MaxLength.kt │ │ │ ├── MaxLengthProcessor.kt │ │ │ ├── MinLength.kt │ │ │ └── MinLengthProcessor.kt │ │ │ ├── lowercase │ │ │ ├── LowerCase.kt │ │ │ └── LowerCaseValidator.kt │ │ │ ├── pattern │ │ │ ├── RegularExpression.kt │ │ │ ├── RegularExpressionConstraintProcessor.kt │ │ │ └── RegularExpressionProcessor.kt │ │ │ └── trim │ │ │ ├── Trim.kt │ │ │ └── TrimValidator.kt │ │ ├── content │ │ └── type │ │ │ ├── BodyParser.kt │ │ │ ├── ContentTypeProvider.kt │ │ │ ├── ContentTypeResponder.kt │ │ │ ├── ResponseSerializer.kt │ │ │ ├── SelectedExceptionSerializer.kt │ │ │ ├── SelectedModule.kt │ │ │ ├── SelectedParser.kt │ │ │ ├── SelectedSerializer.kt │ │ │ ├── binary │ │ │ ├── BinaryContentTypeParser.kt │ │ │ ├── BinaryRequest.kt │ │ │ └── BinaryResponse.kt │ │ │ ├── ktor │ │ │ ├── KtorContentProvider.kt │ │ │ ├── KtorRequest.kt │ │ │ └── KtorResponse.kt │ │ │ └── multipart │ │ │ ├── ContentInputStream.kt │ │ │ ├── FormDataRequest.kt │ │ │ ├── MultipartFormDataContentProvider.kt │ │ │ ├── NamedFileInputStream.kt │ │ │ └── PartEncoding.kt │ │ ├── exception │ │ ├── APIException.kt │ │ ├── APIExceptionBuilder.kt │ │ ├── APIExceptionImpl.kt │ │ ├── OpenAPIBadContentException.kt │ │ ├── OpenAPINoParserException.kt │ │ ├── OpenAPINoSerializerException.kt │ │ ├── OpenAPIParseException.kt │ │ └── OpenAPIRequiredFieldException.kt │ │ ├── interop │ │ └── StatusPages.kt │ │ ├── model │ │ ├── DataModel.kt │ │ ├── Described.kt │ │ ├── base │ │ │ ├── ComponentsModel.kt │ │ │ ├── OpenAPIModel.kt │ │ │ ├── PathItemModel.kt │ │ │ └── RefModel.kt │ │ ├── info │ │ │ ├── ContactModel.kt │ │ │ ├── ExampleModel.kt │ │ │ ├── ExternalDocumentationModel.kt │ │ │ ├── InfoModel.kt │ │ │ ├── LicenseModel.kt │ │ │ └── TagModel.kt │ │ ├── operation │ │ │ ├── HeaderModel.kt │ │ │ ├── MediaTypeEncodingModel.kt │ │ │ ├── MediaTypeModel.kt │ │ │ ├── OperationModel.kt │ │ │ ├── ParameterLocation.kt │ │ │ ├── ParameterModel.kt │ │ │ ├── RequestBodyModel.kt │ │ │ └── StatusResponseModel.kt │ │ ├── schema │ │ │ ├── DataFormat.kt │ │ │ ├── DataType.kt │ │ │ ├── Discriminator.kt │ │ │ └── SchemaModel.kt │ │ ├── security │ │ │ ├── APIKeyLocation.kt │ │ │ ├── FlowsModel.kt │ │ │ ├── HttpSecurityScheme.kt │ │ │ ├── SecurityModel.kt │ │ │ ├── SecuritySchemeModel.kt │ │ │ └── SecuritySchemeType.kt │ │ └── server │ │ │ ├── ServerModel.kt │ │ │ └── ServerVariableModel.kt │ │ ├── modules │ │ ├── CachingModuleProvider.kt │ │ ├── DefaultOpenAPIModule.kt │ │ ├── DependentModule.kt │ │ ├── ModuleProvider.kt │ │ ├── OpenAPIModule.kt │ │ ├── RouteOpenAPIModule.kt │ │ ├── handlers │ │ │ ├── AuthHandler.kt │ │ │ ├── RequestHandlerModule.kt │ │ │ ├── ResponseHandlerModule.kt │ │ │ ├── RouteHandler.kt │ │ │ ├── TagHandlerModule.kt │ │ │ └── ThrowOperationHandler.kt │ │ ├── openapi │ │ │ ├── HandlerModule.kt │ │ │ └── OperationModule.kt │ │ ├── providers │ │ │ ├── AuthProvider.kt │ │ │ ├── MethodProvider.kt │ │ │ ├── ParameterProvider.kt │ │ │ ├── PathProvider.kt │ │ │ ├── StatusProvider.kt │ │ │ ├── TagProviderModule.kt │ │ │ └── ThrowInfoProvider.kt │ │ └── util │ │ │ └── CombinedAuthProviders.kt │ │ ├── parameters │ │ ├── HeaderParamStyle.kt │ │ ├── ParameterStyle.kt │ │ ├── PathParamStyle.kt │ │ ├── QueryParamStyle.kt │ │ ├── handlers │ │ │ ├── ModularParameterHandler.kt │ │ │ ├── ParameterHandler.kt │ │ │ └── UnitParameterHandler.kt │ │ ├── parsers │ │ │ ├── builders │ │ │ │ ├── Builder.kt │ │ │ │ ├── BuilderFactory.kt │ │ │ │ ├── BuilderSelector.kt │ │ │ │ ├── BuilderSelectorFactory.kt │ │ │ │ ├── header │ │ │ │ │ └── simple │ │ │ │ │ │ ├── SimpleBuilder.kt │ │ │ │ │ │ └── SimpleBuilderFactory.kt │ │ │ │ ├── path │ │ │ │ │ ├── label │ │ │ │ │ │ ├── LabelBuilder.kt │ │ │ │ │ │ └── LabelBuilderFactory.kt │ │ │ │ │ ├── matrix │ │ │ │ │ │ ├── MatrixBuilder.kt │ │ │ │ │ │ └── MatrixBuilderFactory.kt │ │ │ │ │ └── simple │ │ │ │ │ │ ├── SimpleBuilder.kt │ │ │ │ │ │ └── SimpleBuilderFactory.kt │ │ │ │ └── query │ │ │ │ │ ├── deepobject │ │ │ │ │ ├── ArrayDeepBuilder.kt │ │ │ │ │ ├── CollectionDeepBuilder.kt │ │ │ │ │ ├── DeepBuilder.kt │ │ │ │ │ ├── DeepBuilderFactory.kt │ │ │ │ │ ├── ListDeepBuilder.kt │ │ │ │ │ ├── MapDeepBuilder.kt │ │ │ │ │ └── ObjectDeepBuilder.kt │ │ │ │ │ ├── delimited │ │ │ │ │ ├── ArrayPipeDelimitedBuilder.kt │ │ │ │ │ ├── ArraySpaceDelimitedBuilder.kt │ │ │ │ │ ├── CollectionDelimitedBuilder.kt │ │ │ │ │ ├── ListPipeDelimitedBuilder.kt │ │ │ │ │ ├── ListSpaceDelimitedBuilder.kt │ │ │ │ │ ├── PipeDelimitedBuilderFactory.kt │ │ │ │ │ └── SpaceDelimitedBuilderFactory.kt │ │ │ │ │ └── form │ │ │ │ │ ├── ArrayExplodedFormBuilder.kt │ │ │ │ │ ├── CollectionExplodedFormBuilder.kt │ │ │ │ │ ├── ConverterFormBuilder.kt │ │ │ │ │ ├── FormBuilder.kt │ │ │ │ │ ├── FormBuilderFactory.kt │ │ │ │ │ └── ListExplodedFormBuilder.kt │ │ │ └── converters │ │ │ │ ├── Converter.kt │ │ │ │ ├── ConverterFactory.kt │ │ │ │ ├── ConverterSelector.kt │ │ │ │ ├── ConverterSelectorFactory.kt │ │ │ │ ├── collection │ │ │ │ ├── ArrayConverter.kt │ │ │ │ ├── CollectionConverter.kt │ │ │ │ ├── CollectionConverterFactory.kt │ │ │ │ ├── ListConverter.kt │ │ │ │ └── ListedConverter.kt │ │ │ │ ├── object │ │ │ │ ├── MapConverter.kt │ │ │ │ ├── MappedConverter.kt │ │ │ │ └── ObjectConverter.kt │ │ │ │ └── primitive │ │ │ │ ├── EnumConverter.kt │ │ │ │ ├── PrimitiveConverter.kt │ │ │ │ └── PrimitiveConverterFactory.kt │ │ └── util │ │ │ ├── DateTimeFormatters.kt │ │ │ ├── ListToArray.kt │ │ │ └── Util.kt │ │ ├── route │ │ ├── Exit.kt │ │ ├── Functions.kt │ │ ├── Info.kt │ │ ├── OpenAPIRoute.kt │ │ ├── RouteConfig.kt │ │ ├── Status.kt │ │ ├── TagModule.kt │ │ ├── Throws.kt │ │ ├── modules │ │ │ ├── HttpMethodProviderModule.kt │ │ │ └── PathProviderModule.kt │ │ ├── path │ │ │ ├── auth │ │ │ │ ├── Functions.kt │ │ │ │ └── OpenAPIAuthenticatedRoute.kt │ │ │ └── normal │ │ │ │ ├── Functions.kt │ │ │ │ └── NormalOpenAPIRoute.kt │ │ ├── response │ │ │ └── OpenAPIPipelineResponseContext.kt │ │ └── util │ │ │ └── RouteBuildingUtil.kt │ │ ├── schema │ │ ├── builder │ │ │ ├── FinalSchemaBuilder.kt │ │ │ ├── SchemaBuilder.kt │ │ │ └── provider │ │ │ │ ├── DefaultCollectionSchemaProvider.kt │ │ │ │ ├── DefaultEnumSchemaProvider.kt │ │ │ │ ├── DefaultMapSchemaProvider.kt │ │ │ │ ├── DefaultObjectSchemaProvider.kt │ │ │ │ ├── DefaultPrimitiveSchemaProvider.kt │ │ │ │ ├── DefaultSetSchemaProvider.kt │ │ │ │ ├── FinalSchemaBuilderProvider.kt │ │ │ │ ├── FinalSchemaBuilderProviderModule.kt │ │ │ │ ├── NothingSchemaProvider.kt │ │ │ │ └── SchemaBuilderProviderModule.kt │ │ ├── namer │ │ │ ├── DefaultSchemaNamer.kt │ │ │ └── SchemaNamer.kt │ │ └── processor │ │ │ ├── SchemaProcessor.kt │ │ │ └── SchemaProcessorAnnotation.kt │ │ ├── util │ │ ├── KTypeUtil.kt │ │ ├── SerializationSettings.kt │ │ └── Util.kt │ │ └── validation │ │ ├── ValidationHandler.kt │ │ ├── Validator.kt │ │ ├── ValidatorAnnotation.kt │ │ └── ValidatorBuilder.kt └── test │ └── kotlin │ ├── Basic.kt │ ├── GeneralBehaviorTest.kt │ ├── JwtAuthDocumentationGenerationTest.kt │ ├── Minimal.kt │ ├── OneOf.kt │ ├── TestServer.kt │ ├── TestServerWithJwtAuth.kt │ ├── TestUtil.kt │ └── com │ └── papsign │ └── ktor │ └── openapigen │ ├── content │ └── type │ │ ├── binary │ │ └── BinaryContentTypeParserTest.kt │ │ └── multipart │ │ └── MultipartFormDataContentProviderTest.kt │ ├── parameters │ └── parsers │ │ ├── Util.kt │ │ ├── builder │ │ └── query │ │ │ └── form │ │ │ ├── ArrayBuilderTest.kt │ │ │ ├── EnumBuilderTest.kt │ │ │ ├── ListBuilderTest.kt │ │ │ ├── MapBuilderTest.kt │ │ │ ├── ObjectBuilderTest.kt │ │ │ └── PrimitiveBuilderTest.kt │ │ └── builders │ │ └── query │ │ └── deepobject │ │ ├── ArrayBuilderTest.kt │ │ ├── EnumBuilderTest.kt │ │ ├── ListBuilderTest.kt │ │ ├── MapBuilderTest.kt │ │ ├── ObjectBuilderTest.kt │ │ └── PrimitiveBuilderTest.kt │ └── routing │ ├── GenericsTest.kt │ └── RoutingTest.kt └── todo.txt /.github/workflows/Build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push, pull_request] 3 | jobs: 4 | check: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - uses: actions/setup-java@v1 9 | with: 10 | java-version: '8' 11 | - run: chmod +x gradlew 12 | - run: ./gradlew check --info 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # Sensitive or high-churn files 13 | .idea/**/dataSources/ 14 | .idea/**/dataSources.ids 15 | .idea/**/dataSources.local.xml 16 | .idea/**/sqlDataSources.xml 17 | .idea/**/dynamic.xml 18 | .idea/**/uiDesigner.xml 19 | .idea/**/dbnavigator.xml 20 | 21 | # Gradle 22 | .idea/**/gradle.xml 23 | .idea/**/libraries 24 | 25 | # CMake 26 | cmake-build-debug/ 27 | cmake-build-release/ 28 | 29 | # Mongo Explorer plugin 30 | .idea/**/mongoSettings.xml 31 | 32 | # File-based project format 33 | *.iws 34 | 35 | # IntelliJ 36 | out/ 37 | 38 | # mpeltonen/sbt-idea plugin 39 | .idea_modules/ 40 | 41 | # JIRA plugin 42 | atlassian-ide-plugin.xml 43 | 44 | # Cursive Clojure plugin 45 | .idea/replstate.xml 46 | 47 | # Crashlytics plugin (for Android Studio and IntelliJ) 48 | com_crashlytics_export_strings.xml 49 | crashlytics.properties 50 | crashlytics-build.properties 51 | fabric.properties 52 | 53 | # Editor-based Rest Client 54 | .idea/httpRequests 55 | ### Gradle template 56 | .gradle 57 | /build/ 58 | 59 | # Ignore Gradle GUI config 60 | gradle-app.setting 61 | 62 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 63 | !gradle-wrapper.jar 64 | 65 | # Cache of project 66 | .gradletasknamecache 67 | 68 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 69 | # gradle/wrapper/gradle-wrapper.properties 70 | ### Kotlin template 71 | # Compiled class file 72 | *.class 73 | 74 | # Log file 75 | *.log 76 | 77 | # BlueJ files 78 | *.ctxt 79 | 80 | # Mobile Tools for Java (J2ME) 81 | .mtj.tmp/ 82 | 83 | # Package Files # 84 | *.jar 85 | *.war 86 | *.nar 87 | *.ear 88 | *.zip 89 | *.tar.gz 90 | *.rar 91 | 92 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 93 | hs_err_pid* 94 | 95 | .idea 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ktor OpenAPI Generator 2 | [![](https://jitpack.io/v/1gravity/Ktor-OpenAPI-Generator.svg)](https://jitpack.io/#1gravity/Ktor-OpenAPI-Generator) 3 | [![Build](https://github.com/1gravity/Ktor-OpenAPI-Generator/workflows/Build/badge.svg)](https://github.com/1gravity/Ktor-OpenAPI-Generator/actions) 4 | 5 | The Ktor OpenAPI Generator is a library to automatically generate the descriptor as you route your ktor application. 6 | 7 | Ktor OpenAPI Generator is: 8 | - Modular 9 | - Strongly typed 10 | - Explicit 11 | 12 | Currently Supported: 13 | - Authentication interoperability with strongly typed Principal (OAuth only, see TestServer in tests) 14 | - Content Negotiation interoperability (see TestServer in tests) 15 | - Custom response codes (as parameter in `@Response`) 16 | - Automatic and custom content Type routing and parsing (see `com.papsign.ktor.openapigen.content.type`, Binary Parser and default JSON parser (that uses the ktor implicit parsing/serializing)) 17 | - Exception handling (use `.throws(ex) {}` in the routes with an APIException object) with Status pages interop (with .withAPI in the StatusPages configuration) 18 | - tags (`.tag(tag) {}` in route with a tag object, currently must be an enum, but may be subject to change) 19 | - Spec compliant Parameter Parsing (see basic example) 20 | - Legacy Polymorphism with use of `@DiscriminatorAnnotation()` attribute and sealed classes 21 | 22 | Extra Features: 23 | - Includes Swagger-UI (enabled by default, can be managed in the `install(OpenAPIGen) { ... }` section) 24 | 25 | ## Examples 26 | 27 | Take a look at [a few examples](https://github.com/1gravity/Ktor-OpenAPI-Generator/wiki/A-few-examples) 28 | 29 | ### Who is using it? 30 | 31 | * 32 | 33 | And others... (add your name above) 34 | 35 | ## Installation 36 | 37 | ### Gradle 38 | 39 | Step 1. Add the JitPack repository to your build file: 40 | ```groovy 41 | allprojects { 42 | repositories { 43 | ... 44 | maven { url 'https://jitpack.io' } 45 | } 46 | } 47 | ``` 48 | Step 2. Add the dependency: 49 | ```Kotlin 50 | dependencies { 51 | implementation("com.github.1gravity:Ktor-OpenAPI-Generator:-SNAPSHOT") 52 | } 53 | ``` 54 | 55 | ```Groovy 56 | dependencies { 57 | implementation 'com.github.1gravity:Ktor-OpenAPI-Generator:-SNAPSHOT' 58 | } 59 | ``` 60 | 61 | ### Git Submodule 62 | Install the submodule: 63 | ```shell 64 | git submodule add https://github.com/1gravity/Ktor-OpenAPI-Generator.git openapigen 65 | ``` 66 | 67 | Declare the folder in settings.gradle: 68 | ```groovy 69 | ... 70 | include 'openapigen' 71 | ``` 72 | Declare the dependency in the main build.gradle 73 | ```groovy 74 | apply plugin: 'kotlin' 75 | ... 76 | 77 | dependencies { 78 | compile project(":openapigen") 79 | ... 80 | } 81 | ``` 82 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper 2 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 3 | 4 | plugins { 5 | kotlin("jvm") 6 | `maven-publish` 7 | } 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | val kotlinVersion = plugins.getPlugin(KotlinPluginWrapper::class.java).kotlinPluginVersion 14 | val ktorVersion = findProperty("ktor_version")?.toString() ?: "" 15 | val slf4jVersion = findProperty("slf4j_version")?.toString() ?: "" 16 | val logVersion = findProperty("logback_version")?.toString() ?: "" 17 | 18 | dependencies { 19 | implementation(kotlin("stdlib", kotlinVersion)) 20 | implementation("org.slf4j:slf4j-api:$slf4jVersion") 21 | testImplementation("io.ktor:ktor-server-core:$ktorVersion") 22 | implementation("io.ktor:ktor-server-host-common:$ktorVersion") 23 | implementation("io.ktor:ktor-metrics:$ktorVersion") 24 | implementation("io.ktor:ktor-server-sessions:$ktorVersion") 25 | 26 | implementation("org.webjars:swagger-ui:3.47.1") 27 | 28 | implementation("org.reflections:reflections:0.9.11") // only used while initializing 29 | 30 | testImplementation("io.ktor:ktor-jackson:$ktorVersion") // needed for parameter parsing and multipart parsing 31 | testImplementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.8") // needed for multipart parsing 32 | testImplementation("io.ktor:ktor-server-netty:$ktorVersion") 33 | testImplementation("io.ktor:ktor-server-test-host:$ktorVersion") 34 | testImplementation("ch.qos.logback:logback-classic:$logVersion") 35 | testImplementation("io.ktor:ktor-auth:$ktorVersion") 36 | testImplementation("io.ktor:ktor-auth-jwt:$ktorVersion") 37 | } 38 | 39 | tasks.withType { 40 | kotlinOptions { 41 | jvmTarget = "1.8" 42 | freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn", "-Xuse-experimental=kotlin.ExperimentalStdlibApi") 43 | } 44 | } 45 | 46 | publishing { 47 | publications { 48 | create("maven") { 49 | group = "com.1gravity" 50 | artifactId = "ktor-openapi-generator" 51 | version = "0.2-beta.20" 52 | 53 | from(components["java"]) 54 | 55 | pom { 56 | name.set("Ktor OpenAPI Generator") 57 | description.set("The Ktor OpenAPI Generator automatically generate the OpenAPI documentation based on the Ktor application routing definition") 58 | packaging = "jar" 59 | 60 | licenses { 61 | license { 62 | name.set("The Apache License, Version 2.0") 63 | url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | 3 | # plugins 4 | kotlinVersion=1.4.32 5 | 6 | # libraries 7 | ktor_version=1.5.3 8 | logback_version=1.2.3 9 | slf4j_version=1.7.30 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1gravity/Ktor-OpenAPI-Generator/cc6e10afbd78a319726b89514ac5aacf385e1ee4/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-7.0-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "com.papsign.ktor.openapigen" 2 | 3 | pluginManagement { 4 | val kotlinVersion: String by settings 5 | 6 | plugins { 7 | kotlin("jvm") version kotlinVersion 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/APITag.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen 2 | 3 | import com.papsign.ktor.openapigen.model.info.ExternalDocumentationModel 4 | import com.papsign.ktor.openapigen.model.info.TagModel 5 | 6 | /** 7 | * This interface is used to define tags to classify endpoints. 8 | * It needs to be implemented using an enum so that the processor properly detects equality. 9 | * 10 | * This is assigned to a service using [com.papsign.ktor.openapigen.route.tag]. 11 | * 12 | * Implementation example: 13 | * 14 | * enum class Tags(override val description: String) : APITag { 15 | * EXAMPLE("Wow this is a tag?!") 16 | * } 17 | * 18 | * @see [com.papsign.ktor.openapigen.route.tag] 19 | */ 20 | interface APITag { 21 | val name: String 22 | val description: String 23 | val externalDocs: ExternalDocumentationModel? 24 | get() = null 25 | 26 | fun toTag(): TagModel { 27 | return TagModel(name, description, externalDocs) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/OpenAPIGenExtension.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen 2 | 3 | interface OpenAPIGenExtension { 4 | fun onInit(gen: OpenAPIGen) 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/OpenAPIGenModuleExtension.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen 2 | 3 | import com.papsign.ktor.openapigen.modules.OpenAPIModule 4 | import kotlin.reflect.full.starProjectedType 5 | 6 | /** 7 | * implement this to automatically register an object as [OpenAPIModule] in the global context 8 | * only works if the object is in a package declared in [OpenAPIGen.Configuration.scanPackagesForModules] 9 | */ 10 | interface OpenAPIGenModuleExtension: OpenAPIModule, OpenAPIGenExtension { 11 | override fun onInit(gen: OpenAPIGen) { 12 | gen.globalModuleProvider.registerModule(this, this::class.starProjectedType) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/SchemaMap.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("UNCHECKED_CAST") 2 | 3 | package com.papsign.ktor.openapigen 4 | 5 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 6 | import com.papsign.ktor.openapigen.util.getKType 7 | import kotlin.reflect.KType 8 | 9 | typealias SchemaMap = Map> 10 | 11 | typealias MutableSchemaMap = MutableMap> 12 | 13 | typealias LinkedHashSchemaMap = LinkedHashMap> 14 | typealias HashSchemaMap = HashMap> 15 | 16 | inline fun SchemaMap.get(): SchemaModel? { 17 | return get(getKType()) as SchemaModel? 18 | } 19 | 20 | inline fun SchemaMap.containsKey(): Boolean { 21 | return containsKey(getKType()) 22 | } 23 | 24 | inline fun MutableSchemaMap.put(value: SchemaModel): SchemaModel? { 25 | return put(getKType(), value) as SchemaModel? 26 | } 27 | 28 | inline fun MutableSchemaMap.remove(): SchemaModel? { 29 | return remove(getKType()) as SchemaModel? 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/SwaggerUI.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen 2 | 3 | import io.ktor.application.ApplicationCall 4 | import io.ktor.features.origin 5 | import io.ktor.http.ContentType 6 | import io.ktor.http.ContentType.Image.PNG 7 | import io.ktor.http.ContentType.Text.CSS 8 | import io.ktor.http.ContentType.Text.Html 9 | import io.ktor.http.ContentType.Text.JavaScript 10 | import io.ktor.http.content.OutgoingContent 11 | import io.ktor.http.withCharset 12 | import io.ktor.request.host 13 | import io.ktor.request.port 14 | import io.ktor.response.respond 15 | import java.net.URL 16 | 17 | class SwaggerUI(private val basePath: String, private val version: String) { 18 | private val notFound = mutableListOf() 19 | private val content = mutableMapOf() 20 | 21 | private class ResourceContent(val resource: URL, val address: String) : OutgoingContent.ByteArrayContent() { 22 | private val contentTypes = mapOf( 23 | "html" to Html, 24 | "css" to CSS, 25 | "js" to JavaScript, 26 | "json" to ContentType.Application.Json.withCharset(Charsets.UTF_8), 27 | "png" to PNG 28 | ) 29 | 30 | private val bytes by lazy { 31 | if (contentType == JavaScript) { 32 | resource.readText().replace("http://localhost:3200/oauth2-redirect.html", address + "oauth2-redirect.html").toByteArray() 33 | } else resource.readBytes() 34 | } 35 | 36 | override val contentType: ContentType? by lazy { 37 | val extension = resource.file.substring(resource.file.lastIndexOf('.') + 1) 38 | contentTypes[extension] ?: Html 39 | } 40 | 41 | override val contentLength: Long? by lazy { 42 | bytes.size.toLong() 43 | } 44 | 45 | override fun bytes(): ByteArray = bytes 46 | override fun toString() = "ResourceContent \"$resource\"" 47 | } 48 | 49 | suspend fun serve(filename: String?, call: ApplicationCall) { 50 | when (filename) { 51 | in notFound -> return 52 | null -> return 53 | else -> { 54 | val resource = this::class.java.getResource("/META-INF/resources/webjars/swagger-ui/$version/$filename") 55 | if (resource == null) { 56 | notFound.add(filename) 57 | return 58 | } 59 | call.respond(content.getOrPut(filename) { ResourceContent(resource, call.redirectUrl()) }) 60 | } 61 | } 62 | } 63 | 64 | private fun ApplicationCall.redirectUrl(): String { 65 | val defaultPort = if (request.origin.scheme == "http") 80 else 443 66 | val hostPort = request.host() + request.port().let { port -> if (port == defaultPort) "" else ":$port" } 67 | val protocol = request.origin.scheme 68 | return "$protocol://$hostPort/${basePath.trim('/')}/" 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/Path.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations 2 | 3 | @Target(AnnotationTarget.CLASS) 4 | @Retention(AnnotationRetention.RUNTIME) 5 | annotation class Path(val path: String) -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/Request.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations 2 | 3 | @Target(AnnotationTarget.CLASS) 4 | @Retention(AnnotationRetention.RUNTIME) 5 | annotation class Request(val description: String = "") -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/Response.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations 2 | 3 | @Target(AnnotationTarget.CLASS) 4 | @Retention(AnnotationRetention.RUNTIME) 5 | annotation class Response(val description: String = "", val statusCode: Int = 200) -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/encodings/APIRequestFormat.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.encodings 2 | 3 | @Target(AnnotationTarget.CLASS) 4 | @Retention(AnnotationRetention.RUNTIME) 5 | annotation class APIRequestFormat -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/encodings/APIResponseFormat.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.encodings 2 | 3 | @Target(AnnotationTarget.CLASS) 4 | @Retention(AnnotationRetention.RUNTIME) 5 | annotation class APIResponseFormat -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/mapping/OpenAPIName.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.mapping 2 | 3 | import com.papsign.ktor.openapigen.annotations.parameters.HeaderParam 4 | import java.util.* 5 | import kotlin.reflect.KParameter 6 | import kotlin.reflect.full.findAnnotation 7 | 8 | @Target(AnnotationTarget.VALUE_PARAMETER) 9 | annotation class OpenAPIName(val name: String) 10 | 11 | private val cache = Collections.synchronizedMap(HashMap()) 12 | 13 | val KParameter.openAPIName: String? 14 | get() = cache.getOrPut(this) { 15 | val caseSensitiveName = findAnnotation()?.name ?: name 16 | if (findAnnotation() != null) caseSensitiveName?.toLowerCase() else caseSensitiveName 17 | } 18 | 19 | fun KParameter.remapOpenAPINames(map: Map): Map { 20 | val replace = this.openAPIName 21 | val actual = this.name 22 | return if (actual != null && replace != actual) map.mapKeys { (key, _) -> if (key == replace) actual else key } else map 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/parameters/APIParam.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.parameters 2 | 3 | import com.papsign.ktor.openapigen.model.operation.ParameterLocation 4 | 5 | 6 | @Target(AnnotationTarget.ANNOTATION_CLASS) 7 | @Retention(AnnotationRetention.RUNTIME) 8 | annotation class APIParam(val `in`: ParameterLocation) 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/parameters/HeaderParam.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.parameters 2 | 3 | import com.papsign.ktor.openapigen.model.operation.ParameterLocation 4 | import com.papsign.ktor.openapigen.parameters.HeaderParamStyle 5 | 6 | @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) 7 | @Retention(AnnotationRetention.RUNTIME) 8 | @APIParam(ParameterLocation.header) 9 | annotation class HeaderParam( 10 | val description: String, 11 | val style: HeaderParamStyle = HeaderParamStyle.simple, 12 | val explode: Boolean = true, 13 | val allowEmptyValues: Boolean = false, 14 | val deprecated: Boolean = false 15 | ) 16 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/parameters/ParamAnnotationUtil.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.parameters 2 | 3 | import kotlin.reflect.full.findAnnotation 4 | 5 | internal val HeaderParam.apiParam: APIParam 6 | get() = annotationClass.findAnnotation()!! 7 | 8 | internal val QueryParam.apiParam: APIParam 9 | get() = annotationClass.findAnnotation()!! 10 | 11 | internal val PathParam.apiParam: APIParam 12 | get() = annotationClass.findAnnotation()!! -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/parameters/PathParam.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.parameters 2 | 3 | import com.papsign.ktor.openapigen.model.operation.ParameterLocation 4 | import com.papsign.ktor.openapigen.parameters.PathParamStyle 5 | 6 | @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) 7 | @Retention(AnnotationRetention.RUNTIME) 8 | @APIParam(ParameterLocation.path) 9 | annotation class PathParam( 10 | val description: String, 11 | val style: PathParamStyle = PathParamStyle.simple, 12 | val explode: Boolean = false, 13 | val deprecated: Boolean = false 14 | ) 15 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/parameters/QueryParam.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.parameters 2 | 3 | import com.papsign.ktor.openapigen.model.operation.ParameterLocation 4 | import com.papsign.ktor.openapigen.parameters.QueryParamStyle 5 | 6 | @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) 7 | @Retention(AnnotationRetention.RUNTIME) 8 | @APIParam(ParameterLocation.query) 9 | annotation class QueryParam( 10 | val description: String, 11 | val style: QueryParamStyle = QueryParamStyle.form, 12 | val explode: Boolean = true, 13 | val allowEmptyValues: Boolean = false, 14 | val deprecated: Boolean = false 15 | ) 16 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/properties/description/Description.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.properties.description 2 | 3 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation 4 | 5 | /** 6 | * Property annotation for providing a description of a schema model property 7 | */ 8 | @Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) 9 | @SchemaProcessorAnnotation(DescriptionProcessor::class) 10 | annotation class Description(val value: String) 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/properties/description/DescriptionProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.properties.description 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessor 5 | import kotlin.reflect.KType 6 | 7 | object DescriptionProcessor: SchemaProcessor { 8 | override fun process(model: SchemaModel<*>, type: KType, annotation: Description): SchemaModel<*> { 9 | model.description = annotation.value 10 | return model 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/SingleTypeValidator.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type 2 | 3 | import com.papsign.ktor.openapigen.validation.Validator 4 | import com.papsign.ktor.openapigen.validation.ValidatorBuilder 5 | import kotlin.reflect.KType 6 | import kotlin.reflect.full.withNullability 7 | 8 | open class SingleTypeValidator(allowedType: KType, private val validator: (A)-> Validator): ValidatorBuilder { 9 | private val allowedType: KType = allowedType.withNullability(false) 10 | override fun build(type: KType, annotation: A): Validator { 11 | if (type.withNullability(false) == allowedType) return validator(annotation) 12 | error("${annotation::class} annotation cannot be applied to type: $type, only $allowedType is allowed") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/common/ConstraintViolation.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.common 2 | 3 | import java.lang.Exception 4 | 5 | abstract class ConstraintViolation(defaultMessage: String, message: String = "", cause: Throwable? = null) 6 | : Exception(message.ifEmpty { defaultMessage }, cause) -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/number/floating/FloatingNumberConstraintProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.number.floating 2 | 3 | import com.papsign.ktor.openapigen.util.getKType 4 | import com.papsign.ktor.openapigen.annotations.type.number.NumberConstraintProcessor 5 | 6 | abstract class FloatingNumberConstraintProcessor: NumberConstraintProcessor(listOf( 7 | getKType(), 8 | getKType() 9 | )) 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/number/floating/clamp/FClamp.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.number.floating.clamp 2 | 3 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation 4 | import com.papsign.ktor.openapigen.validation.ValidatorAnnotation 5 | 6 | @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY) 7 | @SchemaProcessorAnnotation(FClampProcessor::class) 8 | @ValidatorAnnotation(FClampProcessor::class) 9 | annotation class FClamp(val min: Double, val max: Double, val errorMessage: String = "") 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/number/floating/clamp/FClampProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.number.floating.clamp 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | import com.papsign.ktor.openapigen.annotations.type.number.NumberConstraint 5 | import com.papsign.ktor.openapigen.annotations.type.number.floating.FloatingNumberConstraintProcessor 6 | import java.math.BigDecimal 7 | 8 | object FClampProcessor : FloatingNumberConstraintProcessor() { 9 | override fun process( 10 | modelLitteral: SchemaModel.SchemaModelLitteral<*>, 11 | annotation: FClamp 12 | ): SchemaModel.SchemaModelLitteral<*> { 13 | @Suppress("UNCHECKED_CAST") 14 | return (modelLitteral as SchemaModel.SchemaModelLitteral).apply { 15 | minimum = annotation.min 16 | maximum = annotation.max 17 | } 18 | } 19 | 20 | override fun getConstraint(annotation: FClamp): NumberConstraint { 21 | return NumberConstraint(BigDecimal(annotation.min), BigDecimal(annotation.max), errorMessage = annotation.errorMessage) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/number/floating/max/FMax.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.number.floating.max 2 | 3 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation 4 | import com.papsign.ktor.openapigen.validation.ValidatorAnnotation 5 | 6 | @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY) 7 | @SchemaProcessorAnnotation(FMaxProcessor::class) 8 | @ValidatorAnnotation(FMaxProcessor::class) 9 | annotation class FMax(val value: Double, val errorMessage: String = "") 10 | 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/number/floating/max/FMaxProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.number.floating.max 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | import com.papsign.ktor.openapigen.annotations.type.number.NumberConstraint 5 | import com.papsign.ktor.openapigen.annotations.type.number.floating.FloatingNumberConstraintProcessor 6 | import java.math.BigDecimal 7 | 8 | object FMaxProcessor: FloatingNumberConstraintProcessor() { 9 | override fun process( 10 | modelLitteral: SchemaModel.SchemaModelLitteral<*>, 11 | annotation: FMax 12 | ): SchemaModel.SchemaModelLitteral<*> { 13 | @Suppress("UNCHECKED_CAST") 14 | return (modelLitteral as SchemaModel.SchemaModelLitteral).apply { 15 | maximum = annotation.value 16 | } 17 | } 18 | override fun getConstraint(annotation: FMax): NumberConstraint { 19 | return NumberConstraint(max= BigDecimal(annotation.value), errorMessage = annotation.errorMessage) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/number/floating/min/FMin.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.number.floating.min 2 | 3 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation 4 | import com.papsign.ktor.openapigen.validation.ValidatorAnnotation 5 | 6 | @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY) 7 | @SchemaProcessorAnnotation(FMinProcessor::class) 8 | @ValidatorAnnotation(FMinProcessor::class) 9 | annotation class FMin(val value: Double, val errorMessage: String = "") 10 | 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/number/floating/min/FMinProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.number.floating.min 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | import com.papsign.ktor.openapigen.annotations.type.number.NumberConstraint 5 | import com.papsign.ktor.openapigen.annotations.type.number.floating.FloatingNumberConstraintProcessor 6 | import java.math.BigDecimal 7 | 8 | object FMinProcessor: FloatingNumberConstraintProcessor() { 9 | override fun process( 10 | modelLitteral: SchemaModel.SchemaModelLitteral<*>, 11 | annotation: FMin 12 | ): SchemaModel.SchemaModelLitteral<*> { 13 | @Suppress("UNCHECKED_CAST") 14 | return (modelLitteral as SchemaModel.SchemaModelLitteral).apply { 15 | minimum = annotation.value 16 | } 17 | } 18 | 19 | override fun getConstraint(annotation: FMin): NumberConstraint { 20 | return NumberConstraint(min = BigDecimal(annotation.value), errorMessage = annotation.errorMessage) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/number/integer/IntegerNumberConstraintProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.number.integer 2 | 3 | import com.papsign.ktor.openapigen.util.getKType 4 | import com.papsign.ktor.openapigen.annotations.type.number.NumberConstraintProcessor 5 | 6 | abstract class IntegerNumberConstraintProcessor: NumberConstraintProcessor(listOf( 7 | getKType(), 8 | getKType(), 9 | getKType(), 10 | getKType() 11 | )) 12 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/number/integer/clamp/Clamp.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.number.integer.clamp 2 | 3 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation 4 | import com.papsign.ktor.openapigen.validation.ValidatorAnnotation 5 | 6 | @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY) 7 | @SchemaProcessorAnnotation(ClampProcessor::class) 8 | @ValidatorAnnotation(ClampProcessor::class) 9 | annotation class Clamp(val min: Long, val max: Long, val errorMessage: String = "") 10 | 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/number/integer/clamp/ClampProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.number.integer.clamp 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | import com.papsign.ktor.openapigen.annotations.type.number.NumberConstraint 5 | import com.papsign.ktor.openapigen.annotations.type.number.integer.IntegerNumberConstraintProcessor 6 | import java.math.BigDecimal 7 | 8 | object ClampProcessor: IntegerNumberConstraintProcessor() { 9 | override fun process( 10 | modelLitteral: SchemaModel.SchemaModelLitteral<*>, 11 | annotation: Clamp 12 | ): SchemaModel.SchemaModelLitteral<*> { 13 | @Suppress("UNCHECKED_CAST") 14 | return (modelLitteral as SchemaModel.SchemaModelLitteral).apply { 15 | minimum = annotation.min 16 | maximum = annotation.max 17 | } 18 | } 19 | 20 | override fun getConstraint(annotation: Clamp): NumberConstraint { 21 | return NumberConstraint(BigDecimal(annotation.min), BigDecimal(annotation.max), errorMessage = annotation.errorMessage) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/number/integer/max/Max.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.number.integer.max 2 | 3 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation 4 | import com.papsign.ktor.openapigen.validation.ValidatorAnnotation 5 | 6 | @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY) 7 | @SchemaProcessorAnnotation(MaxProcessor::class) 8 | @ValidatorAnnotation(MaxProcessor::class) 9 | annotation class Max(val value: Long, val errorMessage: String = "") 10 | 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/number/integer/max/MaxProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.number.integer.max 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | import com.papsign.ktor.openapigen.annotations.type.number.NumberConstraint 5 | import com.papsign.ktor.openapigen.annotations.type.number.integer.IntegerNumberConstraintProcessor 6 | import java.math.BigDecimal 7 | 8 | object MaxProcessor: IntegerNumberConstraintProcessor() { 9 | override fun process( 10 | modelLitteral: SchemaModel.SchemaModelLitteral<*>, 11 | annotation: Max 12 | ): SchemaModel.SchemaModelLitteral<*> { 13 | @Suppress("UNCHECKED_CAST") 14 | return (modelLitteral as SchemaModel.SchemaModelLitteral).apply { 15 | maximum = annotation.value 16 | } 17 | } 18 | override fun getConstraint(annotation: Max): NumberConstraint { 19 | return NumberConstraint(max= BigDecimal(annotation.value), errorMessage = annotation.errorMessage) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/number/integer/min/Min.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.number.integer.min 2 | 3 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation 4 | import com.papsign.ktor.openapigen.validation.ValidatorAnnotation 5 | 6 | @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY) 7 | @SchemaProcessorAnnotation(MinProcessor::class) 8 | @ValidatorAnnotation(MinProcessor::class) 9 | annotation class Min(val value: Long, val errorMessage: String = "") 10 | 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/number/integer/min/MinProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.number.integer.min 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | import com.papsign.ktor.openapigen.annotations.type.number.NumberConstraint 5 | import com.papsign.ktor.openapigen.annotations.type.number.integer.IntegerNumberConstraintProcessor 6 | import java.math.BigDecimal 7 | 8 | object MinProcessor: IntegerNumberConstraintProcessor() { 9 | override fun process( 10 | modelLitteral: SchemaModel.SchemaModelLitteral<*>, 11 | annotation: Min 12 | ): SchemaModel.SchemaModelLitteral<*> { 13 | @Suppress("UNCHECKED_CAST") 14 | return (modelLitteral as SchemaModel.SchemaModelLitteral).apply { 15 | minimum = annotation.value 16 | } 17 | } 18 | 19 | override fun getConstraint(annotation: Min): NumberConstraint { 20 | return NumberConstraint(min = BigDecimal(annotation.value), errorMessage = annotation.errorMessage) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/object/example/ExampleProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.`object`.example 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessor 5 | import kotlin.reflect.KType 6 | import kotlin.reflect.full.companionObjectInstance 7 | import kotlin.reflect.jvm.jvmErasure 8 | 9 | object ExampleProcessor : SchemaProcessor { 10 | override fun process(model: SchemaModel<*>, type: KType, annotation: WithExample): SchemaModel<*> { 11 | val exampleClass = if (annotation.provider == NoExampleProvider::class) { 12 | type.jvmErasure.companionObjectInstance as? ExampleProvider<*> 13 | ?: error("Classes annotated with ${WithExample::class.simpleName} without a specified example provider must have a companion object implementing ${ExampleProvider::class}") 14 | } else { 15 | annotation.provider.objectInstance ?: error("Classes extending ${ExampleProvider::class} must be objects") 16 | } 17 | @Suppress("UNCHECKED_CAST") 18 | (model as SchemaModel).apply { 19 | examples = examples?.plus(exampleClass.examples ?: listOf()) ?: exampleClass.examples 20 | if (example != null) { 21 | if (exampleClass.example != null) 22 | examples = examples?.plus(exampleClass.example) 23 | } else { 24 | example = exampleClass.example 25 | } 26 | } 27 | return model 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/object/example/ExampleProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.`object`.example 2 | 3 | interface ExampleProvider { 4 | val example: T? 5 | get() = null 6 | val examples: List? 7 | get() = null 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/object/example/NoExampleProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.`object`.example 2 | 3 | internal class NoExampleProvider private constructor(): ExampleProvider 4 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/object/example/WithExample.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.`object`.example 2 | 3 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation 4 | import kotlin.reflect.KClass 5 | 6 | /** 7 | * Careful, no type checking done if you give the wrong provider 8 | */ 9 | @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE, AnnotationTarget.PROPERTY) 10 | @SchemaProcessorAnnotation(ExampleProcessor::class) 11 | annotation class WithExample(val provider: KClass> = NoExampleProvider::class) 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/NotAStringViolation.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string 2 | 3 | import com.papsign.ktor.openapigen.annotations.type.common.ConstraintViolation 4 | 5 | class NotAStringViolation(val value: Any?): ConstraintViolation("Constraint violation: $value is not a string") -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/discriminator/LegacyDiscriminatorProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.example 2 | 3 | import com.papsign.ktor.openapigen.model.schema.DataFormat 4 | import com.papsign.ktor.openapigen.model.schema.DataType 5 | import com.papsign.ktor.openapigen.model.schema.Discriminator 6 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 7 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessor 8 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation 9 | import kotlin.reflect.KType 10 | 11 | @Target(AnnotationTarget.CLASS) 12 | @SchemaProcessorAnnotation(LegacyDiscriminatorProcessor::class) 13 | annotation class DiscriminatorAnnotation(val fieldName: String = "type") 14 | 15 | // Difference between legacy mode and current 16 | // https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators/typescript.md 17 | // For non-legacy mapping is from sub-types to base-type by allOf 18 | // This implementation follow previous implementation, so we need to have 19 | // - discriminatorName in each type Parameters array 20 | // - { discriminator: { propertyName: discriminatorName } } in each type 21 | object LegacyDiscriminatorProcessor : SchemaProcessor { 22 | override fun process(model: SchemaModel<*>, type: KType, annotation: DiscriminatorAnnotation): SchemaModel<*> { 23 | val mapElement = (annotation.fieldName to SchemaModel.SchemaModelLitteral( 24 | DataType.string, 25 | DataFormat.string, 26 | false 27 | )) 28 | 29 | if (model is SchemaModel.OneSchemaModelOf<*>) { 30 | return SchemaModel.OneSchemaModelOf( 31 | model.oneOf, 32 | mapOf(mapElement), 33 | Discriminator(annotation.fieldName) 34 | ) 35 | } 36 | 37 | if (model is SchemaModel.SchemaModelObj<*>) { 38 | 39 | return SchemaModel.SchemaModelObj( 40 | model.properties + mapElement, 41 | model.required, 42 | model.nullable, 43 | model.example, 44 | model.examples, 45 | model.type, 46 | model.description, 47 | Discriminator(annotation.fieldName) 48 | ) 49 | } 50 | 51 | return model 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/example/StringExample.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.example 2 | 3 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation 4 | 5 | /** 6 | * Provide examples for a String property 7 | */ 8 | @Target(AnnotationTarget.PROPERTY) 9 | @SchemaProcessorAnnotation(StringExampleProcessor::class) 10 | annotation class StringExample(vararg val examples: String) 11 | 12 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/example/StringExampleProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.example 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessor 5 | import kotlin.reflect.KType 6 | 7 | object StringExampleProcessor: SchemaProcessor { 8 | override fun process(model: SchemaModel<*>, type: KType, annotation: StringExample): SchemaModel<*> { 9 | (model as SchemaModel).apply { 10 | if (annotation.examples.size > 1) { 11 | examples = examples?.plus(annotation.examples) ?: annotation.examples.asList() 12 | } else { 13 | if (example == null) { 14 | example = annotation.examples.getOrNull(0) 15 | } else { 16 | examples = examples?.plus(annotation.examples) 17 | } 18 | } 19 | } 20 | return model 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/length/Length.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.length 2 | 3 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation 4 | import com.papsign.ktor.openapigen.validation.ValidatorAnnotation 5 | 6 | @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY) 7 | @SchemaProcessorAnnotation(LengthProcessor::class) 8 | @ValidatorAnnotation(LengthProcessor::class) 9 | annotation class Length(val min: Int, val max: Int, val errorMessage: String = "") 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/length/LengthProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.length 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | 5 | object LengthProcessor : LengthConstraintProcessor() { 6 | override fun process(model: SchemaModel.SchemaModelLitteral<*>, annotation: Length): SchemaModel.SchemaModelLitteral<*> { 7 | @Suppress("UNCHECKED_CAST") 8 | return (model as SchemaModel.SchemaModelLitteral).apply { 9 | maxLength = annotation.max 10 | minLength = annotation.min 11 | } 12 | } 13 | 14 | override fun getConstraint(annotation: Length): LengthConstraint { 15 | return LengthConstraint(min = annotation.min, max = annotation.max, errorMessage = annotation.errorMessage) 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/length/MaxLength.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.length 2 | 3 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation 4 | import com.papsign.ktor.openapigen.validation.ValidatorAnnotation 5 | 6 | @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY) 7 | @SchemaProcessorAnnotation(MaxLengthProcessor::class) 8 | @ValidatorAnnotation(MaxLengthProcessor::class) 9 | annotation class MaxLength(val value: Int, val errorMessage: String = "") 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/length/MaxLengthProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.length 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | 5 | object MaxLengthProcessor : LengthConstraintProcessor() { 6 | override fun process(model: SchemaModel.SchemaModelLitteral<*>, annotation: MaxLength): SchemaModel.SchemaModelLitteral<*> { 7 | @Suppress("UNCHECKED_CAST") 8 | return (model as SchemaModel.SchemaModelLitteral).apply { 9 | maxLength = annotation.value 10 | } 11 | } 12 | 13 | override fun getConstraint(annotation: MaxLength): LengthConstraint { 14 | return LengthConstraint(max = annotation.value, errorMessage = annotation.errorMessage) 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/length/MinLength.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.length 2 | 3 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation 4 | import com.papsign.ktor.openapigen.validation.ValidatorAnnotation 5 | 6 | @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY) 7 | @SchemaProcessorAnnotation(MinLengthProcessor::class) 8 | @ValidatorAnnotation(MinLengthProcessor::class) 9 | annotation class MinLength(val value: Int, val errorMessage: String = "") 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/length/MinLengthProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.length 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | 5 | object MinLengthProcessor : LengthConstraintProcessor() { 6 | override fun process(model: SchemaModel.SchemaModelLitteral<*>, annotation: MinLength): SchemaModel.SchemaModelLitteral<*> { 7 | @Suppress("UNCHECKED_CAST") 8 | return (model as SchemaModel.SchemaModelLitteral).apply { 9 | minLength = annotation.value 10 | } 11 | } 12 | 13 | override fun getConstraint(annotation: MinLength): LengthConstraint { 14 | return LengthConstraint(min = annotation.value, errorMessage = annotation.errorMessage) 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/lowercase/LowerCase.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.lowercase 2 | 3 | import com.papsign.ktor.openapigen.validation.ValidatorAnnotation 4 | 5 | @Target(AnnotationTarget.PROPERTY, AnnotationTarget.TYPE) 6 | @ValidatorAnnotation(LowerCaseValidator::class) 7 | annotation class LowerCase 8 | 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/lowercase/LowerCaseValidator.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.lowercase 2 | 3 | import com.papsign.ktor.openapigen.util.getKType 4 | import com.papsign.ktor.openapigen.validation.Validator 5 | import com.papsign.ktor.openapigen.annotations.type.SingleTypeValidator 6 | 7 | object LowerCaseValidator : SingleTypeValidator(getKType(), { LowerCaseValidator }), Validator { 8 | override fun validate(subject: T?): T? { 9 | @Suppress("UNCHECKED_CAST") 10 | return (subject as String?)?.toLowerCase() as T? 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/pattern/RegularExpression.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.pattern 2 | 3 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessorAnnotation 4 | import com.papsign.ktor.openapigen.validation.ValidatorAnnotation 5 | 6 | @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY) 7 | @SchemaProcessorAnnotation(RegularExpressionProcessor::class) 8 | @ValidatorAnnotation(RegularExpressionProcessor::class) 9 | annotation class RegularExpression(@org.intellij.lang.annotations.Language("RegExp") val pattern: String, val errorMessage: String = "") 10 | 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/pattern/RegularExpressionConstraintProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.pattern 2 | 3 | import com.papsign.ktor.openapigen.annotations.type.common.ConstraintViolation 4 | import com.papsign.ktor.openapigen.annotations.type.string.NotAStringViolation 5 | import com.papsign.ktor.openapigen.util.classLogger 6 | import com.papsign.ktor.openapigen.util.getKType 7 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 8 | import com.papsign.ktor.openapigen.schema.processor.SchemaProcessor 9 | import com.papsign.ktor.openapigen.validation.Validator 10 | import com.papsign.ktor.openapigen.validation.ValidatorBuilder 11 | import kotlin.reflect.KType 12 | import kotlin.reflect.full.withNullability 13 | 14 | abstract class RegularExpressionConstraintProcessor(): SchemaProcessor, ValidatorBuilder { 15 | 16 | private val log = classLogger() 17 | 18 | val types = listOf(getKType().withNullability(true), getKType().withNullability(false)) 19 | 20 | abstract fun process(model: SchemaModel.SchemaModelLitteral<*>, annotation: A): SchemaModel.SchemaModelLitteral<*> 21 | 22 | abstract fun getConstraint(annotation: A): RegularExpressionConstraint 23 | 24 | private class RegularExpressionConstraintValidator(private val constraint: RegularExpressionConstraint): Validator { 25 | override fun validate(subject: T?): T? { 26 | if (subject is String?) { 27 | if (subject == null || !constraint.pattern.toRegex().containsMatchIn(subject)) { 28 | throw RegularExpressionConstraintViolation(subject, constraint) 29 | } 30 | } else { 31 | throw NotAStringViolation(subject) 32 | } 33 | return subject 34 | } 35 | } 36 | 37 | override fun build(type: KType, annotation: A): Validator { 38 | return if (types.contains(type)) { 39 | RegularExpressionConstraintValidator(getConstraint(annotation)) 40 | } else { 41 | error("${annotation::class} can only be used on types: $types") 42 | } 43 | } 44 | 45 | override fun process(model: SchemaModel<*>, type: KType, annotation: A): SchemaModel<*> { 46 | return if (model is SchemaModel.SchemaModelLitteral<*> && types.contains(type)) { 47 | process(model, annotation) 48 | } else { 49 | log.warn("${annotation::class} can only be used on types: $types") 50 | model 51 | } 52 | } 53 | } 54 | 55 | data class RegularExpressionConstraint(val pattern: String, val errorMessage: String) 56 | 57 | class RegularExpressionConstraintViolation(val actual: String?, val constraint: RegularExpressionConstraint): ConstraintViolation("Constraint violation: the string " + 58 | "'$actual' does not match the regular expression ${constraint.pattern}", constraint.errorMessage) -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/pattern/RegularExpressionProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.pattern 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | 5 | object RegularExpressionProcessor : RegularExpressionConstraintProcessor() { 6 | override fun process(model: SchemaModel.SchemaModelLitteral<*>, annotation: RegularExpression): SchemaModel.SchemaModelLitteral<*> { 7 | @Suppress("UNCHECKED_CAST") 8 | return (model as SchemaModel.SchemaModelLitteral).apply { 9 | pattern = annotation.pattern 10 | } 11 | } 12 | 13 | override fun getConstraint(annotation: RegularExpression): RegularExpressionConstraint { 14 | return RegularExpressionConstraint(annotation.pattern, annotation.errorMessage) 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/trim/Trim.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.trim 2 | 3 | import com.papsign.ktor.openapigen.validation.ValidatorAnnotation 4 | 5 | @Target(AnnotationTarget.PROPERTY, AnnotationTarget.TYPE) 6 | @ValidatorAnnotation(TrimValidator::class) 7 | annotation class Trim 8 | 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/annotations/type/string/trim/TrimValidator.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.annotations.type.string.trim 2 | 3 | import com.papsign.ktor.openapigen.util.getKType 4 | import com.papsign.ktor.openapigen.validation.Validator 5 | import com.papsign.ktor.openapigen.annotations.type.SingleTypeValidator 6 | 7 | object TrimValidator : SingleTypeValidator(getKType(), { TrimValidator }), Validator { 8 | override fun validate(subject: T?): T? { 9 | @Suppress("UNCHECKED_CAST") 10 | return (subject as String?)?.trim() as T? 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/BodyParser.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type 2 | 3 | import io.ktor.application.ApplicationCall 4 | import io.ktor.http.ContentType 5 | import io.ktor.util.pipeline.PipelineContext 6 | import kotlin.reflect.KType 7 | 8 | interface BodyParser: ContentTypeProvider { 9 | fun getParseableContentTypes(type: KType): List 10 | suspend fun parseBody(clazz: KType, request: PipelineContext): T 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/ContentTypeProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.model.operation.MediaTypeModel 5 | import com.papsign.ktor.openapigen.modules.ModuleProvider 6 | import com.papsign.ktor.openapigen.modules.OpenAPIModule 7 | import io.ktor.http.ContentType 8 | import kotlin.reflect.KType 9 | 10 | interface ContentTypeProvider: OpenAPIModule { 11 | 12 | enum class Usage { 13 | SERIALIZE, PARSE 14 | } 15 | 16 | /** 17 | * Done once when routes are created, for request object and response object 18 | * @return null to disable module, or [Map] to register the handler on every content type 19 | * @throws Exception to signal a bad configuration (usually with assert) 20 | */ 21 | fun getMediaType(type: KType, apiGen: OpenAPIGen, provider: ModuleProvider<*>, example: T?, usage: Usage): Map>? 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/ContentTypeResponder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type 2 | 3 | import com.papsign.ktor.openapigen.route.response.Responder 4 | import io.ktor.application.ApplicationCall 5 | import io.ktor.http.ContentType 6 | import io.ktor.http.HttpStatusCode 7 | import io.ktor.util.pipeline.PipelineContext 8 | 9 | data class ContentTypeResponder(val responseSerializer: ResponseSerializer, val contentType: ContentType): Responder { 10 | override suspend fun respond(response: T, request: PipelineContext) { 11 | responseSerializer.respond(response, request, contentType) 12 | } 13 | 14 | override suspend fun respond(statusCode: HttpStatusCode, response: T, request: PipelineContext) { 15 | responseSerializer.respond(statusCode, response, request, contentType) 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/ResponseSerializer.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type 2 | 3 | import io.ktor.application.ApplicationCall 4 | import io.ktor.http.ContentType 5 | import io.ktor.http.HttpStatusCode 6 | import io.ktor.util.pipeline.PipelineContext 7 | import kotlin.reflect.KType 8 | 9 | interface ResponseSerializer: ContentTypeProvider { 10 | /** 11 | * used to determine which registered response serializer is used, based on the accept header 12 | */ 13 | fun getSerializableContentTypes(type: KType): List 14 | suspend fun respond(response: T, request: PipelineContext, contentType: ContentType) 15 | suspend fun respond(statusCode: HttpStatusCode, response: T, request: PipelineContext, contentType: ContentType) 16 | } 17 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/SelectedExceptionSerializer.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type 2 | 3 | data class SelectedExceptionSerializer(override val module: ResponseSerializer): SelectedModule -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/SelectedModule.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type 2 | 3 | import com.papsign.ktor.openapigen.modules.OpenAPIModule 4 | 5 | interface SelectedModule: OpenAPIModule { 6 | val module: OpenAPIModule 7 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/SelectedParser.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type 2 | 3 | data class SelectedParser(override val module: BodyParser): SelectedModule -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/SelectedSerializer.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type 2 | 3 | data class SelectedSerializer(override val module: ResponseSerializer): SelectedModule -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/binary/BinaryRequest.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type.binary 2 | 3 | import com.papsign.ktor.openapigen.annotations.encodings.APIRequestFormat 4 | 5 | @Target(AnnotationTarget.CLASS) 6 | @Retention(AnnotationRetention.RUNTIME) 7 | @APIRequestFormat 8 | annotation class BinaryRequest(val contentTypes: Array) 9 | 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/binary/BinaryResponse.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type.binary 2 | 3 | import com.papsign.ktor.openapigen.annotations.encodings.APIResponseFormat 4 | 5 | @Target(AnnotationTarget.CLASS) 6 | @Retention(AnnotationRetention.RUNTIME) 7 | @APIResponseFormat 8 | annotation class BinaryResponse(val contentTypes: Array) -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/ktor/KtorRequest.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type.ktor 2 | 3 | import com.papsign.ktor.openapigen.annotations.encodings.APIRequestFormat 4 | 5 | 6 | @Target(AnnotationTarget.CLASS) 7 | @Retention(AnnotationRetention.RUNTIME) 8 | @APIRequestFormat 9 | annotation class KtorRequest 10 | 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/ktor/KtorResponse.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type.ktor 2 | 3 | import com.papsign.ktor.openapigen.annotations.encodings.APIResponseFormat 4 | 5 | @Target(AnnotationTarget.CLASS) 6 | @Retention(AnnotationRetention.RUNTIME) 7 | @APIResponseFormat 8 | annotation class KtorResponse -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/multipart/ContentInputStream.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type.multipart 2 | 3 | import io.ktor.http.ContentType 4 | import java.io.BufferedInputStream 5 | import java.io.InputStream 6 | 7 | open class ContentInputStream(val contentType: ContentType?, inputStream: InputStream): BufferedInputStream(inputStream) -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/multipart/FormDataRequest.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type.multipart 2 | 3 | import com.papsign.ktor.openapigen.annotations.encodings.APIRequestFormat 4 | 5 | @Target(AnnotationTarget.CLASS) 6 | @Retention(AnnotationRetention.RUNTIME) 7 | @APIRequestFormat 8 | annotation class FormDataRequest -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/multipart/NamedFileInputStream.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type.multipart 2 | 3 | import io.ktor.http.ContentType 4 | import java.io.InputStream 5 | 6 | class NamedFileInputStream(val name: String?, contentType: ContentType?, inputStream: InputStream) : ContentInputStream(contentType, inputStream) -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/content/type/multipart/PartEncoding.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.content.type.multipart 2 | 3 | @Target(AnnotationTarget.PROPERTY) 4 | @Retention(AnnotationRetention.RUNTIME) 5 | annotation class PartEncoding(val contentType: String) -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/exception/APIException.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.exception 2 | 3 | import com.papsign.ktor.openapigen.util.getKType 4 | import com.papsign.ktor.openapigen.util.unitKType 5 | import io.ktor.http.HttpStatusCode 6 | import kotlin.reflect.KClass 7 | import kotlin.reflect.KType 8 | 9 | /** 10 | * Interface to describe an API Exception. 11 | */ 12 | interface APIException { 13 | val status: HttpStatusCode 14 | val exceptionClass: KClass 15 | val contentType: KType 16 | get() = unitKType 17 | val contentFn: ((TException) -> TMessage)? 18 | get() = null 19 | val example: TMessage? 20 | get() = null 21 | 22 | companion object { 23 | inline fun apiException(status: HttpStatusCode): APIException = 24 | apiException(status, null as Unit?, null) 25 | 26 | /** 27 | * Convenience function to create an APIException. 28 | * If @param example is null pass in Unit as TMessage (e.g. apiException(status) 29 | */ 30 | inline fun apiException( 31 | status: HttpStatusCode, 32 | example: TMessage? = null, 33 | noinline contentFn: ((TException) -> TMessage)? = null 34 | ): APIException = 35 | APIExceptionImpl( 36 | status = status, 37 | exceptionClass = TException::class, 38 | contentType = getKType(), 39 | contentFn = contentFn, 40 | example = example 41 | ) 42 | } 43 | 44 | } 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/exception/APIExceptionBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.exception 2 | 3 | import io.ktor.http.* 4 | 5 | /** 6 | * Builder for APIExceptions. 7 | * This allows to use a syntax like: 8 | * 9 | * apiException { 10 | * status = HttpStatusCode.NotFound.description("Customer not found") 11 | * example = ErrorMessage("Customer with uuid 26d1229eaba8 not found") 12 | * contentFn = { ErrorMessage(it.message ?: "Customer not found") } 13 | * } 14 | * 15 | * You can also use the 16 | */ 17 | class APIExceptionBuilder { 18 | var status: HttpStatusCode = HttpStatusCode.BadRequest 19 | var example: TMessage? = null 20 | var contentFn: ((TException) -> TMessage)? = null 21 | 22 | companion object { 23 | inline fun apiException( 24 | block: APIExceptionBuilder.() -> Unit 25 | ) : APIException = 26 | APIExceptionBuilder().run { 27 | block(this) 28 | APIException.apiException(status, example, contentFn) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/exception/APIExceptionImpl.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.exception 2 | 3 | import com.papsign.ktor.openapigen.util.unitKType 4 | import io.ktor.http.* 5 | import kotlin.reflect.KClass 6 | import kotlin.reflect.KType 7 | 8 | /** 9 | * Implementation of APIException. 10 | * 11 | * @param status The HTTP status code returned to the client 12 | * @param exceptionClass The Kclass of the exception, if an exception of exceptionClass is thrown, it will be 13 | * handled by this APIException. 14 | * @param example An example of the HTTP response 15 | * @param contentType The media type of the HTTP response 16 | * @param contentFn The function that creates the HTTP response 17 | */ 18 | class APIExceptionImpl( 19 | override val status: HttpStatusCode, 20 | override val exceptionClass: KClass, 21 | override val contentType: KType = unitKType, 22 | override val contentFn: ((TException) -> TMessage)? = null, 23 | override val example: TMessage? = null 24 | ) : APIException 25 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/exception/OpenAPIBadContentException.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.exception 2 | 3 | class OpenAPIBadContentException(msg: String): Exception(msg) 4 | 5 | inline fun assertContent(bool: Boolean, crossinline err: () -> String) { 6 | if (!bool) { 7 | throw OpenAPIBadContentException(err()) 8 | } 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/exception/OpenAPINoParserException.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.exception 2 | 3 | import io.ktor.http.ContentType 4 | 5 | class OpenAPINoParserException(val contentType: ContentType): Exception("No parser found for content type $contentType") 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/exception/OpenAPINoSerializerException.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.exception 2 | 3 | import io.ktor.http.ContentType 4 | 5 | class OpenAPINoSerializerException(val contentTypes: ContentType): Exception("No serializer found for content types $contentTypes") 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/exception/OpenAPIParseException.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.exception 2 | 3 | import kotlin.reflect.KClass 4 | 5 | class OpenAPIParseException(val request: KClass<*>, val actual: Set>): Exception("Could not parse $request as $actual") 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/exception/OpenAPIRequiredFieldException.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.exception 2 | 3 | class OpenAPIRequiredFieldException (override val message: String) : Exception(message) -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/DataModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model 2 | 3 | import com.papsign.ktor.openapigen.util.cleanEmptyValues 4 | import com.papsign.ktor.openapigen.util.convertToValue 5 | import kotlin.reflect.KProperty1 6 | import kotlin.reflect.full.memberProperties 7 | 8 | interface DataModel { 9 | 10 | fun serialize(): Map = 11 | this::class.memberProperties 12 | .associateBy { it.name } 13 | .mapValues { (_, prop) -> convertToValue((prop as KProperty1).get(this)) } 14 | .cleanEmptyValues() 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/Described.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model 2 | 3 | interface Described { 4 | val description: String 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/base/ComponentsModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.base 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | import com.papsign.ktor.openapigen.model.operation.* 5 | import com.papsign.ktor.openapigen.model.info.ExampleModel 6 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 7 | import com.papsign.ktor.openapigen.model.security.SecuritySchemeModel 8 | 9 | data class ComponentsModel( 10 | var schemas: MutableMap> = sortedMapOf(), 11 | var responses: MutableMap = sortedMapOf(), 12 | var parameters: MutableMap> = sortedMapOf(), 13 | var examples: MutableMap> = sortedMapOf(), 14 | var requestBodies: MutableMap = sortedMapOf(), 15 | var headers: MutableMap> = sortedMapOf(), 16 | var securitySchemes: MutableMap> = sortedMapOf() 17 | //links 18 | //callbacks 19 | ): DataModel 20 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/base/OpenAPIModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.base 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | import com.papsign.ktor.openapigen.model.info.ExternalDocumentationModel 5 | import com.papsign.ktor.openapigen.model.info.InfoModel 6 | import com.papsign.ktor.openapigen.model.info.TagModel 7 | import com.papsign.ktor.openapigen.model.security.SecurityModel 8 | import com.papsign.ktor.openapigen.model.server.ServerModel 9 | 10 | data class OpenAPIModel( 11 | var info: InfoModel = InfoModel(), 12 | var openapi: String = "3.0.0", 13 | var servers: MutableList = mutableListOf(), 14 | var paths: MutableMap = mutableMapOf(), 15 | var components: ComponentsModel = ComponentsModel(), 16 | var security: LinkedHashSet = LinkedHashSet(), 17 | var tags: LinkedHashSet = LinkedHashSet(), 18 | var externalDocs: ExternalDocumentationModel? = null 19 | ): DataModel 20 | 21 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/base/PathItemModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.base 2 | 3 | import com.papsign.ktor.openapigen.model.operation.OperationModel 4 | import com.papsign.ktor.openapigen.model.operation.ParameterModel 5 | import com.papsign.ktor.openapigen.model.server.ServerModel 6 | 7 | @Suppress("UNCHECKED_CAST") 8 | class PathItemModel : MutableMap by HashMap() { 9 | var summary: String? by (this as MutableMap) 10 | var description: String? by (this as MutableMap) 11 | var get: OperationModel? by (this as MutableMap) 12 | var put: OperationModel? by (this as MutableMap) 13 | var post: OperationModel? by (this as MutableMap) 14 | var delete: OperationModel? by (this as MutableMap) 15 | var options: OperationModel? by (this as MutableMap) 16 | var head: OperationModel? by (this as MutableMap) 17 | var patch: OperationModel? by (this as MutableMap) 18 | var trace: OperationModel? by (this as MutableMap) 19 | val servers: MutableList 20 | get() = (this as MutableMap>).getOrPut("servers") { mutableListOf() } 21 | var parameters: List> 22 | get() = (this as MutableMap>>).getOrPut("parameters") { listOf() } 23 | set(value) { 24 | (this as MutableMap>>)["parameters"] = value 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/base/RefModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.base 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | 5 | interface RefModel: DataModel { 6 | val `$ref`: String 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/info/ContactModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.info 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | 5 | data class ContactModel( 6 | var name: String? = null, 7 | var url: String? = null, 8 | var email: String? = null 9 | ): DataModel 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/info/ExampleModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.info 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | 5 | data class ExampleModel( 6 | var value: T, 7 | var summary: String? = null, 8 | var description: String? = null 9 | ): DataModel 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/info/ExternalDocumentationModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.info 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | 5 | data class ExternalDocumentationModel( 6 | var url: String, 7 | var description: String? = null 8 | ): DataModel 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/info/InfoModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.info 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | 5 | data class InfoModel( 6 | var title: String = "Default", 7 | var version: String = "0.0.1", 8 | var description: String? = null, 9 | var termsOfService: String? = null, 10 | var contact: ContactModel? = null, 11 | var license: LicenseModel? = null 12 | ): DataModel 13 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/info/LicenseModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.info 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | 5 | data class LicenseModel( 6 | var name: String = "All Rights Reserved", 7 | var url: String? = null 8 | ): DataModel 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/info/TagModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.info 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | import com.papsign.ktor.openapigen.model.info.ExternalDocumentationModel 5 | 6 | data class TagModel( 7 | val name: String, 8 | val description: String? = null, 9 | val externalDocs: ExternalDocumentationModel? = null 10 | ): DataModel 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/operation/HeaderModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.operation 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | import com.papsign.ktor.openapigen.model.info.ExampleModel 5 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 6 | 7 | data class HeaderModel( 8 | var required: Boolean, 9 | var description: String? = null, 10 | var deprecated: Boolean? = null, 11 | var allowEmptyValue: Boolean? = null, 12 | var schema: SchemaModel? = null, 13 | var example: T? = null, 14 | var examples: MutableMap>? = null 15 | // incomplete 16 | ): DataModel 17 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/operation/MediaTypeEncodingModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.operation 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | 5 | data class MediaTypeEncodingModel(val contentType: String): DataModel 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/operation/MediaTypeModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.operation 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | import com.papsign.ktor.openapigen.model.info.ExampleModel 5 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 6 | 7 | data class MediaTypeModel( 8 | val schema: SchemaModel? = null, 9 | val example: T? = null, 10 | val examples: MutableMap>? = null, 11 | val encoding: Map? = null 12 | ): DataModel 13 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/operation/OperationModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.operation 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | import com.papsign.ktor.openapigen.model.info.ExternalDocumentationModel 5 | import com.papsign.ktor.openapigen.model.security.SecurityModel 6 | import com.papsign.ktor.openapigen.model.server.ServerModel 7 | 8 | data class OperationModel( 9 | var tags: List? = null, 10 | var summary: String? = null, 11 | var description: String? = null, 12 | var externalDocs: ExternalDocumentationModel? = null, 13 | var operationId: String? = null, 14 | var parameters: List>? = null, 15 | var requestBody: RequestBodyModel? = null, 16 | var responses: MutableMap = mutableMapOf(), 17 | // var callbacks ... 18 | var deprecated: Boolean? = null, 19 | var security: List? = null, 20 | var servers: List? = null 21 | ): DataModel 22 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/operation/ParameterLocation.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.operation 2 | 3 | enum class ParameterLocation { 4 | query, header, path, cookie 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/operation/ParameterModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.operation 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | import com.papsign.ktor.openapigen.model.info.ExampleModel 5 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 6 | import com.papsign.ktor.openapigen.parameters.ParameterStyle 7 | 8 | data class ParameterModel( 9 | var name: String, 10 | var `in`: ParameterLocation, 11 | var required: Boolean = true, 12 | var description: String? = null, 13 | var deprecated: Boolean? = null, 14 | var allowEmptyValue: Boolean? = null, 15 | var schema: SchemaModel? = null, 16 | var example: T? = null, 17 | var examples: MutableMap>? = null, 18 | var style: ParameterStyle<*>? = null, 19 | var explode: Boolean = false 20 | // incomplete 21 | ): DataModel 22 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/operation/RequestBodyModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.operation 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | 5 | data class RequestBodyModel( 6 | var content: MutableMap>, 7 | var description: String? = null, 8 | var required: Boolean? = null 9 | ): DataModel 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/operation/StatusResponseModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.operation 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | import com.papsign.ktor.openapigen.model.base.RefModel 5 | 6 | data class StatusResponseModel( 7 | var description: String, 8 | var headers: MutableMap>> = mutableMapOf(), 9 | var content: MutableMap> = mutableMapOf() 10 | //links 11 | ): DataModel 12 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/schema/DataFormat.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.schema 2 | 3 | enum class DataFormat { 4 | int32, int64, float, double, string, byte, binary, date, `date-time`, password, email, uuid 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/schema/DataType.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.schema 2 | 3 | enum class DataType { 4 | integer, number, string, boolean, `object`, array 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/schema/Discriminator.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.schema 2 | 3 | data class Discriminator(val propertyName: String) -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/security/APIKeyLocation.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.security 2 | 3 | enum class APIKeyLocation { 4 | query, header, cookie 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/security/HttpSecurityScheme.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.security 2 | 3 | enum class HttpSecurityScheme { 4 | basic, bearer, digest 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/security/SecurityModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.security 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | import com.papsign.ktor.openapigen.model.Described 5 | import com.papsign.ktor.openapigen.util.SerializationSettings 6 | import com.papsign.ktor.openapigen.util.cleanEmptyValues 7 | import com.papsign.ktor.openapigen.util.convertToValue 8 | 9 | class SecurityModel : MutableMap> by mutableMapOf(), DataModel { 10 | operator fun set(scheme: SecuritySchemeModel, requirements: List) where T: Enum, T: Described { 11 | this[scheme.name] = requirements 12 | } 13 | 14 | fun set(scheme: SecuritySchemeModel) where T: Enum, T: Described { 15 | this[scheme] = listOf() 16 | } 17 | 18 | override fun serialize(): Map { 19 | val serializationSettings = SerializationSettings(skipEmptyList = true) 20 | return this.mapValues { (_, prop) -> 21 | convertToValue(prop,serializationSettings) 22 | }.cleanEmptyValues(serializationSettings) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/security/SecuritySchemeModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.security 2 | 3 | import com.papsign.ktor.openapigen.util.cleanEmptyValues 4 | import com.papsign.ktor.openapigen.util.convertToValue 5 | import com.papsign.ktor.openapigen.model.DataModel 6 | import com.papsign.ktor.openapigen.model.Described 7 | import kotlin.reflect.KProperty1 8 | import kotlin.reflect.full.memberProperties 9 | 10 | data class SecuritySchemeModel constructor( 11 | val type: SecuritySchemeType, 12 | val name: String, 13 | val `in`: APIKeyLocation? = null, 14 | val scheme: HttpSecurityScheme? = null, 15 | val bearerFormat: String? = null, 16 | val flows: FlowsModel? = null, 17 | val openIdConnectUrl: String? = null 18 | ): DataModel where TScope : Enum, TScope : Described{ 19 | 20 | override fun serialize(): Map { 21 | return this::class.memberProperties.associateBy { it.name }.mapValues, Any?>, Any?> { (_, prop) -> 22 | convertToValue((prop as KProperty1).get(this)) 23 | }.filter { it.key != "name" }.cleanEmptyValues() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/security/SecuritySchemeType.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.security 2 | 3 | enum class SecuritySchemeType { 4 | apiKey, http, oauth2, openIdConnect 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/server/ServerModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.server 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | import com.papsign.ktor.openapigen.model.server.ServerVariableModel 5 | 6 | data class ServerModel( 7 | var url: String, 8 | var description: String? = null, 9 | var variables: MutableMap = mutableMapOf() 10 | ): DataModel 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/model/server/ServerVariableModel.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.model.server 2 | 3 | import com.papsign.ktor.openapigen.model.DataModel 4 | 5 | data class ServerVariableModel( 6 | var default: String, 7 | var enum: MutableList = mutableListOf(), 8 | var description: String? = null 9 | ): DataModel 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/CachingModuleProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules 2 | 3 | import java.util.* 4 | import kotlin.reflect.KType 5 | import kotlin.reflect.full.isSubtypeOf 6 | 7 | class CachingModuleProvider(previous: Iterable> = listOf()) : ModuleProvider { 8 | 9 | private val modules: MutableList> = Collections.synchronizedList( synchronized(previous) { previous.toMutableList() } ) 10 | 11 | override fun ofType(type: KType): Collection { 12 | val set = LinkedHashSet() 13 | synchronized(modules) { 14 | modules.filter { 15 | it.first.isSubtypeOf(type) 16 | } 17 | }.forEach { 18 | set.remove(it.second) 19 | set.add(it.second) 20 | } 21 | return set 22 | } 23 | 24 | override fun registerModule(module: OpenAPIModule, type: KType) { 25 | if (module is DependentModule) { 26 | module.handlers.forEach { (depType, depModule) -> 27 | if (synchronized(modules) { modules.find { it.second == depModule } } == null) { 28 | registerModule(depModule, depType) 29 | } 30 | } 31 | } 32 | modules.add(type to module) 33 | } 34 | 35 | override fun unRegisterModule(module: OpenAPIModule) { 36 | synchronized(modules) { modules.removeIf { it.second == module } } 37 | } 38 | 39 | override fun child() = CachingModuleProvider(modules) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/DefaultOpenAPIModule.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules 2 | 3 | interface DefaultOpenAPIModule 4 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/DependentModule.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules 2 | 3 | import com.papsign.ktor.openapigen.util.getKType 4 | import kotlin.reflect.KType 5 | 6 | interface DependentModule { 7 | val handlers: Collection> 8 | 9 | companion object { 10 | inline fun handler(handler: T): Pair { 11 | return getKType() to handler 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/ModuleProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules 2 | 3 | import com.papsign.ktor.openapigen.util.getKType 4 | import kotlin.reflect.KType 5 | 6 | interface ModuleProvider> { 7 | fun ofType(type: KType): Collection 8 | fun registerModule(module: OpenAPIModule, type: KType) 9 | fun unRegisterModule(module: OpenAPIModule) 10 | fun child(): THIS 11 | } 12 | 13 | inline fun ModuleProvider<*>.ofType(): Collection = 14 | ofType(getKType()) as Collection 15 | 16 | inline fun ModuleProvider<*>.registerModule(module: T) = 17 | registerModule(module, getKType()) 18 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/OpenAPIModule.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules 2 | 3 | interface OpenAPIModule 4 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/RouteOpenAPIModule.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules 2 | 3 | interface RouteOpenAPIModule: OpenAPIModule -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/handlers/AuthHandler.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules.handlers 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.model.operation.OperationModel 5 | import com.papsign.ktor.openapigen.model.security.SecurityModel 6 | import com.papsign.ktor.openapigen.modules.ModuleProvider 7 | import com.papsign.ktor.openapigen.modules.ofType 8 | import com.papsign.ktor.openapigen.modules.openapi.OperationModule 9 | import com.papsign.ktor.openapigen.modules.providers.AuthProvider 10 | 11 | object AuthHandler: OperationModule { 12 | 13 | override fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>, operation: OperationModel) { 14 | val authHandlers = provider.ofType>() 15 | val security = authHandlers.flatMap { it.security }.distinct() 16 | operation.security = security.map { SecurityModel().also { sec -> 17 | it.forEach { sec[it.scheme.name] = it.requirements } 18 | } } 19 | apiGen.api.components.securitySchemes.putAll(security.flatMap { it.map { it.scheme } }.associateBy { it.name }) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/handlers/RequestHandlerModule.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules.handlers 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.annotations.Request 5 | import com.papsign.ktor.openapigen.util.classLogger 6 | import com.papsign.ktor.openapigen.content.type.BodyParser 7 | import com.papsign.ktor.openapigen.content.type.ContentTypeProvider 8 | import com.papsign.ktor.openapigen.content.type.SelectedParser 9 | import com.papsign.ktor.openapigen.model.operation.OperationModel 10 | import com.papsign.ktor.openapigen.model.operation.RequestBodyModel 11 | import com.papsign.ktor.openapigen.modules.ModuleProvider 12 | import com.papsign.ktor.openapigen.modules.ofType 13 | import com.papsign.ktor.openapigen.modules.openapi.OperationModule 14 | import com.papsign.ktor.openapigen.modules.providers.ParameterProvider 15 | import com.papsign.ktor.openapigen.modules.registerModule 16 | import kotlin.reflect.KType 17 | import kotlin.reflect.full.findAnnotation 18 | import kotlin.reflect.jvm.jvmErasure 19 | 20 | class RequestHandlerModule( 21 | val requestType: KType, 22 | val requestExample: T? = null 23 | ) : OperationModule { 24 | 25 | private val log = classLogger() 26 | 27 | override fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>, operation: OperationModel) { 28 | val map = provider.ofType().mapNotNull { 29 | val mediaType = it.getMediaType(requestType, apiGen, provider, requestExample, ContentTypeProvider.Usage.PARSE) 30 | ?: return@mapNotNull null 31 | provider.registerModule(SelectedParser(it)) 32 | mediaType.map { Pair(it.key.toString(), it.value) } 33 | }.flatten().associate { it } 34 | 35 | val requestMeta = requestType.jvmErasure.findAnnotation() 36 | 37 | val parameters = provider.ofType().flatMap { it.getParameters(apiGen, provider) } 38 | operation.parameters = operation.parameters?.let { (it + parameters).distinct() } ?: parameters 39 | operation.requestBody = operation.requestBody?.apply { 40 | map.forEach { (key, value) -> 41 | content.putIfAbsent(key, value)?.let { if (value != it) log.warn("ContentType of $requestType request $key already registered, ignoring $value") } 42 | } 43 | if (description != null) { 44 | if (requestMeta?.description != null) log.warn("ContentType description of $requestType request already registered, ignoring") 45 | } else { 46 | description = requestMeta?.description 47 | } 48 | } ?: if (map.isNotEmpty()) RequestBodyModel(map.toMutableMap(), description = requestMeta?.description) else null 49 | } 50 | 51 | companion object { 52 | fun create(tType: KType, requestExample: T? = null) = RequestHandlerModule(tType, requestExample) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/handlers/RouteHandler.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules.handlers 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.content.type.SelectedModule 5 | import com.papsign.ktor.openapigen.model.base.PathItemModel 6 | import com.papsign.ktor.openapigen.model.operation.OperationModel 7 | import com.papsign.ktor.openapigen.modules.ModuleProvider 8 | import com.papsign.ktor.openapigen.modules.ofType 9 | import com.papsign.ktor.openapigen.modules.openapi.HandlerModule 10 | import com.papsign.ktor.openapigen.modules.openapi.OperationModule 11 | import com.papsign.ktor.openapigen.modules.providers.MethodProvider 12 | import com.papsign.ktor.openapigen.modules.providers.PathProvider 13 | import org.slf4j.Logger 14 | import org.slf4j.LoggerFactory 15 | 16 | object RouteHandler: HandlerModule { 17 | 18 | private val log: Logger = LoggerFactory.getLogger(this::class.java) 19 | 20 | override fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>) { 21 | val methods = provider.ofType() 22 | if (methods.size > 1) error("API cannot have two methods simultaneously: ${methods.map { it.method.value }}") 23 | val paths = provider.ofType() 24 | val path = "/${paths.flatMap { it.path.split('/').filter(String::isNotEmpty) }.joinToString("/")}" 25 | val operationModules = provider.ofType() 26 | apiGen.api.paths.getOrPut(path) { PathItemModel() }.also {pathItem -> 27 | methods.forEach { 28 | val name = it.method.value.toLowerCase() 29 | //if (pathItem.containsKey(name)) error("$path::$name already defined") 30 | val op = pathItem.getOrPut(name) { OperationModel() } as OperationModel 31 | operationModules.forEach { 32 | it.configure(apiGen, provider, op) 33 | } 34 | } 35 | } 36 | log.trace("Registered $path::${methods.map { it.method.value }} with OpenAPI description with ${provider.ofType().map { it.module::class.simpleName }}") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/handlers/TagHandlerModule.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules.handlers 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.model.operation.OperationModel 5 | import com.papsign.ktor.openapigen.modules.ModuleProvider 6 | import com.papsign.ktor.openapigen.modules.ofType 7 | import com.papsign.ktor.openapigen.modules.openapi.OperationModule 8 | import com.papsign.ktor.openapigen.modules.providers.TagProviderModule 9 | 10 | object TagHandlerModule: OperationModule { 11 | override fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>, operation: OperationModel) { 12 | val tags = provider.ofType().flatMap { it.tags.map(apiGen::getOrRegisterTag) } 13 | val current = operation.tags 14 | if (current != null) { 15 | operation.tags = (tags + current).distinct() 16 | } else { 17 | operation.tags = tags 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/openapi/HandlerModule.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules.openapi 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.modules.ModuleProvider 5 | import com.papsign.ktor.openapigen.modules.OpenAPIModule 6 | 7 | interface HandlerModule: OpenAPIModule { 8 | fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/openapi/OperationModule.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules.openapi 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.model.operation.OperationModel 5 | import com.papsign.ktor.openapigen.modules.ModuleProvider 6 | import com.papsign.ktor.openapigen.modules.OpenAPIModule 7 | 8 | interface OperationModule: OpenAPIModule { 9 | fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>, operation: OperationModel) 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/providers/AuthProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules.providers 2 | 3 | import com.papsign.ktor.openapigen.model.Described 4 | import com.papsign.ktor.openapigen.model.security.SecuritySchemeModel 5 | import com.papsign.ktor.openapigen.modules.DependentModule 6 | import com.papsign.ktor.openapigen.modules.DependentModule.Companion.handler 7 | import com.papsign.ktor.openapigen.modules.OpenAPIModule 8 | import com.papsign.ktor.openapigen.modules.handlers.AuthHandler 9 | import com.papsign.ktor.openapigen.route.path.auth.OpenAPIAuthenticatedRoute 10 | import com.papsign.ktor.openapigen.route.path.normal.NormalOpenAPIRoute 11 | import io.ktor.application.ApplicationCall 12 | import io.ktor.util.pipeline.PipelineContext 13 | import kotlin.reflect.KType 14 | 15 | interface AuthProvider: OpenAPIModule, DependentModule { 16 | suspend fun getAuth(pipeline: PipelineContext): TAuth 17 | fun apply(route: NormalOpenAPIRoute): OpenAPIAuthenticatedRoute 18 | val security: Iterable>> 19 | override val handlers: Collection> 20 | get() = listOf(handler(AuthHandler)) 21 | 22 | data class Security(val scheme: SecuritySchemeModel, val requirements: List) where TScope: Enum, TScope: Described 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/providers/MethodProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules.providers 2 | 3 | import com.papsign.ktor.openapigen.util.getKType 4 | import com.papsign.ktor.openapigen.modules.DependentModule 5 | import com.papsign.ktor.openapigen.modules.DependentModule.Companion.handler 6 | import com.papsign.ktor.openapigen.modules.OpenAPIModule 7 | import com.papsign.ktor.openapigen.modules.handlers.RouteHandler 8 | import io.ktor.http.HttpMethod 9 | import kotlin.reflect.KType 10 | 11 | interface MethodProvider : OpenAPIModule, DependentModule { 12 | val method: HttpMethod 13 | override val handlers: Collection> 14 | get() = listOf(handler(RouteHandler)) 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/providers/ParameterProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules.providers 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.model.operation.ParameterModel 5 | import com.papsign.ktor.openapigen.modules.ModuleProvider 6 | import com.papsign.ktor.openapigen.modules.OpenAPIModule 7 | 8 | interface ParameterProvider: OpenAPIModule { 9 | fun getParameters(apiGen: OpenAPIGen, provider: ModuleProvider<*>): List> 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/providers/PathProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules.providers 2 | 3 | import com.papsign.ktor.openapigen.modules.OpenAPIModule 4 | 5 | interface PathProvider : OpenAPIModule { 6 | val path: String 7 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/providers/StatusProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules.providers 2 | 3 | import com.papsign.ktor.openapigen.modules.OpenAPIModule 4 | import io.ktor.http.HttpStatusCode 5 | import kotlin.reflect.KType 6 | 7 | interface StatusProvider : OpenAPIModule { 8 | fun getStatusForType(responseType: KType): HttpStatusCode 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/providers/TagProviderModule.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules.providers 2 | 3 | import com.papsign.ktor.openapigen.APITag 4 | import com.papsign.ktor.openapigen.modules.DependentModule 5 | import com.papsign.ktor.openapigen.modules.DependentModule.Companion.handler 6 | import com.papsign.ktor.openapigen.modules.OpenAPIModule 7 | import com.papsign.ktor.openapigen.modules.RouteOpenAPIModule 8 | import com.papsign.ktor.openapigen.modules.handlers.TagHandlerModule 9 | import kotlin.reflect.KType 10 | 11 | interface TagProviderModule: RouteOpenAPIModule, DependentModule { 12 | val tags: Collection 13 | override val handlers: Collection> 14 | get() = listOf(handler(TagHandlerModule)) 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/providers/ThrowInfoProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules.providers 2 | 3 | import com.papsign.ktor.openapigen.exception.APIException 4 | import com.papsign.ktor.openapigen.modules.DependentModule 5 | import com.papsign.ktor.openapigen.modules.DependentModule.Companion.handler 6 | import com.papsign.ktor.openapigen.modules.OpenAPIModule 7 | import com.papsign.ktor.openapigen.modules.handlers.ThrowOperationHandler 8 | import kotlin.reflect.KType 9 | 10 | interface ThrowInfoProvider: OpenAPIModule, DependentModule { 11 | val exceptions: List> 12 | override val handlers: Collection> 13 | get() = listOf(handler(ThrowOperationHandler)) 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/modules/util/CombinedAuthProviders.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.modules.util 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/HeaderParamStyle.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderFactory 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.header.simple.SimpleBuilderFactory 6 | 7 | 8 | enum class HeaderParamStyle(override val factory: BuilderFactory, HeaderParamStyle>): ParameterStyle { 9 | simple(SimpleBuilderFactory) 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/ParameterStyle.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderFactory 5 | 6 | interface ParameterStyle where S: ParameterStyle, S: Enum { 7 | val name: String 8 | val factory: BuilderFactory, S> 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/PathParamStyle.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderFactory 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.path.matrix.MatrixBuilderFactory 6 | import com.papsign.ktor.openapigen.parameters.parsers.builders.path.label.LabelBuilderFactory 7 | import com.papsign.ktor.openapigen.parameters.parsers.builders.path.simple.SimpleBuilderFactory 8 | 9 | 10 | enum class PathParamStyle(override val factory: BuilderFactory, PathParamStyle>): ParameterStyle { 11 | simple(SimpleBuilderFactory), label(LabelBuilderFactory), matrix(MatrixBuilderFactory) 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/QueryParamStyle.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderFactory 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject.DeepBuilderFactory 6 | import com.papsign.ktor.openapigen.parameters.parsers.builders.query.delimited.PipeDelimitedBuilderFactory 7 | import com.papsign.ktor.openapigen.parameters.parsers.builders.query.delimited.SpaceDelimitedBuilderFactory 8 | import com.papsign.ktor.openapigen.parameters.parsers.builders.query.form.FormBuilderFactory 9 | 10 | enum class QueryParamStyle(override val factory: BuilderFactory, QueryParamStyle>): ParameterStyle { 11 | form(FormBuilderFactory), spaceDelimited(SpaceDelimitedBuilderFactory), pipeDelimited(PipeDelimitedBuilderFactory), deepObject(DeepBuilderFactory) 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/handlers/ParameterHandler.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.handlers 2 | 3 | import com.papsign.ktor.openapigen.modules.providers.ParameterProvider 4 | import io.ktor.http.Headers 5 | import io.ktor.http.Parameters 6 | 7 | interface ParameterHandler: ParameterProvider { 8 | fun parse(parameters: Parameters, headers: Headers): T 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/handlers/UnitParameterHandler.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.handlers 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.model.operation.ParameterModel 5 | import com.papsign.ktor.openapigen.modules.ModuleProvider 6 | import io.ktor.http.Headers 7 | import io.ktor.http.Parameters 8 | 9 | object UnitParameterHandler : 10 | ParameterHandler { 11 | override fun parse(parameters: Parameters, headers: Headers) = Unit 12 | override fun getParameters(apiGen: OpenAPIGen, provider: ModuleProvider<*>): List> { 13 | return listOf() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/Builder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders 2 | 3 | import com.papsign.ktor.openapigen.parameters.ParameterStyle 4 | 5 | interface Builder where S: ParameterStyle, S: Enum { 6 | val style: S 7 | val explode: Boolean 8 | fun build(key: String, parameters: Map>): Any? 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/BuilderFactory.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders 2 | 3 | import com.papsign.ktor.openapigen.parameters.ParameterStyle 4 | import kotlin.reflect.KType 5 | 6 | interface BuilderFactory, S> where S: ParameterStyle, S: Enum { 7 | fun buildBuilder(type: KType, explode: Boolean): T? 8 | fun buildBuilderForced(type: KType, explode: Boolean): T = buildBuilder(type, explode) ?: error("No ${this.javaClass.declaringClass?.simpleName ?: this.javaClass.simpleName} Builder exists for type $type") 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/BuilderSelector.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders 2 | 3 | import kotlin.reflect.KType 4 | 5 | interface BuilderSelector> { 6 | fun canHandle(type: KType, explode: Boolean): Boolean 7 | fun create(type: KType, explode: Boolean): T 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/BuilderSelectorFactory.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders 2 | 3 | import com.papsign.ktor.openapigen.parameters.ParameterStyle 4 | import kotlin.reflect.KType 5 | 6 | open class BuilderSelectorFactory, S>(vararg val selectors: BuilderSelector): 7 | BuilderFactory where S: ParameterStyle, S: Enum { 8 | override fun buildBuilder(type: KType, explode: Boolean): T? { 9 | return selectors.find { it.canHandle(type, explode) }?.create(type, explode) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/header/simple/SimpleBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.header.simple 2 | 3 | import com.papsign.ktor.openapigen.parameters.HeaderParamStyle 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector 6 | import com.papsign.ktor.openapigen.parameters.parsers.converters.Converter 7 | import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterFactory 8 | import kotlin.reflect.KType 9 | 10 | class SimpleBuilder(val type: KType, override val explode: Boolean): Builder { 11 | override val style: HeaderParamStyle = HeaderParamStyle.simple 12 | private val converter: Converter = ConverterFactory.buildConverterForced(type) 13 | 14 | override fun build(key: String, parameters: Map>): Any? { 15 | val value = parameters[key]?.let { it[0] } ?: return null 16 | val adjusted = if (explode) value.replace('=', ',') else value 17 | return converter.convert(adjusted) 18 | } 19 | 20 | companion object: BuilderSelector { 21 | override fun canHandle(type: KType, explode: Boolean): Boolean { 22 | return true 23 | } 24 | 25 | override fun create(type: KType, explode: Boolean): SimpleBuilder { 26 | return SimpleBuilder(type, explode) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/header/simple/SimpleBuilderFactory.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.header.simple 2 | 3 | import com.papsign.ktor.openapigen.parameters.HeaderParamStyle 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelectorFactory 6 | 7 | object SimpleBuilderFactory: BuilderSelectorFactory, HeaderParamStyle>( 8 | SimpleBuilder 9 | ) 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/path/label/LabelBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.path.label 2 | 3 | import com.papsign.ktor.openapigen.parameters.PathParamStyle 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector 6 | import com.papsign.ktor.openapigen.parameters.parsers.converters.Converter 7 | import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterFactory 8 | import kotlin.reflect.KType 9 | 10 | class LabelBuilder(type: KType, override val explode: Boolean): Builder { 11 | override val style: PathParamStyle = PathParamStyle.label 12 | 13 | private val converter: Converter = ConverterFactory.buildConverterForced(type) 14 | 15 | override fun build(key: String, parameters: Map>): Any? { 16 | val value = parameters[key]?.let { it[0] }?.removePrefix(".") ?: return null 17 | val adjusted = if (explode) value.replace('=', ',').replace('.', ',') else value 18 | return converter.convert(adjusted) 19 | } 20 | 21 | companion object: BuilderSelector { 22 | override fun canHandle(type: KType, explode: Boolean): Boolean { 23 | return true 24 | } 25 | 26 | override fun create(type: KType, explode: Boolean): LabelBuilder { 27 | return LabelBuilder(type, explode) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/path/label/LabelBuilderFactory.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.path.label 2 | 3 | import com.papsign.ktor.openapigen.parameters.PathParamStyle 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelectorFactory 6 | 7 | object LabelBuilderFactory: BuilderSelectorFactory, PathParamStyle>( 8 | LabelBuilder 9 | ) 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/path/matrix/MatrixBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.path.matrix 2 | 3 | import com.papsign.ktor.openapigen.parameters.PathParamStyle 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector 6 | import com.papsign.ktor.openapigen.parameters.parsers.converters.Converter 7 | import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterFactory 8 | import com.papsign.ktor.openapigen.parameters.parsers.converters.`object`.MappedConverter 9 | import com.papsign.ktor.openapigen.parameters.parsers.converters.collection.ListedConverter 10 | import kotlin.reflect.KType 11 | 12 | class MatrixBuilder(type: KType, override val explode: Boolean): Builder { 13 | override val style: PathParamStyle = PathParamStyle.matrix 14 | 15 | private val converter: Converter = ConverterFactory.buildConverterForced(type) 16 | 17 | override fun build(key: String, parameters: Map>): Any? { 18 | val value = parameters[key]?.let { it[0] } ?: return null 19 | return if (explode) { 20 | val groups = value.removePrefix(";").split(';').map { it.split('=').let { Pair(it[0], it.getOrElse(1){""}) } } 21 | when (converter) { 22 | is MappedConverter -> converter.convert(groups.toMap()) 23 | is ListedConverter -> converter.convert(groups.map { it.second }) 24 | else -> converter.convert(groups.first().second) 25 | } 26 | } else { 27 | converter.convert(value.removePrefix(";$key=")) 28 | } 29 | } 30 | 31 | companion object: BuilderSelector { 32 | override fun canHandle(type: KType, explode: Boolean): Boolean { 33 | return true 34 | } 35 | 36 | override fun create(type: KType, explode: Boolean): MatrixBuilder { 37 | return MatrixBuilder(type, explode) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/path/matrix/MatrixBuilderFactory.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.path.matrix 2 | 3 | import com.papsign.ktor.openapigen.parameters.PathParamStyle 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelectorFactory 6 | 7 | object MatrixBuilderFactory: BuilderSelectorFactory, PathParamStyle>( 8 | MatrixBuilder 9 | ) 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/path/simple/SimpleBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.path.simple 2 | 3 | import com.papsign.ktor.openapigen.parameters.PathParamStyle 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector 6 | import com.papsign.ktor.openapigen.parameters.parsers.converters.Converter 7 | import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterFactory 8 | import kotlin.reflect.KType 9 | 10 | class SimpleBuilder(val type: KType, override val explode: Boolean): Builder { 11 | override val style: PathParamStyle = PathParamStyle.simple 12 | private val converter: Converter = ConverterFactory.buildConverterForced(type) 13 | 14 | override fun build(key: String, parameters: Map>): Any? { 15 | val value = parameters[key]?.let { it[0] } ?: return null 16 | val adjusted = if (explode) value.replace('=', ',') else value 17 | return converter.convert(adjusted) 18 | } 19 | 20 | companion object: BuilderSelector { 21 | override fun canHandle(type: KType, explode: Boolean): Boolean { 22 | return true 23 | } 24 | 25 | override fun create(type: KType, explode: Boolean): SimpleBuilder { 26 | return SimpleBuilder(type, explode) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/path/simple/SimpleBuilderFactory.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.path.simple 2 | 3 | import com.papsign.ktor.openapigen.parameters.PathParamStyle 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelectorFactory 6 | 7 | object SimpleBuilderFactory: BuilderSelectorFactory, PathParamStyle>( 8 | SimpleBuilder 9 | ) 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/deepobject/ArrayDeepBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector 4 | import com.papsign.ktor.openapigen.parameters.util.ListToArray 5 | import kotlin.reflect.KType 6 | import kotlin.reflect.jvm.jvmErasure 7 | 8 | class ArrayDeepBuilder(type: KType) : CollectionDeepBuilder(type) { 9 | 10 | private val converter = ListToArray(type) 11 | 12 | override fun transform(lst: List): Any? { 13 | return converter.cvt(lst) 14 | } 15 | 16 | companion object : BuilderSelector { 17 | 18 | override fun canHandle(type: KType, explode: Boolean): Boolean { 19 | return type.jvmErasure.java.isArray 20 | } 21 | 22 | override fun create(type: KType, explode: Boolean): ArrayDeepBuilder { 23 | return ArrayDeepBuilder(type) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/deepobject/CollectionDeepBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject 2 | 3 | import com.papsign.ktor.openapigen.parameters.QueryParamStyle 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 5 | import com.papsign.ktor.openapigen.parameters.util.ListToArray 6 | import kotlin.reflect.KType 7 | 8 | abstract class CollectionDeepBuilder(type: KType) : DeepBuilder { 9 | 10 | private val contentType = ListToArray.arrayComponentKType(type) 11 | 12 | private val builder: Builder by lazy { 13 | DeepBuilderFactory.buildBuilderForced(contentType, explode) 14 | } 15 | 16 | abstract fun transform(lst: List): Any? 17 | 18 | override fun build(key: String, parameters: Map>): Any? { 19 | val names = parameters.filterKeys { it != key && it.startsWith(key) } 20 | return transform(if (names.isEmpty()) { 21 | listOf() 22 | } else { 23 | val indices = 24 | names.entries.groupBy { (k, _) -> k.substring(key.length + 1, k.indexOf("]", key.length)).toInt() } 25 | indices.entries.fold( 26 | ArrayList((0..(indices.keys.maxOrNull() ?: 0)).map { null }) 27 | ) { acc, (idx, params) -> 28 | acc[idx] = builder.build("$key[$idx]", params.associate { (key, value) -> key to value }) 29 | acc 30 | } 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/deepobject/DeepBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject 2 | 3 | import com.papsign.ktor.openapigen.parameters.QueryParamStyle 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 5 | 6 | interface DeepBuilder : 7 | Builder { 8 | override val style: QueryParamStyle 9 | get() = QueryParamStyle.deepObject 10 | override val explode: Boolean 11 | get() = true 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/deepobject/DeepBuilderFactory.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject 2 | 3 | import com.papsign.ktor.openapigen.parameters.QueryParamStyle 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelectorFactory 6 | import com.papsign.ktor.openapigen.parameters.parsers.builders.query.form.ConverterFormBuilder 7 | 8 | object DeepBuilderFactory : BuilderSelectorFactory, QueryParamStyle>( 9 | ConverterFormBuilder.primitive, 10 | ListDeepBuilder, 11 | ArrayDeepBuilder, 12 | MapDeepBuilder, 13 | ObjectDeepBuilder 14 | ) 15 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/deepobject/ListDeepBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector 4 | import kotlin.reflect.KType 5 | import kotlin.reflect.full.isSubclassOf 6 | import kotlin.reflect.jvm.jvmErasure 7 | 8 | class ListDeepBuilder(type: KType) : CollectionDeepBuilder(type) { 9 | 10 | override fun transform(lst: List): Any? { 11 | return lst 12 | } 13 | 14 | companion object : BuilderSelector { 15 | 16 | override fun canHandle(type: KType, explode: Boolean): Boolean { 17 | return type.jvmErasure.isSubclassOf(List::class) 18 | } 19 | 20 | override fun create(type: KType, explode: Boolean): ListDeepBuilder { 21 | return ListDeepBuilder(type) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/deepobject/MapDeepBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector 4 | import com.papsign.ktor.openapigen.parameters.parsers.converters.primitive.PrimitiveConverterFactory 5 | import kotlin.reflect.KType 6 | import kotlin.reflect.full.isSubclassOf 7 | import kotlin.reflect.jvm.jvmErasure 8 | 9 | class MapDeepBuilder(val type: KType) : DeepBuilder { 10 | private val keyType = type.arguments[0].type!! 11 | private val valueType = type.arguments[1].type!! 12 | private val keyBuilder = PrimitiveConverterFactory.buildConverterForced(keyType) 13 | private val valueBuilder by lazy { // must be lazy or will recurse infinitely 14 | DeepBuilderFactory.buildBuilderForced(valueType, explode) 15 | } 16 | 17 | override fun build(key: String, parameters: Map>): Any? { 18 | val names = parameters.filterKeys { it.startsWith(key) } 19 | val indices = names.entries.groupBy { (k, _) -> k.substring(key.length + 1, k.indexOf("]", key.length)) } 20 | return indices.entries.fold(LinkedHashMap()) { acc, (k, value) -> 21 | acc[keyBuilder.convert(k)] = valueBuilder.build("$key[$k]", value.associate { (k, v) -> k to v }) 22 | acc 23 | } 24 | } 25 | 26 | companion object: 27 | BuilderSelector { 28 | 29 | override fun canHandle(type: KType, explode: Boolean): Boolean { 30 | return type.jvmErasure.isSubclassOf(Map::class) 31 | } 32 | 33 | override fun create(type: KType, explode: Boolean): MapDeepBuilder { 34 | return MapDeepBuilder(type) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/deepobject/ObjectDeepBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject 2 | 3 | import com.papsign.ktor.openapigen.annotations.mapping.openAPIName 4 | import com.papsign.ktor.openapigen.parameters.QueryParamStyle 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 6 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector 7 | import java.lang.reflect.InvocationTargetException 8 | import kotlin.reflect.KFunction 9 | import kotlin.reflect.KParameter 10 | import kotlin.reflect.KType 11 | import kotlin.reflect.full.primaryConstructor 12 | import kotlin.reflect.jvm.jvmErasure 13 | 14 | class ObjectDeepBuilder(val type: KType) : DeepBuilder { 15 | 16 | private val builderMap: Map> 17 | private val constructor: KFunction 18 | 19 | init { 20 | val kclass = type.jvmErasure 21 | if (kclass.isData) { 22 | constructor = kclass.primaryConstructor ?: error("Parameter objects must have primary constructors") 23 | builderMap = constructor.parameters.associateWith { parameter -> 24 | DeepBuilderFactory.buildBuilder( 25 | parameter.type, 26 | explode 27 | ) ?: error("Could not find DeepObject Builders for type $type") 28 | } 29 | } else { 30 | error("Only data classes are currently supported for deep objects") 31 | } 32 | } 33 | 34 | override fun build(key: String, parameters: Map>): Any? { 35 | return try { 36 | constructor.callBy(builderMap.mapValues { it.value.build("$key[${it.key.openAPIName}]", parameters) }) 37 | } catch (e: InvocationTargetException) { 38 | null 39 | } 40 | } 41 | 42 | companion object : BuilderSelector { 43 | 44 | override fun canHandle(type: KType, explode: Boolean): Boolean { 45 | return true 46 | } 47 | 48 | override fun create(type: KType, explode: Boolean): ObjectDeepBuilder { 49 | return ObjectDeepBuilder(type) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/delimited/ArrayPipeDelimitedBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.delimited 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector 4 | import com.papsign.ktor.openapigen.parameters.util.ListToArray 5 | import kotlin.reflect.KType 6 | import kotlin.reflect.jvm.jvmErasure 7 | 8 | class ArrayPipeDelimitedBuilder(type: KType): CollectionDelimitedBuilder(type, "|") { 9 | 10 | private val converter = ListToArray(type) 11 | 12 | override fun transform(lst: List): Any? { 13 | return converter.cvt(lst) 14 | } 15 | 16 | companion object : BuilderSelector { 17 | 18 | override fun canHandle(type: KType, explode: Boolean): Boolean { 19 | return !explode && type.jvmErasure.java.isArray 20 | } 21 | 22 | override fun create(type: KType, explode: Boolean): ArrayPipeDelimitedBuilder { 23 | return ArrayPipeDelimitedBuilder( 24 | type 25 | ) 26 | } 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/delimited/ArraySpaceDelimitedBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.delimited 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector 4 | import com.papsign.ktor.openapigen.parameters.util.ListToArray 5 | import kotlin.reflect.KType 6 | import kotlin.reflect.jvm.jvmErasure 7 | 8 | class ArraySpaceDelimitedBuilder(type: KType): CollectionDelimitedBuilder(type, " ") { 9 | 10 | private val converter = ListToArray(type) 11 | 12 | override fun transform(lst: List): Any? { 13 | return converter.cvt(lst) 14 | } 15 | 16 | companion object : BuilderSelector { 17 | 18 | override fun canHandle(type: KType, explode: Boolean): Boolean { 19 | return !explode && type.jvmErasure.java.isArray 20 | } 21 | 22 | override fun create(type: KType, explode: Boolean): ArraySpaceDelimitedBuilder { 23 | return ArraySpaceDelimitedBuilder( 24 | type 25 | ) 26 | } 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/delimited/CollectionDelimitedBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.delimited 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.query.form.FormBuilder 4 | import com.papsign.ktor.openapigen.parameters.parsers.converters.Converter 5 | import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterFactory 6 | import com.papsign.ktor.openapigen.parameters.util.ListToArray 7 | import kotlin.reflect.KType 8 | 9 | abstract class CollectionDelimitedBuilder(type: KType, val delimiter: String): FormBuilder() { 10 | 11 | override val explode: Boolean = false 12 | 13 | abstract fun transform(lst: List): Any? 14 | 15 | private val converter: Converter = ConverterFactory.buildConverterForced(ListToArray.arrayComponentKType(type)) 16 | 17 | override fun build(key: String, parameters: Map>): Any? { 18 | return parameters[key]?.let { it[0] }?.split(delimiter)?.map { converter.convert(it) } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/delimited/ListPipeDelimitedBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.delimited 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector 4 | import kotlin.reflect.KType 5 | import kotlin.reflect.full.isSubclassOf 6 | import kotlin.reflect.jvm.jvmErasure 7 | 8 | class ListPipeDelimitedBuilder(type: KType) : CollectionDelimitedBuilder(type, "|") { 9 | 10 | override fun transform(lst: List): Any? { 11 | return lst 12 | } 13 | 14 | companion object : BuilderSelector { 15 | 16 | override fun canHandle(type: KType, explode: Boolean): Boolean { 17 | return !explode && type.jvmErasure.isSubclassOf(List::class) 18 | } 19 | 20 | override fun create(type: KType, explode: Boolean): ListPipeDelimitedBuilder { 21 | return ListPipeDelimitedBuilder( 22 | type 23 | ) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/delimited/ListSpaceDelimitedBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.delimited 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector 4 | import kotlin.reflect.KType 5 | import kotlin.reflect.full.isSubclassOf 6 | import kotlin.reflect.jvm.jvmErasure 7 | 8 | class ListSpaceDelimitedBuilder(type: KType) : CollectionDelimitedBuilder(type, " ") { 9 | 10 | override fun transform(lst: List): Any? { 11 | return lst 12 | } 13 | 14 | companion object : BuilderSelector { 15 | 16 | override fun canHandle(type: KType, explode: Boolean): Boolean { 17 | return !explode && type.jvmErasure.isSubclassOf(List::class) 18 | } 19 | 20 | override fun create(type: KType, explode: Boolean): ListSpaceDelimitedBuilder { 21 | return ListSpaceDelimitedBuilder( 22 | type 23 | ) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/delimited/PipeDelimitedBuilderFactory.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.delimited 2 | 3 | import com.papsign.ktor.openapigen.parameters.QueryParamStyle 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelectorFactory 6 | import com.papsign.ktor.openapigen.parameters.parsers.builders.query.form.ConverterFormBuilder 7 | 8 | object PipeDelimitedBuilderFactory : BuilderSelectorFactory, QueryParamStyle>( 9 | ConverterFormBuilder.primitive, 10 | ArrayPipeDelimitedBuilder, 11 | ListPipeDelimitedBuilder, 12 | ConverterFormBuilder.all 13 | ) 14 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/delimited/SpaceDelimitedBuilderFactory.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.delimited 2 | 3 | import com.papsign.ktor.openapigen.parameters.QueryParamStyle 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelectorFactory 6 | import com.papsign.ktor.openapigen.parameters.parsers.builders.query.form.ConverterFormBuilder 7 | 8 | object SpaceDelimitedBuilderFactory: BuilderSelectorFactory, QueryParamStyle>( 9 | ConverterFormBuilder.primitive, 10 | ArraySpaceDelimitedBuilder, 11 | ListSpaceDelimitedBuilder, 12 | ConverterFormBuilder.all 13 | ) 14 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/form/ArrayExplodedFormBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.form 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector 4 | import com.papsign.ktor.openapigen.parameters.util.ListToArray 5 | import kotlin.reflect.KType 6 | import kotlin.reflect.jvm.jvmErasure 7 | 8 | class ArrayExplodedFormBuilder(type: KType) : CollectionExplodedFormBuilder(type) { 9 | 10 | private val converter = ListToArray(type) 11 | 12 | override fun transform(lst: List): Any? { 13 | return converter.cvt(lst) 14 | } 15 | 16 | companion object : BuilderSelector { 17 | 18 | override fun canHandle(type: KType, explode: Boolean): Boolean { 19 | return type.jvmErasure.java.isArray && explode 20 | } 21 | 22 | override fun create(type: KType, explode: Boolean): ArrayExplodedFormBuilder { 23 | return ArrayExplodedFormBuilder(type) 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/form/CollectionExplodedFormBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.form 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.converters.Converter 4 | import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterFactory 5 | import com.papsign.ktor.openapigen.parameters.util.ListToArray 6 | import kotlin.reflect.KType 7 | 8 | abstract class CollectionExplodedFormBuilder(type: KType): FormBuilder() { 9 | 10 | 11 | override val explode: Boolean = true 12 | 13 | abstract fun transform(lst: List): Any? 14 | 15 | private val converter: Converter = ConverterFactory.buildConverterForced(ListToArray.arrayComponentKType(type)) 16 | 17 | override fun build(key: String, parameters: Map>): Any? { 18 | return (parameters[key] ?: listOf()).map(converter::convert).let(::transform) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/form/ConverterFormBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.form 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector 4 | import com.papsign.ktor.openapigen.parameters.parsers.converters.Converter 5 | import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterFactory 6 | import com.papsign.ktor.openapigen.parameters.parsers.converters.primitive.PrimitiveConverterFactory 7 | import kotlin.reflect.KType 8 | 9 | open class ConverterFormBuilder(type: KType, factory: ConverterFactory): FormBuilder() { 10 | 11 | private val converter: Converter = factory.buildConverterForced(type) 12 | override val explode: Boolean = false 13 | 14 | override fun build(key: String, parameters: Map>): Any? { 15 | return parameters[key]?.let{it[0]}?.let { converter.convert(it) } 16 | } 17 | 18 | open class Selector(private val factory: ConverterFactory, private val ignoreExplode: Boolean = false): BuilderSelector { 19 | override fun canHandle(type: KType, explode: Boolean): Boolean { 20 | return (ignoreExplode || !explode) && factory.buildConverter(type) != null 21 | } 22 | 23 | override fun create(type: KType, explode: Boolean): ConverterFormBuilder { 24 | return ConverterFormBuilder(type, factory) 25 | } 26 | } 27 | 28 | companion object { 29 | operator fun invoke(factory: ConverterFactory, ignoreExplode: Boolean = false): Selector { 30 | return Selector(factory, ignoreExplode) 31 | } 32 | 33 | val primitive = invoke(PrimitiveConverterFactory, true) 34 | val all = invoke(ConverterFactory, true) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/form/FormBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.form 2 | 3 | import com.papsign.ktor.openapigen.parameters.QueryParamStyle 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 5 | 6 | abstract class FormBuilder: Builder { 7 | override val style: QueryParamStyle = QueryParamStyle.form 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/form/FormBuilderFactory.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.form 2 | 3 | import com.papsign.ktor.openapigen.parameters.QueryParamStyle 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 5 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelectorFactory 6 | 7 | object FormBuilderFactory: BuilderSelectorFactory, QueryParamStyle>( 8 | ConverterFormBuilder.primitive, 9 | ArrayExplodedFormBuilder, 10 | ListExplodedFormBuilder, 11 | ConverterFormBuilder.all 12 | ) 13 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/form/ListExplodedFormBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.form 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.query.delimited.ListSpaceDelimitedBuilder 5 | import kotlin.reflect.KType 6 | import kotlin.reflect.full.isSubclassOf 7 | import kotlin.reflect.jvm.jvmErasure 8 | 9 | class ListExplodedFormBuilder(type: KType) : CollectionExplodedFormBuilder(type) { 10 | 11 | override fun transform(lst: List): Any? { 12 | return lst 13 | } 14 | 15 | 16 | companion object : BuilderSelector { 17 | 18 | override fun canHandle(type: KType, explode: Boolean): Boolean { 19 | return type.jvmErasure.isSubclassOf(List::class) && explode 20 | } 21 | 22 | override fun create(type: KType, explode: Boolean): ListExplodedFormBuilder { 23 | return ListExplodedFormBuilder(type) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/Converter.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.converters 2 | 3 | interface Converter { 4 | fun convert(value: String): Any? 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/ConverterFactory.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.converters 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.converters.`object`.MapConverter 4 | import com.papsign.ktor.openapigen.parameters.parsers.converters.`object`.ObjectConverter 5 | import com.papsign.ktor.openapigen.parameters.parsers.converters.collection.ArrayConverter 6 | import com.papsign.ktor.openapigen.parameters.parsers.converters.collection.ListConverter 7 | import com.papsign.ktor.openapigen.parameters.parsers.converters.primitive.EnumConverter 8 | import com.papsign.ktor.openapigen.parameters.parsers.converters.primitive.PrimitiveConverter 9 | import kotlin.reflect.KType 10 | 11 | interface ConverterFactory { 12 | fun buildConverter(type: KType): Converter? 13 | fun buildConverterForced(type: KType): Converter = buildConverter(type) ?: error("No ${this.javaClass.declaringClass?.simpleName ?: this.javaClass.simpleName} Converter exists for type $type") 14 | 15 | companion object: ConverterSelectorFactory( 16 | PrimitiveConverter, 17 | EnumConverter, 18 | ListConverter, 19 | ArrayConverter, 20 | MapConverter, 21 | ObjectConverter 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/ConverterSelector.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.converters 2 | 3 | import kotlin.reflect.KType 4 | 5 | interface ConverterSelector { 6 | fun canHandle(type: KType): Boolean 7 | fun create(type: KType): Converter 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/ConverterSelectorFactory.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.converters 2 | 3 | import kotlin.reflect.KType 4 | 5 | open class ConverterSelectorFactory(vararg val selectors: ConverterSelector): ConverterFactory { 6 | override fun buildConverter(type: KType): Converter? { 7 | return selectors.find { it.canHandle(type) }?.create(type) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/collection/ArrayConverter.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.converters.collection 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterSelector 4 | import com.papsign.ktor.openapigen.parameters.util.ListToArray 5 | import kotlin.reflect.KType 6 | import kotlin.reflect.jvm.jvmErasure 7 | 8 | class ArrayConverter(type: KType) : CollectionConverter(type) { 9 | 10 | private val converter = ListToArray(type) 11 | 12 | override fun transform(lst: List): Any? { 13 | return converter.cvt(lst) 14 | } 15 | 16 | companion object : ConverterSelector { 17 | override fun canHandle(type: KType): Boolean { 18 | return type.jvmErasure.java.isArray 19 | } 20 | 21 | override fun create(type: KType): CollectionConverter { 22 | return ArrayConverter(type) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/collection/CollectionConverter.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.converters.collection 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.converters.Converter 4 | import com.papsign.ktor.openapigen.parameters.parsers.converters.primitive.PrimitiveConverterFactory 5 | import com.papsign.ktor.openapigen.parameters.util.ListToArray 6 | import kotlin.reflect.KType 7 | 8 | abstract class CollectionConverter(type: KType): ListedConverter { 9 | 10 | private val converter: Converter = PrimitiveConverterFactory.buildConverterForced(ListToArray.arrayComponentKType(type)) 11 | 12 | abstract fun transform(lst: List): Any? 13 | 14 | override fun convert(value: String): Any? { 15 | return convert(value.split(",")) 16 | } 17 | 18 | override fun convert(list: List): Any? { 19 | return list.map(converter::convert).let(::transform) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/collection/CollectionConverterFactory.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.converters.collection 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterSelectorFactory 4 | import com.papsign.ktor.openapigen.parameters.parsers.converters.primitive.EnumConverter 5 | import com.papsign.ktor.openapigen.parameters.parsers.converters.primitive.PrimitiveConverter 6 | 7 | object CollectionConverterFactory: ConverterSelectorFactory( 8 | PrimitiveConverter, 9 | EnumConverter, 10 | ListConverter, 11 | ArrayConverter 12 | ) 13 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/collection/ListConverter.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.converters.collection 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterSelector 4 | import kotlin.reflect.KType 5 | import kotlin.reflect.full.isSubclassOf 6 | import kotlin.reflect.jvm.jvmErasure 7 | 8 | class ListConverter(type: KType) : CollectionConverter(type) { 9 | 10 | override fun transform(lst: List): Any? { 11 | return lst 12 | } 13 | 14 | companion object : ConverterSelector { 15 | override fun canHandle(type: KType): Boolean { 16 | return type.jvmErasure.isSubclassOf(List::class) 17 | } 18 | 19 | override fun create(type: KType): CollectionConverter { 20 | return ListConverter(type) 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/collection/ListedConverter.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.converters.collection 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.converters.Converter 4 | 5 | interface ListedConverter: Converter { 6 | fun convert(list: List): Any? 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/object/MapConverter.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.converters.`object` 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.converters.Converter 4 | import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterSelector 5 | import com.papsign.ktor.openapigen.parameters.parsers.converters.primitive.PrimitiveConverterFactory 6 | import kotlin.reflect.KType 7 | import kotlin.reflect.full.isSubclassOf 8 | import kotlin.reflect.jvm.jvmErasure 9 | 10 | class MapConverter(type: KType): MappedConverter { 11 | 12 | val keyConverter = PrimitiveConverterFactory.buildConverterForced(type.arguments[0].type!!) 13 | val valueConverter = PrimitiveConverterFactory.buildConverterForced(type.arguments[1].type!!) 14 | 15 | override fun convert(value: String): Any? { 16 | return convert(value.split(",").windowed(2).associate { it[0] to it[1] }) 17 | } 18 | 19 | override fun convert(map: Map): Any? { 20 | return map.entries.associate { (key, value) -> keyConverter.convert(key) to valueConverter.convert(value) } 21 | } 22 | 23 | companion object: ConverterSelector { 24 | 25 | override fun canHandle(type: KType): Boolean { 26 | return type.jvmErasure.isSubclassOf(Map::class) 27 | } 28 | 29 | override fun create(type: KType): MapConverter { 30 | return MapConverter(type) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/object/MappedConverter.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.converters.`object` 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.converters.Converter 4 | import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterFactory 5 | 6 | interface MappedConverter: Converter { 7 | fun convert(map: Map): Any? 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/object/ObjectConverter.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.converters.`object` 2 | 3 | import com.papsign.ktor.openapigen.annotations.mapping.openAPIName 4 | import com.papsign.ktor.openapigen.parameters.parsers.converters.Converter 5 | import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterSelector 6 | import com.papsign.ktor.openapigen.parameters.parsers.converters.primitive.PrimitiveConverterFactory 7 | import java.lang.reflect.InvocationTargetException 8 | import kotlin.reflect.KFunction 9 | import kotlin.reflect.KParameter 10 | import kotlin.reflect.KType 11 | import kotlin.reflect.full.primaryConstructor 12 | import kotlin.reflect.jvm.jvmErasure 13 | 14 | class ObjectConverter(type: KType): MappedConverter { 15 | 16 | private val builderMap: Map 17 | private val constructor: KFunction 18 | 19 | init { 20 | val kclass = type.jvmErasure 21 | if (kclass.isData) { 22 | constructor = kclass.primaryConstructor ?: error("Parameter objects must have primary constructors") 23 | builderMap = constructor.parameters.associateWith { parameter -> PrimitiveConverterFactory.buildConverter(parameter.type) ?: error("Invalid type ${parameter.type} in object $type, only enums and primitives are allowed") } 24 | } else { 25 | error("Only data classes are currently supported for Parameter objects") 26 | } 27 | } 28 | 29 | override fun convert(value: String): Any? { 30 | return convert(value.split(",").windowed(2).associate { it[0] to it[1] }) 31 | } 32 | 33 | override fun convert(map: Map): Any? { 34 | return try { constructor.callBy(builderMap.mapValues { (key, value) -> map[key.openAPIName]?.let { value.convert(it) } }) } catch (e: InvocationTargetException) { null } 35 | } 36 | 37 | companion object: ConverterSelector { 38 | 39 | override fun canHandle(type: KType): Boolean { 40 | return true 41 | } 42 | 43 | override fun create(type: KType): ObjectConverter { 44 | return ObjectConverter(type) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/primitive/EnumConverter.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.converters.primitive 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.converters.Converter 4 | import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterSelector 5 | import kotlin.reflect.KType 6 | import kotlin.reflect.jvm.jvmErasure 7 | 8 | class EnumConverter(type: KType): Converter { 9 | 10 | private val enumMap = type.jvmErasure.java.enumConstants.associateBy { it.toString() } 11 | 12 | override fun convert(value: String): Any? { 13 | return enumMap[value] 14 | } 15 | 16 | companion object: ConverterSelector { 17 | override fun canHandle(type: KType): Boolean { 18 | return type.jvmErasure.java.isEnum 19 | } 20 | 21 | override fun create(type: KType): EnumConverter { 22 | return EnumConverter( 23 | type 24 | ) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/primitive/PrimitiveConverterFactory.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.converters.primitive 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterSelectorFactory 4 | 5 | object PrimitiveConverterFactory: ConverterSelectorFactory( 6 | PrimitiveConverter, 7 | EnumConverter 8 | ) 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/util/DateTimeFormatters.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.util 2 | 3 | import java.time.format.DateTimeFormatter 4 | import java.time.format.DateTimeFormatterBuilder 5 | import java.time.temporal.ChronoField 6 | 7 | private fun baseDateTimeFormatterBuilder() = DateTimeFormatterBuilder() 8 | .appendPattern("yyyy-MM-dd['T'][ ]HH:mm[:ss]") 9 | .optionalStart() 10 | .appendFraction(ChronoField.NANO_OF_SECOND, 1, 9, true) 11 | .optionalEnd() 12 | 13 | val localDateTimeFormatter: DateTimeFormatter = baseDateTimeFormatterBuilder().toFormatter() 14 | 15 | val offsetDateTimeFormatter: DateTimeFormatter = baseDateTimeFormatterBuilder() 16 | .appendPattern("[xxx][xx][X]") 17 | .toFormatter() 18 | 19 | val zonedDateTimeFormatter: DateTimeFormatter = baseDateTimeFormatterBuilder() 20 | .appendPattern("[xxx][xx][X]") 21 | .optionalStart() 22 | .appendLiteral('[') 23 | .optionalEnd() 24 | .optionalStart() 25 | .appendZoneId() 26 | .optionalEnd() 27 | .optionalStart() 28 | .appendLiteral(']') 29 | .optionalEnd() 30 | .toFormatter() 31 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/util/ListToArray.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.util 2 | 3 | import kotlin.reflect.KType 4 | import kotlin.reflect.full.starProjectedType 5 | import kotlin.reflect.jvm.jvmErasure 6 | 7 | class ListToArray(arrayType: KType) { 8 | 9 | private val type: Class<*> = arrayComponentClass(arrayType) 10 | 11 | fun cvt(list: List): Any? { 12 | val arr = java.lang.reflect.Array.newInstance(type, list.size) 13 | list.forEachIndexed { index, elem -> 14 | java.lang.reflect.Array.set(arr, index, elem) 15 | } 16 | return arr 17 | } 18 | 19 | companion object { 20 | fun arrayComponentKType(arrayType: KType): KType { 21 | return arrayType.arguments.firstOrNull()?.type ?: arrayType.jvmErasure.java.componentType.kotlin.starProjectedType 22 | } 23 | fun arrayComponentClass(arrayType: KType): Class<*> { 24 | return arrayType.arguments.firstOrNull()?.type?.jvmErasure?.javaObjectType ?: 25 | arrayType.jvmErasure.java.componentType.kotlin.javaPrimitiveType ?: 26 | Any::class.java 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/parameters/util/Util.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.util 2 | 3 | import com.papsign.ktor.openapigen.annotations.parameters.HeaderParam 4 | import com.papsign.ktor.openapigen.annotations.parameters.PathParam 5 | import com.papsign.ktor.openapigen.annotations.parameters.QueryParam 6 | import com.papsign.ktor.openapigen.parameters.handlers.ModularParameterHandler 7 | import com.papsign.ktor.openapigen.parameters.handlers.ParameterHandler 8 | import com.papsign.ktor.openapigen.parameters.handlers.UnitParameterHandler 9 | import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder 10 | import kotlin.reflect.KFunction 11 | import kotlin.reflect.KParameter 12 | import kotlin.reflect.KType 13 | import kotlin.reflect.full.findAnnotation 14 | import kotlin.reflect.full.primaryConstructor 15 | import kotlin.reflect.jvm.jvmErasure 16 | 17 | fun buildParameterHandler(tType: KType): ParameterHandler { 18 | @Suppress("UNCHECKED_CAST") 19 | if (tType.classifier == Unit::class) return UnitParameterHandler as ParameterHandler 20 | val tClass = tType.jvmErasure 21 | assert(tClass.isData) { "API route with ${tClass.simpleName} must be a data class." } 22 | val constructor = tClass.primaryConstructor ?: error("API routes with ${tClass.simpleName} must have a primary constructor.") 23 | val parsers: Map> = constructor.parameters.associateWith { param -> 24 | val type = param.type 25 | param.findAnnotation()?.let { a -> a.style.factory.buildBuilderForced(type, a.explode) } ?: 26 | param.findAnnotation()?.let { a -> a.style.factory.buildBuilderForced(type, a.explode) } ?: 27 | param.findAnnotation()?.let { a -> a.style.factory.buildBuilderForced(type, a.explode) } ?: 28 | error("Parameters must be annotated with @PathParam or @QueryParam") 29 | } 30 | @Suppress("UNCHECKED_CAST") 31 | return ModularParameterHandler( 32 | parsers, 33 | constructor as KFunction 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/route/Exit.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.route 2 | 3 | import io.ktor.routing.Route 4 | import io.ktor.util.pipeline.ContextDsl 5 | 6 | @ContextDsl 7 | inline fun OpenAPIRoute<*>.exitAPI(crossinline fn: Route.() -> Unit) { 8 | ktorRoute.fn() 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/route/Info.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.route 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.model.operation.OperationModel 5 | import com.papsign.ktor.openapigen.modules.ModuleProvider 6 | import com.papsign.ktor.openapigen.modules.RouteOpenAPIModule 7 | import com.papsign.ktor.openapigen.modules.openapi.OperationModule 8 | 9 | /** 10 | * Adds a summary and description for the endpoint being configured 11 | */ 12 | fun info(summary: String? = null, description: String? = null) = EndpointInfo(summary, description) 13 | 14 | data class EndpointInfo( 15 | val summary: String? = null, 16 | val description: String? = null 17 | ) : OperationModule, RouteOpenAPIModule { 18 | override fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>, operation: OperationModel) { 19 | operation.description = description 20 | operation.summary = summary 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/route/RouteConfig.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.route 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.route.path.normal.NormalOpenAPIRoute 5 | import io.ktor.application.Application 6 | import io.ktor.application.feature 7 | import io.ktor.routing.Routing 8 | import io.ktor.routing.routing 9 | import io.ktor.util.pipeline.ContextDsl 10 | 11 | /** 12 | * Wrapper for [io.ktor.routing.routing] to create the endpoints while configuring OpenAPI 13 | * documentation at the same time. 14 | */ 15 | @ContextDsl 16 | fun Application.apiRouting(config: NormalOpenAPIRoute.() -> Unit) { 17 | routing { 18 | NormalOpenAPIRoute( 19 | this, 20 | application.feature(OpenAPIGen).globalModuleProvider 21 | ).apply(config) 22 | } 23 | } 24 | 25 | /** 26 | * Wrapper for [io.ktor.routing.routing] to create the endpoints while configuring OpenAPI 27 | * documentation at the same time. 28 | * 29 | * @param config 30 | */ 31 | @ContextDsl 32 | fun Routing.apiRouting(config: NormalOpenAPIRoute.() -> Unit) { 33 | NormalOpenAPIRoute( 34 | this, 35 | application.feature(OpenAPIGen).globalModuleProvider 36 | ).apply(config) 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/route/Status.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.route 2 | 3 | import com.papsign.ktor.openapigen.annotations.Response 4 | import com.papsign.ktor.openapigen.modules.RouteOpenAPIModule 5 | import com.papsign.ktor.openapigen.modules.providers.StatusProvider 6 | import com.papsign.ktor.openapigen.modules.registerModule 7 | import io.ktor.http.HttpStatusCode 8 | import kotlin.reflect.KAnnotatedElement 9 | import kotlin.reflect.KType 10 | import kotlin.reflect.full.findAnnotation 11 | 12 | /** 13 | * Sets the success response status code of the endpoint 14 | */ 15 | fun status(code: HttpStatusCode) = StatusCode(code) 16 | 17 | /** 18 | * Sets the success response status code of the endpoint 19 | */ 20 | fun status(code: Int) = StatusCode(HttpStatusCode.fromValue(code)) 21 | 22 | /** 23 | * Success response status code of the endpoint will be derived from the @Response annotation 24 | */ 25 | fun responseAnnotationStatus() = ResponseAnnotationStatusCode 26 | 27 | /** 28 | * Sets the success response status code of the endpoints defined in the block 29 | */ 30 | inline fun > T.status(code: HttpStatusCode, crossinline fn: T.() -> Unit) { 31 | child().apply { provider.registerModule(status(code)) }.fn() 32 | } 33 | 34 | /** 35 | * Sets the success response status code of the endpoints defined in the block 36 | */ 37 | inline fun > T.status(code: Int, crossinline fn: T.() -> Unit) { 38 | child().apply { provider.registerModule(status(code)) }.fn() 39 | } 40 | 41 | /** 42 | * Success response status code of the endpoints defined in the block will be derived from the @Response annotation 43 | */ 44 | inline fun > T.responseAnnotationStatus(crossinline fn: T.() -> Unit) { 45 | child().apply { provider.registerModule(responseAnnotationStatus()) }.fn() 46 | } 47 | 48 | data class StatusCode(val code: HttpStatusCode) : StatusProvider, RouteOpenAPIModule { 49 | override fun getStatusForType(responseType: KType): HttpStatusCode = code 50 | } 51 | 52 | object ResponseAnnotationStatusCode : StatusProvider, RouteOpenAPIModule { 53 | override fun getStatusForType(responseType: KType): HttpStatusCode { 54 | return (responseType.classifier as? KAnnotatedElement)?.findAnnotation()?.statusCode?.let { HttpStatusCode.fromValue(it) } ?: HttpStatusCode.OK 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/route/TagModule.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.route 2 | 3 | import com.papsign.ktor.openapigen.APITag 4 | import com.papsign.ktor.openapigen.modules.providers.TagProviderModule 5 | 6 | class TagModule(override val tags: Collection): TagProviderModule 7 | 8 | fun tags(vararg tags: APITag) = TagModule(tags.asList()) 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/route/modules/HttpMethodProviderModule.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.route.modules 2 | 3 | import com.papsign.ktor.openapigen.modules.providers.MethodProvider 4 | import io.ktor.http.HttpMethod 5 | 6 | class HttpMethodProviderModule(override val method: HttpMethod): MethodProvider -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/route/modules/PathProviderModule.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.route.modules 2 | 3 | import com.papsign.ktor.openapigen.modules.providers.PathProvider 4 | 5 | class PathProviderModule(override val path: String): PathProvider -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/route/path/auth/OpenAPIAuthenticatedRoute.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.route.path.auth 2 | 3 | import com.papsign.ktor.openapigen.modules.CachingModuleProvider 4 | import com.papsign.ktor.openapigen.modules.providers.AuthProvider 5 | import com.papsign.ktor.openapigen.modules.registerModule 6 | import com.papsign.ktor.openapigen.route.OpenAPIRoute 7 | import com.papsign.ktor.openapigen.route.response.AuthResponseContextImpl 8 | import com.papsign.ktor.openapigen.route.response.OpenAPIPipelineAuthContext 9 | import io.ktor.routing.Route 10 | import kotlin.reflect.KType 11 | import kotlin.reflect.typeOf 12 | 13 | class OpenAPIAuthenticatedRoute( 14 | route: Route, 15 | provider: CachingModuleProvider = CachingModuleProvider(), 16 | val authProvider: AuthProvider 17 | ) : OpenAPIRoute>(route, provider) { 18 | 19 | override fun child(route: Route): OpenAPIAuthenticatedRoute { 20 | return OpenAPIAuthenticatedRoute(route, provider.child(), authProvider) 21 | } 22 | 23 | @PublishedApi 24 | internal fun handle( 25 | paramsType: KType, 26 | responseType: KType, 27 | requestType: KType, 28 | body: suspend OpenAPIPipelineAuthContext.(TParams, TRequest) -> Unit 29 | ) { 30 | child().apply {// child in case path is branch to prevent propagation of the mutable nature of the provider 31 | provider.registerModule(authProvider) 32 | handle( 33 | paramsType, 34 | responseType, 35 | requestType 36 | ) { pipeline, responder, p, b -> 37 | AuthResponseContextImpl(pipeline, authProvider, this, responder).body(p, b) 38 | } 39 | } 40 | } 41 | 42 | @PublishedApi 43 | internal fun handle( 44 | paramsType: KType, 45 | responseType: KType, 46 | body: suspend OpenAPIPipelineAuthContext.(TParams) -> Unit 47 | ) { 48 | child().apply {// child in case path is branch to prevent propagation of the mutable nature of the provider 49 | provider.registerModule(authProvider) 50 | handle( 51 | paramsType, 52 | responseType, 53 | typeOf() 54 | ) { pipeline, responder, p: TParams, _ -> 55 | AuthResponseContextImpl(pipeline, authProvider, this, responder).body(p) 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/route/path/normal/NormalOpenAPIRoute.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.route.path.normal 2 | 3 | import com.papsign.ktor.openapigen.modules.CachingModuleProvider 4 | import com.papsign.ktor.openapigen.route.OpenAPIRoute 5 | import com.papsign.ktor.openapigen.route.response.OpenAPIPipelineResponseContext 6 | import com.papsign.ktor.openapigen.route.response.ResponseContextImpl 7 | import io.ktor.routing.Route 8 | import kotlin.reflect.KType 9 | import kotlin.reflect.typeOf 10 | 11 | class NormalOpenAPIRoute(route: Route, provider: CachingModuleProvider = CachingModuleProvider()) : 12 | OpenAPIRoute(route, provider) { 13 | 14 | override fun child(route: Route) = NormalOpenAPIRoute(route, provider.child()) 15 | 16 | @PublishedApi 17 | internal fun

handle( 18 | pType: KType, 19 | rType: KType, 20 | bType: KType, 21 | body: suspend OpenAPIPipelineResponseContext.(P, B) -> Unit 22 | ) { 23 | handle(pType, rType, bType) { pipeline, responder, p, b -> 24 | ResponseContextImpl(pipeline, this, responder).body(p, b) 25 | } 26 | } 27 | 28 | @PublishedApi 29 | internal fun

handle( 30 | pType: KType, 31 | rType: KType, 32 | body: suspend OpenAPIPipelineResponseContext.(P) -> Unit 33 | ) { 34 | handle(pType, rType, typeOf()) { pipeline, responder, p, _ -> 35 | ResponseContextImpl(pipeline, this, responder).body(p) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/route/response/OpenAPIPipelineResponseContext.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.route.response 2 | 3 | import com.papsign.ktor.openapigen.annotations.Response 4 | import com.papsign.ktor.openapigen.util.getKType 5 | import com.papsign.ktor.openapigen.modules.ofType 6 | import com.papsign.ktor.openapigen.modules.providers.AuthProvider 7 | import com.papsign.ktor.openapigen.modules.providers.StatusProvider 8 | import com.papsign.ktor.openapigen.route.OpenAPIRoute 9 | import io.ktor.application.ApplicationCall 10 | import io.ktor.http.HttpStatusCode 11 | import io.ktor.util.pipeline.PipelineContext 12 | import kotlin.reflect.full.findAnnotation 13 | 14 | interface Responder { 15 | suspend fun respond(response: TResponse, request: PipelineContext) 16 | suspend fun respond(statusCode: HttpStatusCode, response: TResponse, request: PipelineContext) 17 | } 18 | 19 | interface OpenAPIPipelineContext { 20 | val route: OpenAPIRoute<*> 21 | val pipeline: PipelineContext 22 | val responder: Responder 23 | } 24 | 25 | interface OpenAPIPipelineResponseContext : OpenAPIPipelineContext 26 | interface OpenAPIPipelineAuthContext : OpenAPIPipelineResponseContext { 27 | val authProvider: AuthProvider 28 | } 29 | 30 | class ResponseContextImpl( 31 | override val pipeline: PipelineContext, 32 | override val route: OpenAPIRoute<*>, 33 | override val responder: Responder 34 | ) : OpenAPIPipelineResponseContext 35 | 36 | class AuthResponseContextImpl( 37 | override val pipeline: PipelineContext, 38 | override val authProvider: AuthProvider, 39 | override val route: OpenAPIRoute<*>, 40 | override val responder: Responder 41 | ) : OpenAPIPipelineAuthContext 42 | 43 | 44 | suspend inline fun OpenAPIPipelineResponseContext.respond(response: TResponse) { 45 | val statusCode = route.provider.ofType().lastOrNull()?.getStatusForType(getKType()) ?: TResponse::class.findAnnotation()?.statusCode?.let { HttpStatusCode.fromValue(it) } ?: HttpStatusCode.OK 46 | responder.respond(statusCode, response as Any, pipeline) 47 | } 48 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/route/util/RouteBuildingUtil.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.route.util 2 | 3 | import io.ktor.routing.Route 4 | import io.ktor.routing.RouteSelector 5 | import io.ktor.routing.RouteSelectorEvaluation 6 | import io.ktor.routing.RoutingResolveContext 7 | 8 | fun Route.createConstantChild(): Route { 9 | return createChild(ConstantRouteSelector()) 10 | } 11 | 12 | class ConstantRouteSelector: RouteSelector(RouteSelectorEvaluation.qualityConstant) { 13 | override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation { 14 | return RouteSelectorEvaluation.Constant 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/FinalSchemaBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.schema.builder 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | import com.papsign.ktor.openapigen.schema.builder.SchemaBuilder 5 | import kotlin.reflect.KType 6 | 7 | interface FinalSchemaBuilder { 8 | fun build(type: KType, annotations: List = listOf()): SchemaModel<*> 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/SchemaBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.schema.builder 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | import kotlin.reflect.KType 5 | import kotlin.reflect.full.isSubtypeOf 6 | 7 | interface SchemaBuilder { 8 | /** 9 | * the supertype to be matched to the type in order to select this builder 10 | */ 11 | val superType: KType 12 | 13 | /** 14 | * @throws error when type is unexpected 15 | * MAKE SURE TO CALL FINALIZE ON THE PRODUCED OBJECT, AN NOT ON THE EVENTUAL RETURNED REF 16 | */ 17 | fun build(type: KType, builder: FinalSchemaBuilder, finalize: (SchemaModel<*>)->SchemaModel<*>): SchemaModel<*> 18 | 19 | fun checkType(type: KType) { 20 | if (!type.isSubtypeOf(superType)) error("${this::class} cannot build type $type, only subtypes of $superType are supported") 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/provider/DefaultCollectionSchemaProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.schema.builder.provider 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.OpenAPIGenModuleExtension 5 | import com.papsign.ktor.openapigen.util.getKType 6 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 7 | import com.papsign.ktor.openapigen.modules.DefaultOpenAPIModule 8 | import com.papsign.ktor.openapigen.modules.ModuleProvider 9 | import com.papsign.ktor.openapigen.schema.builder.FinalSchemaBuilder 10 | import com.papsign.ktor.openapigen.schema.builder.SchemaBuilder 11 | import kotlin.reflect.KType 12 | import kotlin.reflect.full.withNullability 13 | 14 | object DefaultCollectionSchemaProvider: SchemaBuilderProviderModule, OpenAPIGenModuleExtension, DefaultOpenAPIModule { 15 | 16 | private val builders = mapOf( 17 | getKType() to { _: KType -> getKType() }, 18 | getKType() to { _: KType -> getKType() }, 19 | getKType() to { _: KType -> getKType() }, 20 | getKType() to { _: KType -> getKType() }, 21 | getKType() to { _: KType -> getKType() }, 22 | getKType>() to { type: KType -> 23 | type.arguments[0].type ?: error("bad type $type: star projected types are not supported") 24 | }, 25 | getKType>() to { type: KType -> 26 | type.arguments[0].type ?: error("bad type $type: star projected types are not supported") 27 | } 28 | ).mapKeys { (key, _) -> 29 | key.withNullability(true) 30 | }.map { (key, value) -> 31 | Builder( 32 | key, 33 | value 34 | ) 35 | } 36 | 37 | override fun provide(apiGen: OpenAPIGen, provider: ModuleProvider<*>): List { 38 | return builders 39 | } 40 | 41 | private data class Builder(override val superType: KType, private val getter: (KType) -> KType) : 42 | SchemaBuilder { 43 | override fun build(type: KType, builder: FinalSchemaBuilder, finalize: (SchemaModel<*>)->SchemaModel<*>): SchemaModel<*> { 44 | checkType(type) 45 | return finalize(SchemaModel.SchemaModelArr(builder.build(getter(type)), type.isMarkedNullable)) 46 | } 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/provider/DefaultEnumSchemaProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.schema.builder.provider 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.OpenAPIGenModuleExtension 5 | import com.papsign.ktor.openapigen.util.getKType 6 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 7 | import com.papsign.ktor.openapigen.modules.DefaultOpenAPIModule 8 | import com.papsign.ktor.openapigen.modules.ModuleProvider 9 | import com.papsign.ktor.openapigen.schema.builder.FinalSchemaBuilder 10 | import com.papsign.ktor.openapigen.schema.builder.SchemaBuilder 11 | import kotlin.reflect.KType 12 | import kotlin.reflect.jvm.jvmErasure 13 | 14 | object DefaultEnumSchemaProvider: SchemaBuilderProviderModule, OpenAPIGenModuleExtension, DefaultOpenAPIModule { 15 | 16 | private object Builder: SchemaBuilder { 17 | override val superType: KType = getKType?>() 18 | 19 | override fun build( 20 | type: KType, 21 | builder: FinalSchemaBuilder, 22 | finalize: (SchemaModel<*>) -> SchemaModel<*> 23 | ): SchemaModel<*> { 24 | checkType(type) 25 | return finalize(SchemaModel.SchemaModelEnum( 26 | type.jvmErasure.java.enumConstants.map { it.toString() }, 27 | type.isMarkedNullable 28 | )) 29 | } 30 | } 31 | 32 | override fun provide(apiGen: OpenAPIGen, provider: ModuleProvider<*>): List { 33 | return listOf(Builder) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/provider/DefaultMapSchemaProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.schema.builder.provider 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.OpenAPIGenModuleExtension 5 | import com.papsign.ktor.openapigen.util.getKType 6 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 7 | import com.papsign.ktor.openapigen.modules.DefaultOpenAPIModule 8 | import com.papsign.ktor.openapigen.modules.ModuleProvider 9 | import com.papsign.ktor.openapigen.schema.builder.FinalSchemaBuilder 10 | import com.papsign.ktor.openapigen.schema.builder.SchemaBuilder 11 | import kotlin.reflect.KType 12 | 13 | object DefaultMapSchemaProvider: SchemaBuilderProviderModule, OpenAPIGenModuleExtension, DefaultOpenAPIModule { 14 | 15 | override fun provide(apiGen: OpenAPIGen, provider: ModuleProvider<*>): List { 16 | return listOf(Builder) 17 | } 18 | 19 | private object Builder : SchemaBuilder { 20 | override val superType: KType = getKType?>() 21 | override fun build(type: KType, builder: FinalSchemaBuilder, finalize: (SchemaModel<*>)->SchemaModel<*>): SchemaModel<*> { 22 | checkType(type) 23 | if (type.arguments[0].type != getKType()) error("bad type $type: Only maps with string keys are supported") 24 | val valueType = type.arguments[1].type ?: error("bad type $type: star projected types are not supported") 25 | @Suppress("UNCHECKED_CAST") 26 | return finalize(SchemaModel.SchemaModelMap( 27 | builder.build(valueType) as SchemaModel, 28 | type.isMarkedNullable 29 | )) 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/provider/DefaultSetSchemaProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.schema.builder.provider 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.OpenAPIGenModuleExtension 5 | import com.papsign.ktor.openapigen.util.getKType 6 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 7 | import com.papsign.ktor.openapigen.modules.DefaultOpenAPIModule 8 | import com.papsign.ktor.openapigen.modules.ModuleProvider 9 | import com.papsign.ktor.openapigen.schema.builder.FinalSchemaBuilder 10 | import com.papsign.ktor.openapigen.schema.builder.SchemaBuilder 11 | import kotlin.reflect.KType 12 | import kotlin.reflect.full.withNullability 13 | 14 | object DefaultSetSchemaProvider: SchemaBuilderProviderModule, OpenAPIGenModuleExtension, DefaultOpenAPIModule { 15 | 16 | private val builders = listOf( 17 | Builder(getKType?>()) { type: KType -> 18 | type.arguments[0].type ?: error("bad type $type: star projected types are not supported") 19 | } 20 | ) 21 | 22 | override fun provide(apiGen: OpenAPIGen, provider: ModuleProvider<*>): List { 23 | return builders 24 | } 25 | 26 | private data class Builder(override val superType: KType, private val getter: (KType) -> KType) : 27 | SchemaBuilder { 28 | override fun build(type: KType, builder: FinalSchemaBuilder, finalize: (SchemaModel<*>)->SchemaModel<*>): SchemaModel<*> { 29 | checkType(type) 30 | return finalize(SchemaModel.SchemaModelArr(builder.build(getter(type)), type.isMarkedNullable, uniqueItems = true)) 31 | } 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/provider/FinalSchemaBuilderProviderModule.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.schema.builder.provider 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.modules.ModuleProvider 5 | import com.papsign.ktor.openapigen.modules.OpenAPIModule 6 | import com.papsign.ktor.openapigen.schema.builder.FinalSchemaBuilder 7 | 8 | interface FinalSchemaBuilderProviderModule: OpenAPIModule { 9 | fun provide(apiGen: OpenAPIGen, provider: ModuleProvider<*>): FinalSchemaBuilder 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/provider/NothingSchemaProvider.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.schema.builder.provider 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.OpenAPIGenModuleExtension 5 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 6 | import com.papsign.ktor.openapigen.modules.DefaultOpenAPIModule 7 | import com.papsign.ktor.openapigen.modules.ModuleProvider 8 | import com.papsign.ktor.openapigen.schema.builder.FinalSchemaBuilder 9 | import com.papsign.ktor.openapigen.schema.builder.SchemaBuilder 10 | import kotlin.reflect.KType 11 | 12 | object NothingSchemaProvider: SchemaBuilderProviderModule, OpenAPIGenModuleExtension, DefaultOpenAPIModule { 13 | 14 | private object NothingNullableProvider { 15 | private val value: Nothing? = null 16 | val type: KType = this::value.returnType 17 | } 18 | 19 | private object Builder: SchemaBuilder { 20 | // Currently we can't do it in a more concise way because of https://youtrack.jetbrains.com/issue/KT-37848 21 | override val superType: KType = NothingNullableProvider.type 22 | 23 | override fun build( 24 | type: KType, 25 | builder: FinalSchemaBuilder, 26 | finalize: (SchemaModel<*>) -> SchemaModel<*> 27 | ): SchemaModel<*> { 28 | checkType(type) 29 | return finalize(SchemaModel.SchemaModelLitteral(nullable = true)) 30 | } 31 | } 32 | 33 | override fun provide(apiGen: OpenAPIGen, provider: ModuleProvider<*>): List { 34 | return listOf(Builder) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/schema/builder/provider/SchemaBuilderProviderModule.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.schema.builder.provider 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.modules.ModuleProvider 5 | import com.papsign.ktor.openapigen.modules.OpenAPIModule 6 | import com.papsign.ktor.openapigen.schema.builder.SchemaBuilder 7 | 8 | interface SchemaBuilderProviderModule: OpenAPIModule { 9 | fun provide(apiGen: OpenAPIGen, provider: ModuleProvider<*>): List 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/schema/namer/DefaultSchemaNamer.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.schema.namer 2 | 3 | import kotlin.reflect.KType 4 | 5 | object DefaultSchemaNamer : SchemaNamer { 6 | override fun get(type: KType): String = type.toString() 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/schema/namer/SchemaNamer.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.schema.namer 2 | 3 | import com.papsign.ktor.openapigen.util.deepStrip 4 | import com.papsign.ktor.openapigen.modules.OpenAPIModule 5 | import kotlin.reflect.KType 6 | 7 | interface SchemaNamer: OpenAPIModule { 8 | operator fun get(type: KType): String 9 | 10 | object Default: SchemaNamer { 11 | override fun get(type: KType): String { 12 | return type.deepStrip().toString() 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/schema/processor/SchemaProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.schema.processor 2 | 3 | import com.papsign.ktor.openapigen.model.schema.SchemaModel 4 | import kotlin.reflect.KType 5 | 6 | interface SchemaProcessor { 7 | fun process(model: SchemaModel<*>, type: KType, annotation: A): SchemaModel<*> 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/schema/processor/SchemaProcessorAnnotation.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.schema.processor 2 | 3 | import kotlin.reflect.KClass 4 | 5 | @Target(AnnotationTarget.ANNOTATION_CLASS) 6 | @MustBeDocumented 7 | annotation class SchemaProcessorAnnotation(val handler: KClass>) 8 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/util/KTypeUtil.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.util 2 | 3 | import kotlin.reflect.* 4 | import kotlin.reflect.full.createType 5 | import kotlin.reflect.full.memberProperties 6 | import kotlin.reflect.jvm.jvmErasure 7 | 8 | val unitKType = getKType() 9 | 10 | internal inline fun isNullable(): Boolean { 11 | return null is T 12 | } 13 | 14 | @PublishedApi 15 | internal inline fun getKType() = typeOf() 16 | 17 | internal fun KType.strip(nullable: Boolean = isMarkedNullable): KType { 18 | return jvmErasure.createType(arguments, nullable) 19 | } 20 | 21 | internal fun KType.deepStrip(nullable: Boolean = isMarkedNullable): KType { 22 | return jvmErasure.createType(arguments.map { it.copy(type = it.type?.deepStrip()) }, nullable) 23 | } 24 | 25 | data class KTypeProperty( 26 | val name: String, 27 | val type: KType, 28 | val source: KProperty1<*, *> 29 | ) 30 | 31 | val KType.memberProperties: List 32 | get() { 33 | val typeParameters = jvmErasure.typeParameters.zip(arguments).associate { Pair(it.first.name, it.second.type) } 34 | return jvmErasure.memberProperties.map { 35 | val retType = it.returnType 36 | val properType = when (val classifier = retType.classifier) { 37 | is KTypeParameter -> typeParameters[classifier.name] ?: it.returnType 38 | else -> it.returnType 39 | } 40 | KTypeProperty(it.name, properType, it) 41 | } 42 | } 43 | 44 | internal val KClass<*>.isInterface get() = java.isInterface 45 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/util/SerializationSettings.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.util 2 | 3 | data class SerializationSettings( 4 | val skipEmptyMap: Boolean = false, 5 | val skipEmptyList: Boolean = false, 6 | val skipEmptyValue: Boolean = false 7 | ) 8 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/util/Util.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.util 2 | 3 | import com.papsign.ktor.openapigen.OpenAPIGen 4 | import com.papsign.ktor.openapigen.model.DataModel 5 | import io.ktor.application.Application 6 | import io.ktor.application.feature 7 | import org.slf4j.Logger 8 | import org.slf4j.LoggerFactory 9 | 10 | val Application.openAPIGen: OpenAPIGen get() = feature(OpenAPIGen) 11 | 12 | internal fun Any.classLogger(): Logger { 13 | return LoggerFactory.getLogger(this::class.java) 14 | } 15 | 16 | internal inline fun classLogger(): Logger { 17 | return LoggerFactory.getLogger(T::class.java) 18 | } 19 | 20 | fun Map.cleanEmptyValues(serializationSettings: SerializationSettings = SerializationSettings()): Map { 21 | return filterValues { 22 | when (it) { 23 | is Map<*, *> -> it.isNotEmpty() || serializationSettings.skipEmptyMap 24 | is Collection<*> -> it.isNotEmpty() || serializationSettings.skipEmptyList 25 | else -> it != null || serializationSettings.skipEmptyValue 26 | } 27 | } 28 | } 29 | 30 | fun convertToValue(value: Any?, serializationSettings: SerializationSettings = SerializationSettings()): Any? { 31 | return when (value) { 32 | is DataModel -> value.serialize() 33 | is Map<*, *> -> value.entries.associate { (key, value) -> Pair(key.toString(), convertToValue(value, serializationSettings)) } 34 | .cleanEmptyValues(serializationSettings) 35 | is Iterable<*> -> value.map { convertToValue(it, serializationSettings) } 36 | else -> value 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/validation/Validator.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.validation 2 | 3 | interface Validator { 4 | /** 5 | * [subject] the serialized property 6 | * [annotation] the annotation instance on the property 7 | * @return the transformed property or [subject] if unchanged 8 | */ 9 | fun validate(subject: T?): T? 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/validation/ValidatorAnnotation.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.validation 2 | 3 | import kotlin.reflect.KClass 4 | 5 | @Target(AnnotationTarget.ANNOTATION_CLASS) 6 | @MustBeDocumented 7 | annotation class ValidatorAnnotation(val handler: KClass>) 8 | -------------------------------------------------------------------------------- /src/main/kotlin/com/papsign/ktor/openapigen/validation/ValidatorBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.validation 2 | 3 | import kotlin.reflect.KType 4 | 5 | interface ValidatorBuilder { 6 | val exceptionTypes: List 7 | get() = listOf() 8 | fun build(type: KType, annotation: A): Validator 9 | } 10 | -------------------------------------------------------------------------------- /src/test/kotlin/GeneralBehaviorTest.kt: -------------------------------------------------------------------------------- 1 | import com.papsign.ktor.openapigen.util.getKType 2 | import org.junit.Test 3 | import java.util.* 4 | import kotlin.reflect.KType 5 | import kotlin.reflect.full.isSubtypeOf 6 | 7 | class GeneralBehaviorTest { 8 | 9 | @Target(AnnotationTarget.TYPE) 10 | annotation class TypeAnnotation 11 | 12 | open class Base 13 | 14 | open class A : Base() 15 | open class B : Base() 16 | open class BA : B() 17 | open class BB : B() 18 | open class BBA : BB() 19 | 20 | @Test 21 | fun testSubtypeHierarchySet() { 22 | val set = TreeSet(kotlin.Comparator { a, b -> 23 | when { 24 | a.isSubtypeOf(b) -> -1 25 | b.isSubtypeOf(a) -> 1 26 | a == b -> 0 27 | else -> 1 28 | } 29 | }) 30 | set.add(getKType()) 31 | println(set) 32 | set.add(getKType()) 33 | println(set) 34 | set.add(getKType()) 35 | println(set) 36 | set.add(getKType()) 37 | println(set) 38 | set.add(getKType()) 39 | println(set) 40 | set.add(getKType()) 41 | println(set) 42 | } 43 | 44 | @Test 45 | fun testArraySubtypes() { 46 | assert(!getKType().isSubtypeOf(getKType>())) 47 | assert(!getKType>().isSubtypeOf(getKType>())) 48 | assert(getKType>().isSubtypeOf(getKType>())) 49 | assert(getKType>().isSubtypeOf(getKType>())) 50 | } 51 | 52 | @Test 53 | fun testMapSubtypes() { 54 | assert(!getKType>().isSubtypeOf(getKType>())) 55 | } 56 | 57 | enum class TestEnum { 58 | A, B, C 59 | } 60 | @Test 61 | fun testEnumSubtypes() { 62 | assert(getKType().isSubtypeOf(getKType>())) 63 | } 64 | 65 | 66 | lateinit var nothing: Nothing 67 | val nullableNothing: Nothing? = null 68 | 69 | val nothingType = ::nothing.returnType 70 | val nullNothingType = ::nullableNothing.returnType 71 | 72 | @Test 73 | fun testNothingSubtypes() { 74 | println(nothingType) 75 | println(nullNothingType) 76 | assert(nothingType.isSubtypeOf(nullNothingType)) 77 | assert(!nullNothingType.isSubtypeOf(nothingType)) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/kotlin/JwtAuthDocumentationGenerationTest.kt: -------------------------------------------------------------------------------- 1 | import TestServerWithJwtAuth.testServerWithJwtAuth 2 | import io.ktor.http.* 3 | import io.ktor.server.testing.* 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Assert.assertTrue 6 | import org.junit.Test 7 | 8 | internal class JwtAuthDocumentationGenerationTest { 9 | 10 | @Test 11 | fun testRequest() = withTestApplication({ 12 | testServerWithJwtAuth() 13 | }) { 14 | with(handleRequest(HttpMethod.Get, "//openapi.json")) { 15 | assertEquals(HttpStatusCode.OK, response.status()) 16 | assertTrue( 17 | response.content!!.contains( 18 | "\"securitySchemes\" : {\n" + 19 | " \"jwtAuth\" : {\n" + 20 | " \"bearerFormat\" : \"JWT\",\n" + 21 | " \"scheme\" : \"bearer\",\n" + 22 | " \"type\" : \"http\"\n" + 23 | " }\n" + 24 | " }" 25 | ) 26 | ) 27 | assertTrue( 28 | response.content!!.contains( 29 | "\"security\" : [ {\n" + 30 | " \"jwtAuth\" : [ ]\n" + 31 | " } ]" 32 | ) 33 | ) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/test/kotlin/TestUtil.kt: -------------------------------------------------------------------------------- 1 | import com.papsign.ktor.openapigen.OpenAPIGen 2 | import com.papsign.ktor.openapigen.schema.namer.DefaultSchemaNamer 3 | import com.papsign.ktor.openapigen.schema.namer.SchemaNamer 4 | import io.ktor.application.Application 5 | import io.ktor.application.install 6 | import io.ktor.features.ContentNegotiation 7 | import io.ktor.jackson.jackson 8 | import kotlin.reflect.KType 9 | 10 | fun Application.installOpenAPI(): OpenAPIGen { 11 | return install(OpenAPIGen) { 12 | // basic info 13 | info { 14 | version = "0.0.1" 15 | title = "Test API" 16 | description = "The Test API" 17 | contact { 18 | name = "Support" 19 | email = "support@test.com" 20 | } 21 | } 22 | // describe the server, add as many as you want 23 | server("http://localhost:8080/") { 24 | description = "Test server" 25 | } 26 | //optional 27 | replaceModule(DefaultSchemaNamer, object: SchemaNamer { 28 | val regex = Regex("[A-Za-z0-9_.]+") 29 | override fun get(type: KType): String { 30 | return type.toString().replace(regex) { it.value.split(".").last() }.replace(Regex(">|<|, "), "_") 31 | } 32 | }) 33 | } 34 | } 35 | 36 | fun Application.installJackson() { 37 | install(ContentNegotiation) { 38 | jackson() 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/test/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builder/query/form/ArrayBuilderTest.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builder.query.form 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.query.form.FormBuilderFactory 4 | import com.papsign.ktor.openapigen.parameters.parsers.testSelector 5 | import org.junit.Test 6 | 7 | class ArrayBuilderTest { 8 | 9 | @Test 10 | fun testFloatArray() { 11 | val key = "key" 12 | val expected = floatArrayOf(1f, 2f, 2.5f) 13 | val parse = mapOf( 14 | key to listOf("1,2,2.5") 15 | ) 16 | FormBuilderFactory.testSelector(expected, key, parse, false) { a, b -> a.contentEquals(b) } 17 | } 18 | 19 | @Test 20 | fun testNullableFloatArray() { 21 | val key = "key" 22 | val expected = arrayOf(1f, null, 2.5f) 23 | val parse = mapOf( 24 | key to listOf("1,,2.5") 25 | ) 26 | FormBuilderFactory.testSelector(expected, key, parse, false) { a, b -> a.contentEquals(b) } 27 | } 28 | 29 | @Test 30 | fun testFloatArrayExploded() { 31 | val key = "key" 32 | val expected = floatArrayOf(1f, 2f, 2.5f) 33 | val parse = mapOf( 34 | key to listOf("1","2","2.5") 35 | ) 36 | FormBuilderFactory.testSelector(expected, key, parse, true) { a, b -> a.contentEquals(b) } 37 | } 38 | 39 | @Test 40 | fun testNullableFloatArrayExploded() { 41 | val key = "key" 42 | val expected = arrayOf(1f, null, 2.5f) 43 | val parse = mapOf( 44 | key to listOf("1", "null", "2.5") 45 | ) 46 | FormBuilderFactory.testSelector(expected, key, parse, true) { a, b -> a.contentEquals(b) } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builder/query/form/EnumBuilderTest.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builder.query.form 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject.DeepBuilderFactory 4 | import com.papsign.ktor.openapigen.parameters.parsers.builders.query.form.FormBuilderFactory 5 | import com.papsign.ktor.openapigen.parameters.parsers.testSelector 6 | import org.junit.Test 7 | 8 | class EnumBuilderTest { 9 | 10 | enum class TestEnum { 11 | A, B, C 12 | } 13 | 14 | @Test 15 | fun testEnum() { 16 | val key = "key" 17 | val expected = TestEnum.B 18 | val parse = mapOf( 19 | key to listOf("B") 20 | ) 21 | FormBuilderFactory.testSelector(expected, key, parse, true) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builder/query/form/ListBuilderTest.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builder.query.form 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.query.form.FormBuilderFactory 4 | import com.papsign.ktor.openapigen.parameters.parsers.testSelector 5 | import org.junit.Test 6 | 7 | class ListBuilderTest { 8 | 9 | @Test 10 | fun testFloatList() { 11 | val key = "key" 12 | val expected = listOf(1f, 2f, 2.5f) 13 | val parse = mapOf( 14 | key to listOf("1,2,2.5") 15 | ) 16 | FormBuilderFactory.testSelector(expected, key, parse, false) 17 | } 18 | 19 | @Test 20 | fun testNullableFloatList() { 21 | val key = "key" 22 | val expected = listOf(1f, null, 2.5f) 23 | val parse = mapOf( 24 | key to listOf("1,null,2.5") 25 | ) 26 | FormBuilderFactory.testSelector(expected, key, parse, false) 27 | } 28 | 29 | @Test 30 | fun testFloatListExploded() { 31 | val key = "key" 32 | val expected = listOf(1f, 2f, 2.5f) 33 | val parse = mapOf( 34 | key to listOf("1","2","2.5") 35 | ) 36 | FormBuilderFactory.testSelector(expected, key, parse, true) 37 | } 38 | 39 | @Test 40 | fun testNullableFloatListExploded() { 41 | val key = "key" 42 | val expected = listOf(1f, null, 2.5f) 43 | val parse = mapOf( 44 | key to listOf("1","","2.5") 45 | ) 46 | FormBuilderFactory.testSelector(expected, key, parse, true) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builder/query/form/MapBuilderTest.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builder.query.form 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.query.form.FormBuilderFactory 4 | import com.papsign.ktor.openapigen.parameters.parsers.testSelector 5 | import org.junit.Test 6 | 7 | class MapBuilderTest { 8 | 9 | @Test 10 | fun testString() { 11 | val key = "key" 12 | val expected = mapOf( 13 | "test" to "test" 14 | ) 15 | val parse = mapOf( 16 | key to listOf("test,test") 17 | ) 18 | FormBuilderFactory.testSelector(expected, key, parse, false) 19 | } 20 | 21 | @Test 22 | fun testFloat() { 23 | val key = "key" 24 | val expected = mapOf( 25 | 12.5f to "test" 26 | ) 27 | val parse = mapOf( 28 | key to listOf("12.5,test") 29 | ) 30 | FormBuilderFactory.testSelector(expected, key, parse, false) 31 | } 32 | 33 | @Test 34 | fun testBoolean() { 35 | val key = "key" 36 | val expected = mapOf( 37 | true to "test" 38 | ) 39 | val parse = mapOf( 40 | key to listOf("true,test") 41 | ) 42 | FormBuilderFactory.testSelector(expected, key, parse, false) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builder/query/form/ObjectBuilderTest.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builder.query.form 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.builders.query.form.FormBuilderFactory 4 | import com.papsign.ktor.openapigen.parameters.parsers.testSelector 5 | import org.junit.Test 6 | 7 | class ObjectBuilderTest { 8 | 9 | data class TestClass1(val string: String) 10 | 11 | @Test 12 | fun test1() { 13 | val key = "key" 14 | val expected = TestClass1("test") 15 | val parse = mapOf( 16 | key to listOf("string,test") 17 | ) 18 | FormBuilderFactory.testSelector(expected, key, parse, false) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/deepobject/ArrayBuilderTest.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.testSelector 4 | import org.junit.Test 5 | 6 | class ArrayBuilderTest { 7 | 8 | @Test 9 | fun testFloatArray() { 10 | val key = "key" 11 | val expected = floatArrayOf(1f, 2f, 2.5f) 12 | val parse = mapOf( 13 | "$key[0]" to listOf("1"), 14 | "$key[1]" to listOf("2"), 15 | "$key[2]" to listOf("2.5") 16 | ) 17 | DeepBuilderFactory.testSelector(expected, key, parse, true) { a, b -> a.contentEquals(b) } 18 | } 19 | 20 | @Test 21 | fun testNullableFloatArray() { 22 | val key = "key" 23 | val expected = arrayOf(1f, null, 2.5f) 24 | val parse = mapOf( 25 | "$key[0]" to listOf("1"), 26 | "$key[2]" to listOf("2.5") 27 | ) 28 | DeepBuilderFactory.testSelector(expected, key, parse, true) { a, b -> a.contentEquals(b) } 29 | } 30 | 31 | data class Test1(val str: String) 32 | 33 | @Test 34 | fun testNullableObjectArray() { 35 | val key = "key" 36 | val expected = arrayOf(Test1("A"), Test1("B"), null) 37 | val parse = mapOf( 38 | "$key[0][str]" to listOf("A"), 39 | "$key[1][str]" to listOf("B"), 40 | "$key[2]" to listOf() 41 | ) 42 | DeepBuilderFactory.testSelector(expected, key, parse, true) { a, b -> a.contentEquals(b) } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/deepobject/EnumBuilderTest.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.testSelector 4 | import org.junit.Test 5 | 6 | class EnumBuilderTest { 7 | 8 | enum class TestEnum { 9 | A, B, C 10 | } 11 | 12 | @Test 13 | fun testEnum() { 14 | val key = "key" 15 | val expected = TestEnum.B 16 | val parse = mapOf( 17 | key to listOf("B") 18 | ) 19 | DeepBuilderFactory.testSelector(expected, key, parse, true) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/deepobject/ListBuilderTest.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.testSelector 4 | import org.junit.Test 5 | 6 | class ListBuilderTest { 7 | 8 | @Test 9 | fun testFloatList() { 10 | val key = "key" 11 | val expected = listOf(1f, 2f, 2.5f) 12 | val parse = mapOf( 13 | "$key[0]" to listOf("1"), 14 | "$key[1]" to listOf("2"), 15 | "$key[2]" to listOf("2.5") 16 | ) 17 | DeepBuilderFactory.testSelector(expected, key, parse, true) 18 | } 19 | 20 | @Test 21 | fun testNullableFloatList() { 22 | val key = "key" 23 | val expected = listOf(1f, null, 2.5f) 24 | val parse = mapOf( 25 | "$key[0]" to listOf("1"), 26 | "$key[2]" to listOf("2.5") 27 | ) 28 | DeepBuilderFactory.testSelector(expected, key, parse, true) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/deepobject/MapBuilderTest.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.testSelector 4 | import org.junit.Test 5 | 6 | class MapBuilderTest { 7 | 8 | @Test 9 | fun testString() { 10 | val key = "key" 11 | val expected = mapOf( 12 | "test" to "test" 13 | ) 14 | val parse = mapOf( 15 | "$key[test]" to listOf("test") 16 | ) 17 | DeepBuilderFactory.testSelector(expected, key, parse, true) 18 | } 19 | 20 | @Test 21 | fun testFloat() { 22 | val key = "key" 23 | val expected = mapOf( 24 | 12.5f to "test" 25 | ) 26 | val parse = mapOf( 27 | "$key[12.5]" to listOf("test") 28 | ) 29 | DeepBuilderFactory.testSelector(expected, key, parse, true) 30 | } 31 | 32 | @Test 33 | fun testBoolean() { 34 | val key = "key" 35 | val expected = mapOf( 36 | true to "test" 37 | ) 38 | val parse = mapOf( 39 | "$key[true]" to listOf("test") 40 | ) 41 | DeepBuilderFactory.testSelector(expected, key, parse, true) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/deepobject/ObjectBuilderTest.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.testSelector 4 | import org.junit.Test 5 | 6 | class ObjectBuilderTest { 7 | 8 | data class TestClass1(val string: String) 9 | 10 | @Test 11 | fun test1() { 12 | val key = "key" 13 | val expected = TestClass1("test") 14 | val parse = mapOf( 15 | "$key[string]" to listOf("test") 16 | ) 17 | DeepBuilderFactory.testSelector(expected, key, parse, true) 18 | } 19 | 20 | data class TestClass2(val nested: TestClass2Nested) 21 | data class TestClass2Nested(val string: String, val float: Float) 22 | 23 | @Test 24 | fun test2() { 25 | val key = "key" 26 | val expected = TestClass2(TestClass2Nested("test", 1f)) 27 | val parse = mapOf( 28 | "$key[nested][string]" to listOf("test"), 29 | "$key[nested][float]" to listOf("1") 30 | ) 31 | DeepBuilderFactory.testSelector(expected, key, parse, true) 32 | } 33 | 34 | 35 | data class TestClass3(val nested: Map) 36 | 37 | @Test 38 | fun test3() { 39 | val key = "key" 40 | val expected = TestClass3(mapOf(true to TestClass3(mapOf(false to TestClass3(mapOf()))))) 41 | val parse = mapOf>( 42 | "$key[nested][true][nested][false][string]" to listOf() 43 | ) 44 | DeepBuilderFactory.testSelector(expected, key, parse, true) 45 | } 46 | 47 | data class TestClass4(val nested: List) 48 | 49 | @Test 50 | fun test4() { 51 | val key = "key" 52 | val expected = TestClass4(listOf(TestClass4(listOf()))) 53 | val parse = mapOf>( 54 | "$key[nested][0][0][nested]" to listOf() 55 | ) 56 | DeepBuilderFactory.testSelector(expected, key, parse, true) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builders/query/deepobject/PrimitiveBuilderTest.kt: -------------------------------------------------------------------------------- 1 | package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject 2 | 3 | import com.papsign.ktor.openapigen.parameters.parsers.testSelector 4 | import org.junit.Test 5 | 6 | class PrimitiveBuilderTest { 7 | 8 | @Test 9 | fun testFloat() { 10 | val key = "key" 11 | val expected = 1f 12 | val parse = mapOf( 13 | key to listOf("1") 14 | ) 15 | DeepBuilderFactory.testSelector(expected, key, parse, true) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | DONE - add annotations for response and body description 2 | DONE - handle tags 3 | DONE - schema sanitizer 4 | DONE - handle error responses 5 | DONE - handle special content types 6 | DONE - handle security schemes 7 | - handle header only responses 8 | DONE - proper parameter serialisation https://swagger.io/docs/specification/serialization/ 9 | 10 | - handle other parameters: Cookie and Header 11 | --------------------------------------------------------------------------------