diff --git a/.github/workflows/pr_checks.yml b/.github/workflows/pr_checks.yml new file mode 100644 index 000000000..acbd5b453 --- /dev/null +++ b/.github/workflows/pr_checks.yml @@ -0,0 +1,37 @@ +name: Run PR Checks +on: pull_request +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.14 + uses: actions/setup-java@v1 + with: + java-version: 1.14 + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }} + restore-keys: ${{ runner.os }}-gradle + - name: Lint using detekt + run: ./gradlew detekt + unit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.14 + uses: actions/setup-java@v1 + with: + java-version: 1.14 + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }} + restore-keys: ${{ runner.os }}-gradle + - name: Assemble with Gradle + run: ./gradlew assemble + - name: Run Unit Tests + run: ./gradlew test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..a7f4bf55b --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,22 @@ +name: Publish to GitHub Packages +on: + push: + branches: [ main ] +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: 1.14 + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }} + restore-keys: ${{ runner.os }}-gradle + - name: Publish package + run: ./gradlew publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 1b6985c00..1f93dfcff 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ # Ignore Gradle build output directory build + +.idea diff --git a/build.gradle.kts b/build.gradle.kts index 29d10a159..e5d1c20f9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,6 +16,7 @@ allprojects { repositories { mavenCentral() + maven { url = uri("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") } } apply(plugin = "org.jetbrains.kotlin.jvm") diff --git a/detekt.yml b/detekt.yml index 7cd298420..0c6680013 100644 --- a/detekt.yml +++ b/detekt.yml @@ -361,7 +361,7 @@ naming: excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] classPattern: '[A-Z][a-zA-Z0-9]*' ConstructorParameterNaming: - active: true + active: false excludes: ['**/test/**', '**/testIntegration/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] parameterPattern: '[a-z][A-Za-z0-9]*' privateParameterPattern: '[a-z][A-Za-z0-9]*' diff --git a/kompendium/build.gradle.kts b/kompendium/build.gradle.kts index 839d16d05..665d3ea55 100644 --- a/kompendium/build.gradle.kts +++ b/kompendium/build.gradle.kts @@ -10,3 +10,21 @@ dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test-junit") testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.0") } + +publishing { + repositories { + maven { + name = "GithubPackages" + url = uri("https://maven.pkg.github.com/lg-backbone/kompendium") + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } + publications { + create("kompendium") { + from(components["kotlin"]) + } + } +} diff --git a/kompendium/src/main/kotlin/kompendium/Library.kt b/kompendium/src/main/kotlin/kompendium/Library.kt deleted file mode 100644 index 6eefd3560..000000000 --- a/kompendium/src/main/kotlin/kompendium/Library.kt +++ /dev/null @@ -1,10 +0,0 @@ -/* - * This Kotlin source file was generated by the Gradle 'init' task. - */ -package kompendium - -class Library { - fun someLibraryMethod(): String { - return "Heya" - } -} diff --git a/kompendium/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt b/kompendium/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt index c0ff31312..88ddef1ea 100644 --- a/kompendium/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt +++ b/kompendium/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt @@ -1,6 +1,5 @@ package org.leafygreens.kompendium -import java.net.URI import org.leafygreens.kompendium.models.OpenApiSpec class Kompendium { diff --git a/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpec.kt b/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpec.kt index 79762f081..b3d170ee0 100644 --- a/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpec.kt +++ b/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpec.kt @@ -3,10 +3,12 @@ package org.leafygreens.kompendium.models data class OpenApiSpec( val openapi: String = "3.0.3", val info: OpenApiSpecInfo? = null, - val servers: List = emptyList(), // TODO Needs to default to server object with url of `/` - val paths: Map = emptyMap(), + // TODO Needs to default to server object with url of `/` + val servers: List? = null, + val paths: Map? = null, val components: OpenApiSpecComponents? = null, - val security: Map = emptyMap(), // todo needs to reference objects in the components -> security scheme 🤔 - val tags: List = emptyList(), + // todo needs to reference objects in the components -> security scheme 🤔 + val security: List>>? = null, + val tags: List? = null, val externalDocs: OpenApiSpecExternalDocumentation? = null ) diff --git a/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecComponents.kt b/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecComponents.kt index ec6967eff..dea3d65d3 100644 --- a/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecComponents.kt +++ b/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecComponents.kt @@ -2,5 +2,5 @@ package org.leafygreens.kompendium.models // TODO I *think* the only thing I need here is the security https://swagger.io/specification/#components-object data class OpenApiSpecComponents( - val securitySchemes: Map + val securitySchemes: Map ) diff --git a/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecMediaType.kt b/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecMediaType.kt index b73369226..a413eebc1 100644 --- a/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecMediaType.kt +++ b/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecMediaType.kt @@ -2,7 +2,7 @@ package org.leafygreens.kompendium.models // TODO Oof -> https://swagger.io/specification/#media-type-object data class OpenApiSpecMediaType( - val schema: OpenApiSpecReferencable, // TODO sheesh -> https://swagger.io/specification/#schema-object needs to be referencable + val schema: OpenApiSpecSchema, // TODO sheesh -> https://swagger.io/specification/#schema-object val example: String? = null, // TODO Enforce type? then serialize? val examples: Map? = null, // needs to be mutually exclusive with example val encoding: Map? = null // todo encoding object -> https://swagger.io/specification/#encoding-object diff --git a/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecOAuthFlow.kt b/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecOAuthFlow.kt index abe261689..4e7820f32 100644 --- a/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecOAuthFlow.kt +++ b/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecOAuthFlow.kt @@ -3,8 +3,8 @@ package org.leafygreens.kompendium.models import java.net.URI data class OpenApiSpecOAuthFlow( - val authorizationUrl: URI, - val tokenUrl: URI, - val refreshUrl: URI, - val scopes: Map + val authorizationUrl: URI? = null, + val tokenUrl: URI? = null, + val refreshUrl: URI? = null, + val scopes: Map? = null ) diff --git a/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecPathItemOperation.kt b/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecPathItemOperation.kt index 8c6af4193..dfb04aad5 100644 --- a/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecPathItemOperation.kt +++ b/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecPathItemOperation.kt @@ -8,10 +8,12 @@ data class OpenApiSpecPathItemOperation( val operationId: String? = null, val parameters: List? = null, val requestBody: OpenApiSpecReferencable? = null, - val responses: Map? = null, // TODO How to enforce `default` requirement + // TODO How to enforce `default` requirement 🧐 + val responses: Map? = null, val callbacks: Map? = null, val deprecated: Boolean = false, - val security: List>>? = null, // todo big yikes... also needs to reference objects in the security scheme 🤔 + // todo big yikes... also needs to reference objects in the security scheme 🤔 + val security: List>>? = null, val servers: List? = null, val `x-codegen-request-body-name`: String? = null ) diff --git a/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecReferencable.kt b/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecReferencable.kt index 206a3ec99..07a1385e8 100644 --- a/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecReferencable.kt +++ b/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecReferencable.kt @@ -27,7 +27,10 @@ data class OpenApiSpecParameter( val description: String?, val required: Boolean = true, val deprecated: Boolean = false, - val allowEmptyValue: Boolean = false + val allowEmptyValue: Boolean = false, + val style: String? = null, + val explode: Boolean? = false, + val schema: OpenApiSpecSchema? = null ) : OpenApiSpecReferencable() data class OpenApiSpecRequest( diff --git a/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecSchema.kt b/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecSchema.kt new file mode 100644 index 000000000..e660f154a --- /dev/null +++ b/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecSchema.kt @@ -0,0 +1,31 @@ +package org.leafygreens.kompendium.models + +sealed class OpenApiSpecSchema + +sealed class OpenApiSpecSchemaTyped( + val type: String, +) : OpenApiSpecSchema() + +data class OpenApiSpecSchemaArray( + val items: T +) : OpenApiSpecSchemaTyped("array") + +data class OpenApiSpecSchemaString( + val default: String, + val `enum`: Set? = null +) : OpenApiSpecSchemaTyped("string") + +// TODO In Kt 1.5 Should be able to reference external sealed classes +data class OpenApiSpecSchemaRef( + val `$ref`: String +) : OpenApiSpecSchema() + +data class OpenApiSpecSchemaSecurity( + val type: String? = null, // TODO Enum? "apiKey", "http", "oauth2", "openIdConnect" + val name: String? = null, + val `in`: String? = null, + val scheme: String? = null, + val flows: OpenApiSpecOAuthFlows? = null, + val bearerFormat: String? = null, + val description: String? = null, +) : OpenApiSpecSchema() diff --git a/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecSecuritySchema.kt b/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecSecuritySchema.kt deleted file mode 100644 index 85588b3b7..000000000 --- a/kompendium/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecSecuritySchema.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.leafygreens.kompendium.models - -data class OpenApiSpecSecuritySchema( - val type: String, // TODO Enum? "apiKey", "http", "oauth2", "openIdConnect" - val name: String, - val `in`: String, - val scheme: String, - val flows: OpenApiSpecOAuthFlows, - val bearerFormat: String?, - val description: String?, -) - diff --git a/kompendium/src/test/kotlin/kompendium/LibraryTest.kt b/kompendium/src/test/kotlin/kompendium/LibraryTest.kt deleted file mode 100644 index 0aff026b5..000000000 --- a/kompendium/src/test/kotlin/kompendium/LibraryTest.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* - * This Kotlin source file was generated by the Gradle 'init' task. - */ -package kompendium - -import kotlin.test.Test -import kotlin.test.assertEquals - -class LibraryTest { - @Test fun testSomeLibraryMethod() { - val classUnderTest = Library() - assertEquals(classUnderTest.someLibraryMethod(), "Heya", "someLibraryMethod should return 'true'") - } -} diff --git a/kompendium/src/test/kotlin/org/leafygreens/kompendium/util/TestData.kt b/kompendium/src/test/kotlin/org/leafygreens/kompendium/util/TestData.kt index 2741a47dd..7cf5f5aa6 100644 --- a/kompendium/src/test/kotlin/org/leafygreens/kompendium/util/TestData.kt +++ b/kompendium/src/test/kotlin/org/leafygreens/kompendium/util/TestData.kt @@ -3,16 +3,23 @@ package org.leafygreens.kompendium.util import java.io.File import java.net.URI import org.leafygreens.kompendium.models.OpenApiSpec +import org.leafygreens.kompendium.models.OpenApiSpecComponents import org.leafygreens.kompendium.models.OpenApiSpecExternalDocumentation import org.leafygreens.kompendium.models.OpenApiSpecInfo import org.leafygreens.kompendium.models.OpenApiSpecInfoContact import org.leafygreens.kompendium.models.OpenApiSpecInfoLicense import org.leafygreens.kompendium.models.OpenApiSpecMediaType +import org.leafygreens.kompendium.models.OpenApiSpecOAuthFlow +import org.leafygreens.kompendium.models.OpenApiSpecOAuthFlows +import org.leafygreens.kompendium.models.OpenApiSpecParameter import org.leafygreens.kompendium.models.OpenApiSpecPathItem import org.leafygreens.kompendium.models.OpenApiSpecPathItemOperation -import org.leafygreens.kompendium.models.OpenApiSpecReferenceObject import org.leafygreens.kompendium.models.OpenApiSpecRequest import org.leafygreens.kompendium.models.OpenApiSpecResponse +import org.leafygreens.kompendium.models.OpenApiSpecSchemaArray +import org.leafygreens.kompendium.models.OpenApiSpecSchemaRef +import org.leafygreens.kompendium.models.OpenApiSpecSchemaSecurity +import org.leafygreens.kompendium.models.OpenApiSpecSchemaString import org.leafygreens.kompendium.models.OpenApiSpecServer import org.leafygreens.kompendium.models.OpenApiSpecTag @@ -26,7 +33,11 @@ object TestData { val testSpec = OpenApiSpec( info = OpenApiSpecInfo( title = "Swagger Petstore", - description = "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", + description = """ + This is a sample server Petstore server. You can find out more about Swagger at + [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). + For this sample, you can use the api key `special-key` to test the authorization filters. + """.trimIndent(), termsOfService = URI("http://swagger.io/terms/"), contact = OpenApiSpecInfoContact( name = "Team Swag", @@ -82,10 +93,10 @@ object TestData { description = "Pet object that needs to be added to the store", content = mapOf( "application/json" to OpenApiSpecMediaType( - schema = OpenApiSpecReferenceObject(`$ref` = "#/components/schemas/Pet") + schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet") ), "application/xml" to OpenApiSpecMediaType( - schema = OpenApiSpecReferenceObject(`$ref` = "#/components/schemas/Pet") + schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet") ) ), required = true @@ -119,10 +130,10 @@ object TestData { description = "Pet object that needs to be added to the store", content = mapOf( "application/json" to OpenApiSpecMediaType( - schema = OpenApiSpecReferenceObject(`$ref` = "#/components/schemas/Pet") + schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet") ), "application/xml" to OpenApiSpecMediaType( - schema = OpenApiSpecReferenceObject(`$ref` = "#/components/schemas/Pet") + schema = OpenApiSpecSchemaRef(`$ref` = "#/components/schemas/Pet") ) ) ), @@ -139,6 +150,75 @@ object TestData { ), `x-codegen-request-body-name` = "body" ) + ), + "/pet/findByStatus" to OpenApiSpecPathItem( + get = OpenApiSpecPathItemOperation( + tags = setOf("pet"), + summary = "Find Pets by status", + description = "Multiple status values can be provided with comma separated strings", + operationId = "findPetsByStatus", + parameters = listOf( + OpenApiSpecParameter( + name = "status", + `in` = "query", + description = "Status values that need to be considered for filter", + required = true, + style = "form", + explode = true, + schema = OpenApiSpecSchemaArray( + items = OpenApiSpecSchemaString( + default = "available", + `enum` = setOf("available", "pending", "sold") + ) + ) + ) + ), + responses = mapOf( + "200" to OpenApiSpecResponse( + description = "successful operation", + content = mapOf( + "application/xml" to OpenApiSpecMediaType( + schema = OpenApiSpecSchemaArray( + items = OpenApiSpecSchemaRef("#/components/schemas/Pet") + ) + ), + "application/json" to OpenApiSpecMediaType( + schema = OpenApiSpecSchemaArray( + items = OpenApiSpecSchemaRef("#/components/schemas/Pet") + ) + ) + ) + ), + "400" to OpenApiSpecResponse( + description = "Invalid status value", + content = mapOf() + ) + ), + security = listOf(mapOf( + "petstore_auth" to listOf("write:pets", "read:pets") + )) + ) + ) + ), + components = OpenApiSpecComponents( + securitySchemes = mapOf( + "petstore_auth" to OpenApiSpecSchemaSecurity( + type = "oauth2", + flows = OpenApiSpecOAuthFlows( + implicit = OpenApiSpecOAuthFlow( + authorizationUrl = URI("http://petstore.swagger.io/oauth/dialog"), + scopes = mapOf( + "write:pets" to "modify pets in your account", + "read:pets" to "read your pets" + ) + ) + ) + ), + "api_key" to OpenApiSpecSchemaSecurity( + type = "apiKey", + name = "api_key", + `in` = "header" + ) ) ) ) diff --git a/kompendium/src/test/resources/petstore.json b/kompendium/src/test/resources/petstore.json index 9ef6c103d..520560bcf 100644 --- a/kompendium/src/test/resources/petstore.json +++ b/kompendium/src/test/resources/petstore.json @@ -3,7 +3,7 @@ "info" : { "title" : "Swagger Petstore", "version" : "1.0.0", - "description" : "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", + "description" : "This is a sample server Petstore server. You can find out more about Swagger at\n[http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).\nFor this sample, you can use the api key `special-key` to test the authorization filters.", "termsOfService" : "http://swagger.io/terms/", "contact" : { "name" : "Team Swag", @@ -93,9 +93,86 @@ } ], "x-codegen-request-body-name" : "body" } + }, + "/pet/findByStatus" : { + "get" : { + "tags" : [ "pet" ], + "summary" : "Find Pets by status", + "description" : "Multiple status values can be provided with comma separated strings", + "operationId" : "findPetsByStatus", + "parameters" : [ { + "name" : "status", + "in" : "query", + "description" : "Status values that need to be considered for filter", + "required" : true, + "deprecated" : false, + "allowEmptyValue" : false, + "style" : "form", + "explode" : true, + "schema" : { + "items" : { + "default" : "available", + "enum" : [ "available", "pending", "sold" ], + "type" : "string" + }, + "type" : "array" + } + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/xml" : { + "schema" : { + "items" : { + "$ref" : "#/components/schemas/Pet" + }, + "type" : "array" + } + }, + "application/json" : { + "schema" : { + "items" : { + "$ref" : "#/components/schemas/Pet" + }, + "type" : "array" + } + } + } + }, + "400" : { + "description" : "Invalid status value", + "content" : { } + } + }, + "deprecated" : false, + "security" : [ { + "petstore_auth" : [ "write:pets", "read:pets" ] + } ] + } + } + }, + "components" : { + "securitySchemes" : { + "petstore_auth" : { + "type" : "oauth2", + "flows" : { + "implicit" : { + "authorizationUrl" : "http://petstore.swagger.io/oauth/dialog", + "scopes" : { + "write:pets" : "modify pets in your account", + "read:pets" : "read your pets" + } + } + } + }, + "api_key" : { + "type" : "apiKey", + "name" : "api_key", + "in" : "header" + } } }, - "security" : { }, "tags" : [ { "name" : "pet", "description" : "Everything about your Pets", diff --git a/playground/src/main/kotlin/org/leafygreens/kompendium/playground/Main.kt b/playground/src/main/kotlin/org/leafygreens/kompendium/playground/Main.kt index 66128ac96..733a8cfd7 100644 --- a/playground/src/main/kotlin/org/leafygreens/kompendium/playground/Main.kt +++ b/playground/src/main/kotlin/org/leafygreens/kompendium/playground/Main.kt @@ -8,7 +8,6 @@ import io.ktor.routing.route import io.ktor.routing.routing import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty -import kompendium.Library fun main() { embeddedServer( @@ -22,7 +21,7 @@ fun Application.mainModule() { routing { route("/") { get { - call.respondText(Library().someLibraryMethod()) + call.respondText("hi") } } }