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
- Cleaned up and broke out handlers into separate classes
- Serializer cleanup
- Tests now run against Jackson, Gson and kotlinx on every run
### 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.notarizedAuthRoute
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.kotest.core.spec.style.DescribeSpec
@ -21,7 +21,7 @@ class KompendiumAuthTest : DescribeSpec({
}
// act
openApiTest("notarized_basic_authenticated_get.json") {
openApiTestAllSerializers("notarized_basic_authenticated_get.json") {
configBasicAuth()
notarizedAuthRoute(authConfig)
}
@ -35,7 +35,7 @@ class KompendiumAuthTest : DescribeSpec({
}
// act
openApiTest("notarized_jwt_authenticated_get.json") {
openApiTestAllSerializers("notarized_jwt_authenticated_get.json") {
configJwtAuth()
notarizedAuthRoute(authConfig)
}
@ -60,7 +60,7 @@ class KompendiumAuthTest : DescribeSpec({
}
// act
openApiTest("notarized_oauth_all_flows.json") {
openApiTestAllSerializers("notarized_oauth_all_flows.json") {
setupOauth()
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
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
Central to Kompendium is the concept of notarization.

View File

@ -1,5 +1,6 @@
plugins {
kotlin("jvm")
kotlin("plugin.serialization")
id("io.bkbn.sourdough.library.jvm")
id("io.gitlab.arturbosch.detekt")
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-test-host", 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 = "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.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.constrainedDoubleInfo
import io.bkbn.kompendium.core.util.constrainedIntInfo
@ -60,37 +60,37 @@ import io.ktor.http.HttpStatusCode
class KompendiumTest : DescribeSpec({
describe("Notarized Open API Metadata Tests") {
it("Can notarize a get request") {
openApiTest("notarized_get.json") { notarizedGetModule() }
openApiTestAllSerializers("notarized_get.json") { notarizedGetModule() }
}
it("Can notarize a post request") {
openApiTest("notarized_post.json") { notarizedPostModule() }
openApiTestAllSerializers("notarized_post.json") { notarizedPostModule() }
}
it("Can notarize a put request") {
openApiTest("notarized_put.json") { notarizedPutModule() }
openApiTestAllSerializers("notarized_put.json") { notarizedPutModule() }
}
it("Can notarize a delete request") {
openApiTest("notarized_delete.json") { notarizedDeleteModule() }
openApiTestAllSerializers("notarized_delete.json") { notarizedDeleteModule() }
}
it("Can notarize a patch request") {
openApiTest("notarized_patch.json") { notarizedPatchModule() }
openApiTestAllSerializers("notarized_patch.json") { notarizedPatchModule() }
}
it("Can notarize a head request") {
openApiTest("notarized_head.json") { notarizedHeadModule() }
openApiTestAllSerializers("notarized_head.json") { notarizedHeadModule() }
}
it("Can notarize an options request") {
openApiTest("notarized_options.json") { notarizedOptionsModule() }
openApiTestAllSerializers("notarized_options.json") { notarizedOptionsModule() }
}
it("Can notarize a complex type") {
openApiTest("complex_type.json") { complexType() }
openApiTestAllSerializers("complex_type.json") { complexType() }
}
it("Can notarize primitives") {
openApiTest("notarized_primitives.json") { primitives() }
openApiTestAllSerializers("notarized_primitives.json") { primitives() }
}
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") {
openApiTest("non_required_params.json") { nonRequiredParamsGet() }
openApiTestAllSerializers("non_required_params.json") { nonRequiredParamsGet() }
}
}
describe("Notarized Ktor Functionality Tests") {
@ -122,80 +122,80 @@ class KompendiumTest : DescribeSpec({
}
describe("Route Parsing") {
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") {
openApiTest("root_route.json") { rootModule() }
openApiTestAllSerializers("root_route.json") { rootModule() }
}
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") {
openApiTest("trailing_slash.json") { trailingSlash() }
openApiTestAllSerializers("trailing_slash.json") { trailingSlash() }
}
}
describe("Exceptions") {
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") {
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") {
openApiTest("polymorphic_error_status_codes.json") { notarizedGetWithPolymorphicErrorResponse() }
openApiTestAllSerializers("polymorphic_error_status_codes.json") { notarizedGetWithPolymorphicErrorResponse() }
}
it("Can add a generic exception response") {
openApiTest("generic_exception.json") { notarizedGetWithGenericErrorResponse() }
openApiTestAllSerializers("generic_exception.json") { notarizedGetWithGenericErrorResponse() }
}
}
describe("Examples") {
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") {
openApiTest("example_parameters.json") { exampleParams() }
openApiTestAllSerializers("example_parameters.json") { exampleParams() }
}
}
describe("Defaults") {
it("Can generate a default parameter values") {
openApiTest("query_with_default_parameter.json") { withDefaultParameter() }
openApiTestAllSerializers("query_with_default_parameter.json") { withDefaultParameter() }
}
}
describe("Required Fields") {
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") {
openApiTest("default_param.json") { defaultParameter() }
openApiTestAllSerializers("default_param.json") { defaultParameter() }
}
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") {
openApiTest("nullable_field.json") { nullableField() }
openApiTestAllSerializers("nullable_field.json") { nullableField() }
}
}
describe("Polymorphism and Generics") {
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") {
openApiTest("polymorphic_list_response.json") { polymorphicCollectionResponse() }
openApiTestAllSerializers("polymorphic_list_response.json") { polymorphicCollectionResponse() }
}
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") {
openApiTest("sealed_interface_response.json") { polymorphicInterfaceResponse() }
openApiTestAllSerializers("sealed_interface_response.json") { polymorphicInterfaceResponse() }
}
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") {
openApiTest("polymorphic_response_with_generics.json") { genericPolymorphicResponse() }
openApiTestAllSerializers("polymorphic_response_with_generics.json") { genericPolymorphicResponse() }
}
it("Can handle an absolutely psycho inheritance test") {
openApiTest("crazy_polymorphic_example.json") { genericPolymorphicResponseMultipleImpls() }
openApiTestAllSerializers("crazy_polymorphic_example.json") { genericPolymorphicResponseMultipleImpls() }
}
}
describe("Miscellaneous") {
@ -203,59 +203,59 @@ class KompendiumTest : DescribeSpec({
apiFunctionalityTest(getFileSnapshot("redoc.html"), "/docs") { returnsList() }
}
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") {
openApiTest("undeclared_field.json") { undeclaredType() }
openApiTestAllSerializers("undeclared_field.json") { undeclaredType() }
}
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") {
openApiTest("field_override.json") { overrideFieldInfo() }
openApiTestAllSerializers("field_override.json") { overrideFieldInfo() }
}
it("Can serialize a recursive type using references") {
openApiTest("simple_recursive.json") { simpleRecursive() }
openApiTestAllSerializers("simple_recursive.json") { simpleRecursive() }
}
}
describe("Constraints") {
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") {
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") {
openApiTest("exclusive_min_max.json") { exclusiveMinMax() }
openApiTestAllSerializers("exclusive_min_max.json") { exclusiveMinMax() }
}
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") {
openApiTest("min_max_string.json") { minMaxString() }
openApiTestAllSerializers("min_max_string.json") { minMaxString() }
}
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") {
openApiTest("min_max_array.json") { minMaxArray() }
openApiTestAllSerializers("min_max_array.json") { minMaxArray() }
}
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") {
openApiTest("multiple_of_int.json") { multipleOfInt() }
openApiTestAllSerializers("multiple_of_int.json") { multipleOfInt() }
}
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") {
openApiTest("min_max_free_form.json") { minMaxFreeForm() }
openApiTestAllSerializers("min_max_free_form.json") { minMaxFreeForm() }
}
}
describe("Free Form") {
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
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
import io.kotest.assertions.json.shouldEqualJson
import io.kotest.assertions.ktor.shouldHaveStatus
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
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.HttpStatusCode
import io.ktor.serialization.json
import io.ktor.server.testing.TestApplicationEngine
import io.ktor.server.testing.createTestEnvironment
import io.ktor.server.testing.handleRequest
import io.ktor.server.testing.withApplication
import kotlinx.serialization.json.Json
import java.io.File
object TestHelpers {
@ -42,16 +48,44 @@ object TestHelpers {
/**
* 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
* 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 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()) {
moduleFunction(application.apply {
kompendium()
docs()
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)
}

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

View File

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

View File

@ -17,7 +17,7 @@ sourdough {
}
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 {

View File

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

View File

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

View File

@ -3,6 +3,8 @@ package io.bkbn.kompendium.oas.serialization
import kotlin.reflect.KClass
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.KSerializer
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
@ -17,8 +19,7 @@ class AnySerializer<T : Any> : KSerializer<T> {
error("Abandon all hope ye who enter 💀")
}
override val descriptor: SerialDescriptor
get() = TODO("Not yet implemented")
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("KompendiumAny", PrimitiveKind.STRING)
@OptIn(InternalSerializationApi::class)
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
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.polymorphic
object KompendiumSerializersModule {
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())
}
}

View File

@ -18,6 +18,10 @@ object NumberSerializer : KSerializer<Number> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Number", PrimitiveKind.DOUBLE)
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(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")
}

View File

@ -39,7 +39,7 @@ fun main() {
// Application Module
private fun Application.mainModule() {
install(ContentNegotiation) {
json(json = Util.kotlinxConfig)
json()
}
install(Kompendium) {
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.PostInfo
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.BasicRequest
import io.bkbn.kompendium.playground.BasicModels.BasicResponse
@ -38,7 +37,6 @@ import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import java.util.UUID
/**
@ -57,7 +55,7 @@ fun main() {
private fun Application.mainModule() {
// Installs Simple JSON Content Negotiation
install(ContentNegotiation) {
json(json = Util.kotlinxConfig)
json()
}
// Installs the Kompendium Plugin and sets up baseline server metadata
install(Kompendium) {
@ -76,7 +74,7 @@ private fun Application.mainModule() {
call.respond(HttpStatusCode.NoContent)
}
// It can also infer path parameters
route("/{a}") {
route("/testerino/{a}") {
notarizedGet(simpleGetExampleWithParameters) {
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")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@ fun main() {
private fun Application.mainModule() {
// Installs Simple JSON Content Negotiation
install(ContentNegotiation) {
json(json = Util.kotlinxConfig)
json()
}
install(Webjars)
// 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.Info
import io.bkbn.kompendium.oas.info.License
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
import io.bkbn.kompendium.oas.server.Server
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import java.net.URI
@OptIn(ExperimentalSerializationApi::class)
object Util {
val kotlinxConfig = Json {
classDiscriminator = "class"
serializersModule = KompendiumSerializersModule.module
prettyPrint = true
explicitNulls = false
encodeDefaults = true
}
val baseSpec = OpenApiSpec(
info = Info(
title = "Simple Demo API",