chore: test all serializers (#180)

This commit is contained in:
Ryan Brink
2022-02-08 13:07:53 -05:00
committed by GitHub
parent d9cde5b0d8
commit 99590563fc
28 changed files with 229 additions and 137 deletions

View File

@ -6,6 +6,8 @@
### Changed ### Changed
- Cleaned up and broke out handlers into separate classes - Cleaned up and broke out handlers into separate classes
- Serializer cleanup
- Tests now run against Jackson, Gson and kotlinx on every run
### Remove ### Remove

View File

@ -8,7 +8,7 @@ import io.bkbn.kompendium.auth.util.configBasicAuth
import io.bkbn.kompendium.auth.util.configJwtAuth import io.bkbn.kompendium.auth.util.configJwtAuth
import io.bkbn.kompendium.auth.util.notarizedAuthRoute import io.bkbn.kompendium.auth.util.notarizedAuthRoute
import io.bkbn.kompendium.auth.util.setupOauth import io.bkbn.kompendium.auth.util.setupOauth
import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTest import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
import io.bkbn.kompendium.oas.security.OAuth import io.bkbn.kompendium.oas.security.OAuth
import io.kotest.core.spec.style.DescribeSpec import io.kotest.core.spec.style.DescribeSpec
@ -21,7 +21,7 @@ class KompendiumAuthTest : DescribeSpec({
} }
// act // act
openApiTest("notarized_basic_authenticated_get.json") { openApiTestAllSerializers("notarized_basic_authenticated_get.json") {
configBasicAuth() configBasicAuth()
notarizedAuthRoute(authConfig) notarizedAuthRoute(authConfig)
} }
@ -35,7 +35,7 @@ class KompendiumAuthTest : DescribeSpec({
} }
// act // act
openApiTest("notarized_jwt_authenticated_get.json") { openApiTestAllSerializers("notarized_jwt_authenticated_get.json") {
configJwtAuth() configJwtAuth()
notarizedAuthRoute(authConfig) notarizedAuthRoute(authConfig)
} }
@ -60,7 +60,7 @@ class KompendiumAuthTest : DescribeSpec({
} }
// act // act
openApiTest("notarized_oauth_all_flows.json") { openApiTestAllSerializers("notarized_oauth_all_flows.json") {
setupOauth() setupOauth()
notarizedAuthRoute(authConfig) notarizedAuthRoute(authConfig)
} }

View File

@ -21,6 +21,14 @@ The downside is that issues could exist in serialization frameworks that have no
Gson and KotlinX serialization have all been tested. If you run into any serialization issues, particularly with a Gson and KotlinX serialization have all been tested. If you run into any serialization issues, particularly with a
serializer not listed above, please open an issue on GitHub 🙏 serializer not listed above, please open an issue on GitHub 🙏
Note for Kotlinx ⚠️
You will need to include the `SerializersModule` provided in `KompendiumSerializersModule` in order to serialize
any provided defaults. This comes down to how Kotlinx expects users to handle serializing `Any`. Essentially, this
serializer module will convert any `Any` serialization to be `Contextual`. This is pretty hacky, but seemed to be the
only way to get Kotlinx to play nice with serializing `Any`. If you come up with a better solution, definitely go ahead
and open up a PR!
## Notarization ## Notarization
Central to Kompendium is the concept of notarization. Central to Kompendium is the concept of notarization.

View File

@ -1,5 +1,6 @@
plugins { plugins {
kotlin("jvm") kotlin("jvm")
kotlin("plugin.serialization")
id("io.bkbn.sourdough.library.jvm") id("io.bkbn.sourdough.library.jvm")
id("io.gitlab.arturbosch.detekt") id("io.gitlab.arturbosch.detekt")
id("com.adarshr.test-logger") id("com.adarshr.test-logger")
@ -40,6 +41,7 @@ dependencies {
testFixturesApi(group = "io.ktor", name = "ktor-server-core", version = ktorVersion) testFixturesApi(group = "io.ktor", name = "ktor-server-core", version = ktorVersion)
testFixturesApi(group = "io.ktor", name = "ktor-server-test-host", version = ktorVersion) testFixturesApi(group = "io.ktor", name = "ktor-server-test-host", version = ktorVersion)
testFixturesApi(group = "io.ktor", name = "ktor-jackson", version = ktorVersion) testFixturesApi(group = "io.ktor", name = "ktor-jackson", version = ktorVersion)
testFixturesApi(group = "io.ktor", name = "ktor-gson", version = ktorVersion)
testFixturesApi(group = "io.ktor", name = "ktor-serialization", version = ktorVersion) testFixturesApi(group = "io.ktor", name = "ktor-serialization", version = ktorVersion)
testFixturesApi(group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version = "1.3.2") testFixturesApi(group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version = "1.3.2")

View File

@ -2,7 +2,7 @@ package io.bkbn.kompendium.core
import io.bkbn.kompendium.core.fixtures.TestHelpers.apiFunctionalityTest import io.bkbn.kompendium.core.fixtures.TestHelpers.apiFunctionalityTest
import io.bkbn.kompendium.core.fixtures.TestHelpers.getFileSnapshot import io.bkbn.kompendium.core.fixtures.TestHelpers.getFileSnapshot
import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTest import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
import io.bkbn.kompendium.core.util.complexType import io.bkbn.kompendium.core.util.complexType
import io.bkbn.kompendium.core.util.constrainedDoubleInfo import io.bkbn.kompendium.core.util.constrainedDoubleInfo
import io.bkbn.kompendium.core.util.constrainedIntInfo import io.bkbn.kompendium.core.util.constrainedIntInfo
@ -60,37 +60,37 @@ import io.ktor.http.HttpStatusCode
class KompendiumTest : DescribeSpec({ class KompendiumTest : DescribeSpec({
describe("Notarized Open API Metadata Tests") { describe("Notarized Open API Metadata Tests") {
it("Can notarize a get request") { it("Can notarize a get request") {
openApiTest("notarized_get.json") { notarizedGetModule() } openApiTestAllSerializers("notarized_get.json") { notarizedGetModule() }
} }
it("Can notarize a post request") { it("Can notarize a post request") {
openApiTest("notarized_post.json") { notarizedPostModule() } openApiTestAllSerializers("notarized_post.json") { notarizedPostModule() }
} }
it("Can notarize a put request") { it("Can notarize a put request") {
openApiTest("notarized_put.json") { notarizedPutModule() } openApiTestAllSerializers("notarized_put.json") { notarizedPutModule() }
} }
it("Can notarize a delete request") { it("Can notarize a delete request") {
openApiTest("notarized_delete.json") { notarizedDeleteModule() } openApiTestAllSerializers("notarized_delete.json") { notarizedDeleteModule() }
} }
it("Can notarize a patch request") { it("Can notarize a patch request") {
openApiTest("notarized_patch.json") { notarizedPatchModule() } openApiTestAllSerializers("notarized_patch.json") { notarizedPatchModule() }
} }
it("Can notarize a head request") { it("Can notarize a head request") {
openApiTest("notarized_head.json") { notarizedHeadModule() } openApiTestAllSerializers("notarized_head.json") { notarizedHeadModule() }
} }
it("Can notarize an options request") { it("Can notarize an options request") {
openApiTest("notarized_options.json") { notarizedOptionsModule() } openApiTestAllSerializers("notarized_options.json") { notarizedOptionsModule() }
} }
it("Can notarize a complex type") { it("Can notarize a complex type") {
openApiTest("complex_type.json") { complexType() } openApiTestAllSerializers("complex_type.json") { complexType() }
} }
it("Can notarize primitives") { it("Can notarize primitives") {
openApiTest("notarized_primitives.json") { primitives() } openApiTestAllSerializers("notarized_primitives.json") { primitives() }
} }
it("Can notarize a top level list response") { it("Can notarize a top level list response") {
openApiTest("response_list.json") { returnsList() } openApiTestAllSerializers("response_list.json") { returnsList() }
} }
it("Can notarize a route with non-required params") { it("Can notarize a route with non-required params") {
openApiTest("non_required_params.json") { nonRequiredParamsGet() } openApiTestAllSerializers("non_required_params.json") { nonRequiredParamsGet() }
} }
} }
describe("Notarized Ktor Functionality Tests") { describe("Notarized Ktor Functionality Tests") {
@ -122,80 +122,80 @@ class KompendiumTest : DescribeSpec({
} }
describe("Route Parsing") { describe("Route Parsing") {
it("Can parse a simple path and store it under the expected route") { it("Can parse a simple path and store it under the expected route") {
openApiTest("path_parser.json") { pathParsingTestModule() } openApiTestAllSerializers("path_parser.json") { pathParsingTestModule() }
} }
it("Can notarize the root route") { it("Can notarize the root route") {
openApiTest("root_route.json") { rootModule() } openApiTestAllSerializers("root_route.json") { rootModule() }
} }
it("Can notarize a route under the root module without appending trailing slash") { it("Can notarize a route under the root module without appending trailing slash") {
openApiTest("nested_under_root.json") { nestedUnderRootModule() } openApiTestAllSerializers("nested_under_root.json") { nestedUnderRootModule() }
} }
it("Can notarize a route with a trailing slash") { it("Can notarize a route with a trailing slash") {
openApiTest("trailing_slash.json") { trailingSlash() } openApiTestAllSerializers("trailing_slash.json") { trailingSlash() }
} }
} }
describe("Exceptions") { describe("Exceptions") {
it("Can add an exception status code to a response") { it("Can add an exception status code to a response") {
openApiTest("notarized_get_with_exception_response.json") { notarizedGetWithNotarizedException() } openApiTestAllSerializers("notarized_get_with_exception_response.json") { notarizedGetWithNotarizedException() }
} }
it("Can support multiple response codes") { it("Can support multiple response codes") {
openApiTest("notarized_get_with_multiple_exception_responses.json") { notarizedGetWithMultipleThrowables() } openApiTestAllSerializers("notarized_get_with_multiple_exception_responses.json") { notarizedGetWithMultipleThrowables() }
} }
it("Can add a polymorphic exception response") { it("Can add a polymorphic exception response") {
openApiTest("polymorphic_error_status_codes.json") { notarizedGetWithPolymorphicErrorResponse() } openApiTestAllSerializers("polymorphic_error_status_codes.json") { notarizedGetWithPolymorphicErrorResponse() }
} }
it("Can add a generic exception response") { it("Can add a generic exception response") {
openApiTest("generic_exception.json") { notarizedGetWithGenericErrorResponse() } openApiTestAllSerializers("generic_exception.json") { notarizedGetWithGenericErrorResponse() }
} }
} }
describe("Examples") { describe("Examples") {
it("Can generate example response and request bodies") { it("Can generate example response and request bodies") {
openApiTest("example_req_and_resp.json") { withExamples() } openApiTestAllSerializers("example_req_and_resp.json") { withExamples() }
} }
it("Can describe example parameters") { it("Can describe example parameters") {
openApiTest("example_parameters.json") { exampleParams() } openApiTestAllSerializers("example_parameters.json") { exampleParams() }
} }
} }
describe("Defaults") { describe("Defaults") {
it("Can generate a default parameter values") { it("Can generate a default parameter values") {
openApiTest("query_with_default_parameter.json") { withDefaultParameter() } openApiTestAllSerializers("query_with_default_parameter.json") { withDefaultParameter() }
} }
} }
describe("Required Fields") { describe("Required Fields") {
it("Marks a parameter required if there is no default and it is not marked nullable") { it("Marks a parameter required if there is no default and it is not marked nullable") {
openApiTest("required_param.json") { requiredParameter() } openApiTestAllSerializers("required_param.json") { requiredParameter() }
} }
it("Does not mark a parameter as required if a default value is provided") { it("Does not mark a parameter as required if a default value is provided") {
openApiTest("default_param.json") { defaultParameter() } openApiTestAllSerializers("default_param.json") { defaultParameter() }
} }
it("Does not mark a field as required if a default value is provided") { it("Does not mark a field as required if a default value is provided") {
openApiTest("default_field.json") { defaultField() } openApiTestAllSerializers("default_field.json") { defaultField() }
} }
it("Marks a field as nullable when expected") { it("Marks a field as nullable when expected") {
openApiTest("nullable_field.json") { nullableField() } openApiTestAllSerializers("nullable_field.json") { nullableField() }
} }
} }
describe("Polymorphism and Generics") { describe("Polymorphism and Generics") {
it("can generate a polymorphic response type") { it("can generate a polymorphic response type") {
openApiTest("polymorphic_response.json") { polymorphicResponse() } openApiTestAllSerializers("polymorphic_response.json") { polymorphicResponse() }
} }
it("Can generate a collection with polymorphic response type") { it("Can generate a collection with polymorphic response type") {
openApiTest("polymorphic_list_response.json") { polymorphicCollectionResponse() } openApiTestAllSerializers("polymorphic_list_response.json") { polymorphicCollectionResponse() }
} }
it("Can generate a map with a polymorphic response type") { it("Can generate a map with a polymorphic response type") {
openApiTest("polymorphic_map_response.json") { polymorphicMapResponse() } openApiTestAllSerializers("polymorphic_map_response.json") { polymorphicMapResponse() }
} }
it("Can generate a polymorphic response from a sealed interface") { it("Can generate a polymorphic response from a sealed interface") {
openApiTest("sealed_interface_response.json") { polymorphicInterfaceResponse() } openApiTestAllSerializers("sealed_interface_response.json") { polymorphicInterfaceResponse() }
} }
it("Can generate a response type with a generic type") { it("Can generate a response type with a generic type") {
openApiTest("generic_response.json") { simpleGenericResponse() } openApiTestAllSerializers("generic_response.json") { simpleGenericResponse() }
} }
it("Can generate a polymorphic response type with generics") { it("Can generate a polymorphic response type with generics") {
openApiTest("polymorphic_response_with_generics.json") { genericPolymorphicResponse() } openApiTestAllSerializers("polymorphic_response_with_generics.json") { genericPolymorphicResponse() }
} }
it("Can handle an absolutely psycho inheritance test") { it("Can handle an absolutely psycho inheritance test") {
openApiTest("crazy_polymorphic_example.json") { genericPolymorphicResponseMultipleImpls() } openApiTestAllSerializers("crazy_polymorphic_example.json") { genericPolymorphicResponseMultipleImpls() }
} }
} }
describe("Miscellaneous") { describe("Miscellaneous") {
@ -203,59 +203,59 @@ class KompendiumTest : DescribeSpec({
apiFunctionalityTest(getFileSnapshot("redoc.html"), "/docs") { returnsList() } apiFunctionalityTest(getFileSnapshot("redoc.html"), "/docs") { returnsList() }
} }
it("Can add an operation id to a notarized route") { it("Can add an operation id to a notarized route") {
openApiTest("notarized_get_with_operation_id.json") { withOperationId() } openApiTestAllSerializers("notarized_get_with_operation_id.json") { withOperationId() }
} }
it("Can add an undeclared field") { it("Can add an undeclared field") {
openApiTest("undeclared_field.json") { undeclaredType() } openApiTestAllSerializers("undeclared_field.json") { undeclaredType() }
} }
it("Can add a custom header parameter with a name override") { it("Can add a custom header parameter with a name override") {
openApiTest("override_parameter_name.json") { headerParameter() } openApiTestAllSerializers("override_parameter_name.json") { headerParameter() }
} }
it("Can override field values via annotation") { it("Can override field values via annotation") {
openApiTest("field_override.json") { overrideFieldInfo() } openApiTestAllSerializers("field_override.json") { overrideFieldInfo() }
} }
it("Can serialize a recursive type using references") { it("Can serialize a recursive type using references") {
openApiTest("simple_recursive.json") { simpleRecursive() } openApiTestAllSerializers("simple_recursive.json") { simpleRecursive() }
} }
} }
describe("Constraints") { describe("Constraints") {
it("Can set a minimum and maximum integer value") { it("Can set a minimum and maximum integer value") {
openApiTest("min_max_int_field.json") { constrainedIntInfo() } openApiTestAllSerializers("min_max_int_field.json") { constrainedIntInfo() }
} }
it("Can set a minimum and maximum double value") { it("Can set a minimum and maximum double value") {
openApiTest("min_max_double_field.json") { constrainedDoubleInfo() } openApiTestAllSerializers("min_max_double_field.json") { constrainedDoubleInfo() }
} }
it("Can set an exclusive min and exclusive max integer value") { it("Can set an exclusive min and exclusive max integer value") {
openApiTest("exclusive_min_max.json") { exclusiveMinMax() } openApiTestAllSerializers("exclusive_min_max.json") { exclusiveMinMax() }
} }
it("Can add a custom format to a string field") { it("Can add a custom format to a string field") {
openApiTest("formatted_param_type.json") { formattedParam() } openApiTestAllSerializers("formatted_param_type.json") { formattedParam() }
} }
it("Can set a minimum and maximum length on a string field") { it("Can set a minimum and maximum length on a string field") {
openApiTest("min_max_string.json") { minMaxString() } openApiTestAllSerializers("min_max_string.json") { minMaxString() }
} }
it("Can set a custom regex pattern on a string field") { it("Can set a custom regex pattern on a string field") {
openApiTest("regex_string.json") { regexString() } openApiTestAllSerializers("regex_string.json") { regexString() }
} }
it("Can set a minimum and maximum item count on an array field") { it("Can set a minimum and maximum item count on an array field") {
openApiTest("min_max_array.json") { minMaxArray() } openApiTestAllSerializers("min_max_array.json") { minMaxArray() }
} }
it("Can set a unique items constraint on an array field") { it("Can set a unique items constraint on an array field") {
openApiTest("unique_array.json") { uniqueArray() } openApiTestAllSerializers("unique_array.json") { uniqueArray() }
} }
it("Can set a multiple-of constraint on an int field") { it("Can set a multiple-of constraint on an int field") {
openApiTest("multiple_of_int.json") { multipleOfInt() } openApiTestAllSerializers("multiple_of_int.json") { multipleOfInt() }
} }
it("Can set a multiple of constraint on an double field") { it("Can set a multiple of constraint on an double field") {
openApiTest("multiple_of_double.json") { multipleOfDouble() } openApiTestAllSerializers("multiple_of_double.json") { multipleOfDouble() }
} }
it("Can set a minimum and maximum number of properties on a free-form type") { it("Can set a minimum and maximum number of properties on a free-form type") {
openApiTest("min_max_free_form.json") { minMaxFreeForm() } openApiTestAllSerializers("min_max_free_form.json") { minMaxFreeForm() }
} }
} }
describe("Free Form") { describe("Free Form") {
it("Can create a free-form field") { it("Can create a free-form field") {
openApiTest("free_form_object.json") { freeFormObject() } openApiTestAllSerializers("free_form_object.json") { freeFormObject() }
} }
} }
}) })

View File

@ -0,0 +1,7 @@
package io.bkbn.kompendium.core.fixtures
enum class SupportedSerializer {
KOTLINX,
GSON,
JACKSON
}

View File

@ -1,16 +1,22 @@
package io.bkbn.kompendium.core.fixtures package io.bkbn.kompendium.core.fixtures
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
import io.kotest.assertions.json.shouldEqualJson import io.kotest.assertions.json.shouldEqualJson
import io.kotest.assertions.ktor.shouldHaveStatus import io.kotest.assertions.ktor.shouldHaveStatus
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe import io.kotest.matchers.shouldNotBe
import io.ktor.application.Application import io.ktor.application.Application
import io.ktor.application.install
import io.ktor.features.ContentNegotiation
import io.ktor.gson.gson
import io.ktor.http.HttpMethod import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.serialization.json
import io.ktor.server.testing.TestApplicationEngine import io.ktor.server.testing.TestApplicationEngine
import io.ktor.server.testing.createTestEnvironment import io.ktor.server.testing.createTestEnvironment
import io.ktor.server.testing.handleRequest import io.ktor.server.testing.handleRequest
import io.ktor.server.testing.withApplication import io.ktor.server.testing.withApplication
import kotlinx.serialization.json.Json
import java.io.File import java.io.File
object TestHelpers { object TestHelpers {
@ -42,16 +48,44 @@ object TestHelpers {
/** /**
* This will take a provided JSON snapshot file, retrieve it from the resource folder, * This will take a provided JSON snapshot file, retrieve it from the resource folder,
* and build a test ktor server to compare the expected output with the output found in the default * and build a test ktor server to compare the expected output with the output found in the default
* OpenAPI json endpoint * OpenAPI json endpoint. By default, this will run the same test with Gson, Kotlinx, and Jackson serializers
* @param snapshotName The snapshot file to retrieve from the resources folder * @param snapshotName The snapshot file to retrieve from the resources folder
* @param moduleFunction Initializer for the application to allow tests to pass the required Ktor modules * @param moduleFunction Initializer for the application to allow tests to pass the required Ktor modules
*/ */
fun openApiTest(snapshotName: String, moduleFunction: Application.() -> Unit) { fun openApiTestAllSerializers(snapshotName: String, moduleFunction: Application.() -> Unit) {
openApiTest(snapshotName, SupportedSerializer.KOTLINX, moduleFunction)
openApiTest(snapshotName, SupportedSerializer.JACKSON, moduleFunction)
openApiTest(snapshotName, SupportedSerializer.GSON, moduleFunction)
}
private fun openApiTest(
snapshotName: String,
serializer: SupportedSerializer,
moduleFunction: Application.() -> Unit
) {
withApplication(createTestEnvironment()) { withApplication(createTestEnvironment()) {
moduleFunction(application.apply { moduleFunction(application.apply {
kompendium() kompendium()
docs() docs()
jacksonConfigModule() when (serializer) {
SupportedSerializer.KOTLINX -> {
install(ContentNegotiation) {
json(Json {
encodeDefaults = true
explicitNulls = false
serializersModule = KompendiumSerializersModule.module
})
}
}
SupportedSerializer.GSON -> {
install(ContentNegotiation) {
gson()
}
}
SupportedSerializer.JACKSON -> {
jacksonConfigModule()
}
}
}) })
compareOpenAPISpec(snapshotName) compareOpenAPISpec(snapshotName)
} }

View File

@ -18,6 +18,7 @@ import io.bkbn.kompendium.annotations.constraint.Minimum
import io.bkbn.kompendium.annotations.constraint.MultipleOf import io.bkbn.kompendium.annotations.constraint.MultipleOf
import io.bkbn.kompendium.annotations.constraint.Pattern import io.bkbn.kompendium.annotations.constraint.Pattern
import io.bkbn.kompendium.annotations.constraint.UniqueItems import io.bkbn.kompendium.annotations.constraint.UniqueItems
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import java.math.BigDecimal import java.math.BigDecimal
import java.math.BigInteger import java.math.BigInteger
@ -46,10 +47,12 @@ data class TestParams(
@Param(ParamType.QUERY) val aa: Int @Param(ParamType.QUERY) val aa: Int
) )
@Serializable
data class TestNested(val nesty: String) data class TestNested(val nesty: String)
data class TestWithUUID(val id: UUID) data class TestWithUUID(val id: UUID)
@Serializable
data class TestRequest( data class TestRequest(
@Field(name = "field_name") @Field(name = "field_name")
val fieldName: TestNested, val fieldName: TestNested,
@ -57,6 +60,7 @@ data class TestRequest(
val aaa: List<Long> val aaa: List<Long>
) )
@Serializable
data class TestResponse(val c: String) data class TestResponse(val c: String)
data class TestGeneric<T>(val messy: String, val potato: T) data class TestGeneric<T>(val messy: String, val potato: T)

View File

@ -1,6 +1,6 @@
package io.bkbn.kompendium.locations package io.bkbn.kompendium.locations
import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTest import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
import io.bkbn.kompendium.locations.util.locationsConfig import io.bkbn.kompendium.locations.util.locationsConfig
import io.bkbn.kompendium.locations.util.notarizedDeleteNestedLocation import io.bkbn.kompendium.locations.util.notarizedDeleteNestedLocation
import io.bkbn.kompendium.locations.util.notarizedDeleteSimpleLocation import io.bkbn.kompendium.locations.util.notarizedDeleteSimpleLocation
@ -16,56 +16,56 @@ class KompendiumLocationsTest : DescribeSpec({
describe("Locations") { describe("Locations") {
it("Can notarize a get request with a simple location") { it("Can notarize a get request with a simple location") {
// act // act
openApiTest("notarized_get_simple_location.json") { openApiTestAllSerializers("notarized_get_simple_location.json") {
locationsConfig() locationsConfig()
notarizedGetSimpleLocation() notarizedGetSimpleLocation()
} }
} }
it("Can notarize a get request with a nested location") { it("Can notarize a get request with a nested location") {
// act // act
openApiTest("notarized_get_nested_location.json") { openApiTestAllSerializers("notarized_get_nested_location.json") {
locationsConfig() locationsConfig()
notarizedGetNestedLocation() notarizedGetNestedLocation()
} }
} }
it("Can notarize a post with a simple location") { it("Can notarize a post with a simple location") {
// act // act
openApiTest("notarized_post_simple_location.json") { openApiTestAllSerializers("notarized_post_simple_location.json") {
locationsConfig() locationsConfig()
notarizedPostSimpleLocation() notarizedPostSimpleLocation()
} }
} }
it("Can notarize a post with a nested location") { it("Can notarize a post with a nested location") {
// act // act
openApiTest("notarized_post_nested_location.json") { openApiTestAllSerializers("notarized_post_nested_location.json") {
locationsConfig() locationsConfig()
notarizedPostNestedLocation() notarizedPostNestedLocation()
} }
} }
it("Can notarize a put with a simple location") { it("Can notarize a put with a simple location") {
// act // act
openApiTest("notarized_put_simple_location.json") { openApiTestAllSerializers("notarized_put_simple_location.json") {
locationsConfig() locationsConfig()
notarizedPutSimpleLocation() notarizedPutSimpleLocation()
} }
} }
it("Can notarize a put with a nested location") { it("Can notarize a put with a nested location") {
// act // act
openApiTest("notarized_put_nested_location.json") { openApiTestAllSerializers("notarized_put_nested_location.json") {
locationsConfig() locationsConfig()
notarizedPutNestedLocation() notarizedPutNestedLocation()
} }
} }
it("Can notarize a delete with a simple location") { it("Can notarize a delete with a simple location") {
// act // act
openApiTest("notarized_delete_simple_location.json") { openApiTestAllSerializers("notarized_delete_simple_location.json") {
locationsConfig() locationsConfig()
notarizedDeleteSimpleLocation() notarizedDeleteSimpleLocation()
} }
} }
it("Can notarize a delete with a nested location") { it("Can notarize a delete with a nested location") {
// act // act
openApiTest("notarized_delete_nested_location.json") { openApiTestAllSerializers("notarized_delete_nested_location.json") {
locationsConfig() locationsConfig()
notarizedDeleteNestedLocation() notarizedDeleteNestedLocation()
} }

View File

@ -17,7 +17,7 @@ sourdough {
} }
dependencies { dependencies {
implementation(group = "org.jetbrains.kotlinx", "kotlinx-serialization-json", version = "1.3.1") implementation(group = "org.jetbrains.kotlinx", "kotlinx-serialization-json", version = "1.3.2")
} }
testing { testing {

View File

@ -1,10 +1,9 @@
package io.bkbn.kompendium.oas.schema package io.bkbn.kompendium.oas.schema
import kotlinx.serialization.ExperimentalSerializationApi import io.bkbn.kompendium.oas.serialization.ComponentSchemaSerializer
import kotlinx.serialization.json.JsonClassDiscriminator import kotlinx.serialization.Serializable
@OptIn(ExperimentalSerializationApi::class) @Serializable(with = ComponentSchemaSerializer::class)
@JsonClassDiscriminator("component_type") // todo figure out a way to filter this
sealed interface ComponentSchema { sealed interface ComponentSchema {
val description: String? val description: String?
get() = null get() = null

View File

@ -1,8 +1,7 @@
package io.bkbn.kompendium.oas.security package io.bkbn.kompendium.oas.security
import kotlinx.serialization.ExperimentalSerializationApi import io.bkbn.kompendium.oas.serialization.SecuritySchemaSerializer
import kotlinx.serialization.json.JsonClassDiscriminator import kotlinx.serialization.Serializable
@OptIn(ExperimentalSerializationApi::class) @Serializable(with = SecuritySchemaSerializer::class)
@JsonClassDiscriminator("schema_type") // todo figure out a way to filter this
sealed interface SecuritySchema sealed interface SecuritySchema

View File

@ -3,6 +3,8 @@ package io.bkbn.kompendium.oas.serialization
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
@ -17,8 +19,7 @@ class AnySerializer<T : Any> : KSerializer<T> {
error("Abandon all hope ye who enter 💀") error("Abandon all hope ye who enter 💀")
} }
override val descriptor: SerialDescriptor override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("KompendiumAny", PrimitiveKind.STRING)
get() = TODO("Not yet implemented")
@OptIn(InternalSerializationApi::class) @OptIn(InternalSerializationApi::class)
fun serialize(encoder: Encoder, obj: T, clazz: KClass<T>) { fun serialize(encoder: Encoder, obj: T, clazz: KClass<T>) {

View File

@ -0,0 +1,45 @@
package io.bkbn.kompendium.oas.serialization
import io.bkbn.kompendium.oas.schema.AnyOfSchema
import io.bkbn.kompendium.oas.schema.ArraySchema
import io.bkbn.kompendium.oas.schema.ComponentSchema
import io.bkbn.kompendium.oas.schema.DictionarySchema
import io.bkbn.kompendium.oas.schema.EnumSchema
import io.bkbn.kompendium.oas.schema.FormattedSchema
import io.bkbn.kompendium.oas.schema.FreeFormSchema
import io.bkbn.kompendium.oas.schema.ObjectSchema
import io.bkbn.kompendium.oas.schema.ReferencedSchema
import io.bkbn.kompendium.oas.schema.SimpleSchema
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
@OptIn(ExperimentalSerializationApi::class)
@Serializer(forClass = ComponentSchema::class)
object ComponentSchemaSerializer : KSerializer<ComponentSchema> {
override fun deserialize(decoder: Decoder): ComponentSchema {
error("Abandon all hope ye who enter 💀")
}
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ComponentSchema", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: ComponentSchema) {
when (value) {
is AnyOfSchema -> AnyOfSchema.serializer().serialize(encoder, value)
is ReferencedSchema -> ReferencedSchema.serializer().serialize(encoder, value)
is ArraySchema -> ArraySchema.serializer().serialize(encoder, value)
is DictionarySchema -> DictionarySchema.serializer().serialize(encoder, value)
is EnumSchema -> EnumSchema.serializer().serialize(encoder, value)
is FormattedSchema -> FormattedSchema.serializer().serialize(encoder, value)
is FreeFormSchema -> FreeFormSchema.serializer().serialize(encoder, value)
is ObjectSchema -> ObjectSchema.serializer().serialize(encoder, value)
is SimpleSchema -> SimpleSchema.serializer().serialize(encoder, value)
}
}
}

View File

@ -1,44 +1,9 @@
package io.bkbn.kompendium.oas.serialization package io.bkbn.kompendium.oas.serialization
import io.bkbn.kompendium.oas.schema.AnyOfSchema
import io.bkbn.kompendium.oas.schema.ArraySchema
import io.bkbn.kompendium.oas.schema.ComponentSchema
import io.bkbn.kompendium.oas.schema.DictionarySchema
import io.bkbn.kompendium.oas.schema.EnumSchema
import io.bkbn.kompendium.oas.schema.FormattedSchema
import io.bkbn.kompendium.oas.schema.FreeFormSchema
import io.bkbn.kompendium.oas.schema.ObjectSchema
import io.bkbn.kompendium.oas.schema.ReferencedSchema
import io.bkbn.kompendium.oas.schema.SimpleSchema
import io.bkbn.kompendium.oas.security.ApiKeyAuth
import io.bkbn.kompendium.oas.security.BasicAuth
import io.bkbn.kompendium.oas.security.BearerAuth
import io.bkbn.kompendium.oas.security.OAuth
import io.bkbn.kompendium.oas.security.SecuritySchema
import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
object KompendiumSerializersModule { object KompendiumSerializersModule {
val module = SerializersModule { val module = SerializersModule {
polymorphic(ComponentSchema::class) {
subclass(SimpleSchema::class, SimpleSchema.serializer())
subclass(FormattedSchema::class, FormattedSchema.serializer())
subclass(ObjectSchema::class, ObjectSchema.serializer())
subclass(AnyOfSchema::class, AnyOfSchema.serializer())
subclass(ArraySchema::class, ArraySchema.serializer())
subclass(DictionarySchema::class, DictionarySchema.serializer())
subclass(EnumSchema::class, EnumSchema.serializer())
subclass(FreeFormSchema::class, FreeFormSchema.serializer())
subclass(ReferencedSchema::class, ReferencedSchema.serializer())
}
polymorphic(SecuritySchema::class) {
subclass(ApiKeyAuth::class, ApiKeyAuth.serializer())
subclass(BasicAuth::class, BasicAuth.serializer())
subclass(BearerAuth::class, BearerAuth.serializer())
subclass(OAuth::class, OAuth.serializer())
}
contextual(Any::class, AnySerializer()) contextual(Any::class, AnySerializer())
} }
} }

View File

@ -18,6 +18,10 @@ object NumberSerializer : KSerializer<Number> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Number", PrimitiveKind.DOUBLE) override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Number", PrimitiveKind.DOUBLE)
override fun serialize(encoder: Encoder, value: Number) { override fun serialize(encoder: Encoder, value: Number) {
encoder.encodeString(value.toString()) when (value) {
is Int -> encoder.encodeInt(value)
is Double -> encoder.encodeDouble(value)
else -> error("Invalid OpenApi Number $value")
}
} }
} }

View File

@ -0,0 +1,34 @@
package io.bkbn.kompendium.oas.serialization
import io.bkbn.kompendium.oas.security.ApiKeyAuth
import io.bkbn.kompendium.oas.security.BasicAuth
import io.bkbn.kompendium.oas.security.BearerAuth
import io.bkbn.kompendium.oas.security.OAuth
import io.bkbn.kompendium.oas.security.SecuritySchema
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
@OptIn(ExperimentalSerializationApi::class)
@Serializer(forClass = SecuritySchema::class)
object SecuritySchemaSerializer : KSerializer<SecuritySchema> {
override fun deserialize(decoder: Decoder): SecuritySchema {
error("Abandon all hope ye who enter 💀")
}
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("SecuritySchema", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: SecuritySchema) {
when (value) {
is ApiKeyAuth -> ApiKeyAuth.serializer().serialize(encoder, value)
is BasicAuth -> BasicAuth.serializer().serialize(encoder, value)
is BearerAuth -> BearerAuth.serializer().serialize(encoder, value)
is OAuth -> OAuth.serializer().serialize(encoder, value)
}
}
}

View File

@ -36,7 +36,7 @@ dependencies {
implementation("org.slf4j:slf4j-simple:1.7.35") implementation("org.slf4j:slf4j-simple:1.7.35")
implementation(group = "org.jetbrains.kotlinx", "kotlinx-serialization-json", version = "1.3.1") implementation(group = "org.jetbrains.kotlinx", "kotlinx-serialization-json", version = "1.3.2")
implementation(group = "joda-time", name = "joda-time", version = "2.10.13") implementation(group = "joda-time", name = "joda-time", version = "2.10.13")
} }

View File

@ -39,7 +39,7 @@ fun main() {
// Application Module // Application Module
private fun Application.mainModule() { private fun Application.mainModule() {
install(ContentNegotiation) { install(ContentNegotiation) {
json(json = Util.kotlinxConfig) json()
} }
install(Kompendium) { install(Kompendium) {
spec = Util.baseSpec spec = Util.baseSpec

View File

@ -15,7 +15,6 @@ import io.bkbn.kompendium.core.metadata.method.DeleteInfo
import io.bkbn.kompendium.core.metadata.method.GetInfo import io.bkbn.kompendium.core.metadata.method.GetInfo
import io.bkbn.kompendium.core.metadata.method.PostInfo import io.bkbn.kompendium.core.metadata.method.PostInfo
import io.bkbn.kompendium.core.routes.redoc import io.bkbn.kompendium.core.routes.redoc
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
import io.bkbn.kompendium.playground.BasicModels.BasicParameters import io.bkbn.kompendium.playground.BasicModels.BasicParameters
import io.bkbn.kompendium.playground.BasicModels.BasicRequest import io.bkbn.kompendium.playground.BasicModels.BasicRequest
import io.bkbn.kompendium.playground.BasicModels.BasicResponse import io.bkbn.kompendium.playground.BasicModels.BasicResponse
@ -38,7 +37,6 @@ import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty import io.ktor.server.netty.Netty
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import java.util.UUID import java.util.UUID
/** /**
@ -57,7 +55,7 @@ fun main() {
private fun Application.mainModule() { private fun Application.mainModule() {
// Installs Simple JSON Content Negotiation // Installs Simple JSON Content Negotiation
install(ContentNegotiation) { install(ContentNegotiation) {
json(json = Util.kotlinxConfig) json()
} }
// Installs the Kompendium Plugin and sets up baseline server metadata // Installs the Kompendium Plugin and sets up baseline server metadata
install(Kompendium) { install(Kompendium) {
@ -76,7 +74,7 @@ private fun Application.mainModule() {
call.respond(HttpStatusCode.NoContent) call.respond(HttpStatusCode.NoContent)
} }
// It can also infer path parameters // It can also infer path parameters
route("/{a}") { route("/testerino/{a}") {
notarizedGet(simpleGetExampleWithParameters) { notarizedGet(simpleGetExampleWithParameters) {
val a = call.parameters["a"] ?: error("Unable to read expected path parameter") val a = call.parameters["a"] ?: error("Unable to read expected path parameter")
val b = call.request.queryParameters["b"]?.toInt() ?: error("Unable to read expected query parameter") val b = call.request.queryParameters["b"]?.toInt() ?: error("Unable to read expected query parameter")

View File

@ -54,7 +54,7 @@ fun main() {
private fun Application.mainModule() { private fun Application.mainModule() {
// Installs Simple JSON Content Negotiation // Installs Simple JSON Content Negotiation
install(ContentNegotiation) { install(ContentNegotiation) {
json(json = Util.kotlinxConfig) json()
} }
// Installs the Kompendium Plugin and sets up baseline server metadata // Installs the Kompendium Plugin and sets up baseline server metadata
install(Kompendium) { install(Kompendium) {

View File

@ -36,7 +36,7 @@ fun main() {
private fun Application.mainModule() { private fun Application.mainModule() {
// Installs Simple JSON Content Negotiation // Installs Simple JSON Content Negotiation
install(ContentNegotiation) { install(ContentNegotiation) {
json(json = Util.kotlinxConfig) json()
} }
// Installs the Kompendium Plugin and sets up baseline server metadata // Installs the Kompendium Plugin and sets up baseline server metadata
install(Kompendium) { install(Kompendium) {

View File

@ -35,7 +35,7 @@ fun main() {
private fun Application.mainModule() { private fun Application.mainModule() {
// Installs Simple JSON Content Negotiation // Installs Simple JSON Content Negotiation
install(ContentNegotiation) { install(ContentNegotiation) {
json(json = Util.kotlinxConfig) json()
} }
// Installs the Kompendium Plugin and sets up baseline server metadata // Installs the Kompendium Plugin and sets up baseline server metadata
install(Kompendium) { install(Kompendium) {

View File

@ -39,7 +39,7 @@ fun main() {
private fun Application.mainModule() { private fun Application.mainModule() {
install(ContentNegotiation) { install(ContentNegotiation) {
json(json = Util.kotlinxConfig) json()
} }
install(Kompendium) { install(Kompendium) {
spec = Util.baseSpec spec = Util.baseSpec

View File

@ -34,7 +34,7 @@ fun main() {
private fun Application.mainModule() { private fun Application.mainModule() {
// Installs Simple JSON Content Negotiation // Installs Simple JSON Content Negotiation
install(ContentNegotiation) { install(ContentNegotiation) {
json(json = Util.kotlinxConfig) json()
} }
// Installs the Kompendium Plugin and sets up baseline server metadata // Installs the Kompendium Plugin and sets up baseline server metadata
install(Kompendium) { install(Kompendium) {

View File

@ -58,7 +58,7 @@ fun main() {
private fun Application.mainModule() { private fun Application.mainModule() {
install(ContentNegotiation) { install(ContentNegotiation) {
json(json = Util.kotlinxConfig) json()
} }
install(Kompendium) { install(Kompendium) {
spec = Util.baseSpec spec = Util.baseSpec

View File

@ -33,7 +33,7 @@ fun main() {
private fun Application.mainModule() { private fun Application.mainModule() {
// Installs Simple JSON Content Negotiation // Installs Simple JSON Content Negotiation
install(ContentNegotiation) { install(ContentNegotiation) {
json(json = Util.kotlinxConfig) json()
} }
install(Webjars) install(Webjars)
// Installs the Kompendium Plugin and sets up baseline server metadata // Installs the Kompendium Plugin and sets up baseline server metadata

View File

@ -4,22 +4,12 @@ import io.bkbn.kompendium.oas.OpenApiSpec
import io.bkbn.kompendium.oas.info.Contact import io.bkbn.kompendium.oas.info.Contact
import io.bkbn.kompendium.oas.info.Info import io.bkbn.kompendium.oas.info.Info
import io.bkbn.kompendium.oas.info.License import io.bkbn.kompendium.oas.info.License
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
import io.bkbn.kompendium.oas.server.Server import io.bkbn.kompendium.oas.server.Server
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import java.net.URI import java.net.URI
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
object Util { object Util {
val kotlinxConfig = Json {
classDiscriminator = "class"
serializersModule = KompendiumSerializersModule.module
prettyPrint = true
explicitNulls = false
encodeDefaults = true
}
val baseSpec = OpenApiSpec( val baseSpec = OpenApiSpec(
info = Info( info = Info(
title = "Simple Demo API", title = "Simple Demo API",