From c73c9b46057b4f6ee723e4d47156f13eb45c1cc5 Mon Sep 17 00:00:00 2001 From: Ryan Brink <5607577+unredundant@users.noreply.github.com> Date: Sat, 13 Aug 2022 09:59:59 -0700 Subject: [PATCH] breaking change: V3 alpha (#256) --- build.gradle.kts | 8 +- {kompendium-core => core}/Module.md | 0 core/build.gradle.kts | 62 ++ .../core/attribute/KompendiumAttributes.kt | 8 + .../kompendium/core/metadata/DeleteInfo.kt | 40 ++ .../bkbn/kompendium/core/metadata/GetInfo.kt | 40 ++ .../bkbn/kompendium/core/metadata/HeadInfo.kt | 40 ++ .../kompendium/core/metadata/MethodInfo.kt | 64 ++ .../core/metadata/MethodInfoWithRequest.kt | 15 + .../kompendium/core/metadata/OptionsInfo.kt | 40 ++ .../kompendium/core/metadata/PatchInfo.kt | 42 ++ .../bkbn/kompendium/core/metadata/PostInfo.kt | 42 ++ .../bkbn/kompendium/core/metadata/PutInfo.kt | 42 ++ .../kompendium/core/metadata/RequestInfo.kt | 46 ++ .../kompendium/core/metadata/ResponseInfo.kt | 53 ++ .../core/plugin/NotarizedApplication.kt | 45 ++ .../kompendium/core/plugin/NotarizedRoute.kt | 171 +++++ .../io/bkbn/kompendium/core/routes/Redoc.kt | 13 +- .../io/bkbn/kompendium/core/util/Helpers.kt | 35 + .../io/bkbn/kompendium/core/KompendiumTest.kt | 201 ++++++ .../bkbn/kompendium/core/util/TestModules.kt | 609 +++++++++++++++++ .../test/resources/T0001__notarized_get.json | 58 +- .../test/resources/T0002__notarized_post.json | 120 ++-- .../test/resources/T0003__notarized_put.json | 120 ++-- .../resources/T0004__notarized_delete.json | 65 +- .../resources/T0005__notarized_patch.json | 101 +-- .../test/resources/T0006__notarized_head.json | 35 +- .../resources/T0007__notarized_options.json | 59 +- .../test/resources/T0008__complex_type.json | 79 ++- .../T0009__notarized_primitives.json | 30 +- .../test/resources/T0010__response_list.json | 68 +- .../resources/T0011__non_required_params.json | 52 +- .../test/resources/T0012__path_parser.json | 25 +- .../src/test/resources/T0013__root_route.json | 48 +- .../resources/T0014__nested_under_root.json | 32 +- .../test/resources/T0015__trailing_slash.json | 72 ++ ...notarized_get_with_exception_response.json | 41 +- ...get_with_multiple_exception_responses.json | 61 +- ...T0018__polymorphic_error_status_codes.json | 97 ++- .../resources/T0019__generic_exception.json | 84 +-- .../T0020__example_req_and_resp.json | 114 ++-- .../resources/T0021__example_parameters.json | 34 +- .../T0022__query_with_default_parameter.json | 30 +- .../test/resources/T0023__required_param.json | 25 +- .../resources/T0024__non_required_param.json | 23 +- .../test/resources/T0025__default_field.json | 34 +- .../test/resources/T0026__nullable_field.json | 39 +- .../T0027__polymorphic_response.json | 74 +- .../T0028__polymorphic_list_response.json | 79 ++- .../T0029__polymorphic_map_response.json | 74 +- .../T0030__simple_generic_response.json | 23 +- .../T0031__nested_generic_response.json | 29 +- ...2__polymorphic_response_with_generics.json | 77 +-- .../T0033__crazy_polymorphic_example.json | 145 ++++ ...0034__notarized_get_with_operation_id.json | 33 +- .../T0035__override_parameter_name.json | 21 +- .../resources/T0036__nullable_fields.json | 121 ++++ .../resources/T0037__nullable_enum_field.json | 37 +- .../T0038__formatted_date_time_string.json | 25 +- .../src/test/resources/redoc.html | 0 .../core/fixtures/SupportedSerializers.kt | 0 .../kompendium/core/fixtures/TestHelpers.kt | 104 +++ .../kompendium/core/fixtures/TestModels.kt | 120 ++++ .../kompendium/core/fixtures/TestSpecs.kt | 2 +- detekt.yml | 6 +- gradle.properties | 4 +- json-schema/build.gradle.kts | 32 + .../kompendium/json/schema/SchemaGenerator.kt | 65 ++ .../json/schema/definition/AnyOfDefinition.kt | 6 + .../json/schema/definition/ArrayDefinition.kt | 10 + .../json/schema/definition/EnumDefinition.kt | 8 + .../json/schema/definition/JsonSchema.kt | 33 + .../json/schema/definition/MapDefinition.kt | 10 + .../schema/definition/NullableDefinition.kt | 6 + .../json/schema/definition/OneOfDefinition.kt | 8 + .../schema/definition/ReferenceDefinition.kt | 6 + .../json/schema/definition/TypeDefinition.kt | 47 ++ .../json/schema/handler/CollectionHandler.kt | 33 + .../json/schema/handler/EnumHandler.kt | 21 + .../json/schema/handler/MapHandler.kt | 37 + .../schema/handler/SealedObjectHandler.kt | 32 + .../schema/handler/SimpleObjectHandler.kt | 76 +++ .../kompendium/json/schema/util/Helpers.kt | 29 + .../json/schema/util/ReferenceCache.kt | 9 + .../json/schema/SchemaGeneratorTest.kt | 98 +++ .../src/test/resources/T0001__scalar_int.json | 4 + .../test/resources/T0002__scalar_bool.json | 3 + .../test/resources/T0003__scalar_string.json | 3 + .../test/resources/T0004__simple_object.json | 16 + .../test/resources/T0005__complex_object.json | 22 + .../resources/T0006__nullable_object.json | 23 + .../test/resources/T0007__simple_enum.json | 3 + .../test/resources/T0008__nullable_enum.json | 13 + .../test/resources/T0009__scalar_array.json | 7 + .../test/resources/T0010__object_array.json | 6 + .../test/resources/T0011__nullable_array.json | 14 + .../src/test/resources/T0012__scalar_map.json | 7 + .../src/test/resources/T0013__object_map.json | 6 + .../test/resources/T0014__nullable_map.json | 14 + .../resources/T0015__polymorphic_object.json | 10 + .../resources/T0016__recursive_object.json | 13 + kompendium-annotations/Module.md | 13 - kompendium-annotations/build.gradle.kts | 23 - .../io/bkbn/kompendium/annotations/Field.kt | 9 - .../kompendium/annotations/FreeFormObject.kt | 5 - .../io/bkbn/kompendium/annotations/Param.kt | 9 - .../bkbn/kompendium/annotations/ParamType.kt | 11 - .../bkbn/kompendium/annotations/Referenced.kt | 12 - .../kompendium/annotations/UndeclaredField.kt | 15 - .../annotations/constraint/Format.kt | 5 - .../annotations/constraint/MaxItems.kt | 5 - .../annotations/constraint/MaxLength.kt | 5 - .../annotations/constraint/MaxProperties.kt | 5 - .../annotations/constraint/Maximum.kt | 5 - .../annotations/constraint/MinItems.kt | 5 - .../annotations/constraint/MinLength.kt | 5 - .../annotations/constraint/MinProperties.kt | 5 - .../annotations/constraint/Minimum.kt | 5 - .../annotations/constraint/MultipleOf.kt | 5 - .../annotations/constraint/Pattern.kt | 5 - .../annotations/constraint/UniqueItems.kt | 5 - kompendium-auth/Module.md | 13 - kompendium-auth/build.gradle.kts | 37 - .../io/bkbn/kompendium/auth/Notarized.kt | 40 -- .../auth/configuration/ApiKeyConfiguration.kt | 8 - .../configuration/BasicAuthConfiguration.kt | 3 - .../configuration/JwtAuthConfiguration.kt | 6 - .../auth/configuration/OAuthConfiguration.kt | 9 - .../configuration/SecurityConfiguration.kt | 5 - .../kompendium/auth/KompendiumAuthTest.kt | 69 -- .../bkbn/kompendium/auth/util/TestModules.kt | 92 --- .../notarized_basic_authenticated_get.json | 99 --- .../notarized_jwt_authenticated_get.json | 100 --- .../resources/notarized_oauth_all_flows.json | 119 ---- kompendium-core/build.gradle.kts | 56 -- .../io/bkbn/kompendium/core/Kompendium.kt | 49 -- .../kompendium/core/KompendiumPreFlight.kt | 50 -- .../kotlin/io/bkbn/kompendium/core/Kontent.kt | 98 --- .../io/bkbn/kompendium/core/Notarized.kt | 172 ----- .../core/constraint/ConstraintScanner.kt | 133 ---- .../core/handler/CollectionHandler.kt | 51 -- .../kompendium/core/handler/EnumHandler.kt | 20 - .../kompendium/core/handler/MapHandler.kt | 54 -- .../kompendium/core/handler/ObjectHandler.kt | 217 ------ .../kompendium/core/handler/SchemaHandler.kt | 38 -- .../kompendium/core/metadata/ExceptionInfo.kt | 12 - .../core/metadata/ParameterExample.kt | 3 - .../kompendium/core/metadata/RequestInfo.kt | 8 - .../kompendium/core/metadata/ResponseInfo.kt | 10 - .../kompendium/core/metadata/SchemaMap.kt | 5 - .../bkbn/kompendium/core/metadata/TypeMap.kt | 6 - .../core/metadata/method/DeleteInfo.kt | 17 - .../core/metadata/method/GetInfo.kt | 17 - .../core/metadata/method/HeadInfo.kt | 17 - .../core/metadata/method/MethodInfo.kt | 24 - .../core/metadata/method/OptionsInfo.kt | 17 - .../core/metadata/method/PatchInfo.kt | 19 - .../core/metadata/method/PostInfo.kt | 19 - .../core/metadata/method/PutInfo.kt | 19 - .../core/parser/DefaultMethodParser.kt | 3 - .../kompendium/core/parser/IMethodParser.kt | 260 ------- .../io/bkbn/kompendium/core/routes/Swagger.kt | 81 --- .../io/bkbn/kompendium/core/util/Helpers.kt | 85 --- .../io/bkbn/kompendium/core/KompendiumTest.kt | 371 ---------- .../io/bkbn/kompendium/core/KontentTest.kt | 215 ------ .../bkbn/kompendium/core/util/TestModules.kt | 640 ------------------ .../resources/crazy_polymorphic_example.json | 257 ------- .../src/test/resources/default_field.json | 95 --- .../test/resources/example_parameters.json | 102 --- .../src/test/resources/field_override.json | 69 -- .../resources/formatted_array_item_type.json | 95 --- .../src/test/resources/free_form_field.json | 70 -- .../src/test/resources/free_form_object.json | 58 -- .../src/test/resources/min_max_free_form.json | 72 -- .../src/test/resources/min_max_int_field.json | 74 -- .../src/test/resources/min_max_string.json | 71 -- .../test/resources/multiple_of_double.json | 71 -- .../src/test/resources/multiple_of_int.json | 71 -- .../src/test/resources/notarized_delete.json | 64 -- .../src/test/resources/nullable_field.json | 92 --- .../src/test/resources/nullable_fields.json | 119 ---- .../src/test/resources/petstore.json | 199 ------ .../query_with_default_parameter.json | 100 --- .../src/test/resources/regex_string.json | 70 -- .../resources/sealed_interface_response.json | 131 ---- .../src/test/resources/simple_recursive.json | 95 --- .../src/test/resources/undeclared_field.json | 79 --- .../src/test/resources/unique_array.json | 74 -- .../kompendium/core/fixtures/TestHelpers.kt | 155 ----- .../kompendium/core/fixtures/TestModels.kt | 274 -------- .../kompendium/core/fixtures/TestModules.kt | 35 - .../core/fixtures/TestResponseInfo.kt | 325 --------- .../locations/LocationMethodParser.kt | 84 --- .../kompendium/locations/NotarizedLocation.kt | 110 --- .../locations/KompendiumLocationsTest.kt | 82 --- .../kompendium/locations/util/TestModels.kt | 22 - .../kompendium/locations/util/TestModules.kt | 107 --- .../locations/util/TestResponseInfo.kt | 97 --- .../io/bkbn/kompendium/oas/OpenApiSpec.kt | 21 - .../kompendium/oas/component/Components.kt | 11 - .../io/bkbn/kompendium/oas/info/Contact.kt | 13 - .../io/bkbn/kompendium/oas/info/Info.kt | 16 - .../io/bkbn/kompendium/oas/info/License.kt | 12 - .../io/bkbn/kompendium/oas/path/Path.kt | 19 - .../bkbn/kompendium/oas/path/PathOperation.kt | 27 - .../kompendium/oas/payload/AnyOfPayload.kt | 5 - .../bkbn/kompendium/oas/payload/MediaType.kt | 14 - .../bkbn/kompendium/oas/payload/Parameter.kt | 22 - .../io/bkbn/kompendium/oas/payload/Payload.kt | 3 - .../io/bkbn/kompendium/oas/payload/Request.kt | 10 - .../bkbn/kompendium/oas/payload/Response.kt | 11 - .../bkbn/kompendium/oas/schema/AnyOfSchema.kt | 6 - .../bkbn/kompendium/oas/schema/ArraySchema.kt | 18 - .../kompendium/oas/schema/ComponentSchema.kt | 39 -- .../kompendium/oas/schema/DictionarySchema.kt | 14 - .../bkbn/kompendium/oas/schema/EnumSchema.kt | 31 - .../kompendium/oas/schema/FormattedSchema.kt | 23 - .../kompendium/oas/schema/FreeFormSchema.kt | 17 - .../kompendium/oas/schema/ObjectSchema.kt | 36 - .../kompendium/oas/schema/ReferencedSchema.kt | 11 - .../kompendium/oas/schema/SimpleSchema.kt | 17 - .../bkbn/kompendium/oas/schema/TypedSchema.kt | 7 - .../ComponentSchemaSerializer.kt | 45 -- .../io/bkbn/kompendium/oas/server/Server.kt | 13 - .../kompendium/oas/server/ServerVariable.kt | 10 - kompendium-playground/build.gradle.kts | 46 -- .../kompendium/playground/AuthPlayground.kt | 101 --- .../kompendium/playground/BasicPlayground.kt | 219 ------ .../playground/ConstraintPlayground.kt | 145 ---- .../playground/CustomTypePlayground.kt | 74 -- .../playground/ExceptionPlayground.kt | 109 --- .../playground/GenericPlayground.kt | 83 --- .../playground/GsonSerializationPlayground.kt | 49 -- .../JacksonSerializationPlayground.kt | 52 -- .../playground/LocationPlayground.kt | 130 ---- .../playground/PolymorphicPlayground.kt | 82 --- .../playground/RecursionPlayground.kt | 103 --- .../SerializerOverridePlayground.kt | 85 --- .../playground/SwaggerPlayground.kt | 94 --- kompendium-swagger-ui/Module.md | 52 -- kompendium-swagger-ui/build.gradle.kts | 35 - .../io/bkbn/kompendium/swagger/JsConfig.kt | 29 - .../io/bkbn/kompendium/swagger/JsUtils.kt | 14 - .../kompendium/swagger/ReflectionUtils.kt | 6 - .../io/bkbn/kompendium/swagger/SwaggerUI.kt | 78 --- .../kompendium/swagger/SwaggerWebJarUtils.kt | 24 - .../bkbn/kompendium/swagger/SwaggerUiTest.kt | 54 -- .../io/bkbn/kompendium/swagger/TestHelpers.kt | 79 --- {kompendium-locations => locations}/Module.md | 0 .../build.gradle.kts | 7 +- .../locations/LocationMethodParser.kt | 84 +++ .../kompendium/locations/NotarizedLocation.kt | 110 +++ .../locations/KompendiumLocationsTest.kt | 82 +++ .../kompendium/locations/util/TestModels.kt | 22 + .../kompendium/locations/util/TestModules.kt | 107 +++ .../locations/util/TestResponseInfo.kt | 97 +++ .../notarized_delete_nested_location.json | 0 .../notarized_delete_simple_location.json | 0 .../notarized_get_nested_location.json | 0 ...sted_location_from_non_location_class.json | 0 .../notarized_get_simple_location.json | 0 .../notarized_post_nested_location.json | 0 .../notarized_post_simple_location.json | 0 .../notarized_put_nested_location.json | 0 .../notarized_put_simple_location.json | 0 {kompendium-oas => oas}/Module.md | 0 {kompendium-oas => oas}/build.gradle.kts | 7 +- .../io/bkbn/kompendium/oas/OpenApiSpec.kt | 42 ++ .../oas/common/ExternalDocumentation.kt | 8 + .../io/bkbn/kompendium/oas/common/Tag.kt | 0 .../kompendium/oas/component/Components.kt | 17 + .../io/bkbn/kompendium/oas/info/Contact.kt | 22 + .../io/bkbn/kompendium/oas/info/Info.kt | 32 + .../io/bkbn/kompendium/oas/info/License.kt | 23 + .../io/bkbn/kompendium/oas/path/Path.kt | 41 ++ .../bkbn/kompendium/oas/path/PathOperation.kt | 44 ++ .../bkbn/kompendium/oas/payload/Encoding.kt | 24 + .../io/bkbn/kompendium/oas/payload/Header.kt | 25 + .../io/bkbn/kompendium/oas/payload/Link.kt | 26 + .../bkbn/kompendium/oas/payload/MediaType.kt | 24 + .../bkbn/kompendium/oas/payload/Parameter.kt | 43 ++ .../io/bkbn/kompendium/oas/payload/Request.kt | 19 + .../bkbn/kompendium/oas/payload/Response.kt | 22 + .../kompendium/oas/security/ApiKeyAuth.kt | 0 .../bkbn/kompendium/oas/security/BasicAuth.kt | 0 .../kompendium/oas/security/BearerAuth.kt | 0 .../io/bkbn/kompendium/oas/security/OAuth.kt | 0 .../kompendium/oas/security/SecuritySchema.kt | 0 .../oas/serialization/AnySerializer.kt | 0 .../KompendiumSerializersModule.kt | 0 .../oas/serialization/NumberSerializer.kt | 0 .../serialization/SecuritySchemaSerializer.kt | 0 .../oas/serialization/UriSerializer.kt | 0 .../io/bkbn/kompendium/oas/server/Server.kt | 25 + .../kompendium/oas/server/ServerVariable.kt | 19 + .../kompendium/oas/security/ApiKeyAuthTest.kt | 0 .../Module.md | 0 playground/build.gradle.kts | 44 ++ .../kompendium/playground/AuthPlayground.kt | 115 ++++ .../kompendium/playground/BasicPlayground.kt | 77 +++ .../playground/CustomTypePlayground.kt | 90 +++ .../playground/ExceptionPlayground.kt | 90 +++ .../playground/GsonSerializationPlayground.kt | 73 ++ .../JacksonSerializationPlayground.kt | 77 +++ .../bkbn/kompendium/playground/util/Models.kt | 16 + .../bkbn/kompendium/playground/util/Util.kt | 2 +- .../src/main/resources/logback.xml | 0 settings.gradle.kts | 16 +- 308 files changed, 5410 insertions(+), 10204 deletions(-) rename {kompendium-core => core}/Module.md (100%) create mode 100644 core/build.gradle.kts create mode 100644 core/src/main/kotlin/io/bkbn/kompendium/core/attribute/KompendiumAttributes.kt create mode 100644 core/src/main/kotlin/io/bkbn/kompendium/core/metadata/DeleteInfo.kt create mode 100644 core/src/main/kotlin/io/bkbn/kompendium/core/metadata/GetInfo.kt create mode 100644 core/src/main/kotlin/io/bkbn/kompendium/core/metadata/HeadInfo.kt create mode 100644 core/src/main/kotlin/io/bkbn/kompendium/core/metadata/MethodInfo.kt create mode 100644 core/src/main/kotlin/io/bkbn/kompendium/core/metadata/MethodInfoWithRequest.kt create mode 100644 core/src/main/kotlin/io/bkbn/kompendium/core/metadata/OptionsInfo.kt create mode 100644 core/src/main/kotlin/io/bkbn/kompendium/core/metadata/PatchInfo.kt create mode 100644 core/src/main/kotlin/io/bkbn/kompendium/core/metadata/PostInfo.kt create mode 100644 core/src/main/kotlin/io/bkbn/kompendium/core/metadata/PutInfo.kt create mode 100644 core/src/main/kotlin/io/bkbn/kompendium/core/metadata/RequestInfo.kt create mode 100644 core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ResponseInfo.kt create mode 100644 core/src/main/kotlin/io/bkbn/kompendium/core/plugin/NotarizedApplication.kt create mode 100644 core/src/main/kotlin/io/bkbn/kompendium/core/plugin/NotarizedRoute.kt rename {kompendium-core => core}/src/main/kotlin/io/bkbn/kompendium/core/routes/Redoc.kt (83%) create mode 100644 core/src/main/kotlin/io/bkbn/kompendium/core/util/Helpers.kt create mode 100644 core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt create mode 100644 core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt rename kompendium-core/src/test/resources/notarized_get_with_operation_id.json => core/src/test/resources/T0001__notarized_get.json (67%) rename kompendium-core/src/test/resources/notarized_post.json => core/src/test/resources/T0002__notarized_post.json (56%) rename kompendium-core/src/test/resources/notarized_put.json => core/src/test/resources/T0003__notarized_put.json (56%) rename kompendium-core/src/test/resources/generic_response.json => core/src/test/resources/T0004__notarized_delete.json (56%) rename kompendium-core/src/test/resources/notarized_patch.json => core/src/test/resources/T0005__notarized_patch.json (59%) rename kompendium-core/src/test/resources/notarized_head.json => core/src/test/resources/T0006__notarized_head.json (58%) rename kompendium-core/src/test/resources/notarized_options.json => core/src/test/resources/T0007__notarized_options.json (65%) rename kompendium-core/src/test/resources/complex_type.json => core/src/test/resources/T0008__complex_type.json (80%) rename kompendium-core/src/test/resources/notarized_primitives.json => core/src/test/resources/T0009__notarized_primitives.json (71%) rename kompendium-core/src/test/resources/response_list.json => core/src/test/resources/T0010__response_list.json (60%) rename kompendium-core/src/test/resources/non_required_params.json => core/src/test/resources/T0011__non_required_params.json (61%) rename kompendium-core/src/test/resources/path_parser.json => core/src/test/resources/T0012__path_parser.json (81%) rename kompendium-core/src/test/resources/nested_under_root.json => core/src/test/resources/T0013__root_route.json (69%) rename kompendium-core/src/test/resources/default_param.json => core/src/test/resources/T0014__nested_under_root.json (72%) create mode 100644 core/src/test/resources/T0015__trailing_slash.json rename kompendium-core/src/test/resources/notarized_get_with_exception_response.json => core/src/test/resources/T0016__notarized_get_with_exception_response.json (74%) rename kompendium-core/src/test/resources/notarized_get_with_multiple_exception_responses.json => core/src/test/resources/T0017__notarized_get_with_multiple_exception_responses.json (76%) rename kompendium-core/src/test/resources/polymorphic_error_status_codes.json => core/src/test/resources/T0018__polymorphic_error_status_codes.json (66%) rename kompendium-core/src/test/resources/generic_exception.json => core/src/test/resources/T0019__generic_exception.json (67%) rename kompendium-core/src/test/resources/example_req_and_resp.json => core/src/test/resources/T0020__example_req_and_resp.json (69%) rename kompendium-core/src/test/resources/trailing_slash.json => core/src/test/resources/T0021__example_parameters.json (78%) rename kompendium-core/src/test/resources/notarized_get.json => core/src/test/resources/T0022__query_with_default_parameter.json (78%) rename kompendium-core/src/test/resources/root_route.json => core/src/test/resources/T0023__required_param.json (81%) rename kompendium-core/src/test/resources/required_param.json => core/src/test/resources/T0024__non_required_param.json (79%) rename kompendium-core/src/test/resources/min_max_double_field.json => core/src/test/resources/T0025__default_field.json (70%) rename kompendium-core/src/test/resources/exclusive_min_max.json => core/src/test/resources/T0026__nullable_field.json (66%) rename kompendium-core/src/test/resources/polymorphic_list_response.json => core/src/test/resources/T0027__polymorphic_response.json (68%) rename kompendium-core/src/test/resources/polymorphic_map_response.json => core/src/test/resources/T0028__polymorphic_list_response.json (65%) rename kompendium-core/src/test/resources/polymorphic_response.json => core/src/test/resources/T0029__polymorphic_map_response.json (66%) rename kompendium-core/src/test/resources/formatted_no_format_string.json => core/src/test/resources/T0030__simple_generic_response.json (75%) rename kompendium-core/src/test/resources/min_max_array.json => core/src/test/resources/T0031__nested_generic_response.json (72%) rename kompendium-core/src/test/resources/polymorphic_response_with_generics.json => core/src/test/resources/T0032__polymorphic_response_with_generics.json (60%) create mode 100644 core/src/test/resources/T0033__crazy_polymorphic_example.json rename kompendium-core/src/test/resources/formatted_param_type.json => core/src/test/resources/T0034__notarized_get_with_operation_id.json (71%) rename kompendium-core/src/test/resources/override_parameter_name.json => core/src/test/resources/T0035__override_parameter_name.json (80%) create mode 100644 core/src/test/resources/T0036__nullable_fields.json rename kompendium-core/src/test/resources/nullable_enum_field.json => core/src/test/resources/T0037__nullable_enum_field.json (69%) rename kompendium-core/src/test/resources/formatted_date_time_string.json => core/src/test/resources/T0038__formatted_date_time_string.json (75%) rename {kompendium-core => core}/src/test/resources/redoc.html (100%) rename {kompendium-core => core}/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/SupportedSerializers.kt (100%) create mode 100644 core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestHelpers.kt create mode 100644 core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModels.kt rename {kompendium-core => core}/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestSpecs.kt (93%) create mode 100644 json-schema/build.gradle.kts create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/SchemaGenerator.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/AnyOfDefinition.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/ArrayDefinition.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/EnumDefinition.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/JsonSchema.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/MapDefinition.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/NullableDefinition.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/OneOfDefinition.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/ReferenceDefinition.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/TypeDefinition.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/CollectionHandler.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/EnumHandler.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/MapHandler.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/SealedObjectHandler.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/SimpleObjectHandler.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/util/Helpers.kt create mode 100644 json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/util/ReferenceCache.kt create mode 100644 json-schema/src/test/kotlin/io/bkbn/kompendium/json/schema/SchemaGeneratorTest.kt create mode 100644 json-schema/src/test/resources/T0001__scalar_int.json create mode 100644 json-schema/src/test/resources/T0002__scalar_bool.json create mode 100644 json-schema/src/test/resources/T0003__scalar_string.json create mode 100644 json-schema/src/test/resources/T0004__simple_object.json create mode 100644 json-schema/src/test/resources/T0005__complex_object.json create mode 100644 json-schema/src/test/resources/T0006__nullable_object.json create mode 100644 json-schema/src/test/resources/T0007__simple_enum.json create mode 100644 json-schema/src/test/resources/T0008__nullable_enum.json create mode 100644 json-schema/src/test/resources/T0009__scalar_array.json create mode 100644 json-schema/src/test/resources/T0010__object_array.json create mode 100644 json-schema/src/test/resources/T0011__nullable_array.json create mode 100644 json-schema/src/test/resources/T0012__scalar_map.json create mode 100644 json-schema/src/test/resources/T0013__object_map.json create mode 100644 json-schema/src/test/resources/T0014__nullable_map.json create mode 100644 json-schema/src/test/resources/T0015__polymorphic_object.json create mode 100644 json-schema/src/test/resources/T0016__recursive_object.json delete mode 100644 kompendium-annotations/Module.md delete mode 100644 kompendium-annotations/build.gradle.kts delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/Field.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/FreeFormObject.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/Param.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/ParamType.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/Referenced.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/UndeclaredField.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Format.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MaxItems.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MaxLength.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MaxProperties.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Maximum.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MinItems.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MinLength.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MinProperties.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Minimum.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MultipleOf.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Pattern.kt delete mode 100644 kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/UniqueItems.kt delete mode 100644 kompendium-auth/Module.md delete mode 100644 kompendium-auth/build.gradle.kts delete mode 100644 kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/Notarized.kt delete mode 100644 kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/ApiKeyConfiguration.kt delete mode 100644 kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/BasicAuthConfiguration.kt delete mode 100644 kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/JwtAuthConfiguration.kt delete mode 100644 kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/OAuthConfiguration.kt delete mode 100644 kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/SecurityConfiguration.kt delete mode 100644 kompendium-auth/src/test/kotlin/io/bkbn/kompendium/auth/KompendiumAuthTest.kt delete mode 100644 kompendium-auth/src/test/kotlin/io/bkbn/kompendium/auth/util/TestModules.kt delete mode 100644 kompendium-auth/src/test/resources/notarized_basic_authenticated_get.json delete mode 100644 kompendium-auth/src/test/resources/notarized_jwt_authenticated_get.json delete mode 100644 kompendium-auth/src/test/resources/notarized_oauth_all_flows.json delete mode 100644 kompendium-core/build.gradle.kts delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Kompendium.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/KompendiumPreFlight.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Kontent.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Notarized.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/constraint/ConstraintScanner.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/CollectionHandler.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/EnumHandler.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/MapHandler.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/ObjectHandler.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/SchemaHandler.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ExceptionInfo.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ParameterExample.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/RequestInfo.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ResponseInfo.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/SchemaMap.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/TypeMap.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/DeleteInfo.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/GetInfo.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/HeadInfo.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/MethodInfo.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/OptionsInfo.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PatchInfo.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PostInfo.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PutInfo.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/parser/DefaultMethodParser.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/parser/IMethodParser.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/routes/Swagger.kt delete mode 100644 kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/util/Helpers.kt delete mode 100644 kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt delete mode 100644 kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KontentTest.kt delete mode 100644 kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt delete mode 100644 kompendium-core/src/test/resources/crazy_polymorphic_example.json delete mode 100644 kompendium-core/src/test/resources/default_field.json delete mode 100644 kompendium-core/src/test/resources/example_parameters.json delete mode 100644 kompendium-core/src/test/resources/field_override.json delete mode 100644 kompendium-core/src/test/resources/formatted_array_item_type.json delete mode 100644 kompendium-core/src/test/resources/free_form_field.json delete mode 100644 kompendium-core/src/test/resources/free_form_object.json delete mode 100644 kompendium-core/src/test/resources/min_max_free_form.json delete mode 100644 kompendium-core/src/test/resources/min_max_int_field.json delete mode 100644 kompendium-core/src/test/resources/min_max_string.json delete mode 100644 kompendium-core/src/test/resources/multiple_of_double.json delete mode 100644 kompendium-core/src/test/resources/multiple_of_int.json delete mode 100644 kompendium-core/src/test/resources/notarized_delete.json delete mode 100644 kompendium-core/src/test/resources/nullable_field.json delete mode 100644 kompendium-core/src/test/resources/nullable_fields.json delete mode 100644 kompendium-core/src/test/resources/petstore.json delete mode 100644 kompendium-core/src/test/resources/query_with_default_parameter.json delete mode 100644 kompendium-core/src/test/resources/regex_string.json delete mode 100644 kompendium-core/src/test/resources/sealed_interface_response.json delete mode 100644 kompendium-core/src/test/resources/simple_recursive.json delete mode 100644 kompendium-core/src/test/resources/undeclared_field.json delete mode 100644 kompendium-core/src/test/resources/unique_array.json delete mode 100644 kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestHelpers.kt delete mode 100644 kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModels.kt delete mode 100644 kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModules.kt delete mode 100644 kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestResponseInfo.kt delete mode 100644 kompendium-locations/src/main/kotlin/io/bkbn/kompendium/locations/LocationMethodParser.kt delete mode 100644 kompendium-locations/src/main/kotlin/io/bkbn/kompendium/locations/NotarizedLocation.kt delete mode 100644 kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/KompendiumLocationsTest.kt delete mode 100644 kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModels.kt delete mode 100644 kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModules.kt delete mode 100644 kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestResponseInfo.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/OpenApiSpec.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/component/Components.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Contact.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Info.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/info/License.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/path/Path.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/path/PathOperation.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/AnyOfPayload.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/MediaType.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Parameter.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Payload.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Request.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Response.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/AnyOfSchema.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ArraySchema.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ComponentSchema.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/DictionarySchema.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/EnumSchema.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/FormattedSchema.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/FreeFormSchema.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ObjectSchema.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ReferencedSchema.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/SimpleSchema.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/TypedSchema.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/ComponentSchemaSerializer.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/server/Server.kt delete mode 100644 kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/server/ServerVariable.kt delete mode 100644 kompendium-playground/build.gradle.kts delete mode 100644 kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/AuthPlayground.kt delete mode 100644 kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/BasicPlayground.kt delete mode 100644 kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/ConstraintPlayground.kt delete mode 100644 kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/CustomTypePlayground.kt delete mode 100644 kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/ExceptionPlayground.kt delete mode 100644 kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/GenericPlayground.kt delete mode 100644 kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/GsonSerializationPlayground.kt delete mode 100644 kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/JacksonSerializationPlayground.kt delete mode 100644 kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/LocationPlayground.kt delete mode 100644 kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/PolymorphicPlayground.kt delete mode 100644 kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/RecursionPlayground.kt delete mode 100644 kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/SerializerOverridePlayground.kt delete mode 100644 kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/SwaggerPlayground.kt delete mode 100644 kompendium-swagger-ui/Module.md delete mode 100644 kompendium-swagger-ui/build.gradle.kts delete mode 100644 kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/JsConfig.kt delete mode 100644 kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/JsUtils.kt delete mode 100644 kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/ReflectionUtils.kt delete mode 100644 kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerUI.kt delete mode 100644 kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerWebJarUtils.kt delete mode 100644 kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/SwaggerUiTest.kt delete mode 100644 kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/TestHelpers.kt rename {kompendium-locations => locations}/Module.md (100%) rename {kompendium-locations => locations}/build.gradle.kts (75%) create mode 100644 locations/src/main/kotlin/io/bkbn/kompendium/locations/LocationMethodParser.kt create mode 100644 locations/src/main/kotlin/io/bkbn/kompendium/locations/NotarizedLocation.kt create mode 100644 locations/src/test/kotlin/io/bkbn/kompendium/locations/KompendiumLocationsTest.kt create mode 100644 locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModels.kt create mode 100644 locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModules.kt create mode 100644 locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestResponseInfo.kt rename {kompendium-locations => locations}/src/test/resources/notarized_delete_nested_location.json (100%) rename {kompendium-locations => locations}/src/test/resources/notarized_delete_simple_location.json (100%) rename {kompendium-locations => locations}/src/test/resources/notarized_get_nested_location.json (100%) rename {kompendium-locations => locations}/src/test/resources/notarized_get_nested_location_from_non_location_class.json (100%) rename {kompendium-locations => locations}/src/test/resources/notarized_get_simple_location.json (100%) rename {kompendium-locations => locations}/src/test/resources/notarized_post_nested_location.json (100%) rename {kompendium-locations => locations}/src/test/resources/notarized_post_simple_location.json (100%) rename {kompendium-locations => locations}/src/test/resources/notarized_put_nested_location.json (100%) rename {kompendium-locations => locations}/src/test/resources/notarized_put_simple_location.json (100%) rename {kompendium-oas => oas}/Module.md (100%) rename {kompendium-oas => oas}/build.gradle.kts (82%) create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/OpenApiSpec.kt rename {kompendium-oas => oas}/src/main/kotlin/io/bkbn/kompendium/oas/common/ExternalDocumentation.kt (52%) rename {kompendium-oas => oas}/src/main/kotlin/io/bkbn/kompendium/oas/common/Tag.kt (100%) create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/component/Components.kt create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Contact.kt create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Info.kt create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/info/License.kt create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/path/Path.kt create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/path/PathOperation.kt create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Encoding.kt create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Header.kt create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Link.kt create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/MediaType.kt create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Parameter.kt create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Request.kt create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Response.kt rename {kompendium-oas => oas}/src/main/kotlin/io/bkbn/kompendium/oas/security/ApiKeyAuth.kt (100%) rename {kompendium-oas => oas}/src/main/kotlin/io/bkbn/kompendium/oas/security/BasicAuth.kt (100%) rename {kompendium-oas => oas}/src/main/kotlin/io/bkbn/kompendium/oas/security/BearerAuth.kt (100%) rename {kompendium-oas => oas}/src/main/kotlin/io/bkbn/kompendium/oas/security/OAuth.kt (100%) rename {kompendium-oas => oas}/src/main/kotlin/io/bkbn/kompendium/oas/security/SecuritySchema.kt (100%) rename {kompendium-oas => oas}/src/main/kotlin/io/bkbn/kompendium/oas/serialization/AnySerializer.kt (100%) rename {kompendium-oas => oas}/src/main/kotlin/io/bkbn/kompendium/oas/serialization/KompendiumSerializersModule.kt (100%) rename {kompendium-oas => oas}/src/main/kotlin/io/bkbn/kompendium/oas/serialization/NumberSerializer.kt (100%) rename {kompendium-oas => oas}/src/main/kotlin/io/bkbn/kompendium/oas/serialization/SecuritySchemaSerializer.kt (100%) rename {kompendium-oas => oas}/src/main/kotlin/io/bkbn/kompendium/oas/serialization/UriSerializer.kt (100%) create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/server/Server.kt create mode 100644 oas/src/main/kotlin/io/bkbn/kompendium/oas/server/ServerVariable.kt rename {kompendium-oas => oas}/src/test/kotlin/io/bkbn/kompendium/oas/security/ApiKeyAuthTest.kt (100%) rename {kompendium-playground => playground}/Module.md (100%) create mode 100644 playground/build.gradle.kts create mode 100644 playground/src/main/kotlin/io/bkbn/kompendium/playground/AuthPlayground.kt create mode 100644 playground/src/main/kotlin/io/bkbn/kompendium/playground/BasicPlayground.kt create mode 100644 playground/src/main/kotlin/io/bkbn/kompendium/playground/CustomTypePlayground.kt create mode 100644 playground/src/main/kotlin/io/bkbn/kompendium/playground/ExceptionPlayground.kt create mode 100644 playground/src/main/kotlin/io/bkbn/kompendium/playground/GsonSerializationPlayground.kt create mode 100644 playground/src/main/kotlin/io/bkbn/kompendium/playground/JacksonSerializationPlayground.kt create mode 100644 playground/src/main/kotlin/io/bkbn/kompendium/playground/util/Models.kt rename {kompendium-playground => playground}/src/main/kotlin/io/bkbn/kompendium/playground/util/Util.kt (96%) rename {kompendium-playground => playground}/src/main/resources/logback.xml (100%) diff --git a/build.gradle.kts b/build.gradle.kts index e54b69c46..9ae5ace4c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,8 @@ plugins { - kotlin("jvm") version "1.6.21" apply false - kotlin("plugin.serialization") version "1.6.21" apply false - id("io.bkbn.sourdough.library.jvm") version "0.6.0" apply false - id("io.bkbn.sourdough.application.jvm") version "0.6.0" apply false + kotlin("jvm") version "1.7.10" apply false + kotlin("plugin.serialization") version "1.7.10" apply false + id("io.bkbn.sourdough.library.jvm") version "0.9.0" apply false + id("io.bkbn.sourdough.application.jvm") version "0.9.0" apply false id("io.bkbn.sourdough.root") version "0.9.0" id("com.github.jakemarsden.git-hooks") version "0.0.2" id("org.jetbrains.dokka") version "1.7.10" diff --git a/kompendium-core/Module.md b/core/Module.md similarity index 100% rename from kompendium-core/Module.md rename to core/Module.md diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 000000000..4a5cd6fae --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,62 @@ +plugins { + kotlin("jvm") + kotlin("plugin.serialization") + id("io.bkbn.sourdough.library.jvm") + id("io.gitlab.arturbosch.detekt") + id("com.adarshr.test-logger") + id("org.jetbrains.dokka") + id("maven-publish") + id("java-library") + id("signing") + id("java-test-fixtures") +} + +sourdoughLibrary { + libraryName.set("Kompendium Core") + libraryDescription.set("Core functionality for the Kompendium library") + compilerArgs.set(listOf("-opt-in=kotlin.RequiresOptIn")) +} + +dependencies { + // VERSIONS + val kotestVersion: String by project + val ktorVersion: String by project + + // IMPLEMENTATION + + api(projects.kompendiumOas) + api(projects.kompendiumJsonSchema) + + implementation("io.ktor:ktor-server-core:$ktorVersion") + implementation("io.ktor:ktor-server-cio:$ktorVersion") + implementation("io.ktor:ktor-server-html-builder:$ktorVersion") + implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") + implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") + implementation("ch.qos.logback:logback-classic:1.2.11") + + // TEST FIXTURES + + testFixturesApi("io.kotest:kotest-runner-junit5-jvm:$kotestVersion") + testFixturesApi("io.kotest:kotest-assertions-core-jvm:$kotestVersion") + testFixturesApi("io.kotest:kotest-property-jvm:$kotestVersion") + testFixturesApi("io.kotest:kotest-assertions-json-jvm:$kotestVersion") + testFixturesApi("io.kotest:kotest-assertions-ktor-jvm:4.4.3") + + testFixturesApi("io.ktor:ktor-server-core:$ktorVersion") + testFixturesApi("io.ktor:ktor-server-test-host:$ktorVersion") + testFixturesApi("io.ktor:ktor-serialization:$ktorVersion") + testFixturesApi("io.ktor:ktor-serialization-jackson:$ktorVersion") + testFixturesApi("io.ktor:ktor-serialization-gson:$ktorVersion") + testFixturesApi("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") + testFixturesApi("io.ktor:ktor-server-content-negotiation:$ktorVersion") + + testFixturesApi("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3") +} + +testing { + suites { + named("test", JvmTestSuite::class) { + useJUnitJupiter() + } + } +} diff --git a/core/src/main/kotlin/io/bkbn/kompendium/core/attribute/KompendiumAttributes.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/attribute/KompendiumAttributes.kt new file mode 100644 index 000000000..348c6a6a4 --- /dev/null +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/attribute/KompendiumAttributes.kt @@ -0,0 +1,8 @@ +package io.bkbn.kompendium.core.attribute + +import io.bkbn.kompendium.oas.OpenApiSpec +import io.ktor.util.AttributeKey + +object KompendiumAttributes { + val openApiSpec = AttributeKey("OpenApiSpec") +} diff --git a/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/DeleteInfo.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/DeleteInfo.kt new file mode 100644 index 000000000..2e5752cec --- /dev/null +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/DeleteInfo.kt @@ -0,0 +1,40 @@ +package io.bkbn.kompendium.core.metadata + +import io.bkbn.kompendium.oas.common.ExternalDocumentation +import io.bkbn.kompendium.oas.payload.Parameter + +class DeleteInfo private constructor( + override val response: ResponseInfo, + override val errors: MutableList, + override val tags: Set, + override val summary: String, + override val description: String, + override val externalDocumentation: ExternalDocumentation?, + override val operationId: String?, + override val deprecated: Boolean, + override val parameters: List +): MethodInfo { + + companion object { + fun builder(init: Builder.() -> Unit): DeleteInfo { + val builder = Builder() + builder.init() + return builder.build() + } + } + + class Builder : MethodInfo.Builder() { + override fun build() = DeleteInfo( + response = response!!, + errors = errors, + tags = tags, + summary = summary!!, + description = description!!, + externalDocumentation = externalDocumentation, + operationId = operationId, + deprecated = deprecated, + parameters = parameters + ) + } + +} diff --git a/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/GetInfo.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/GetInfo.kt new file mode 100644 index 000000000..c221ad817 --- /dev/null +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/GetInfo.kt @@ -0,0 +1,40 @@ +package io.bkbn.kompendium.core.metadata + +import io.bkbn.kompendium.oas.common.ExternalDocumentation +import io.bkbn.kompendium.oas.payload.Parameter + +class GetInfo private constructor( + override val response: ResponseInfo, + override val errors: MutableList, + override val tags: Set, + override val summary: String, + override val description: String, + override val externalDocumentation: ExternalDocumentation?, + override val operationId: String?, + override val deprecated: Boolean, + override val parameters: List +): MethodInfo { + + companion object { + fun builder(init: Builder.() -> Unit): GetInfo { + val builder = Builder() + builder.init() + return builder.build() + } + } + + class Builder : MethodInfo.Builder() { + override fun build() = GetInfo( + response = response ?: error("You must provide a response in order to notarize a GET"), + errors = errors, + tags = tags, + summary = summary ?: error("You must provide a summary in order to notarize a GET"), + description = description ?: error("You must provide a description in order to notarize a GET"), + externalDocumentation = externalDocumentation, + operationId = operationId, + deprecated = deprecated, + parameters = parameters + ) + } + +} diff --git a/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/HeadInfo.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/HeadInfo.kt new file mode 100644 index 000000000..accee2de0 --- /dev/null +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/HeadInfo.kt @@ -0,0 +1,40 @@ +package io.bkbn.kompendium.core.metadata + +import io.bkbn.kompendium.oas.common.ExternalDocumentation +import io.bkbn.kompendium.oas.payload.Parameter + +class HeadInfo private constructor( + override val response: ResponseInfo, + override val errors: MutableList, + override val tags: Set, + override val summary: String, + override val description: String, + override val externalDocumentation: ExternalDocumentation?, + override val operationId: String?, + override val deprecated: Boolean, + override val parameters: List +): MethodInfo { + + companion object { + fun builder(init: Builder.() -> Unit): HeadInfo { + val builder = Builder() + builder.init() + return builder.build() + } + } + + class Builder : MethodInfo.Builder() { + override fun build() = HeadInfo( + response = response!!, + errors = errors, + tags = tags, + summary = summary!!, + description = description!!, + externalDocumentation = externalDocumentation, + operationId = operationId, + deprecated = deprecated, + parameters = parameters + ) + } + +} diff --git a/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/MethodInfo.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/MethodInfo.kt new file mode 100644 index 000000000..ca2706ac2 --- /dev/null +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/MethodInfo.kt @@ -0,0 +1,64 @@ +package io.bkbn.kompendium.core.metadata + +import io.bkbn.kompendium.oas.common.ExternalDocumentation +import io.bkbn.kompendium.oas.payload.Parameter + +sealed interface MethodInfo { + val response: ResponseInfo + val errors: List + val tags: Set + val summary: String + val description: String + val externalDocumentation: ExternalDocumentation? + get() = null + val operationId: String? + get() = null + val deprecated: Boolean + get() = false + val parameters: List + get() = emptyList() + + abstract class Builder { + internal var response: ResponseInfo? = null + internal var summary: String? = null + internal var description: String? = null + internal var externalDocumentation: ExternalDocumentation? = null + internal var operationId: String? = null + internal var deprecated: Boolean = false + internal var tags: Set = emptySet() + internal var parameters: List = emptyList() + internal var errors: MutableList = mutableListOf() + + fun response(init: ResponseInfo.Builder.() -> Unit) = apply { + val builder = ResponseInfo.Builder() + builder.init() + this.response = builder.build() + } + + fun canRespond(init: ResponseInfo.Builder.() -> Unit) = apply { + val builder = ResponseInfo.Builder() + builder.init() + errors.add(builder.build()) + } + + fun canRespond(responses: List) = apply { + errors.addAll(responses) + } + + fun summary(s: String) = apply { this.summary = s } + + fun description(s: String) = apply { this.description = s } + + fun externalDocumentation(docs: ExternalDocumentation) = apply { this.externalDocumentation = docs } + + fun operationId(id: String) = apply { this.operationId = id } + + fun isDeprecated() = apply { this.deprecated = true } + + fun tags(vararg tags: String) = apply { this.tags = tags.toSet() } + + fun parameters(vararg parameters: Parameter) = apply { this.parameters = parameters.toList() } + + abstract fun build(): T + } +} diff --git a/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/MethodInfoWithRequest.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/MethodInfoWithRequest.kt new file mode 100644 index 000000000..a28275aaf --- /dev/null +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/MethodInfoWithRequest.kt @@ -0,0 +1,15 @@ +package io.bkbn.kompendium.core.metadata + +sealed interface MethodInfoWithRequest : MethodInfo { + val request: RequestInfo + + abstract class Builder : MethodInfo.Builder() { + internal var request: RequestInfo? = null + + fun request(init: RequestInfo.Builder.() -> Unit) = apply { + val builder = RequestInfo.Builder() + builder.init() + this.request = builder.build() + } + } +} diff --git a/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/OptionsInfo.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/OptionsInfo.kt new file mode 100644 index 000000000..c56fc319f --- /dev/null +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/OptionsInfo.kt @@ -0,0 +1,40 @@ +package io.bkbn.kompendium.core.metadata + +import io.bkbn.kompendium.oas.common.ExternalDocumentation +import io.bkbn.kompendium.oas.payload.Parameter + +class OptionsInfo private constructor( + override val response: ResponseInfo, + override val errors: MutableList, + override val tags: Set, + override val summary: String, + override val description: String, + override val externalDocumentation: ExternalDocumentation?, + override val operationId: String?, + override val deprecated: Boolean, + override val parameters: List +): MethodInfo { + + companion object { + fun builder(init: Builder.() -> Unit): OptionsInfo { + val builder = Builder() + builder.init() + return builder.build() + } + } + + class Builder : MethodInfo.Builder() { + override fun build() = OptionsInfo( + response = response!!, + errors = errors, + tags = tags, + summary = summary!!, + description = description!!, + externalDocumentation = externalDocumentation, + operationId = operationId, + deprecated = deprecated, + parameters = parameters + ) + } + +} diff --git a/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/PatchInfo.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/PatchInfo.kt new file mode 100644 index 000000000..4551a670a --- /dev/null +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/PatchInfo.kt @@ -0,0 +1,42 @@ +package io.bkbn.kompendium.core.metadata + +import io.bkbn.kompendium.oas.common.ExternalDocumentation +import io.bkbn.kompendium.oas.payload.Parameter + +class PatchInfo private constructor( + override val request: RequestInfo, + override val errors: MutableList, + override val response: ResponseInfo, + override val tags: Set, + override val summary: String, + override val description: String, + override val externalDocumentation: ExternalDocumentation?, + override val operationId: String?, + override val deprecated: Boolean, + override val parameters: List +): MethodInfoWithRequest { + + companion object { + fun builder(init: Builder.() -> Unit): PatchInfo { + val builder = Builder() + builder.init() + return builder.build() + } + } + + class Builder : MethodInfoWithRequest.Builder() { + override fun build() = PatchInfo( + request = request!!, + errors = errors, + response = response!!, + tags = tags, + summary = summary!!, + description = description!!, + externalDocumentation = externalDocumentation, + operationId = operationId, + deprecated = deprecated, + parameters = parameters + ) + } + +} diff --git a/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/PostInfo.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/PostInfo.kt new file mode 100644 index 000000000..666137fcb --- /dev/null +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/PostInfo.kt @@ -0,0 +1,42 @@ +package io.bkbn.kompendium.core.metadata + +import io.bkbn.kompendium.oas.common.ExternalDocumentation +import io.bkbn.kompendium.oas.payload.Parameter + +class PostInfo private constructor( + override val request: RequestInfo, + override val errors: MutableList, + override val response: ResponseInfo, + override val tags: Set, + override val summary: String, + override val description: String, + override val externalDocumentation: ExternalDocumentation?, + override val operationId: String?, + override val deprecated: Boolean, + override val parameters: List +): MethodInfoWithRequest { + + companion object { + fun builder(init: Builder.() -> Unit): PostInfo { + val builder = Builder() + builder.init() + return builder.build() + } + } + + class Builder : MethodInfoWithRequest.Builder() { + override fun build() = PostInfo( + request = request!!, + errors = errors, + response = response!!, + tags = tags, + summary = summary!!, + description = description!!, + externalDocumentation = externalDocumentation, + operationId = operationId, + deprecated = deprecated, + parameters = parameters + ) + } + +} diff --git a/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/PutInfo.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/PutInfo.kt new file mode 100644 index 000000000..b6a0af0f7 --- /dev/null +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/PutInfo.kt @@ -0,0 +1,42 @@ +package io.bkbn.kompendium.core.metadata + +import io.bkbn.kompendium.oas.common.ExternalDocumentation +import io.bkbn.kompendium.oas.payload.Parameter + +class PutInfo private constructor( + override val request: RequestInfo, + override val errors: MutableList, + override val response: ResponseInfo, + override val tags: Set, + override val summary: String, + override val description: String, + override val externalDocumentation: ExternalDocumentation?, + override val operationId: String?, + override val deprecated: Boolean, + override val parameters: List +): MethodInfoWithRequest { + + companion object { + fun builder(init: Builder.() -> Unit): PutInfo { + val builder = Builder() + builder.init() + return builder.build() + } + } + + class Builder : MethodInfoWithRequest.Builder() { + override fun build() = PutInfo( + request = request!!, + errors = errors, + response = response!!, + tags = tags, + summary = summary!!, + description = description!!, + externalDocumentation = externalDocumentation, + operationId = operationId, + deprecated = deprecated, + parameters = parameters + ) + } + +} diff --git a/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/RequestInfo.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/RequestInfo.kt new file mode 100644 index 000000000..99a4c4473 --- /dev/null +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/RequestInfo.kt @@ -0,0 +1,46 @@ +package io.bkbn.kompendium.core.metadata + +import io.bkbn.kompendium.oas.payload.MediaType +import kotlin.reflect.KType +import kotlin.reflect.typeOf + +class RequestInfo private constructor( + val requestType: KType, + val description: String, + val examples: Map? +) { + + companion object { + fun builder(init: Builder.() -> Unit): RequestInfo { + val builder = Builder() + builder.init() + return builder.build() + } + } + + class Builder { + private var requestType: KType? = null + private var description: String? = null + private var examples: Map? = null + + fun requestType(t: KType) = apply { + this.requestType = t + } + + inline fun requestType() = apply { requestType(typeOf()) } + + fun description(s: String) = apply { this.description = s } + + fun examples(vararg e: Pair) = apply { + this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) } + println(this.examples) + } + + fun build() = RequestInfo( + requestType = requestType!!, + description = description!!, + examples = examples + ) + } + +} diff --git a/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ResponseInfo.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ResponseInfo.kt new file mode 100644 index 000000000..74daa24a0 --- /dev/null +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ResponseInfo.kt @@ -0,0 +1,53 @@ +package io.bkbn.kompendium.core.metadata + +import io.bkbn.kompendium.oas.payload.MediaType +import io.ktor.http.HttpStatusCode +import kotlin.reflect.KType +import kotlin.reflect.typeOf + +class ResponseInfo private constructor( + val responseCode: HttpStatusCode, + val responseType: KType, + val description: String, + val examples: Map? +) { + + companion object { + fun builder(init: Builder.() -> Unit): ResponseInfo { + val builder = Builder() + builder.init() + return builder.build() + } + } + + class Builder { + private var responseCode: HttpStatusCode? = null + private var responseType: KType? = null + private var description: String? = null + private var examples: Map? = null + + fun responseCode(code: HttpStatusCode) = apply { + this.responseCode = code + } + + fun responseType(t: KType) = apply { + this.responseType = t + } + + inline fun responseType() = apply { responseType(typeOf()) } + + fun description(s: String) = apply { this.description = s } + + fun examples(vararg e: Pair) = apply { + this.examples = e.toMap().mapValues { (_, v) -> MediaType.Example(v) } + println(this.examples) + } + + fun build() = ResponseInfo( + responseCode = responseCode ?: error("You must provide a response code in order to build a Response!"), + responseType = responseType ?: error("You must provide a response type in order to build a Response!"), + description = description ?: error("You must provide a description in order to build a Response!"), + examples = examples + ) + } +} diff --git a/core/src/main/kotlin/io/bkbn/kompendium/core/plugin/NotarizedApplication.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/plugin/NotarizedApplication.kt new file mode 100644 index 000000000..a329652e0 --- /dev/null +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/plugin/NotarizedApplication.kt @@ -0,0 +1,45 @@ +package io.bkbn.kompendium.core.plugin + +import io.bkbn.kompendium.core.attribute.KompendiumAttributes +import io.bkbn.kompendium.json.schema.definition.JsonSchema +import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug +import io.bkbn.kompendium.oas.OpenApiSpec +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.call +import io.ktor.server.application.createApplicationPlugin +import io.ktor.server.response.respond +import io.ktor.server.routing.Routing +import io.ktor.server.routing.application +import io.ktor.server.routing.get +import io.ktor.server.routing.route +import io.ktor.server.routing.routing +import kotlin.reflect.KClass +import kotlin.reflect.KType + +object NotarizedApplication { + + class Config { + lateinit var spec: OpenApiSpec + var openApiJson: Routing.() -> Unit = { + route("/openapi.json") { + get { + call.respond(HttpStatusCode.OK, this@route.application.attributes[KompendiumAttributes.openApiSpec]) + } + } + } + var customTypes: Map = emptyMap() + } + + operator fun invoke() = createApplicationPlugin( + name = "NotarizedApplication", + createConfiguration = ::Config + ) { + val spec = pluginConfig.spec + val routing = application.routing { } + pluginConfig.openApiJson(routing) + pluginConfig.customTypes.forEach { (type, schema) -> + spec.components.schemas[type.getSimpleSlug()] = schema + } + application.attributes.put(KompendiumAttributes.openApiSpec, spec) + } +} diff --git a/core/src/main/kotlin/io/bkbn/kompendium/core/plugin/NotarizedRoute.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/plugin/NotarizedRoute.kt new file mode 100644 index 000000000..bd86e643e --- /dev/null +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/plugin/NotarizedRoute.kt @@ -0,0 +1,171 @@ +package io.bkbn.kompendium.core.plugin + +import io.bkbn.kompendium.core.attribute.KompendiumAttributes +import io.bkbn.kompendium.core.metadata.DeleteInfo +import io.bkbn.kompendium.core.metadata.GetInfo +import io.bkbn.kompendium.core.metadata.HeadInfo +import io.bkbn.kompendium.core.metadata.MethodInfo +import io.bkbn.kompendium.core.metadata.MethodInfoWithRequest +import io.bkbn.kompendium.core.metadata.OptionsInfo +import io.bkbn.kompendium.core.metadata.PatchInfo +import io.bkbn.kompendium.core.metadata.PostInfo +import io.bkbn.kompendium.core.metadata.PutInfo +import io.bkbn.kompendium.core.metadata.ResponseInfo +import io.bkbn.kompendium.core.util.Helpers.getReferenceSlug +import io.bkbn.kompendium.core.util.Helpers.getSimpleSlug +import io.bkbn.kompendium.json.schema.SchemaGenerator +import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition +import io.bkbn.kompendium.oas.OpenApiSpec +import io.bkbn.kompendium.oas.path.Path +import io.bkbn.kompendium.oas.path.PathOperation +import io.bkbn.kompendium.oas.payload.MediaType +import io.bkbn.kompendium.oas.payload.Parameter +import io.bkbn.kompendium.oas.payload.Request +import io.bkbn.kompendium.oas.payload.Response +import io.ktor.server.application.ApplicationCallPipeline +import io.ktor.server.application.Hook +import io.ktor.server.application.createRouteScopedPlugin +import io.ktor.server.routing.Route +import kotlin.reflect.KClass +import kotlin.reflect.KType + +object NotarizedRoute { + + class Config { + var tags: Set = emptySet() + var parameters: List = emptyList() + var get: GetInfo? = null + var post: PostInfo? = null + var put: PutInfo? = null + var delete: DeleteInfo? = null + var patch: PatchInfo? = null + var head: HeadInfo? = null + var options: OptionsInfo? = null + var security: Map>? = null + internal var path: Path? = null + } + + private object InstallHook : Hook<(ApplicationCallPipeline) -> Unit> { + override fun install(pipeline: ApplicationCallPipeline, handler: (ApplicationCallPipeline) -> Unit) { + handler(pipeline) + } + } + + operator fun invoke() = createRouteScopedPlugin( + name = "NotarizedRoute", + createConfiguration = ::Config + ) { + + // This is required in order to introspect the route path + on(InstallHook) { + val route = it as? Route ?: return@on + val spec = application.attributes[KompendiumAttributes.openApiSpec] + val routePath = route.calculateRoutePath() + require(spec.paths[routePath] == null) { + """ + The specified path ${Parameter.Location.path} has already been documented! + Please make sure that all notarized paths are unique + """.trimIndent() + } + spec.paths[routePath] = pluginConfig.path!! + } + + val spec = application.attributes[KompendiumAttributes.openApiSpec] + + val path = Path() + path.parameters = pluginConfig.parameters + + pluginConfig.get?.addToSpec(path, spec, pluginConfig) + pluginConfig.delete?.addToSpec(path, spec, pluginConfig) + pluginConfig.head?.addToSpec(path, spec, pluginConfig) + pluginConfig.options?.addToSpec(path, spec, pluginConfig) + pluginConfig.post?.addToSpec(path, spec, pluginConfig) + pluginConfig.put?.addToSpec(path, spec, pluginConfig) + pluginConfig.patch?.addToSpec(path, spec, pluginConfig) + + pluginConfig.path = path + } + + private fun MethodInfo.addToSpec(path: Path, spec: OpenApiSpec, config: Config) { + SchemaGenerator.fromTypeOrUnit(this.response.responseType, spec.components.schemas)?.let { schema -> + spec.components.schemas[this.response.responseType.getSimpleSlug()] = schema + } + + errors.forEach { error -> + SchemaGenerator.fromTypeOrUnit(error.responseType, spec.components.schemas)?.let { schema -> + spec.components.schemas[error.responseType.getSimpleSlug()] = schema + } + } + + when (this) { + is MethodInfoWithRequest -> { + SchemaGenerator.fromTypeOrUnit(this.request.requestType, spec.components.schemas)?.let { schema -> + spec.components.schemas[this.request.requestType.getSimpleSlug()] = schema + } + } + + else -> {} + } + + val operations = this.toPathOperation(config) + + when (this) { + is DeleteInfo -> path.delete = operations + is GetInfo -> path.get = operations + is HeadInfo -> path.head = operations + is PatchInfo -> path.patch = operations + is PostInfo -> path.post = operations + is PutInfo -> path.put = operations + is OptionsInfo -> path.options = operations + } + } + + private fun MethodInfo.toPathOperation(config: Config) = PathOperation( + tags = config.tags.plus(this.tags), + summary = this.summary, + description = this.description, + externalDocs = this.externalDocumentation, + operationId = this.operationId, + deprecated = this.deprecated, + parameters = this.parameters, + security = config.security + ?.map { (k, v) -> k to v } + ?.map { listOf(it).toMap() } + ?.toList(), + requestBody = when (this) { + is MethodInfoWithRequest -> Request( + description = this.request.description, + content = this.request.requestType.toReferenceContent(this.request.examples), + required = true + ) + + else -> null + }, + responses = mapOf( + this.response.responseCode.value to Response( + description = this.response.description, + content = this.response.responseType.toReferenceContent(this.response.examples) + ) + ).plus(this.errors.toResponseMap()) + ) + + private fun List.toResponseMap(): Map = associate { error -> + error.responseCode.value to Response( + description = error.description, + content = error.responseType.toReferenceContent(error.examples) + ) + } + + private fun KType.toReferenceContent(examples: Map?): Map? = + when (this.classifier as KClass<*>) { + Unit::class -> null + else -> mapOf( + "application/json" to MediaType( + schema = ReferenceDefinition(this.getReferenceSlug()), + examples = examples + ) + ) + } + + private fun Route.calculateRoutePath() = toString().replace(Regex("/\\(.+\\)"), "") +} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/routes/Redoc.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/routes/Redoc.kt similarity index 83% rename from kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/routes/Redoc.kt rename to core/src/main/kotlin/io/bkbn/kompendium/core/routes/Redoc.kt index 242ac46a7..df1262c5e 100644 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/routes/Redoc.kt +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/routes/Redoc.kt @@ -1,10 +1,11 @@ package io.bkbn.kompendium.core.routes -import io.ktor.application.call -import io.ktor.html.respondHtml -import io.ktor.routing.Routing -import io.ktor.routing.get -import io.ktor.routing.route +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.call +import io.ktor.server.html.respondHtml +import io.ktor.server.routing.Routing +import io.ktor.server.routing.get +import io.ktor.server.routing.route import kotlinx.html.body import kotlinx.html.head import kotlinx.html.link @@ -22,7 +23,7 @@ import kotlinx.html.unsafe fun Routing.redoc(pageTitle: String = "Docs", specUrl: String = "/openapi.json") { route("/docs") { get { - call.respondHtml { + call.respondHtml(HttpStatusCode.OK) { head { title { +pageTitle diff --git a/core/src/main/kotlin/io/bkbn/kompendium/core/util/Helpers.kt b/core/src/main/kotlin/io/bkbn/kompendium/core/util/Helpers.kt new file mode 100644 index 000000000..56826efbe --- /dev/null +++ b/core/src/main/kotlin/io/bkbn/kompendium/core/util/Helpers.kt @@ -0,0 +1,35 @@ +package io.bkbn.kompendium.core.util + +import kotlin.reflect.KClass +import kotlin.reflect.KProperty +import kotlin.reflect.KType +import kotlin.reflect.full.createType +import kotlin.reflect.jvm.javaField +import org.slf4j.LoggerFactory +import java.lang.reflect.ParameterizedType +import java.util.Locale + +object Helpers { + + private const val COMPONENT_SLUG = "#/components/schemas" + + fun KType.getSimpleSlug(): String = when { + this.arguments.isNotEmpty() -> genericNameAdapter(this, classifier as KClass<*>) + else -> (classifier as KClass<*>).simpleName ?: error("Could not determine simple name for $this") + } + + fun KType.getReferenceSlug(): String = when { + arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}" + else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).simpleName}" + } + + /** + * Adapts a class with type parameters into a reference friendly string + */ + private fun genericNameAdapter(type: KType, clazz: KClass<*>): String { + val classNames = type.arguments + .map { it.type?.classifier as KClass<*> } + .map { it.simpleName } + return classNames.joinToString(separator = "-", prefix = "${clazz.simpleName}-") + } +} diff --git a/core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt b/core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt new file mode 100644 index 000000000..f88bc4c5e --- /dev/null +++ b/core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt @@ -0,0 +1,201 @@ +package io.bkbn.kompendium.core + +import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers +import io.bkbn.kompendium.core.util.TestModules.complexRequest +import io.bkbn.kompendium.core.util.TestModules.dateTimeString +import io.bkbn.kompendium.core.util.TestModules.defaultField +import io.bkbn.kompendium.core.util.TestModules.defaultParameter +import io.bkbn.kompendium.core.util.TestModules.exampleParams +import io.bkbn.kompendium.core.util.TestModules.nestedUnderRoot +import io.bkbn.kompendium.core.util.TestModules.nonRequiredParams +import io.bkbn.kompendium.core.util.TestModules.notarizedDelete +import io.bkbn.kompendium.core.util.TestModules.notarizedGet +import io.bkbn.kompendium.core.util.TestModules.singleException +import io.bkbn.kompendium.core.util.TestModules.genericException +import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponse +import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponseMultipleImpls +import io.bkbn.kompendium.core.util.TestModules.headerParameter +import io.bkbn.kompendium.core.util.TestModules.multipleExceptions +import io.bkbn.kompendium.core.util.TestModules.nestedGenericResponse +import io.bkbn.kompendium.core.util.TestModules.nonRequiredParam +import io.bkbn.kompendium.core.util.TestModules.polymorphicException +import io.bkbn.kompendium.core.util.TestModules.notarizedHead +import io.bkbn.kompendium.core.util.TestModules.notarizedOptions +import io.bkbn.kompendium.core.util.TestModules.notarizedPatch +import io.bkbn.kompendium.core.util.TestModules.notarizedPost +import io.bkbn.kompendium.core.util.TestModules.notarizedPut +import io.bkbn.kompendium.core.util.TestModules.nullableEnumField +import io.bkbn.kompendium.core.util.TestModules.nullableField +import io.bkbn.kompendium.core.util.TestModules.nullableNestedObject +import io.bkbn.kompendium.core.util.TestModules.polymorphicCollectionResponse +import io.bkbn.kompendium.core.util.TestModules.polymorphicMapResponse +import io.bkbn.kompendium.core.util.TestModules.polymorphicResponse +import io.bkbn.kompendium.core.util.TestModules.primitives +import io.bkbn.kompendium.core.util.TestModules.reqRespExamples +import io.bkbn.kompendium.core.util.TestModules.requiredParams +import io.bkbn.kompendium.core.util.TestModules.returnsList +import io.bkbn.kompendium.core.util.TestModules.rootRoute +import io.bkbn.kompendium.core.util.TestModules.simpleGenericResponse +import io.bkbn.kompendium.core.util.TestModules.simplePathParsing +import io.bkbn.kompendium.core.util.TestModules.trailingSlash +import io.bkbn.kompendium.core.util.TestModules.withOperationId +import io.bkbn.kompendium.json.schema.definition.TypeDefinition +import io.kotest.core.spec.style.DescribeSpec +import kotlin.reflect.typeOf +import java.time.Instant + +class KompendiumTest : DescribeSpec({ + describe("Notarized Open API Metadata Tests") { + it("Can notarize a get request") { + openApiTestAllSerializers("T0001__notarized_get.json") { notarizedGet() } + } + it("Can notarize a post request") { + openApiTestAllSerializers("T0002__notarized_post.json") { notarizedPost() } + } + it("Can notarize a put request") { + openApiTestAllSerializers("T0003__notarized_put.json") { notarizedPut() } + } + it("Can notarize a delete request") { + openApiTestAllSerializers("T0004__notarized_delete.json") { notarizedDelete() } + } + it("Can notarize a patch request") { + openApiTestAllSerializers("T0005__notarized_patch.json") { notarizedPatch() } + } + it("Can notarize a head request") { + openApiTestAllSerializers("T0006__notarized_head.json") { notarizedHead() } + } + it("Can notarize an options request") { + openApiTestAllSerializers("T0007__notarized_options.json") { notarizedOptions() } + } + it("Can notarize a complex type") { + openApiTestAllSerializers("T0008__complex_type.json") { complexRequest() } + } + it("Can notarize primitives") { + openApiTestAllSerializers("T0009__notarized_primitives.json") { primitives() } + } + it("Can notarize a top level list response") { + openApiTestAllSerializers("T0010__response_list.json") { returnsList() } + } + it("Can notarize a route with non-required params") { + openApiTestAllSerializers("T0011__non_required_params.json") { nonRequiredParams() } + } + } + describe("Route Parsing") { + it("Can parse a simple path and store it under the expected route") { + openApiTestAllSerializers("T0012__path_parser.json") { simplePathParsing() } + } + it("Can notarize the root route") { + openApiTestAllSerializers("T0013__root_route.json") { rootRoute() } + } + it("Can notarize a route under the root module without appending trailing slash") { + openApiTestAllSerializers("T0014__nested_under_root.json") { nestedUnderRoot() } + } + it("Can notarize a route with a trailing slash") { + openApiTestAllSerializers("T0015__trailing_slash.json") { trailingSlash() } + } + } + describe("Exceptions") { + it("Can add an exception status code to a response") { + openApiTestAllSerializers("T0016__notarized_get_with_exception_response.json") { singleException() } + } + it("Can support multiple response codes") { + openApiTestAllSerializers("T0017__notarized_get_with_multiple_exception_responses.json") { multipleExceptions() } + } + it("Can add a polymorphic exception response") { + openApiTestAllSerializers("T0018__polymorphic_error_status_codes.json") { polymorphicException() } + } + it("Can add a generic exception response") { + openApiTestAllSerializers("T0019__generic_exception.json") { genericException() } + } + } + describe("Examples") { + it("Can generate example response and request bodies") { + openApiTestAllSerializers("T0020__example_req_and_resp.json") { reqRespExamples() } + } + it("Can describe example parameters") { + openApiTestAllSerializers("T0021__example_parameters.json") { exampleParams() } + } + } + describe("Defaults") { + it("Can generate a default parameter value") { + openApiTestAllSerializers("T0022__query_with_default_parameter.json") { defaultParameter() } + } + } + describe("Required Fields") { + it("Marks a parameter as required if there is no default and it is not marked nullable") { + openApiTestAllSerializers("T0023__required_param.json") { requiredParams() } + } + it("Can mark a parameter as not required") { + openApiTestAllSerializers("T0024__non_required_param.json") { nonRequiredParam() } + } + it("Does not mark a field as required if a default value is provided") { + openApiTestAllSerializers("T0025__default_field.json") { defaultField() } + } + it("Does not mark a nullable field as required") { + openApiTestAllSerializers("T0026__nullable_field.json") { nullableField() } + } + } + describe("Polymorphism and Generics") { + it("can generate a polymorphic response type") { + openApiTestAllSerializers("T0027__polymorphic_response.json") { polymorphicResponse() } + } + it("Can generate a collection with polymorphic response type") { + openApiTestAllSerializers("T0028__polymorphic_list_response.json") { polymorphicCollectionResponse() } + } + it("Can generate a map with a polymorphic response type") { + openApiTestAllSerializers("T0029__polymorphic_map_response.json") { polymorphicMapResponse() } + } + it("Can generate a response type with a generic type") { + openApiTestAllSerializers("T0030__simple_generic_response.json") { simpleGenericResponse() } + } + it("Can generate a response type with a nested generic type") { + openApiTestAllSerializers("T0031__nested_generic_response.json") { nestedGenericResponse() } + } + it("Can generate a polymorphic response type with generics") { + openApiTestAllSerializers("T0032__polymorphic_response_with_generics.json") { genericPolymorphicResponse() } + } + it("Can handle an absolutely psycho inheritance test") { + openApiTestAllSerializers("T0033__crazy_polymorphic_example.json") { genericPolymorphicResponseMultipleImpls() } + } + } + describe("Miscellaneous") { + xit("Can generate the necessary ReDoc home page") { + // TODO apiFunctionalityTest(getFileSnapshot("redoc.html"), "/docs") { returnsList() } + } + it("Can add an operation id to a notarized route") { + openApiTestAllSerializers("T0034__notarized_get_with_operation_id.json") { withOperationId() } + } + xit("Can add an undeclared field") { + // TODO openApiTestAllSerializers("undeclared_field.json") { undeclaredType() } + } + it("Can add a custom header parameter with a name override") { + openApiTestAllSerializers("T0035__override_parameter_name.json") { headerParameter() } + } + xit("Can override field name") { + // TODO Assess strategies here + } + xit("Can serialize a recursive type") { + // TODO openApiTestAllSerializers("simple_recursive.json") { simpleRecursive() } + } + it("Nullable fields do not lead to doom") { + openApiTestAllSerializers("T0036__nullable_fields.json") { nullableNestedObject() } + } + it("Can have a nullable enum as a member field") { + openApiTestAllSerializers("T0037__nullable_enum_field.json") { nullableEnumField() } + } + } + describe("Constraints") { + // TODO Assess strategies here + } + describe("Formats") { + it("Can set a format for a simple type schema") { + openApiTestAllSerializers( + snapshotName = "T0038__formatted_date_time_string.json", + customTypes = mapOf(typeOf() to TypeDefinition(type = "string", format = "date")) + ) { dateTimeString() } + } + } + describe("Free Form") { + // todo Assess strategies here + } +}) diff --git a/core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt b/core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt new file mode 100644 index 000000000..586187c22 --- /dev/null +++ b/core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt @@ -0,0 +1,609 @@ +package io.bkbn.kompendium.core.util + +import io.bkbn.kompendium.core.fixtures.ColumnSchema +import io.bkbn.kompendium.core.fixtures.ComplexRequest +import io.bkbn.kompendium.core.fixtures.DateTimeString +import io.bkbn.kompendium.core.fixtures.DefaultField +import io.bkbn.kompendium.core.fixtures.ExceptionResponse +import io.bkbn.kompendium.core.fixtures.Flibbity +import io.bkbn.kompendium.core.fixtures.FlibbityGibbit +import io.bkbn.kompendium.core.fixtures.Gibbity +import io.bkbn.kompendium.core.fixtures.NullableEnum +import io.bkbn.kompendium.core.fixtures.NullableField +import io.bkbn.kompendium.core.fixtures.ProfileUpdateRequest +import io.bkbn.kompendium.core.fixtures.TestCreatedResponse +import io.bkbn.kompendium.core.fixtures.TestNested +import io.bkbn.kompendium.core.fixtures.TestRequest +import io.bkbn.kompendium.core.fixtures.TestResponse +import io.bkbn.kompendium.core.fixtures.TestSimpleRequest +import io.bkbn.kompendium.core.metadata.DeleteInfo +import io.bkbn.kompendium.core.metadata.GetInfo +import io.bkbn.kompendium.core.metadata.HeadInfo +import io.bkbn.kompendium.core.metadata.OptionsInfo +import io.bkbn.kompendium.core.metadata.PatchInfo +import io.bkbn.kompendium.core.metadata.PostInfo +import io.bkbn.kompendium.core.metadata.PutInfo +import io.bkbn.kompendium.core.plugin.NotarizedRoute +import io.bkbn.kompendium.json.schema.definition.TypeDefinition +import io.bkbn.kompendium.oas.payload.Parameter +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.response.respond +import io.ktor.server.response.respondText +import io.ktor.server.routing.Routing +import io.ktor.server.routing.delete +import io.ktor.server.routing.get +import io.ktor.server.routing.head +import io.ktor.server.routing.options +import io.ktor.server.routing.patch +import io.ktor.server.routing.post +import io.ktor.server.routing.put +import io.ktor.server.routing.route + +object TestModules { + private const val defaultPath = "/test/{a}" + private const val rootPath = "/" + private const val defaultResponseDescription = "A Successful Endeavor" + private const val defaultRequestDescription = "You gotta send it" + private const val defaultPathSummary = "Great Summary!" + private const val defaultPathDescription = "testing more" + + private val defaultParams = listOf( + Parameter( + name = "a", + `in` = Parameter.Location.path, + schema = TypeDefinition.STRING, + ), + Parameter( + name = "aa", + `in` = Parameter.Location.query, + schema = TypeDefinition.INT + ) + ) + + fun Routing.notarizedGet() { + route(defaultPath) { + install(NotarizedRoute()) { + parameters = defaultParams + get = GetInfo.builder { + response { + responseCode(HttpStatusCode.OK) + responseType() + description(defaultResponseDescription) + } + summary(defaultPathSummary) + description(defaultPathDescription) + } + } + get { + call.respondText { "hey dude ‼️ congrats on the get request" } + } + } + } + + fun Routing.notarizedPost() { + route(defaultPath) { + install(NotarizedRoute()) { + parameters = defaultParams + post = PostInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + request { + requestType() + description("A Test request") + } + response { + responseCode(HttpStatusCode.Created) + responseType() + description(defaultResponseDescription) + } + } + } + post { + call.respondText { "hey dude ‼️ congrats on the post request" } + } + } + } + + fun Routing.notarizedPut() { + route(defaultPath) { + install(NotarizedRoute()) { + parameters = defaultParams + put = PutInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + request { + requestType() + description("A Test request") + } + response { + responseCode(HttpStatusCode.Created) + responseType() + description(defaultResponseDescription) + } + } + } + put { + call.respondText { "hey dude ‼️ congrats on the post request" } + } + } + } + + fun Routing.notarizedDelete() { + route(defaultPath) { + install(NotarizedRoute()) { + parameters = defaultParams + delete = DeleteInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + response { + responseCode(HttpStatusCode.NoContent) + responseType() + description(defaultResponseDescription) + } + } + } + } + delete { + call.respond(HttpStatusCode.NoContent) + } + } + + fun Routing.notarizedPatch() { + route(defaultPath) { + install(NotarizedRoute()) { + parameters = defaultParams + patch = PatchInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + request { + description("A Test request") + requestType() + } + response { + responseCode(HttpStatusCode.Created) + responseType() + description(defaultResponseDescription) + } + } + } + patch { + call.respond(HttpStatusCode.Created) { TestCreatedResponse(123, "Nice!") } + } + } + } + + fun Routing.notarizedHead() { + route(defaultPath) { + install(NotarizedRoute()) { + parameters = defaultParams + head = HeadInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + + response { + description("great!") + responseCode(HttpStatusCode.Created) + responseType() + } + } + } + head { + call.respond(HttpStatusCode.OK) + } + } + } + + fun Routing.notarizedOptions() { + route(defaultPath) { + install(NotarizedRoute()) { + parameters = defaultParams + options = OptionsInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + response { + responseCode(HttpStatusCode.OK) + responseType() + description("nice") + } + } + } + options { + call.respond(HttpStatusCode.NoContent) + } + } + } + + fun Routing.complexRequest() { + route(rootPath) { + install(NotarizedRoute()) { + put = PutInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + request { + requestType() + description("A Complex request") + } + response { + responseCode(HttpStatusCode.Created) + responseType() + description(defaultResponseDescription) + } + } + } + patch { + call.respond(HttpStatusCode.Created, TestCreatedResponse(123, "nice!")) + } + } + } + + fun Routing.primitives() { + route(rootPath) { + install(NotarizedRoute()) { + put = PutInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + request { + requestType() + description("A Test Request") + } + response { + responseCode(HttpStatusCode.Created) + responseType() + description(defaultResponseDescription) + } + } + } + } + } + + fun Routing.returnsList() { + route(defaultPath) { + install(NotarizedRoute()) { + parameters = defaultParams + get = GetInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + response { + description("A Successful List-y Endeavor") + responseCode(HttpStatusCode.OK) + responseType>() + } + } + } + } + } + + fun Routing.nonRequiredParams() { + route("/optional") { + install(NotarizedRoute()) { + parameters = listOf( + Parameter( + name = "notRequired", + `in` = Parameter.Location.query, + schema = TypeDefinition.STRING, + required = false, + ), + Parameter( + name = "required", + `in` = Parameter.Location.query, + schema = TypeDefinition.STRING + ) + ) + get = GetInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + response { + responseType() + description("Empty") + responseCode(HttpStatusCode.NoContent) + } + } + } + } + } + + fun Routing.simplePathParsing() { + route("/this") { + route("/is") { + route("/a") { + route("/complex") { + route("path") { + route("with/an/{id}") { + install(NotarizedRoute()) { + get = GetInfo.builder { + parameters = listOf( + Parameter( + name = "id", + `in` = Parameter.Location.path, + schema = TypeDefinition.STRING + ) + ) + summary(defaultPathSummary) + description(defaultPathDescription) + response { + description(defaultResponseDescription) + responseCode(HttpStatusCode.OK) + responseType() + } + } + } + } + } + } + } + } + } + } + + fun Routing.rootRoute() { + route(rootPath) { + install(NotarizedRoute()) { + parameters = listOf(defaultParams.last()) + get = GetInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + response { + description(defaultResponseDescription) + responseCode(HttpStatusCode.OK) + responseType() + } + } + } + } + } + + fun Routing.nestedUnderRoot() { + route("/") { + route("/testerino") { + install(NotarizedRoute()) { + get = GetInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + response { + description(defaultResponseDescription) + responseCode(HttpStatusCode.OK) + responseType() + } + } + } + } + } + } + + fun Routing.trailingSlash() { + route("/test") { + route("/") { + install(NotarizedRoute()) { + get = GetInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + response { + description(defaultResponseDescription) + responseCode(HttpStatusCode.OK) + responseType() + } + } + } + } + } + } + + fun Routing.singleException() { + route(rootPath) { + install(NotarizedRoute()) { + get = GetInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + response { + description(defaultResponseDescription) + responseCode(HttpStatusCode.OK) + responseType() + } + canRespond { + description("Bad Things Happened") + responseCode(HttpStatusCode.BadRequest) + responseType() + } + } + } + } + } + + fun Routing.multipleExceptions() { + route(rootPath) { + install(NotarizedRoute()) { + get = GetInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + response { + description(defaultResponseDescription) + responseCode(HttpStatusCode.OK) + responseType() + } + canRespond { + description("Bad Things Happened") + responseCode(HttpStatusCode.BadRequest) + responseType() + } + canRespond { + description("Access Denied") + responseCode(HttpStatusCode.Forbidden) + responseType() + } + } + } + } + } + + fun Routing.polymorphicException() { + route(rootPath) { + install(NotarizedRoute()) { + get = GetInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + response { + description(defaultResponseDescription) + responseCode(HttpStatusCode.OK) + responseType() + } + canRespond { + description("Bad Things Happened") + responseCode(HttpStatusCode.InternalServerError) + responseType() + } + } + } + } + } + + fun Routing.genericException() { + route(rootPath) { + install(NotarizedRoute()) { + get = GetInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + response { + description(defaultResponseDescription) + responseCode(HttpStatusCode.OK) + responseType() + } + canRespond { + description("Bad Things Happened") + responseCode(HttpStatusCode.BadRequest) + responseType>() + } + } + } + } + } + + fun Routing.reqRespExamples() { + route(rootPath) { + install(NotarizedRoute()) { + post = PostInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + request { + description(defaultRequestDescription) + requestType() + examples( + "Testerina" to TestRequest(TestNested("asdf"), 1.5, emptyList()) + ) + } + response { + description(defaultResponseDescription) + responseCode(HttpStatusCode.OK) + responseType() + examples( + "Testerino" to TestResponse("Heya") + ) + } + } + } + } + } + + fun Routing.exampleParams() = basicGetGenerator( + params = listOf( + Parameter( + name = "id", + `in` = Parameter.Location.path, + schema = TypeDefinition.STRING, + examples = mapOf( + "foo" to Parameter.Example("testing") + ) + ) + ) + ) + + fun Routing.defaultParameter() = basicGetGenerator( + params = listOf( + Parameter( + name = "id", + `in` = Parameter.Location.path, + schema = TypeDefinition.STRING.withDefault("IDK") + ) + ) + ) + + fun Routing.requiredParams() = basicGetGenerator( + params = listOf( + Parameter( + name = "id", + `in` = Parameter.Location.path, + schema = TypeDefinition.STRING + ) + ) + ) + + fun Routing.nonRequiredParam() = basicGetGenerator( + params = listOf( + Parameter( + name = "id", + `in` = Parameter.Location.query, + schema = TypeDefinition.STRING, + required = false + ) + ) + ) + + fun Routing.defaultField() = basicGetGenerator() + + fun Routing.nullableField() = basicGetGenerator() + + fun Routing.polymorphicResponse() = basicGetGenerator() + + fun Routing.polymorphicCollectionResponse() = basicGetGenerator>() + + fun Routing.polymorphicMapResponse() = basicGetGenerator>() + + fun Routing.simpleGenericResponse() = basicGetGenerator>() + + fun Routing.nestedGenericResponse() = basicGetGenerator>>() + + fun Routing.genericPolymorphicResponse() = basicGetGenerator>() + + fun Routing.genericPolymorphicResponseMultipleImpls() = basicGetGenerator>() + + fun Routing.withOperationId() = basicGetGenerator(operationId = "getThisDude") + + fun Routing.nullableNestedObject() = basicGetGenerator() + + fun Routing.nullableEnumField() = basicGetGenerator() + + fun Routing.dateTimeString() = basicGetGenerator() + + fun Routing.headerParameter() = basicGetGenerator( params = listOf( + Parameter( + name = "X-User-Email", + `in` = Parameter.Location.header, + schema = TypeDefinition.STRING, + required = true + ) + )) + + fun Routing.simpleRecursive() = basicGetGenerator() + + private inline fun Routing.basicGetGenerator( + params: List = emptyList(), + operationId: String? = null + ) { + route(rootPath) { + install(NotarizedRoute()) { + get = GetInfo.builder { + summary(defaultPathSummary) + description(defaultPathDescription) + operationId?.let { operationId(it) } + parameters = params + response { + description(defaultResponseDescription) + responseCode(HttpStatusCode.OK) + responseType() + } + } + } + } + } +} diff --git a/kompendium-core/src/test/resources/notarized_get_with_operation_id.json b/core/src/test/resources/T0001__notarized_get.json similarity index 67% rename from kompendium-core/src/test/resources/notarized_get_with_operation_id.json rename to core/src/test/resources/T0001__notarized_get.json index e5a207037..7f6180fce 100644 --- a/kompendium-core/src/test/resources/notarized_get_with_operation_id.json +++ b/core/src/test/resources/T0001__notarized_get.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,33 +27,12 @@ } ], "paths": { - "/test": { + "/test/{a}": { "get": { "tags": [], - "summary": "Another get test", + "summary": "Great Summary!", "description": "testing more", - "operationId": "getTest", - "parameters": [ - { - "name": "a", - "in": "path", - "schema": { - "type": "string" - }, - "required": true, - "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false - } - ], + "parameters": [], "responses": { "200": { "description": "A Successful Endeavor", @@ -66,12 +46,35 @@ } }, "deprecated": false - } + }, + "parameters": [ + { + "name": "a", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "deprecated": false + }, + { + "name": "aa", + "in": "query", + "schema": { + "type": "number", + "format": "int32" + }, + "required": true, + "deprecated": false + } + ] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -79,8 +82,7 @@ }, "required": [ "c" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/notarized_post.json b/core/src/test/resources/T0002__notarized_post.json similarity index 56% rename from kompendium-core/src/test/resources/notarized_post.json rename to core/src/test/resources/T0002__notarized_post.json index bd3184d33..b512f6dbe 100644 --- a/kompendium-core/src/test/resources/notarized_post.json +++ b/core/src/test/resources/T0002__notarized_post.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,38 +27,18 @@ } ], "paths": { - "/test": { + "/test/{a}": { "post": { "tags": [], - "summary": "Test post endpoint", - "description": "Post your tests here!", - "parameters": [ - { - "name": "a", - "in": "path", - "schema": { - "type": "string" - }, - "required": true, - "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false - } - ], + "summary": "Great Summary!", + "description": "testing more", + "parameters": [], "requestBody": { "description": "A Test request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TestRequest" + "$ref": "#/components/schemas/TestSimpleRequest" } } }, @@ -76,61 +57,64 @@ } }, "deprecated": false - } + }, + "parameters": [ + { + "name": "a", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "deprecated": false + }, + { + "name": "aa", + "in": "query", + "schema": { + "type": "number", + "format": "int32" + }, + "required": true, + "deprecated": false + } + ] } }, + "webhooks": {}, "components": { "schemas": { - "TestRequest": { - "properties": { - "aaa": { - "items": { - "format": "int64", - "type": "integer" - }, - "type": "array" - }, - "b": { - "format": "double", - "type": "number" - }, - "field_name": { - "$ref": "#/components/schemas/TestNested" - } - }, - "required": [ - "field_name", - "b", - "aaa" - ], - "type": "object" - }, - "TestNested": { - "properties": { - "nesty": { - "type": "string" - } - }, - "required": [ - "nesty" - ], - "type": "object" - }, "TestCreatedResponse": { + "type": "object", "properties": { "c": { "type": "string" }, "id": { - "format": "int32", - "type": "integer" + "type": "number", + "format": "int32" } }, "required": [ - "id", - "c" - ], - "type": "object" + "c", + "id" + ] + }, + "TestSimpleRequest": { + "type": "object", + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "number", + "format": "int32" + } + }, + "required": [ + "a", + "b" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/notarized_put.json b/core/src/test/resources/T0003__notarized_put.json similarity index 56% rename from kompendium-core/src/test/resources/notarized_put.json rename to core/src/test/resources/T0003__notarized_put.json index 4447fa9c3..529e3fcdc 100644 --- a/kompendium-core/src/test/resources/notarized_put.json +++ b/core/src/test/resources/T0003__notarized_put.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,38 +27,18 @@ } ], "paths": { - "/test": { + "/test/{a}": { "put": { "tags": [], - "summary": "Test put endpoint", - "description": "Put your tests here!", - "parameters": [ - { - "name": "a", - "in": "path", - "schema": { - "type": "string" - }, - "required": true, - "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false - } - ], + "summary": "Great Summary!", + "description": "testing more", + "parameters": [], "requestBody": { "description": "A Test request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TestRequest" + "$ref": "#/components/schemas/TestSimpleRequest" } } }, @@ -76,61 +57,64 @@ } }, "deprecated": false - } + }, + "parameters": [ + { + "name": "a", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "deprecated": false + }, + { + "name": "aa", + "in": "query", + "schema": { + "type": "number", + "format": "int32" + }, + "required": true, + "deprecated": false + } + ] } }, + "webhooks": {}, "components": { "schemas": { - "TestRequest": { - "properties": { - "aaa": { - "items": { - "format": "int64", - "type": "integer" - }, - "type": "array" - }, - "b": { - "format": "double", - "type": "number" - }, - "field_name": { - "$ref": "#/components/schemas/TestNested" - } - }, - "required": [ - "field_name", - "b", - "aaa" - ], - "type": "object" - }, - "TestNested": { - "properties": { - "nesty": { - "type": "string" - } - }, - "required": [ - "nesty" - ], - "type": "object" - }, "TestCreatedResponse": { + "type": "object", "properties": { "c": { "type": "string" }, "id": { - "format": "int32", - "type": "integer" + "type": "number", + "format": "int32" } }, "required": [ - "id", - "c" - ], - "type": "object" + "c", + "id" + ] + }, + "TestSimpleRequest": { + "type": "object", + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "number", + "format": "int32" + } + }, + "required": [ + "a", + "b" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/generic_response.json b/core/src/test/resources/T0004__notarized_delete.json similarity index 56% rename from kompendium-core/src/test/resources/generic_response.json rename to core/src/test/resources/T0004__notarized_delete.json index 23fbdf233..06f806f04 100644 --- a/kompendium-core/src/test/resources/generic_response.json +++ b/core/src/test/resources/T0004__notarized_delete.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,47 +27,45 @@ } ], "paths": { - "/test/polymorphic": { - "get": { + "/test/{a}": { + "delete": { "tags": [], - "summary": "Single Generic", - "description": "Simple generic data class", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TestGeneric-Int" - } - } - } + "204": { + "description": "A Successful Endeavor" } }, "deprecated": false - } - } - }, - "components": { - "schemas": { - "TestGeneric-Int": { - "properties": { - "messy": { + }, + "parameters": [ + { + "name": "a", + "in": "path", + "schema": { "type": "string" }, - "potato": { - "format": "int32", - "type": "integer" - } + "required": true, + "deprecated": false }, - "required": [ - "messy", - "potato" - ], - "type": "object" - } - }, + { + "name": "aa", + "in": "query", + "schema": { + "type": "number", + "format": "int32" + }, + "required": true, + "deprecated": false + } + ] + } + }, + "webhooks": {}, + "components": { + "schemas": {}, "securitySchemes": {} }, "security": [], diff --git a/kompendium-core/src/test/resources/notarized_patch.json b/core/src/test/resources/T0005__notarized_patch.json similarity index 59% rename from kompendium-core/src/test/resources/notarized_patch.json rename to core/src/test/resources/T0005__notarized_patch.json index 434738426..9b12c4025 100644 --- a/kompendium-core/src/test/resources/notarized_patch.json +++ b/core/src/test/resources/T0005__notarized_patch.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,18 +27,18 @@ } ], "paths": { - "/test": { + "/test/{a}": { "patch": { "tags": [], - "summary": "Test patch endpoint", - "description": "patch your tests here!", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "requestBody": { "description": "A Test request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TestRequest" + "$ref": "#/components/schemas/TestSimpleRequest" } } }, @@ -49,63 +50,71 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TestResponse" + "$ref": "#/components/schemas/TestCreatedResponse" } } } } }, "deprecated": false - } + }, + "parameters": [ + { + "name": "a", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "deprecated": false + }, + { + "name": "aa", + "in": "query", + "schema": { + "type": "number", + "format": "int32" + }, + "required": true, + "deprecated": false + } + ] } }, + "webhooks": {}, "components": { "schemas": { - "TestRequest": { - "properties": { - "aaa": { - "items": { - "format": "int64", - "type": "integer" - }, - "type": "array" - }, - "b": { - "format": "double", - "type": "number" - }, - "field_name": { - "$ref": "#/components/schemas/TestNested" - } - }, - "required": [ - "field_name", - "b", - "aaa" - ], - "type": "object" - }, - "TestNested": { - "properties": { - "nesty": { - "type": "string" - } - }, - "required": [ - "nesty" - ], - "type": "object" - }, - "TestResponse": { + "TestCreatedResponse": { + "type": "object", "properties": { "c": { "type": "string" + }, + "id": { + "type": "number", + "format": "int32" } }, "required": [ - "c" - ], - "type": "object" + "c", + "id" + ] + }, + "TestSimpleRequest": { + "type": "object", + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "number", + "format": "int32" + } + }, + "required": [ + "a", + "b" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/notarized_head.json b/core/src/test/resources/T0006__notarized_head.json similarity index 58% rename from kompendium-core/src/test/resources/notarized_head.json rename to core/src/test/resources/T0006__notarized_head.json index eeb33cb97..2eb3c9aef 100644 --- a/kompendium-core/src/test/resources/notarized_head.json +++ b/core/src/test/resources/T0006__notarized_head.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,21 +27,43 @@ } ], "paths": { - "/test": { + "/test/{a}": { "head": { "tags": [], - "summary": "Test head endpoint", - "description": "head test 💀", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "responses": { - "200": { + "201": { "description": "great!" } }, "deprecated": false - } + }, + "parameters": [ + { + "name": "a", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "deprecated": false + }, + { + "name": "aa", + "in": "query", + "schema": { + "type": "number", + "format": "int32" + }, + "required": true, + "deprecated": false + } + ] } }, + "webhooks": {}, "components": { "schemas": {}, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/notarized_options.json b/core/src/test/resources/T0007__notarized_options.json similarity index 65% rename from kompendium-core/src/test/resources/notarized_options.json rename to core/src/test/resources/T0007__notarized_options.json index 0c53ad55a..ed03f06aa 100644 --- a/kompendium-core/src/test/resources/notarized_options.json +++ b/core/src/test/resources/T0007__notarized_options.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,32 +27,12 @@ } ], "paths": { - "/test": { + "/test/{a}": { "options": { "tags": [], - "summary": "Test options", - "description": "endpoint of options", - "parameters": [ - { - "name": "a", - "in": "path", - "schema": { - "type": "string" - }, - "required": true, - "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false - } - ], + "summary": "Great Summary!", + "description": "testing more", + "parameters": [], "responses": { "200": { "description": "nice", @@ -65,12 +46,35 @@ } }, "deprecated": false - } + }, + "parameters": [ + { + "name": "a", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "deprecated": false + }, + { + "name": "aa", + "in": "query", + "schema": { + "type": "number", + "format": "int32" + }, + "required": true, + "deprecated": false + } + ] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -78,8 +82,7 @@ }, "required": [ "c" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/complex_type.json b/core/src/test/resources/T0008__complex_type.json similarity index 80% rename from kompendium-core/src/test/resources/complex_type.json rename to core/src/test/resources/T0008__complex_type.json index 58da5a221..d3ea01721 100644 --- a/kompendium-core/src/test/resources/complex_type.json +++ b/core/src/test/resources/T0008__complex_type.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,11 +27,11 @@ } ], "paths": { - "/test": { + "/": { "put": { "tags": [], - "summary": "Test put endpoint", - "description": "Put your tests here!", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "requestBody": { "description": "A Complex request", @@ -56,14 +57,33 @@ } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { - "ComplexRequest": { + "TestCreatedResponse": { + "type": "object", "properties": { - "amazing_field": { + "c": { + "type": "string" + }, + "id": { + "type": "number", + "format": "int32" + } + }, + "required": [ + "c", + "id" + ] + }, + "ComplexRequest": { + "type": "object", + "properties": { + "amazingField": { "type": "string" }, "org": { @@ -77,13 +97,13 @@ } }, "required": [ + "amazingField", "org", - "amazing_field", "tables" - ], - "type": "object" + ] }, "NestedComplexItem": { + "type": "object", "properties": { "alias": { "additionalProperties": { @@ -96,44 +116,23 @@ } }, "required": [ - "name", - "alias" - ], - "type": "object" + "alias", + "name" + ] }, "CrazyItem": { + "type": "object", "properties": { "enumeration": { - "$ref": "#/components/schemas/SimpleEnum" + "enum": [ + "ONE", + "TWO" + ] } }, "required": [ "enumeration" - ], - "type": "object" - }, - "SimpleEnum": { - "enum": [ - "ONE", - "TWO" - ], - "type": "string" - }, - "TestCreatedResponse": { - "properties": { - "c": { - "type": "string" - }, - "id": { - "format": "int32", - "type": "integer" - } - }, - "required": [ - "id", - "c" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/notarized_primitives.json b/core/src/test/resources/T0009__notarized_primitives.json similarity index 71% rename from kompendium-core/src/test/resources/notarized_primitives.json rename to core/src/test/resources/T0009__notarized_primitives.json index 1a4c68dd1..5b79c3d46 100644 --- a/kompendium-core/src/test/resources/notarized_primitives.json +++ b/core/src/test/resources/T0009__notarized_primitives.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,19 +27,18 @@ } ], "paths": { - "/test": { + "/": { "put": { "tags": [], - "summary": "Test put endpoint", - "description": "Put your tests here!", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "requestBody": { - "description": "A Test request", + "description": "A Test Request", "content": { "application/json": { "schema": { - "format": "int32", - "type": "integer" + "$ref": "#/components/schemas/Int" } } }, @@ -50,18 +50,28 @@ "content": { "application/json": { "schema": { - "type": "boolean" + "$ref": "#/components/schemas/Boolean" } } } } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { - "schemas": {}, + "schemas": { + "Boolean": { + "type": "boolean" + }, + "Int": { + "type": "number", + "format": "int32" + } + }, "securitySchemes": {} }, "security": [], diff --git a/kompendium-core/src/test/resources/response_list.json b/core/src/test/resources/T0010__response_list.json similarity index 60% rename from kompendium-core/src/test/resources/response_list.json rename to core/src/test/resources/T0010__response_list.json index fbc08577b..42ea48497 100644 --- a/kompendium-core/src/test/resources/response_list.json +++ b/core/src/test/resources/T0010__response_list.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,54 +27,54 @@ } ], "paths": { - "/test": { + "/test/{a}": { "get": { "tags": [], - "summary": "Another get test", + "summary": "Great Summary!", "description": "testing more", - "parameters": [ - { - "name": "a", - "in": "path", - "schema": { - "type": "string" - }, - "required": true, - "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false - } - ], + "parameters": [], "responses": { "200": { "description": "A Successful List-y Endeavor", "content": { "application/json": { "schema": { - "items": { - "$ref": "#/components/schemas/TestResponse" - }, - "type": "array" + "$ref": "#/components/schemas/List-TestResponse" } } } } }, "deprecated": false - } + }, + "parameters": [ + { + "name": "a", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "deprecated": false + }, + { + "name": "aa", + "in": "query", + "schema": { + "type": "number", + "format": "int32" + }, + "required": true, + "deprecated": false + } + ] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -81,8 +82,13 @@ }, "required": [ "c" - ], - "type": "object" + ] + }, + "List-TestResponse": { + "items": { + "$ref": "#/components/schemas/TestResponse" + }, + "type": "array" } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/non_required_params.json b/core/src/test/resources/T0011__non_required_params.json similarity index 61% rename from kompendium-core/src/test/resources/non_required_params.json rename to core/src/test/resources/T0011__non_required_params.json index 0fd6cdadb..b17c2bbce 100644 --- a/kompendium-core/src/test/resources/non_required_params.json +++ b/core/src/test/resources/T0011__non_required_params.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,41 +27,42 @@ } ], "paths": { - "/test/optional": { + "/optional": { "get": { "tags": [], - "summary": "No request params and response body", + "summary": "Great Summary!", "description": "testing more", - "parameters": [ - { - "name": "notRequired", - "in": "query", - "schema": { - "type": "string", - "nullable": true - }, - "required": false, - "deprecated": false - }, - { - "name": "required", - "in": "query", - "schema": { - "type": "string" - }, - "required": true, - "deprecated": false - } - ], + "parameters": [], "responses": { "204": { "description": "Empty" } }, "deprecated": false - } + }, + "parameters": [ + { + "name": "notRequired", + "in": "query", + "schema": { + "type": "string" + }, + "required": false, + "deprecated": false + }, + { + "name": "required", + "in": "query", + "schema": { + "type": "string" + }, + "required": true, + "deprecated": false + } + ] } }, + "webhooks": {}, "components": { "schemas": {}, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/path_parser.json b/core/src/test/resources/T0012__path_parser.json similarity index 81% rename from kompendium-core/src/test/resources/path_parser.json rename to core/src/test/resources/T0012__path_parser.json index 0bc1caa6d..181a4e6be 100644 --- a/kompendium-core/src/test/resources/path_parser.json +++ b/core/src/test/resources/T0012__path_parser.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -29,27 +30,17 @@ "/this/is/a/complex/path/with/an/{id}": { "get": { "tags": [], - "summary": "Another get test", + "summary": "Great Summary!", "description": "testing more", "parameters": [ { - "name": "a", + "name": "id", "in": "path", "schema": { "type": "string" }, "required": true, "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false } ], "responses": { @@ -65,12 +56,15 @@ } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -78,8 +72,7 @@ }, "required": [ "c" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/nested_under_root.json b/core/src/test/resources/T0013__root_route.json similarity index 69% rename from kompendium-core/src/test/resources/nested_under_root.json rename to core/src/test/resources/T0013__root_route.json index 3ae55a7ea..687737f54 100644 --- a/kompendium-core/src/test/resources/nested_under_root.json +++ b/core/src/test/resources/T0013__root_route.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,32 +27,12 @@ } ], "paths": { - "/testerino": { + "/": { "get": { "tags": [], - "summary": "Another get test", + "summary": "Great Summary!", "description": "testing more", - "parameters": [ - { - "name": "a", - "in": "path", - "schema": { - "type": "string" - }, - "required": true, - "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false - } - ], + "parameters": [], "responses": { "200": { "description": "A Successful Endeavor", @@ -65,12 +46,26 @@ } }, "deprecated": false - } + }, + "parameters": [ + { + "name": "aa", + "in": "query", + "schema": { + "type": "number", + "format": "int32" + }, + "required": true, + "deprecated": false + } + ] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -78,8 +73,7 @@ }, "required": [ "c" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/default_param.json b/core/src/test/resources/T0014__nested_under_root.json similarity index 72% rename from kompendium-core/src/test/resources/default_param.json rename to core/src/test/resources/T0014__nested_under_root.json index 343ba5419..e116b858c 100644 --- a/kompendium-core/src/test/resources/default_param.json +++ b/core/src/test/resources/T0014__nested_under_root.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,26 +27,15 @@ } ], "paths": { - "/test/required_param": { + "/testerino": { "get": { "tags": [], - "summary": "default param", - "description": "Cool stuff", - "parameters": [ - { - "name": "b", - "in": "query", - "schema": { - "type": "string", - "default": "heyo" - }, - "required": false, - "deprecated": false - } - ], + "summary": "Great Summary!", + "description": "testing more", + "parameters": [], "responses": { "200": { - "description": "A successful endeavor", + "description": "A Successful Endeavor", "content": { "application/json": { "schema": { @@ -56,12 +46,15 @@ } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -69,8 +62,7 @@ }, "required": [ "c" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/core/src/test/resources/T0015__trailing_slash.json b/core/src/test/resources/T0015__trailing_slash.json new file mode 100644 index 000000000..682c92f25 --- /dev/null +++ b/core/src/test/resources/T0015__trailing_slash.json @@ -0,0 +1,72 @@ +{ + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", + "info": { + "title": "Test API", + "version": "1.33.7", + "description": "An amazing, fully-ish 😉 generated API spec", + "termsOfService": "https://example.com", + "contact": { + "name": "Homer Simpson", + "url": "https://gph.is/1NPUDiM", + "email": "chunkylover53@aol.com" + }, + "license": { + "name": "MIT", + "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" + } + }, + "servers": [ + { + "url": "https://myawesomeapi.com", + "description": "Production instance of my API" + }, + { + "url": "https://staging.myawesomeapi.com", + "description": "Where the fun stuff happens" + } + ], + "paths": { + "/test/": { + "get": { + "tags": [], + "summary": "Great Summary!", + "description": "testing more", + "parameters": [], + "responses": { + "200": { + "description": "A Successful Endeavor", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestResponse" + } + } + } + } + }, + "deprecated": false + }, + "parameters": [] + } + }, + "webhooks": {}, + "components": { + "schemas": { + "TestResponse": { + "type": "object", + "properties": { + "c": { + "type": "string" + } + }, + "required": [ + "c" + ] + } + }, + "securitySchemes": {} + }, + "security": [], + "tags": [] +} diff --git a/kompendium-core/src/test/resources/notarized_get_with_exception_response.json b/core/src/test/resources/T0016__notarized_get_with_exception_response.json similarity index 74% rename from kompendium-core/src/test/resources/notarized_get_with_exception_response.json rename to core/src/test/resources/T0016__notarized_get_with_exception_response.json index 6425c0f4b..f70a0f97b 100644 --- a/kompendium-core/src/test/resources/notarized_get_with_exception_response.json +++ b/core/src/test/resources/T0016__notarized_get_with_exception_response.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,32 +27,12 @@ } ], "paths": { - "/test": { + "/": { "get": { "tags": [], - "summary": "Another get test", + "summary": "Great Summary!", "description": "testing more", - "parameters": [ - { - "name": "a", - "in": "path", - "schema": { - "type": "string" - }, - "required": true, - "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false - } - ], + "parameters": [], "responses": { "200": { "description": "A Successful Endeavor", @@ -75,12 +56,15 @@ } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -88,10 +72,10 @@ }, "required": [ "c" - ], - "type": "object" + ] }, "ExceptionResponse": { + "type": "object", "properties": { "message": { "type": "string" @@ -99,8 +83,7 @@ }, "required": [ "message" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/notarized_get_with_multiple_exception_responses.json b/core/src/test/resources/T0017__notarized_get_with_multiple_exception_responses.json similarity index 76% rename from kompendium-core/src/test/resources/notarized_get_with_multiple_exception_responses.json rename to core/src/test/resources/T0017__notarized_get_with_multiple_exception_responses.json index 5b31493e4..02a59a3ed 100644 --- a/kompendium-core/src/test/resources/notarized_get_with_multiple_exception_responses.json +++ b/core/src/test/resources/T0017__notarized_get_with_multiple_exception_responses.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,32 +27,12 @@ } ], "paths": { - "/test": { + "/": { "get": { "tags": [], - "summary": "Another get test", + "summary": "Great Summary!", "description": "testing more", - "parameters": [ - { - "name": "a", - "in": "path", - "schema": { - "type": "string" - }, - "required": true, - "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false - } - ], + "parameters": [], "responses": { "200": { "description": "A Successful Endeavor", @@ -63,16 +44,6 @@ } } }, - "403": { - "description": "Access Denied", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ExceptionResponse" - } - } - } - }, "400": { "description": "Bad Things Happened", "content": { @@ -82,15 +53,28 @@ } } } + }, + "403": { + "description": "Access Denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExceptionResponse" + } + } + } } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -98,10 +82,10 @@ }, "required": [ "c" - ], - "type": "object" + ] }, "ExceptionResponse": { + "type": "object", "properties": { "message": { "type": "string" @@ -109,8 +93,7 @@ }, "required": [ "message" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/polymorphic_error_status_codes.json b/core/src/test/resources/T0018__polymorphic_error_status_codes.json similarity index 66% rename from kompendium-core/src/test/resources/polymorphic_error_status_codes.json rename to core/src/test/resources/T0018__polymorphic_error_status_codes.json index bbd8574d1..38136706f 100644 --- a/kompendium-core/src/test/resources/polymorphic_error_status_codes.json +++ b/core/src/test/resources/T0018__polymorphic_error_status_codes.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,32 +27,12 @@ } ], "paths": { - "/test": { + "/": { "get": { "tags": [], - "summary": "Another get test", + "summary": "Great Summary!", "description": "testing more", - "parameters": [ - { - "name": "a", - "in": "path", - "schema": { - "type": "string" - }, - "required": true, - "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false - } - ], + "parameters": [], "responses": { "200": { "description": "A Successful Endeavor", @@ -63,31 +44,27 @@ } } }, - "501": { - "description": "The Gibbits are ANGRY", + "500": { + "description": "Bad Things Happened", "content": { "application/json": { "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/SimpleGibbit" - }, - { - "$ref": "#/components/schemas/ComplexGibbit" - } - ] + "$ref": "#/components/schemas/FlibbityGibbit" } } } } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -95,10 +72,30 @@ }, "required": [ "c" - ], - "type": "object" + ] + }, + "ComplexGibbit": { + "type": "object", + "properties": { + "b": { + "type": "string" + }, + "c": { + "type": "number", + "format": "int32" + }, + "z": { + "type": "string" + } + }, + "required": [ + "b", + "c", + "z" + ] }, "SimpleGibbit": { + "type": "object", "properties": { "a": { "type": "string" @@ -109,27 +106,17 @@ }, "required": [ "a" - ], - "type": "object" + ] }, - "ComplexGibbit": { - "properties": { - "b": { - "type": "string" + "FlibbityGibbit": { + "anyOf": [ + { + "$ref": "#/components/schemas/ComplexGibbit" }, - "c": { - "format": "int32", - "type": "integer" - }, - "z": { - "type": "string" + { + "$ref": "#/components/schemas/SimpleGibbit" } - }, - "required": [ - "b", - "c" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/generic_exception.json b/core/src/test/resources/T0019__generic_exception.json similarity index 67% rename from kompendium-core/src/test/resources/generic_exception.json rename to core/src/test/resources/T0019__generic_exception.json index 7258b1152..0ea6e4368 100644 --- a/kompendium-core/src/test/resources/generic_exception.json +++ b/core/src/test/resources/T0019__generic_exception.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,32 +27,12 @@ } ], "paths": { - "/test": { + "/": { "get": { "tags": [], - "summary": "Another get test", + "summary": "Great Summary!", "description": "testing more", - "parameters": [ - { - "name": "a", - "in": "path", - "schema": { - "type": "string" - }, - "required": true, - "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false - } - ], + "parameters": [], "responses": { "200": { "description": "A Successful Endeavor", @@ -64,30 +45,26 @@ } }, "400": { - "description": "Wow serious things went wrong", + "description": "Bad Things Happened", "content": { "application/json": { "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/Gibbity-String" - }, - { - "$ref": "#/components/schemas/Bibbity-String" - } - ] + "$ref": "#/components/schemas/Flibbity-String" } } } } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -95,21 +72,10 @@ }, "required": [ "c" - ], - "type": "object" - }, - "Gibbity-String": { - "properties": { - "a": { - "type": "string" - } - }, - "required": [ - "a" - ], - "type": "object" + ] }, "Bibbity-String": { + "type": "object", "properties": { "b": { "type": "string" @@ -121,8 +87,28 @@ "required": [ "b", "f" - ], - "type": "object" + ] + }, + "Gibbity-String": { + "type": "object", + "properties": { + "a": { + "type": "string" + } + }, + "required": [ + "a" + ] + }, + "Flibbity-String": { + "anyOf": [ + { + "$ref": "#/components/schemas/Bibbity-String" + }, + { + "$ref": "#/components/schemas/Gibbity-String" + } + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/example_req_and_resp.json b/core/src/test/resources/T0020__example_req_and_resp.json similarity index 69% rename from kompendium-core/src/test/resources/example_req_and_resp.json rename to core/src/test/resources/T0020__example_req_and_resp.json index 04a8b16a6..e50f852c7 100644 --- a/kompendium-core/src/test/resources/example_req_and_resp.json +++ b/core/src/test/resources/T0020__example_req_and_resp.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,39 +27,28 @@ } ], "paths": { - "/test/examples": { + "/": { "post": { "tags": [], - "summary": "Example Parameters", - "description": "A test for setting parameter examples", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "requestBody": { - "description": "Test", + "description": "You gotta send it", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TestRequest" }, "examples": { - "one": { + "Testerina": { "value": { "fieldName": { - "nesty": "hey" + "nesty": "asdf" }, - "b": 4.0, + "b": 1.5, "aaa": [] } - }, - "two": { - "value": { - "fieldName": { - "nesty": "hello" - }, - "b": 3.8, - "aaa": [ - 31324234 - ] - } } } } @@ -66,17 +56,17 @@ "required": true }, "responses": { - "201": { - "description": "nice", + "200": { + "description": "A Successful Endeavor", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TestResponse" }, "examples": { - "test": { + "Testerino": { "value": { - "c": "spud" + "c": "Heya" } } } @@ -85,47 +75,15 @@ } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { - "TestRequest": { - "properties": { - "aaa": { - "items": { - "format": "int64", - "type": "integer" - }, - "type": "array" - }, - "b": { - "format": "double", - "type": "number" - }, - "field_name": { - "$ref": "#/components/schemas/TestNested" - } - }, - "required": [ - "field_name", - "b", - "aaa" - ], - "type": "object" - }, - "TestNested": { - "properties": { - "nesty": { - "type": "string" - } - }, - "required": [ - "nesty" - ], - "type": "object" - }, "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -133,8 +91,42 @@ }, "required": [ "c" - ], - "type": "object" + ] + }, + "TestRequest": { + "type": "object", + "properties": { + "aaa": { + "items": { + "type": "number", + "format": "int64" + }, + "type": "array" + }, + "b": { + "type": "number", + "format": "double" + }, + "fieldName": { + "$ref": "#/components/schemas/TestNested" + } + }, + "required": [ + "aaa", + "b", + "fieldName" + ] + }, + "TestNested": { + "type": "object", + "properties": { + "nesty": { + "type": "string" + } + }, + "required": [ + "nesty" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/trailing_slash.json b/core/src/test/resources/T0021__example_parameters.json similarity index 78% rename from kompendium-core/src/test/resources/trailing_slash.json rename to core/src/test/resources/T0021__example_parameters.json index bcfd906f0..ac6bb7d72 100644 --- a/kompendium-core/src/test/resources/trailing_slash.json +++ b/core/src/test/resources/T0021__example_parameters.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,30 +27,25 @@ } ], "paths": { - "/test/": { + "/": { "get": { "tags": [], - "summary": "Another get test", + "summary": "Great Summary!", "description": "testing more", "parameters": [ { - "name": "a", + "name": "id", "in": "path", "schema": { "type": "string" }, "required": true, - "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false + "deprecated": false, + "examples": { + "foo": { + "value": "testing" + } + } } ], "responses": { @@ -65,12 +61,15 @@ } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -78,8 +77,7 @@ }, "required": [ "c" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/notarized_get.json b/core/src/test/resources/T0022__query_with_default_parameter.json similarity index 78% rename from kompendium-core/src/test/resources/notarized_get.json rename to core/src/test/resources/T0022__query_with_default_parameter.json index 5d327bc67..0e244cd13 100644 --- a/kompendium-core/src/test/resources/notarized_get.json +++ b/core/src/test/resources/T0022__query_with_default_parameter.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,27 +27,18 @@ } ], "paths": { - "/test": { + "/": { "get": { "tags": [], - "summary": "Another get test", + "summary": "Great Summary!", "description": "testing more", "parameters": [ { - "name": "a", + "name": "id", "in": "path", "schema": { - "type": "string" - }, - "required": true, - "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" + "type": "string", + "default": "IDK" }, "required": true, "deprecated": false @@ -65,12 +57,15 @@ } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -78,8 +73,7 @@ }, "required": [ "c" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/root_route.json b/core/src/test/resources/T0023__required_param.json similarity index 81% rename from kompendium-core/src/test/resources/root_route.json rename to core/src/test/resources/T0023__required_param.json index b4fc750ea..015e5297e 100644 --- a/kompendium-core/src/test/resources/root_route.json +++ b/core/src/test/resources/T0023__required_param.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -29,27 +30,17 @@ "/": { "get": { "tags": [], - "summary": "Another get test", + "summary": "Great Summary!", "description": "testing more", "parameters": [ { - "name": "a", + "name": "id", "in": "path", "schema": { "type": "string" }, "required": true, "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false } ], "responses": { @@ -65,12 +56,15 @@ } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -78,8 +72,7 @@ }, "required": [ "c" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/required_param.json b/core/src/test/resources/T0024__non_required_param.json similarity index 79% rename from kompendium-core/src/test/resources/required_param.json rename to core/src/test/resources/T0024__non_required_param.json index fdbec4100..2bdf8224c 100644 --- a/kompendium-core/src/test/resources/required_param.json +++ b/core/src/test/resources/T0024__non_required_param.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,25 +27,25 @@ } ], "paths": { - "/test/required_param": { + "/": { "get": { "tags": [], - "summary": "required param", - "description": "Cool stuff", + "summary": "Great Summary!", + "description": "testing more", "parameters": [ { - "name": "a", + "name": "id", "in": "query", "schema": { "type": "string" }, - "required": true, + "required": false, "deprecated": false } ], "responses": { "200": { - "description": "A successful endeavor", + "description": "A Successful Endeavor", "content": { "application/json": { "schema": { @@ -55,12 +56,15 @@ } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -68,8 +72,7 @@ }, "required": [ "c" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/min_max_double_field.json b/core/src/test/resources/T0025__default_field.json similarity index 70% rename from kompendium-core/src/test/resources/min_max_double_field.json rename to core/src/test/resources/T0025__default_field.json index ae7c842e0..d73bb36e1 100644 --- a/kompendium-core/src/test/resources/min_max_double_field.json +++ b/core/src/test/resources/T0025__default_field.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,45 +27,46 @@ } ], "paths": { - "/test/constrained_int": { + "/": { "get": { "tags": [], - "summary": "Constrained int field", - "description": "Cool stuff", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "responses": { "200": { - "description": "A successful endeavor", + "description": "A Successful Endeavor", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MinMaxDouble" + "$ref": "#/components/schemas/DefaultField" } } } } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { - "MinMaxDouble": { + "DefaultField": { + "type": "object", "properties": { "a": { - "format": "double", + "type": "string" + }, + "b": { "type": "number", - "minimum": 5.5, - "maximum": 13.37, - "exclusiveMinimum": false, - "exclusiveMaximum": false + "format": "int32" } }, "required": [ - "a" - ], - "type": "object" + "b" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/exclusive_min_max.json b/core/src/test/resources/T0026__nullable_field.json similarity index 66% rename from kompendium-core/src/test/resources/exclusive_min_max.json rename to core/src/test/resources/T0026__nullable_field.json index 3880314f8..5b74c244f 100644 --- a/kompendium-core/src/test/resources/exclusive_min_max.json +++ b/core/src/test/resources/T0026__nullable_field.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,45 +27,47 @@ } ], "paths": { - "/test/constrained_int": { + "/": { "get": { "tags": [], - "summary": "Constrained int field", - "description": "Cool stuff", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "responses": { "200": { - "description": "A successful endeavor", + "description": "A Successful Endeavor", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ExclusiveMinMax" + "$ref": "#/components/schemas/NullableField" } } } } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { - "ExclusiveMinMax": { + "NullableField": { + "type": "object", "properties": { "a": { - "format": "int32", - "type": "integer", - "minimum": 5, - "maximum": 100, - "exclusiveMinimum": true, - "exclusiveMaximum": true + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] } }, - "required": [ - "a" - ], - "type": "object" + "required": [] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/polymorphic_list_response.json b/core/src/test/resources/T0027__polymorphic_response.json similarity index 68% rename from kompendium-core/src/test/resources/polymorphic_list_response.json rename to core/src/test/resources/T0027__polymorphic_response.json index 05d9aeedd..df40ec0c9 100644 --- a/kompendium-core/src/test/resources/polymorphic_list_response.json +++ b/core/src/test/resources/T0027__polymorphic_response.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,41 +27,54 @@ } ], "paths": { - "/test/polymorphiclist": { + "/": { "get": { "tags": [], - "summary": "Oh so many gibbits", - "description": "Polymorphic list response", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "responses": { "200": { - "description": "A successful endeavor", + "description": "A Successful Endeavor", "content": { "application/json": { "schema": { - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/SimpleGibbit" - }, - { - "$ref": "#/components/schemas/ComplexGibbit" - } - ] - }, - "type": "array" + "$ref": "#/components/schemas/FlibbityGibbit" } } } } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { + "ComplexGibbit": { + "type": "object", + "properties": { + "b": { + "type": "string" + }, + "c": { + "type": "number", + "format": "int32" + }, + "z": { + "type": "string" + } + }, + "required": [ + "b", + "c", + "z" + ] + }, "SimpleGibbit": { + "type": "object", "properties": { "a": { "type": "string" @@ -71,27 +85,17 @@ }, "required": [ "a" - ], - "type": "object" + ] }, - "ComplexGibbit": { - "properties": { - "b": { - "type": "string" + "FlibbityGibbit": { + "anyOf": [ + { + "$ref": "#/components/schemas/ComplexGibbit" }, - "c": { - "format": "int32", - "type": "integer" - }, - "z": { - "type": "string" + { + "$ref": "#/components/schemas/SimpleGibbit" } - }, - "required": [ - "b", - "c" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/polymorphic_map_response.json b/core/src/test/resources/T0028__polymorphic_list_response.json similarity index 65% rename from kompendium-core/src/test/resources/polymorphic_map_response.json rename to core/src/test/resources/T0028__polymorphic_list_response.json index 268266592..8cd11e1d2 100644 --- a/kompendium-core/src/test/resources/polymorphic_map_response.json +++ b/core/src/test/resources/T0028__polymorphic_list_response.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,41 +27,54 @@ } ], "paths": { - "/test/polymorphicmap": { + "/": { "get": { "tags": [], - "summary": "By gawd that's a lot of gibbits", - "description": "Polymorphic list response", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "responses": { "200": { - "description": "A successful endeavor", + "description": "A Successful Endeavor", "content": { "application/json": { "schema": { - "additionalProperties": { - "anyOf": [ - { - "$ref": "#/components/schemas/SimpleGibbit" - }, - { - "$ref": "#/components/schemas/ComplexGibbit" - } - ] - }, - "type": "object" + "$ref": "#/components/schemas/List-FlibbityGibbit" } } } } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { + "ComplexGibbit": { + "type": "object", + "properties": { + "b": { + "type": "string" + }, + "c": { + "type": "number", + "format": "int32" + }, + "z": { + "type": "string" + } + }, + "required": [ + "b", + "c", + "z" + ] + }, "SimpleGibbit": { + "type": "object", "properties": { "a": { "type": "string" @@ -71,27 +85,20 @@ }, "required": [ "a" - ], - "type": "object" + ] }, - "ComplexGibbit": { - "properties": { - "b": { - "type": "string" - }, - "c": { - "format": "int32", - "type": "integer" - }, - "z": { - "type": "string" - } + "List-FlibbityGibbit": { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/ComplexGibbit" + }, + { + "$ref": "#/components/schemas/SimpleGibbit" + } + ] }, - "required": [ - "b", - "c" - ], - "type": "object" + "type": "array" } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/polymorphic_response.json b/core/src/test/resources/T0029__polymorphic_map_response.json similarity index 66% rename from kompendium-core/src/test/resources/polymorphic_response.json rename to core/src/test/resources/T0029__polymorphic_map_response.json index 75fff2e55..245f13cfa 100644 --- a/kompendium-core/src/test/resources/polymorphic_response.json +++ b/core/src/test/resources/T0029__polymorphic_map_response.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,38 +27,54 @@ } ], "paths": { - "/test/polymorphic": { + "/": { "get": { "tags": [], - "summary": "All the gibbits", - "description": "Polymorphic response", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "responses": { "200": { - "description": "A successful endeavor", + "description": "A Successful Endeavor", "content": { "application/json": { "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/SimpleGibbit" - }, - { - "$ref": "#/components/schemas/ComplexGibbit" - } - ] + "$ref": "#/components/schemas/Map-String-FlibbityGibbit" } } } } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { + "ComplexGibbit": { + "type": "object", + "properties": { + "b": { + "type": "string" + }, + "c": { + "type": "number", + "format": "int32" + }, + "z": { + "type": "string" + } + }, + "required": [ + "b", + "c", + "z" + ] + }, "SimpleGibbit": { + "type": "object", "properties": { "a": { "type": "string" @@ -68,26 +85,19 @@ }, "required": [ "a" - ], - "type": "object" + ] }, - "ComplexGibbit": { - "properties": { - "b": { - "type": "string" - }, - "c": { - "format": "int32", - "type": "integer" - }, - "z": { - "type": "string" - } + "Map-String-FlibbityGibbit": { + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/components/schemas/ComplexGibbit" + }, + { + "$ref": "#/components/schemas/SimpleGibbit" + } + ] }, - "required": [ - "b", - "c" - ], "type": "object" } }, diff --git a/kompendium-core/src/test/resources/formatted_no_format_string.json b/core/src/test/resources/T0030__simple_generic_response.json similarity index 75% rename from kompendium-core/src/test/resources/formatted_no_format_string.json rename to core/src/test/resources/T0030__simple_generic_response.json index 6d9309755..7a44d2eb1 100644 --- a/kompendium-core/src/test/resources/formatted_no_format_string.json +++ b/core/src/test/resources/T0030__simple_generic_response.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,31 +27,34 @@ } ], "paths": { - "/test/date_time_format": { + "/": { "get": { "tags": [], - "summary": "Date time string test", - "description": "Cool stuff", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "responses": { "200": { - "description": "A successful endeavor", + "description": "A Successful Endeavor", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DateTimeString" + "$ref": "#/components/schemas/Gibbity-String" } } } } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { - "DateTimeString": { + "Gibbity-String": { + "type": "object", "properties": { "a": { "type": "string" @@ -58,8 +62,7 @@ }, "required": [ "a" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/min_max_array.json b/core/src/test/resources/T0031__nested_generic_response.json similarity index 72% rename from kompendium-core/src/test/resources/min_max_array.json rename to core/src/test/resources/T0031__nested_generic_response.json index 68e58e156..a2a1abd1c 100644 --- a/kompendium-core/src/test/resources/min_max_array.json +++ b/core/src/test/resources/T0031__nested_generic_response.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,45 +27,45 @@ } ], "paths": { - "/test/required_param": { + "/": { "get": { "tags": [], - "summary": "required param", - "description": "Cool stuff", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "responses": { "200": { - "description": "A successful endeavor", + "description": "A Successful Endeavor", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/MinMaxArray" + "$ref": "#/components/schemas/Gibbity-Map" } } } } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { - "MinMaxArray": { + "Gibbity-Map": { + "type": "object", "properties": { "a": { - "items": { + "additionalProperties": { "type": "string" }, - "minItems": 1, - "maxItems": 10, - "type": "array" + "type": "object" } }, "required": [ "a" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/polymorphic_response_with_generics.json b/core/src/test/resources/T0032__polymorphic_response_with_generics.json similarity index 60% rename from kompendium-core/src/test/resources/polymorphic_response_with_generics.json rename to core/src/test/resources/T0032__polymorphic_response_with_generics.json index 03628789f..cb0785750 100644 --- a/kompendium-core/src/test/resources/polymorphic_response_with_generics.json +++ b/core/src/test/resources/T0032__polymorphic_response_with_generics.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,73 +27,69 @@ } ], "paths": { - "/test/polymorphic": { + "/": { "get": { "tags": [], - "summary": "More flibbity", - "description": "Polymorphic with generics", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "responses": { "200": { - "description": "A successful endeavor", + "description": "A Successful Endeavor", "content": { "application/json": { "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/Gibbity-TestNested" - }, - { - "$ref": "#/components/schemas/Bibbity-TestNested" - } - ] + "$ref": "#/components/schemas/Flibbity-Double" } } } } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { - "Gibbity-TestNested": { - "properties": { - "a": { - "$ref": "#/components/schemas/TestNested" - } - }, - "required": [ - "a" - ], - "type": "object" - }, - "TestNested": { - "properties": { - "nesty": { - "type": "string" - } - }, - "required": [ - "nesty" - ], - "type": "object" - }, - "Bibbity-TestNested": { + "Bibbity-Double": { + "type": "object", "properties": { "b": { "type": "string" }, "f": { - "$ref": "#/components/schemas/TestNested" + "type": "number", + "format": "double" } }, "required": [ "b", "f" - ], - "type": "object" + ] + }, + "Gibbity-Double": { + "type": "object", + "properties": { + "a": { + "type": "number", + "format": "double" + } + }, + "required": [ + "a" + ] + }, + "Flibbity-Double": { + "anyOf": [ + { + "$ref": "#/components/schemas/Bibbity-Double" + }, + { + "$ref": "#/components/schemas/Gibbity-Double" + } + ] } }, "securitySchemes": {} diff --git a/core/src/test/resources/T0033__crazy_polymorphic_example.json b/core/src/test/resources/T0033__crazy_polymorphic_example.json new file mode 100644 index 000000000..725ddc1aa --- /dev/null +++ b/core/src/test/resources/T0033__crazy_polymorphic_example.json @@ -0,0 +1,145 @@ +{ + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", + "info": { + "title": "Test API", + "version": "1.33.7", + "description": "An amazing, fully-ish 😉 generated API spec", + "termsOfService": "https://example.com", + "contact": { + "name": "Homer Simpson", + "url": "https://gph.is/1NPUDiM", + "email": "chunkylover53@aol.com" + }, + "license": { + "name": "MIT", + "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" + } + }, + "servers": [ + { + "url": "https://myawesomeapi.com", + "description": "Production instance of my API" + }, + { + "url": "https://staging.myawesomeapi.com", + "description": "Where the fun stuff happens" + } + ], + "paths": { + "/": { + "get": { + "tags": [], + "summary": "Great Summary!", + "description": "testing more", + "parameters": [], + "responses": { + "200": { + "description": "A Successful Endeavor", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Flibbity-FlibbityGibbit" + } + } + } + } + }, + "deprecated": false + }, + "parameters": [] + } + }, + "webhooks": {}, + "components": { + "schemas": { + "ComplexGibbit": { + "type": "object", + "properties": { + "b": { + "type": "string" + }, + "c": { + "type": "number", + "format": "int32" + }, + "z": { + "type": "string" + } + }, + "required": [ + "b", + "c", + "z" + ] + }, + "SimpleGibbit": { + "type": "object", + "properties": { + "a": { + "type": "string" + }, + "z": { + "type": "string" + } + }, + "required": [ + "a" + ] + }, + "Bibbity-FlibbityGibbit": { + "type": "object", + "properties": { + "b": { + "type": "string" + }, + "f": { + "anyOf": [ + { + "$ref": "#/components/schemas/ComplexGibbit" + }, + { + "$ref": "#/components/schemas/SimpleGibbit" + } + ] + } + }, + "required": [ + "b", + "f" + ] + }, + "Gibbity-FlibbityGibbit": { + "type": "object", + "properties": { + "a": { + "anyOf": [ + { + "$ref": "#/components/schemas/ComplexGibbit" + }, + { + "$ref": "#/components/schemas/SimpleGibbit" + } + ] + } + }, + "required": [ + "a" + ] + }, + "Flibbity-FlibbityGibbit": { + "anyOf": [ + { + "$ref": "#/components/schemas/Bibbity-FlibbityGibbit" + }, + { + "$ref": "#/components/schemas/Gibbity-FlibbityGibbit" + } + ] + } + }, + "securitySchemes": {} + }, + "security": [], + "tags": [] +} diff --git a/kompendium-core/src/test/resources/formatted_param_type.json b/core/src/test/resources/T0034__notarized_get_with_operation_id.json similarity index 71% rename from kompendium-core/src/test/resources/formatted_param_type.json rename to core/src/test/resources/T0034__notarized_get_with_operation_id.json index 21612b070..895503d51 100644 --- a/kompendium-core/src/test/resources/formatted_param_type.json +++ b/core/src/test/resources/T0034__notarized_get_with_operation_id.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,26 +27,16 @@ } ], "paths": { - "/test/required_param": { + "/": { "get": { "tags": [], - "summary": "required param", - "description": "Cool stuff", - "parameters": [ - { - "name": "a", - "in": "query", - "schema": { - "type": "string", - "format": "password" - }, - "required": true, - "deprecated": false - } - ], + "summary": "Great Summary!", + "description": "testing more", + "operationId": "getThisDude", + "parameters": [], "responses": { "200": { - "description": "A successful endeavor", + "description": "A Successful Endeavor", "content": { "application/json": { "schema": { @@ -56,12 +47,15 @@ } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -69,8 +63,7 @@ }, "required": [ "c" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/override_parameter_name.json b/core/src/test/resources/T0035__override_parameter_name.json similarity index 80% rename from kompendium-core/src/test/resources/override_parameter_name.json rename to core/src/test/resources/T0035__override_parameter_name.json index 8cd72f1de..5f30a0521 100644 --- a/kompendium-core/src/test/resources/override_parameter_name.json +++ b/core/src/test/resources/T0035__override_parameter_name.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,14 +27,14 @@ } ], "paths": { - "/test/with_header": { + "/": { "get": { "tags": [], - "summary": "testing header stuffs", - "description": "Good for many things", + "summary": "Great Summary!", + "description": "testing more", "parameters": [ { - "name": "X-UserEmail", + "name": "X-User-Email", "in": "header", "schema": { "type": "string" @@ -44,7 +45,7 @@ ], "responses": { "200": { - "description": "A successful endeavor", + "description": "A Successful Endeavor", "content": { "application/json": { "schema": { @@ -55,12 +56,15 @@ } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { "TestResponse": { + "type": "object", "properties": { "c": { "type": "string" @@ -68,8 +72,7 @@ }, "required": [ "c" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/core/src/test/resources/T0036__nullable_fields.json b/core/src/test/resources/T0036__nullable_fields.json new file mode 100644 index 000000000..82b949529 --- /dev/null +++ b/core/src/test/resources/T0036__nullable_fields.json @@ -0,0 +1,121 @@ +{ + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", + "info": { + "title": "Test API", + "version": "1.33.7", + "description": "An amazing, fully-ish 😉 generated API spec", + "termsOfService": "https://example.com", + "contact": { + "name": "Homer Simpson", + "url": "https://gph.is/1NPUDiM", + "email": "chunkylover53@aol.com" + }, + "license": { + "name": "MIT", + "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" + } + }, + "servers": [ + { + "url": "https://myawesomeapi.com", + "description": "Production instance of my API" + }, + { + "url": "https://staging.myawesomeapi.com", + "description": "Where the fun stuff happens" + } + ], + "paths": { + "/": { + "get": { + "tags": [], + "summary": "Great Summary!", + "description": "testing more", + "parameters": [], + "responses": { + "200": { + "description": "A Successful Endeavor", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileUpdateRequest" + } + } + } + } + }, + "deprecated": false + }, + "parameters": [] + } + }, + "webhooks": {}, + "components": { + "schemas": { + "ProfileUpdateRequest": { + "type": "object", + "properties": { + "metadata": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "properties": { + "isPrivate": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "boolean" + } + ] + }, + "otherThing": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + } + }, + "required": [] + } + ] + }, + "mood": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string" + } + ] + }, + "viewCount": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "number", + "format": "int64" + } + ] + } + }, + "required": [] + } + }, + "securitySchemes": {} + }, + "security": [], + "tags": [] +} diff --git a/kompendium-core/src/test/resources/nullable_enum_field.json b/core/src/test/resources/T0037__nullable_enum_field.json similarity index 69% rename from kompendium-core/src/test/resources/nullable_enum_field.json rename to core/src/test/resources/T0037__nullable_enum_field.json index a100ca747..35484c30d 100644 --- a/kompendium-core/src/test/resources/nullable_enum_field.json +++ b/core/src/test/resources/T0037__nullable_enum_field.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,15 +27,15 @@ } ], "paths": { - "/nullable/enum": { + "/": { "get": { "tags": [], - "summary": "Has a nullable enum field", - "description": "should still work!", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "responses": { "200": { - "description": "A successful endeavor", + "description": "A Successful Endeavor", "content": { "application/json": { "schema": { @@ -45,25 +46,31 @@ } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { "NullableEnum": { + "type": "object", "properties": { "a": { - "$ref": "#/components/schemas/TestEnum" + "oneOf": [ + { + "type": "null" + }, + { + "enum": [ + "YES", + "NO" + ] + } + ] } }, - "type": "object" - }, - "TestEnum": { - "enum": [ - "YES", - "NO" - ], - "type": "string" + "required": [] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/formatted_date_time_string.json b/core/src/test/resources/T0038__formatted_date_time_string.json similarity index 75% rename from kompendium-core/src/test/resources/formatted_date_time_string.json rename to core/src/test/resources/T0038__formatted_date_time_string.json index fd8d44e58..1609a4022 100644 --- a/kompendium-core/src/test/resources/formatted_date_time_string.json +++ b/core/src/test/resources/T0038__formatted_date_time_string.json @@ -1,5 +1,6 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", + "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "info": { "title": "Test API", "version": "1.33.7", @@ -26,15 +27,15 @@ } ], "paths": { - "/test/date_time_format": { + "/": { "get": { "tags": [], - "summary": "Date time string test", - "description": "Cool stuff", + "summary": "Great Summary!", + "description": "testing more", "parameters": [], "responses": { "200": { - "description": "A successful endeavor", + "description": "A Successful Endeavor", "content": { "application/json": { "schema": { @@ -45,22 +46,28 @@ } }, "deprecated": false - } + }, + "parameters": [] } }, + "webhooks": {}, "components": { "schemas": { + "Instant": { + "type": "string", + "format": "date" + }, "DateTimeString": { + "type": "object", "properties": { "a": { "type": "string", - "format": "date-time" + "format": "date" } }, "required": [ "a" - ], - "type": "object" + ] } }, "securitySchemes": {} diff --git a/kompendium-core/src/test/resources/redoc.html b/core/src/test/resources/redoc.html similarity index 100% rename from kompendium-core/src/test/resources/redoc.html rename to core/src/test/resources/redoc.html diff --git a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/SupportedSerializers.kt b/core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/SupportedSerializers.kt similarity index 100% rename from kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/SupportedSerializers.kt rename to core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/SupportedSerializers.kt diff --git a/core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestHelpers.kt b/core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestHelpers.kt new file mode 100644 index 000000000..3e84d25b0 --- /dev/null +++ b/core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestHelpers.kt @@ -0,0 +1,104 @@ +package io.bkbn.kompendium.core.fixtures + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.databind.SerializationFeature +import io.bkbn.kompendium.core.fixtures.TestSpecs.defaultSpec +import io.bkbn.kompendium.core.plugin.NotarizedApplication +import io.bkbn.kompendium.core.routes.redoc +import io.bkbn.kompendium.json.schema.definition.JsonSchema +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 io.kotest.assertions.json.shouldEqualJson +import io.kotest.assertions.ktor.client.shouldHaveStatus +import io.kotest.matchers.shouldNot +import io.kotest.matchers.string.beBlank +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.serialization.gson.gson +import io.ktor.serialization.jackson.jackson +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.routing.Routing +import io.ktor.server.testing.ApplicationTestBuilder +import io.ktor.server.testing.testApplication +import kotlin.reflect.KType +import kotlinx.serialization.json.Json +import java.io.File +import java.net.URI + +object TestHelpers { + private const val OPEN_API_ENDPOINT = "/openapi.json" + + fun getFileSnapshot(fileName: String): String { + val snapshotPath = "src/test/resources" + val file = File("$snapshotPath/$fileName") + return file.readText() + } + + /** + * Performs the baseline expected tests on an OpenAPI result. Confirms that the endpoint + * exists as expected, and that the content matches the expected blob found in the specified file + * @param snapshotName The snapshot file to retrieve from the resources folder + */ + private suspend fun ApplicationTestBuilder.compareOpenAPISpec(snapshotName: String) { + val response = client.get(OPEN_API_ENDPOINT) + response shouldHaveStatus HttpStatusCode.OK + response.bodyAsText() shouldNot beBlank() + response.bodyAsText() shouldEqualJson getFileSnapshot(snapshotName) + } + + /** + * 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. 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 openApiTestAllSerializers( + snapshotName: String, + customTypes: Map = emptyMap(), + routeUnderTest: Routing.() -> Unit + ) { + openApiTest(snapshotName, SupportedSerializer.KOTLINX, routeUnderTest, customTypes) + openApiTest(snapshotName, SupportedSerializer.JACKSON, routeUnderTest, customTypes) + openApiTest(snapshotName, SupportedSerializer.GSON, routeUnderTest, customTypes) + } + + private fun openApiTest( + snapshotName: String, + serializer: SupportedSerializer, + routeUnderTest: Routing.() -> Unit, + typeOverrides: Map = emptyMap() + ) = testApplication { + install(NotarizedApplication()) { + customTypes = typeOverrides + spec = defaultSpec() + } + install(ContentNegotiation) { + when (serializer) { + SupportedSerializer.KOTLINX -> json(Json { + encodeDefaults = true + explicitNulls = false + serializersModule = KompendiumSerializersModule.module + }) + + SupportedSerializer.GSON -> gson() + SupportedSerializer.JACKSON -> jackson(ContentType.Application.Json) { + enable(SerializationFeature.INDENT_OUTPUT) + setSerializationInclusion(JsonInclude.Include.NON_NULL) + } + } + } + routing { + redoc() + routeUnderTest() + } + compareOpenAPISpec(snapshotName) + } +} diff --git a/core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModels.kt b/core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModels.kt new file mode 100644 index 000000000..754bc7c85 --- /dev/null +++ b/core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModels.kt @@ -0,0 +1,120 @@ +package io.bkbn.kompendium.core.fixtures + +import kotlinx.serialization.Serializable +import java.time.Instant + +@Serializable +data class TestNested(val nesty: String) + +@Serializable +data class TestRequest( + val fieldName: TestNested, + val b: Double, + val aaa: List +) + +@Serializable +data class TestSimpleRequest( + val a: String, + val b: Int +) + +@Serializable +data class TestResponse(val c: String) + +@Serializable +enum class TestEnum { + YES, + NO +} + +@Serializable +data class NullableEnum(val a: TestEnum? = null) + +data class TestCreatedResponse(val id: Int, val c: String) + +data class DateTimeString( + val a: Instant +) + +data class DefaultField( + val a: String = "hi", + val b: Int +) + +data class NullableField( + val a: String? +) + +data class ComplexRequest( + val org: String, + val amazingField: String, + val tables: List +) + +data class NestedComplexItem( + val name: String, + val alias: CustomAlias +) + +typealias CustomAlias = Map + +data class CrazyItem(val enumeration: SimpleEnum) + +enum class SimpleEnum { + ONE, + TWO +} + +data class ExceptionResponse(val message: String) + +sealed class FlibbityGibbit { + abstract val z: String +} + +data class SimpleGibbit(val a: String, override val z: String = "z") : FlibbityGibbit() +data class ComplexGibbit(val b: String, val c: Int, override val z: String) : FlibbityGibbit() + +sealed interface SlammaJamma + +data class OneJamma(val a: Int) : SlammaJamma +data class AnothaJamma(val b: Float) : SlammaJamma + +data class InsaneJamma(val c: SlammaJamma) : SlammaJamma + +sealed interface Flibbity + +data class Gibbity(val a: T) : Flibbity +data class Bibbity(val b: String, val f: T) : Flibbity + +data class NestedFlibbity( + val flibbity: Flibbity +) + +enum class ColumnMode { + NULLABLE, + REQUIRED, + REPEATED +} + +data class ColumnSchema( + val name: String, + val type: String, + val description: String, + val mode: ColumnMode, + val subColumns: List = emptyList() +) + +@Serializable +public data class ProfileUpdateRequest( + public val mood: String?, + public val viewCount: Long?, + public val metadata: ProfileMetadataUpdateRequest? +) + + +@Serializable +public data class ProfileMetadataUpdateRequest( + public val isPrivate: Boolean?, + public val otherThing: String? +) diff --git a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestSpecs.kt b/core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestSpecs.kt similarity index 93% rename from kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestSpecs.kt rename to core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestSpecs.kt index b842f2095..14f880d6e 100644 --- a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestSpecs.kt +++ b/core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestSpecs.kt @@ -13,7 +13,7 @@ object TestSpecs { info = Info( title = "Test API", version = "1.33.7", - description = "An amazing, fully-ish 😉 generated API spec", + description = "An amazing, fully-ish \uD83D\uDE09 generated API spec", termsOfService = URI("https://example.com"), contact = Contact( name = "Homer Simpson", diff --git a/detekt.yml b/detekt.yml index 7096bfd7c..6c38a00ac 100644 --- a/detekt.yml +++ b/detekt.yml @@ -4,17 +4,15 @@ complexity: LongParameterList: active: true functionThreshold: 10 - constructorThreshold: 10 + constructorThreshold: 15 ComplexMethod: threshold: 20 -formatting: - ParameterListWrapping: - active: false style: MaxLineLength: excludes: ['**/test/**/*'] active: true maxLineLength: 120 + excludeCommentStatements: true MagicNumber: excludes: ['**/kompendium-playground/**/*', '**/test/**/*'] naming: diff --git a/gradle.properties b/gradle.properties index 1fe9b27e0..d3d4e8ea7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Kompendium -project.version=2.3.5 +project.version=3.0.0-alpha # Kotlin kotlin.code.style=official # Gradle @@ -8,5 +8,5 @@ org.gradle.vfs.verbose=true org.gradle.jvmargs=-Xmx2000m # Dependencies -ktorVersion=1.6.8 +ktorVersion=2.1.0 kotestVersion=5.4.2 diff --git a/json-schema/build.gradle.kts b/json-schema/build.gradle.kts new file mode 100644 index 000000000..4fd1fee5c --- /dev/null +++ b/json-schema/build.gradle.kts @@ -0,0 +1,32 @@ +plugins { + kotlin("jvm") + kotlin("plugin.serialization") + id("io.bkbn.sourdough.library.jvm") + id("io.gitlab.arturbosch.detekt") + id("com.adarshr.test-logger") + id("org.jetbrains.dokka") + id("maven-publish") + id("java-library") + id("signing") +} + +sourdoughLibrary { + libraryName.set("Kompendium JSON Schema") + libraryDescription.set("Json Schema Generator") + compilerArgs.set(listOf("-opt-in=kotlin.RequiresOptIn")) +} + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-reflect:1.7.10") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3") + + testImplementation(testFixtures(projects.kompendiumCore)) +} + +testing { + suites { + named("test", JvmTestSuite::class) { + useJUnitJupiter() + } + } +} diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/SchemaGenerator.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/SchemaGenerator.kt new file mode 100644 index 000000000..a5e8ac423 --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/SchemaGenerator.kt @@ -0,0 +1,65 @@ +package io.bkbn.kompendium.json.schema + +import io.bkbn.kompendium.json.schema.definition.JsonSchema +import io.bkbn.kompendium.json.schema.definition.NullableDefinition +import io.bkbn.kompendium.json.schema.definition.OneOfDefinition +import io.bkbn.kompendium.json.schema.definition.TypeDefinition +import io.bkbn.kompendium.json.schema.handler.CollectionHandler +import io.bkbn.kompendium.json.schema.handler.EnumHandler +import io.bkbn.kompendium.json.schema.handler.MapHandler +import io.bkbn.kompendium.json.schema.handler.SimpleObjectHandler +import io.bkbn.kompendium.json.schema.handler.SealedObjectHandler +import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug +import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.full.isSubclassOf +import kotlin.reflect.typeOf + +object SchemaGenerator { + inline fun fromTypeToSchema(cache: MutableMap = mutableMapOf()) = + fromTypeToSchema(typeOf(), cache) + + fun fromTypeToSchema(type: KType, cache: MutableMap): JsonSchema { + cache[type.getSimpleSlug()]?.let { + return it + } + return when (val clazz = type.classifier as KClass<*>) { + Unit::class -> error( + """ + Unit cannot be converted to JsonSchema. + If you are looking for a method will return null when called with Unit, + please call SchemaGenerator.fromTypeOrUnit() + """.trimIndent() + ) + Int::class -> checkForNull(type, TypeDefinition.INT) + Long::class -> checkForNull(type, TypeDefinition.LONG) + Double::class -> checkForNull(type, TypeDefinition.DOUBLE) + Float::class -> checkForNull(type, TypeDefinition.FLOAT) + String::class -> checkForNull(type, TypeDefinition.STRING) + Boolean::class -> checkForNull(type, TypeDefinition.BOOLEAN) + else -> when { + clazz.isSubclassOf(Enum::class) -> EnumHandler.handle(type, clazz) + clazz.isSubclassOf(Collection::class) -> CollectionHandler.handle(type, cache) + clazz.isSubclassOf(Map::class) -> MapHandler.handle(type, cache) + else -> { + if (clazz.isSealed) { + SealedObjectHandler.handle(type, clazz, cache) + } else { + SimpleObjectHandler.handle(type, clazz, cache) + } + } + } + } + } + + fun fromTypeOrUnit(type: KType, cache: MutableMap = mutableMapOf()): JsonSchema? = + when (type.classifier as KClass<*>) { + Unit::class -> null + else -> fromTypeToSchema(type, cache) + } + + private fun checkForNull(type: KType, schema: JsonSchema): JsonSchema = when (type.isMarkedNullable) { + true -> OneOfDefinition(NullableDefinition(), schema) + false -> schema + } +} diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/AnyOfDefinition.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/AnyOfDefinition.kt new file mode 100644 index 000000000..8aefb84b2 --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/AnyOfDefinition.kt @@ -0,0 +1,6 @@ +package io.bkbn.kompendium.json.schema.definition + +import kotlinx.serialization.Serializable + +@Serializable +data class AnyOfDefinition(val anyOf: Set) : JsonSchema diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/ArrayDefinition.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/ArrayDefinition.kt new file mode 100644 index 000000000..94dd359ee --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/ArrayDefinition.kt @@ -0,0 +1,10 @@ +package io.bkbn.kompendium.json.schema.definition + +import kotlinx.serialization.Serializable + +@Serializable +data class ArrayDefinition( + val items: JsonSchema +) : JsonSchema { + val type: String = "array" +} diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/EnumDefinition.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/EnumDefinition.kt new file mode 100644 index 000000000..5da5c56cb --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/EnumDefinition.kt @@ -0,0 +1,8 @@ +package io.bkbn.kompendium.json.schema.definition + +import kotlinx.serialization.Serializable + +@Serializable +data class EnumDefinition( + val enum: Set +) : JsonSchema diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/JsonSchema.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/JsonSchema.kt new file mode 100644 index 000000000..d6d6675e4 --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/JsonSchema.kt @@ -0,0 +1,33 @@ +package io.bkbn.kompendium.json.schema.definition + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +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 + +@Serializable(with = JsonSchema.Serializer::class) +sealed interface JsonSchema { + + object Serializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("JsonSchema", PrimitiveKind.STRING) + override fun deserialize(decoder: Decoder): JsonSchema { + error("Abandon all hope ye who enter 💀") + } + + override fun serialize(encoder: Encoder, value: JsonSchema) { + when (value) { + is ReferenceDefinition -> ReferenceDefinition.serializer().serialize(encoder, value) + is TypeDefinition -> TypeDefinition.serializer().serialize(encoder, value) + is EnumDefinition -> EnumDefinition.serializer().serialize(encoder, value) + is ArrayDefinition -> ArrayDefinition.serializer().serialize(encoder, value) + is MapDefinition -> MapDefinition.serializer().serialize(encoder, value) + is NullableDefinition -> NullableDefinition.serializer().serialize(encoder, value) + is OneOfDefinition -> OneOfDefinition.serializer().serialize(encoder, value) + is AnyOfDefinition -> AnyOfDefinition.serializer().serialize(encoder, value) + } + } + } +} diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/MapDefinition.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/MapDefinition.kt new file mode 100644 index 000000000..946f5fcc2 --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/MapDefinition.kt @@ -0,0 +1,10 @@ +package io.bkbn.kompendium.json.schema.definition + +import kotlinx.serialization.Serializable + +@Serializable +data class MapDefinition( + val additionalProperties: JsonSchema +) : JsonSchema { + val type: String = "object" +} diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/NullableDefinition.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/NullableDefinition.kt new file mode 100644 index 000000000..10e5a3e76 --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/NullableDefinition.kt @@ -0,0 +1,6 @@ +package io.bkbn.kompendium.json.schema.definition + +import kotlinx.serialization.Serializable + +@Serializable +data class NullableDefinition(val type: String = "null") : JsonSchema diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/OneOfDefinition.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/OneOfDefinition.kt new file mode 100644 index 000000000..88e711a7b --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/OneOfDefinition.kt @@ -0,0 +1,8 @@ +package io.bkbn.kompendium.json.schema.definition + +import kotlinx.serialization.Serializable + +@Serializable +data class OneOfDefinition(val oneOf: Set) : JsonSchema { + constructor(vararg types: JsonSchema) : this(types.toSet()) +} diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/ReferenceDefinition.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/ReferenceDefinition.kt new file mode 100644 index 000000000..62c0587c1 --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/ReferenceDefinition.kt @@ -0,0 +1,6 @@ +package io.bkbn.kompendium.json.schema.definition + +import kotlinx.serialization.Serializable + +@Serializable +data class ReferenceDefinition(val `$ref`: String) : JsonSchema diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/TypeDefinition.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/TypeDefinition.kt new file mode 100644 index 000000000..40b331c78 --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/definition/TypeDefinition.kt @@ -0,0 +1,47 @@ +package io.bkbn.kompendium.json.schema.definition + +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable + +@Serializable +data class TypeDefinition( + val type: String, + val format: String? = null, + val description: String? = null, + val properties: Map? = null, + val required: Set? = null, + @Contextual val default: Any? = null, +) : JsonSchema { + + fun withDefault(default: Any): TypeDefinition = this.copy(default = default) + + companion object { + val INT = TypeDefinition( + type = "number", + format = "int32" + ) + + val LONG = TypeDefinition( + type = "number", + format = "int64" + ) + + val DOUBLE = TypeDefinition( + type = "number", + format = "double" + ) + + val FLOAT = TypeDefinition( + type = "number", + format = "float" + ) + + val STRING = TypeDefinition( + type = "string" + ) + + val BOOLEAN = TypeDefinition( + type = "boolean" + ) + } +} diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/CollectionHandler.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/CollectionHandler.kt new file mode 100644 index 000000000..316904189 --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/CollectionHandler.kt @@ -0,0 +1,33 @@ +package io.bkbn.kompendium.json.schema.handler + +import io.bkbn.kompendium.json.schema.SchemaGenerator +import io.bkbn.kompendium.json.schema.definition.ArrayDefinition +import io.bkbn.kompendium.json.schema.definition.JsonSchema +import io.bkbn.kompendium.json.schema.definition.NullableDefinition +import io.bkbn.kompendium.json.schema.definition.OneOfDefinition +import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition +import io.bkbn.kompendium.json.schema.definition.TypeDefinition +import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug +import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug +import kotlin.reflect.KType + +object CollectionHandler { + + fun handle(type: KType, cache: MutableMap): JsonSchema { + val collectionType = type.arguments.first().type!! + val typeSchema = SchemaGenerator.fromTypeToSchema(collectionType, cache).let { + if (it is TypeDefinition && it.type == "object") { + cache[collectionType.getSimpleSlug()] = it + ReferenceDefinition(collectionType.getReferenceSlug()) + } else { + it + } + } + val definition = ArrayDefinition(typeSchema) + return when (type.isMarkedNullable) { + true -> OneOfDefinition(NullableDefinition(), definition) + false -> definition + } + } + +} diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/EnumHandler.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/EnumHandler.kt new file mode 100644 index 000000000..b3744617e --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/EnumHandler.kt @@ -0,0 +1,21 @@ +package io.bkbn.kompendium.json.schema.handler + +import io.bkbn.kompendium.json.schema.definition.EnumDefinition +import io.bkbn.kompendium.json.schema.definition.JsonSchema +import io.bkbn.kompendium.json.schema.definition.NullableDefinition +import io.bkbn.kompendium.json.schema.definition.OneOfDefinition +import kotlin.reflect.KClass +import kotlin.reflect.KType + +object EnumHandler { + + fun handle(type: KType, clazz: KClass<*>): JsonSchema { + val options = clazz.java.enumConstants.map { it.toString() }.toSet() + val definition = EnumDefinition(enum = options) + return when (type.isMarkedNullable) { + true -> OneOfDefinition(NullableDefinition(), definition) + false -> definition + } + } + +} diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/MapHandler.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/MapHandler.kt new file mode 100644 index 000000000..5b57cf033 --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/MapHandler.kt @@ -0,0 +1,37 @@ +package io.bkbn.kompendium.json.schema.handler + +import io.bkbn.kompendium.json.schema.SchemaGenerator +import io.bkbn.kompendium.json.schema.definition.JsonSchema +import io.bkbn.kompendium.json.schema.definition.MapDefinition +import io.bkbn.kompendium.json.schema.definition.NullableDefinition +import io.bkbn.kompendium.json.schema.definition.OneOfDefinition +import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition +import io.bkbn.kompendium.json.schema.definition.TypeDefinition +import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug +import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug +import kotlin.reflect.KClass +import kotlin.reflect.KType + +object MapHandler { + + fun handle(type: KType, cache: MutableMap): JsonSchema { + require(type.arguments.first().type!!.classifier as KClass<*> == String::class) { + "JSON requires that map keys MUST be Strings. You provided ${type.arguments.first().type}" + } + val valueType = type.arguments[1].type!! + val valueSchema = SchemaGenerator.fromTypeToSchema(valueType, cache).let { + if (it is TypeDefinition && it.type == "object") { + cache[valueType.getSimpleSlug()] = it + ReferenceDefinition(valueType.getReferenceSlug()) + } else { + it + } + } + val definition = MapDefinition(valueSchema) + return when (type.isMarkedNullable) { + true -> OneOfDefinition(NullableDefinition(), definition) + false -> definition + } + } + +} diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/SealedObjectHandler.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/SealedObjectHandler.kt new file mode 100644 index 000000000..fc152975c --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/SealedObjectHandler.kt @@ -0,0 +1,32 @@ +package io.bkbn.kompendium.json.schema.handler + +import io.bkbn.kompendium.json.schema.SchemaGenerator +import io.bkbn.kompendium.json.schema.definition.AnyOfDefinition +import io.bkbn.kompendium.json.schema.definition.JsonSchema +import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition +import io.bkbn.kompendium.json.schema.definition.TypeDefinition +import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug +import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug +import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.full.createType + +object SealedObjectHandler { + + fun handle(type: KType, clazz: KClass<*>, cache: MutableMap): JsonSchema { + val subclasses = clazz.sealedSubclasses + .map { it.createType(type.arguments) } + .map { t -> + SchemaGenerator.fromTypeToSchema(t, cache).let { js -> + if (js is TypeDefinition && js.type == "object") { + cache[t.getSimpleSlug()] = js + ReferenceDefinition(t.getReferenceSlug()) + } else { + js + } + } + } + .toSet() + return AnyOfDefinition(subclasses) + } +} diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/SimpleObjectHandler.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/SimpleObjectHandler.kt new file mode 100644 index 000000000..a3c11b928 --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/handler/SimpleObjectHandler.kt @@ -0,0 +1,76 @@ +package io.bkbn.kompendium.json.schema.handler + +import io.bkbn.kompendium.json.schema.SchemaGenerator +import io.bkbn.kompendium.json.schema.definition.JsonSchema +import io.bkbn.kompendium.json.schema.definition.NullableDefinition +import io.bkbn.kompendium.json.schema.definition.OneOfDefinition +import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition +import io.bkbn.kompendium.json.schema.definition.TypeDefinition +import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug +import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug +import kotlin.reflect.KClass +import kotlin.reflect.KProperty +import kotlin.reflect.KType +import kotlin.reflect.KTypeParameter +import kotlin.reflect.KTypeProjection +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.primaryConstructor + +object SimpleObjectHandler { + + fun handle(type: KType, clazz: KClass<*>, cache: MutableMap): JsonSchema { + // cache[type.getSimpleSlug()] = ReferenceDefinition("RECURSION_PLACEHOLDER") + val typeMap = clazz.typeParameters.zip(type.arguments).toMap() + val props = clazz.memberProperties.associate { prop -> + val schema = when (typeMap.containsKey(prop.returnType.classifier)) { + true -> handleGenericProperty(prop, typeMap, cache) + false -> handleProperty(prop, cache) + } + + prop.name to schema + } + + val required = clazz.memberProperties.filterNot { prop -> prop.returnType.isMarkedNullable } + .filterNot { prop -> clazz.primaryConstructor!!.parameters.find { it.name == prop.name }!!.isOptional } + .map { it.name } + .toSet() + + + val definition = TypeDefinition( + type = "object", + properties = props, + required = required + ) + + return when (type.isMarkedNullable) { + true -> OneOfDefinition(NullableDefinition(), definition) + false -> definition + } + } + + private fun handleGenericProperty( + prop: KProperty<*>, + typeMap: Map, + cache: MutableMap + ): JsonSchema { + val type = typeMap[prop.returnType.classifier]?.type!! + return SchemaGenerator.fromTypeToSchema(type, cache).let { + if (it is TypeDefinition && it.type == "object") { + cache[type.getSimpleSlug()] = it + ReferenceDefinition(prop.returnType.getReferenceSlug()) + } else { + it + } + } + } + + private fun handleProperty(prop: KProperty<*>, cache: MutableMap): JsonSchema = + SchemaGenerator.fromTypeToSchema(prop.returnType, cache).let { + if (it is TypeDefinition && it.type == "object") { + cache[prop.returnType.getSimpleSlug()] = it + ReferenceDefinition(prop.returnType.getReferenceSlug()) + } else { + it + } + } +} diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/util/Helpers.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/util/Helpers.kt new file mode 100644 index 000000000..252c9f1fc --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/util/Helpers.kt @@ -0,0 +1,29 @@ +package io.bkbn.kompendium.json.schema.util + +import kotlin.reflect.KClass +import kotlin.reflect.KType + +object Helpers { + + private const val COMPONENT_SLUG = "#/components/schemas" + + fun KType.getSimpleSlug(): String = when { + this.arguments.isNotEmpty() -> genericNameAdapter(this, classifier as KClass<*>) + else -> (classifier as KClass<*>).simpleName ?: error("Could not determine simple name for $this") + } + + fun KType.getReferenceSlug(): String = when { + arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}" + else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).simpleName}" + } + + /** + * Adapts a class with type parameters into a reference friendly string + */ + private fun genericNameAdapter(type: KType, clazz: KClass<*>): String { + val classNames = type.arguments + .map { it.type?.classifier as KClass<*> } + .map { it.simpleName } + return classNames.joinToString(separator = "-", prefix = "${clazz.simpleName}-") + } +} diff --git a/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/util/ReferenceCache.kt b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/util/ReferenceCache.kt new file mode 100644 index 000000000..bdd47ecbe --- /dev/null +++ b/json-schema/src/main/kotlin/io/bkbn/kompendium/json/schema/util/ReferenceCache.kt @@ -0,0 +1,9 @@ +package io.bkbn.kompendium.json.schema.util + +import io.bkbn.kompendium.json.schema.definition.JsonSchema +import kotlin.reflect.KType + +data class ReferenceCache( + val referenceRootPath: String = "#/", + val cache: MutableMap = mutableMapOf() +) diff --git a/json-schema/src/test/kotlin/io/bkbn/kompendium/json/schema/SchemaGeneratorTest.kt b/json-schema/src/test/kotlin/io/bkbn/kompendium/json/schema/SchemaGeneratorTest.kt new file mode 100644 index 000000000..181de9c62 --- /dev/null +++ b/json-schema/src/test/kotlin/io/bkbn/kompendium/json/schema/SchemaGeneratorTest.kt @@ -0,0 +1,98 @@ +package io.bkbn.kompendium.json.schema + +import io.bkbn.kompendium.core.fixtures.ComplexRequest +import io.bkbn.kompendium.core.fixtures.FlibbityGibbit +import io.bkbn.kompendium.core.fixtures.SimpleEnum +import io.bkbn.kompendium.core.fixtures.SlammaJamma +import io.bkbn.kompendium.core.fixtures.TestHelpers.getFileSnapshot +import io.bkbn.kompendium.core.fixtures.TestResponse +import io.bkbn.kompendium.core.fixtures.TestSimpleRequest +import io.bkbn.kompendium.json.schema.definition.JsonSchema +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.DescribeSpec +import kotlinx.serialization.json.Json + +class SchemaGeneratorTest : DescribeSpec({ + describe("Scalars") { + it("Can generate the schema for an Int") { + jsonSchemaTest("T0001__scalar_int.json") + } + it("Can generate the schema for a Boolean") { + jsonSchemaTest("T0002__scalar_bool.json") + } + it("Can generate the schema for a String") { + jsonSchemaTest("T0003__scalar_string.json") + } + } + describe("Objects") { + it("Can generate the schema for a simple object") { + jsonSchemaTest("T0004__simple_object.json") + } + it("Can generate the schema for a complex object") { + jsonSchemaTest("T0005__complex_object.json") + } + it("Can generate the schema for a nullable object") { + jsonSchemaTest("T0006__nullable_object.json") + } + it("Can generate the schema for a polymorphic object") { + jsonSchemaTest("T0015__polymorphic_object.json") + } + xit("Can generate the schema for a recursive type") { + // TODO jsonSchemaTest("T0016__recursive_object.json") + } + } + describe("Enums") { + it("Can generate the schema for a simple enum") { + jsonSchemaTest("T0007__simple_enum.json") + } + it("Can generate the schema for a nullable enum") { + jsonSchemaTest("T0008__nullable_enum.json") + } + } + describe("Arrays") { + it("Can generate the schema for an array of scalars") { + jsonSchemaTest>("T0009__scalar_array.json") + } + it("Can generate the schema for an array of objects") { + jsonSchemaTest>("T0010__object_array.json") + } + it("Can generate the schema for a nullable array") { + jsonSchemaTest?>("T0011__nullable_array.json") + } + } + describe("Maps") { + it("Can generate the schema for a map of scalars") { + jsonSchemaTest>("T0012__scalar_map.json") + } + it("Throws an error when map keys are not strings") { + shouldThrow { SchemaGenerator.fromTypeToSchema>() } + } + it("Can generate the schema for a map of objects") { + jsonSchemaTest>("T0013__object_map.json") + } + it("Can generate the schema for a nullable map") { + jsonSchemaTest?>("T0014__nullable_map.json") + } + } +}) { + companion object { + private val json = Json { + encodeDefaults = true + explicitNulls = false + prettyPrint = true + } + + private fun JsonSchema.serialize() = json.encodeToString(JsonSchema.serializer(), this) + + private inline fun jsonSchemaTest(snapshotName: String) { + // act + val schema = SchemaGenerator.fromTypeToSchema() + + // todo add cache assertions!!! + + // assert + schema.serialize() shouldEqualJson getFileSnapshot(snapshotName) + } + } +} diff --git a/json-schema/src/test/resources/T0001__scalar_int.json b/json-schema/src/test/resources/T0001__scalar_int.json new file mode 100644 index 000000000..2958fb1ba --- /dev/null +++ b/json-schema/src/test/resources/T0001__scalar_int.json @@ -0,0 +1,4 @@ +{ + "type": "number", + "format": "int32" +} diff --git a/json-schema/src/test/resources/T0002__scalar_bool.json b/json-schema/src/test/resources/T0002__scalar_bool.json new file mode 100644 index 000000000..dd97725d3 --- /dev/null +++ b/json-schema/src/test/resources/T0002__scalar_bool.json @@ -0,0 +1,3 @@ +{ + "type": "boolean" +} diff --git a/json-schema/src/test/resources/T0003__scalar_string.json b/json-schema/src/test/resources/T0003__scalar_string.json new file mode 100644 index 000000000..28ac6e864 --- /dev/null +++ b/json-schema/src/test/resources/T0003__scalar_string.json @@ -0,0 +1,3 @@ +{ + "type": "string" +} diff --git a/json-schema/src/test/resources/T0004__simple_object.json b/json-schema/src/test/resources/T0004__simple_object.json new file mode 100644 index 000000000..873c85a2e --- /dev/null +++ b/json-schema/src/test/resources/T0004__simple_object.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "number", + "format": "int32" + } + }, + "required": [ + "a", + "b" + ] +} diff --git a/json-schema/src/test/resources/T0005__complex_object.json b/json-schema/src/test/resources/T0005__complex_object.json new file mode 100644 index 000000000..78879f3bd --- /dev/null +++ b/json-schema/src/test/resources/T0005__complex_object.json @@ -0,0 +1,22 @@ +{ + "type": "object", + "properties": { + "amazingField": { + "type": "string" + }, + "org": { + "type": "string" + }, + "tables": { + "items": { + "$ref": "#/components/schemas/NestedComplexItem" + }, + "type": "array" + } + }, + "required": [ + "amazingField", + "org", + "tables" + ] +} diff --git a/json-schema/src/test/resources/T0006__nullable_object.json b/json-schema/src/test/resources/T0006__nullable_object.json new file mode 100644 index 000000000..981b5ea17 --- /dev/null +++ b/json-schema/src/test/resources/T0006__nullable_object.json @@ -0,0 +1,23 @@ +{ + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "number", + "format": "int32" + } + }, + "required": [ + "a", + "b" + ] + } + ] +} diff --git a/json-schema/src/test/resources/T0007__simple_enum.json b/json-schema/src/test/resources/T0007__simple_enum.json new file mode 100644 index 000000000..19f2d782e --- /dev/null +++ b/json-schema/src/test/resources/T0007__simple_enum.json @@ -0,0 +1,3 @@ +{ + "enum": [ "ONE", "TWO" ] +} diff --git a/json-schema/src/test/resources/T0008__nullable_enum.json b/json-schema/src/test/resources/T0008__nullable_enum.json new file mode 100644 index 000000000..5c75d54dc --- /dev/null +++ b/json-schema/src/test/resources/T0008__nullable_enum.json @@ -0,0 +1,13 @@ +{ + "oneOf": [ + { + "type": "null" + }, + { + "enum": [ + "ONE", + "TWO" + ] + } + ] +} diff --git a/json-schema/src/test/resources/T0009__scalar_array.json b/json-schema/src/test/resources/T0009__scalar_array.json new file mode 100644 index 000000000..582fd9342 --- /dev/null +++ b/json-schema/src/test/resources/T0009__scalar_array.json @@ -0,0 +1,7 @@ +{ + "items": { + "type": "number", + "format": "int32" + }, + "type": "array" +} diff --git a/json-schema/src/test/resources/T0010__object_array.json b/json-schema/src/test/resources/T0010__object_array.json new file mode 100644 index 000000000..832ebea24 --- /dev/null +++ b/json-schema/src/test/resources/T0010__object_array.json @@ -0,0 +1,6 @@ +{ + "items": { + "$ref": "#/components/schemas/TestResponse" + }, + "type": "array" +} diff --git a/json-schema/src/test/resources/T0011__nullable_array.json b/json-schema/src/test/resources/T0011__nullable_array.json new file mode 100644 index 000000000..f438d03c9 --- /dev/null +++ b/json-schema/src/test/resources/T0011__nullable_array.json @@ -0,0 +1,14 @@ +{ + "oneOf": [ + { + "type": "null" + }, + { + "items": { + "type": "number", + "format": "int32" + }, + "type": "array" + } + ] +} diff --git a/json-schema/src/test/resources/T0012__scalar_map.json b/json-schema/src/test/resources/T0012__scalar_map.json new file mode 100644 index 000000000..92d992ead --- /dev/null +++ b/json-schema/src/test/resources/T0012__scalar_map.json @@ -0,0 +1,7 @@ +{ + "additionalProperties": { + "type": "number", + "format": "int32" + }, + "type": "object" +} diff --git a/json-schema/src/test/resources/T0013__object_map.json b/json-schema/src/test/resources/T0013__object_map.json new file mode 100644 index 000000000..1c56d69a9 --- /dev/null +++ b/json-schema/src/test/resources/T0013__object_map.json @@ -0,0 +1,6 @@ +{ + "additionalProperties": { + "$ref": "#/components/schemas/TestResponse" + }, + "type": "object" +} diff --git a/json-schema/src/test/resources/T0014__nullable_map.json b/json-schema/src/test/resources/T0014__nullable_map.json new file mode 100644 index 000000000..f56ddc95d --- /dev/null +++ b/json-schema/src/test/resources/T0014__nullable_map.json @@ -0,0 +1,14 @@ +{ + "oneOf": [ + { + "type": "null" + }, + { + "additionalProperties": { + "type": "number", + "format": "int32" + }, + "type": "object" + } + ] +} diff --git a/json-schema/src/test/resources/T0015__polymorphic_object.json b/json-schema/src/test/resources/T0015__polymorphic_object.json new file mode 100644 index 000000000..71aaf3544 --- /dev/null +++ b/json-schema/src/test/resources/T0015__polymorphic_object.json @@ -0,0 +1,10 @@ +{ + "anyOf": [ + { + "$ref": "#/components/schemas/ComplexGibbit" + }, + { + "$ref": "#/components/schemas/SimpleGibbit" + } + ] +} diff --git a/json-schema/src/test/resources/T0016__recursive_object.json b/json-schema/src/test/resources/T0016__recursive_object.json new file mode 100644 index 000000000..4d518bda1 --- /dev/null +++ b/json-schema/src/test/resources/T0016__recursive_object.json @@ -0,0 +1,13 @@ +{ + "anyOf": [ + { + "$ref": "#/components/schemas/OneJamma" + }, + { + "$ref": "#/components/schemas/AnothaJamma" + }, + { + "$ref": "#/components/schemas/InsaneJamma" + } + ] +} diff --git a/kompendium-annotations/Module.md b/kompendium-annotations/Module.md deleted file mode 100644 index 51d3df474..000000000 --- a/kompendium-annotations/Module.md +++ /dev/null @@ -1,13 +0,0 @@ -# Module kompendium-annotations - -This module houses all annotations that Kompendium uses to provide key metadata when performing reflective analysis. - -It is separated from core predominantly to allow for potential future integrations with [Kotlin Symbol Processing](https://github.com/google/ksp) - -# Package io.bkbn.kompendium.annotations - -Contains all annotations used by Kompendium - -# Package io.bkbn.kompendium.annotations.constraint - -Annotations that place bespoke constraints on individual fields of your API schemas. diff --git a/kompendium-annotations/build.gradle.kts b/kompendium-annotations/build.gradle.kts deleted file mode 100644 index e9211c512..000000000 --- a/kompendium-annotations/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ -plugins { - kotlin("jvm") - id("io.bkbn.sourdough.library.jvm") - id("io.gitlab.arturbosch.detekt") - id("com.adarshr.test-logger") - id("org.jetbrains.dokka") - id("maven-publish") - id("java-library") - id("signing") -} - -sourdough { - libraryName.set("Kompendium Annotations") - libraryDescription.set("A set of annotations used by Kompendium to generate OpenAPI Specifications") -} - -testing { - suites { - named("test", JvmTestSuite::class) { - useJUnitJupiter() - } - } -} diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/Field.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/Field.kt deleted file mode 100644 index 22b6b1848..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/Field.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.bkbn.kompendium.annotations - -/** - * Annotation used to perform field level overrides. - * @param name Indicates that a field name override is desired. Often used for camel case to snake case conversions. - */ -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class Field(val name: String = "", val description: String = "") diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/FreeFormObject.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/FreeFormObject.kt deleted file mode 100644 index d64b903c0..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/FreeFormObject.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.annotations - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) -annotation class FreeFormObject diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/Param.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/Param.kt deleted file mode 100644 index 13966584c..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/Param.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.bkbn.kompendium.annotations - -/** - * Used to indicate that a field in a data class represents an OpenAPI parameter - * @param type The type of parameter, must be valid [ParamType] - */ -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class Param(val type: ParamType) diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/ParamType.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/ParamType.kt deleted file mode 100644 index 48f5eb759..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/ParamType.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.bkbn.kompendium.annotations - -/** - * The allowed parameter types as specified by the OpenAPI specification - */ -enum class ParamType { - COOKIE, - HEADER, - PATH, - QUERY -} diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/Referenced.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/Referenced.kt deleted file mode 100644 index f9b46cc67..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/Referenced.kt +++ /dev/null @@ -1,12 +0,0 @@ -package io.bkbn.kompendium.annotations - -/** - * This instructs Kompendium to store the class as a referenced component. - * This is mandatory for any data models that have recursive children. - * If you do not annotate a recursive class with [Referenced], you will - * get a stack overflow error when you try to launch your API - */ -@Deprecated("This annotation now does nothing, as all complex objects are stored as references") -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.CLASS) -annotation class Referenced diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/UndeclaredField.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/UndeclaredField.kt deleted file mode 100644 index 790c8c205..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/UndeclaredField.kt +++ /dev/null @@ -1,15 +0,0 @@ -package io.bkbn.kompendium.annotations - -import kotlin.reflect.KClass - -/** - * This annotation allows users to add additional fields that are not part of the core data model. This should be used - * EXTREMELY sparingly. Most useful in supporting a variety of polymorphic serialization techniques. - * @param field Name of the extra field to add to the model - * @param clazz Class type of the field being added. If this is a complex type, you are most likely doing something - * wrong. - */ -@Repeatable -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.CLASS) -annotation class UndeclaredField(val field: String, val clazz: KClass<*>) diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Format.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Format.kt deleted file mode 100644 index a8ab1ec09..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Format.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.annotations.constraint - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY, AnnotationTarget.TYPE) -annotation class Format(val format: String) diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MaxItems.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MaxItems.kt deleted file mode 100644 index c938750b2..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MaxItems.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.annotations.constraint - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class MaxItems(val items: Int) diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MaxLength.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MaxLength.kt deleted file mode 100644 index 95ee0154e..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MaxLength.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.annotations.constraint - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class MaxLength(val length: Int) diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MaxProperties.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MaxProperties.kt deleted file mode 100644 index 2aa6ffed0..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MaxProperties.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.annotations.constraint - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class MaxProperties(val properties: Int) diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Maximum.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Maximum.kt deleted file mode 100644 index 4e06cdd3a..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Maximum.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.annotations.constraint - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class Maximum(val max: String, val exclusive: Boolean = false) diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MinItems.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MinItems.kt deleted file mode 100644 index 94cdbc36f..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MinItems.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.annotations.constraint - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class MinItems(val items: Int) diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MinLength.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MinLength.kt deleted file mode 100644 index 646ed2907..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MinLength.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.annotations.constraint - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class MinLength(val length: Int) diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MinProperties.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MinProperties.kt deleted file mode 100644 index eede7a453..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MinProperties.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.annotations.constraint - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class MinProperties(val properties: Int) diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Minimum.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Minimum.kt deleted file mode 100644 index b8652cda8..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Minimum.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.annotations.constraint - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class Minimum(val min: String, val exclusive: Boolean = false) diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MultipleOf.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MultipleOf.kt deleted file mode 100644 index 3bc002090..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/MultipleOf.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.annotations.constraint - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class MultipleOf(val multiple: String) diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Pattern.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Pattern.kt deleted file mode 100644 index a1b76ba62..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/Pattern.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.annotations.constraint - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class Pattern(val pattern: String) diff --git a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/UniqueItems.kt b/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/UniqueItems.kt deleted file mode 100644 index f1e3f7b1f..000000000 --- a/kompendium-annotations/src/main/kotlin/io/bkbn/kompendium/annotations/constraint/UniqueItems.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.annotations.constraint - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.PROPERTY) -annotation class UniqueItems diff --git a/kompendium-auth/Module.md b/kompendium-auth/Module.md deleted file mode 100644 index 425ede7e1..000000000 --- a/kompendium-auth/Module.md +++ /dev/null @@ -1,13 +0,0 @@ -# Module kompendium-auth - -This module is responsible for providing wrappers around ktor-auth configuration blocks, allowing users to document -their API authentication with minimal modifications to their existing configuration. - -# Package io.bkbn.kompendium.auth - -Base package that is responsible for setting up required authentication route handlers along with exposing wrapper -methods for each ktor-auth authentication mechanism. - -# Package io.bkbn.kompendium.auth - -Houses the available security configurations. At the moment, `Basic`, `JWT`, `ApiKey`, and `OAuth` are supported diff --git a/kompendium-auth/build.gradle.kts b/kompendium-auth/build.gradle.kts deleted file mode 100644 index 89726d7de..000000000 --- a/kompendium-auth/build.gradle.kts +++ /dev/null @@ -1,37 +0,0 @@ -plugins { - kotlin("jvm") - id("io.bkbn.sourdough.library.jvm") - id("io.gitlab.arturbosch.detekt") - id("com.adarshr.test-logger") - id("org.jetbrains.dokka") - id("maven-publish") - id("java-library") - id("signing") -} - -sourdough { - libraryName.set("Kompendium Authentication") - libraryDescription.set("Kompendium library to pair with Ktor Auth to provide authorization info to OpenAPI") -} - -dependencies { - // IMPLEMENTATION - - val ktorVersion: String by project - implementation(projects.kompendiumCore) - implementation(group = "io.ktor", name = "ktor-server-core", version = ktorVersion) - implementation(group = "io.ktor", name = "ktor-auth", version = ktorVersion) - implementation(group = "io.ktor", name = "ktor-auth-jwt", version = ktorVersion) - - // TESTING - - testImplementation(testFixtures(projects.kompendiumCore)) -} - -testing { - suites { - named("test", JvmTestSuite::class) { - useJUnitJupiter() - } - } -} diff --git a/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/Notarized.kt b/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/Notarized.kt deleted file mode 100644 index f5ee89369..000000000 --- a/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/Notarized.kt +++ /dev/null @@ -1,40 +0,0 @@ -package io.bkbn.kompendium.auth - -import io.bkbn.kompendium.auth.configuration.ApiKeyConfiguration -import io.bkbn.kompendium.auth.configuration.BasicAuthConfiguration -import io.bkbn.kompendium.auth.configuration.JwtAuthConfiguration -import io.bkbn.kompendium.auth.configuration.OAuthConfiguration -import io.bkbn.kompendium.auth.configuration.SecurityConfiguration -import io.bkbn.kompendium.core.Kompendium -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.ktor.application.feature -import io.ktor.auth.authenticate -import io.ktor.routing.Route -import io.ktor.routing.application - -object Notarized { - - fun Route.notarizedAuthenticate( - vararg configurations: SecurityConfiguration, - optional: Boolean = false, - build: Route.() -> Unit - ): Route { - val configurationNames = configurations.map { it.name }.toTypedArray() - val feature = application.feature(Kompendium) - - configurations.forEach { config -> - feature.config.spec.components.securitySchemes[config.name] = when (config) { - is ApiKeyConfiguration -> ApiKeyAuth(config.location, config.keyName) - is BasicAuthConfiguration -> BasicAuth() - is JwtAuthConfiguration -> BearerAuth(config.bearerFormat) - is OAuthConfiguration -> OAuth(config.description, config.flows) - } - } - - return authenticate(*configurationNames, optional = optional, build = build) - } - -} diff --git a/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/ApiKeyConfiguration.kt b/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/ApiKeyConfiguration.kt deleted file mode 100644 index 3675ff292..000000000 --- a/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/ApiKeyConfiguration.kt +++ /dev/null @@ -1,8 +0,0 @@ -package io.bkbn.kompendium.auth.configuration - -import io.bkbn.kompendium.oas.security.ApiKeyAuth - -interface ApiKeyConfiguration : SecurityConfiguration { - val location: ApiKeyAuth.ApiKeyLocation - val keyName: String -} diff --git a/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/BasicAuthConfiguration.kt b/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/BasicAuthConfiguration.kt deleted file mode 100644 index b207f8ac7..000000000 --- a/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/BasicAuthConfiguration.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.bkbn.kompendium.auth.configuration - -interface BasicAuthConfiguration : SecurityConfiguration diff --git a/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/JwtAuthConfiguration.kt b/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/JwtAuthConfiguration.kt deleted file mode 100644 index 052ce42fc..000000000 --- a/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/JwtAuthConfiguration.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.bkbn.kompendium.auth.configuration - -interface JwtAuthConfiguration : SecurityConfiguration { - val bearerFormat: String - get() = "JWT" -} diff --git a/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/OAuthConfiguration.kt b/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/OAuthConfiguration.kt deleted file mode 100644 index 880c1d352..000000000 --- a/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/OAuthConfiguration.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.bkbn.kompendium.auth.configuration - -import io.bkbn.kompendium.oas.security.OAuth - -interface OAuthConfiguration: SecurityConfiguration { - val flows: OAuth.Flows - val description: String? - get() = null -} diff --git a/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/SecurityConfiguration.kt b/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/SecurityConfiguration.kt deleted file mode 100644 index 1941e3a71..000000000 --- a/kompendium-auth/src/main/kotlin/io/bkbn/kompendium/auth/configuration/SecurityConfiguration.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.auth.configuration - -sealed interface SecurityConfiguration { - val name: String -} diff --git a/kompendium-auth/src/test/kotlin/io/bkbn/kompendium/auth/KompendiumAuthTest.kt b/kompendium-auth/src/test/kotlin/io/bkbn/kompendium/auth/KompendiumAuthTest.kt deleted file mode 100644 index 88246623a..000000000 --- a/kompendium-auth/src/test/kotlin/io/bkbn/kompendium/auth/KompendiumAuthTest.kt +++ /dev/null @@ -1,69 +0,0 @@ -package io.bkbn.kompendium.auth - -import io.bkbn.kompendium.auth.configuration.BasicAuthConfiguration -import io.bkbn.kompendium.auth.configuration.JwtAuthConfiguration -import io.bkbn.kompendium.auth.configuration.OAuthConfiguration -import io.bkbn.kompendium.auth.util.AuthConfigName -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.openApiTestAllSerializers -import io.bkbn.kompendium.oas.security.OAuth -import io.kotest.core.spec.style.DescribeSpec - -class KompendiumAuthTest : DescribeSpec({ - describe("Basic Authentication") { - it("Can create a notarized basic authentication record with all expected information") { - // arrange - val authConfig = object : BasicAuthConfiguration { - override val name: String = AuthConfigName.Basic - } - - // act - openApiTestAllSerializers("notarized_basic_authenticated_get.json") { - configBasicAuth() - notarizedAuthRoute(authConfig) - } - } - } - describe("JWT Authentication") { - it("Can create a simple notarized JWT route") { - // arrange - val authConfig = object : JwtAuthConfiguration { - override val name: String = AuthConfigName.JWT - } - - // act - openApiTestAllSerializers("notarized_jwt_authenticated_get.json") { - configJwtAuth() - notarizedAuthRoute(authConfig) - } - } - } - describe("OAuth Authentication") { - it("Can create an Oauth schema with all possible flows") { - // arrange - val flows = OAuth.Flows( - implicit = OAuth.Flows.Implicit( - "https://accounts.google.com/o/oauth2/auth", - scopes = mapOf("test" to "is a cool scope", "this" to "is also cool") - ), - authorizationCode = OAuth.Flows.AuthorizationCode("https://accounts.google.com/o/oauth2/auth"), - password = OAuth.Flows.Password("https://accounts.google.com/o/oauth2/auth"), - clientCredentials = OAuth.Flows.ClientCredential("https://accounts.google.com/token") - ) - - val authConfig = object : OAuthConfiguration { - override val flows: OAuth.Flows = flows - override val name: String = AuthConfigName.OAuth - } - - // act - openApiTestAllSerializers("notarized_oauth_all_flows.json") { - setupOauth() - notarizedAuthRoute(authConfig) - } - } - } -}) diff --git a/kompendium-auth/src/test/kotlin/io/bkbn/kompendium/auth/util/TestModules.kt b/kompendium-auth/src/test/kotlin/io/bkbn/kompendium/auth/util/TestModules.kt deleted file mode 100644 index d4a9d6bfe..000000000 --- a/kompendium-auth/src/test/kotlin/io/bkbn/kompendium/auth/util/TestModules.kt +++ /dev/null @@ -1,92 +0,0 @@ -package io.bkbn.kompendium.auth.util - -import io.bkbn.kompendium.auth.Notarized.notarizedAuthenticate -import io.bkbn.kompendium.auth.configuration.SecurityConfiguration -import io.bkbn.kompendium.core.Notarized.notarizedGet -import io.bkbn.kompendium.core.fixtures.TestParams -import io.bkbn.kompendium.core.fixtures.TestResponse -import io.bkbn.kompendium.core.fixtures.TestResponseInfo -import io.bkbn.kompendium.core.metadata.method.GetInfo -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.auth.Authentication -import io.ktor.auth.OAuthServerSettings -import io.ktor.auth.UserIdPrincipal -import io.ktor.auth.basic -import io.ktor.auth.jwt.jwt -import io.ktor.auth.oauth -import io.ktor.client.HttpClient -import io.ktor.client.engine.cio.CIO -import io.ktor.http.HttpMethod -import io.ktor.response.respondText -import io.ktor.routing.route -import io.ktor.routing.routing - -fun Application.setupOauth() { - install(Authentication) { - oauth("oauth") { - urlProvider = { "http://localhost:8080/callback" } - client = HttpClient(CIO) - providerLookup = { - OAuthServerSettings.OAuth2ServerSettings( - name = "google", - authorizeUrl = "https://accounts.google.com/o/oauth2/auth", - accessTokenUrl = "https://accounts.google.com/o/oauth2/token", - requestMethod = HttpMethod.Post, - clientId = System.getenv("GOOGLE_CLIENT_ID"), - clientSecret = System.getenv("GOOGLE_CLIENT_SECRET"), - defaultScopes = listOf("https://www.googleapis.com/auth/userinfo.profile") - ) - } - } - } -} - -fun Application.configBasicAuth() { - install(Authentication) { - basic(AuthConfigName.Basic) { - realm = "Ktor Server" - validate { credentials -> - if (credentials.name == credentials.password) { - UserIdPrincipal(credentials.name) - } else { - null - } - } - } - } -} - -fun Application.notarizedAuthRoute(authConfig: SecurityConfiguration) { - routing { - notarizedAuthenticate(authConfig) { - route("/test") { notarizedGet(testGetInfo(authConfig.name)) { - call.respondText { "hey dude ‼️ congratz on the get request" } - } - } - } - } -} - -fun Application.configJwtAuth() { - install(Authentication) { - jwt(AuthConfigName.JWT) { - realm = "Ktor server" - } - } -} - -fun testGetInfo(vararg security: String) = - GetInfo( - summary = "Another get test", - description = "testing more", - responseInfo = TestResponseInfo.testGetResponse, - securitySchemes = security.toSet() - ) - -object AuthConfigName { - const val Basic = "basic" - const val JWT = "jwt" - const val OAuth = "oauth" -} diff --git a/kompendium-auth/src/test/resources/notarized_basic_authenticated_get.json b/kompendium-auth/src/test/resources/notarized_basic_authenticated_get.json deleted file mode 100644 index 61b521d06..000000000 --- a/kompendium-auth/src/test/resources/notarized_basic_authenticated_get.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test": { - "get": { - "tags": [], - "summary": "Another get test", - "description": "testing more", - "parameters": [ - { - "name": "a", - "in": "path", - "schema": { - "type": "string" - }, - "required": true, - "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false - } - ], - "responses": { - "200": { - "description": "A Successful Endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TestResponse" - } - } - } - } - }, - "deprecated": false, - "security": [ - { - "basic": [] - } - ] - } - } - }, - "components": { - "schemas": { - "TestResponse": { - "properties": { - "c": { - "type": "string" - } - }, - "required": [ - "c" - ], - "type": "object" - } - }, - "securitySchemes": { - "basic": { - "type": "http", - "scheme": "basic" - } - } - }, - "security": [], - "tags": [] -} diff --git a/kompendium-auth/src/test/resources/notarized_jwt_authenticated_get.json b/kompendium-auth/src/test/resources/notarized_jwt_authenticated_get.json deleted file mode 100644 index 09d0c93ea..000000000 --- a/kompendium-auth/src/test/resources/notarized_jwt_authenticated_get.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test": { - "get": { - "tags": [], - "summary": "Another get test", - "description": "testing more", - "parameters": [ - { - "name": "a", - "in": "path", - "schema": { - "type": "string" - }, - "required": true, - "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false - } - ], - "responses": { - "200": { - "description": "A Successful Endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TestResponse" - } - } - } - } - }, - "deprecated": false, - "security": [ - { - "jwt": [] - } - ] - } - } - }, - "components": { - "schemas": { - "TestResponse": { - "properties": { - "c": { - "type": "string" - } - }, - "required": [ - "c" - ], - "type": "object" - } - }, - "securitySchemes": { - "jwt": { - "bearerFormat": "JWT", - "type": "http", - "scheme": "bearer" - } - } - }, - "security": [], - "tags": [] -} diff --git a/kompendium-auth/src/test/resources/notarized_oauth_all_flows.json b/kompendium-auth/src/test/resources/notarized_oauth_all_flows.json deleted file mode 100644 index 8b8f8c4af..000000000 --- a/kompendium-auth/src/test/resources/notarized_oauth_all_flows.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test": { - "get": { - "tags": [], - "summary": "Another get test", - "description": "testing more", - "parameters": [ - { - "name": "a", - "in": "path", - "schema": { - "type": "string" - }, - "required": true, - "deprecated": false - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false - } - ], - "responses": { - "200": { - "description": "A Successful Endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TestResponse" - } - } - } - } - }, - "deprecated": false, - "security": [ - { - "oauth": [] - } - ] - } - } - }, - "components": { - "schemas": { - "TestResponse": { - "properties": { - "c": { - "type": "string" - } - }, - "required": [ - "c" - ], - "type": "object" - } - }, - "securitySchemes": { - "oauth": { - "flows": { - "implicit": { - "authorizationUrl": "https://accounts.google.com/o/oauth2/auth", - "scopes": { - "test": "is a cool scope", - "this": "is also cool" - } - }, - "authorizationCode": { - "authorizationUrl": "https://accounts.google.com/o/oauth2/auth", - "scopes": {} - }, - "password": { - "tokenUrl": "https://accounts.google.com/o/oauth2/auth", - "scopes": {} - }, - "clientCredentials": { - "tokenUrl": "https://accounts.google.com/token", - "scopes": {} - } - }, - "type": "oauth2" - } - } - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/build.gradle.kts b/kompendium-core/build.gradle.kts deleted file mode 100644 index 355e1a2e1..000000000 --- a/kompendium-core/build.gradle.kts +++ /dev/null @@ -1,56 +0,0 @@ -plugins { - kotlin("jvm") - kotlin("plugin.serialization") - id("io.bkbn.sourdough.library.jvm") - id("io.gitlab.arturbosch.detekt") - id("com.adarshr.test-logger") - id("org.jetbrains.dokka") - id("maven-publish") - id("java-library") - id("signing") - id("java-test-fixtures") -} - -sourdough { - libraryName.set("Kompendium Core") - libraryDescription.set("Core functionality for the Kompendium library") - compilerArgs.set(listOf("-opt-in=kotlin.RequiresOptIn")) -} - -dependencies { - // VERSIONS - val ktorVersion: String by project - val kotestVersion: String by project - - // IMPLEMENTATION - - api(projects.kompendiumOas) - api(projects.kompendiumAnnotations) - - implementation(group = "io.ktor", name = "ktor-server-core", version = ktorVersion) - implementation(group = "io.ktor", name = "ktor-html-builder", version = ktorVersion) - - // TEST FIXTURES - - testFixturesApi(group = "io.kotest", name = "kotest-runner-junit5-jvm", version = kotestVersion) - testFixturesApi(group = "io.kotest", name = "kotest-assertions-core-jvm", version = kotestVersion) - testFixturesApi(group = "io.kotest", name = "kotest-property-jvm", version = kotestVersion) - testFixturesApi(group = "io.kotest", name = "kotest-assertions-json-jvm", version = kotestVersion) - testFixturesApi(group = "io.kotest", name = "kotest-assertions-ktor-jvm", version = "4.4.3") - - 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.3") -} - -testing { - suites { - named("test", JvmTestSuite::class) { - useJUnitJupiter() - } - } -} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Kompendium.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Kompendium.kt deleted file mode 100644 index 5e7d46752..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Kompendium.kt +++ /dev/null @@ -1,49 +0,0 @@ -package io.bkbn.kompendium.core - -import io.bkbn.kompendium.core.metadata.SchemaMap -import io.bkbn.kompendium.oas.OpenApiSpec -import io.bkbn.kompendium.oas.schema.TypedSchema -import io.ktor.application.Application -import io.ktor.application.ApplicationFeature -import io.ktor.application.call -import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.routing.Routing -import io.ktor.routing.get -import io.ktor.routing.route -import io.ktor.routing.routing -import io.ktor.util.AttributeKey -import kotlin.reflect.KClass - -class Kompendium(val config: Configuration) { - - class Configuration { - lateinit var spec: OpenApiSpec - - var openApiJson: Routing.(OpenApiSpec) -> Unit = { spec -> - route("/openapi.json") { - get { - call.respond(HttpStatusCode.OK, spec) - } - } - } - - var bodyCache: SchemaMap = mutableMapOf() - var parameterCache: SchemaMap = mutableMapOf() - - // TODO Add tests for this!! - fun addCustomTypeSchema(clazz: KClass<*>, schema: TypedSchema) { - bodyCache[clazz.simpleName!!] = schema - } - } - - companion object Feature : ApplicationFeature { - override val key: AttributeKey = AttributeKey("Kompendium") - override fun install(pipeline: Application, configure: Configuration.() -> Unit): Kompendium { - val configuration = Configuration().apply(configure) - val routing = pipeline.routing { } - configuration.openApiJson(routing, configuration.spec) - return Kompendium(configuration) - } - } -} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/KompendiumPreFlight.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/KompendiumPreFlight.kt deleted file mode 100644 index f39a4c7bd..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/KompendiumPreFlight.kt +++ /dev/null @@ -1,50 +0,0 @@ -package io.bkbn.kompendium.core - -import io.bkbn.kompendium.oas.schema.EnumSchema -import io.bkbn.kompendium.oas.schema.ObjectSchema -import io.ktor.application.feature -import io.ktor.routing.Route -import io.ktor.routing.application -import kotlin.reflect.KType -import kotlin.reflect.typeOf - -/** - * Functions are considered preflight when they are used to intercept a method ahead of running. - */ -object KompendiumPreFlight { - - /** - * Performs all content analysis on the types provided to a notarized route and adds it to the top level spec - * @param TParam - * @param TReq - * @param TResp - * @param block The function to execute, provided type information of the parameters above - * @return [Route] - */ - @OptIn(ExperimentalStdlibApi::class) - inline fun Route.methodNotarizationPreFlight( - block: (KType, KType, KType) -> Route - ): Route { - val feature = this.application.feature(Kompendium) - val requestType = typeOf() - val responseType = typeOf() - val paramType = typeOf() - addToCache(paramType, requestType, responseType, feature) - return block.invoke(paramType, requestType, responseType) - } - - fun addToCache(paramType: KType, requestType: KType, responseType: KType, feature: Kompendium) { - Kontent.generateKontent(requestType, feature.config.bodyCache) - Kontent.generateKontent(responseType, feature.config.bodyCache) - Kontent.generateKontent(paramType, feature.config.parameterCache) - feature.generateReferences() - } - - fun Kompendium.generateReferences() { - config.bodyCache - .filterValues { it is ObjectSchema || it is EnumSchema } - .forEach { (k, v) -> - config.spec.components.schemas[k] = v - } - } -} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Kontent.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Kontent.kt deleted file mode 100644 index 685aed752..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Kontent.kt +++ /dev/null @@ -1,98 +0,0 @@ -package io.bkbn.kompendium.core - -import io.bkbn.kompendium.core.handler.CollectionHandler -import io.bkbn.kompendium.core.handler.EnumHandler -import io.bkbn.kompendium.core.handler.MapHandler -import io.bkbn.kompendium.core.handler.ObjectHandler -import io.bkbn.kompendium.core.metadata.SchemaMap -import io.bkbn.kompendium.core.util.Helpers.logged -import io.bkbn.kompendium.oas.schema.FormattedSchema -import io.bkbn.kompendium.oas.schema.SimpleSchema -import kotlin.reflect.KClass -import kotlin.reflect.KType -import kotlin.reflect.full.createType -import kotlin.reflect.full.isSubclassOf -import kotlin.reflect.typeOf -import org.slf4j.LoggerFactory -import java.math.BigDecimal -import java.math.BigInteger -import java.util.UUID - -/** - * Responsible for generating the schema map that is used to power all object references across the API Spec. - */ -object Kontent { - - private val logger = LoggerFactory.getLogger(javaClass) - - /** - * Analyzes a type [T] for its top-level and any nested schemas, and adds them to a [SchemaMap], if provided - * @param T type to analyze - * @param cache Existing schema map to append to - * @return an updated schema map containing all type information for [T] - */ - @OptIn(ExperimentalStdlibApi::class) - inline fun generateKontent( - cache: SchemaMap = mutableMapOf() - ) { - val kontentType = typeOf() - generateKTypeKontent(kontentType, cache) - } - - /** - * Analyzes a [KType] for its top-level and any nested schemas, and adds them to a [SchemaMap], if provided - * @param type [KType] to analyze - * @param cache Existing schema map to append to - * @return an updated schema map containing all type information for [KType] type - */ - fun generateKontent( - type: KType, - cache: SchemaMap = mutableMapOf() - ) { - gatherSubTypes(type).forEach { - generateKTypeKontent(it, cache) - } - } - - private fun gatherSubTypes(type: KType): List { - val classifier = type.classifier as KClass<*> - return if (classifier.isSealed) { - classifier.sealedSubclasses.map { - it.createType(type.arguments) - } - } else { - listOf(type) - } - } - - /** - * Recursively fills schema map depending on [KType] classifier - * @param type [KType] to parse - * @param cache Existing schema map to append to - */ - fun generateKTypeKontent( - type: KType, - cache: SchemaMap = mutableMapOf(), - ) = logged(object {}.javaClass.enclosingMethod.name, mapOf("cache" to cache)) { - logger.debug("Parsing Kontent of $type") - when (val clazz = type.classifier as KClass<*>) { - Unit::class -> cache - Int::class -> cache[clazz.simpleName!!] = FormattedSchema("int32", "integer") - Long::class -> cache[clazz.simpleName!!] = FormattedSchema("int64", "integer") - Double::class -> cache[clazz.simpleName!!] = FormattedSchema("double", "number") - Float::class -> cache[clazz.simpleName!!] = FormattedSchema("float", "number") - String::class -> cache[clazz.simpleName!!] = SimpleSchema("string") - Boolean::class -> cache[clazz.simpleName!!] = SimpleSchema("boolean") - UUID::class -> cache[clazz.simpleName!!] = FormattedSchema("uuid", "string") - BigDecimal::class -> cache[clazz.simpleName!!] = FormattedSchema("double", "number") - BigInteger::class -> cache[clazz.simpleName!!] = FormattedSchema("int64", "integer") - ByteArray::class -> cache[clazz.simpleName!!] = FormattedSchema("byte", "string") - else -> when { - clazz.isSubclassOf(Collection::class) -> CollectionHandler.handle(type, clazz, cache) - clazz.isSubclassOf(Enum::class) -> EnumHandler.handle(type, clazz, cache) - clazz.isSubclassOf(Map::class) -> MapHandler.handle(type, clazz, cache) - else -> ObjectHandler.handle(type, clazz, cache) - } - } - } -} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Notarized.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Notarized.kt deleted file mode 100644 index 346809db0..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/Notarized.kt +++ /dev/null @@ -1,172 +0,0 @@ -package io.bkbn.kompendium.core - -import io.bkbn.kompendium.annotations.Param -import io.bkbn.kompendium.core.KompendiumPreFlight.methodNotarizationPreFlight -import io.bkbn.kompendium.core.metadata.method.DeleteInfo -import io.bkbn.kompendium.core.metadata.method.GetInfo -import io.bkbn.kompendium.core.metadata.method.HeadInfo -import io.bkbn.kompendium.core.metadata.method.OptionsInfo -import io.bkbn.kompendium.core.metadata.method.PatchInfo -import io.bkbn.kompendium.core.metadata.method.PostInfo -import io.bkbn.kompendium.core.metadata.method.PutInfo -import io.bkbn.kompendium.core.parser.DefaultMethodParser.calculateRoutePath -import io.bkbn.kompendium.core.parser.DefaultMethodParser.parseMethodInfo -import io.bkbn.kompendium.oas.path.Path -import io.bkbn.kompendium.oas.path.PathOperation -import io.ktor.application.ApplicationCall -import io.ktor.application.feature -import io.ktor.http.HttpMethod -import io.ktor.routing.Route -import io.ktor.routing.application -import io.ktor.routing.method -import io.ktor.util.pipeline.PipelineInterceptor - -/** - * Notarization methods are the primary way that a Ktor API using Kompendium differentiates - * from a default Ktor application. On instantiation, a notarized route, provided with the proper metadata, - * will reflectively analyze all pertinent data to build a corresponding OpenAPI entry. - */ -object Notarized { - - /** - * Notarization for an HTTP GET request - * @param TParam The class containing all parameter fields. Each field must be annotated with @[Param] - * @param TResp Class detailing the expected API response - * @param info Route metadata - * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation] - */ - inline fun Route.notarizedGet( - info: GetInfo, - postProcess: (PathOperation) -> PathOperation = { p -> p }, - noinline body: PipelineInterceptor - ): Route = methodNotarizationPreFlight { paramType, requestType, responseType -> - val feature = this.application.feature(Kompendium) - val path = calculateRoutePath() - feature.config.spec.paths.getOrPut(path) { Path() } - val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature) - feature.config.spec.paths[path]?.get = postProcess(baseInfo) - return method(HttpMethod.Get) { handle(body) } - } - - /** - * Notarization for an HTTP POST request - * @param TParam The class containing all parameter fields. Each field must be annotated with @[Param] - * @param TReq Class detailing the expected API request body - * @param TResp Class detailing the expected API response - * @param info Route metadata - * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation] - */ - inline fun Route.notarizedPost( - info: PostInfo, - postProcess: (PathOperation) -> PathOperation = { p -> p }, - noinline body: PipelineInterceptor - ): Route = methodNotarizationPreFlight { paramType, requestType, responseType -> - val feature = this.application.feature(Kompendium) - val path = calculateRoutePath() - feature.config.spec.paths.getOrPut(path) { Path() } - val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature) - feature.config.spec.paths[path]?.post = postProcess(baseInfo) - return method(HttpMethod.Post) { handle(body) } - } - - /** - * Notarization for an HTTP PUT request - * @param TParam The class containing all parameter fields. Each field must be annotated with @[Param] - * @param TReq Class detailing the expected API request body - * @param TResp Class detailing the expected API response - * @param info Route metadata - * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation] - */ - inline fun Route.notarizedPut( - info: PutInfo, - postProcess: (PathOperation) -> PathOperation = { p -> p }, - noinline body: PipelineInterceptor, - ): Route = methodNotarizationPreFlight { paramType, requestType, responseType -> - val feature = this.application.feature(Kompendium) - val path = calculateRoutePath() - feature.config.spec.paths.getOrPut(path) { Path() } - val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature) - feature.config.spec.paths[path]?.put = postProcess(baseInfo) - return method(HttpMethod.Put) { handle(body) } - } - - /** - * Notarization for an HTTP PATCH request - * @param TParam The class containing all parameter fields. Each field must be annotated with @[Param] - * @param TReq Class detailing the expected API request body - * @param TResp Class detailing the expected API response - * @param info Route metadata - * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation] - */ - inline fun Route.notarizedPatch( - info: PatchInfo, - postProcess: (PathOperation) -> PathOperation = { p -> p }, - noinline body: PipelineInterceptor, - ): Route = methodNotarizationPreFlight { paramType, requestType, responseType -> - val feature = this.application.feature(Kompendium) - val path = calculateRoutePath() - feature.config.spec.paths.getOrPut(path) { Path() } - val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature) - feature.config.spec.paths[path]?.patch = postProcess(baseInfo) - return method(HttpMethod.Patch) { handle(body) } - } - - /** - * Notarization for an HTTP DELETE request - * @param TParam The class containing all parameter fields. Each field must be annotated with @[Param] - * @param TResp Class detailing the expected API response - * @param info Route metadata - * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation] - */ - inline fun Route.notarizedDelete( - info: DeleteInfo, - postProcess: (PathOperation) -> PathOperation = { p -> p }, - noinline body: PipelineInterceptor - ): Route = methodNotarizationPreFlight { paramType, requestType, responseType -> - val feature = this.application.feature(Kompendium) - val path = calculateRoutePath() - feature.config.spec.paths.getOrPut(path) { Path() } - val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature) - feature.config.spec.paths[path]?.delete = postProcess(baseInfo) - return method(HttpMethod.Delete) { handle(body) } - } - - /** - * Notarization for an HTTP HEAD request - * @param TParam The class containing all parameter fields. Each field must be annotated with @[Param] - * @param info Route metadata - * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation] - */ - inline fun Route.notarizedHead( - info: HeadInfo, - postProcess: (PathOperation) -> PathOperation = { p -> p }, - noinline body: PipelineInterceptor - ): Route = methodNotarizationPreFlight { paramType, requestType, responseType -> - val feature = this.application.feature(Kompendium) - val path = calculateRoutePath() - feature.config.spec.paths.getOrPut(path) { Path() } - val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature) - feature.config.spec.paths[path]?.head = postProcess(baseInfo) - return method(HttpMethod.Head) { handle(body) } - } - - /** - * Notarization for an HTTP OPTION request - * @param TParam The class containing all parameter fields. Each field must be annotated with @[Param] - * @param TResp Class detailing the expected API response - * @param info Route metadata - * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation] - */ - inline fun Route.notarizedOptions( - info: OptionsInfo, - postProcess: (PathOperation) -> PathOperation = { p -> p }, - noinline body: PipelineInterceptor - ): Route = methodNotarizationPreFlight { paramType, requestType, responseType -> - val feature = this.application.feature(Kompendium) - val path = calculateRoutePath() - feature.config.spec.paths.getOrPut(path) { Path() } - val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature) - feature.config.spec.paths[path]?.options = postProcess(baseInfo) - return method(HttpMethod.Options) { handle(body) } - } -} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/constraint/ConstraintScanner.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/constraint/ConstraintScanner.kt deleted file mode 100644 index d0c31967a..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/constraint/ConstraintScanner.kt +++ /dev/null @@ -1,133 +0,0 @@ -package io.bkbn.kompendium.core.constraint - -import io.bkbn.kompendium.annotations.Field -import io.bkbn.kompendium.annotations.constraint.Format -import io.bkbn.kompendium.annotations.constraint.MaxItems -import io.bkbn.kompendium.annotations.constraint.MaxLength -import io.bkbn.kompendium.annotations.constraint.Maximum -import io.bkbn.kompendium.annotations.constraint.MinItems -import io.bkbn.kompendium.annotations.constraint.MinLength -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 io.bkbn.kompendium.core.util.Helpers.toNumber -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 kotlin.reflect.KClass -import kotlin.reflect.KProperty1 -import kotlin.reflect.KType -import kotlin.reflect.full.findAnnotation -import kotlin.reflect.full.memberProperties -import kotlin.reflect.full.primaryConstructor - -fun ComponentSchema.scanForConstraints(type: KType, prop: KProperty1<*, *>): ComponentSchema = - when (this) { - is AnyOfSchema -> scanForConstraints(type, prop) - is ArraySchema -> scanForConstraints(type, prop) - is DictionarySchema -> this // TODO Anything here? - is EnumSchema -> scanForConstraints(prop) - is FormattedSchema -> scanForConstraints(type, prop) - is FreeFormSchema -> this // todo anything here? - is ObjectSchema -> scanForConstraints(type, prop) - is SimpleSchema -> scanForConstraints(type, prop) - is ReferencedSchema -> this // todo anything here? - } - -fun AnyOfSchema.scanForConstraints(type: KType, prop: KProperty1<*, *>): AnyOfSchema { - val anyOf = anyOf.map { it.scanForConstraints(type, prop) } - return this.copy( - anyOf = anyOf - ) -} - -fun ArraySchema.scanForConstraints(type: KType, prop: KProperty1<*, *>): ArraySchema { - val minItems = prop.findAnnotation()?.items ?: this.minItems - val maxItems = prop.findAnnotation()?.items ?: this.maxItems - val uniqueItems = prop.findAnnotation()?.let { true } ?: this.uniqueItems - val items = items.scanForConstraints(type, prop) - - return this.copy( - minItems = minItems, - maxItems = maxItems, - uniqueItems = uniqueItems, - items = items - ) -} - -fun EnumSchema.scanForConstraints(prop: KProperty1<*, *>): EnumSchema { - if (prop.returnType.isMarkedNullable) { - return this.copy(nullable = true) - } - - return this -} - -fun FormattedSchema.scanForConstraints(type: KType, prop: KProperty1<*, *>): FormattedSchema { - val minimum = prop.findAnnotation()?.min?.toNumber() ?: this.minimum - val exclusiveMinimum = prop.findAnnotation()?.exclusive ?: this.exclusiveMinimum - val maximum = prop.findAnnotation()?.max?.toNumber() ?: this.maximum - val exclusiveMaximum = prop.findAnnotation()?.exclusive ?: this.exclusiveMaximum - val multipleOf = prop.findAnnotation()?.multiple?.toNumber() ?: this.multipleOf - val format = type.arguments.firstOrNull()?.type?.findAnnotation()?.format - ?: prop.findAnnotation()?.format ?: this.format - val nullable = if (prop.returnType.isMarkedNullable) true else this.nullable - - return this.copy( - minimum = minimum, - maximum = maximum, - exclusiveMinimum = exclusiveMinimum, - exclusiveMaximum = exclusiveMaximum, - multipleOf = multipleOf, - nullable = nullable, - format = format - ) -} - -fun SimpleSchema.scanForConstraints(type: KType, prop: KProperty1<*, *>): SimpleSchema { - val minLength = prop.findAnnotation()?.length ?: this.minLength - val maxLength = prop.findAnnotation()?.length ?: this.maxLength - val pattern = prop.findAnnotation()?.pattern ?: this.pattern - val format = type.arguments.firstOrNull()?.type?.findAnnotation()?.format - ?: prop.findAnnotation()?.format ?: this.format - val nullable = if (prop.returnType.isMarkedNullable) true else this.nullable - - return this.copy( - minLength = minLength, - maxLength = maxLength, - pattern = pattern, - format = format, - nullable = nullable - ) -} - -fun ObjectSchema.scanForConstraints(type: KType, prop: KProperty1<*, *>): ObjectSchema { - val clazz = type.classifier as KClass<*> - var schema = this.adjustForRequiredParams(clazz) - if (prop.returnType.isMarkedNullable) { - schema = schema.copy(nullable = true) - } - - return schema -} - -fun ObjectSchema.adjustForRequiredParams(clazz: KClass<*>): ObjectSchema { - val requiredParams = clazz.primaryConstructor?.parameters?.filterNot { it.isOptional } ?: emptyList() - var schema = this - if (requiredParams.isNotEmpty()) { - schema = schema.copy(required = requiredParams.map { param -> - clazz.memberProperties.first { it.name == param.name }.findAnnotation() - ?.let { field -> field.name.ifBlank { param.name!! } } - ?: param.name!! - }) - } - return schema -} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/CollectionHandler.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/CollectionHandler.kt deleted file mode 100644 index 0903f1cba..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/CollectionHandler.kt +++ /dev/null @@ -1,51 +0,0 @@ -package io.bkbn.kompendium.core.handler - -import io.bkbn.kompendium.core.Kontent -import io.bkbn.kompendium.core.Kontent.generateKTypeKontent -import io.bkbn.kompendium.core.metadata.SchemaMap -import io.bkbn.kompendium.core.util.Helpers -import io.bkbn.kompendium.core.util.Helpers.getSimpleSlug -import io.bkbn.kompendium.oas.schema.AnyOfSchema -import io.bkbn.kompendium.oas.schema.ArraySchema -import kotlin.reflect.KClass -import kotlin.reflect.KType -import org.slf4j.LoggerFactory - -object CollectionHandler : SchemaHandler { - - private val logger = LoggerFactory.getLogger(javaClass) - - /** - * Handler for when a [Collection] is encountered - * @param type Collection type information - * @param clazz Collection class information - * @param cache Existing schema map to append to - */ - override fun handle(type: KType, clazz: KClass<*>, cache: SchemaMap) { - logger.debug("Collection detected for $type, generating schema and appending to cache") - val collectionType = type.arguments.first().type!! - val collectionClass = collectionType.classifier as KClass<*> - logger.debug("Obtained collection class: $collectionClass") - val referenceName = Helpers.genericNameAdapter(type, clazz) - val valueReference = when (collectionClass.isSealed) { - true -> { - val subTypes = gatherSubTypes(collectionType) - AnyOfSchema(subTypes.map { - generateKTypeKontent(it, cache) - val schema = cache[it.getSimpleSlug()] ?: error("${it.getSimpleSlug()} not found") - val slug = it.getSimpleSlug() - postProcessSchema(schema, slug) - }) - } - false -> { - generateKTypeKontent(collectionType, cache) - val schema = cache[collectionClass.simpleName] ?: error("${collectionClass.simpleName} not found") - val slug = collectionClass.simpleName!! - postProcessSchema(schema, slug) - } - } - val schema = ArraySchema(items = valueReference) - Kontent.generateKontent(collectionType, cache) - cache[referenceName] = schema - } -} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/EnumHandler.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/EnumHandler.kt deleted file mode 100644 index 70255b0ba..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/EnumHandler.kt +++ /dev/null @@ -1,20 +0,0 @@ -package io.bkbn.kompendium.core.handler - -import io.bkbn.kompendium.core.metadata.SchemaMap -import io.bkbn.kompendium.oas.schema.EnumSchema -import kotlin.reflect.KClass -import kotlin.reflect.KType - -object EnumHandler : SchemaHandler { - - /** - * Handler for when an [Enum] is encountered - * @param type Map type information - * @param clazz Class of the object to analyze - * @param cache Existing schema map to append to - */ - override fun handle(type: KType, clazz: KClass<*>, cache: SchemaMap) { - val options = clazz.java.enumConstants.map { it.toString() }.toSet() - cache[clazz.simpleName!!] = EnumSchema(options) - } -} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/MapHandler.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/MapHandler.kt deleted file mode 100644 index ecb2ea4ec..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/MapHandler.kt +++ /dev/null @@ -1,54 +0,0 @@ -package io.bkbn.kompendium.core.handler - -import io.bkbn.kompendium.core.Kontent.generateKTypeKontent -import io.bkbn.kompendium.core.Kontent.generateKontent -import io.bkbn.kompendium.core.metadata.SchemaMap -import io.bkbn.kompendium.core.util.Helpers.genericNameAdapter -import io.bkbn.kompendium.core.util.Helpers.getSimpleSlug -import io.bkbn.kompendium.oas.schema.AnyOfSchema -import io.bkbn.kompendium.oas.schema.DictionarySchema -import kotlin.reflect.KClass -import kotlin.reflect.KType -import org.slf4j.LoggerFactory - -object MapHandler : SchemaHandler { - - private val logger = LoggerFactory.getLogger(javaClass) - - /** - * Handler for when a [Map] is encountered - * @param type Map type information - * @param clazz Map class information - * @param cache Existing schema map to append to - */ - override fun handle(type: KType, clazz: KClass<*>, cache: SchemaMap) { - logger.debug("Map detected for $type, generating schema and appending to cache") - val (keyType, valType) = type.arguments.map { it.type } - logger.debug("Obtained map types -> key: $keyType and value: $valType") - if (keyType?.classifier != String::class) { - error("Invalid Map $type: OpenAPI dictionaries must have keys of type String") - } - val valClass = valType!!.classifier as KClass<*> - val valClassName = valClass.simpleName - val referenceName = genericNameAdapter(type, clazz) - val valueReference = when (valClass.isSealed) { - true -> { - val subTypes = gatherSubTypes(valType) - AnyOfSchema(subTypes.map { - generateKTypeKontent(it, cache) - val schema = cache[it.getSimpleSlug()] ?: error("${it.getSimpleSlug()} not found") - val slug = it.getSimpleSlug() - postProcessSchema(schema, slug) - }) - } - false -> { - generateKTypeKontent(valType, cache) - val schema = cache[valClassName] ?: error("$valClassName not found") - postProcessSchema(schema, valClassName!!) - } - } - val schema = DictionarySchema(additionalProperties = valueReference) - generateKontent(valType, cache) - cache[referenceName] = schema - } -} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/ObjectHandler.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/ObjectHandler.kt deleted file mode 100644 index 112b9e50e..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/ObjectHandler.kt +++ /dev/null @@ -1,217 +0,0 @@ -package io.bkbn.kompendium.core.handler - -import io.bkbn.kompendium.annotations.Field -import io.bkbn.kompendium.annotations.FreeFormObject -import io.bkbn.kompendium.annotations.UndeclaredField -import io.bkbn.kompendium.annotations.constraint.MaxProperties -import io.bkbn.kompendium.annotations.constraint.MinProperties -import io.bkbn.kompendium.core.Kontent -import io.bkbn.kompendium.core.Kontent.generateKontent -import io.bkbn.kompendium.core.constraint.adjustForRequiredParams -import io.bkbn.kompendium.core.constraint.scanForConstraints -import io.bkbn.kompendium.core.metadata.SchemaMap -import io.bkbn.kompendium.core.metadata.TypeMap -import io.bkbn.kompendium.core.util.Helpers.getReferenceSlug -import io.bkbn.kompendium.core.util.Helpers.getSimpleSlug -import io.bkbn.kompendium.oas.schema.AnyOfSchema -import io.bkbn.kompendium.oas.schema.ComponentSchema -import io.bkbn.kompendium.oas.schema.FreeFormSchema -import io.bkbn.kompendium.oas.schema.ObjectSchema -import io.bkbn.kompendium.oas.schema.ReferencedSchema -import kotlin.reflect.KClass -import kotlin.reflect.KClassifier -import kotlin.reflect.KProperty1 -import kotlin.reflect.KType -import kotlin.reflect.full.createType -import kotlin.reflect.full.findAnnotation -import kotlin.reflect.full.hasAnnotation -import kotlin.reflect.full.memberProperties -import kotlin.reflect.jvm.javaField -import org.slf4j.LoggerFactory - -object ObjectHandler : SchemaHandler { - - private val logger = LoggerFactory.getLogger(javaClass) - - /** - * In the event of an object type, this method will parse out individual fields to recursively aggregate object map. - * @param type Map type information - * @param clazz Class of the object to analyze - * @param cache Existing schema map to append to - */ - override fun handle(type: KType, clazz: KClass<*>, cache: SchemaMap) { - // This needs to be simple because it will be stored under its appropriate reference component implicitly - val slug = type.getSimpleSlug() - // Only analyze if component has not already been stored in the cache - if (!cache.containsKey(slug)) { - logger.debug("$slug was not found in cache, generating now") - // check if free form object - if (clazz.hasAnnotation()) { - cache[type.getSimpleSlug()] = FreeFormSchema() - } else { - // todo this should be some kind of empty schema at this point, then throw error if not updated eventually - cache[type.getSimpleSlug()] = ReferencedSchema(type.getReferenceSlug()) - val typeMap: TypeMap = clazz.typeParameters.zip(type.arguments).toMap() - val fieldMap = clazz.generateFieldMap(typeMap, cache) - .plus(clazz.generateUndeclaredFieldMap(cache)) - .mapValues { (_, fieldSchema) -> - val fieldSlug = cache.filter { (_, vv) -> vv == fieldSchema }.keys.firstOrNull() - postProcessSchema(fieldSchema, fieldSlug) - } - logger.debug("$slug contains $fieldMap") - val schema = ObjectSchema(fieldMap).adjustForRequiredParams(clazz) - logger.debug("$slug schema: $schema") - cache[slug] = schema - } - } - } - - /** - * Associates each member with a Pair of prop name to property schema - */ - private fun KClass<*>.generateFieldMap(typeMap: TypeMap, cache: SchemaMap) = memberProperties.associate { prop -> - logger.debug("Analyzing $prop in class $this") - // Short circuit if data is free form - when (prop.findAnnotation()) { - null -> handleDefault(typeMap, prop, cache) - else -> handleFreeForm(prop) - } - } - - private fun KClass<*>.generateUndeclaredFieldMap(cache: SchemaMap) = - annotations.filterIsInstance().associate { - logger.debug("Identified undeclared field $it") - val undeclaredType = it.clazz.createType() - generateKontent(undeclaredType, cache) - it.field to cache[undeclaredType.getSimpleSlug()]!! - } - - private fun handleDefault( - typeMap: TypeMap, - prop: KProperty1<*, *>, - cache: SchemaMap - ): Pair { - val field = prop.javaField?.type?.kotlin ?: error("Unable to parse field type from $prop") - val baseType = scanForGeneric(typeMap, prop) - val baseClazz = baseType.classifier as KClass<*> - val allTypes = scanForSealed(baseClazz, baseType) - updateCache(cache, field, allTypes) - val propSchema = constructComponentSchema( - typeMap = typeMap, - prop = prop, - fieldClazz = field, - clazz = baseClazz, - type = baseType, - cache = cache - ) - return propSchema.adjustForFieldOverrides(prop) - } - - private fun ComponentSchema.adjustForFieldOverrides(prop: KProperty1<*, *>): Pair { - var name = prop.name - prop.findAnnotation()?.let { fieldOverrides -> - if (fieldOverrides.description.isNotBlank()) { - this.setDescription(fieldOverrides.description) - } - if (fieldOverrides.name.isNotBlank()) { - name = fieldOverrides.name - } - } - return Pair(name, this) - } - - private fun handleFreeForm(prop: KProperty1<*, *>): Pair { - val minProperties = prop.findAnnotation() - val maxProperties = prop.findAnnotation() - val schema = FreeFormSchema( - minProperties = minProperties?.properties, - maxProperties = maxProperties?.properties - ) - return Pair(prop.name, schema) - } - - /** - * Yoinks any generic types from the type map should the field be a generic - */ - private fun scanForGeneric(typeMap: TypeMap, prop: KProperty1<*, *>): KType = - if (typeMap.containsKey(prop.returnType.classifier)) { - logger.debug("Generic type detected") - typeMap[prop.returnType.classifier]?.type!! - } else { - prop.returnType - } - - /** - * Scans a class for sealed subclasses. If found, returns a list with all children. Otherwise, returns - * the base type - */ - private fun scanForSealed(clazz: KClass<*>, type: KType): List = if (clazz.isSealed) { - clazz.sealedSubclasses.map { it.createType(type.arguments) } - } else { - listOf(type) - } - - /** - * Takes the type information provided and adds any missing data to the schema map - */ - private fun updateCache(cache: SchemaMap, clazz: KClass<*>, types: List) { - if (!cache.containsKey(clazz.simpleName)) { - logger.debug("Cache was missing ${clazz.simpleName}, adding now") - types.forEach { - Kontent.generateKTypeKontent(it, cache) - } - } - } - - private fun constructComponentSchema( - typeMap: TypeMap, - clazz: KClass<*>, - fieldClazz: KClass<*>, - prop: KProperty1<*, *>, - type: KType, - cache: SchemaMap - ): ComponentSchema = - when (typeMap.containsKey(prop.returnType.classifier)) { - true -> handleGenericProperty(typeMap, clazz, type, prop.returnType.classifier, cache) - false -> handleStandardProperty(clazz, fieldClazz, prop, type, cache) - }.scanForConstraints(type, prop) - - /** - * If a field has type parameters, leverage the constructed [TypeMap] to construct the [ComponentSchema] - */ - private fun handleGenericProperty( - typeMap: TypeMap, - clazz: KClass<*>, - type: KType, - classifier: KClassifier?, - cache: SchemaMap - ): ComponentSchema = if (clazz.isSealed) { - val refs = clazz.sealedSubclasses - .map { it.createType(type.arguments) } - .map { it.getSimpleSlug() } - .map { cache[it] ?: error("$it not available in cache") } - AnyOfSchema(refs) - } else { - val slug = typeMap[classifier]?.type!!.getSimpleSlug() - cache[slug] ?: error("$slug not found in cache") - } - - /** - * If a field has no type parameters, build its [ComponentSchema] without referencing the [TypeMap] - */ - private fun handleStandardProperty( - clazz: KClass<*>, - fieldClazz: KClass<*>, - prop: KProperty1<*, *>, - type: KType, - cache: SchemaMap - ): ComponentSchema = if (clazz.isSealed) { - val refs = clazz.sealedSubclasses - .map { it.createType(type.arguments) } - .map { cache[it.getSimpleSlug()] ?: error("$it not found in cache") } - AnyOfSchema(refs) - } else { - val slug = fieldClazz.getSimpleSlug(prop) - cache[slug] ?: error("$slug not found in cache") - } -} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/SchemaHandler.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/SchemaHandler.kt deleted file mode 100644 index d29e24c91..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/handler/SchemaHandler.kt +++ /dev/null @@ -1,38 +0,0 @@ -package io.bkbn.kompendium.core.handler - -import io.bkbn.kompendium.core.metadata.SchemaMap -import io.bkbn.kompendium.core.util.Helpers.COMPONENT_SLUG -import io.bkbn.kompendium.oas.schema.ComponentSchema -import io.bkbn.kompendium.oas.schema.EnumSchema -import io.bkbn.kompendium.oas.schema.ObjectSchema -import io.bkbn.kompendium.oas.schema.ReferencedSchema -import kotlin.reflect.KClass -import kotlin.reflect.KType -import kotlin.reflect.full.createType - -interface SchemaHandler { - fun handle(type: KType, clazz: KClass<*>, cache: SchemaMap) - - fun gatherSubTypes(type: KType): List { - val classifier = type.classifier as KClass<*> - return if (classifier.isSealed) { - classifier.sealedSubclasses.map { - it.createType(type.arguments) - } - } else { - listOf(type) - } - } - - fun postProcessSchema(schema: ComponentSchema, slug: String?): ComponentSchema = when (schema) { - is ObjectSchema -> { - require(slug != null) { "Slug cannot be null for an object schema! $schema" } - ReferencedSchema(COMPONENT_SLUG.plus("/").plus(slug)) - } - is EnumSchema -> { - require(slug != null) { "Slug cannot be null for an enum schema! $schema" } - ReferencedSchema(COMPONENT_SLUG.plus("/").plus(slug)) - } - else -> schema - } -} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ExceptionInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ExceptionInfo.kt deleted file mode 100644 index 44d8cb71a..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ExceptionInfo.kt +++ /dev/null @@ -1,12 +0,0 @@ -package io.bkbn.kompendium.core.metadata - -import io.ktor.http.HttpStatusCode -import kotlin.reflect.KType - -data class ExceptionInfo( - val responseType: KType, - val status: HttpStatusCode, - val description: String, - val mediaTypes: List = listOf("application/json"), - val examples: Map = emptyMap() -) diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ParameterExample.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ParameterExample.kt deleted file mode 100644 index 11dcafebd..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ParameterExample.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.bkbn.kompendium.core.metadata - -data class ParameterExample(val parameterName: String, val exampleName: String, val exampleValue: Any) diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/RequestInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/RequestInfo.kt deleted file mode 100644 index 1507cdd1d..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/RequestInfo.kt +++ /dev/null @@ -1,8 +0,0 @@ -package io.bkbn.kompendium.core.metadata - -data class RequestInfo( - val description: String, - val required: Boolean = true, - val mediaTypes: List = listOf("application/json"), - val examples: Map = emptyMap() -) diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ResponseInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ResponseInfo.kt deleted file mode 100644 index e01bed823..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/ResponseInfo.kt +++ /dev/null @@ -1,10 +0,0 @@ -package io.bkbn.kompendium.core.metadata - -import io.ktor.http.HttpStatusCode - -data class ResponseInfo( - val status: HttpStatusCode, - val description: String, - val mediaTypes: List = listOf("application/json"), - val examples: Map = emptyMap() -) diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/SchemaMap.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/SchemaMap.kt deleted file mode 100644 index 22c016ca1..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/SchemaMap.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.core.metadata - -import io.bkbn.kompendium.oas.schema.ComponentSchema - -typealias SchemaMap = MutableMap diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/TypeMap.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/TypeMap.kt deleted file mode 100644 index bdfb5aa20..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/TypeMap.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.bkbn.kompendium.core.metadata - -import kotlin.reflect.KTypeParameter -import kotlin.reflect.KTypeProjection - -typealias TypeMap = Map diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/DeleteInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/DeleteInfo.kt deleted file mode 100644 index f27875984..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/DeleteInfo.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.bkbn.kompendium.core.metadata.method - -import io.bkbn.kompendium.core.metadata.ExceptionInfo -import io.bkbn.kompendium.core.metadata.ParameterExample -import io.bkbn.kompendium.core.metadata.ResponseInfo - -data class DeleteInfo( - override val responseInfo: ResponseInfo, - override val summary: String, - override val description: String? = null, - override val tags: Set = emptySet(), - override val deprecated: Boolean = false, - override val securitySchemes: Set = emptySet(), - override val canThrow: Set> = emptySet(), - override val parameterExamples: Set = emptySet(), - override val operationId: String? = null -) : MethodInfo diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/GetInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/GetInfo.kt deleted file mode 100644 index b1737591b..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/GetInfo.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.bkbn.kompendium.core.metadata.method - -import io.bkbn.kompendium.core.metadata.ExceptionInfo -import io.bkbn.kompendium.core.metadata.ParameterExample -import io.bkbn.kompendium.core.metadata.ResponseInfo - -data class GetInfo( - override val responseInfo: ResponseInfo, - override val summary: String, - override val description: String? = null, - override val tags: Set = emptySet(), - override val deprecated: Boolean = false, - override val securitySchemes: Set = emptySet(), - override val canThrow: Set> = emptySet(), - override val parameterExamples: Set = emptySet(), - override val operationId: String? = null -) : MethodInfo diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/HeadInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/HeadInfo.kt deleted file mode 100644 index f7de1ced9..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/HeadInfo.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.bkbn.kompendium.core.metadata.method - -import io.bkbn.kompendium.core.metadata.ExceptionInfo -import io.bkbn.kompendium.core.metadata.ParameterExample -import io.bkbn.kompendium.core.metadata.ResponseInfo - -data class HeadInfo( - override val responseInfo: ResponseInfo, - override val summary: String, - override val description: String? = null, - override val tags: Set = emptySet(), - override val deprecated: Boolean = false, - override val securitySchemes: Set = emptySet(), - override val canThrow: Set> = emptySet(), - override val parameterExamples: Set = emptySet(), - override val operationId: String? = null -) : MethodInfo diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/MethodInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/MethodInfo.kt deleted file mode 100644 index 141338258..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/MethodInfo.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.bkbn.kompendium.core.metadata.method - -import io.bkbn.kompendium.core.metadata.ExceptionInfo -import io.bkbn.kompendium.core.metadata.ParameterExample -import io.bkbn.kompendium.core.metadata.ResponseInfo - -sealed interface MethodInfo { - val summary: String - val description: String? - get() = null - val tags: Set - get() = emptySet() - val deprecated: Boolean - get() = false - val securitySchemes: Set - get() = emptySet() - val canThrow: Set> - get() = emptySet() - val responseInfo: ResponseInfo - val parameterExamples: Set - get() = emptySet() - val operationId: String? - get() = null -} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/OptionsInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/OptionsInfo.kt deleted file mode 100644 index 0150ce175..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/OptionsInfo.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.bkbn.kompendium.core.metadata.method - -import io.bkbn.kompendium.core.metadata.ExceptionInfo -import io.bkbn.kompendium.core.metadata.ParameterExample -import io.bkbn.kompendium.core.metadata.ResponseInfo - -data class OptionsInfo( - override val responseInfo: ResponseInfo, - override val summary: String, - override val description: String? = null, - override val tags: Set = emptySet(), - override val deprecated: Boolean = false, - override val securitySchemes: Set = emptySet(), - override val canThrow: Set> = emptySet(), - override val parameterExamples: Set = emptySet(), - override val operationId: String? = null -) : MethodInfo diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PatchInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PatchInfo.kt deleted file mode 100644 index 6cd819bc7..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PatchInfo.kt +++ /dev/null @@ -1,19 +0,0 @@ -package io.bkbn.kompendium.core.metadata.method - -import io.bkbn.kompendium.core.metadata.ExceptionInfo -import io.bkbn.kompendium.core.metadata.ParameterExample -import io.bkbn.kompendium.core.metadata.RequestInfo -import io.bkbn.kompendium.core.metadata.ResponseInfo - -data class PatchInfo( - val requestInfo: RequestInfo?, - override val responseInfo: ResponseInfo, - override val summary: String, - override val description: String? = null, - override val tags: Set = emptySet(), - override val deprecated: Boolean = false, - override val securitySchemes: Set = emptySet(), - override val canThrow: Set> = emptySet(), - override val parameterExamples: Set = emptySet(), - override val operationId: String? = null -) : MethodInfo diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PostInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PostInfo.kt deleted file mode 100644 index cc8dd688c..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PostInfo.kt +++ /dev/null @@ -1,19 +0,0 @@ -package io.bkbn.kompendium.core.metadata.method - -import io.bkbn.kompendium.core.metadata.ExceptionInfo -import io.bkbn.kompendium.core.metadata.ParameterExample -import io.bkbn.kompendium.core.metadata.RequestInfo -import io.bkbn.kompendium.core.metadata.ResponseInfo - -data class PostInfo( - val requestInfo: RequestInfo?, - override val responseInfo: ResponseInfo, - override val summary: String, - override val description: String? = null, - override val tags: Set = emptySet(), - override val deprecated: Boolean = false, - override val securitySchemes: Set = emptySet(), - override val canThrow: Set> = emptySet(), - override val parameterExamples: Set = emptySet(), - override val operationId: String? = null -) : MethodInfo diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PutInfo.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PutInfo.kt deleted file mode 100644 index cc1c2b2d6..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/metadata/method/PutInfo.kt +++ /dev/null @@ -1,19 +0,0 @@ -package io.bkbn.kompendium.core.metadata.method - -import io.bkbn.kompendium.core.metadata.ExceptionInfo -import io.bkbn.kompendium.core.metadata.ParameterExample -import io.bkbn.kompendium.core.metadata.RequestInfo -import io.bkbn.kompendium.core.metadata.ResponseInfo - -data class PutInfo( - val requestInfo: RequestInfo?, - override val responseInfo: ResponseInfo, - override val summary: String, - override val description: String? = null, - override val tags: Set = emptySet(), - override val deprecated: Boolean = false, - override val securitySchemes: Set = emptySet(), - override val canThrow: Set> = emptySet(), - override val parameterExamples: Set = emptySet(), - override val operationId: String? = null -) : MethodInfo diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/parser/DefaultMethodParser.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/parser/DefaultMethodParser.kt deleted file mode 100644 index bcd99e963..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/parser/DefaultMethodParser.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.bkbn.kompendium.core.parser - -object DefaultMethodParser : IMethodParser diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/parser/IMethodParser.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/parser/IMethodParser.kt deleted file mode 100644 index 1359c0d89..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/parser/IMethodParser.kt +++ /dev/null @@ -1,260 +0,0 @@ -package io.bkbn.kompendium.core.parser - -import io.bkbn.kompendium.annotations.Param -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.KompendiumPreFlight.generateReferences -import io.bkbn.kompendium.core.Kontent -import io.bkbn.kompendium.core.metadata.ExceptionInfo -import io.bkbn.kompendium.core.metadata.ParameterExample -import io.bkbn.kompendium.core.metadata.RequestInfo -import io.bkbn.kompendium.core.metadata.ResponseInfo -import io.bkbn.kompendium.core.metadata.method.MethodInfo -import io.bkbn.kompendium.core.metadata.method.PatchInfo -import io.bkbn.kompendium.core.metadata.method.PostInfo -import io.bkbn.kompendium.core.metadata.method.PutInfo -import io.bkbn.kompendium.core.util.Helpers -import io.bkbn.kompendium.core.util.Helpers.capitalized -import io.bkbn.kompendium.core.util.Helpers.getReferenceSlug -import io.bkbn.kompendium.core.util.Helpers.getSimpleSlug -import io.bkbn.kompendium.oas.path.PathOperation -import io.bkbn.kompendium.oas.payload.MediaType -import io.bkbn.kompendium.oas.payload.Parameter -import io.bkbn.kompendium.oas.payload.Request -import io.bkbn.kompendium.oas.payload.Response -import io.bkbn.kompendium.oas.schema.AnyOfSchema -import io.bkbn.kompendium.oas.schema.ObjectSchema -import io.bkbn.kompendium.oas.schema.ReferencedSchema -import io.ktor.routing.Route -import kotlin.reflect.KClass -import kotlin.reflect.KParameter -import kotlin.reflect.KProperty -import kotlin.reflect.KType -import kotlin.reflect.full.createType -import kotlin.reflect.full.findAnnotation -import kotlin.reflect.full.hasAnnotation -import kotlin.reflect.full.memberProperties -import kotlin.reflect.full.primaryConstructor -import java.util.Locale -import java.util.UUID - -interface IMethodParser { - /** - * Generates the OpenAPI Path spec from provided metadata - * @param info implementation of the [MethodInfo] sealed class - * @param paramType Type of `TParam` - * @param requestType Type of `TReq` if required - * @param responseType Type of `TResp` - * @return object representing the OpenAPI Path spec. - */ - fun parseMethodInfo( - info: MethodInfo<*, *>, - paramType: KType, - requestType: KType, - responseType: KType, - feature: Kompendium - ) = PathOperation( - summary = info.summary, - description = info.description, - operationId = info.operationId, - tags = info.tags, - deprecated = info.deprecated, - parameters = paramType.toParameterSpec(info, feature), - responses = parseResponse(responseType, info.responseInfo, feature).plus(parseExceptions(info.canThrow, feature)), - requestBody = when (info) { - is PutInfo<*, *, *> -> requestType.toRequestSpec(info.requestInfo, feature) - is PostInfo<*, *, *> -> requestType.toRequestSpec(info.requestInfo, feature) - is PatchInfo<*, *, *> -> requestType.toRequestSpec(info.requestInfo, feature) - else -> null - }, - security = if (info.securitySchemes.isNotEmpty()) listOf( - // TODO support scopes - info.securitySchemes.associateWith { listOf() } - ) else null - ) - - fun parseResponse( - responseType: KType, - responseInfo: ResponseInfo<*>?, - feature: Kompendium - ): Map = responseType.toResponseSpec(responseInfo, feature)?.let { mapOf(it) }.orEmpty() - - fun parseExceptions( - exceptionInfo: Set>, - feature: Kompendium, - ): Map = exceptionInfo.associate { info -> - Kontent.generateKontent(info.responseType, feature.config.bodyCache) - feature.generateReferences() - val response = Response( - description = info.description, - content = feature.resolveContent(info.responseType, info.mediaTypes, info.examples) - ) - Pair(info.status.value, response) - } - - /** - * Converts a [KType] to an [Request] - * @receiver [KType] to convert - * @param requestInfo request metadata - * @return Will return a generated [Request] if requestInfo is not null - */ - fun KType.toRequestSpec(requestInfo: RequestInfo<*>?, feature: Kompendium): Request? = - when (requestInfo) { - null -> null - else -> { - Request( - description = requestInfo.description, - content = feature.resolveContent(this, requestInfo.mediaTypes, requestInfo.examples as Map) - ?: mapOf(), - required = requestInfo.required - ) - } - } - - /** - * Converts a [KType] to a pairing of http status code to [Response] - * @receiver [KType] to convert - * @param responseInfo response metadata - * @return Will return a generated [Pair] if responseInfo is not null - */ - fun KType.toResponseSpec(responseInfo: ResponseInfo<*>?, feature: Kompendium): Pair? = - when (responseInfo) { - null -> null - else -> { - val specResponse = Response( - description = responseInfo.description, - content = feature.resolveContent(this, responseInfo.mediaTypes, responseInfo.examples as Map) - ) - Pair(responseInfo.status.value, specResponse) - } - } - - /** - * Generates MediaTypes along with any examples provided - * @param type [KType] Type of the object - * @param mediaTypes list of acceptable http media types - * @param examples Mapping of named examples of valid bodies. - * @return Named mapping of media types. - */ - fun Kompendium.resolveContent( - type: KType, - mediaTypes: List, - examples: Map - ): Map? { - val classifier = type.classifier as KClass<*> - return if (type != Helpers.UNIT_TYPE && mediaTypes.isNotEmpty()) { - mediaTypes.associateWith { - val schema = if (classifier.isSealed) { - val refs = classifier.sealedSubclasses - .map { it.createType(type.arguments) } - .map { ReferencedSchema(it.getReferenceSlug()) } - AnyOfSchema(refs) - } else { - if (config.spec.components.schemas.containsKey(type.getSimpleSlug())) { - ReferencedSchema(type.getReferenceSlug()) - } else { - config.bodyCache[type.getSimpleSlug()] ?: error("REEEE") - } - } - MediaType( - schema = schema, - examples = examples.mapValues { (_, v) -> MediaType.Example(v) }.ifEmpty { null } - ) - } - } else null - } - - /** - * Parses a type for all parameter information. All fields in the receiver - * must be annotated with [io.bkbn.kompendium.annotations.Param]. - * @receiver type - * @return list of valid parameter specs as detailed by the [KType] members - * @throws [IllegalStateException] if the class could not be parsed properly - */ - fun KType.toParameterSpec(info: MethodInfo<*, *>, feature: Kompendium): List { - val clazz = classifier as KClass<*> - return clazz.memberProperties - .filter { prop -> prop.hasAnnotation() } - .map { prop -> prop.toParameter(info, this, clazz, feature) } - } - - fun KProperty<*>.toParameter( - info: MethodInfo<*, *>, - parentType: KType, - parentClazz: KClass<*>, - feature: Kompendium - ): Parameter { - val wrapperSchema = feature.config.parameterCache[parentType.getSimpleSlug()]!! as ObjectSchema - val anny = this.findAnnotation() - ?: error("Field $name is not annotated with KompendiumParam") - val schema = wrapperSchema.properties[name] - ?: error("Could not find component type for $this") - val defaultValue = getDefaultParameterValue(parentClazz, this) - return Parameter( - name = name, - `in` = anny.type.name.lowercase(Locale.getDefault()), - schema = schema.addDefault(defaultValue), - description = schema.description, - required = !returnType.isMarkedNullable && defaultValue == null, - examples = info.parameterExamples.mapToSpec(name) - ) - } - - fun Set.mapToSpec(parameterName: String): Map? { - val filtered = filter { it.parameterName == parameterName } - return if (filtered.isEmpty()) { - null - } else { - filtered.associate { it.exampleName to Parameter.Example(it.exampleValue) } - } - } - - /** - * Absolutely disgusting reflection to determine if a default value is available for a given property. - * @param clazz to which the property belongs - * @param prop the property in question - * @return The default value if found - */ - fun getDefaultParameterValue(clazz: KClass<*>, prop: KProperty<*>): Any? { - val constructor = clazz.primaryConstructor - val parameterInQuestion = constructor - ?.parameters - ?.find { it.name == prop.name } - ?: error("could not find parameter ${prop.name}") - if (!parameterInQuestion.isOptional) { - return null - } - val values = constructor - .parameters - .filterNot { it.isOptional } - .associateWith { defaultValueInjector(it) } - val instance = constructor.callBy(values) - val methods = clazz.java.methods - val getterName = "get${prop.name.capitalized()}" - val getterFunction = methods.find { it.name == getterName } - ?: error("Could not associate ${prop.name} with a getter") - return getterFunction.invoke(instance) - } - - /** - * Allows the reflection invoker to populate a parameter map with values in order to sus out any default parameters. - * @param param Parameter to provide value for - * @return value of the proper type to match param - * @throws [IllegalStateException] if parameter type is not one of the basic types supported below. - */ - fun defaultValueInjector(param: KParameter): Any = when (param.type.classifier) { - String::class -> "test" - Boolean::class -> false - Int::class -> 1 - Long::class -> 2 - Double::class -> 1.0 - Float::class -> 1.0 - UUID::class -> UUID.randomUUID() - else -> error("Unsupported Type") - } - - /** - * Uses the built-in Ktor route path [Route.toString] but cuts out any meta route such as authentication... anything - * that matches the RegEx pattern `/\\(.+\\)` - */ - fun Route.calculateRoutePath() = toString().replace(Regex("/\\(.+\\)"), "") -} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/routes/Swagger.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/routes/Swagger.kt deleted file mode 100644 index 4c417d7f4..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/routes/Swagger.kt +++ /dev/null @@ -1,81 +0,0 @@ -package io.bkbn.kompendium.core.routes - -import io.ktor.application.call -import io.ktor.html.respondHtml -import io.ktor.routing.Routing -import io.ktor.routing.get -import io.ktor.routing.route -import kotlinx.html.body -import kotlinx.html.div -import kotlinx.html.head -import kotlinx.html.id -import kotlinx.html.link -import kotlinx.html.meta -import kotlinx.html.script -import kotlinx.html.title -import kotlinx.html.unsafe - -/** - * Provides an out-of-the-box route to view docs using Swagger - * @param pageTitle Webpage title you wish to be displayed on your docs - * @param specUrl url to point Swagger to the OpenAPI json document - */ -fun Routing.swagger(pageTitle: String = "Docs", specUrl: String = "/openapi.json") { - route("/swagger-ui") { - get { - call.respondHtml { - head { - title { - +pageTitle - } - meta { - charset = "utf-8" - } - meta { - name = "viewport" - content = "width=device-width, initial-scale=1" - } - link { - href = "https://unpkg.com/swagger-ui-dist@4.5.0/swagger-ui.css" - rel = "stylesheet" - } - } - body { - div { - id = "swagger-ui" - } - script { - src = "https://unpkg.com/swagger-ui-dist@4.5.0/swagger-ui-standalone-preset.js" - } - script { - src = "https://unpkg.com/swagger-ui-dist@4.5.0/swagger-ui-bundle.js" - } - unsafe { - +""" - - """.trimIndent() - } - } - } - } - } -} diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/util/Helpers.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/util/Helpers.kt deleted file mode 100644 index 326aeffdf..000000000 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/util/Helpers.kt +++ /dev/null @@ -1,85 +0,0 @@ -package io.bkbn.kompendium.core.util - -import kotlin.reflect.KClass -import kotlin.reflect.KProperty -import kotlin.reflect.KType -import kotlin.reflect.full.createType -import kotlin.reflect.jvm.javaField -import org.slf4j.LoggerFactory -import java.lang.reflect.ParameterizedType -import java.util.Locale - -object Helpers { - - private val logger = LoggerFactory.getLogger(javaClass) - - const val COMPONENT_SLUG = "#/components/schemas" - - val UNIT_TYPE by lazy { Unit::class.createType() } - - /** - * Higher order function that takes a map of names to object and will log their state ahead of function invocation - * along with the result of the function invocation - */ - fun logged(functionName: String, entities: Map, block: () -> T): T { - entities.forEach { (name, entity) -> logger.debug("Ahead of $functionName invocation, $name: $entity") } - val result = block.invoke() - logger.debug("Result of $functionName invocation: $result") - return result - } - - fun KClass<*>.getSimpleSlug(prop: KProperty<*>): String = when { - this.typeParameters.isNotEmpty() -> genericNameAdapter(this, prop) - else -> simpleName ?: error("Could not determine simple name for $this") - } - - fun KType.getSimpleSlug(): String = when { - this.arguments.isNotEmpty() -> genericNameAdapter(this, classifier as KClass<*>) - else -> (classifier as KClass<*>).simpleName ?: error("Could not determine simple name for $this") - } - - fun KType.getReferenceSlug(): String = when { - arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}" - else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).simpleName}" - } - - /** - * Will build a reference slug that is useful for schema caching and references, particularly - * in the case of a class with type parameters - */ - fun KClass<*>.getReferenceSlug(prop: KProperty<*>): String = when { - this.typeParameters.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, prop)}" - else -> "$COMPONENT_SLUG/${simpleName}" - } - - /** - * Adapts a class with type parameters into a reference friendly string - */ - fun genericNameAdapter(field: KClass<*>, prop: KProperty<*>): String { - val typeArgs = (prop.javaField?.genericType as ParameterizedType).actualTypeArguments - val classNames = typeArgs.map { it as Class<*> }.map { it.kotlin }.map { it.simpleName } - return classNames.joinToString(separator = "-", prefix = "${field.simpleName}-") - } - - /** - * Adapts a class with type parameters into a reference friendly string - */ - fun genericNameAdapter(type: KType, clazz: KClass<*>): String { - val classNames = type.arguments - .map { it.type?.classifier as KClass<*> } - .map { it.simpleName } - return classNames.joinToString(separator = "-", prefix = "${clazz.simpleName}-") - } - - fun String.capitalized() = replaceFirstChar { - if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() - } - - fun String.toNumber(): Number { - return try { - this.toInt() - } catch (e: NumberFormatException) { - this.toDouble() - } - } -} diff --git a/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt b/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt deleted file mode 100644 index 4b36a1e2f..000000000 --- a/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt +++ /dev/null @@ -1,371 +0,0 @@ -package io.bkbn.kompendium.core - -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.databind.ObjectMapper -import io.bkbn.kompendium.core.fixtures.TestHelpers.apiFunctionalityTest -import io.bkbn.kompendium.core.fixtures.TestHelpers.compareOpenAPISpec -import io.bkbn.kompendium.core.fixtures.TestHelpers.getFileSnapshot -import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers -import io.bkbn.kompendium.core.fixtures.TestSpecs.defaultSpec -import io.bkbn.kompendium.core.fixtures.docs -import io.bkbn.kompendium.core.util.complexType -import io.bkbn.kompendium.core.util.constrainedDoubleInfo -import io.bkbn.kompendium.core.util.constrainedIntInfo -import io.bkbn.kompendium.core.util.dateTimeString -import io.bkbn.kompendium.core.util.defaultField -import io.bkbn.kompendium.core.util.defaultParameter -import io.bkbn.kompendium.core.util.exampleParams -import io.bkbn.kompendium.core.util.exclusiveMinMax -import io.bkbn.kompendium.core.util.formattedParam -import io.bkbn.kompendium.core.util.formattedType -import io.bkbn.kompendium.core.util.freeFormField -import io.bkbn.kompendium.core.util.freeFormObject -import io.bkbn.kompendium.core.util.genericPolymorphicResponse -import io.bkbn.kompendium.core.util.genericPolymorphicResponseMultipleImpls -import io.bkbn.kompendium.core.util.headerParameter -import io.bkbn.kompendium.core.util.minMaxArray -import io.bkbn.kompendium.core.util.minMaxFreeForm -import io.bkbn.kompendium.core.util.minMaxString -import io.bkbn.kompendium.core.util.multipleOfDouble -import io.bkbn.kompendium.core.util.multipleOfInt -import io.bkbn.kompendium.core.util.nestedUnderRootModule -import io.bkbn.kompendium.core.util.nonRequiredParamsGet -import io.bkbn.kompendium.core.util.notarizedDeleteModule -import io.bkbn.kompendium.core.util.notarizedGetModule -import io.bkbn.kompendium.core.util.notarizedGetWithGenericErrorResponse -import io.bkbn.kompendium.core.util.notarizedGetWithMultipleThrowables -import io.bkbn.kompendium.core.util.notarizedGetWithNotarizedException -import io.bkbn.kompendium.core.util.notarizedGetWithPolymorphicErrorResponse -import io.bkbn.kompendium.core.util.notarizedHeadModule -import io.bkbn.kompendium.core.util.notarizedOptionsModule -import io.bkbn.kompendium.core.util.notarizedPatchModule -import io.bkbn.kompendium.core.util.notarizedPostModule -import io.bkbn.kompendium.core.util.notarizedPutModule -import io.bkbn.kompendium.core.util.nullableEnumField -import io.bkbn.kompendium.core.util.nullableField -import io.bkbn.kompendium.core.util.nullableNestedObject -import io.bkbn.kompendium.core.util.overrideFieldInfo -import io.bkbn.kompendium.core.util.pathParsingTestModule -import io.bkbn.kompendium.core.util.polymorphicCollectionResponse -import io.bkbn.kompendium.core.util.polymorphicInterfaceResponse -import io.bkbn.kompendium.core.util.polymorphicMapResponse -import io.bkbn.kompendium.core.util.polymorphicResponse -import io.bkbn.kompendium.core.util.primitives -import io.bkbn.kompendium.core.util.regexString -import io.bkbn.kompendium.core.util.requiredParameter -import io.bkbn.kompendium.core.util.returnsList -import io.bkbn.kompendium.core.util.rootModule -import io.bkbn.kompendium.core.util.simpleGenericResponse -import io.bkbn.kompendium.core.util.simpleRecursive -import io.bkbn.kompendium.core.util.trailingSlash -import io.bkbn.kompendium.core.util.undeclaredType -import io.bkbn.kompendium.core.util.uniqueArray -import io.bkbn.kompendium.core.util.withDefaultParameter -import io.bkbn.kompendium.core.util.withExamples -import io.bkbn.kompendium.core.util.withOperationId -import io.bkbn.kompendium.oas.schema.FormattedSchema -import io.bkbn.kompendium.oas.schema.SimpleSchema -import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule -import io.kotest.core.spec.style.DescribeSpec -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.features.ContentNegotiation -import io.ktor.http.ContentType -import io.ktor.http.HttpMethod -import io.ktor.http.HttpStatusCode -import io.ktor.jackson.jackson -import io.ktor.response.respondText -import io.ktor.routing.get -import io.ktor.routing.route -import io.ktor.serialization.json -import io.ktor.server.testing.withTestApplication -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import java.time.Instant - -class KompendiumTest : DescribeSpec({ - describe("Notarized Open API Metadata Tests") { - it("Can notarize a get request") { - openApiTestAllSerializers("notarized_get.json") { notarizedGetModule() } - } - it("Can notarize a post request") { - openApiTestAllSerializers("notarized_post.json") { notarizedPostModule() } - } - it("Can notarize a put request") { - openApiTestAllSerializers("notarized_put.json") { notarizedPutModule() } - } - it("Can notarize a delete request") { - openApiTestAllSerializers("notarized_delete.json") { notarizedDeleteModule() } - } - it("Can notarize a patch request") { - openApiTestAllSerializers("notarized_patch.json") { notarizedPatchModule() } - } - it("Can notarize a head request") { - openApiTestAllSerializers("notarized_head.json") { notarizedHeadModule() } - } - it("Can notarize an options request") { - openApiTestAllSerializers("notarized_options.json") { notarizedOptionsModule() } - } - it("Can notarize a complex type") { - openApiTestAllSerializers("complex_type.json") { complexType() } - } - it("Can notarize primitives") { - openApiTestAllSerializers("notarized_primitives.json") { primitives() } - } - it("Can notarize a top level list response") { - openApiTestAllSerializers("response_list.json") { returnsList() } - } - it("Can notarize a route with non-required params") { - openApiTestAllSerializers("non_required_params.json") { nonRequiredParamsGet() } - } - } - describe("Notarized Ktor Functionality Tests") { - it("Can notarized a get request and return the expected result") { - apiFunctionalityTest("hey dude ‼️ congratz on the get request") { notarizedGetModule() } - } - it("Can notarize a post request and return the expected result") { - apiFunctionalityTest( - "hey dude ✌️ congratz on the post request", - httpMethod = HttpMethod.Post - ) { notarizedPostModule() } - } - it("Can notarize a put request and return the expected result") { - apiFunctionalityTest("hey pal 🌝 whatcha doin' here?", httpMethod = HttpMethod.Put) { notarizedPutModule() } - } - it("Can notarize a delete request and return the expected result") { - apiFunctionalityTest( - null, - httpMethod = HttpMethod.Delete, - expectedStatusCode = HttpStatusCode.NoContent - ) { notarizedDeleteModule() } - } - it("Can notarize the root route and return the expected result") { - apiFunctionalityTest("☎️🏠🌲", "/") { rootModule() } - } - it("Can notarize a trailing slash route and return the expected result") { - apiFunctionalityTest("🙀👾", "/test/") { trailingSlash() } - } - } - describe("Route Parsing") { - it("Can parse a simple path and store it under the expected route") { - openApiTestAllSerializers("path_parser.json") { pathParsingTestModule() } - } - it("Can notarize the root route") { - openApiTestAllSerializers("root_route.json") { rootModule() } - } - it("Can notarize a route under the root module without appending trailing slash") { - openApiTestAllSerializers("nested_under_root.json") { nestedUnderRootModule() } - } - it("Can notarize a route with a trailing slash") { - openApiTestAllSerializers("trailing_slash.json") { trailingSlash() } - } - } - describe("Exceptions") { - it("Can add an exception status code to a response") { - openApiTestAllSerializers("notarized_get_with_exception_response.json") { notarizedGetWithNotarizedException() } - } - it("Can support multiple response codes") { - openApiTestAllSerializers("notarized_get_with_multiple_exception_responses.json") { notarizedGetWithMultipleThrowables() } - } - it("Can add a polymorphic exception response") { - openApiTestAllSerializers("polymorphic_error_status_codes.json") { notarizedGetWithPolymorphicErrorResponse() } - } - it("Can add a generic exception response") { - openApiTestAllSerializers("generic_exception.json") { notarizedGetWithGenericErrorResponse() } - } - } - describe("Examples") { - it("Can generate example response and request bodies") { - openApiTestAllSerializers("example_req_and_resp.json") { withExamples() } - } - it("Can describe example parameters") { - openApiTestAllSerializers("example_parameters.json") { exampleParams() } - } - } - describe("Defaults") { - it("Can generate a default parameter values") { - 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") { - openApiTestAllSerializers("required_param.json") { requiredParameter() } - } - it("Does not mark a parameter as required if a default value is provided") { - openApiTestAllSerializers("default_param.json") { defaultParameter() } - } - it("Does not mark a field as required if a default value is provided") { - openApiTestAllSerializers("default_field.json") { defaultField() } - } - it("Marks a field as nullable when expected") { - openApiTestAllSerializers("nullable_field.json") { nullableField() } - } - } - describe("Polymorphism and Generics") { - it("can generate a polymorphic response type") { - openApiTestAllSerializers("polymorphic_response.json") { polymorphicResponse() } - } - it("Can generate a collection with polymorphic response type") { - openApiTestAllSerializers("polymorphic_list_response.json") { polymorphicCollectionResponse() } - } - it("Can generate a map with a polymorphic response type") { - openApiTestAllSerializers("polymorphic_map_response.json") { polymorphicMapResponse() } - } - it("Can generate a polymorphic response from a sealed interface") { - openApiTestAllSerializers("sealed_interface_response.json") { polymorphicInterfaceResponse() } - } - it("Can generate a response type with a generic type") { - openApiTestAllSerializers("generic_response.json") { simpleGenericResponse() } - } - it("Can generate a polymorphic response type with generics") { - openApiTestAllSerializers("polymorphic_response_with_generics.json") { genericPolymorphicResponse() } - } - it("Can handle an absolutely psycho inheritance test") { - openApiTestAllSerializers("crazy_polymorphic_example.json") { genericPolymorphicResponseMultipleImpls() } - } - } - describe("Miscellaneous") { - it("Can generate the necessary ReDoc home page") { - apiFunctionalityTest(getFileSnapshot("redoc.html"), "/docs") { returnsList() } - } - it("Can add an operation id to a notarized route") { - openApiTestAllSerializers("notarized_get_with_operation_id.json") { withOperationId() } - } - it("Can add an undeclared field") { - openApiTestAllSerializers("undeclared_field.json") { undeclaredType() } - } - it("Can add a custom header parameter with a name override") { - openApiTestAllSerializers("override_parameter_name.json") { headerParameter() } - } - it("Can override field values via annotation") { - openApiTestAllSerializers("field_override.json") { overrideFieldInfo() } - } - it("Can serialize a recursive type") { - openApiTestAllSerializers("simple_recursive.json") { simpleRecursive() } - } - it("Nullable fields do not lead to doom") { - openApiTestAllSerializers("nullable_fields.json") { nullableNestedObject() } - } - it("Can have a nullable enum as a member field") { - openApiTestAllSerializers("nullable_enum_field.json") { nullableEnumField() } - } - } - describe("Constraints") { - it("Can set a minimum and maximum integer value") { - openApiTestAllSerializers("min_max_int_field.json") { constrainedIntInfo() } - } - it("Can set a minimum and maximum double value") { - openApiTestAllSerializers("min_max_double_field.json") { constrainedDoubleInfo() } - } - it("Can set an exclusive min and exclusive max integer value") { - openApiTestAllSerializers("exclusive_min_max.json") { exclusiveMinMax() } - } - it("Can add a custom format to a string field") { - openApiTestAllSerializers("formatted_param_type.json") { formattedParam() } - } - it("Can set a minimum and maximum length on a string field") { - openApiTestAllSerializers("min_max_string.json") { minMaxString() } - } - it("Can set a custom regex pattern on a string field") { - openApiTestAllSerializers("regex_string.json") { regexString() } - } - it("Can set a minimum and maximum item count on an array field") { - openApiTestAllSerializers("min_max_array.json") { minMaxArray() } - } - it("Can set a unique items constraint on an array field") { - openApiTestAllSerializers("unique_array.json") { uniqueArray() } - } - it("Can set a multiple-of constraint on an int field") { - openApiTestAllSerializers("multiple_of_int.json") { multipleOfInt() } - } - it("Can set a multiple of constraint on an double field") { - openApiTestAllSerializers("multiple_of_double.json") { multipleOfDouble() } - } - it("Can set a minimum and maximum number of properties on a free-form type") { - openApiTestAllSerializers("min_max_free_form.json") { minMaxFreeForm() } - } - it("Can add a custom format to a collection type") { - openApiTestAllSerializers("formatted_array_item_type.json") { formattedType() } - } - } - describe("Formats") { - it("Can set a format on a simple type schema") { - openApiTestAllSerializers("formatted_date_time_string.json", { dateTimeString() }) { - addCustomTypeSchema(Instant::class, SimpleSchema("string", format = "date-time")) - } - } - it("Can set a format on formatted type schema") { - openApiTestAllSerializers("formatted_date_time_string.json", { dateTimeString() }) { - addCustomTypeSchema(Instant::class, FormattedSchema("date-time", "string")) - } - } - it("Can bypass a format on a simple type schema") { - openApiTestAllSerializers("formatted_no_format_string.json", { dateTimeString() }) { - addCustomTypeSchema(Instant::class, SimpleSchema("string")) - } - } - } - describe("Free Form") { - it("Can create a free-form field") { - openApiTestAllSerializers("free_form_field.json") { freeFormField() } - } - it("Can create a top-level free form object") { - openApiTestAllSerializers("free_form_object.json") { freeFormObject() } - } - } - describe("Serialization overrides") { - it("Can override the jackson serializer") { - withTestApplication({ - install(Kompendium) { - spec = defaultSpec() - openApiJson = { spec -> - val om = ObjectMapper().apply { - setSerializationInclusion(JsonInclude.Include.NON_NULL) - } - route("/openapi.json") { - get { - call.respondText { om.writeValueAsString(spec) } - } - } - } - } - install(ContentNegotiation) { - jackson(ContentType.Application.Json) - } - docs() - withExamples() - }) { - compareOpenAPISpec("example_req_and_resp.json") - } - } - it("Can override the kotlinx serializer") { - withTestApplication({ - install(Kompendium) { - spec = defaultSpec() - openApiJson = { spec -> - val om = ObjectMapper().apply { - setSerializationInclusion(JsonInclude.Include.NON_NULL) - } - route("/openapi.json") { - get { - val customSerializer = Json { - serializersModule = KompendiumSerializersModule.module - encodeDefaults = true - explicitNulls = false - } - call.respondText { customSerializer.encodeToString(spec) } - } - } - } - } - install(ContentNegotiation) { - json() - } - docs() - withExamples() - }) { - compareOpenAPISpec("example_req_and_resp.json") - } - } - } -}) diff --git a/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KontentTest.kt b/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KontentTest.kt deleted file mode 100644 index 7cff10847..000000000 --- a/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KontentTest.kt +++ /dev/null @@ -1,215 +0,0 @@ -package io.bkbn.kompendium.core - -import io.bkbn.kompendium.core.Kontent.generateKontent -import io.bkbn.kompendium.core.fixtures.ComplexRequest -import io.bkbn.kompendium.core.fixtures.TestBigNumberModel -import io.bkbn.kompendium.core.fixtures.TestByteArrayModel -import io.bkbn.kompendium.core.fixtures.TestInvalidMap -import io.bkbn.kompendium.core.fixtures.TestNestedModel -import io.bkbn.kompendium.core.fixtures.TestSimpleModel -import io.bkbn.kompendium.core.fixtures.TestSimpleWithEnumList -import io.bkbn.kompendium.core.fixtures.TestSimpleWithEnums -import io.bkbn.kompendium.core.fixtures.TestSimpleWithList -import io.bkbn.kompendium.core.fixtures.TestSimpleWithMap -import io.bkbn.kompendium.core.fixtures.TestWithUUID -import io.bkbn.kompendium.core.metadata.SchemaMap -import io.bkbn.kompendium.oas.schema.DictionarySchema -import io.bkbn.kompendium.oas.schema.FormattedSchema -import io.bkbn.kompendium.oas.schema.ObjectSchema -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.core.spec.style.DescribeSpec -import io.kotest.matchers.maps.beEmpty -import io.kotest.matchers.maps.shouldContainKey -import io.kotest.matchers.maps.shouldHaveKey -import io.kotest.matchers.maps.shouldHaveSize -import io.kotest.matchers.should -import io.kotest.matchers.shouldBe -import io.kotest.matchers.shouldNotBe -import java.util.UUID - -class KontentTest : DescribeSpec({ - describe("Kontent analysis") { - it("Can return an empty map when passed Unit") { - // arrange - val cache: SchemaMap = mutableMapOf() - - // act - generateKontent(cache) - - // assert - cache should beEmpty() - } - it("Can return a single map result when analyzing a primitive") { - // arrange - val cache: SchemaMap = mutableMapOf() - - // act - generateKontent(cache) - - // assert - cache shouldHaveSize 1 - cache["Long"] shouldBe FormattedSchema("int64", "integer") - } - it("Can handle BigDecimal and BigInteger Types") { - // arrange - val cache: SchemaMap = mutableMapOf() - - // act - generateKontent(cache) - - // assert - cache shouldHaveSize 3 - cache shouldContainKey TestBigNumberModel::class.simpleName!! - cache["BigDecimal"] shouldBe FormattedSchema("double", "number") - cache["BigInteger"] shouldBe FormattedSchema("int64", "integer") - } - it("Can handle ByteArray type") { - // arrange - val cache: SchemaMap = mutableMapOf() - - // act - generateKontent(cache) - - // assert - cache shouldHaveSize 2 - cache shouldContainKey TestByteArrayModel::class.simpleName!! - cache["ByteArray"] shouldBe FormattedSchema("byte", "string") - } - it("Allows objects to reference their base type in the cache") { - // arrange - val cache: SchemaMap = mutableMapOf() - - // act - generateKontent(cache) - - // assert - cache shouldNotBe null - cache shouldHaveSize 3 - cache shouldContainKey TestSimpleModel::class.simpleName!! - } - it("Can generate cache for nested object types") { - // arrange - val cache: SchemaMap = mutableMapOf() - - // act - generateKontent(cache) - - // assert - cache shouldNotBe null - cache shouldHaveSize 4 - cache shouldContainKey TestNestedModel::class.simpleName!! - cache shouldContainKey TestSimpleModel::class.simpleName!! - } - it("Does not repeat generation for cached items") { - // arrange - val cache: SchemaMap = mutableMapOf() - - // arrange - val clazz = TestNestedModel::class - generateKontent(cache) - - // act - generateKontent(cache) - - // assert TODO Spy to check invocation count? - cache shouldNotBe null - cache shouldHaveSize 4 - cache shouldContainKey clazz.simpleName!! - cache shouldContainKey TestSimpleModel::class.simpleName!! - } - it("allows for generation of enum types") { - // arrange - val cache: SchemaMap = mutableMapOf() - - // act - generateKontent(cache) - - // assert - cache shouldNotBe null - cache shouldHaveSize 3 - cache shouldContainKey TestSimpleWithEnums::class.simpleName!! - } - it("Allows for generation of map fields") { - // arrange - val cache: SchemaMap = mutableMapOf() - - // act - generateKontent(cache) - - // assert - cache shouldNotBe null - cache shouldHaveSize 5 - cache shouldContainKey "Map-String-TestSimpleModel" - cache shouldContainKey TestSimpleWithMap::class.simpleName!! - cache[TestSimpleWithMap::class.simpleName] as ObjectSchema shouldNotBe null // TODO Improve - } - it("Throws an error if a map is of an invalid type") { - // assert - shouldThrow { generateKontent() } - } - it("Can generate for collection fields") { - // arrange - val cache: SchemaMap = mutableMapOf() - - // act - generateKontent(cache) - - // assert - cache shouldNotBe null - cache shouldHaveSize 6 - cache shouldContainKey "List-TestSimpleModel" - cache shouldContainKey TestSimpleWithList::class.simpleName!! - } - it("Can parse an enum list as a field") { - // arrange - val cache: SchemaMap = mutableMapOf() - - // act - generateKontent(cache) - - // assert - cache shouldNotBe null - cache shouldHaveSize 4 - cache shouldHaveKey "List-SimpleEnum" - } - it("Can support UUIDs") { - // arrange - val cache: SchemaMap = mutableMapOf() - - // act - generateKontent(cache) - - // assert - cache shouldNotBe null - cache shouldHaveSize 2 - cache shouldContainKey UUID::class.simpleName!! - cache shouldContainKey TestWithUUID::class.simpleName!! - cache[UUID::class.simpleName] as FormattedSchema shouldBe FormattedSchema("uuid", "string") - } - it("Can generate a top level list response") { - // arrange - val cache: SchemaMap = mutableMapOf() - - // act - generateKontent>(cache) - - // assert - cache shouldNotBe null - cache shouldHaveSize 4 - cache shouldContainKey "List-TestSimpleModel" - } - it("Can handle a complex type") { - // arrange - val cache: SchemaMap = mutableMapOf() - - // act - generateKontent(cache) - - // assert - cache shouldNotBe null - cache shouldHaveSize 7 - cache shouldContainKey "Map-String-CrazyItem" - cache["Map-String-CrazyItem"] as DictionarySchema shouldNotBe null - } - } -}) diff --git a/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt b/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt deleted file mode 100644 index c8f61a55b..000000000 --- a/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt +++ /dev/null @@ -1,640 +0,0 @@ -package io.bkbn.kompendium.core.util - -import io.bkbn.kompendium.core.Notarized.notarizedDelete -import io.bkbn.kompendium.core.Notarized.notarizedGet -import io.bkbn.kompendium.core.Notarized.notarizedHead -import io.bkbn.kompendium.core.Notarized.notarizedOptions -import io.bkbn.kompendium.core.Notarized.notarizedPatch -import io.bkbn.kompendium.core.Notarized.notarizedPost -import io.bkbn.kompendium.core.Notarized.notarizedPut -import io.bkbn.kompendium.core.fixtures.Bibbity -import io.bkbn.kompendium.core.fixtures.ComplexGibbit -import io.bkbn.kompendium.core.fixtures.DateTimeString -import io.bkbn.kompendium.core.fixtures.DefaultParameter -import io.bkbn.kompendium.core.fixtures.Gibbity -import io.bkbn.kompendium.core.fixtures.Mysterious -import io.bkbn.kompendium.core.fixtures.SimpleGibbit -import io.bkbn.kompendium.core.fixtures.TestFieldOverride -import io.bkbn.kompendium.core.fixtures.TestHelpers.DEFAULT_TEST_ENDPOINT -import io.bkbn.kompendium.core.fixtures.TestNested -import io.bkbn.kompendium.core.fixtures.TestRequest -import io.bkbn.kompendium.core.fixtures.TestResponse -import io.bkbn.kompendium.core.fixtures.TestResponseInfo -import io.bkbn.kompendium.core.fixtures.TestResponseInfo.dateTimeString -import io.bkbn.kompendium.core.fixtures.TestResponseInfo.defaultField -import io.bkbn.kompendium.core.fixtures.TestResponseInfo.defaultParam -import io.bkbn.kompendium.core.fixtures.TestResponseInfo.formattedParam -import io.bkbn.kompendium.core.fixtures.TestResponseInfo.minMaxString -import io.bkbn.kompendium.core.fixtures.TestResponseInfo.nullableField -import io.bkbn.kompendium.core.fixtures.TestResponseInfo.nullableNested -import io.bkbn.kompendium.core.fixtures.TestResponseInfo.regexString -import io.bkbn.kompendium.core.fixtures.TestResponseInfo.requiredParam -import io.bkbn.kompendium.core.metadata.RequestInfo -import io.bkbn.kompendium.core.metadata.ResponseInfo -import io.bkbn.kompendium.core.metadata.method.GetInfo -import io.bkbn.kompendium.core.metadata.method.PostInfo -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.response.respondText -import io.ktor.routing.route -import io.ktor.routing.routing -import java.time.Instant - -fun Application.notarizedGetWithNotarizedException() { - routing { - route("/test") { - notarizedGet(TestResponseInfo.testGetWithException) { - error("something terrible has happened!") - } - } - } -} - -fun Application.notarizedGetWithMultipleThrowables() { - routing { - route("/test") { - notarizedGet(TestResponseInfo.testGetWithMultipleExceptions) { - error("something terrible has happened!") - } - } - } -} - -fun Application.notarizedGetWithPolymorphicErrorResponse() { - routing { - route(DEFAULT_TEST_ENDPOINT) { - notarizedGet(TestResponseInfo.testGetWithPolymorphicException) { - error("something terrible has happened!") - } - } - } -} - -fun Application.notarizedGetWithGenericErrorResponse() { - routing { - route(DEFAULT_TEST_ENDPOINT) { - notarizedGet(TestResponseInfo.testGetWithGenericException) { - error("something terrible has happened!") - } - } - } -} - -fun Application.notarizedGetModule() { - routing { - route("/test") { - notarizedGet(TestResponseInfo.testGetInfo) { - call.respondText { "hey dude ‼️ congratz on the get request" } - } - } - } -} - -fun Application.notarizedPostModule() { - routing { - route("/test") { - notarizedPost(TestResponseInfo.testPostInfo) { - call.respondText { "hey dude ✌️ congratz on the post request" } - } - } - } -} - -fun Application.notarizedDeleteModule() { - routing { - route("/test") { - notarizedDelete(TestResponseInfo.testDeleteInfo) { - call.respond(HttpStatusCode.NoContent) - } - } - } -} - -fun Application.notarizedPatchModule() { - routing { - route("/test") { - notarizedPatch(TestResponseInfo.testPatchInfo) { - call.respondText { "hey dude ✌️ congratz on the patch request" } - } - } - } -} - -fun Application.notarizedHeadModule() { - routing { - route("/test") { - notarizedHead(TestResponseInfo.testHeadInfo) { - call.response.status(HttpStatusCode.OK) - } - } - } -} - -fun Application.notarizedOptionsModule() { - routing { - route("/test") { - notarizedOptions(TestResponseInfo.testOptionsInfo) { - call.response.status(HttpStatusCode.OK) - } - } - } -} - -fun Application.notarizedPutModule() { - routing { - route("/test") { - notarizedPut(TestResponseInfo.testPutInfoAlso) { - call.respondText { "hey pal 🌝 whatcha doin' here?" } - } - } - } -} - -fun Application.pathParsingTestModule() { - routing { - route("/this") { - route("/is") { - route("/a") { - route("/complex") { - route("path") { - route("with/an/{id}") { - notarizedGet(TestResponseInfo.testGetInfo) { - call.respondText { "Aww you followed this whole route 🥺" } - } - } - } - } - } - } - } - } -} - -fun Application.rootModule() { - routing { - route("/") { - notarizedGet(TestResponseInfo.testGetInfo) { - call.respondText { "☎️🏠🌲" } - } - } - } -} - -fun Application.nestedUnderRootModule() { - routing { - route("/") { - route("/testerino") { - notarizedGet(TestResponseInfo.testGetInfo) { - call.respondText { "🤔🔥" } - } - } - } - } -} - -fun Application.trailingSlash() { - routing { - route("/test") { - route("/") { - notarizedGet(TestResponseInfo.testGetInfo) { - call.respondText { "🙀👾" } - } - } - } - } -} - -fun Application.returnsList() { - routing { - route("/test") { - notarizedGet(TestResponseInfo.testGetInfoAgain) { - call.respondText { "hey dude ur doing amazing work!" } - } - } - } -} - -fun Application.complexType() { - routing { - route("/test") { - notarizedPut(TestResponseInfo.testPutInfo) { - call.respondText { "heya" } - } - } - } -} - -fun Application.primitives() { - routing { - route("/test") { - notarizedPut(TestResponseInfo.testPutInfoAgain) { - call.respondText { "heya" } - } - } - } -} - -fun Application.withExamples() { - routing { - route("/test/examples") { - notarizedPost( - info = PostInfo( - summary = "Example Parameters", - description = "A test for setting parameter examples", - requestInfo = RequestInfo( - description = "Test", - examples = mapOf( - "one" to TestRequest(fieldName = TestNested(nesty = "hey"), b = 4.0, aaa = emptyList()), - "two" to TestRequest(fieldName = TestNested(nesty = "hello"), b = 3.8, aaa = listOf(31324234)) - ) - ), - responseInfo = ResponseInfo( - status = HttpStatusCode.Created, - description = "nice", - examples = mapOf("test" to TestResponse(c = "spud")) - ), - ) - ) { - call.respond(HttpStatusCode.OK) - } - } - } -} - -fun Application.withDefaultParameter() { - routing { - route("/test") { - notarizedGet( - info = GetInfo( - summary = "Testing Default Params", - description = "Should have a default parameter value", - responseInfo = ResponseInfo( - HttpStatusCode.OK, - "A good response" - ) - ) - ) { - call.respond(TestResponse("hey")) - } - } - } -} - -fun Application.withOperationId() { - routing { - route("/test") { - notarizedGet( - info = TestResponseInfo.testGetInfo.copy(operationId = "getTest") - ) { - call.respond(HttpStatusCode.OK) - } - } - } -} - -fun Application.nonRequiredParamsGet() { - routing { - route("/test/optional") { - notarizedGet(TestResponseInfo.testOptionalParams) { - call.respond(HttpStatusCode.OK) - } - } - } -} - -fun Application.polymorphicResponse() { - routing { - route("/test/polymorphic") { - notarizedGet(TestResponseInfo.polymorphicResponse) { - call.respond(HttpStatusCode.OK, SimpleGibbit("hey")) - } - } - } -} - -fun Application.polymorphicCollectionResponse() { - routing { - route("/test/polymorphiclist") { - notarizedGet(TestResponseInfo.polymorphicListResponse) { - call.respond(HttpStatusCode.OK, listOf(SimpleGibbit("hi"))) - } - } - } -} - -fun Application.polymorphicMapResponse() { - routing { - route("/test/polymorphicmap") { - notarizedGet(TestResponseInfo.polymorphicMapResponse) { - call.respond(HttpStatusCode.OK, listOf(SimpleGibbit("hi"))) - } - } - } -} - -fun Application.polymorphicInterfaceResponse() { - routing { - route("/test/polymorphicmap") { - notarizedGet(TestResponseInfo.polymorphicInterfaceResponse) { - call.respond(HttpStatusCode.OK, listOf(SimpleGibbit("hi"))) - } - } - } -} - -fun Application.genericPolymorphicResponse() { - routing { - route("/test/polymorphic") { - notarizedGet(TestResponseInfo.genericPolymorphicResponse) { - call.respond(HttpStatusCode.OK, Gibbity("hey")) - } - } - } -} - -fun Application.genericPolymorphicResponseMultipleImpls() { - routing { - route("/test/polymorphic") { - notarizedGet(TestResponseInfo.genericPolymorphicResponse) { - call.respond(HttpStatusCode.OK, Gibbity("hey")) - } - } - route("/test/also/poly") { - notarizedGet(TestResponseInfo.anotherGenericPolymorphicResponse) { - call.respond(HttpStatusCode.OK, Bibbity("test", ComplexGibbit("nice", 1))) - } - } - } -} - -fun Application.undeclaredType() { - routing { - route("/test/polymorphic") { - notarizedGet(TestResponseInfo.undeclaredResponseType) { - call.respond(HttpStatusCode.OK, Mysterious("hi")) - } - } - } -} - -fun Application.headerParameter() { - routing { - route("/test/with_header") { - notarizedGet(TestResponseInfo.headerParam) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.simpleGenericResponse() { - routing { - route("/test/polymorphic") { - notarizedGet(TestResponseInfo.genericResponse) { - call.respond(HttpStatusCode.OK, Gibbity("hey")) - } - } - } -} - -fun Application.overrideFieldInfo() { - routing { - route("/test/field_override") { - notarizedGet(TestResponseInfo.fieldOverride) { - call.respond(HttpStatusCode.OK, TestFieldOverride(true)) - } - } - } -} - -fun Application.simpleRecursive() { - routing { - route("/test/simple_recursive") { - notarizedGet(TestResponseInfo.simpleRecursive) { - call.respond(HttpStatusCode.OK) - } - } - } -} - -fun Application.nullableNestedObject() { - routing { - route("/nullable/nested") { - notarizedPost(nullableNested) { - call.respond(HttpStatusCode.OK) - } - } - } -} - -fun Application.nullableEnumField() { - routing { - route("/nullable/enum") { - notarizedGet(TestResponseInfo.nullableEnumField) { - call.respond(HttpStatusCode.OK) - } - } - } -} - -fun Application.constrainedIntInfo() { - routing { - route("/test/constrained_int") { - notarizedGet(TestResponseInfo.minMaxInt) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.constrainedDoubleInfo() { - routing { - route("/test/constrained_int") { - notarizedGet(TestResponseInfo.minMaxDouble) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.exclusiveMinMax() { - routing { - route("/test/constrained_int") { - notarizedGet(TestResponseInfo.exclusiveMinMax) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.requiredParameter() { - routing { - route("/test/required_param") { - notarizedGet(requiredParam) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.defaultParameter() { - routing { - route("/test/required_param") { - notarizedGet(defaultParam) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.defaultField() { - routing { - route("/test/required_param") { - notarizedPost(defaultField) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.nullableField() { - routing { - route("/test/required_param") { - notarizedPost(nullableField) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.formattedParam() { - routing { - route("/test/required_param") { - notarizedGet(formattedParam) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.minMaxString() { - routing { - route("/test/required_param") { - notarizedGet(minMaxString) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.regexString() { - routing { - route("/test/required_param") { - notarizedGet(regexString) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.dateTimeString() { - routing { - route("/test/date_time_format") { - notarizedGet(dateTimeString) { - call.respond(HttpStatusCode.OK, DateTimeString(Instant.now())) - } - } - } -} - -fun Application.minMaxArray() { - routing { - route("/test/required_param") { - notarizedGet(TestResponseInfo.minMaxArray) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.uniqueArray() { - routing { - route("/test/required_param") { - notarizedGet(TestResponseInfo.uniqueArray) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.multipleOfInt() { - routing { - route("/test/required_param") { - notarizedGet(TestResponseInfo.multipleOfInt) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.multipleOfDouble() { - routing { - route("/test/required_param") { - notarizedGet(TestResponseInfo.multipleOfDouble) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.freeFormField() { - routing { - route("/test/required_param") { - notarizedGet(TestResponseInfo.freeFormField) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.freeFormObject() { - routing { - route("/test/required_param") { - notarizedGet(TestResponseInfo.freeFormObject) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.minMaxFreeForm() { - routing { - route("/test/required_param") { - notarizedGet(TestResponseInfo.minMaxFreeForm) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} - -fun Application.exampleParams() { - routing { - route("/test/{a}") { - notarizedGet(TestResponseInfo.exampleParams) { - call.respondText { "Hi 🌊" } - } - } - } -} - -fun Application.formattedType() { - routing { - route("/test/formatted_type") { - notarizedPost(TestResponseInfo.formattedArrayItemType) { - call.respond(HttpStatusCode.OK, TestResponse("hi")) - } - } - } -} diff --git a/kompendium-core/src/test/resources/crazy_polymorphic_example.json b/kompendium-core/src/test/resources/crazy_polymorphic_example.json deleted file mode 100644 index 20cd409a1..000000000 --- a/kompendium-core/src/test/resources/crazy_polymorphic_example.json +++ /dev/null @@ -1,257 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/polymorphic": { - "get": { - "tags": [], - "summary": "More flibbity", - "description": "Polymorphic with generics", - "parameters": [], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/Gibbity-TestNested" - }, - { - "$ref": "#/components/schemas/Bibbity-TestNested" - } - ] - } - } - } - } - }, - "deprecated": false - } - }, - "/test/also/poly": { - "get": { - "tags": [], - "summary": "The Most Flibbity", - "description": "Polymorphic with generics but like... crazier", - "parameters": [], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/Gibbity-FlibbityGibbit" - }, - { - "$ref": "#/components/schemas/Bibbity-FlibbityGibbit" - } - ] - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "Gibbity-TestNested": { - "properties": { - "a": { - "$ref": "#/components/schemas/TestNested" - } - }, - "required": [ - "a" - ], - "type": "object" - }, - "TestNested": { - "properties": { - "nesty": { - "type": "string" - } - }, - "required": [ - "nesty" - ], - "type": "object" - }, - "Bibbity-TestNested": { - "properties": { - "b": { - "type": "string" - }, - "f": { - "$ref": "#/components/schemas/TestNested" - } - }, - "required": [ - "b", - "f" - ], - "type": "object" - }, - "Gibbity-FlibbityGibbit": { - "properties": { - "a": { - "anyOf": [ - { - "properties": { - "a": { - "type": "string" - }, - "z": { - "type": "string" - } - }, - "required": [ - "a" - ], - "type": "object" - }, - { - "properties": { - "b": { - "type": "string" - }, - "c": { - "format": "int32", - "type": "integer" - }, - "z": { - "type": "string" - } - }, - "required": [ - "b", - "c" - ], - "type": "object" - } - ] - } - }, - "required": [ - "a" - ], - "type": "object" - }, - "SimpleGibbit": { - "properties": { - "a": { - "type": "string" - }, - "z": { - "type": "string" - } - }, - "required": [ - "a" - ], - "type": "object" - }, - "ComplexGibbit": { - "properties": { - "b": { - "type": "string" - }, - "c": { - "format": "int32", - "type": "integer" - }, - "z": { - "type": "string" - } - }, - "required": [ - "b", - "c" - ], - "type": "object" - }, - "Bibbity-FlibbityGibbit": { - "properties": { - "b": { - "type": "string" - }, - "f": { - "anyOf": [ - { - "properties": { - "a": { - "type": "string" - }, - "z": { - "type": "string" - } - }, - "required": [ - "a" - ], - "type": "object" - }, - { - "properties": { - "b": { - "type": "string" - }, - "c": { - "format": "int32", - "type": "integer" - }, - "z": { - "type": "string" - } - }, - "required": [ - "b", - "c" - ], - "type": "object" - } - ] - } - }, - "required": [ - "b", - "f" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/default_field.json b/kompendium-core/src/test/resources/default_field.json deleted file mode 100644 index 619d9420a..000000000 --- a/kompendium-core/src/test/resources/default_field.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/required_param": { - "post": { - "tags": [], - "summary": "default param", - "description": "Cool stuff", - "parameters": [], - "requestBody": { - "description": "cool", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DefaultField" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TestResponse" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "DefaultField": { - "properties": { - "a": { - "type": "string" - }, - "b": { - "format": "int32", - "type": "integer" - } - }, - "required": [ - "b" - ], - "type": "object" - }, - "TestResponse": { - "properties": { - "c": { - "type": "string" - } - }, - "required": [ - "c" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/example_parameters.json b/kompendium-core/src/test/resources/example_parameters.json deleted file mode 100644 index d3e44336a..000000000 --- a/kompendium-core/src/test/resources/example_parameters.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/{a}": { - "get": { - "tags": [], - "summary": "param stuff", - "description": "Cool stuff", - "parameters": [ - { - "name": "a", - "in": "path", - "schema": { - "type": "string" - }, - "required": true, - "deprecated": false, - "examples": { - "Testerino": { - "value": "a" - }, - "Testerina": { - "value": "b" - } - } - }, - { - "name": "aa", - "in": "query", - "schema": { - "format": "int32", - "type": "integer" - }, - "required": true, - "deprecated": false, - "examples": { - "Wowza": { - "value": 6 - } - } - } - ], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TestResponse" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "TestResponse": { - "properties": { - "c": { - "type": "string" - } - }, - "required": [ - "c" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/field_override.json b/kompendium-core/src/test/resources/field_override.json deleted file mode 100644 index 693526eaa..000000000 --- a/kompendium-core/src/test/resources/field_override.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/field_override": { - "get": { - "tags": [], - "summary": "A Response with a spicy field", - "description": "Important info within!", - "parameters": [], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TestFieldOverride" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "TestFieldOverride": { - "properties": { - "real_name": { - "type": "boolean" - } - }, - "required": [ - "real_name" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/formatted_array_item_type.json b/kompendium-core/src/test/resources/formatted_array_item_type.json deleted file mode 100644 index 9e6f58c97..000000000 --- a/kompendium-core/src/test/resources/formatted_array_item_type.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/formatted_type": { - "post": { - "tags": [], - "summary": "formatted array item type", - "description": "Cool stuff", - "parameters": [], - "requestBody": { - "description": "cool", - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/FormattedArrayItemType" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TestResponse" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "FormattedArrayItemType": { - "properties": { - "a": { - "items": { - "type": "string", - "format": "binary" - }, - "type": "array" - } - }, - "required": [ - "a" - ], - "type": "object" - }, - "TestResponse": { - "properties": { - "c": { - "type": "string" - } - }, - "required": [ - "c" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/free_form_field.json b/kompendium-core/src/test/resources/free_form_field.json deleted file mode 100644 index 8eae097a3..000000000 --- a/kompendium-core/src/test/resources/free_form_field.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/required_param": { - "get": { - "tags": [], - "summary": "required param", - "description": "Cool stuff", - "parameters": [], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FreeFormData" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "FreeFormData": { - "properties": { - "data": { - "additionalProperties": true, - "type": "object" - } - }, - "required": [ - "data" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/free_form_object.json b/kompendium-core/src/test/resources/free_form_object.json deleted file mode 100644 index 053137806..000000000 --- a/kompendium-core/src/test/resources/free_form_object.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/required_param": { - "get": { - "tags": [], - "summary": "required param", - "description": "Cool stuff", - "parameters": [], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "additionalProperties": true, - "type": "object" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": {}, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/min_max_free_form.json b/kompendium-core/src/test/resources/min_max_free_form.json deleted file mode 100644 index 423664e9b..000000000 --- a/kompendium-core/src/test/resources/min_max_free_form.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/required_param": { - "get": { - "tags": [], - "summary": "required param", - "description": "Cool stuff", - "parameters": [], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MinMaxFreeForm" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "MinMaxFreeForm": { - "properties": { - "data": { - "minProperties": 5, - "maxProperties": 10, - "additionalProperties": true, - "type": "object" - } - }, - "required": [ - "data" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/min_max_int_field.json b/kompendium-core/src/test/resources/min_max_int_field.json deleted file mode 100644 index 217089441..000000000 --- a/kompendium-core/src/test/resources/min_max_int_field.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/constrained_int": { - "get": { - "tags": [], - "summary": "Constrained int field", - "description": "Cool stuff", - "parameters": [], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MinMaxInt" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "MinMaxInt": { - "properties": { - "a": { - "format": "int32", - "type": "integer", - "minimum": 5, - "maximum": 100, - "exclusiveMinimum": false, - "exclusiveMaximum": false - } - }, - "required": [ - "a" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/min_max_string.json b/kompendium-core/src/test/resources/min_max_string.json deleted file mode 100644 index c48873fd0..000000000 --- a/kompendium-core/src/test/resources/min_max_string.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/required_param": { - "get": { - "tags": [], - "summary": "required param", - "description": "Cool stuff", - "parameters": [], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MinMaxString" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "MinMaxString": { - "properties": { - "a": { - "type": "string", - "minLength": 42, - "maxLength": 1337 - } - }, - "required": [ - "a" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/multiple_of_double.json b/kompendium-core/src/test/resources/multiple_of_double.json deleted file mode 100644 index 3a6df7403..000000000 --- a/kompendium-core/src/test/resources/multiple_of_double.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/required_param": { - "get": { - "tags": [], - "summary": "required param", - "description": "Cool stuff", - "parameters": [], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MultipleOfDouble" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "MultipleOfDouble": { - "properties": { - "a": { - "format": "double", - "type": "number", - "multipleOf": 2.5 - } - }, - "required": [ - "a" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/multiple_of_int.json b/kompendium-core/src/test/resources/multiple_of_int.json deleted file mode 100644 index 4e8aca700..000000000 --- a/kompendium-core/src/test/resources/multiple_of_int.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/required_param": { - "get": { - "tags": [], - "summary": "required param", - "description": "Cool stuff", - "parameters": [], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MultipleOfInt" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "MultipleOfInt": { - "properties": { - "a": { - "format": "int32", - "type": "integer", - "multipleOf": 5 - } - }, - "required": [ - "a" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/notarized_delete.json b/kompendium-core/src/test/resources/notarized_delete.json deleted file mode 100644 index 7cdfe569f..000000000 --- a/kompendium-core/src/test/resources/notarized_delete.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "openapi" : "3.0.3", - "info" : { - "title" : "Test API", - "version" : "1.33.7", - "description" : "An amazing, fully-ish 😉 generated API spec", - "termsOfService" : "https://example.com", - "contact" : { - "name" : "Homer Simpson", - "url" : "https://gph.is/1NPUDiM", - "email" : "chunkylover53@aol.com" - }, - "license" : { - "name" : "MIT", - "url" : "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers" : [ { - "url" : "https://myawesomeapi.com", - "description" : "Production instance of my API" - }, { - "url" : "https://staging.myawesomeapi.com", - "description" : "Where the fun stuff happens" - } ], - "paths" : { - "/test" : { - "delete" : { - "tags" : [ ], - "summary" : "Test delete endpoint", - "description" : "testing my deletes", - "parameters" : [ { - "name" : "a", - "in" : "path", - "schema" : { - "type" : "string" - }, - "required" : true, - "deprecated" : false - }, { - "name" : "aa", - "in" : "query", - "schema" : { - "format" : "int32", - "type" : "integer" - }, - "required" : true, - "deprecated" : false - } ], - "responses" : { - "204" : { - "description" : "A Successful Endeavor" - } - }, - "deprecated" : false - } - } - }, - "components" : { - "schemas": {}, - "securitySchemes" : { } - }, - "security" : [ ], - "tags" : [ ] -} diff --git a/kompendium-core/src/test/resources/nullable_field.json b/kompendium-core/src/test/resources/nullable_field.json deleted file mode 100644 index dfd4c8e63..000000000 --- a/kompendium-core/src/test/resources/nullable_field.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/required_param": { - "post": { - "tags": [], - "summary": "default param", - "description": "Cool stuff", - "parameters": [], - "requestBody": { - "description": "cool", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NullableField" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TestResponse" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "NullableField": { - "properties": { - "a": { - "type": "string", - "nullable": true - } - }, - "required": [ - "a" - ], - "type": "object" - }, - "TestResponse": { - "properties": { - "c": { - "type": "string" - } - }, - "required": [ - "c" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/nullable_fields.json b/kompendium-core/src/test/resources/nullable_fields.json deleted file mode 100644 index b8e9c8e16..000000000 --- a/kompendium-core/src/test/resources/nullable_fields.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/nullable/nested": { - "post": { - "tags": [], - "summary": "Has a bunch of nullable fields", - "description": "Should still work!", - "parameters": [], - "requestBody": { - "description": "Cool", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProfileUpdateRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TestResponse" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "ProfileUpdateRequest": { - "properties": { - "metadata": { - "$ref": "#/components/schemas/ProfileMetadataUpdateRequest" - }, - "mood": { - "type": "string", - "nullable": true - }, - "viewCount": { - "format": "int64", - "type": "integer", - "nullable": true - } - }, - "required": [ - "mood", - "viewCount", - "metadata" - ], - "type": "object" - }, - "ProfileMetadataUpdateRequest": { - "properties": { - "isPrivate": { - "type": "boolean", - "nullable": true - }, - "otherThing": { - "type": "string", - "nullable": true - } - }, - "required": [ - "isPrivate", - "otherThing" - ], - "type": "object" - }, - "TestResponse": { - "properties": { - "c": { - "type": "string" - } - }, - "required": [ - "c" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/petstore.json b/kompendium-core/src/test/resources/petstore.json deleted file mode 100644 index 57e5b28b3..000000000 --- a/kompendium-core/src/test/resources/petstore.json +++ /dev/null @@ -1,199 +0,0 @@ -{ - "openapi" : "3.0.3", - "info" : { - "title" : "Swagger Petstore", - "version" : "1.0.0", - "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", - "email" : "apiteam@swagger.io" - }, - "license" : { - "name" : "Apache 2.0", - "url" : "http://www.apache.org/licenses/LICENSE-2.0.html" - } - }, - "servers" : [ { - "url" : "https://petstore.swagger.io/v2" - }, { - "url" : "http://petstore.swagger.io/v2" - } ], - "paths" : { - "/pet" : { - "put" : { - "tags" : [ "pet" ], - "summary" : "Update an existing pet", - "operationId" : "updatePet", - "requestBody" : { - "description" : "Pet object that needs to be added to the store", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Pet" - } - }, - "application/xml" : { - "schema" : { - "$ref" : "#/components/schemas/Pet" - } - } - }, - "required" : true - }, - "responses" : { - "400" : { - "description" : "Invalid ID supplied", - "content" : { } - }, - "404" : { - "description" : "Pet not found", - "content" : { } - }, - "405" : { - "description" : "Validation exception", - "content" : { } - } - }, - "deprecated" : false, - "security" : [ { - "petstore_auth" : [ "write:pets", "read:pets" ] - } ], - "x-codegen-request-body-name" : "body" - }, - "post" : { - "tags" : [ "pet" ], - "summary" : "Add a new pet to the store", - "operationId" : "addPet", - "requestBody" : { - "description" : "Pet object that needs to be added to the store", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/Pet" - } - }, - "application/xml" : { - "schema" : { - "$ref" : "#/components/schemas/Pet" - } - } - }, - "required" : false - }, - "responses" : { - "405" : { - "description" : "Invalid Input", - "content" : { } - } - }, - "deprecated" : false, - "security" : [ { - "petstore_auth" : [ "write:pets", "read:pets" ] - } ], - "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", - "schema" : { - "items" : { - "default" : "available", - "enum" : [ "available", "pending", "sold" ], - "type" : "string" - }, - "type" : "array" - }, - "description" : "Status values that need to be considered for filter", - "required" : true, - "deprecated" : false, - "style" : "form", - "explode" : true - } ], - "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" : { - "schemas" : { }, - "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", - "externalDocs" : { - "url" : "http://swagger.io", - "description" : "Find out more" - } - }, { - "name" : "store", - "description" : "Access to Petstore orders" - }, { - "name" : "user", - "description" : "Operations about user", - "externalDocs" : { - "url" : "http://swagger.io", - "description" : "Find out more about our store" - } - } ], - "externalDocs" : { - "url" : "http://swagger.io", - "description" : "Find out more about Swagger" - } -} diff --git a/kompendium-core/src/test/resources/query_with_default_parameter.json b/kompendium-core/src/test/resources/query_with_default_parameter.json deleted file mode 100644 index 85dcdd60e..000000000 --- a/kompendium-core/src/test/resources/query_with_default_parameter.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test": { - "get": { - "tags": [], - "summary": "Testing Default Params", - "description": "Should have a default parameter value", - "parameters": [ - { - "name": "a", - "in": "query", - "schema": { - "format": "int32", - "type": "integer", - "default": 100 - }, - "required": false, - "deprecated": false - }, - { - "name": "b", - "in": "path", - "schema": { - "type": "string", - "nullable": true - }, - "required": false, - "deprecated": false - }, - { - "name": "c", - "in": "path", - "schema": { - "type": "boolean" - }, - "required": true, - "deprecated": false - } - ], - "responses": { - "200": { - "description": "A good response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TestResponse" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "TestResponse": { - "properties": { - "c": { - "type": "string" - } - }, - "required": [ - "c" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/regex_string.json b/kompendium-core/src/test/resources/regex_string.json deleted file mode 100644 index 8b110d231..000000000 --- a/kompendium-core/src/test/resources/regex_string.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/required_param": { - "get": { - "tags": [], - "summary": "required param", - "description": "Cool stuff", - "parameters": [], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RegexString" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "RegexString": { - "properties": { - "a": { - "type": "string", - "pattern": "^\\d{3}-\\d{2}-\\d{4}$" - } - }, - "required": [ - "a" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/sealed_interface_response.json b/kompendium-core/src/test/resources/sealed_interface_response.json deleted file mode 100644 index 363838de7..000000000 --- a/kompendium-core/src/test/resources/sealed_interface_response.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/polymorphicmap": { - "get": { - "tags": [], - "summary": "Come on and slam", - "description": "and welcome to the jam", - "parameters": [], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/OneJamma" - }, - { - "$ref": "#/components/schemas/AnothaJamma" - }, - { - "$ref": "#/components/schemas/InsaneJamma" - } - ] - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "OneJamma": { - "properties": { - "a": { - "format": "int32", - "type": "integer" - } - }, - "required": [ - "a" - ], - "type": "object" - }, - "AnothaJamma": { - "properties": { - "b": { - "format": "float", - "type": "number" - } - }, - "required": [ - "b" - ], - "type": "object" - }, - "InsaneJamma": { - "properties": { - "c": { - "anyOf": [ - { - "properties": { - "a": { - "format": "int32", - "type": "integer" - } - }, - "required": [ - "a" - ], - "type": "object" - }, - { - "properties": { - "b": { - "format": "float", - "type": "number" - } - }, - "required": [ - "b" - ], - "type": "object" - }, - { - "$ref": "#/components/schemas/InsaneJamma" - } - ] - } - }, - "required": [ - "c" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/simple_recursive.json b/kompendium-core/src/test/resources/simple_recursive.json deleted file mode 100644 index ac93c586c..000000000 --- a/kompendium-core/src/test/resources/simple_recursive.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/simple_recursive": { - "get": { - "tags": [], - "summary": "Simple recursive example", - "description": "Pretty neato!", - "parameters": [], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ColumnSchema" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "ColumnSchema": { - "properties": { - "description": { - "type": "string" - }, - "mode": { - "$ref": "#/components/schemas/ColumnMode" - }, - "name": { - "type": "string" - }, - "subColumns": { - "items": { - "$ref": "#/components/schemas/ColumnSchema" - }, - "type": "array" - }, - "type": { - "type": "string" - } - }, - "required": [ - "name", - "type", - "description", - "mode" - ], - "type": "object" - }, - "ColumnMode": { - "enum": [ - "NULLABLE", - "REQUIRED", - "REPEATED" - ], - "type": "string" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/undeclared_field.json b/kompendium-core/src/test/resources/undeclared_field.json deleted file mode 100644 index 0ee4c3e46..000000000 --- a/kompendium-core/src/test/resources/undeclared_field.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/polymorphic": { - "get": { - "tags": [], - "summary": "spooky class", - "description": "break this glass in scenario of emergency", - "parameters": [], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Mysterious" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "Mysterious": { - "properties": { - "nowYouSeeMe": { - "type": "string" - }, - "nowYouDont": { - "$ref": "#/components/schemas/Hehe" - } - }, - "required": [ - "nowYouSeeMe" - ], - "type": "object" - }, - "Hehe": { - "enum": [ - "HAHA", - "HOHO" - ], - "type": "string" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/test/resources/unique_array.json b/kompendium-core/src/test/resources/unique_array.json deleted file mode 100644 index eb2cd584a..000000000 --- a/kompendium-core/src/test/resources/unique_array.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Test API", - "version": "1.33.7", - "description": "An amazing, fully-ish 😉 generated API spec", - "termsOfService": "https://example.com", - "contact": { - "name": "Homer Simpson", - "url": "https://gph.is/1NPUDiM", - "email": "chunkylover53@aol.com" - }, - "license": { - "name": "MIT", - "url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE" - } - }, - "servers": [ - { - "url": "https://myawesomeapi.com", - "description": "Production instance of my API" - }, - { - "url": "https://staging.myawesomeapi.com", - "description": "Where the fun stuff happens" - } - ], - "paths": { - "/test/required_param": { - "get": { - "tags": [], - "summary": "required param", - "description": "Cool stuff", - "parameters": [], - "responses": { - "200": { - "description": "A successful endeavor", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UniqueArray" - } - } - } - } - }, - "deprecated": false - } - } - }, - "components": { - "schemas": { - "UniqueArray": { - "properties": { - "a": { - "items": { - "format": "int32", - "type": "integer" - }, - "uniqueItems": true, - "type": "array" - } - }, - "required": [ - "a" - ], - "type": "object" - } - }, - "securitySchemes": {} - }, - "security": [], - "tags": [] -} diff --git a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestHelpers.kt b/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestHelpers.kt deleted file mode 100644 index 817d1ae5c..000000000 --- a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestHelpers.kt +++ /dev/null @@ -1,155 +0,0 @@ -package io.bkbn.kompendium.core.fixtures - -import io.bkbn.kompendium.core.Kompendium -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 { - - private const val OPEN_API_ENDPOINT = "/openapi.json" - const val DEFAULT_TEST_ENDPOINT = "/test" - - fun getFileSnapshot(fileName: String): String { - val snapshotPath = "src/test/resources" - val file = File("$snapshotPath/$fileName") - return file.readText() - } - - /** - * Performs the baseline expected tests on an OpenAPI result. Confirms that the endpoint - * exists as expected, and that the content matches the expected blob found in the specified file - * @param snapshotName The snapshot file to retrieve from the resources folder - */ - fun TestApplicationEngine.compareOpenAPISpec(snapshotName: String) { - // act - handleRequest(HttpMethod.Get, OPEN_API_ENDPOINT).apply { - // assert - response shouldHaveStatus HttpStatusCode.OK - response.content shouldNotBe null - response.content!! shouldEqualJson getFileSnapshot(snapshotName) - } - } - - /** - * 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. 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 openApiTestAllSerializers(snapshotName: String, moduleFunction: Application.() -> Unit) { - openApiTestAllSerializers(snapshotName, moduleFunction) {} - } - - fun openApiTestAllSerializers( - snapshotName: String, moduleFunction: Application.() -> Unit, - kompendiumConfigurer: Kompendium.Configuration.() -> Unit - ) { - openApiTest(snapshotName, SupportedSerializer.KOTLINX, moduleFunction, kompendiumConfigurer) - openApiTest(snapshotName, SupportedSerializer.JACKSON, moduleFunction, kompendiumConfigurer) - openApiTest(snapshotName, SupportedSerializer.GSON, moduleFunction, kompendiumConfigurer) - } - - private fun openApiTest( - snapshotName: String, - serializer: SupportedSerializer, - moduleFunction: Application.() -> Unit, - kompendiumConfigurer: Kompendium.Configuration.() -> Unit - ) { - withApplication(createTestEnvironment()) { - moduleFunction(application.apply { - kompendium(kompendiumConfigurer) - 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) - } - } - - /** - * This allows us to easily confirm that an API endpoints functionality is not impacted by notarization. - * @param httpMethod The HTTP operation that we wish to perform - * @param endpoint The relative endpoint we wish to hit - * @param expectedResponse The expected response content of the operation - * @param expectedStatusCode The expected status code of the operation - */ - fun apiFunctionalityTest( - expectedResponse: String?, - endpoint: String = DEFAULT_TEST_ENDPOINT, - httpMethod: HttpMethod = HttpMethod.Get, - expectedStatusCode: HttpStatusCode = HttpStatusCode.OK, - moduleFunction: Application.() -> Unit - ) { - withApplication(createTestEnvironment()) { - moduleFunction(application.apply { - kompendium() - docs() - jacksonConfigModule() - }) - when (expectedResponse) { - null -> testWithNoResponse(httpMethod, endpoint, expectedStatusCode) - else -> testWithResponse(httpMethod, expectedResponse, endpoint, expectedStatusCode) - } - } - } - - private fun TestApplicationEngine.testWithResponse( - httpMethod: HttpMethod, - expectedResponse: String, - endpoint: String, - expectedStatusCode: HttpStatusCode, - ) { - handleRequest(httpMethod, endpoint).apply { - // assert - response shouldHaveStatus expectedStatusCode - response.content shouldNotBe null - response.content shouldBe expectedResponse - } - } - - private fun TestApplicationEngine.testWithNoResponse( - httpMethod: HttpMethod, - endpoint: String, - expectedStatusCode: HttpStatusCode, - ) { - handleRequest(httpMethod, endpoint).apply { - // assert - response shouldHaveStatus expectedStatusCode - response.content shouldBe null - } - } -} diff --git a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModels.kt b/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModels.kt deleted file mode 100644 index f237f293f..000000000 --- a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModels.kt +++ /dev/null @@ -1,274 +0,0 @@ -package io.bkbn.kompendium.core.fixtures - -import io.bkbn.kompendium.annotations.Field -import io.bkbn.kompendium.annotations.FreeFormObject -import io.bkbn.kompendium.annotations.Param -import io.bkbn.kompendium.annotations.ParamType -import io.bkbn.kompendium.annotations.UndeclaredField -import io.bkbn.kompendium.annotations.constraint.Format -import io.bkbn.kompendium.annotations.constraint.MaxItems -import io.bkbn.kompendium.annotations.constraint.MaxLength -import io.bkbn.kompendium.annotations.constraint.MaxProperties -import io.bkbn.kompendium.annotations.constraint.Maximum -import io.bkbn.kompendium.annotations.constraint.MinItems -import io.bkbn.kompendium.annotations.constraint.MinLength -import io.bkbn.kompendium.annotations.constraint.MinProperties -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 -import java.time.Instant -import java.util.UUID - -data class TestSimpleModel(val a: String, val b: Int) - -data class TestBigNumberModel(val a: BigDecimal, val b: BigInteger) - -data class TestByteArrayModel(val a: ByteArray) - -data class TestNestedModel(val inner: TestSimpleModel) - -data class TestSimpleWithEnums(val a: String, val b: SimpleEnum) - -data class TestSimpleWithMap(val a: String, val b: Map) - -data class TestSimpleWithList(val a: Boolean, val b: List) - -data class TestSimpleWithEnumList(val a: Double, val b: List) - -data class TestInvalidMap(val a: Map) - -data class TestParams( - @Param(ParamType.PATH) val a: String, - @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, - val b: Double, - val aaa: List -) - -@Serializable -data class TestResponse(val c: String) - -@Serializable -enum class TestEnum { - YES, - NO -} - -@Serializable -data class NullableEnum(val a: TestEnum? = null) - -data class TestGeneric(val messy: String, val potato: T) - -data class TestCreatedResponse(val id: Int, val c: String) - -data class TestFieldOverride( - @Field(name = "real_name", description = "A Field that is super important!") - val b: Boolean -) - -data class MinMaxInt( - @Minimum("5") - @Maximum("100") - val a: Int -) - -data class MinMaxDouble( - @Minimum("5.5") - @Maximum("13.37") - val a: Double -) - -data class ExclusiveMinMax( - @Minimum("5", true) - @Maximum("100", true) - val a: Int -) - -data class FormattedString( - @Format("password") - @Param(ParamType.QUERY) - val a: String -) - -data class FormattedArrayItemType( - val a: List<@Format("binary") String> -) - -data class MinMaxString( - @MinLength(42) - @MaxLength(1337) - val a: String -) - -data class RegexString( - @Pattern("^\\d{3}-\\d{2}-\\d{4}\$") - val a: String -) - -data class DateTimeString( - val a: Instant -) - -data class MinMaxArray( - @MinItems(1) - @MaxItems(10) - val a: List -) - -data class UniqueArray( - @UniqueItems - val a: List -) - -data class MultipleOfInt( - @MultipleOf("5") - val a: Int -) - -data class MultipleOfDouble( - @MultipleOf("2.5") - val a: Double -) - -data class FreeFormData( - @FreeFormObject - val data: JsonElement -) - -@FreeFormObject -object AnythingGoesMan - -data class MinMaxFreeForm( - @FreeFormObject - @MinProperties(5) - @MaxProperties(10) - val data: JsonElement -) - - -data class RequiredParam( - @Param(ParamType.QUERY) - val a: String -) - -data class DefaultParam( - @Param(ParamType.QUERY) - val b: String = "heyo" -) - -data class DefaultField( - val a: String = "hi", - val b: Int -) - -data class NullableField( - val a: String? -) - -data class ComplexRequest( - val org: String, - @Field("amazing_field") - val amazingField: String, - val tables: List -) - -data class NestedComplexItem( - val name: String, - val alias: CustomAlias -) - -typealias CustomAlias = Map - -data class CrazyItem(val enumeration: SimpleEnum) - -enum class SimpleEnum { - ONE, - TWO -} - -data class DefaultParameter( - @Param(ParamType.QUERY) val a: Int = 100, - @Param(ParamType.PATH) val b: String?, - @Param(ParamType.PATH) val c: Boolean -) - -data class ExceptionResponse(val message: String) - -data class OptionalParams( - @Param(ParamType.QUERY) val required: String, - @Param(ParamType.QUERY) val notRequired: String? -) - -sealed class FlibbityGibbit { - abstract val z: String -} - -data class SimpleGibbit(val a: String, override val z: String = "z") : FlibbityGibbit() -data class ComplexGibbit(val b: String, val c: Int, override val z: String = "z") : FlibbityGibbit() - -sealed interface SlammaJamma - -data class OneJamma(val a: Int) : SlammaJamma -data class AnothaJamma(val b: Float) : SlammaJamma - -data class InsaneJamma(val c: SlammaJamma) : SlammaJamma - -sealed interface Flibbity - -data class Gibbity(val a: T) : Flibbity -data class Bibbity(val b: String, val f: T) : Flibbity - -enum class Hehe { - HAHA, - HOHO -} - -@UndeclaredField("nowYouDont", Hehe::class) -data class Mysterious(val nowYouSeeMe: String) - -data class HeaderNameTest( - @Param(type = ParamType.HEADER) val `X-UserEmail`: String -) - -enum class ColumnMode { - NULLABLE, - REQUIRED, - REPEATED -} - -data class ColumnSchema( - val name: String, - val type: String, - val description: String, - val mode: ColumnMode, - val subColumns: List = emptyList() -) - -@Serializable -public data class ProfileUpdateRequest( - public val mood: String?, - public val viewCount: Long?, - public val metadata: ProfileMetadataUpdateRequest? -) - - -@Serializable -public data class ProfileMetadataUpdateRequest( - public val isPrivate: Boolean?, - public val otherThing: String? -) diff --git a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModules.kt b/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModules.kt deleted file mode 100644 index b9cf20add..000000000 --- a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModules.kt +++ /dev/null @@ -1,35 +0,0 @@ -package io.bkbn.kompendium.core.fixtures - -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.databind.SerializationFeature -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.fixtures.TestSpecs.defaultSpec -import io.bkbn.kompendium.core.routes.redoc -import io.ktor.application.Application -import io.ktor.application.install -import io.ktor.features.ContentNegotiation -import io.ktor.http.ContentType -import io.ktor.jackson.jackson -import io.ktor.routing.routing - -fun Application.docs() { - routing { - redoc() - } -} - -fun Application.jacksonConfigModule() { - install(ContentNegotiation) { - jackson(ContentType.Application.Json) { - enable(SerializationFeature.INDENT_OUTPUT) - setSerializationInclusion(JsonInclude.Include.NON_NULL) - } - } -} - -fun Application.kompendium(configurer: Kompendium.Configuration.() -> Unit = {}) { - install(Kompendium) { - spec = defaultSpec() - configurer() - } -} diff --git a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestResponseInfo.kt b/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestResponseInfo.kt deleted file mode 100644 index 076071523..000000000 --- a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestResponseInfo.kt +++ /dev/null @@ -1,325 +0,0 @@ -package io.bkbn.kompendium.core.fixtures - -import io.bkbn.kompendium.core.metadata.ExceptionInfo -import io.bkbn.kompendium.core.metadata.ParameterExample -import io.bkbn.kompendium.core.metadata.RequestInfo -import io.bkbn.kompendium.core.metadata.ResponseInfo -import io.bkbn.kompendium.core.metadata.method.DeleteInfo -import io.bkbn.kompendium.core.metadata.method.GetInfo -import io.bkbn.kompendium.core.metadata.method.HeadInfo -import io.bkbn.kompendium.core.metadata.method.OptionsInfo -import io.bkbn.kompendium.core.metadata.method.PatchInfo -import io.bkbn.kompendium.core.metadata.method.PostInfo -import io.bkbn.kompendium.core.metadata.method.PutInfo -import io.ktor.http.ContentType -import io.ktor.http.HttpStatusCode -import kotlin.reflect.typeOf - -object TestResponseInfo { - val testGetResponse = ResponseInfo(HttpStatusCode.OK, "A Successful Endeavor") - private val testGetListResponse = - ResponseInfo>(HttpStatusCode.OK, "A Successful List-y Endeavor") - private val testPostResponse = ResponseInfo(HttpStatusCode.Created, "A Successful Endeavor") - private val testPatchResponse = ResponseInfo(HttpStatusCode.Created, "A Successful Endeavor") - private val testPostResponseAgain = ResponseInfo(HttpStatusCode.Created, "A Successful Endeavor") - private val testDeleteResponse = - ResponseInfo(HttpStatusCode.NoContent, "A Successful Endeavor", mediaTypes = emptyList()) - private val testRequest = RequestInfo("A Test request") - private val testRequestAgain = RequestInfo("A Test request") - private val complexRequest = RequestInfo("A Complex request") - val testGetInfo = GetInfo( - summary = "Another get test", - description = "testing more", - responseInfo = testGetResponse - ) - val testGetInfoAgain = GetInfo>( - summary = "Another get test", - description = "testing more", - responseInfo = testGetListResponse - ) - private val accessDeniedResponse = ExceptionInfo( - responseType = typeOf(), - description = "Access Denied", - status = HttpStatusCode.Forbidden - ) - private val polymorphicException = ExceptionInfo( - responseType = typeOf(), - description = "The Gibbits are ANGRY", - status = HttpStatusCode.NotImplemented - ) - private val genericException = ExceptionInfo>( - responseType = typeOf>(), - description = "Wow serious things went wrong", - status = HttpStatusCode.BadRequest - ) - private val exceptionResponseInfo = ExceptionInfo( - responseType = typeOf(), - description = "Bad Things Happened", - status = HttpStatusCode.BadRequest - ) - val testGetWithException = testGetInfo.copy( - canThrow = setOf(exceptionResponseInfo) - ) - val testGetWithMultipleExceptions = testGetInfo.copy( - canThrow = setOf(accessDeniedResponse, exceptionResponseInfo) - ) - val testGetWithPolymorphicException = testGetInfo.copy( - canThrow = setOf(polymorphicException) - ) - val testGetWithGenericException = testGetInfo.copy( - canThrow = setOf(genericException) - ) - val testPostInfo = PostInfo( - summary = "Test post endpoint", - description = "Post your tests here!", - responseInfo = testPostResponse, - requestInfo = testRequest - ) - val testPutInfo = PutInfo( - summary = "Test put endpoint", - description = "Put your tests here!", - responseInfo = testPostResponse, - requestInfo = complexRequest - ) - val testPatchInfo = PatchInfo( - summary = "Test patch endpoint", - description = "patch your tests here!", - responseInfo = testPatchResponse, - requestInfo = testRequest - ) - val testHeadInfo = HeadInfo( - summary = "Test head endpoint", - description = "head test 💀", - responseInfo = ResponseInfo(HttpStatusCode.OK, "great!") - ) - val testOptionsInfo = OptionsInfo( - summary = "Test options", - description = "endpoint of options", - responseInfo = ResponseInfo(HttpStatusCode.OK, "nice") - ) - val testPutInfoAlso = PutInfo( - summary = "Test put endpoint", - description = "Put your tests here!", - responseInfo = testPostResponse, - requestInfo = testRequest - ) - val testPutInfoAgain = PutInfo( - summary = "Test put endpoint", - description = "Put your tests here!", - responseInfo = testPostResponseAgain, - requestInfo = testRequestAgain - ) - val testDeleteInfo = DeleteInfo( - summary = "Test delete endpoint", - description = "testing my deletes", - responseInfo = testDeleteResponse - ) - val testOptionalParams = GetInfo( - summary = "No request params and response body", - description = "testing more", - responseInfo = ResponseInfo(HttpStatusCode.NoContent, "Empty") - ) - val polymorphicResponse = GetInfo( - summary = "All the gibbits", - description = "Polymorphic response", - responseInfo = simpleOkResponse() - ) - val polymorphicListResponse = GetInfo>( - summary = "Oh so many gibbits", - description = "Polymorphic list response", - responseInfo = simpleOkResponse() - ) - val polymorphicMapResponse = GetInfo>( - summary = "By gawd that's a lot of gibbits", - description = "Polymorphic list response", - responseInfo = simpleOkResponse() - ) - val polymorphicInterfaceResponse = GetInfo( - summary = "Come on and slam", - description = "and welcome to the jam", - responseInfo = simpleOkResponse() - ) - val genericPolymorphicResponse = GetInfo>( - summary = "More flibbity", - description = "Polymorphic with generics", - responseInfo = simpleOkResponse() - ) - val anotherGenericPolymorphicResponse = GetInfo>( - summary = "The Most Flibbity", - description = "Polymorphic with generics but like... crazier", - responseInfo = simpleOkResponse() - ) - val undeclaredResponseType = GetInfo( - summary = "spooky class", - description = "break this glass in scenario of emergency", - responseInfo = simpleOkResponse() - ) - val headerParam = GetInfo( - summary = "testing header stuffs", - description = "Good for many things", - responseInfo = simpleOkResponse() - ) - val genericResponse = GetInfo>( - summary = "Single Generic", - description = "Simple generic data class", - responseInfo = simpleOkResponse() - ) - val fieldOverride = GetInfo( - summary = "A Response with a spicy field", - description = "Important info within!", - responseInfo = simpleOkResponse() - ) - - val simpleRecursive = GetInfo( - summary = "Simple recursive example", - description = "Pretty neato!", - responseInfo = simpleOkResponse() - ) - - val nullableNested = PostInfo( - summary = "Has a bunch of nullable fields", - description = "Should still work!", - requestInfo = RequestInfo( - description = "Cool" - ), - responseInfo = simpleOkResponse() - ) - - val nullableEnumField = GetInfo( - summary = "Has a nullable enum field", - description = "should still work!", - responseInfo = simpleOkResponse() - ) - - val minMaxInt = GetInfo( - summary = "Constrained int field", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - val minMaxDouble = GetInfo( - summary = "Constrained int field", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - val exclusiveMinMax = GetInfo( - summary = "Constrained int field", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - val requiredParam = GetInfo( - summary = "required param", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - val formattedParam = GetInfo( - summary = "required param", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - val minMaxString = GetInfo( - summary = "required param", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - val regexString = GetInfo( - summary = "required param", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - - val dateTimeString = GetInfo( - summary = "Date time string test", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - val minMaxArray = GetInfo( - summary = "required param", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - val uniqueArray = GetInfo( - summary = "required param", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - val multipleOfInt = GetInfo( - summary = "required param", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - val multipleOfDouble = GetInfo( - summary = "required param", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - val freeFormField = GetInfo( - summary = "required param", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - val freeFormObject = GetInfo( - summary = "required param", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - val minMaxFreeForm = GetInfo( - summary = "required param", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - val defaultParam = GetInfo( - summary = "default param", - description = "Cool stuff", - responseInfo = simpleOkResponse() - ) - - val defaultField = PostInfo( - summary = "default param", - description = "Cool stuff", - responseInfo = simpleOkResponse(), - requestInfo = RequestInfo("cool") - ) - - val nullableField = PostInfo( - summary = "default param", - description = "Cool stuff", - responseInfo = simpleOkResponse(), - requestInfo = RequestInfo("cool") - ) - - val exampleParams = GetInfo( - summary = "param stuff", - description = "Cool stuff", - responseInfo = simpleOkResponse(), - parameterExamples = setOf( - ParameterExample(TestParams::a.name, "Testerino", "a"), - ParameterExample(TestParams::a.name, "Testerina", "b"), - ParameterExample(TestParams::aa.name, "Wowza", 6), - ) - ) - - val formattedArrayItemType = PostInfo( - summary = "formatted array item type", - description = "Cool stuff", - responseInfo = simpleOkResponse(), - requestInfo = RequestInfo("cool") - .copy(mediaTypes = listOf(ContentType.MultiPart.FormData.toString())) - ) - - private fun simpleOkResponse() = ResponseInfo(HttpStatusCode.OK, "A successful endeavor") -} diff --git a/kompendium-locations/src/main/kotlin/io/bkbn/kompendium/locations/LocationMethodParser.kt b/kompendium-locations/src/main/kotlin/io/bkbn/kompendium/locations/LocationMethodParser.kt deleted file mode 100644 index d23f206ec..000000000 --- a/kompendium-locations/src/main/kotlin/io/bkbn/kompendium/locations/LocationMethodParser.kt +++ /dev/null @@ -1,84 +0,0 @@ -package io.bkbn.kompendium.locations - -import io.bkbn.kompendium.annotations.Param -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.metadata.method.MethodInfo -import io.bkbn.kompendium.core.parser.IMethodParser -import io.bkbn.kompendium.oas.path.Path -import io.bkbn.kompendium.oas.path.PathOperation -import io.bkbn.kompendium.oas.payload.Parameter -import io.ktor.application.feature -import io.ktor.locations.KtorExperimentalLocationsAPI -import io.ktor.locations.Location -import io.ktor.routing.Route -import io.ktor.routing.application -import kotlin.reflect.KAnnotatedElement -import kotlin.reflect.KClass -import kotlin.reflect.KClassifier -import kotlin.reflect.KType -import kotlin.reflect.full.createType -import kotlin.reflect.full.findAnnotation -import kotlin.reflect.full.hasAnnotation -import kotlin.reflect.full.memberProperties - -@OptIn(KtorExperimentalLocationsAPI::class) -object LocationMethodParser : IMethodParser { - override fun KType.toParameterSpec(info: MethodInfo<*, *>, feature: Kompendium): List { - val clazzList = determineLocationParents(classifier!!) - return clazzList.associateWith { it.memberProperties } - .flatMap { (clazz, memberProperties) -> memberProperties.associateWith { clazz }.toList() } - .filter { (prop, _) -> prop.hasAnnotation() } - .map { (prop, clazz) -> prop.toParameter(info, clazz.createType(), clazz, feature) } - } - - private fun determineLocationParents(classifier: KClassifier): List> { - var clazz: KClass<*>? = classifier as KClass<*> - val clazzList = mutableListOf>() - while (clazz != null) { - clazzList.add(clazz) - clazz = getLocationParent(clazz) - } - return clazzList - } - - private fun getLocationParent(clazz: KClass<*>): KClass<*>? { - val parent = clazz.memberProperties - .find { (it.returnType.classifier as KAnnotatedElement).hasAnnotation() } - return parent?.returnType?.classifier as? KClass<*> - } - - fun KClass<*>.calculateLocationPath(suffix: String = ""): String { - val locationAnnotation = this.findAnnotation() - require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" } - val parent = this.java.declaringClass?.kotlin?.takeIf { it.hasAnnotation() } - val newSuffix = locationAnnotation.path.plus(suffix) - return when (parent) { - null -> newSuffix - else -> parent.calculateLocationPath(newSuffix) - } - } - - inline fun processBaseInfo( - paramType: KType, - requestType: KType, - responseType: KType, - info: MethodInfo<*, *>, - route: Route - ): LocationBaseInfo { - val locationAnnotation = TParam::class.findAnnotation() - require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" } - val path = route.calculateRoutePath() - val locationPath = TParam::class.calculateLocationPath() - val pathWithLocation = path.plus(locationPath) - val feature = route.application.feature(Kompendium) - feature.config.spec.paths.getOrPut(pathWithLocation) { Path() } - val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature) - return LocationBaseInfo(baseInfo, feature, pathWithLocation) - } - - data class LocationBaseInfo( - val op: PathOperation, - val feature: Kompendium, - val path: String - ) -} diff --git a/kompendium-locations/src/main/kotlin/io/bkbn/kompendium/locations/NotarizedLocation.kt b/kompendium-locations/src/main/kotlin/io/bkbn/kompendium/locations/NotarizedLocation.kt deleted file mode 100644 index 16b689fbc..000000000 --- a/kompendium-locations/src/main/kotlin/io/bkbn/kompendium/locations/NotarizedLocation.kt +++ /dev/null @@ -1,110 +0,0 @@ -package io.bkbn.kompendium.locations - -import io.bkbn.kompendium.core.KompendiumPreFlight.methodNotarizationPreFlight -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.metadata.method.PutInfo -import io.bkbn.kompendium.oas.path.PathOperation -import io.ktor.application.ApplicationCall -import io.ktor.http.HttpMethod -import io.ktor.locations.KtorExperimentalLocationsAPI -import io.ktor.locations.handle -import io.ktor.locations.location -import io.ktor.routing.Route -import io.ktor.routing.method -import io.ktor.util.pipeline.PipelineContext - -/** - * This version of notarized routes leverages the Ktor [io.ktor.locations.Locations] plugin to provide type safe access - * to all path and query parameters. - */ -@KtorExperimentalLocationsAPI -object NotarizedLocation { - - /** - * Notarization for an HTTP GET request leveraging the Ktor [io.ktor.locations.Locations] plugin - * @param TParam The class containing all parameter fields. - * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param]. - * Additionally, the class must be annotated with @[io.ktor.locations.Location]. - * @param TResp Class detailing the expected API response - * @param info Route metadata - * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation] - */ - inline fun Route.notarizedGet( - info: GetInfo, - postProcess: (PathOperation) -> PathOperation = { p -> p }, - noinline body: suspend PipelineContext.(TParam) -> Unit - ): Route = methodNotarizationPreFlight() { paramType, requestType, responseType -> - val lbi = LocationMethodParser.processBaseInfo(paramType, requestType, responseType, info, this) - lbi.feature.config.spec.paths[lbi.path]?.get = postProcess(lbi.op) - return location(TParam::class) { - method(HttpMethod.Get) { handle(body) } - } - } - - /** - * Notarization for an HTTP POST request leveraging the Ktor [io.ktor.locations.Locations] plugin - * @param TParam The class containing all parameter fields. - * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param] - * Additionally, the class must be annotated with @[io.ktor.locations.Location]. - * @param TReq Class detailing the expected API request body - * @param TResp Class detailing the expected API response - * @param info Route metadata - * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation] - */ - inline fun Route.notarizedPost( - info: PostInfo, - postProcess: (PathOperation) -> PathOperation = { p -> p }, - noinline body: suspend PipelineContext.(TParam) -> Unit - ): Route = methodNotarizationPreFlight() { paramType, requestType, responseType -> - val lbi = LocationMethodParser.processBaseInfo(paramType, requestType, responseType, info, this) - lbi.feature.config.spec.paths[lbi.path]?.post = postProcess(lbi.op) - return location(TParam::class) { - method(HttpMethod.Post) { handle(body) } - } - } - - /** - * Notarization for an HTTP Delete request leveraging the Ktor [io.ktor.locations.Locations] plugin - * @param TParam The class containing all parameter fields. - * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param] - * Additionally, the class must be annotated with @[io.ktor.locations.Location]. - * @param TReq Class detailing the expected API request body - * @param TResp Class detailing the expected API response - * @param info Route metadata - * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation] - */ - inline fun Route.notarizedPut( - info: PutInfo, - postProcess: (PathOperation) -> PathOperation = { p -> p }, - noinline body: suspend PipelineContext.(TParam) -> Unit - ): Route = methodNotarizationPreFlight() { paramType, requestType, responseType -> - val lbi = LocationMethodParser.processBaseInfo(paramType, requestType, responseType, info, this) - lbi.feature.config.spec.paths[lbi.path]?.put = postProcess(lbi.op) - return location(TParam::class) { - method(HttpMethod.Put) { handle(body) } - } - } - - /** - * Notarization for an HTTP POST request leveraging the Ktor [io.ktor.locations.Locations] plugin - * @param TParam The class containing all parameter fields. - * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param] - * Additionally, the class must be annotated with @[io.ktor.locations.Location]. - * @param TResp Class detailing the expected API response - * @param info Route metadata - * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation] - */ - inline fun Route.notarizedDelete( - info: DeleteInfo, - postProcess: (PathOperation) -> PathOperation = { p -> p }, - noinline body: suspend PipelineContext.(TParam) -> Unit - ): Route = methodNotarizationPreFlight { paramType, requestType, responseType -> - val lbi = LocationMethodParser.processBaseInfo(paramType, requestType, responseType, info, this) - lbi.feature.config.spec.paths[lbi.path]?.delete = postProcess(lbi.op) - return location(TParam::class) { - method(HttpMethod.Delete) { handle(body) } - } - } -} diff --git a/kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/KompendiumLocationsTest.kt b/kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/KompendiumLocationsTest.kt deleted file mode 100644 index c45ad217f..000000000 --- a/kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/KompendiumLocationsTest.kt +++ /dev/null @@ -1,82 +0,0 @@ -package io.bkbn.kompendium.locations - -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 -import io.bkbn.kompendium.locations.util.notarizedGetNestedLocation -import io.bkbn.kompendium.locations.util.notarizedGetNestedLocationFromNonLocationClass -import io.bkbn.kompendium.locations.util.notarizedGetSimpleLocation -import io.bkbn.kompendium.locations.util.notarizedPostNestedLocation -import io.bkbn.kompendium.locations.util.notarizedPostSimpleLocation -import io.bkbn.kompendium.locations.util.notarizedPutNestedLocation -import io.bkbn.kompendium.locations.util.notarizedPutSimpleLocation -import io.kotest.core.spec.style.DescribeSpec - -class KompendiumLocationsTest : DescribeSpec({ - describe("Locations") { - it("Can notarize a get request with a simple location") { - // act - openApiTestAllSerializers("notarized_get_simple_location.json") { - locationsConfig() - notarizedGetSimpleLocation() - } - } - it("Can notarize a get request with a nested location") { - // act - openApiTestAllSerializers("notarized_get_nested_location.json") { - locationsConfig() - notarizedGetNestedLocation() - } - } - it("Can notarize a post with a simple location") { - // act - openApiTestAllSerializers("notarized_post_simple_location.json") { - locationsConfig() - notarizedPostSimpleLocation() - } - } - it("Can notarize a post with a nested location") { - // act - openApiTestAllSerializers("notarized_post_nested_location.json") { - locationsConfig() - notarizedPostNestedLocation() - } - } - it("Can notarize a put with a simple location") { - // act - openApiTestAllSerializers("notarized_put_simple_location.json") { - locationsConfig() - notarizedPutSimpleLocation() - } - } - it("Can notarize a put with a nested location") { - // act - openApiTestAllSerializers("notarized_put_nested_location.json") { - locationsConfig() - notarizedPutNestedLocation() - } - } - it("Can notarize a delete with a simple location") { - // act - openApiTestAllSerializers("notarized_delete_simple_location.json") { - locationsConfig() - notarizedDeleteSimpleLocation() - } - } - it("Can notarize a delete with a nested location") { - // act - openApiTestAllSerializers("notarized_delete_nested_location.json") { - locationsConfig() - notarizedDeleteNestedLocation() - } - } - it("Can notarize a get with a nested location nested in a non-location class") { - // act - openApiTestAllSerializers("notarized_get_nested_location_from_non_location_class.json") { - locationsConfig() - notarizedGetNestedLocationFromNonLocationClass() - } - } - } -}) diff --git a/kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModels.kt b/kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModels.kt deleted file mode 100644 index a0aee790d..000000000 --- a/kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModels.kt +++ /dev/null @@ -1,22 +0,0 @@ -package io.bkbn.kompendium.locations.util - -import io.bkbn.kompendium.annotations.Param -import io.bkbn.kompendium.annotations.ParamType -import io.ktor.locations.Location - -@Location("/test/{name}") -data class SimpleLoc(@Param(ParamType.PATH) val name: String) { - @Location("/nesty") - data class NestedLoc(@Param(ParamType.QUERY) val isCool: Boolean, val parent: SimpleLoc) -} - -object NonLocationObject { - @Location("/test/{name}") - data class SimpleLoc(@Param(ParamType.PATH) val name: String) { - @Location("/nesty") - data class NestedLoc(@Param(ParamType.QUERY) val isCool: Boolean, val parent: SimpleLoc) - } -} - -data class SimpleResponse(val result: Boolean) -data class SimpleRequest(val input: String) diff --git a/kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModules.kt b/kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModules.kt deleted file mode 100644 index 3345dc126..000000000 --- a/kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModules.kt +++ /dev/null @@ -1,107 +0,0 @@ -package io.bkbn.kompendium.locations.util - -import io.bkbn.kompendium.locations.NotarizedLocation.notarizedDelete -import io.bkbn.kompendium.locations.NotarizedLocation.notarizedGet -import io.bkbn.kompendium.locations.NotarizedLocation.notarizedPost -import io.bkbn.kompendium.locations.NotarizedLocation.notarizedPut -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.locations.Locations -import io.ktor.response.respondText -import io.ktor.routing.route -import io.ktor.routing.routing - -fun Application.locationsConfig() { - install(Locations) -} - -fun Application.notarizedGetSimpleLocation() { - routing { - route("/test") { - notarizedGet(TestResponseInfo.testGetSimpleLocation) { - call.respondText { "hey dude ‼️ congratz on the get request" } - } - } - } -} - -fun Application.notarizedGetNestedLocation() { - routing { - route("/test") { - notarizedGet(TestResponseInfo.testGetNestedLocation) { - call.respondText { "hey dude ‼️ congratz on the get request" } - } - } - } -} - -fun Application.notarizedPostSimpleLocation() { - routing { - route("/test") { - notarizedPost(TestResponseInfo.testPostSimpleLocation) { - call.respondText { "hey dude ‼️ congratz on the get request" } - } - } - } -} - -fun Application.notarizedPostNestedLocation() { - routing { - route("/test") { - notarizedPost(TestResponseInfo.testPostNestedLocation) { - call.respondText { "hey dude ‼️ congratz on the get request" } - } - } - } -} - -fun Application.notarizedPutSimpleLocation() { - routing { - route("/test") { - notarizedPut(TestResponseInfo.testPutSimpleLocation) { - call.respondText { "hey dude ‼️ congratz on the get request" } - } - } - } -} - -fun Application.notarizedPutNestedLocation() { - routing { - route("/test") { - notarizedPut(TestResponseInfo.testPutNestedLocation) { - call.respondText { "hey dude ‼️ congratz on the get request" } - } - } - } -} - -fun Application.notarizedDeleteSimpleLocation() { - routing { - route("/test") { - notarizedDelete(TestResponseInfo.testDeleteSimpleLocation) { - call.respondText { "hey dude ‼️ congratz on the get request" } - } - } - } -} - -fun Application.notarizedDeleteNestedLocation() { - routing { - route("/test") { - notarizedDelete(TestResponseInfo.testDeleteNestedLocation) { - call.respondText { "hey dude ‼️ congratz on the get request" } - } - } - } -} - -fun Application.notarizedGetNestedLocationFromNonLocationClass() { - routing { - route("/test") { - notarizedGet(TestResponseInfo.testGetNestedLocationFromNonLocationClass) { - call.respondText { "hey dude ‼️ congratz on the get request" } - } - } - } -} diff --git a/kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestResponseInfo.kt b/kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestResponseInfo.kt deleted file mode 100644 index 652583f2c..000000000 --- a/kompendium-locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestResponseInfo.kt +++ /dev/null @@ -1,97 +0,0 @@ -package io.bkbn.kompendium.locations.util - -import io.bkbn.kompendium.core.metadata.RequestInfo -import io.bkbn.kompendium.core.metadata.ResponseInfo -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.metadata.method.PutInfo -import io.ktor.http.HttpStatusCode - -object TestResponseInfo { - val testGetSimpleLocation = GetInfo( - summary = "Location Test", - description = "A cool test", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "A successful endeavor" - ) - ) - val testPostSimpleLocation = PostInfo( - summary = "Location Test", - description = "A cool test", - requestInfo = RequestInfo( - description = "Cool stuff" - ), - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "A successful endeavor" - ) - ) - val testPutSimpleLocation = PutInfo( - summary = "Location Test", - description = "A cool test", - requestInfo = RequestInfo( - description = "Cool stuff" - ), - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "A successful endeavor" - ) - ) - val testDeleteSimpleLocation = DeleteInfo( - summary = "Location Test", - description = "A cool test", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "A successful endeavor" - ) - ) - val testGetNestedLocation = GetInfo( - summary = "Location Test", - description = "A cool test", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "A successful endeavor" - ) - ) - val testPostNestedLocation = PostInfo( - summary = "Location Test", - description = "A cool test", - requestInfo = RequestInfo( - description = "Cool stuff" - ), - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "A successful endeavor" - ) - ) - val testPutNestedLocation = PutInfo( - summary = "Location Test", - description = "A cool test", - requestInfo = RequestInfo( - description = "Cool stuff" - ), - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "A successful endeavor" - ) - ) - val testDeleteNestedLocation = DeleteInfo( - summary = "Location Test", - description = "A cool test", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "A successful endeavor" - ) - ) - - val testGetNestedLocationFromNonLocationClass = GetInfo( - summary = "Location Test", - description = "A cool test", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "A successful endeavor" - ) - ) -} diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/OpenApiSpec.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/OpenApiSpec.kt deleted file mode 100644 index d6cf9d92e..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/OpenApiSpec.kt +++ /dev/null @@ -1,21 +0,0 @@ -package io.bkbn.kompendium.oas - -import io.bkbn.kompendium.oas.common.ExternalDocumentation -import io.bkbn.kompendium.oas.common.Tag -import io.bkbn.kompendium.oas.component.Components -import io.bkbn.kompendium.oas.info.Info -import io.bkbn.kompendium.oas.path.Path -import io.bkbn.kompendium.oas.server.Server -import kotlinx.serialization.Serializable - -@Serializable -data class OpenApiSpec( - val openapi: String = "3.0.3", - val info: Info, - val servers: MutableList = mutableListOf(), - val paths: MutableMap = mutableMapOf(), - val components: Components = Components(), - val security: MutableList>> = mutableListOf(), - val tags: MutableList = mutableListOf(), - val externalDocs: ExternalDocumentation? = null -) diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/component/Components.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/component/Components.kt deleted file mode 100644 index f8fc07e50..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/component/Components.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.bkbn.kompendium.oas.component - -import io.bkbn.kompendium.oas.schema.ComponentSchema -import io.bkbn.kompendium.oas.security.SecuritySchema -import kotlinx.serialization.Serializable - -@Serializable -data class Components( - val schemas: MutableMap = mutableMapOf(), - val securitySchemes: MutableMap = mutableMapOf() -) diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Contact.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Contact.kt deleted file mode 100644 index 2c41e138b..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Contact.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.bkbn.kompendium.oas.info - -import io.bkbn.kompendium.oas.serialization.UriSerializer -import kotlinx.serialization.Serializable -import java.net.URI - -@Serializable -data class Contact( - var name: String, - @Serializable(with = UriSerializer::class) - var url: URI? = null, - var email: String? = null // TODO Enforce email? -) diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Info.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Info.kt deleted file mode 100644 index d22cb3568..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Info.kt +++ /dev/null @@ -1,16 +0,0 @@ -package io.bkbn.kompendium.oas.info - -import io.bkbn.kompendium.oas.serialization.UriSerializer -import kotlinx.serialization.Serializable -import java.net.URI - -@Serializable -data class Info( - var title: String? = null, - var version: String? = null, - var description: String? = null, - @Serializable(with = UriSerializer::class) - var termsOfService: URI? = null, - var contact: Contact? = null, - var license: License? = null -) diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/info/License.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/info/License.kt deleted file mode 100644 index 907d313bc..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/info/License.kt +++ /dev/null @@ -1,12 +0,0 @@ -package io.bkbn.kompendium.oas.info - -import io.bkbn.kompendium.oas.serialization.UriSerializer -import kotlinx.serialization.Serializable -import java.net.URI - -@Serializable -data class License( - var name: String, - @Serializable(with = UriSerializer::class) - var url: URI? = null -) diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/path/Path.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/path/Path.kt deleted file mode 100644 index 76510bf04..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/path/Path.kt +++ /dev/null @@ -1,19 +0,0 @@ -package io.bkbn.kompendium.oas.path - -import io.bkbn.kompendium.oas.payload.Parameter -import io.bkbn.kompendium.oas.server.Server -import kotlinx.serialization.Serializable - -@Serializable -data class Path( - var get: PathOperation? = null, - var put: PathOperation? = null, - var post: PathOperation? = null, - var delete: PathOperation? = null, - var options: PathOperation? = null, - var head: PathOperation? = null, - var patch: PathOperation? = null, - var trace: PathOperation? = null, - var servers: List? = null, - var parameters: List? = null -) diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/path/PathOperation.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/path/PathOperation.kt deleted file mode 100644 index d55dc15b2..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/path/PathOperation.kt +++ /dev/null @@ -1,27 +0,0 @@ -package io.bkbn.kompendium.oas.path - -import io.bkbn.kompendium.oas.common.ExternalDocumentation -import io.bkbn.kompendium.oas.payload.Parameter -import io.bkbn.kompendium.oas.payload.Payload -import io.bkbn.kompendium.oas.payload.Request -import io.bkbn.kompendium.oas.payload.Response -import io.bkbn.kompendium.oas.server.Server -import kotlinx.serialization.Serializable - -@Serializable -data class PathOperation( - var tags: Set = emptySet(), - var summary: String? = null, - var description: String? = null, - var externalDocs: ExternalDocumentation? = null, - var operationId: String? = null, - var parameters: List? = null, - var requestBody: Request? = null, - // TODO How to enforce `default` requirement 🧐 - var responses: Map? = null, - var callbacks: Map? = null, // todo what is this? - var deprecated: Boolean = false, - var security: List>>? = null, - var servers: List? = null, - var `x-codegen-request-body-name`: String? = null -) diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/AnyOfPayload.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/AnyOfPayload.kt deleted file mode 100644 index 8a061a6b9..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/AnyOfPayload.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.bkbn.kompendium.oas.payload - -import io.bkbn.kompendium.oas.schema.ComponentSchema - -data class AnyOfPayload(val anyOf: List) : Payload diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/MediaType.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/MediaType.kt deleted file mode 100644 index 662efada5..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/MediaType.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.bkbn.kompendium.oas.payload - -import io.bkbn.kompendium.oas.schema.ComponentSchema -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable - -@Serializable -data class MediaType( - val schema: ComponentSchema, - val examples: Map? = null -) { - @Serializable - data class Example(val value: @Contextual Any) -} diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Parameter.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Parameter.kt deleted file mode 100644 index 017ca200c..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Parameter.kt +++ /dev/null @@ -1,22 +0,0 @@ -package io.bkbn.kompendium.oas.payload - -import io.bkbn.kompendium.oas.schema.ComponentSchema -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable - -@Serializable -data class Parameter( - val name: String, - val `in`: String, // TODO Enum? "query", "header", "path" or "cookie" - val schema: ComponentSchema, - val description: String? = null, - val required: Boolean = true, - val deprecated: Boolean = false, - val allowEmptyValue: Boolean? = null, - val style: String? = null, - val explode: Boolean? = null, - val examples: Map? = null -) { - @Serializable - data class Example(val value: @Contextual Any) -} diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Payload.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Payload.kt deleted file mode 100644 index 8e76771fb..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Payload.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.bkbn.kompendium.oas.payload - -sealed interface Payload diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Request.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Request.kt deleted file mode 100644 index a2e010e9c..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Request.kt +++ /dev/null @@ -1,10 +0,0 @@ -package io.bkbn.kompendium.oas.payload - -import kotlinx.serialization.Serializable - -@Serializable -data class Request( - val description: String?, - val content: Map, - val required: Boolean = false -) : Payload diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Response.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Response.kt deleted file mode 100644 index 651cf2b96..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Response.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.bkbn.kompendium.oas.payload - -import kotlinx.serialization.Serializable - -@Serializable -data class Response( - val description: String? = null, - val headers: Map? = null, - val content: Map? = null, - val links: Map? = null -) : Payload diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/AnyOfSchema.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/AnyOfSchema.kt deleted file mode 100644 index 3e1102348..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/AnyOfSchema.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.bkbn.kompendium.oas.schema - -import kotlinx.serialization.Serializable - -@Serializable -data class AnyOfSchema(val anyOf: List, override val description: String? = null) : ComponentSchema diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ArraySchema.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ArraySchema.kt deleted file mode 100644 index ff9587a89..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ArraySchema.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.bkbn.kompendium.oas.schema - -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable - -@Serializable -data class ArraySchema( - val items: ComponentSchema, - override val default: @Contextual Any? = null, - override val description: String? = null, - override val nullable: Boolean? = null, - // constraints - val minItems: Int? = null, - val maxItems: Int? = null, - val uniqueItems: Boolean? = null -) : TypedSchema { - override val type: String = "array" -} diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ComponentSchema.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ComponentSchema.kt deleted file mode 100644 index c6ac7951e..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ComponentSchema.kt +++ /dev/null @@ -1,39 +0,0 @@ -package io.bkbn.kompendium.oas.schema - -import io.bkbn.kompendium.oas.serialization.ComponentSchemaSerializer -import kotlinx.serialization.Serializable - -@Serializable(with = ComponentSchemaSerializer::class) -sealed interface ComponentSchema { - val description: String? - get() = null - - val default: Any? - get() = null - - fun addDefault(default: Any?): ComponentSchema = when (this) { - is AnyOfSchema -> error("Cannot add default to anyOf reference") // todo is this true though? - is ArraySchema -> this.copy(default = default) - is DictionarySchema -> this.copy(default = default) - is EnumSchema -> this.copy(default = default) - is FormattedSchema -> this.copy(default = default) - is ObjectSchema -> this.copy(default = default) - is SimpleSchema -> this.copy(default = default) - is ReferencedSchema -> this.copy(default = default) - is FreeFormSchema -> this.copy(default = default) - else -> error("Compiler bug??") - } - - fun setDescription(description: String): ComponentSchema = when (this) { - is AnyOfSchema -> this.copy(description = description) - is ArraySchema -> this.copy(description = description) - is DictionarySchema -> this.copy(description = description) - is EnumSchema -> this.copy(description = description) - is FormattedSchema -> this.copy(description = description) - is ObjectSchema -> this.copy(description = description) - is SimpleSchema -> this.copy(description = description) - is ReferencedSchema -> this.copy(description = description) - is FreeFormSchema -> this.copy(description = description) - else -> error("Compiler bug??") - } -} diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/DictionarySchema.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/DictionarySchema.kt deleted file mode 100644 index a25932888..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/DictionarySchema.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.bkbn.kompendium.oas.schema - -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable - -@Serializable -data class DictionarySchema( - val additionalProperties: ComponentSchema, - override val default: @Contextual Any? = null, - override val description: String? = null, - override val nullable: Boolean? = null -) : TypedSchema { - override val type: String = "object" -} diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/EnumSchema.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/EnumSchema.kt deleted file mode 100644 index 7750af1b8..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/EnumSchema.kt +++ /dev/null @@ -1,31 +0,0 @@ -package io.bkbn.kompendium.oas.schema - -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable - -@Serializable -data class EnumSchema( - val enum: Set, - override val default: @Contextual Any? = null, - override val description: String? = null, - override val nullable: Boolean? = null -) : TypedSchema { - override val type: String = "string" - - override fun equals(other: Any?): Boolean { - if (other !is EnumSchema) return false - if (enum != other.enum) return false - // TODO Going to need some way to differentiate nullable vs non-nullable reference schemas 😬 - // if (nullable != other.nullable) return false - return true - } - - override fun hashCode(): Int { - var result = enum.hashCode() - result = 31 * result + (default?.hashCode() ?: 0) - result = 31 * result + (description?.hashCode() ?: 0) - result = 31 * result + (nullable?.hashCode() ?: 0) - result = 31 * result + type.hashCode() - return result - } -} diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/FormattedSchema.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/FormattedSchema.kt deleted file mode 100644 index ebcdd287f..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/FormattedSchema.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.bkbn.kompendium.oas.schema - -import io.bkbn.kompendium.oas.serialization.NumberSerializer -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable - -@Serializable -data class FormattedSchema( - val format: String, - override val type: String, - override val default: @Contextual Any? = null, - override val description: String? = null, - override val nullable: Boolean? = null, - // Constraints - @Serializable(with = NumberSerializer::class) - val minimum: Number? = null, - @Serializable(with = NumberSerializer::class) - val maximum: Number? = null, - val exclusiveMinimum: Boolean? = null, - val exclusiveMaximum: Boolean? = null, - @Serializable(with = NumberSerializer::class) - val multipleOf: Number? = null, -) : TypedSchema diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/FreeFormSchema.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/FreeFormSchema.kt deleted file mode 100644 index 4f4ae4012..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/FreeFormSchema.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.bkbn.kompendium.oas.schema - -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable - -@Serializable -data class FreeFormSchema( - override val nullable: Boolean? = null, - // constraints - val minProperties: Int? = null, - val maxProperties: Int? = null, - override val default: @Contextual Any? = null, - override val description: String? = null, -) : TypedSchema { - val additionalProperties: Boolean = true - override val type: String = "object" -} diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ObjectSchema.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ObjectSchema.kt deleted file mode 100644 index 0470a4975..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ObjectSchema.kt +++ /dev/null @@ -1,36 +0,0 @@ -package io.bkbn.kompendium.oas.schema - -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable - -@Serializable -data class ObjectSchema( - val properties: Map, - override val default: @Contextual Any? = null, - override val description: String? = null, - override val nullable: Boolean? = null, - // constraints - val required: List? = null -) : TypedSchema { - override val type = "object" - - override fun equals(other: Any?): Boolean { - if (other !is ObjectSchema) return false - if (properties != other.properties) return false - if (description != other.description) return false - // TODO Going to need some way to differentiate nullable vs non-nullable reference schemas 😬 -// if (nullable != other.nullable) return false - if (required != other.required) return false - return true - } - - override fun hashCode(): Int { - var result = properties.hashCode() - result = 31 * result + (default?.hashCode() ?: 0) - result = 31 * result + (description?.hashCode() ?: 0) - result = 31 * result + (nullable?.hashCode() ?: 0) - result = 31 * result + (required?.hashCode() ?: 0) - result = 31 * result + type.hashCode() - return result - } -} diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ReferencedSchema.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ReferencedSchema.kt deleted file mode 100644 index 0ed9fb3aa..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/ReferencedSchema.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.bkbn.kompendium.oas.schema - -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable - -@Serializable -data class ReferencedSchema( - val `$ref`: String, - override val default: @Contextual Any? = null, - override val description: String? = null -) : ComponentSchema diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/SimpleSchema.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/SimpleSchema.kt deleted file mode 100644 index c0b3f7dc6..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/SimpleSchema.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.bkbn.kompendium.oas.schema - -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable - -@Serializable -data class SimpleSchema( - override val type: String, - override val default: @Contextual Any? = null, - override val description: String? = null, - override val nullable: Boolean? = null, - // Constraints - val minLength: Int? = null, - val maxLength: Int? = null, - val pattern: String? = null, - val format: String? = null -) : TypedSchema diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/TypedSchema.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/TypedSchema.kt deleted file mode 100644 index 6a3adf5ce..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/schema/TypedSchema.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.bkbn.kompendium.oas.schema - -sealed interface TypedSchema : ComponentSchema { - val type: String - val nullable: Boolean? - override val default: Any? -} diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/ComponentSchemaSerializer.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/ComponentSchemaSerializer.kt deleted file mode 100644 index 3baf534aa..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/ComponentSchemaSerializer.kt +++ /dev/null @@ -1,45 +0,0 @@ -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 { - 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) - } - } - -} diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/server/Server.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/server/Server.kt deleted file mode 100644 index 4ed3c911b..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/server/Server.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.bkbn.kompendium.oas.server - -import io.bkbn.kompendium.oas.serialization.UriSerializer -import kotlinx.serialization.Serializable -import java.net.URI - -@Serializable -data class Server( - @Serializable(with = UriSerializer::class) - val url: URI, - val description: String? = null, - var variables: Map? = null -) diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/server/ServerVariable.kt b/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/server/ServerVariable.kt deleted file mode 100644 index 805856021..000000000 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/server/ServerVariable.kt +++ /dev/null @@ -1,10 +0,0 @@ -package io.bkbn.kompendium.oas.server - -import kotlinx.serialization.Serializable - -@Serializable -data class ServerVariable( - val `enum`: Set, // todo enforce not empty - val default: String, - val description: String? -) diff --git a/kompendium-playground/build.gradle.kts b/kompendium-playground/build.gradle.kts deleted file mode 100644 index 2275b19d3..000000000 --- a/kompendium-playground/build.gradle.kts +++ /dev/null @@ -1,46 +0,0 @@ -plugins { - kotlin("jvm") - kotlin("plugin.serialization") - id("io.bkbn.sourdough.application.jvm") - id("application") -} - -sourdough { - compilerArgs.set(listOf("-opt-in=kotlin.RequiresOptIn")) -} - -dependencies { - // IMPLEMENTATION - implementation(projects.kompendiumCore) - implementation(projects.kompendiumAuth) - implementation(projects.kompendiumLocations) - implementation(projects.kompendiumSwaggerUi) - - // Ktor - val ktorVersion: String by project - - implementation(group = "io.ktor", name = "ktor-server-core", version = ktorVersion) - implementation(group = "io.ktor", name = "ktor-server-netty", version = ktorVersion) - implementation(group = "io.ktor", name = "ktor-auth", version = ktorVersion) - implementation(group = "io.ktor", name = "ktor-auth-jwt", version = ktorVersion) - implementation(group = "io.ktor", name = "ktor-serialization", version = ktorVersion) - implementation(group = "io.ktor", name = "ktor-jackson", version = ktorVersion) - implementation(group = "io.ktor", name = "ktor-gson", version = ktorVersion) - implementation(group = "io.ktor", name = "ktor-locations", version = ktorVersion) - - // Logging - implementation("org.apache.logging.log4j:log4j-api-kotlin:1.2.0") - implementation("org.apache.logging.log4j:log4j-api:2.18.0") - implementation("org.apache.logging.log4j:log4j-core:2.18.0") - implementation("org.slf4j:slf4j-api:1.7.36") - implementation("org.slf4j:slf4j-simple:1.7.36") - - - implementation(group = "org.jetbrains.kotlinx", "kotlinx-serialization-json", version = "1.3.2") - implementation(group = "org.jetbrains.kotlinx", "kotlinx-datetime", version = "0.3.2") - - implementation(group = "joda-time", name = "joda-time", version = "2.11.0") -} -repositories { - mavenCentral() -} diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/AuthPlayground.kt b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/AuthPlayground.kt deleted file mode 100644 index e86b96d7e..000000000 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/AuthPlayground.kt +++ /dev/null @@ -1,101 +0,0 @@ -package io.bkbn.kompendium.playground - -import io.bkbn.kompendium.auth.Notarized.notarizedAuthenticate -import io.bkbn.kompendium.auth.configuration.BasicAuthConfiguration -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.Notarized.notarizedGet -import io.bkbn.kompendium.core.metadata.ResponseInfo -import io.bkbn.kompendium.core.metadata.method.GetInfo -import io.bkbn.kompendium.core.routes.redoc -import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule -import io.bkbn.kompendium.playground.AuthPlaygroundToC.simpleAuthenticatedGet -import io.bkbn.kompendium.playground.util.Util -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.auth.Authentication -import io.ktor.auth.UserIdPrincipal -import io.ktor.auth.basic -import io.ktor.features.ContentNegotiation -import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.routing.routing -import io.ktor.serialization.json -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json - -/** - * Application entrypoint. Run this and head on over to `localhost:8081/docs` - * to see some documented, authenticated routes. - */ -fun main() { - embeddedServer( - Netty, - port = 8081, - module = Application::mainModule - ).start(wait = true) -} - -// Application Module -private fun Application.mainModule() { - install(ContentNegotiation) { - json(Json { - serializersModule = KompendiumSerializersModule.module - encodeDefaults = true - explicitNulls = false - }) - } - install(Kompendium) { - spec = Util.baseSpec - } - install(Authentication) { - // We can leverage the security config name to prevent typos - basic(SecurityConfigurations.basic.name) { - realm = "Access to the '/' path" - validate { credentials -> - if (credentials.name == "admin" && credentials.password == "foobar") { - UserIdPrincipal(credentials.name) - } else { - null - } - } - - } - } - routing { - redoc(pageTitle = "Authenticated API") - notarizedAuthenticate(SecurityConfigurations.basic) { - notarizedGet(simpleAuthenticatedGet) { - call.respond(HttpStatusCode.OK, AuthModels.SimpleAuthResponse(true)) - } - } - } -} - -// This is where we define the available security configurations for our app -object SecurityConfigurations { - val basic = object : BasicAuthConfiguration { - override val name: String = "basic" - } -} - -// This is a table of contents to hold all the metadata for our various API endpoints -object AuthPlaygroundToC { - val simpleAuthenticatedGet = GetInfo( - summary = "Simple GET Request behind authentication", - description = "Can only make this request if you are a true OG", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "Proves that you are in fact an OG" - ), - tags = setOf("Authenticated"), - securitySchemes = setOf(SecurityConfigurations.basic.name) - ) -} - -object AuthModels { - @Serializable - data class SimpleAuthResponse(val isOG: Boolean) -} diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/BasicPlayground.kt b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/BasicPlayground.kt deleted file mode 100644 index 9adbb2b9f..000000000 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/BasicPlayground.kt +++ /dev/null @@ -1,219 +0,0 @@ -package io.bkbn.kompendium.playground - -import com.fasterxml.jackson.annotation.JsonProperty -import com.google.gson.annotations.SerializedName -import io.bkbn.kompendium.annotations.Field -import io.bkbn.kompendium.annotations.Param -import io.bkbn.kompendium.annotations.ParamType -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.Notarized.notarizedDelete -import io.bkbn.kompendium.core.Notarized.notarizedGet -import io.bkbn.kompendium.core.Notarized.notarizedPost -import io.bkbn.kompendium.core.Notarized.notarizedPut -import io.bkbn.kompendium.core.metadata.RequestInfo -import io.bkbn.kompendium.core.metadata.ResponseInfo -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.metadata.method.PutInfo -import io.bkbn.kompendium.core.routes.redoc -import io.bkbn.kompendium.core.routes.swagger -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 -import io.bkbn.kompendium.playground.BasicPlaygroundToC.simpleDeleteRequest -import io.bkbn.kompendium.playground.BasicPlaygroundToC.simpleGetExample -import io.bkbn.kompendium.playground.BasicPlaygroundToC.simpleGetExampleWithParameters -import io.bkbn.kompendium.playground.BasicPlaygroundToC.simplePostRequest -import io.bkbn.kompendium.playground.BasicPlaygroundToC.simplePutInfo -import io.bkbn.kompendium.playground.util.Util -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.features.ContentNegotiation -import io.ktor.http.HttpStatusCode -import io.ktor.request.receive -import io.ktor.response.respond -import io.ktor.routing.route -import io.ktor.routing.routing -import io.ktor.serialization.json -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 - -/** - * Application entrypoint. Run this and head on over to `localhost:8081/docs` - * to see a very simple yet beautifully documented API - */ -fun main() { - embeddedServer( - Netty, - port = 8081, - module = Application::mainModule - ).start(wait = true) -} - -// Application Module -private fun Application.mainModule() { - // Installs Simple JSON Content Negotiation - install(ContentNegotiation) { - json(Json { - serializersModule = KompendiumSerializersModule.module - encodeDefaults = true - explicitNulls = false - }) - } - // Installs the Kompendium Plugin and sets up baseline server metadata - install(Kompendium) { - spec = Util.baseSpec - } - // Configures the routes for our API - routing { - // This adds ReDoc support at the `/docs` endpoint. - // By default, it will point at the `/openapi.json` created by Kompendium - redoc(pageTitle = "Simple API Docs") - // You can also use swagger! - swagger(pageTitle = "Swaggerlicious") - // Kompendium infers the route path from the Ktor Route. This will show up as the root path `/` - notarizedGet(simpleGetExample) { - call.respond(HttpStatusCode.OK, BasicResponse(c = UUID.randomUUID().toString(), d = null)) - } - notarizedDelete(simpleDeleteRequest) { - call.respond(HttpStatusCode.NoContent) - } - // It can also infer path parameters - 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") - call.respond(HttpStatusCode.OK, BasicResponse(c = "$a: $b", d = BasicModels.BasicEnum.NO)) - } - } - route("/create") { - notarizedPost(simplePostRequest) { - val request = call.receive() - when (request.d) { - true -> call.respond(HttpStatusCode.OK, BasicResponse(c = "So it is true!", d = null)) - false -> call.respond(HttpStatusCode.OK, BasicResponse(c = "Oh, I knew it!", d = BasicModels.BasicEnum.YES)) - } - } - } - route("/update") { - notarizedPut(simplePutInfo) { - call.respond(HttpStatusCode.NoContent) - } - } - } -} - -// This is a table of contents to hold all the metadata for our various API endpoints -object BasicPlaygroundToC { - /** - * This is the information required to document a simple request. Here we declare that our endpoint - * takes no parameters, and will return an object of type [BasicResponse] - * with status code [HttpStatusCode.OK] - */ - val simpleGetExample = GetInfo( - summary = "Simple, Documented GET Request", - description = "This is to showcase just how easy it is to document your Ktor API!", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "This means everything went as expected!", - examples = mapOf("demo" to BasicResponse(c = "52c099d7-8642-46cc-b34e-22f39b923cf4", BasicModels.BasicEnum.NO)) - ), - tags = setOf("Simple") - ) - - /** - * This showcases a GET request with parameters. Here we declare that our endpoint takes a path and a query - * parameter. This is inferred by the annotations on [BasicParameters.a] and [BasicParameters.b] - * respectively. - */ - val simpleGetExampleWithParameters = GetInfo( - summary = "Simple, Documented GET Request with Parameters", - description = "This showcases how easy it is to document your input parameters!", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "This means everything went as expected!", - examples = mapOf("demo" to BasicResponse(c = "52c099d7-8642-46cc-b34e-22f39b923cf4", BasicModels.BasicEnum.YES)) - ), - tags = setOf("Parameters") - ) - - /** - * This showcases a POST request with a request body of type [BasicRequest] - */ - val simplePostRequest = PostInfo( - summary = "Simple, Documented POST Request", - description = "Showcases how easy it is to document a post request!", - requestInfo = RequestInfo( - description = "This is the required info for this request!", - examples = mapOf("demo" to BasicRequest(true)) - ), - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "This means everything went as expected!", - examples = mapOf("demo" to BasicResponse(c = "So it is true!", null)) - ), - tags = setOf("Simple") - ) - - val simplePutInfo = PutInfo( - summary = "Simple, Documented POST Request", - description = "Showcases how easy it is to document a post request!", - requestInfo = null, - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "This means everything went as expected!", - examples = mapOf("demo" to BasicResponse(c = "So it is true!", null)) - ), - tags = setOf("Simple") - ) - - /** - * This showcases a DELETE request - */ - val simpleDeleteRequest = DeleteInfo( - summary = "Simple, documented DELETE Request", - description = "Cleanin' house", - responseInfo = ResponseInfo( - status = HttpStatusCode.NoContent, - description = "We wiped the files boss" - ), - tags = setOf("Simple") - ) -} - -object BasicModels { - - @Serializable - enum class BasicEnum { - YES, - NO - } - - @Serializable - data class BasicResponse(val c: String, val d: BasicEnum? = null) - - @Serializable - data class BasicParameters( - @Param(type = ParamType.PATH) - val a: String, - @Param(type = ParamType.QUERY) - val b: Int - ) - - @Serializable - data class BasicRequest( - @JsonProperty("best_field") - @SerializedName("best_field") - @SerialName("best_field") - @Field(description = "This is a super important field!!", name = "best_field") - val d: Boolean - ) -} - diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/ConstraintPlayground.kt b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/ConstraintPlayground.kt deleted file mode 100644 index 6f412549e..000000000 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/ConstraintPlayground.kt +++ /dev/null @@ -1,145 +0,0 @@ -package io.bkbn.kompendium.playground - -import io.bkbn.kompendium.annotations.Field -import io.bkbn.kompendium.annotations.FreeFormObject -import io.bkbn.kompendium.annotations.Param -import io.bkbn.kompendium.annotations.ParamType -import io.bkbn.kompendium.annotations.constraint.Format -import io.bkbn.kompendium.annotations.constraint.MaxItems -import io.bkbn.kompendium.annotations.constraint.MaxLength -import io.bkbn.kompendium.annotations.constraint.Maximum -import io.bkbn.kompendium.annotations.constraint.MinItems -import io.bkbn.kompendium.annotations.constraint.MinLength -import io.bkbn.kompendium.annotations.constraint.MinProperties -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 io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.Notarized.notarizedGet -import io.bkbn.kompendium.core.Notarized.notarizedPost -import io.bkbn.kompendium.core.metadata.RequestInfo -import io.bkbn.kompendium.core.metadata.ResponseInfo -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.ConstrainedModels.ConstrainedParams -import io.bkbn.kompendium.playground.ConstrainedModels.ConstrainedRequest -import io.bkbn.kompendium.playground.ConstrainedModels.ConstrainedResponse -import io.bkbn.kompendium.playground.ConstrainedPlaygroundToC.simpleConstrainedGet -import io.bkbn.kompendium.playground.ConstrainedPlaygroundToC.simpleConstrainedPost -import io.bkbn.kompendium.playground.util.Util -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.features.ContentNegotiation -import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.routing.routing -import io.ktor.serialization.json -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement - -fun main() { - embeddedServer( - Netty, - port = 8081, - module = Application::mainModule - ).start(wait = true) -} - -// Application Module -private fun Application.mainModule() { - // Installs Simple JSON Content Negotiation - install(ContentNegotiation) { - json(Json { - serializersModule = KompendiumSerializersModule.module - encodeDefaults = true - explicitNulls = false - }) - } - // Installs the Kompendium Plugin and sets up baseline server metadata - install(Kompendium) { - spec = Util.baseSpec - } - // Configures the routes for our API - routing { - // This adds ReDoc support at the `/docs` endpoint. - // By default, it will point at the `/openapi.json` created by Kompendium - redoc(pageTitle = "Constrained API Docs") - notarizedGet(simpleConstrainedGet) { - call.respond(HttpStatusCode.OK, ConstrainedResponse(100)) - } - notarizedPost(simpleConstrainedPost) { - call.respond(HttpStatusCode.OK, ConstrainedResponse(100)) - } - } -} - -object ConstrainedPlaygroundToC { - val simpleConstrainedGet = GetInfo( - summary = "Simple, Constrained get", - description = "Shows that you can set constraints on given fields", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "Cool stuff", - examples = mapOf("demo" to ConstrainedResponse()) - ) - ) - val simpleConstrainedPost = - PostInfo( - summary = "Simple, Constrained post", - description = "Shows that you can set constraints on given fields", - requestInfo = RequestInfo( - description = "Cool stuff" - ), - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "Cool stuff", - examples = mapOf("demo" to ConstrainedResponse()) - ) - ) -} - -object ConstrainedModels { - @Serializable - data class ConstrainedResponse( - @Minimum("5") - @Maximum("101") - @MultipleOf("5") - val a: Int = 10, - @MinItems(100) - @MaxItems(1000) - @UniqueItems - val b: List = (0..500).map { it.toString() } - ) - - @Serializable - data class ConstrainedParams( - @Field(description = "This is a really important field!") - @Param(ParamType.QUERY) - @MinLength(11) - @MaxLength(11) - @Pattern("^\\d{3}-\\d{2}-\\d{4}\$") - @Format("password") - val ssn: String = "111-11-1111" - ) - - @Serializable - data class ConstrainedRequest( - val fieldy: Field, - @MinProperties(1) - @FreeFormObject - val data: JsonElement? = null - ) { - @Serializable - data class Field(val nesty: Nested?) { - @Serializable - data class Nested(val a: Int = 100, val b: Boolean) - } - } -} diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/CustomTypePlayground.kt b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/CustomTypePlayground.kt deleted file mode 100644 index d7e5e428c..000000000 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/CustomTypePlayground.kt +++ /dev/null @@ -1,74 +0,0 @@ -package io.bkbn.kompendium.playground - -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.Notarized.notarizedGet -import io.bkbn.kompendium.core.metadata.ResponseInfo -import io.bkbn.kompendium.core.metadata.method.GetInfo -import io.bkbn.kompendium.core.routes.redoc -import io.bkbn.kompendium.oas.schema.SimpleSchema -import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule -import io.bkbn.kompendium.playground.CustomTypePlaygroundToC.simpleGetExample -import io.bkbn.kompendium.playground.util.Util -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.features.ContentNegotiation -import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.routing.routing -import io.ktor.serialization.json -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json - -fun main() { - embeddedServer( - Netty, - port = 8081, - module = Application::mainModule - ).start(wait = true) -} - -private fun Application.mainModule() { - install(ContentNegotiation) { - json(Json { - serializersModule = KompendiumSerializersModule.module - encodeDefaults = true - explicitNulls = false - }) - } - install(Kompendium) { - spec = Util.baseSpec - // Tells Kompendium how to handle a specific type - addCustomTypeSchema(Instant::class, SimpleSchema("string", format = "date-time")) - } - routing { - redoc(pageTitle = "Custom overridden type Docs") - notarizedGet(simpleGetExample) { - call.respond(HttpStatusCode.OK, CustomTypeModels.AwesomeThingHappened("this http call!", Clock.System.now())) - } - } -} - -private object CustomTypePlaygroundToC { - val simpleGetExample = GetInfo( - summary = "Simple, Documented GET Request", - description = "This is to showcase just how easy it is to document your Ktor API!", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "This means everything went as expected!", - ), - tags = setOf("Simple") - ) -} - -private object CustomTypeModels { - @Serializable - data class AwesomeThingHappened( - val thing: String, - val timestamp: Instant - ) -} diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/ExceptionPlayground.kt b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/ExceptionPlayground.kt deleted file mode 100644 index f23564030..000000000 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/ExceptionPlayground.kt +++ /dev/null @@ -1,109 +0,0 @@ -package io.bkbn.kompendium.playground - -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.Notarized.notarizedGet -import io.bkbn.kompendium.core.metadata.ExceptionInfo -import io.bkbn.kompendium.core.metadata.ResponseInfo -import io.bkbn.kompendium.core.metadata.method.GetInfo -import io.bkbn.kompendium.core.routes.redoc -import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule -import io.bkbn.kompendium.playground.ExceptionPlaygroundToC.simpleGetExample -import io.bkbn.kompendium.playground.util.Util -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.features.ContentNegotiation -import io.ktor.features.StatusPages -import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.routing.routing -import io.ktor.serialization.json -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import kotlin.reflect.typeOf -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -import java.time.LocalDateTime - -// Application Entrypoint -fun main() { - embeddedServer( - Netty, - port = 8081, - module = Application::mainModule - ).start(wait = true) -} - -// Application Module -private fun Application.mainModule() { - // Installs Simple JSON Content Negotiation - install(ContentNegotiation) { - json(Json { - serializersModule = KompendiumSerializersModule.module - encodeDefaults = true - explicitNulls = false - }) - } - // Installs the Kompendium Plugin and sets up baseline server metadata - install(Kompendium) { - spec = Util.baseSpec - } - install(StatusPages) { - exception { - call.respond(HttpStatusCode.BadRequest, ExceptionModels.ExceptionResponse("Bad user thing happened")) - } - exception { - call.respond(HttpStatusCode.BadRequest, ExceptionModels.ExceptionResponse("Bad coder thing happened")) - } - } - // Configures the routes for our API - routing { - // This adds ReDoc support at the `/docs` endpoint. - // By default, it will point at the `/openapi.json` created by Kompendium - redoc(pageTitle = "Simple Exception Examples") - notarizedGet(simpleGetExample) { - if (LocalDateTime.now().second % 2 == 0) { - throw ExceptionModels.BadCoderException() - } else { - throw ExceptionModels.BadUserException() - } - } - } -} - -// This is a table of contents to hold all the metadata for our various API endpoints -object ExceptionPlaygroundToC { - private val simpleException = ExceptionInfo( - responseType = typeOf(), - status = HttpStatusCode.BadRequest, - description = "Indicates that the user is a TOTAL LOSER hehehe" - ) - - private val badCoderSignal = ExceptionInfo( - responseType = typeOf(), - status = HttpStatusCode.InternalServerError, - description = "Indicates that the engineer is a TOTAL NOOB mwahahaha" - ) - - val simpleGetExample = GetInfo( - summary = "A route that throws an exception", - description = "You will never see the real response MWAHAHAHA", - responseInfo = ResponseInfo( - HttpStatusCode.OK, - description = "Not gonna happen pal" - ), - canThrow = setOf(simpleException, badCoderSignal) - ) -} - -object ExceptionModels { - @Serializable - data class WontGetHere(val result: Int) - - @Serializable - data class ExceptionResponse(val message: String) - - class BadUserException : Exception() - - class BadCoderException : Exception() -} diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/GenericPlayground.kt b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/GenericPlayground.kt deleted file mode 100644 index d08d36623..000000000 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/GenericPlayground.kt +++ /dev/null @@ -1,83 +0,0 @@ -package io.bkbn.kompendium.playground - -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.Notarized.notarizedGet -import io.bkbn.kompendium.core.metadata.ResponseInfo -import io.bkbn.kompendium.core.metadata.method.GetInfo -import io.bkbn.kompendium.core.routes.redoc -import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule -import io.bkbn.kompendium.playground.GenericPlaygroundToC.simpleGenericGet -import io.bkbn.kompendium.playground.util.Util -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.features.ContentNegotiation -import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.routing.routing -import io.ktor.serialization.json -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json - -/** - * Application entrypoint. Run this and head on over to `localhost:8081/docs` - * to see a very simple yet beautifully documented API - */ -fun main() { - embeddedServer( - Netty, - port = 8081, - module = Application::mainModule - ).start(wait = true) -} - -// Application Module -private fun Application.mainModule() { - // Installs Simple JSON Content Negotiation - install(ContentNegotiation) { - json(Json { - serializersModule = KompendiumSerializersModule.module - encodeDefaults = true - explicitNulls = false - }) - } - // Installs the Kompendium Plugin and sets up baseline server metadata - install(Kompendium) { - spec = Util.baseSpec - } - routing { - redoc(pageTitle = "Simple API Docs") - notarizedGet(simpleGenericGet) { - call.respond( - HttpStatusCode.OK, - GenericModels.Foosy(GenericModels.Barzo(5), listOf("hey", "now", "you're", "an", "all-start")) - ) - } - } -} - - -// This is a table of contents to hold all the metadata for our various API endpoints -object GenericPlaygroundToC { - val simpleGenericGet = GetInfo>( - summary = "Lots 'o Generics", - description = "Pretty funky huh", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "Enjoy all this data, pal" - ) - ) -} - -object GenericModels { - @Serializable - data class Foosy(val test: T, val otherThing: List) - - @Serializable - data class Barzo(val result: G) - - @Serializable - data class SimpleG(val result: G) -} diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/GsonSerializationPlayground.kt b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/GsonSerializationPlayground.kt deleted file mode 100644 index 89dc39f2b..000000000 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/GsonSerializationPlayground.kt +++ /dev/null @@ -1,49 +0,0 @@ -package io.bkbn.kompendium.playground - -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.Notarized.notarizedPost -import io.bkbn.kompendium.core.routes.redoc -import io.bkbn.kompendium.playground.util.Util -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.features.ContentNegotiation -import io.ktor.gson.gson -import io.ktor.http.HttpStatusCode -import io.ktor.request.receive -import io.ktor.response.respond -import io.ktor.routing.route -import io.ktor.routing.routing -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty - -fun main() { - embeddedServer( - Netty, - port = 8081, - module = Application::mainModule - ).start(wait = true) -} - -private fun Application.mainModule() { - install(ContentNegotiation) { - gson { - setPrettyPrinting() - } - } - install(Kompendium) { - spec = Util.baseSpec - } - routing { - redoc() - route("/create") { - notarizedPost(BasicPlaygroundToC.simplePostRequest) { - val request = call.receive() - when (request.d) { - true -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "So it is true!", null)) - false -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "Oh, I knew it!", null)) - } - } - } - } -} diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/JacksonSerializationPlayground.kt b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/JacksonSerializationPlayground.kt deleted file mode 100644 index 16feb5a75..000000000 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/JacksonSerializationPlayground.kt +++ /dev/null @@ -1,52 +0,0 @@ -package io.bkbn.kompendium.playground - -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.databind.SerializationFeature -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.Notarized.notarizedPost -import io.bkbn.kompendium.core.routes.redoc -import io.bkbn.kompendium.playground.util.Util -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.features.ContentNegotiation -import io.ktor.http.HttpStatusCode -import io.ktor.jackson.jackson -import io.ktor.request.receive -import io.ktor.response.respond -import io.ktor.routing.route -import io.ktor.routing.routing -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty - -fun main() { - embeddedServer( - Netty, - port = 8081, - module = Application::mainModule - ).start(wait = true) -} - -private fun Application.mainModule() { - install(ContentNegotiation) { - jackson { - enable(SerializationFeature.INDENT_OUTPUT) - setSerializationInclusion(JsonInclude.Include.NON_NULL) - } - } - install(Kompendium) { - spec = Util.baseSpec - } - routing { - redoc() - route("/create") { - notarizedPost(BasicPlaygroundToC.simplePostRequest) { - val request = call.receive() - when (request.d) { - true -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "So it is true!")) - false -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "Oh, I knew it!")) - } - } - } - } -} diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/LocationPlayground.kt b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/LocationPlayground.kt deleted file mode 100644 index a2727f9ab..000000000 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/LocationPlayground.kt +++ /dev/null @@ -1,130 +0,0 @@ -package io.bkbn.kompendium.playground - -import io.bkbn.kompendium.annotations.Param -import io.bkbn.kompendium.annotations.ParamType -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.metadata.ResponseInfo -import io.bkbn.kompendium.core.metadata.method.GetInfo -import io.bkbn.kompendium.core.routes.redoc -import io.bkbn.kompendium.locations.NotarizedLocation.notarizedGet -import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule -import io.bkbn.kompendium.playground.LocationsToC.ohBoiUCrazy -import io.bkbn.kompendium.playground.LocationsToC.testLocation -import io.bkbn.kompendium.playground.LocationsToC.testNestLocation -import io.bkbn.kompendium.playground.util.Util -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.features.ContentNegotiation -import io.ktor.http.HttpStatusCode -import io.ktor.locations.Location -import io.ktor.locations.Locations -import io.ktor.response.respondText -import io.ktor.routing.routing -import io.ktor.serialization.json -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json - -/** - * Application entrypoint. Run this and head on over to `localhost:8081/docs` - * to see a very simple yet beautifully documented API - */ -fun main() { - embeddedServer( - Netty, - port = 8081, - module = Application::mainModule - ).start(wait = true) -} - -private fun Application.mainModule() { - install(ContentNegotiation) { - json(Json { - serializersModule = KompendiumSerializersModule.module - encodeDefaults = true - explicitNulls = false - }) - } - install(Kompendium) { - spec = Util.baseSpec - } - install(Locations) - routing { - redoc() - /** - * Notice the difference here between this and the standard notarizedGet! tl contains your input parameters - */ - notarizedGet(testLocation) { tl -> - call.respondText { tl.name } - } - notarizedGet(testNestLocation) { tnl -> - call.respondText { tnl.idk.toString() } - } - notarizedGet(ohBoiUCrazy) { obuc -> - call.respondText { obuc.parent.parent.name } - } - } -} - -private object LocationsToC { - val testLocation = GetInfo( - summary = "Shallow", - description = "Ez Pz Lemon Squeezy", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "Great!" - ) - ) - val testNestLocation = GetInfo( - summary = "Nested", - description = "Gettin' scary", - responseInfo = ResponseInfo( - status = HttpStatusCode.Continue, - description = "Hmmm" - ) - ) - val ohBoiUCrazy = GetInfo( - summary = "Example Deeply Nested", - description = "We deep now", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "nice", - examples = mapOf("test" to LocationModels.ExampleResponse(c = "spud")) - ), - ) -} - -// For more info make sure to read through the Ktor location docs -// Additionally, make sure to note that even though we define the locations here, we still must annotate fields -// with KompendiumParam!!! -object TestLocationsParent { - - @Location("test/{name}") - data class TestLocations( - @Param(ParamType.PATH) - val name: String, - ) { - - @Location("/spaghetti") - data class NestedTestLocations( - @Param(ParamType.QUERY) - val idk: Int, - val parent: TestLocations - ) { - - @Location("/hehe/{madness}") - data class OhBoiUCrazy( - @Param(ParamType.PATH) - val madness: Boolean, - val parent: NestedTestLocations - ) - } - } -} - -object LocationModels { - @Serializable - data class ExampleResponse(val c: String) -} diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/PolymorphicPlayground.kt b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/PolymorphicPlayground.kt deleted file mode 100644 index 155769404..000000000 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/PolymorphicPlayground.kt +++ /dev/null @@ -1,82 +0,0 @@ -package io.bkbn.kompendium.playground - -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.Notarized.notarizedGet -import io.bkbn.kompendium.core.metadata.ResponseInfo -import io.bkbn.kompendium.core.metadata.method.GetInfo -import io.bkbn.kompendium.core.routes.redoc -import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule -import io.bkbn.kompendium.playground.PolymorphicPlaygroundToC.polymorphicExample -import io.bkbn.kompendium.playground.util.Util -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.features.ContentNegotiation -import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.routing.routing -import io.ktor.serialization.json -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json - -/** - * Application entrypoint. Run this and head on over to `localhost:8081/docs` - * to see a very simple yet beautifully documented API - */ -fun main() { - embeddedServer( - Netty, - port = 8081, - module = Application::mainModule - ).start(wait = true) -} - -private fun Application.mainModule() { - // Installs Simple JSON Content Negotiation - install(ContentNegotiation) { - json(Json { - serializersModule = KompendiumSerializersModule.module - encodeDefaults = true - explicitNulls = false - }) - } - // Installs the Kompendium Plugin and sets up baseline server metadata - install(Kompendium) { - spec = Util.baseSpec - } - // Configures the routes for our API - routing { - redoc(pageTitle = "Polymorphic API Examples") - notarizedGet(polymorphicExample) { - call.respond(HttpStatusCode.OK, PolymorphicModels.OneJamma(1337)) - } - } -} - -// This is a table of contents to hold all the metadata for our various API endpoints -object PolymorphicPlaygroundToC { - val polymorphicExample = GetInfo( - summary = "C'mon and Slam", - description = "And welcome to the jam", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "You have successfully slammed and/or jammed", - examples = mapOf( - "one" to PolymorphicModels.OneJamma(42), - "two" to PolymorphicModels.AnothaJamma(4.2) - ) - ) - ) -} - -object PolymorphicModels { - sealed interface SlammaJamma - - @Serializable - data class OneJamma(val a: Int) : SlammaJamma - - @Serializable - data class AnothaJamma(val b: Double) : SlammaJamma -} diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/RecursionPlayground.kt b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/RecursionPlayground.kt deleted file mode 100644 index 28b0a2116..000000000 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/RecursionPlayground.kt +++ /dev/null @@ -1,103 +0,0 @@ -package io.bkbn.kompendium.playground - -import io.bkbn.kompendium.annotations.Referenced -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.Notarized.notarizedGet -import io.bkbn.kompendium.core.metadata.ResponseInfo -import io.bkbn.kompendium.core.metadata.method.GetInfo -import io.bkbn.kompendium.core.routes.redoc -import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule -import io.bkbn.kompendium.playground.util.Util -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.features.ContentNegotiation -import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.routing.route -import io.ktor.routing.routing -import io.ktor.serialization.json -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json - -enum class ColumnMode { - NULLABLE, - REQUIRED, - REPEATED -} - -@Referenced // Indicates that Kompendium should store this class as a $ref component. -@Serializable -data class ColumnSchema( - val name: String, - val type: String, - val description: String, - val mode: ColumnMode, - val subColumns: List = emptyList() -) - -sealed interface RecursiveSlammaJamma - -@Serializable -data class OneJamma(val a: Int) : RecursiveSlammaJamma - -@Serializable -data class AnothaJamma(val b: Float) : RecursiveSlammaJamma - -@Referenced -@Serializable -data class InsaneJamma(val c: RecursiveSlammaJamma) : RecursiveSlammaJamma - -fun main() { - embeddedServer( - Netty, - port = 8081, - module = Application::mainModule - ).start(wait = true) -} - -private fun Application.mainModule() { - install(ContentNegotiation) { - json(Json { - serializersModule = KompendiumSerializersModule.module - encodeDefaults = true - explicitNulls = false - }) - } - install(Kompendium) { - spec = Util.baseSpec - } - routing { - redoc(pageTitle = "Recursive API Docs") - notarizedGet( - GetInfo( - summary = "Its recursive", - description = "This is how we do it!", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "This means everything went as expected!", - ), - tags = setOf("Simple") - ) - ) { - call.respond(HttpStatusCode.OK, "Nice!") - } - route("cmon_and_slam") { - notarizedGet( - GetInfo( - summary = "Its recursive", - description = "This is how we do it!", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "This means everything went as expected!", - ), - tags = setOf("Simple") - ) - ) { - call.respond(HttpStatusCode.OK, "Nice!") - } - } - } -} diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/SerializerOverridePlayground.kt b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/SerializerOverridePlayground.kt deleted file mode 100644 index 8aeaad1f5..000000000 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/SerializerOverridePlayground.kt +++ /dev/null @@ -1,85 +0,0 @@ -package io.bkbn.kompendium.playground - -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.Notarized.notarizedGet -import io.bkbn.kompendium.core.metadata.ResponseInfo -import io.bkbn.kompendium.core.metadata.method.GetInfo -import io.bkbn.kompendium.core.routes.swagger -import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule -import io.bkbn.kompendium.playground.Customization.customSerializer -import io.bkbn.kompendium.playground.SerializerOverridePlaygroundToC.getExample -import io.bkbn.kompendium.playground.util.Util -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.features.ContentNegotiation -import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.response.respondText -import io.ktor.routing.get -import io.ktor.routing.route -import io.ktor.routing.routing -import io.ktor.serialization.json -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json - -fun main() { - embeddedServer( - Netty, - port = 8081, - module = Application::mainModule - ).start(wait = true) -} - -private fun Application.mainModule() { - install(ContentNegotiation) { - json(Json { - encodeDefaults = false - }) - } - install(Kompendium) { - spec = Util.baseSpec - openApiJson = { spec -> - route("/openapi.json") { - get { - call.respondText { customSerializer.encodeToString(spec) } - } - } - } - } - routing { - swagger(pageTitle = "Docs") - notarizedGet(getExample) { - call.respond(HttpStatusCode.OK, SerializerOverrideModel.OhYeaCoolData(null)) - } - } -} - -object SerializerOverridePlaygroundToC { - val getExample = GetInfo( - summary = "Overriding the serializer", - description = "Pretty neat!", - responseInfo = ResponseInfo( - status = HttpStatusCode.OK, - description = "This means everything went as expected!", - examples = mapOf("demo" to SerializerOverrideModel.OhYeaCoolData(null)) - ), - tags = setOf("Custom") - ) -} - -object SerializerOverrideModel { - @Serializable - data class OhYeaCoolData(val num: Int?, val test: String = "gonezo") -} - -object Customization { - val customSerializer = Json { - serializersModule = KompendiumSerializersModule.module - encodeDefaults = true - explicitNulls = false - } -} diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/SwaggerPlayground.kt b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/SwaggerPlayground.kt deleted file mode 100644 index 72415e06c..000000000 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/SwaggerPlayground.kt +++ /dev/null @@ -1,94 +0,0 @@ -package io.bkbn.kompendium.playground - -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.Notarized.notarizedGet -import io.bkbn.kompendium.oas.component.Components -import io.bkbn.kompendium.oas.security.OAuth -import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule -import io.bkbn.kompendium.playground.util.Util -import io.bkbn.kompendium.swagger.JsConfig -import io.bkbn.kompendium.swagger.SwaggerUI -import io.ktor.application.Application -import io.ktor.application.call -import io.ktor.application.install -import io.ktor.features.ContentNegotiation -import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.routing.routing -import io.ktor.serialization.json -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import java.net.URI -import kotlinx.serialization.json.Json -import java.util.UUID -import kotlinx.serialization.ExperimentalSerializationApi - -/** - * Application entrypoint. Run this and head on over to `localhost:8081/docs` - * to see a very simple yet beautifully documented API - */ -@ExperimentalSerializationApi -fun main() { - embeddedServer( - Netty, - port = 8081, - module = Application::mainModule - ).start(wait = true) -} - -const val securitySchemaName = "oauth" - -// Application Module -@ExperimentalSerializationApi -private fun Application.mainModule() { - // Installs Simple JSON Content Negotiation - install(ContentNegotiation) { - json(Json { - serializersModule = KompendiumSerializersModule.module - encodeDefaults = true - explicitNulls = false - }) - } - // Installs the Kompendium Plugin and sets up baseline server metadata - install(Kompendium) { - spec = Util.baseSpec.copy(components = Components(securitySchemes = mutableMapOf( - securitySchemaName to OAuth(description = "OAuth Auth", flows = OAuth.Flows( - authorizationCode = OAuth.Flows.AuthorizationCode( - authorizationUrl = "http://localhost/auth", - tokenUrl = "http://localhost/token" - ) - )) - ))) - } - - install(SwaggerUI) { - swaggerUrl = "/swagger-ui" - jsConfig = JsConfig( - specs = mapOf( - "My API v1" to URI("/openapi.json"), - "My API v2" to URI("/openapi.json") - ), - // This part will be inserted after Swagger UI is loaded in Browser. - // Example is prepared according to this documentation: https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/ - jsInit = { - """ - window.ui.initOAuth({ - clientId: 'CLIENT_ID', - clientSecret: 'CLIENT_SECRET', - realm: 'MY REALM', - appName: 'TEST APP', - useBasicAuthenticationWithAccessCodeGrant: true - }); - """ - } - ) - } - - // Configures the routes for our API - routing { - // Kompendium infers the route path from the Ktor Route. This will show up as the root path `/` - notarizedGet(BasicPlaygroundToC.simpleGetExample.copy(securitySchemes = setOf(securitySchemaName))) { - call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = UUID.randomUUID().toString())) - } - } -} diff --git a/kompendium-swagger-ui/Module.md b/kompendium-swagger-ui/Module.md deleted file mode 100644 index 2a63c2c42..000000000 --- a/kompendium-swagger-ui/Module.md +++ /dev/null @@ -1,52 +0,0 @@ -# Module kompendium-swagger-ui - -This module is responsible for frontend part of `SwaggerUI` built on top on WebJar. - -Solution is wrapped into KTor plugin that may be tuned with configuration properties according to -Swagger UI official documentation (`JsConfig` is responsible for that): - -https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/ - -Current implementation covers only most important part of specification properties (we'll be adding more time to time) - -# Module configuration - -Minimal SwaggerUI plugin configuration: - -```kotlin -import io.bkbn.kompendium.swagger.JsConfig -import io.bkbn.kompendium.swagger.SwaggerUI -import io.ktor.application.install - -install(SwaggerUI) { - swaggerUrl = "/swagger-ui" - jsConfig = JsConfig( - specs = mapOf( - "Your API name" to URI("/openapi.json") - ) - ) -} - -``` - -Additionally, there is a way to add additional initialization code in SwaggerUI JS. -`JsConfig.jsInit` is responsible for that: - -```kotlin -JsConfig( - //... - jsInit = { - """ - ui.initOAuth(...) - """ - } -) -``` - -# Playground example - -There is an example that demonstrates how this plugin is working in `kompendium-playground` module: - -``` -io.bkbn.kompendium.playground.SwaggerPlayground.kt -``` \ No newline at end of file diff --git a/kompendium-swagger-ui/build.gradle.kts b/kompendium-swagger-ui/build.gradle.kts deleted file mode 100644 index 596017c7e..000000000 --- a/kompendium-swagger-ui/build.gradle.kts +++ /dev/null @@ -1,35 +0,0 @@ -plugins { - kotlin("jvm") - id("io.bkbn.sourdough.library.jvm") - id("io.gitlab.arturbosch.detekt") - id("com.adarshr.test-logger") - id("org.jetbrains.dokka") - id("maven-publish") - id("java-library") - id("signing") - id("java-test-fixtures") -} - -sourdough { - libraryName.set("Kompendium Swagger") - libraryDescription.set("Offers Swagger as a bundled WebJAR for Ktor") -} - -dependencies { - val ktorVersion: String by project - - implementation(projects.kompendiumCore) - implementation(group = "io.ktor", name = "ktor-server-core", version = ktorVersion) - implementation(group = "org.webjars", name = "webjars-locator-core", version = "0.52") - implementation(group = "org.webjars", name = "swagger-ui", version = "4.13.2") - - testImplementation(testFixtures(projects.kompendiumCore)) -} - -testing { - suites { - named("test", JvmTestSuite::class) { - useJUnitJupiter() - } - } -} diff --git a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/JsConfig.kt b/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/JsConfig.kt deleted file mode 100644 index d4c90b246..000000000 --- a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/JsConfig.kt +++ /dev/null @@ -1,29 +0,0 @@ -package io.bkbn.kompendium.swagger - -import java.net.URI - -// This class represents this specification: -// https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/ -data class JsConfig( - val specs: Map, - val deepLinking: Boolean = true, - val displayOperationId: Boolean = true, - val displayRequestDuration: Boolean = true, - val docExpansion: String = "none", - val operationsSorter: String = "alpha", - val defaultModelExpandDepth: Int = 4, - val defaultModelsExpandDepth: Int = 4, - val persistAuthorization: Boolean = true, - val tagsSorter: String = "alpha", - val tryItOutEnabled: Boolean = false, - val validatorUrl: String? = null, - val jsInit: () -> String? = { null } -) - -internal fun JsConfig.toJsProps(): String = asMap() - .filterKeys { !setOf("specs", "jsInit").contains(it) } - .map{ "${it.key}: ${it.value.toJs()}" } - .joinToString(separator = ",\n ") - -internal fun JsConfig.getSpecUrlsProps(): String = - if (specs.isEmpty()) "[]" else specs.map { "{url: ${it.value.toJs()}, name: ${it.key.toJs()}}" }.toString() diff --git a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/JsUtils.kt b/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/JsUtils.kt deleted file mode 100644 index 0c6716165..000000000 --- a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/JsUtils.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.bkbn.kompendium.swagger - -internal const val JS_UNDEFINED = "undefined" - -internal fun String?.toJs(): String = this?.let { "'$it'" } ?: JS_UNDEFINED -internal fun Boolean?.toJs(): String = this?.toString() ?: JS_UNDEFINED -internal fun Int?.toJs(): String = this?.toString() ?: JS_UNDEFINED - -internal fun Any?.toJs(): String = when(this) { - is String? -> toJs() - is Int? -> toJs() - is Boolean? -> toJs() - else -> toString().toJs() -} diff --git a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/ReflectionUtils.kt b/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/ReflectionUtils.kt deleted file mode 100644 index 1d9f3fbd4..000000000 --- a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/ReflectionUtils.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.bkbn.kompendium.swagger - -import kotlin.reflect.full.memberProperties - -internal inline fun T.asMap(): Map = - T::class.memberProperties.associate { it.name to it.get(this) } diff --git a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerUI.kt b/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerUI.kt deleted file mode 100644 index 2bb90273d..000000000 --- a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerUI.kt +++ /dev/null @@ -1,78 +0,0 @@ -package io.bkbn.kompendium.swagger - -import io.ktor.application.Application -import io.ktor.application.ApplicationFeature -import io.ktor.application.call -import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.response.respondRedirect -import io.ktor.routing.Routing -import io.ktor.routing.get -import io.ktor.routing.routing -import io.ktor.util.AttributeKey -import java.net.URI -import org.webjars.WebJarAssetLocator - -@Suppress("unused") -class SwaggerUI(val config: Configuration) { - - class Configuration { - // The primary Swagger-UI page (will redirect to target page) - var swaggerUrl: String = "/swagger-ui" - // The Root path to Swagger resources (in most cases a path to the webjar resources) - var swaggerBaseUrl: String = "/webjars/swagger-ui" - // Configuration for SwaggerUI JS initialization - lateinit var jsConfig: JsConfig - // Application context path (for example if application have the following path: http://domain.com/app, use: "/app") - var contextPath: String = "" - } - - companion object Feature: ApplicationFeature { - - private fun Configuration.toInternal(): InternalConfiguration = InternalConfiguration( - swaggerUrl = URI(swaggerUrl), - swaggerBaseUrl = URI(swaggerBaseUrl), - jsConfig = jsConfig, - contextPath = contextPath - ) - - private data class InternalConfiguration( - val swaggerUrl: URI, - val swaggerBaseUrl: URI, - val jsConfig: JsConfig, - val contextPath: String - ) { - val redirectIndexUrl: String = "${contextPath}${swaggerBaseUrl}/index.html" - } - - private val locator = WebJarAssetLocator() - - private val installRoutes: Routing.(InternalConfiguration) -> Unit = { config -> - - get(config.swaggerUrl.toString()) { - call.respondRedirect(config.redirectIndexUrl) - } - - get("${config.swaggerBaseUrl}/{filename}") { - call.parameters["filename"]!!.let { filename -> - when(filename) { - "swagger-initializer.js" -> - locator.getSwaggerInitializerContent(jsConfig = config.jsConfig) - else -> - locator.getSwaggerResourceContent(path = filename) - } - }.let { call.respond(HttpStatusCode.OK, it) } - } - } - - override val key: AttributeKey = AttributeKey("SwaggerUI") - - override fun install(pipeline: Application, configure: Configuration.() -> Unit): SwaggerUI { - pipeline.routing { }.let { routing -> - val configuration: Configuration = Configuration().apply(configure) - installRoutes(routing, configuration.toInternal()) - return SwaggerUI(config = configuration) - } - } - } -} diff --git a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerWebJarUtils.kt b/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerWebJarUtils.kt deleted file mode 100644 index 1397e1493..000000000 --- a/kompendium-swagger-ui/src/main/kotlin/io/bkbn/kompendium/swagger/SwaggerWebJarUtils.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.bkbn.kompendium.swagger - -import io.ktor.features.NotFoundException -import io.ktor.http.content.ByteArrayContent -import java.net.URL -import org.webjars.WebJarAssetLocator - -internal fun WebJarAssetLocator.getSwaggerResource(path: String): URL = - this::class.java.getResource(getFullPath("swagger-ui", path).let { if (it.startsWith("/")) it else "/$it" }) - ?: throw NotFoundException("Resource not found: $path") - -internal fun WebJarAssetLocator.getSwaggerResourceContent(path: String): ByteArrayContent = - ByteArrayContent(getSwaggerResource(path).readBytes()) - -internal fun WebJarAssetLocator.getSwaggerInitializerContent(jsConfig: JsConfig): ByteArrayContent = ByteArrayContent( - getSwaggerResource(path = "swagger-initializer.js").readText() - .replaceFirst("url: \"https://petstore.swagger.io/v2/swagger.json\",", "urls: ${jsConfig.getSpecUrlsProps()},") - .replaceFirst("deepLinking: true", jsConfig.toJsProps()) - .let { content -> - jsConfig.jsInit()?.let { - content.replaceFirst("});", "});\n$it") - } ?: content - }.toByteArray() - ) diff --git a/kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/SwaggerUiTest.kt b/kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/SwaggerUiTest.kt deleted file mode 100644 index 00113b2f1..000000000 --- a/kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/SwaggerUiTest.kt +++ /dev/null @@ -1,54 +0,0 @@ -package io.bkbn.kompendium.swagger - -import io.bkbn.kompendium.swagger.TestHelpers.TEST_SWAGGER_UI_INDEX -import io.bkbn.kompendium.swagger.TestHelpers.TEST_SWAGGER_UI_INIT_JS -import io.bkbn.kompendium.swagger.TestHelpers.TEST_SWAGGER_UI_ROOT -import io.bkbn.kompendium.swagger.TestHelpers.compareRedirect -import io.bkbn.kompendium.swagger.TestHelpers.compareResource -import io.bkbn.kompendium.swagger.TestHelpers.withSwaggerApplication -import io.kotest.core.spec.style.DescribeSpec - -class SwaggerUiTest: DescribeSpec ({ - - describe("Swagger UI resources") { - - it ("Redirects /swagger-ui -> index.html") { - withSwaggerApplication { - compareRedirect(TEST_SWAGGER_UI_ROOT, TEST_SWAGGER_UI_INDEX) - } - } - - it ("Can return original: index.html") { - withSwaggerApplication { - compareResource(TEST_SWAGGER_UI_INDEX, listOf( - "Swagger UI", - "
", - "src=\"./swagger-initializer.js\"" - )) - } - } - - it("Can return generated: swagger-initializer.js") { - withSwaggerApplication { - compareResource(TEST_SWAGGER_UI_INIT_JS, listOf( - "url: '/openapi.json', name: 'My API v1'", - "url: '/openapi.json', name: 'My API v2'", - "defaultModelExpandDepth: 4", - "defaultModelsExpandDepth: 4", - "displayOperationId: true", - "displayRequestDuration: true", - "operationsSorter: 'alpha'", - "persistAuthorization: true", - "tagsSorter: 'alpha'", - "window.ui.initOAuth", - "clientId: 'CLIENT_ID'", - "clientSecret: 'CLIENT_SECRET'", - "realm: 'MY REALM'", - "appName: 'TEST APP'", - "useBasicAuthenticationWithAccessCodeGrant: true" - )) - } - } - } - -}) diff --git a/kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/TestHelpers.kt b/kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/TestHelpers.kt deleted file mode 100644 index dd63347c2..000000000 --- a/kompendium-swagger-ui/src/test/kotlin/io/bkbn/kompendium/swagger/TestHelpers.kt +++ /dev/null @@ -1,79 +0,0 @@ -package io.bkbn.kompendium.swagger - -import io.bkbn.kompendium.core.Kompendium -import io.bkbn.kompendium.core.fixtures.kompendium -import io.kotest.assertions.ktor.shouldHaveStatus -import io.kotest.matchers.shouldBe -import io.kotest.matchers.shouldNotBe -import io.kotest.matchers.string.shouldContain -import io.ktor.application.Application -import io.ktor.application.install -import io.ktor.http.HttpHeaders -import io.ktor.http.HttpMethod -import io.ktor.http.HttpStatusCode -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 java.net.URI - -object TestHelpers { - - const val TEST_SWAGGER_UI_ROOT = "/swagger-ui" - const val TEST_SWAGGER_UI_INDEX = "/webjars/swagger-ui/index.html" - const val TEST_SWAGGER_UI_INIT_JS = "/webjars/swagger-ui/swagger-initializer.js" - - // The same config the same as in Playground - private val basicSwaggerUiConfig: SwaggerUI.Configuration.() -> Unit = { - swaggerUrl = "/swagger-ui" - jsConfig = JsConfig( - specs = mapOf( - "My API v1" to URI("/openapi.json"), - "My API v2" to URI("/openapi.json") - ), - jsInit = { - """ - window.ui.initOAuth({ - clientId: 'CLIENT_ID', - clientSecret: 'CLIENT_SECRET', - realm: 'MY REALM', - appName: 'TEST APP', - useBasicAuthenticationWithAccessCodeGrant: true - }); - """ - } - ) - } - - fun TestApplicationEngine.compareRedirect(resourceName: String, targetResource: String) { - handleRequest(HttpMethod.Get, resourceName).apply { - response shouldHaveStatus HttpStatusCode.Found - response.headers[HttpHeaders.Location] shouldBe targetResource - } - } - - fun TestApplicationEngine.compareResource(resourceName: String, mustContain: List) { - handleRequest(HttpMethod.Get, resourceName).apply { - response shouldHaveStatus HttpStatusCode.OK - response.content shouldNotBe null - mustContain.forEach { - response.content!! shouldContain it - } - } - } - - fun withSwaggerApplication( - moduleFunction: Application.() -> Unit = {}, - kompendiumConfigurer: Kompendium.Configuration.() -> Unit = {}, - swaggerUIConfigurer: SwaggerUI.Configuration.() -> Unit = basicSwaggerUiConfig, - test: TestApplicationEngine.() -> R - ) { - withApplication(createTestEnvironment()) { - moduleFunction(application.apply { - kompendium(kompendiumConfigurer) - install(SwaggerUI, swaggerUIConfigurer) - }) - test() - } - } -} diff --git a/kompendium-locations/Module.md b/locations/Module.md similarity index 100% rename from kompendium-locations/Module.md rename to locations/Module.md diff --git a/kompendium-locations/build.gradle.kts b/locations/build.gradle.kts similarity index 75% rename from kompendium-locations/build.gradle.kts rename to locations/build.gradle.kts index 7bc5eaa5e..4175e1503 100644 --- a/kompendium-locations/build.gradle.kts +++ b/locations/build.gradle.kts @@ -9,7 +9,7 @@ plugins { id("signing") } -sourdough { +sourdoughLibrary { libraryName.set("Kompendium Locations") libraryDescription.set("Supplemental library for Kompendium offering support for Ktor's Location API") compilerArgs.set(listOf("-opt-in=kotlin.RequiresOptIn")) @@ -18,10 +18,9 @@ sourdough { dependencies { // IMPLEMENTATION - val ktorVersion: String by project implementation(projects.kompendiumCore) - implementation(group = "io.ktor", name = "ktor-server-core", version = ktorVersion) - implementation(group = "io.ktor", name = "ktor-locations", version = ktorVersion) + implementation("io.ktor:ktor-server-core:2.1.0") + implementation("io.ktor:ktor-server-locations:2.1.0") // TESTING diff --git a/locations/src/main/kotlin/io/bkbn/kompendium/locations/LocationMethodParser.kt b/locations/src/main/kotlin/io/bkbn/kompendium/locations/LocationMethodParser.kt new file mode 100644 index 000000000..8c024471d --- /dev/null +++ b/locations/src/main/kotlin/io/bkbn/kompendium/locations/LocationMethodParser.kt @@ -0,0 +1,84 @@ +package io.bkbn.kompendium.locations + +//import io.bkbn.kompendium.annotations.Param +//import io.bkbn.kompendium.core.Kompendium +//import io.bkbn.kompendium.core.metadata.method.MethodInfo +//import io.bkbn.kompendium.core.parser.IMethodParser +//import io.bkbn.kompendium.oas.path.Path +//import io.bkbn.kompendium.oas.path.PathOperation +//import io.bkbn.kompendium.oas.payload.Parameter +//import io.ktor.application.feature +//import io.ktor.locations.KtorExperimentalLocationsAPI +//import io.ktor.locations.Location +//import io.ktor.routing.Route +//import io.ktor.routing.application +//import kotlin.reflect.KAnnotatedElement +//import kotlin.reflect.KClass +//import kotlin.reflect.KClassifier +//import kotlin.reflect.KType +//import kotlin.reflect.full.createType +//import kotlin.reflect.full.findAnnotation +//import kotlin.reflect.full.hasAnnotation +//import kotlin.reflect.full.memberProperties +// +//@OptIn(KtorExperimentalLocationsAPI::class) +//object LocationMethodParser : IMethodParser { +// override fun KType.toParameterSpec(info: MethodInfo<*, *>, feature: Kompendium): List { +// val clazzList = determineLocationParents(classifier!!) +// return clazzList.associateWith { it.memberProperties } +// .flatMap { (clazz, memberProperties) -> memberProperties.associateWith { clazz }.toList() } +// .filter { (prop, _) -> prop.hasAnnotation() } +// .map { (prop, clazz) -> prop.toParameter(info, clazz.createType(), clazz, feature) } +// } +// +// private fun determineLocationParents(classifier: KClassifier): List> { +// var clazz: KClass<*>? = classifier as KClass<*> +// val clazzList = mutableListOf>() +// while (clazz != null) { +// clazzList.add(clazz) +// clazz = getLocationParent(clazz) +// } +// return clazzList +// } +// +// private fun getLocationParent(clazz: KClass<*>): KClass<*>? { +// val parent = clazz.memberProperties +// .find { (it.returnType.classifier as KAnnotatedElement).hasAnnotation() } +// return parent?.returnType?.classifier as? KClass<*> +// } +// +// fun KClass<*>.calculateLocationPath(suffix: String = ""): String { +// val locationAnnotation = this.findAnnotation() +// require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" } +// val parent = this.java.declaringClass?.kotlin?.takeIf { it.hasAnnotation() } +// val newSuffix = locationAnnotation.path.plus(suffix) +// return when (parent) { +// null -> newSuffix +// else -> parent.calculateLocationPath(newSuffix) +// } +// } +// +// inline fun processBaseInfo( +// paramType: KType, +// requestType: KType, +// responseType: KType, +// info: MethodInfo<*, *>, +// route: Route +// ): LocationBaseInfo { +// val locationAnnotation = TParam::class.findAnnotation() +// require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" } +// val path = route.calculateRoutePath() +// val locationPath = TParam::class.calculateLocationPath() +// val pathWithLocation = path.plus(locationPath) +// val feature = route.application.feature(Kompendium) +// feature.config.spec.paths.getOrPut(pathWithLocation) { Path() } +// val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature) +// return LocationBaseInfo(baseInfo, feature, pathWithLocation) +// } +// +// data class LocationBaseInfo( +// val op: PathOperation, +// val feature: Kompendium, +// val path: String +// ) +//} diff --git a/locations/src/main/kotlin/io/bkbn/kompendium/locations/NotarizedLocation.kt b/locations/src/main/kotlin/io/bkbn/kompendium/locations/NotarizedLocation.kt new file mode 100644 index 000000000..d9d8f260a --- /dev/null +++ b/locations/src/main/kotlin/io/bkbn/kompendium/locations/NotarizedLocation.kt @@ -0,0 +1,110 @@ +package io.bkbn.kompendium.locations + +//import io.bkbn.kompendium.core.KompendiumPreFlight.methodNotarizationPreFlight +//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.metadata.method.PutInfo +//import io.bkbn.kompendium.oas.path.PathOperation +//import io.ktor.application.ApplicationCall +//import io.ktor.http.HttpMethod +//import io.ktor.locations.KtorExperimentalLocationsAPI +//import io.ktor.locations.handle +//import io.ktor.locations.location +//import io.ktor.routing.Route +//import io.ktor.routing.method +//import io.ktor.util.pipeline.PipelineContext +// +///** +// * This version of notarized routes leverages the Ktor [io.ktor.locations.Locations] plugin to provide type safe access +// * to all path and query parameters. +// */ +//@KtorExperimentalLocationsAPI +//object NotarizedLocation { +// +// /** +// * Notarization for an HTTP GET request leveraging the Ktor [io.ktor.locations.Locations] plugin +// * @param TParam The class containing all parameter fields. +// * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param]. +// * Additionally, the class must be annotated with @[io.ktor.locations.Location]. +// * @param TResp Class detailing the expected API response +// * @param info Route metadata +// * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation] +// */ +// inline fun Route.notarizedGet( +// info: GetInfo, +// postProcess: (PathOperation) -> PathOperation = { p -> p }, +// noinline body: suspend PipelineContext.(TParam) -> Unit +// ): Route = methodNotarizationPreFlight() { paramType, requestType, responseType -> +// val lbi = LocationMethodParser.processBaseInfo(paramType, requestType, responseType, info, this) +// lbi.feature.config.spec.paths[lbi.path]?.get = postProcess(lbi.op) +// return location(TParam::class) { +// method(HttpMethod.Get) { handle(body) } +// } +// } +// +// /** +// * Notarization for an HTTP POST request leveraging the Ktor [io.ktor.locations.Locations] plugin +// * @param TParam The class containing all parameter fields. +// * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param] +// * Additionally, the class must be annotated with @[io.ktor.locations.Location]. +// * @param TReq Class detailing the expected API request body +// * @param TResp Class detailing the expected API response +// * @param info Route metadata +// * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation] +// */ +// inline fun Route.notarizedPost( +// info: PostInfo, +// postProcess: (PathOperation) -> PathOperation = { p -> p }, +// noinline body: suspend PipelineContext.(TParam) -> Unit +// ): Route = methodNotarizationPreFlight() { paramType, requestType, responseType -> +// val lbi = LocationMethodParser.processBaseInfo(paramType, requestType, responseType, info, this) +// lbi.feature.config.spec.paths[lbi.path]?.post = postProcess(lbi.op) +// return location(TParam::class) { +// method(HttpMethod.Post) { handle(body) } +// } +// } +// +// /** +// * Notarization for an HTTP Delete request leveraging the Ktor [io.ktor.locations.Locations] plugin +// * @param TParam The class containing all parameter fields. +// * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param] +// * Additionally, the class must be annotated with @[io.ktor.locations.Location]. +// * @param TReq Class detailing the expected API request body +// * @param TResp Class detailing the expected API response +// * @param info Route metadata +// * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation] +// */ +// inline fun Route.notarizedPut( +// info: PutInfo, +// postProcess: (PathOperation) -> PathOperation = { p -> p }, +// noinline body: suspend PipelineContext.(TParam) -> Unit +// ): Route = methodNotarizationPreFlight() { paramType, requestType, responseType -> +// val lbi = LocationMethodParser.processBaseInfo(paramType, requestType, responseType, info, this) +// lbi.feature.config.spec.paths[lbi.path]?.put = postProcess(lbi.op) +// return location(TParam::class) { +// method(HttpMethod.Put) { handle(body) } +// } +// } +// +// /** +// * Notarization for an HTTP POST request leveraging the Ktor [io.ktor.locations.Locations] plugin +// * @param TParam The class containing all parameter fields. +// * Each field must be annotated with @[io.bkbn.kompendium.annotations.Param] +// * Additionally, the class must be annotated with @[io.ktor.locations.Location]. +// * @param TResp Class detailing the expected API response +// * @param info Route metadata +// * @param postProcess Adds an optional callback hook to perform manual overrides on the generated [PathOperation] +// */ +// inline fun Route.notarizedDelete( +// info: DeleteInfo, +// postProcess: (PathOperation) -> PathOperation = { p -> p }, +// noinline body: suspend PipelineContext.(TParam) -> Unit +// ): Route = methodNotarizationPreFlight { paramType, requestType, responseType -> +// val lbi = LocationMethodParser.processBaseInfo(paramType, requestType, responseType, info, this) +// lbi.feature.config.spec.paths[lbi.path]?.delete = postProcess(lbi.op) +// return location(TParam::class) { +// method(HttpMethod.Delete) { handle(body) } +// } +// } +//} diff --git a/locations/src/test/kotlin/io/bkbn/kompendium/locations/KompendiumLocationsTest.kt b/locations/src/test/kotlin/io/bkbn/kompendium/locations/KompendiumLocationsTest.kt new file mode 100644 index 000000000..b54f18c85 --- /dev/null +++ b/locations/src/test/kotlin/io/bkbn/kompendium/locations/KompendiumLocationsTest.kt @@ -0,0 +1,82 @@ +package io.bkbn.kompendium.locations + +//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 +//import io.bkbn.kompendium.locations.util.notarizedGetNestedLocation +//import io.bkbn.kompendium.locations.util.notarizedGetNestedLocationFromNonLocationClass +//import io.bkbn.kompendium.locations.util.notarizedGetSimpleLocation +//import io.bkbn.kompendium.locations.util.notarizedPostNestedLocation +//import io.bkbn.kompendium.locations.util.notarizedPostSimpleLocation +//import io.bkbn.kompendium.locations.util.notarizedPutNestedLocation +//import io.bkbn.kompendium.locations.util.notarizedPutSimpleLocation +//import io.kotest.core.spec.style.DescribeSpec +// +//class KompendiumLocationsTest : DescribeSpec({ +// describe("Locations") { +// it("Can notarize a get request with a simple location") { +// // act +// openApiTestAllSerializers("notarized_get_simple_location.json") { +// locationsConfig() +// notarizedGetSimpleLocation() +// } +// } +// it("Can notarize a get request with a nested location") { +// // act +// openApiTestAllSerializers("notarized_get_nested_location.json") { +// locationsConfig() +// notarizedGetNestedLocation() +// } +// } +// it("Can notarize a post with a simple location") { +// // act +// openApiTestAllSerializers("notarized_post_simple_location.json") { +// locationsConfig() +// notarizedPostSimpleLocation() +// } +// } +// it("Can notarize a post with a nested location") { +// // act +// openApiTestAllSerializers("notarized_post_nested_location.json") { +// locationsConfig() +// notarizedPostNestedLocation() +// } +// } +// it("Can notarize a put with a simple location") { +// // act +// openApiTestAllSerializers("notarized_put_simple_location.json") { +// locationsConfig() +// notarizedPutSimpleLocation() +// } +// } +// it("Can notarize a put with a nested location") { +// // act +// openApiTestAllSerializers("notarized_put_nested_location.json") { +// locationsConfig() +// notarizedPutNestedLocation() +// } +// } +// it("Can notarize a delete with a simple location") { +// // act +// openApiTestAllSerializers("notarized_delete_simple_location.json") { +// locationsConfig() +// notarizedDeleteSimpleLocation() +// } +// } +// it("Can notarize a delete with a nested location") { +// // act +// openApiTestAllSerializers("notarized_delete_nested_location.json") { +// locationsConfig() +// notarizedDeleteNestedLocation() +// } +// } +// it("Can notarize a get with a nested location nested in a non-location class") { +// // act +// openApiTestAllSerializers("notarized_get_nested_location_from_non_location_class.json") { +// locationsConfig() +// notarizedGetNestedLocationFromNonLocationClass() +// } +// } +// } +//}) diff --git a/locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModels.kt b/locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModels.kt new file mode 100644 index 000000000..60316b711 --- /dev/null +++ b/locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModels.kt @@ -0,0 +1,22 @@ +package io.bkbn.kompendium.locations.util + +//import io.bkbn.kompendium.annotations.Param +//import io.bkbn.kompendium.annotations.ParamType +//import io.ktor.locations.Location +// +//@Location("/test/{name}") +//data class SimpleLoc(@Param(ParamType.PATH) val name: String) { +// @Location("/nesty") +// data class NestedLoc(@Param(ParamType.QUERY) val isCool: Boolean, val parent: SimpleLoc) +//} +// +//object NonLocationObject { +// @Location("/test/{name}") +// data class SimpleLoc(@Param(ParamType.PATH) val name: String) { +// @Location("/nesty") +// data class NestedLoc(@Param(ParamType.QUERY) val isCool: Boolean, val parent: SimpleLoc) +// } +//} +// +//data class SimpleResponse(val result: Boolean) +//data class SimpleRequest(val input: String) diff --git a/locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModules.kt b/locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModules.kt new file mode 100644 index 000000000..94bc7292c --- /dev/null +++ b/locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestModules.kt @@ -0,0 +1,107 @@ +package io.bkbn.kompendium.locations.util + +//import io.bkbn.kompendium.locations.NotarizedLocation.notarizedDelete +//import io.bkbn.kompendium.locations.NotarizedLocation.notarizedGet +//import io.bkbn.kompendium.locations.NotarizedLocation.notarizedPost +//import io.bkbn.kompendium.locations.NotarizedLocation.notarizedPut +//import io.ktor.application.Application +//import io.ktor.application.call +//import io.ktor.application.install +//import io.ktor.locations.Locations +//import io.ktor.response.respondText +//import io.ktor.routing.route +//import io.ktor.routing.routing +// +//fun Application.locationsConfig() { +// install(Locations) +//} +// +//fun Application.notarizedGetSimpleLocation() { +// routing { +// route("/test") { +// notarizedGet(TestResponseInfo.testGetSimpleLocation) { +// call.respondText { "hey dude ‼️ congratz on the get request" } +// } +// } +// } +//} +// +//fun Application.notarizedGetNestedLocation() { +// routing { +// route("/test") { +// notarizedGet(TestResponseInfo.testGetNestedLocation) { +// call.respondText { "hey dude ‼️ congratz on the get request" } +// } +// } +// } +//} +// +//fun Application.notarizedPostSimpleLocation() { +// routing { +// route("/test") { +// notarizedPost(TestResponseInfo.testPostSimpleLocation) { +// call.respondText { "hey dude ‼️ congratz on the get request" } +// } +// } +// } +//} +// +//fun Application.notarizedPostNestedLocation() { +// routing { +// route("/test") { +// notarizedPost(TestResponseInfo.testPostNestedLocation) { +// call.respondText { "hey dude ‼️ congratz on the get request" } +// } +// } +// } +//} +// +//fun Application.notarizedPutSimpleLocation() { +// routing { +// route("/test") { +// notarizedPut(TestResponseInfo.testPutSimpleLocation) { +// call.respondText { "hey dude ‼️ congratz on the get request" } +// } +// } +// } +//} +// +//fun Application.notarizedPutNestedLocation() { +// routing { +// route("/test") { +// notarizedPut(TestResponseInfo.testPutNestedLocation) { +// call.respondText { "hey dude ‼️ congratz on the get request" } +// } +// } +// } +//} +// +//fun Application.notarizedDeleteSimpleLocation() { +// routing { +// route("/test") { +// notarizedDelete(TestResponseInfo.testDeleteSimpleLocation) { +// call.respondText { "hey dude ‼️ congratz on the get request" } +// } +// } +// } +//} +// +//fun Application.notarizedDeleteNestedLocation() { +// routing { +// route("/test") { +// notarizedDelete(TestResponseInfo.testDeleteNestedLocation) { +// call.respondText { "hey dude ‼️ congratz on the get request" } +// } +// } +// } +//} +// +//fun Application.notarizedGetNestedLocationFromNonLocationClass() { +// routing { +// route("/test") { +// notarizedGet(TestResponseInfo.testGetNestedLocationFromNonLocationClass) { +// call.respondText { "hey dude ‼️ congratz on the get request" } +// } +// } +// } +//} diff --git a/locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestResponseInfo.kt b/locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestResponseInfo.kt new file mode 100644 index 000000000..7995687b6 --- /dev/null +++ b/locations/src/test/kotlin/io/bkbn/kompendium/locations/util/TestResponseInfo.kt @@ -0,0 +1,97 @@ +package io.bkbn.kompendium.locations.util + +//import io.bkbn.kompendium.core.legacy.metadata.RequestInfo +//import io.bkbn.kompendium.core.legacy.metadata.ResponseInfo +//import io.bkbn.kompendium.core.legacy.metadata.method.DeleteInfo +//import io.bkbn.kompendium.core.legacy.metadata.method.GetInfo +//import io.bkbn.kompendium.core.legacy.metadata.method.PostInfo +//import io.bkbn.kompendium.core.legacy.metadata.method.PutInfo +//import io.ktor.http.HttpStatusCode +// +//object TestResponseInfo { +// val testGetSimpleLocation = GetInfo( +// summary = "Location Test", +// description = "A cool test", +// responseInfo = ResponseInfo( +// status = HttpStatusCode.OK, +// description = "A successful endeavor" +// ) +// ) +// val testPostSimpleLocation = PostInfo( +// summary = "Location Test", +// description = "A cool test", +// requestInfo = RequestInfo( +// description = "Cool stuff" +// ), +// responseInfo = ResponseInfo( +// status = HttpStatusCode.OK, +// description = "A successful endeavor" +// ) +// ) +// val testPutSimpleLocation = PutInfo( +// summary = "Location Test", +// description = "A cool test", +// requestInfo = RequestInfo( +// description = "Cool stuff" +// ), +// responseInfo = ResponseInfo( +// status = HttpStatusCode.OK, +// description = "A successful endeavor" +// ) +// ) +// val testDeleteSimpleLocation = DeleteInfo( +// summary = "Location Test", +// description = "A cool test", +// responseInfo = ResponseInfo( +// status = HttpStatusCode.OK, +// description = "A successful endeavor" +// ) +// ) +// val testGetNestedLocation = GetInfo( +// summary = "Location Test", +// description = "A cool test", +// responseInfo = ResponseInfo( +// status = HttpStatusCode.OK, +// description = "A successful endeavor" +// ) +// ) +// val testPostNestedLocation = PostInfo( +// summary = "Location Test", +// description = "A cool test", +// requestInfo = RequestInfo( +// description = "Cool stuff" +// ), +// responseInfo = ResponseInfo( +// status = HttpStatusCode.OK, +// description = "A successful endeavor" +// ) +// ) +// val testPutNestedLocation = PutInfo( +// summary = "Location Test", +// description = "A cool test", +// requestInfo = RequestInfo( +// description = "Cool stuff" +// ), +// responseInfo = ResponseInfo( +// status = HttpStatusCode.OK, +// description = "A successful endeavor" +// ) +// ) +// val testDeleteNestedLocation = DeleteInfo( +// summary = "Location Test", +// description = "A cool test", +// responseInfo = ResponseInfo( +// status = HttpStatusCode.OK, +// description = "A successful endeavor" +// ) +// ) +// +// val testGetNestedLocationFromNonLocationClass = GetInfo( +// summary = "Location Test", +// description = "A cool test", +// responseInfo = ResponseInfo( +// status = HttpStatusCode.OK, +// description = "A successful endeavor" +// ) +// ) +//} diff --git a/kompendium-locations/src/test/resources/notarized_delete_nested_location.json b/locations/src/test/resources/notarized_delete_nested_location.json similarity index 100% rename from kompendium-locations/src/test/resources/notarized_delete_nested_location.json rename to locations/src/test/resources/notarized_delete_nested_location.json diff --git a/kompendium-locations/src/test/resources/notarized_delete_simple_location.json b/locations/src/test/resources/notarized_delete_simple_location.json similarity index 100% rename from kompendium-locations/src/test/resources/notarized_delete_simple_location.json rename to locations/src/test/resources/notarized_delete_simple_location.json diff --git a/kompendium-locations/src/test/resources/notarized_get_nested_location.json b/locations/src/test/resources/notarized_get_nested_location.json similarity index 100% rename from kompendium-locations/src/test/resources/notarized_get_nested_location.json rename to locations/src/test/resources/notarized_get_nested_location.json diff --git a/kompendium-locations/src/test/resources/notarized_get_nested_location_from_non_location_class.json b/locations/src/test/resources/notarized_get_nested_location_from_non_location_class.json similarity index 100% rename from kompendium-locations/src/test/resources/notarized_get_nested_location_from_non_location_class.json rename to locations/src/test/resources/notarized_get_nested_location_from_non_location_class.json diff --git a/kompendium-locations/src/test/resources/notarized_get_simple_location.json b/locations/src/test/resources/notarized_get_simple_location.json similarity index 100% rename from kompendium-locations/src/test/resources/notarized_get_simple_location.json rename to locations/src/test/resources/notarized_get_simple_location.json diff --git a/kompendium-locations/src/test/resources/notarized_post_nested_location.json b/locations/src/test/resources/notarized_post_nested_location.json similarity index 100% rename from kompendium-locations/src/test/resources/notarized_post_nested_location.json rename to locations/src/test/resources/notarized_post_nested_location.json diff --git a/kompendium-locations/src/test/resources/notarized_post_simple_location.json b/locations/src/test/resources/notarized_post_simple_location.json similarity index 100% rename from kompendium-locations/src/test/resources/notarized_post_simple_location.json rename to locations/src/test/resources/notarized_post_simple_location.json diff --git a/kompendium-locations/src/test/resources/notarized_put_nested_location.json b/locations/src/test/resources/notarized_put_nested_location.json similarity index 100% rename from kompendium-locations/src/test/resources/notarized_put_nested_location.json rename to locations/src/test/resources/notarized_put_nested_location.json diff --git a/kompendium-locations/src/test/resources/notarized_put_simple_location.json b/locations/src/test/resources/notarized_put_simple_location.json similarity index 100% rename from kompendium-locations/src/test/resources/notarized_put_simple_location.json rename to locations/src/test/resources/notarized_put_simple_location.json diff --git a/kompendium-oas/Module.md b/oas/Module.md similarity index 100% rename from kompendium-oas/Module.md rename to oas/Module.md diff --git a/kompendium-oas/build.gradle.kts b/oas/build.gradle.kts similarity index 82% rename from kompendium-oas/build.gradle.kts rename to oas/build.gradle.kts index cd0afa9b9..d8d5bbcfe 100644 --- a/kompendium-oas/build.gradle.kts +++ b/oas/build.gradle.kts @@ -10,16 +10,15 @@ plugins { id("signing") } -sourdough { +sourdoughLibrary { libraryName.set("Kompendium OpenAPI Spec") libraryDescription.set("Collections of kotlin data classes modeling the OpenAPI specification") compilerArgs.set(listOf("-opt-in=kotlin.RequiresOptIn")) } dependencies { - implementation(group = "org.jetbrains.kotlinx", "kotlinx-serialization-json", version = "1.3.2") - - // TESTING + api(projects.kompendiumJsonSchema) + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3") testImplementation(testFixtures(projects.kompendiumCore)) } diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/OpenApiSpec.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/OpenApiSpec.kt new file mode 100644 index 000000000..54016e09c --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/OpenApiSpec.kt @@ -0,0 +1,42 @@ +package io.bkbn.kompendium.oas + +import io.bkbn.kompendium.oas.common.ExternalDocumentation +import io.bkbn.kompendium.oas.common.Tag +import io.bkbn.kompendium.oas.component.Components +import io.bkbn.kompendium.oas.info.Info +import io.bkbn.kompendium.oas.path.Path +import io.bkbn.kompendium.oas.server.Server +import kotlinx.serialization.Serializable + +/** + * This is the root object of the OpenAPI document. + * + * https://spec.openapis.org/oas/v3.1.0#openapi-object + * + * @param openapi This string MUST be the version number of the OpenAPI Specification that the OpenAPI document uses. + * Kompendium only supports OpenAPI 3.1 + * @param jsonSchemaDialect The default value for the $schema keyword within Schema Objects contained within this OAS document. + * Kompendium only supports the 2020 draft + * @param info Provides metadata about the API. + * @param servers An array of Server Objects, which provide connectivity information to a target server. + * If the property is not provided, or is an empty array, the default value would be a Server Object with a url value of /. + * @param paths The available paths and operations for the API. + * @param webhooks The incoming webhooks that MAY be received as part of this API and that the API consumer MAY choose to implement. + * @param components An element to hold various schemas for the document. + * @param security A declaration of which security mechanisms can be used across the API. + * @param tags A list of tags used by the document with additional metadata. + * @param externalDocs Additional external documentation. + */ +@Serializable +data class OpenApiSpec( + val openapi: String = "3.1.0", + val jsonSchemaDialect: String = "https://json-schema.org/draft/2020-12/schema", + val info: Info, + val servers: MutableList = mutableListOf(), + val paths: MutableMap = mutableMapOf(), + val webhooks: MutableMap = mutableMapOf(), + val components: Components = Components(), + val security: MutableList>> = mutableListOf(), + val tags: MutableList = mutableListOf(), + val externalDocs: ExternalDocumentation? = null +) diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/common/ExternalDocumentation.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/common/ExternalDocumentation.kt similarity index 52% rename from kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/common/ExternalDocumentation.kt rename to oas/src/main/kotlin/io/bkbn/kompendium/oas/common/ExternalDocumentation.kt index 53a449cd3..d8a36669a 100644 --- a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/common/ExternalDocumentation.kt +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/common/ExternalDocumentation.kt @@ -4,6 +4,14 @@ import io.bkbn.kompendium.oas.serialization.UriSerializer import kotlinx.serialization.Serializable import java.net.URI +/** + * Allows referencing an external resource for extended documentation. + * + * https://spec.openapis.org/oas/v3.1.0#external-documentation-object + * + * @param url The URL for the target documentation. + * @param description A description of the target documentation. + */ @Serializable data class ExternalDocumentation( @Serializable(with = UriSerializer::class) diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/common/Tag.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/common/Tag.kt similarity index 100% rename from kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/common/Tag.kt rename to oas/src/main/kotlin/io/bkbn/kompendium/oas/common/Tag.kt diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/component/Components.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/component/Components.kt new file mode 100644 index 000000000..d7d91cc43 --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/component/Components.kt @@ -0,0 +1,17 @@ +package io.bkbn.kompendium.oas.component + +import io.bkbn.kompendium.json.schema.definition.JsonSchema +import io.bkbn.kompendium.oas.security.SecuritySchema +import kotlinx.serialization.Serializable + +/** + * Holds a set of reusable objects for different aspects of the OAS. All objects defined within the components object + * will have no effect on the API unless they are explicitly referenced from properties outside the components object. + * + * https://spec.openapis.org/oas/v3.1.0#components-object + */ +@Serializable +data class Components( + val schemas: MutableMap = mutableMapOf(), + val securitySchemes: MutableMap = mutableMapOf() +) diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Contact.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Contact.kt new file mode 100644 index 000000000..ac97c4eef --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Contact.kt @@ -0,0 +1,22 @@ +package io.bkbn.kompendium.oas.info + +import io.bkbn.kompendium.oas.serialization.UriSerializer +import kotlinx.serialization.Serializable +import java.net.URI + +/** + * Contact information for the exposed API. + * + * https://spec.openapis.org/oas/v3.1.0#contactObject + * + * @param name The identifying name of the contact person/organization. + * @param url The URL pointing to the contact information. + * @param email The email address of the contact person/organization. + */ +@Serializable +data class Contact( + var name: String, + @Serializable(with = UriSerializer::class) + var url: URI? = null, + var email: String? = null +) diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Info.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Info.kt new file mode 100644 index 000000000..3d88ee749 --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/info/Info.kt @@ -0,0 +1,32 @@ +package io.bkbn.kompendium.oas.info + +import io.bkbn.kompendium.oas.serialization.UriSerializer +import kotlinx.serialization.Serializable +import java.net.URI + +/** + * The object provides metadata about the API. + * The metadata MAY be used by the clients if needed, and MAY be presented in + * editing or documentation generation tools for convenience. + * + * https://spec.openapis.org/oas/v3.1.0#infoObject + * + * @param title The title of the API. + * @param version The version of the OpenAPI document. + * @param summary A short summary of the API. + * @param description A description of the API. + * @param termsOfService A URL to the Terms of Service for the API. + * @param contact The contact information for the exposed API. + * @param license The license information for the exposed API. + */ +@Serializable +data class Info( + val title: String, + var version: String, + var summary: String? = null, + var description: String? = null, + @Serializable(with = UriSerializer::class) + var termsOfService: URI? = null, + var contact: Contact? = null, + var license: License? = null, +) diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/info/License.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/info/License.kt new file mode 100644 index 000000000..258e684b3 --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/info/License.kt @@ -0,0 +1,23 @@ +package io.bkbn.kompendium.oas.info + +import io.bkbn.kompendium.oas.serialization.UriSerializer +import kotlinx.serialization.Serializable +import java.net.URI + +/** + * License information for the exposed API. + * + * https://spec.openapis.org/oas/v3.1.0#license-object + * + * @param name The license name used for the API. + * @param identifier An SPDX license expression for the API. The identifier field is mutually exclusive of the url field. + * @param url A URL to the license used for the API. + * + */ +@Serializable +data class License( + var name: String, + var identifier: String? = null, + @Serializable(with = UriSerializer::class) + var url: URI? = null +) diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/path/Path.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/path/Path.kt new file mode 100644 index 000000000..1791c1391 --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/path/Path.kt @@ -0,0 +1,41 @@ +package io.bkbn.kompendium.oas.path + +import io.bkbn.kompendium.oas.payload.Parameter +import io.bkbn.kompendium.oas.server.Server +import kotlinx.serialization.Serializable + +/** + * Describes the operations available on a single path. + * + * https://spec.openapis.org/oas/v3.1.0#path-item-object + * + * @param summary An optional, string summary, intended to apply to all operations in this path. + * @param description An optional, string description, intended to apply to all operations in this path. + * @param get A definition of a GET operation on this path. + * @param put A definition of a GET operation on this path. + * @param post A definition of a GET operation on this path. + * @param delete A definition of a GET operation on this path. + * @param options A definition of a GET operation on this path. + * @param head A definition of a GET operation on this path. + * @param patch A definition of a GET operation on this path. + * @param trace A definition of a GET operation on this path. + * @param servers An alternative server array to service all operations in this path. + * @param parameters A list of parameters that are applicable for all the operations described under this path. + * These parameters can be overridden at the operation level, + * but cannot be removed there. The list MUST NOT include duplicated parameters. + */ +@Serializable +data class Path( + var summary: String? = null, + var description: String? = null, + var get: PathOperation? = null, + var put: PathOperation? = null, + var post: PathOperation? = null, + var delete: PathOperation? = null, + var options: PathOperation? = null, + var head: PathOperation? = null, + var patch: PathOperation? = null, + var trace: PathOperation? = null, + var servers: List? = null, + var parameters: List? = null +) diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/path/PathOperation.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/path/PathOperation.kt new file mode 100644 index 000000000..6a0a7aa7f --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/path/PathOperation.kt @@ -0,0 +1,44 @@ +package io.bkbn.kompendium.oas.path + +import io.bkbn.kompendium.oas.common.ExternalDocumentation +import io.bkbn.kompendium.oas.payload.Parameter +import io.bkbn.kompendium.oas.payload.Request +import io.bkbn.kompendium.oas.payload.Response +import io.bkbn.kompendium.oas.server.Server +import kotlinx.serialization.Serializable + +/** + * Describes a single API operation on a path. + * + * https://spec.openapis.org/oas/v3.1.0#operation-object + * + * @param tags A list of tags for API documentation control. Tags can be used for logical grouping of operations by resources or any other qualifier. + * @param summary A short summary of what the operation does. + * @param description A verbose explanation of the operation behavior. + * @param externalDocs Additional external documentation for this operation. + * @param operationId Unique string used to identify the operation. The id MUST be unique among + * all operations described in the API. The operationId value is case-sensitive. + * @param parameters A list of parameters that are applicable for this operation. If a parameter is already defined at + * the Path Item, the new definition will override it but can never remove it. The list MUST NOT include duplicated parameters + * @param requestBody The request body applicable for this operation. + * @param responses The list of possible responses as they are returned from executing this operation. + * @param callbacks A map of possible out-of band callbacks related to the parent operation. + * @param deprecated Declares this operation to be deprecated. + * @param security A declaration of which security mechanisms can be used for this operation. + * @param servers An alternative server array to service this operation. + */ +@Serializable +data class PathOperation( + var tags: Set = emptySet(), + var summary: String? = null, + var description: String? = null, + var externalDocs: ExternalDocumentation? = null, + var operationId: String? = null, + var parameters: List? = null, + var requestBody: Request? = null, + var responses: Map? = null, + var callbacks: Map? = null, + var deprecated: Boolean = false, + var security: List>>? = null, + var servers: List? = null, +) diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Encoding.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Encoding.kt new file mode 100644 index 000000000..4e7c2189d --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Encoding.kt @@ -0,0 +1,24 @@ +package io.bkbn.kompendium.oas.payload + +import kotlinx.serialization.Serializable + +/** + * A single encoding definition applied to a single schema property. + * + * https://spec.openapis.org/oas/v3.1.0#encoding-object + * + * @param contentType The Content-Type for encoding a specific property. + * @param headers A map allowing additional information to be provided as headers + * @param style Describes how a specific property value will be serialized depending on its type. + * @param explode When this is true, property values of type array or object generate separate parameters for each + * value of the array, or key-value-pair of the map. For other types of properties this property has no effect. + * @param allowReserved Determines whether the parameter value SHOULD allow reserved characters, as defined by RFC3986 + */ +@Serializable +data class Encoding( + val contentType: String, + val headers: MutableMap, + val style: String, + val explode: Boolean, + val allowReserved: Boolean = false +) diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Header.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Header.kt new file mode 100644 index 000000000..1e8644436 --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Header.kt @@ -0,0 +1,25 @@ +package io.bkbn.kompendium.oas.payload + +import io.bkbn.kompendium.json.schema.definition.JsonSchema +import kotlinx.serialization.Serializable + +/** + * Describes a header object + * https://spec.openapis.org/oas/v3.1.0#header-object + * + * @param description A brief description of the parameter. + * @param required Determines whether this parameter is mandatory. If the parameter location is "path", + * this property is REQUIRED and its value MUST be true. Otherwise, the property MAY be included and its default value is false. + * @param deprecated Specifies that a parameter is deprecated and SHOULD be transitioned out of usage. + * @param allowEmptyValue Sets the ability to pass empty-valued parameters. + * This is valid only for query parameters and allows sending a parameter with an empty value. + */ +@Serializable +data class Header( + val schema: JsonSchema, + val description: String? = null, + val required: Boolean = true, + val deprecated: Boolean = false, + val allowEmptyValue: Boolean? = null, + // todo support styling https://spec.openapis.org/oas/v3.1.0#style-values +) diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Link.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Link.kt new file mode 100644 index 000000000..0fc9d489f --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Link.kt @@ -0,0 +1,26 @@ +package io.bkbn.kompendium.oas.payload + +import io.bkbn.kompendium.oas.server.Server +import kotlinx.serialization.Serializable + +/** + * The Link object represents a possible design-time link for a response. + * + * https://spec.openapis.org/oas/v3.1.0#link-object + * + * @param operationRef A relative or absolute URI reference to an OAS operation. + * @param operationId The name of an existing, resolvable OAS operation + * @param parameters A map representing parameters to pass to an operation as specified with operationId or identified via operationRef. + * @param requestBody Used as a request body when calling the target operation. + * @param description A description of the link. + * @param server A server object to be used by the target operation. + */ +@Serializable +data class Link( + val operationRef: String? = null, + val operationId: String? = null, + val parameters: Map? = null, + val requestBody: Map? = null, + val description: String? = null, + val server: Server? = null +) diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/MediaType.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/MediaType.kt new file mode 100644 index 000000000..0d01e7785 --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/MediaType.kt @@ -0,0 +1,24 @@ +package io.bkbn.kompendium.oas.payload + +import io.bkbn.kompendium.json.schema.definition.JsonSchema +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable + +/** + * Each Media Type Object provides schema and examples for the media type identified by its key. + * + * https://spec.openapis.org/oas/v3.1.0#media-type-object + * + * @param schema The schema defining the content of the request, response, or parameter. + * @param examples Examples of the media type. Each example object SHOULD match the media type and specified schema if present. + * @param encoding A map between a property name and its encoding information. + */ +@Serializable +data class MediaType( + val schema: JsonSchema, + val examples: Map? = null, + val encoding: Map? = null, +) { + @Serializable + data class Example(@Contextual val value: Any) +} diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Parameter.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Parameter.kt new file mode 100644 index 000000000..33b7c6ce1 --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Parameter.kt @@ -0,0 +1,43 @@ +package io.bkbn.kompendium.oas.payload + +import io.bkbn.kompendium.json.schema.definition.JsonSchema +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable + +/** + * Describes a single operation parameter + * https://spec.openapis.org/oas/v3.1.0#parameter-object + * + * @param name The name of the parameter. Parameter names are case-sensitive. + * @param in The location of the parameter. + * @param description A brief description of the parameter. + * @param required Determines whether this parameter is mandatory. If the parameter location is "path", + * this property is REQUIRED and its value MUST be true. Otherwise, the property MAY be included and its default value is false. + * @param deprecated Specifies that a parameter is deprecated and SHOULD be transitioned out of usage. + * @param allowEmptyValue Sets the ability to pass empty-valued parameters. + * This is valid only for query parameters and allows sending a parameter with an empty value. + */ +@Serializable +data class Parameter( + val name: String, + val `in`: Location, + val schema: JsonSchema, + val description: String? = null, + val required: Boolean = true, + val deprecated: Boolean = false, + val allowEmptyValue: Boolean? = null, + val examples: Map? = null, + // todo support styling https://spec.openapis.org/oas/v3.1.0#style-values +) { + @Serializable + data class Example(@Contextual val value: Any) + + @Suppress("EnumNaming") + @Serializable + enum class Location { + query, + header, + path, + cookie + } +} diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Request.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Request.kt new file mode 100644 index 000000000..773a0f820 --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Request.kt @@ -0,0 +1,19 @@ +package io.bkbn.kompendium.oas.payload + +import kotlinx.serialization.Serializable + +/** + * Describes a single request body. + * + * https://spec.openapis.org/oas/v3.1.0#request-body-object + * + * @param description A brief description of the request body. + * @param content The content of the request body. The key is a media type or media type range and the value describes it. + * @param required Determines if the request body is required in the request. + */ +@Serializable +data class Request( + val description: String?, + val content: Map?, // todo maybe this needs to be required?? 👀 + val required: Boolean = false +) diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Response.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Response.kt new file mode 100644 index 000000000..aa8350688 --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/payload/Response.kt @@ -0,0 +1,22 @@ +package io.bkbn.kompendium.oas.payload + +import kotlinx.serialization.Serializable + +/** + * Describes a single response from an API Operation + * + * https://spec.openapis.org/oas/v3.1.0#response-object + * + * @param description A description of the response. + * @param headers Maps a header name to its definition. + * @param content A map containing descriptions of potential response payloads. + * The key is a media type or media type range and the value describes it. + * @param links A map of operations links that can be followed from the response. + */ +@Serializable +data class Response( + val description: String, + val headers: Map? = null, + val content: Map? = null, + val links: Map? = null +) diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/security/ApiKeyAuth.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/security/ApiKeyAuth.kt similarity index 100% rename from kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/security/ApiKeyAuth.kt rename to oas/src/main/kotlin/io/bkbn/kompendium/oas/security/ApiKeyAuth.kt diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/security/BasicAuth.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/security/BasicAuth.kt similarity index 100% rename from kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/security/BasicAuth.kt rename to oas/src/main/kotlin/io/bkbn/kompendium/oas/security/BasicAuth.kt diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/security/BearerAuth.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/security/BearerAuth.kt similarity index 100% rename from kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/security/BearerAuth.kt rename to oas/src/main/kotlin/io/bkbn/kompendium/oas/security/BearerAuth.kt diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/security/OAuth.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/security/OAuth.kt similarity index 100% rename from kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/security/OAuth.kt rename to oas/src/main/kotlin/io/bkbn/kompendium/oas/security/OAuth.kt diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/security/SecuritySchema.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/security/SecuritySchema.kt similarity index 100% rename from kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/security/SecuritySchema.kt rename to oas/src/main/kotlin/io/bkbn/kompendium/oas/security/SecuritySchema.kt diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/AnySerializer.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/AnySerializer.kt similarity index 100% rename from kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/AnySerializer.kt rename to oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/AnySerializer.kt diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/KompendiumSerializersModule.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/KompendiumSerializersModule.kt similarity index 100% rename from kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/KompendiumSerializersModule.kt rename to oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/KompendiumSerializersModule.kt diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/NumberSerializer.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/NumberSerializer.kt similarity index 100% rename from kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/NumberSerializer.kt rename to oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/NumberSerializer.kt diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/SecuritySchemaSerializer.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/SecuritySchemaSerializer.kt similarity index 100% rename from kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/SecuritySchemaSerializer.kt rename to oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/SecuritySchemaSerializer.kt diff --git a/kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/UriSerializer.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/UriSerializer.kt similarity index 100% rename from kompendium-oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/UriSerializer.kt rename to oas/src/main/kotlin/io/bkbn/kompendium/oas/serialization/UriSerializer.kt diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/server/Server.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/server/Server.kt new file mode 100644 index 000000000..ae55a7250 --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/server/Server.kt @@ -0,0 +1,25 @@ +package io.bkbn.kompendium.oas.server + +import io.bkbn.kompendium.oas.serialization.UriSerializer +import kotlinx.serialization.Serializable +import java.net.URI + +/** + * An object representing a Server. + * + * https://spec.openapis.org/oas/v3.1.0#server-object + * + * @param url A URL to the target host. This URL supports Server Variables and MAY be relative, to indicate that + * the host location is relative to the location where the OpenAPI document is being served. Variable + * substitutions will be made when a variable is named in {brackets}. + * @param description An optional string describing the host designated by the URL. + * @param variables A map between a variable name and its value. + * The value is used for substitution in the server’s URL template. + */ +@Serializable +data class Server( + @Serializable(with = UriSerializer::class) + val url: URI, + val description: String? = null, + var variables: Map? = null +) diff --git a/oas/src/main/kotlin/io/bkbn/kompendium/oas/server/ServerVariable.kt b/oas/src/main/kotlin/io/bkbn/kompendium/oas/server/ServerVariable.kt new file mode 100644 index 000000000..524bb8f2c --- /dev/null +++ b/oas/src/main/kotlin/io/bkbn/kompendium/oas/server/ServerVariable.kt @@ -0,0 +1,19 @@ +package io.bkbn.kompendium.oas.server + +import kotlinx.serialization.Serializable + +/** + * An object representing a Server Variable for server URL template substitution. + * + * https://spec.openapis.org/oas/v3.1.0#serverVariableObject + * + * @param enum An enumeration of string values to be used if the substitution options are from a limited set. The array MUST NOT be empty. + * @param default The default value to use for substitution, which SHALL be sent if an alternate value is not supplied. + * @param description An optional description for the server variable. + */ +@Serializable +data class ServerVariable( + val `enum`: Set, // todo enforce not empty + val default: String, + val description: String? +) diff --git a/kompendium-oas/src/test/kotlin/io/bkbn/kompendium/oas/security/ApiKeyAuthTest.kt b/oas/src/test/kotlin/io/bkbn/kompendium/oas/security/ApiKeyAuthTest.kt similarity index 100% rename from kompendium-oas/src/test/kotlin/io/bkbn/kompendium/oas/security/ApiKeyAuthTest.kt rename to oas/src/test/kotlin/io/bkbn/kompendium/oas/security/ApiKeyAuthTest.kt diff --git a/kompendium-playground/Module.md b/playground/Module.md similarity index 100% rename from kompendium-playground/Module.md rename to playground/Module.md diff --git a/playground/build.gradle.kts b/playground/build.gradle.kts new file mode 100644 index 000000000..c7f23dd6c --- /dev/null +++ b/playground/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + kotlin("jvm") + kotlin("plugin.serialization") + id("io.bkbn.sourdough.application.jvm") + id("application") +} + +sourdoughApp { + compilerArgs.set(listOf("-opt-in=kotlin.RequiresOptIn")) +} + +dependencies { + // IMPLEMENTATION + implementation(projects.kompendiumCore) + implementation(projects.kompendiumLocations) + + // Ktor + val ktorVersion: String by project + + implementation("io.ktor:ktor-server-core:$ktorVersion") + implementation("io.ktor:ktor-server-netty:$ktorVersion") + implementation("io.ktor:ktor-server-auth:$ktorVersion") + implementation("io.ktor:ktor-server-auth-jwt:$ktorVersion") + implementation("io.ktor:ktor-serialization:$ktorVersion") + implementation("io.ktor:ktor-server-status-pages:$ktorVersion") + implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") + implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") + implementation("io.ktor:ktor-serialization-jackson:$ktorVersion") + implementation("io.ktor:ktor-serialization-gson:$ktorVersion") + implementation("io.ktor:ktor-server-locations:$ktorVersion") + + // Logging + implementation("org.apache.logging.log4j:log4j-api-kotlin:1.2.0") + implementation("org.apache.logging.log4j:log4j-api:2.17.2") + implementation("org.apache.logging.log4j:log4j-core:2.18.0") + implementation("org.slf4j:slf4j-api:1.7.36") + implementation("org.slf4j:slf4j-simple:1.7.36") + + + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3") + implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") + + implementation("joda-time:joda-time:2.10.14") +} diff --git a/playground/src/main/kotlin/io/bkbn/kompendium/playground/AuthPlayground.kt b/playground/src/main/kotlin/io/bkbn/kompendium/playground/AuthPlayground.kt new file mode 100644 index 000000000..e6c63c753 --- /dev/null +++ b/playground/src/main/kotlin/io/bkbn/kompendium/playground/AuthPlayground.kt @@ -0,0 +1,115 @@ +package io.bkbn.kompendium.playground + +import io.bkbn.kompendium.core.metadata.GetInfo +import io.bkbn.kompendium.core.plugin.NotarizedApplication +import io.bkbn.kompendium.core.plugin.NotarizedRoute +import io.bkbn.kompendium.core.routes.redoc +import io.bkbn.kompendium.json.schema.definition.TypeDefinition +import io.bkbn.kompendium.oas.component.Components +import io.bkbn.kompendium.oas.payload.Parameter +import io.bkbn.kompendium.oas.security.BasicAuth +import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule +import io.bkbn.kompendium.playground.util.ExampleResponse +import io.bkbn.kompendium.playground.util.Util.baseSpec +import io.ktor.http.HttpStatusCode +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.auth.Authentication +import io.ktor.server.auth.UserIdPrincipal +import io.ktor.server.auth.authenticate +import io.ktor.server.auth.basic +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.response.respond +import io.ktor.server.routing.Route +import io.ktor.server.routing.get +import io.ktor.server.routing.route +import io.ktor.server.routing.routing +import kotlinx.serialization.json.Json + +/** + * Application entrypoint. Run this and head on over to `localhost:8081/docs` + * to see a very simple yet beautifully documented API + */ +fun main() { + embeddedServer( + Netty, + port = 8081, + module = Application::mainModule + ).start(wait = true) +} + +private fun Application.mainModule() { + // Installs Simple JSON Content Negotiation + install(ContentNegotiation) { + json(Json { + serializersModule = KompendiumSerializersModule.module + encodeDefaults = true + explicitNulls = false + }) + } + install(Authentication) { + basic("basic") { + realm = "Ktor Server" + validate { credentials -> + if (credentials.name == credentials.password) { + UserIdPrincipal(credentials.name) + } else { + null + } + } + } + } + install(NotarizedApplication()) { + spec = baseSpec.copy( + components = Components( + securitySchemes = mutableMapOf( + "basic" to BasicAuth() + ) + ) + ) + } + routing { + redoc(pageTitle = "Simple API Docs") + authenticate("basic") { + // This adds ReDoc support at the `/docs` endpoint. + // By default, it will point at the `/openapi.json` created by Kompendium + + // Route with a get handler + route("/{id}") { + idDocumentation() + get { + call.respond(HttpStatusCode.OK, ExampleResponse(true)) + } + } + } + } +} + +// Documentation for our route +private fun Route.idDocumentation() { + install(NotarizedRoute()) { + parameters = listOf( + Parameter( + name = "id", + `in` = Parameter.Location.path, + schema = TypeDefinition.STRING + ) + ) + get = GetInfo.builder { + summary("Get user by id") + description("A very neat endpoint!") + security = mapOf( + "basic" to emptyList() + ) + response { + responseCode(HttpStatusCode.OK) + responseType() + description("Will return whether or not the user is real 😱") + } + } + } +} diff --git a/playground/src/main/kotlin/io/bkbn/kompendium/playground/BasicPlayground.kt b/playground/src/main/kotlin/io/bkbn/kompendium/playground/BasicPlayground.kt new file mode 100644 index 000000000..5ca2b0a31 --- /dev/null +++ b/playground/src/main/kotlin/io/bkbn/kompendium/playground/BasicPlayground.kt @@ -0,0 +1,77 @@ +package io.bkbn.kompendium.playground + +import io.bkbn.kompendium.core.metadata.GetInfo +import io.bkbn.kompendium.core.plugin.NotarizedApplication +import io.bkbn.kompendium.core.plugin.NotarizedRoute +import io.bkbn.kompendium.core.routes.redoc +import io.bkbn.kompendium.json.schema.definition.TypeDefinition +import io.bkbn.kompendium.oas.payload.Parameter +import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule +import io.bkbn.kompendium.playground.util.ExampleResponse +import io.bkbn.kompendium.playground.util.Util.baseSpec +import io.ktor.http.HttpStatusCode +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.response.respond +import io.ktor.server.routing.Route +import io.ktor.server.routing.get +import io.ktor.server.routing.route +import io.ktor.server.routing.routing +import kotlinx.serialization.json.Json + +fun main() { + embeddedServer( + Netty, + port = 8081, + module = Application::mainModule + ).start(wait = true) +} + +private fun Application.mainModule() { + install(ContentNegotiation) { + json(Json { + serializersModule = KompendiumSerializersModule.module + encodeDefaults = true + explicitNulls = false + }) + } + install(NotarizedApplication()) { + spec = baseSpec + } + routing { + redoc(pageTitle = "Simple API Docs") + + route("/{id}") { + idDocumentation() + get { + call.respond(HttpStatusCode.OK, ExampleResponse(true)) + } + } + } +} + +private fun Route.idDocumentation() { + install(NotarizedRoute()) { + parameters = listOf( + Parameter( + name = "id", + `in` = Parameter.Location.path, + schema = TypeDefinition.STRING + ) + ) + get = GetInfo.builder { + summary("Get user by id") + description("A very neat endpoint!") + response { + responseCode(HttpStatusCode.OK) + responseType() + description("Will return whether or not the user is real 😱") + } + } + } +} diff --git a/playground/src/main/kotlin/io/bkbn/kompendium/playground/CustomTypePlayground.kt b/playground/src/main/kotlin/io/bkbn/kompendium/playground/CustomTypePlayground.kt new file mode 100644 index 000000000..b0d98383b --- /dev/null +++ b/playground/src/main/kotlin/io/bkbn/kompendium/playground/CustomTypePlayground.kt @@ -0,0 +1,90 @@ +package io.bkbn.kompendium.playground + +import io.bkbn.kompendium.core.metadata.GetInfo +import io.bkbn.kompendium.core.plugin.NotarizedApplication +import io.bkbn.kompendium.core.plugin.NotarizedRoute +import io.bkbn.kompendium.core.routes.redoc +import io.bkbn.kompendium.json.schema.definition.TypeDefinition +import io.bkbn.kompendium.oas.payload.Parameter +import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule +import io.bkbn.kompendium.playground.util.CustomTypeResponse +import io.bkbn.kompendium.playground.util.Util.baseSpec +import io.ktor.http.HttpStatusCode +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.response.respond +import io.ktor.server.routing.Route +import io.ktor.server.routing.get +import io.ktor.server.routing.route +import io.ktor.server.routing.routing +import kotlin.reflect.typeOf +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.serialization.json.Json +import java.util.UUID + +fun main() { + embeddedServer( + Netty, + port = 8081, + module = Application::mainModule + ).start(wait = true) +} + +private fun Application.mainModule() { + install(ContentNegotiation) { + json(Json { + serializersModule = KompendiumSerializersModule.module + encodeDefaults = true + explicitNulls = false + }) + } + install(NotarizedApplication()) { + spec = baseSpec + customTypes = mapOf( + typeOf() to TypeDefinition(type = "string", format = "date-time") + ) + } + routing { + redoc(pageTitle = "Simple API Docs") + + route("/{id}") { + idDocumentation() + get { + call.respond( + HttpStatusCode.OK, + CustomTypeResponse( + thing = UUID.randomUUID().toString(), + timestamp = Clock.System.now() + ) + ) + } + } + } +} + +private fun Route.idDocumentation() { + install(NotarizedRoute()) { + parameters = listOf( + Parameter( + name = "id", + `in` = Parameter.Location.path, + schema = TypeDefinition.STRING + ) + ) + get = GetInfo.builder { + summary("Important Thing") + description("Do this often, it's important!") + response { + responseCode(HttpStatusCode.OK) + responseType() + description("Showcases using a custom type!") + } + } + } +} diff --git a/playground/src/main/kotlin/io/bkbn/kompendium/playground/ExceptionPlayground.kt b/playground/src/main/kotlin/io/bkbn/kompendium/playground/ExceptionPlayground.kt new file mode 100644 index 000000000..a31a18f6a --- /dev/null +++ b/playground/src/main/kotlin/io/bkbn/kompendium/playground/ExceptionPlayground.kt @@ -0,0 +1,90 @@ +package io.bkbn.kompendium.playground + +import io.bkbn.kompendium.core.metadata.GetInfo +import io.bkbn.kompendium.core.plugin.NotarizedApplication +import io.bkbn.kompendium.core.plugin.NotarizedRoute +import io.bkbn.kompendium.core.routes.redoc +import io.bkbn.kompendium.json.schema.definition.TypeDefinition +import io.bkbn.kompendium.oas.payload.Parameter +import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule +import io.bkbn.kompendium.playground.util.ExampleResponse +import io.bkbn.kompendium.playground.util.ExceptionResponse +import io.bkbn.kompendium.playground.util.Util.baseSpec +import io.ktor.http.HttpStatusCode +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.plugins.statuspages.StatusPages +import io.ktor.server.response.respond +import io.ktor.server.routing.Route +import io.ktor.server.routing.get +import io.ktor.server.routing.route +import io.ktor.server.routing.routing +import kotlinx.serialization.json.Json +import java.lang.RuntimeException + +fun main() { + embeddedServer( + Netty, + port = 8081, + module = Application::mainModule + ).start(wait = true) +} + +private fun Application.mainModule() { + install(ContentNegotiation) { + json(Json { + serializersModule = KompendiumSerializersModule.module + encodeDefaults = true + explicitNulls = false + }) + } + install(NotarizedApplication()) { + spec = baseSpec + } + install(StatusPages) { + exception { call, _ -> + call.respond(HttpStatusCode.InternalServerError, ExceptionResponse("Bad Thing Happened!")) + } + } + routing { + redoc(pageTitle = "Simple API Docs") + + route("/{id}") { + idDocumentation() + get { + throw RuntimeException("This wasn't your fault I promise <3") + } + } + } +} + +private fun Route.idDocumentation() { + install(NotarizedRoute()) { + parameters = listOf( + Parameter( + name = "id", + `in` = Parameter.Location.path, + schema = TypeDefinition.STRING + ) + ) + get = GetInfo.builder { + summary("Get user by id") + description("A very neat endpoint!") + response { + responseCode(HttpStatusCode.OK) + responseType() + description("Will return whether or not the user is real 😱") + canRespond { + description("Bad Things Happened") + responseCode(HttpStatusCode.InternalServerError) + responseType() + } + } + } + } +} diff --git a/playground/src/main/kotlin/io/bkbn/kompendium/playground/GsonSerializationPlayground.kt b/playground/src/main/kotlin/io/bkbn/kompendium/playground/GsonSerializationPlayground.kt new file mode 100644 index 000000000..2ca8c4573 --- /dev/null +++ b/playground/src/main/kotlin/io/bkbn/kompendium/playground/GsonSerializationPlayground.kt @@ -0,0 +1,73 @@ +package io.bkbn.kompendium.playground + +import io.bkbn.kompendium.core.metadata.GetInfo +import io.bkbn.kompendium.core.plugin.NotarizedApplication +import io.bkbn.kompendium.core.plugin.NotarizedRoute +import io.bkbn.kompendium.core.routes.redoc +import io.bkbn.kompendium.json.schema.definition.TypeDefinition +import io.bkbn.kompendium.oas.payload.Parameter +import io.bkbn.kompendium.playground.util.ExampleResponse +import io.bkbn.kompendium.playground.util.Util.baseSpec +import io.ktor.http.HttpStatusCode +import io.ktor.serialization.gson.gson +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.response.respond +import io.ktor.server.routing.Route +import io.ktor.server.routing.get +import io.ktor.server.routing.route +import io.ktor.server.routing.routing + +fun main() { + embeddedServer( + Netty, + port = 8081, + module = Application::mainModule + ).start(wait = true) +} + +private fun Application.mainModule() { + install(ContentNegotiation) { + gson { + setPrettyPrinting() + } + } + install(NotarizedApplication()) { + spec = baseSpec + } + routing { + redoc(pageTitle = "Simple API Docs") + + route("/{id}") { + idDocumentation() + get { + call.respond(HttpStatusCode.OK, ExampleResponse(true)) + } + } + } +} + +private fun Route.idDocumentation() { + install(NotarizedRoute()) { + parameters = listOf( + Parameter( + name = "id", + `in` = Parameter.Location.path, + schema = TypeDefinition.STRING + ) + ) + get = GetInfo.builder { + summary("Get user by id") + description("A very neat endpoint!") + response { + responseCode(HttpStatusCode.OK) + responseType() + description("Will return whether or not the user is real 😱") + } + } + } +} diff --git a/playground/src/main/kotlin/io/bkbn/kompendium/playground/JacksonSerializationPlayground.kt b/playground/src/main/kotlin/io/bkbn/kompendium/playground/JacksonSerializationPlayground.kt new file mode 100644 index 000000000..a3dc8ebcb --- /dev/null +++ b/playground/src/main/kotlin/io/bkbn/kompendium/playground/JacksonSerializationPlayground.kt @@ -0,0 +1,77 @@ +package io.bkbn.kompendium.playground + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.databind.SerializationFeature +import io.bkbn.kompendium.core.metadata.GetInfo +import io.bkbn.kompendium.core.plugin.NotarizedApplication +import io.bkbn.kompendium.core.plugin.NotarizedRoute +import io.bkbn.kompendium.core.routes.redoc +import io.bkbn.kompendium.json.schema.definition.TypeDefinition +import io.bkbn.kompendium.oas.payload.Parameter +import io.bkbn.kompendium.playground.util.ExampleResponse +import io.bkbn.kompendium.playground.util.Util.baseSpec +import io.ktor.http.HttpStatusCode +import io.ktor.serialization.jackson.jackson +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.response.respond +import io.ktor.server.routing.Route +import io.ktor.server.routing.get +import io.ktor.server.routing.route +import io.ktor.server.routing.routing + +fun main() { + embeddedServer( + Netty, + port = 8081, + module = Application::mainModule + ).start(wait = true) +} + +private fun Application.mainModule() { + install(ContentNegotiation) { + jackson { + enable(SerializationFeature.INDENT_OUTPUT) + setSerializationInclusion(JsonInclude.Include.NON_NULL) + } + } + install(NotarizedApplication()) { + spec = baseSpec + } + routing { + redoc(pageTitle = "Simple API Docs") + + route("/{id}") { + idDocumentation() + get { + call.respond(HttpStatusCode.OK, ExampleResponse(true)) + } + } + } +} + +private fun Route.idDocumentation() { + install(NotarizedRoute()) { + parameters = listOf( + Parameter( + name = "id", + `in` = Parameter.Location.path, + schema = TypeDefinition.STRING + ) + ) + get = GetInfo.builder { + summary("Get user by id") + description("A very neat endpoint!") + response { + responseCode(HttpStatusCode.OK) + responseType() + description("Will return whether or not the user is real 😱") + } + } + } +} + diff --git a/playground/src/main/kotlin/io/bkbn/kompendium/playground/util/Models.kt b/playground/src/main/kotlin/io/bkbn/kompendium/playground/util/Models.kt new file mode 100644 index 000000000..a38a87eeb --- /dev/null +++ b/playground/src/main/kotlin/io/bkbn/kompendium/playground/util/Models.kt @@ -0,0 +1,16 @@ +package io.bkbn.kompendium.playground.util + +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +@Serializable +data class ExampleResponse(val isReal: Boolean) + +@Serializable +data class CustomTypeResponse( + val thing: String, + val timestamp: Instant +) + +@Serializable +data class ExceptionResponse(val message: String) diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/util/Util.kt b/playground/src/main/kotlin/io/bkbn/kompendium/playground/util/Util.kt similarity index 96% rename from kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/util/Util.kt rename to playground/src/main/kotlin/io/bkbn/kompendium/playground/util/Util.kt index 774b2f45f..2b6ad6974 100644 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/util/Util.kt +++ b/playground/src/main/kotlin/io/bkbn/kompendium/playground/util/Util.kt @@ -8,7 +8,6 @@ import io.bkbn.kompendium.oas.server.Server import kotlinx.serialization.ExperimentalSerializationApi import java.net.URI -@OptIn(ExperimentalSerializationApi::class) object Util { val baseSpec = OpenApiSpec( info = Info( @@ -37,4 +36,5 @@ object Util { ) ) ) + } diff --git a/kompendium-playground/src/main/resources/logback.xml b/playground/src/main/resources/logback.xml similarity index 100% rename from kompendium-playground/src/main/resources/logback.xml rename to playground/src/main/resources/logback.xml diff --git a/settings.gradle.kts b/settings.gradle.kts index 9a87c3191..8affb6a13 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,12 +1,14 @@ rootProject.name = "kompendium" -include("kompendium-annotations") -include("kompendium-core") -include("kompendium-oas") -include("kompendium-auth") -include("kompendium-swagger-ui") -include("kompendium-playground") -include("kompendium-locations") +include("core") +include("oas") +include("playground") +include("locations") +include("json-schema") + +run { + rootProject.children.forEach { it.name = "${rootProject.name}-${it.name}" } +} // Feature Previews enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")