Skip to content

Commit

Permalink
split into a separate processor with new annotation to handle finding…
Browse files Browse the repository at this point in the history
… the correct classes.
  • Loading branch information
Charles Bazeley committed Dec 10, 2024
1 parent c45fba1 commit e6c5479
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.openfolder.kotlinasyncapi.annotation

@Target(AnnotationTarget.CLASS)
annotation class AsyncApiDocumentation
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.openfolder.kotlinasyncapi.context.annotation

import org.openfolder.kotlinasyncapi.annotation.AsyncApiAnnotation
import org.openfolder.kotlinasyncapi.annotation.AsyncApiDocumentation
import org.openfolder.kotlinasyncapi.annotation.Schema
import org.openfolder.kotlinasyncapi.annotation.channel.Channel
import org.openfolder.kotlinasyncapi.annotation.channel.Message
Expand All @@ -24,15 +25,15 @@ import org.openfolder.kotlinasyncapi.model.server.ReferencableServerVariablesMap
import org.openfolder.kotlinasyncapi.model.server.ReferencableServersMap
import kotlin.reflect.KClass
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.functions

class AnnotationProvider(
private val applicationPackage: Package? = null,
private val classLoader: ClassLoader? = null,
private val scanner: AnnotationScanner,
private val messageProcessor: AnnotationProcessor<Message, KClass<*>>,
private val schemaProcessor: AnnotationProcessor<Schema, KClass<*>>,
private val channelProcessor: AnnotationProcessor<Channel, Any>
private val channelProcessor: AnnotationProcessor<Channel, KClass<*>>,
private val asyncApiDocumentationProcessor: AnnotationProcessor<AsyncApiDocumentation, KClass<*>>
) : AsyncApiContextProvider {

private val componentToChannelMapping = mutableMapOf<String, String>()
Expand Down Expand Up @@ -68,32 +69,21 @@ class AnnotationProvider(

annotatedClasses
.flatMap { clazz ->
val classAnnotations = listOfNotNull(
listOfNotNull(
clazz.findAnnotation<Message>()?.let { clazz to it },
clazz.findAnnotation<Schema>()?.let { clazz to it },
clazz.findAnnotation<Channel>()?.let { clazz to it }
)

val functionAnnotations = clazz.functions.flatMap { function ->
listOfNotNull(
function.findAnnotation<Message>()?.let { function to it },
function.findAnnotation<Schema>()?.let { function to it },
function.findAnnotation<Channel>()?.let { function to it }
)
}

classAnnotations + functionAnnotations
}
.mapNotNull { (element, annotation) ->
.mapNotNull { (clazz, annotation) ->
when (annotation) {
is Message -> if (element is KClass<*>) messageProcessor.process(annotation, element) else null
is Schema -> if (element is KClass<*>) schemaProcessor.process(annotation, element) else null
is Channel -> channelProcessor.process(annotation, element).also {
if (element is KClass<*>) {
componentToChannelMapping[element.java.simpleName] =
annotation.value.takeIf { it.isNotEmpty() } ?: element.java.simpleName
}
is Message -> messageProcessor.process(annotation, clazz)
is Schema -> schemaProcessor.process(annotation, clazz)
is Channel -> channelProcessor.process(annotation, clazz).also {
componentToChannelMapping[clazz.java.simpleName] =
annotation.value.takeIf { it.isNotEmpty() } ?: clazz.java.simpleName
}
is AsyncApiDocumentation -> asyncApiDocumentationProcessor.process(annotation, clazz)
else -> null
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.openfolder.kotlinasyncapi.context.annotation.processor

import org.openfolder.kotlinasyncapi.annotation.AsyncApiDocumentation
import org.openfolder.kotlinasyncapi.annotation.channel.Channel
import org.openfolder.kotlinasyncapi.annotation.channel.Publish
import org.openfolder.kotlinasyncapi.annotation.channel.Subscribe
import org.openfolder.kotlinasyncapi.model.component.Components
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.functions
import kotlin.reflect.full.hasAnnotation

class AsyncApiDocumentationProcessor : AnnotationProcessor<AsyncApiDocumentation, KClass<*>> {
override fun process(annotation: AsyncApiDocumentation, context: KClass<*>): Components {
return Components().apply {
channels {
context.functions.filter { it.hasAnnotation<Channel>() }.forEach { currentFunction ->
currentFunction.findAnnotation<Channel>()!!.toChannel()
.apply {
subscribe = subscribe ?: currentFunction.findAnnotation<Subscribe>()?.toOperation()
publish = publish ?: currentFunction.findAnnotation<Publish>()?.toOperation()
}
.also {
put(currentFunction.name, it)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,22 @@ import org.openfolder.kotlinasyncapi.annotation.channel.Publish
import org.openfolder.kotlinasyncapi.annotation.channel.Subscribe
import org.openfolder.kotlinasyncapi.model.component.Components
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.functions
import kotlin.reflect.full.hasAnnotation

class ChannelProcessor : AnnotationProcessor<Channel, Any> {
override fun process(annotation: Channel, context: Any): Components {
class ChannelProcessor : AnnotationProcessor<Channel, KClass<*>> {
override fun process(annotation: Channel, context: KClass<*>): Components {
return Components().apply {
channels {
when (context) {
is KClass<*> -> {
annotation.toChannel()
.apply {
subscribe = subscribe ?: context.findSubscribeOperation()?.toOperation()
publish = publish ?: context.findPublishOperation()?.toOperation()
}
.also {
put(context.java.simpleName, it)
}
annotation.toChannel()
.apply {
subscribe = subscribe ?: context.findSubscribeOperation()?.toOperation()
publish = publish ?: context.findPublishOperation()?.toOperation()
}
is KFunction<*> -> {
annotation.toChannel()
.apply {
subscribe = subscribe ?: context.findAnnotation<Subscribe>()?.toOperation()
publish = publish ?: context.findAnnotation<Publish>()?.toOperation()
}
.also {
put(context.name, it)
}
.also {
put(context.java.simpleName, it)
}
else -> throw IllegalArgumentException("Unsupported context type: ${context::class}")
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.openfolder.kotlinasyncapi.context.annotation.processor

import org.junit.jupiter.api.Test
import org.openfolder.kotlinasyncapi.annotation.AsyncApiDocumentation
import org.openfolder.kotlinasyncapi.annotation.channel.Channel
import org.openfolder.kotlinasyncapi.annotation.channel.Message
import org.openfolder.kotlinasyncapi.annotation.channel.Parameter
import org.openfolder.kotlinasyncapi.annotation.channel.SecurityRequirement
import org.openfolder.kotlinasyncapi.annotation.channel.Subscribe
import org.openfolder.kotlinasyncapi.context.TestUtils.assertJsonEquals
import org.openfolder.kotlinasyncapi.context.TestUtils.json
import kotlin.reflect.full.findAnnotation

internal class AsyncApiDocumentationProcessorTest {

private val processor = AsyncApiDocumentationProcessor()

@Test
fun `should process async api documentation annotation on class`() {
val payload = TestChannelFunction::class
val annotation = payload.findAnnotation<AsyncApiDocumentation>()!!

val expected = json("annotation/async_api_documentation_component.json")
val actual = json(processor.process(annotation, payload))

assertJsonEquals(expected, actual)
}


@AsyncApiDocumentation
class TestChannelFunction {
@Channel(
value = "some/{parameter}/channel",
description = "testDescription",
servers = ["dev"],
parameters = [
Parameter(
value = "parameter",
description = "testDescription"
)
]
)
@Subscribe(
operationId = "testOperationId",
security = [
SecurityRequirement(
key = "petstore_auth",
values = ["write:pets", "read:pets"]
)
],
message = Message(TestSubscribeMessage::class)
)
fun testSubscribe() {}
}

@Message
data class TestSubscribeMessage(
val id: Int = 0,
val name: String,
val isTest: Boolean
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@ import org.openfolder.kotlinasyncapi.annotation.channel.Subscribe
import org.openfolder.kotlinasyncapi.context.TestUtils.assertJsonEquals
import org.openfolder.kotlinasyncapi.context.TestUtils.json
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.functions

internal class ChannelProcessorTest {

private val processor = ChannelProcessor()

@Test
fun `should process channel annotation on class`() {
fun `should process channel annotation`() {
val payload = TestChannel::class
val annotation = payload.findAnnotation<Channel>()!!

Expand Down Expand Up @@ -66,43 +65,6 @@ internal class ChannelProcessorTest {
fun testPublish() {}
}

@Test
fun `should process channel annotation on function`() {
val payload = TestChannelFunction::class
val annotation = payload.functions.flatMap { it.annotations }.filterIsInstance<Channel>().firstOrNull()!!

val expected = json("annotation/channel_component_function.json")
val actual = json(processor.process(annotation, payload))

assertJsonEquals(expected, actual)
}


class TestChannelFunction {
@Channel(
value = "some/{parameter}/channel",
description = "testDescription",
servers = ["dev"],
parameters = [
Parameter(
value = "parameter",
description = "testDescription"
)
]
)
@Subscribe(
operationId = "testOperationId",
security = [
SecurityRequirement(
key = "petstore_auth",
values = ["write:pets", "read:pets"]
)
],
message = Message(TestSubscribeMessage::class)
)
fun testSubscribe() {}
}

@Message
data class TestSubscribeMessage(
val id: Int = 0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"channels" : {
"TestChannelFunction" : {
"testSubscribe" : {
"description" : "testDescription",
"servers" : [ "dev" ],
"subscribe" : {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.openfolder.kotlinasyncapi.context.PackageInfoProvider
import org.openfolder.kotlinasyncapi.context.ResourceProvider
import org.openfolder.kotlinasyncapi.context.annotation.AnnotationProvider
import org.openfolder.kotlinasyncapi.context.annotation.DefaultAnnotationScanner
import org.openfolder.kotlinasyncapi.context.annotation.processor.AsyncApiDocumentationProcessor
import org.openfolder.kotlinasyncapi.context.annotation.processor.ChannelProcessor
import org.openfolder.kotlinasyncapi.context.annotation.processor.MessageProcessor
import org.openfolder.kotlinasyncapi.context.annotation.processor.SchemaProcessor
Expand Down Expand Up @@ -52,6 +53,8 @@ class AsyncApiModule(

private val channelProcessor = ChannelProcessor()

private val asyncApiDocumentationProcessor = AsyncApiDocumentationProcessor()

private val annotationScanner = DefaultAnnotationScanner()

private val annotationProvider = with(configuration) {
Expand All @@ -62,6 +65,7 @@ class AsyncApiModule(
messageProcessor = messageProcessor,
schemaProcessor = schemaProcessor,
channelProcessor = channelProcessor,
asyncApiDocumentationProcessor = asyncApiDocumentationProcessor
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.openfolder.kotlinasyncapi.springweb

import org.openfolder.kotlinasyncapi.annotation.AsyncApiDocumentation
import kotlin.reflect.KClass
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
Expand All @@ -13,6 +14,7 @@ import org.openfolder.kotlinasyncapi.context.annotation.AnnotationProvider
import org.openfolder.kotlinasyncapi.context.annotation.AnnotationScanner
import org.openfolder.kotlinasyncapi.context.annotation.DefaultAnnotationScanner
import org.openfolder.kotlinasyncapi.context.annotation.processor.AnnotationProcessor
import org.openfolder.kotlinasyncapi.context.annotation.processor.AsyncApiDocumentationProcessor
import org.openfolder.kotlinasyncapi.context.annotation.processor.MessageProcessor
import org.openfolder.kotlinasyncapi.context.annotation.processor.SchemaProcessor
import org.openfolder.kotlinasyncapi.context.annotation.processor.ChannelProcessor
Expand Down Expand Up @@ -102,6 +104,10 @@ internal open class AsyncApiAnnotationAutoConfiguration {
open fun channelProcessor() =
ChannelProcessor()

@Bean
open fun asyncApiDocumentationProcessor() =
AsyncApiDocumentationProcessor()

@Bean
open fun annotationScanner() =
DefaultAnnotationScanner()
Expand All @@ -112,14 +118,16 @@ internal open class AsyncApiAnnotationAutoConfiguration {
scanner: AnnotationScanner,
messageProcessor: AnnotationProcessor<Message, KClass<*>>,
schemaProcessor: AnnotationProcessor<Schema, KClass<*>>,
channelProcessor: AnnotationProcessor<Channel, Any>
channelClassProcessor: AnnotationProcessor<Channel, KClass<*>>,
asyncApiDocumentationProcessor: AnnotationProcessor<AsyncApiDocumentation, KClass<*>>
) = packageFromContext(context)?.let {
AnnotationProvider(
applicationPackage = it,
scanner = scanner,
messageProcessor = messageProcessor,
schemaProcessor = schemaProcessor,
channelProcessor = channelProcessor,
channelProcessor = channelClassProcessor,
asyncApiDocumentationProcessor = asyncApiDocumentationProcessor,
)
}

Expand Down

0 comments on commit e6c5479

Please sign in to comment.