@@ -3,21 +3,11 @@ package io.javalin.openapi.processor.generators
33import com.google.gson.JsonArray
44import com.google.gson.JsonObject
55import io.javalin.http.HttpStatus
6- import io.javalin.openapi.ContentType.AUTODETECT
7- import io.javalin.openapi.HttpMethod
8- import io.javalin.openapi.NULL_CLASS
9- import io.javalin.openapi.NULL_STRING
10- import io.javalin.openapi.OpenApi
11- import io.javalin.openapi.OpenApiContent
6+ import io.javalin.openapi.*
127import io.javalin.openapi.OpenApiOperation.AUTO_GENERATE
13- import io.javalin.openapi.OpenApiParam
14- import io.javalin.openapi.OpenApiRequestBody
15- import io.javalin.openapi.OpenApiResponse
16- import io.javalin.openapi.OpenApis
178import io.javalin.openapi.experimental.ClassDefinition
189import io.javalin.openapi.experimental.StructureType.ARRAY
1910import io.javalin.openapi.experimental.processor.generators.ExampleGenerator
20- import io.javalin.openapi.experimental.processor.generators.ExampleGenerator.toExampleProperty
2111import io.javalin.openapi.experimental.processor.shared.addIfNotEmpty
2212import io.javalin.openapi.experimental.processor.shared.addString
2313import io.javalin.openapi.experimental.processor.shared.computeIfAbsent
@@ -26,7 +16,6 @@ import io.javalin.openapi.experimental.processor.shared.info
2616import io.javalin.openapi.experimental.processor.shared.saveResource
2717import io.javalin.openapi.experimental.processor.shared.toJsonArray
2818import io.javalin.openapi.experimental.processor.shared.toPrettyString
29- import io.javalin.openapi.getFormattedPath
3019import io.javalin.openapi.processor.OpenApiAnnotationProcessor.Companion.context
3120import io.javalin.openapi.processor.generators.OpenApiGenerator.In.COOKIE
3221import io.javalin.openapi.processor.generators.OpenApiGenerator.In.FORM_DATA
@@ -381,32 +370,46 @@ internal class OpenApiGenerator {
381370 it.titlecase(Locale .getDefault())
382371 }
383372
384- private fun JsonObject.addContent (element : Element , contentAnnotations : Array <OpenApiContent >) = context.inContext {
385- val requestBodyContent = JsonObject ()
386- val requestBodySchemes = TreeMap <String , JsonObject >()
387-
388- for (contentAnnotation in contentAnnotations) {
389- val from = contentAnnotation.getTypeMirror { from }
390- val format = contentAnnotation.format.takeIf { it != NULL_STRING }
391- val properties = contentAnnotation.properties
392- var type = contentAnnotation.type.takeIf { it != NULL_STRING }
393- var mimeType = contentAnnotation.mimeType.takeIf { it != AUTODETECT }
394- val example = contentAnnotation.example.takeIf { it != NULL_STRING }
395- val exampleObjects = contentAnnotation.exampleObjects.map { it.toExampleProperty() }
396-
397- if (mimeType == null ) {
398- when (NULL_CLASS ::class .qualifiedName) {
399- // Use 'type` as `mimeType` if there's no other mime-type declaration in @OpenApiContent annotation
400- // ~ https://github.com/javalin/javalin-openapi/issues/88
401- from.getFullName() -> {
402- mimeType = type
403- type = null
373+ private fun JsonObject.addContent (element : Element , contentAnnotations : Array <OpenApiContent >) =
374+ context.inContext {
375+ val requestBodyContent = JsonObject ()
376+ val requestBodySchemes = TreeMap <String , JsonObject >()
377+
378+ for (contentAnnotation in contentAnnotations) {
379+ val (mimeType, mediaTypeSchema) =
380+ contentAnnotation
381+ .toData()
382+ .toMimeTypeSchema(element, contentAnnotation)
383+ ? : continue
384+
385+ requestBodySchemes[mimeType] = mediaTypeSchema
386+ }
387+
388+ requestBodySchemes.forEach { (mimeType, scheme) ->
389+ requestBodyContent.add(mimeType, scheme)
390+ }
391+
392+ if (requestBodyContent.size() > 0 ) {
393+ add(" content" , requestBodyContent)
394+ }
395+ }
396+
397+ private fun OpenApiContentData.toMimeTypeSchema (element : Element , source : Annotation ): Pair <String , JsonObject >? =
398+ context.inContext {
399+ var contentData = this @toMimeTypeSchema
400+ val from = source.getTypeMirror { contentData.from() }
401+
402+ if (contentData.mimeType == null ) {
403+ contentData =
404+ when (NULL_CLASS ::class .qualifiedName) {
405+ // Use 'type` as `mimeType` if there's no other mime-type declaration in @OpenApiContent annotation
406+ // ~ https://github.com/javalin/javalin-openapi/issues/88
407+ from.getFullName() -> contentData.copy(mimeType = type, type = null )
408+ else -> contentData.copy(mimeType = detectContentType(from))
404409 }
405- else -> mimeType = detectContentType(from)
406- }
407410 }
408411
409- if (mimeType == null ) {
412+ if (contentData. mimeType == null ) {
410413 val trees = context.trees
411414
412415 if (trees != null ) {
@@ -421,106 +424,122 @@ internal class OpenApiGenerator {
421424 Source:
422425 Annotation in ${compilationUnit.lineMap.getLineNumber(startPosition)} at ${compilationUnit.sourceFile.name} line
423426 Annotation:
424- $contentAnnotation
427+ $source
425428 """ .trimIndent()
426429 )
427430 }
428431
429- continue
432+ return @inContext null
430433 }
431434
432- val schema: JsonObject = when {
433- properties.isEmpty() && from.getFullName() != NULL_CLASS ::class .java.name ->
434- createTypeDescriptionWithReferences(from)
435- properties.isEmpty() -> {
436- val schema = JsonObject ()
437- type?.also { schema.addProperty(" type" , it) }
438- format?.also { schema.addProperty(" format" , it) }
439- schema
440- }
441- else -> {
442- val schema = JsonObject ()
443- val propertiesSchema = JsonObject ()
444- schema.addProperty(" type" , " object" )
435+ val mediaType = JsonObject ()
436+ val mediaTypeSchema = contentData.toTypeSchema(source)
445437
446- for (contentProperty in properties) {
447- val propertyFormat = contentProperty.format.takeIf { it != NULL_STRING }
438+ if (mediaTypeSchema.size() > 0 ) {
439+ mediaType.add(" schema" , mediaTypeSchema)
440+ }
441+ mediaType.addContentExample(contentData)
448442
449- val contentPropertyFrom = contentAnnotation.getTypeMirror { contentProperty.from }
450- val propertyScheme = if (contentPropertyFrom.getFullName() != NULL_CLASS ::class .java.name) {
451- createTypeDescriptionWithReferences(contentPropertyFrom)
452- } else {
453- JsonObject ().apply {
454- addProperty(" type" , contentProperty.type)
455- propertyFormat?.let { addProperty(" format" , it) }
456- }
457- }
443+ return @inContext contentData.mimeType!! to mediaType
444+ }
458445
459- propertiesSchema.add(contentProperty.name,
460- if (contentProperty.isArray) {
461- // wrap into OpenAPI array object
462- JsonObject ().apply {
463- addProperty(" type" , " array" )
464- add(" items" , propertyScheme)
465- }
466- } else {
467- propertyScheme
468- }
469- )
470- }
446+ private fun JsonObject.addContentExample (contentData : OpenApiContentData ) {
447+ if (contentData.example != null ) {
448+ addProperty(" example" , contentData.example)
449+ }
471450
472- schema.add(" properties" , propertiesSchema)
473- schema
474- }
451+ if (contentData.exampleObjects != null ) {
452+ val generatorResult = ExampleGenerator .generateFromExamples(contentData.exampleObjects!! )
453+
454+ when {
455+ generatorResult.simpleValue != null -> addProperty(" example" , generatorResult.simpleValue)
456+ generatorResult.jsonElement != null -> add(" example" , generatorResult.jsonElement)
475457 }
458+ }
476459
477- val mediaType = JsonObject ()
460+ }
478461
479- if (schema.size() > 0 ) {
480- mediaType.add(" schema" , schema)
481- }
462+ private fun OpenApiContentData.toTypeSchema (source : Annotation ): JsonObject = context.inContext {
463+ val from = source.getTypeMirror { from() }
482464
483- if (example != null ) {
484- mediaType.addProperty(" example" , example)
465+ when {
466+ properties == null && additionalProperties == null && from.getFullName() != NULL_CLASS ::class .java.name ->
467+ createTypeDescriptionWithReferences(from)
468+ properties == null && additionalProperties == null -> {
469+ val schema = JsonObject ()
470+ type?.also { schema.addProperty(" type" , it) }
471+ format?.also { schema.addProperty(" format" , it) }
472+ schema
485473 }
474+ else -> {
475+ val schema = JsonObject ()
476+ schema.addProperty(" type" , " object" )
486477
487- if (exampleObjects.isNotEmpty()) {
488- val generatorResult = ExampleGenerator .generateFromExamples(exampleObjects)
478+ if (properties != null ) {
479+ schema.add(" properties" , properties!! .toTypeSchema())
480+ }
489481
490- when {
491- generatorResult.simpleValue != null -> mediaType.addProperty(" example" , generatorResult.simpleValue)
492- generatorResult.jsonElement != null -> mediaType.add(" example" , generatorResult.jsonElement)
482+ additionalProperties?.let {
483+ val additionalPropertiesData = it.toData()
484+ schema.add(" additionalProperties" , additionalPropertiesData.toTypeSchema(it))
485+ schema.addContentExample(additionalPropertiesData)
493486 }
487+ schema
494488 }
495-
496- requestBodySchemes[mimeType] = mediaType
497489 }
490+ }
498491
499- requestBodySchemes.forEach { (mimeType, scheme) ->
500- requestBodyContent.add(mimeType, scheme)
501- }
492+ private fun Collection<OpenApiContentProperty>.toTypeSchema (): JsonObject =
493+ context.inContext {
494+ val propertiesSchema = JsonObject ()
495+
496+ for (contentProperty in this @toTypeSchema) {
497+ val propertyFormat = contentProperty.format.takeIf { it != NULL_STRING }
498+
499+ val contentPropertyFrom = contentProperty.getTypeMirror { contentProperty.from }
500+ val propertyScheme = if (contentPropertyFrom.getFullName() != NULL_CLASS ::class .java.name) {
501+ createTypeDescriptionWithReferences(contentPropertyFrom)
502+ } else {
503+ JsonObject ().apply {
504+ addProperty(" type" , contentProperty.type)
505+ propertyFormat?.let { addProperty(" format" , it) }
506+ }
507+ }
502508
503- if (requestBodyContent.size() > 0 ) {
504- add(" content" , requestBodyContent)
509+ propertiesSchema.add(contentProperty.name,
510+ if (contentProperty.isArray) {
511+ // wrap into OpenAPI array object
512+ JsonObject ().apply {
513+ addProperty(" type" , " array" )
514+ add(" items" , propertyScheme)
515+ }
516+ } else {
517+ propertyScheme
518+ }
519+ )
520+ }
521+
522+ propertiesSchema
505523 }
506- }
507524
508- private fun detectContentType (typeMirror : TypeMirror ): String = context.inContext {
509- val model = typeMirror.toClassDefinition()
525+ private fun detectContentType (typeMirror : TypeMirror ): String =
526+ context.inContext {
527+ val model = typeMirror.toClassDefinition()
510528
511- when {
512- (model.structureType == ARRAY && model.simpleName == " Byte" ) || model.simpleName == " [B" || model.simpleName == " File" -> " application/octet-stream"
513- model.structureType == ARRAY -> " application/json"
514- model.simpleName == " String" -> " text/plain"
515- else -> " application/json"
529+ when {
530+ (model.structureType == ARRAY && model.simpleName == " Byte" ) || model.simpleName == " [B" || model.simpleName == " File" -> " application/octet-stream"
531+ model.structureType == ARRAY -> " application/json"
532+ model.simpleName == " String" -> " text/plain"
533+ else -> " application/json"
534+ }
516535 }
517- }
518536
519- private fun createTypeDescriptionWithReferences (type : TypeMirror ): JsonObject = context.inContext {
520- val model = type.toClassDefinition()
521- val (json, references) = context.typeSchemaGenerator.createEmbeddedTypeDescription(model)
522- componentReferences.putAll(references.associateBy { it.fullName })
523- json
524- }
537+ private fun createTypeDescriptionWithReferences (type : TypeMirror ): JsonObject =
538+ context.inContext {
539+ val model = type.toClassDefinition()
540+ val (json, references) = context.typeSchemaGenerator.createEmbeddedTypeDescription(model)
541+ componentReferences.putAll(references.associateBy { it.fullName })
542+ json
543+ }
525544
526545}
0 commit comments