diff --git a/src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/ConverterSelectorFactory.kt b/src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/ConverterSelectorFactory.kt index c3a28ed78..51e4d21cc 100644 --- a/src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/ConverterSelectorFactory.kt +++ b/src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/converters/ConverterSelectorFactory.kt @@ -1,9 +1,65 @@ package com.papsign.ktor.openapigen.parameters.parsers.converters +import kotlin.reflect.KClass import kotlin.reflect.KType -open class ConverterSelectorFactory(vararg val selectors: ConverterSelector): ConverterFactory { +open class ConverterSelectorFactory(vararg selectors: ConverterSelector): ConverterFactory { + private val converterSelectors = selectors.toMutableList() + override fun buildConverter(type: KType): Converter? { - return selectors.find { it.canHandle(type) }?.create(type) + return converterSelectors.find { it.canHandle(type) }?.create(type) + } + + fun injectConverterBefore(kclass: KClass, converterSelector: ConverterSelector) { + converterSelectors.find { kclass.isInstance(it) } + ?.let { + val index = converterSelectors.indexOf(it) + val shiftIndex = if (index <= 0) 0 else index + + var previous = converterSelectors.getOrNull(shiftIndex) + converterSelectors[index] = converterSelector + + if (shiftIndex == (converterSelectors.size - 1)) { + previous?.let { converterSelectors.add(it) } + } else { + for (i in (index + 1) until converterSelectors.size) { + previous?.let { + val current = converterSelectors[i] + converterSelectors[i] = it + previous = current + } + } + previous?.let { converterSelectors.add(it) } + } + } + ?: converterSelectors.add(converterSelector) + } + + fun injectConverterAfter(kclass: KClass, converterSelector: ConverterSelector) { + converterSelectors.find { kclass.isInstance(it) } + ?.let { + val index = converterSelectors.indexOf(it) + + if (index == (converterSelectors.size - 1)) { + converterSelectors.add(converterSelector) + } else { + var previous = converterSelectors.getOrNull(index + 1) + converterSelectors[index + 1] = converterSelector + + for (i in (index + 2) until converterSelectors.size) { + previous?.let { + val current = converterSelectors[i] + converterSelectors[i] = it + previous = current + } + } + previous?.let { converterSelectors.add(it) } + } + } + ?: converterSelectors.add(converterSelector) + } + + fun removeConverter(kclass: KClass) { + converterSelectors.removeIf { kclass.isInstance(it) } } } diff --git a/src/test/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builder/query/form/CustomBuilderTest.kt b/src/test/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builder/query/form/CustomBuilderTest.kt new file mode 100644 index 000000000..e787d1f51 --- /dev/null +++ b/src/test/kotlin/com/papsign/ktor/openapigen/parameters/parsers/builder/query/form/CustomBuilderTest.kt @@ -0,0 +1,87 @@ +package com.papsign.ktor.openapigen.parameters.parsers.builder.query.form + +import com.papsign.ktor.openapigen.parameters.parsers.builders.query.form.FormBuilderFactory +import com.papsign.ktor.openapigen.parameters.parsers.converters.Converter +import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterSelector +import com.papsign.ktor.openapigen.parameters.parsers.converters.primitive.PrimitiveConverter +import com.papsign.ktor.openapigen.parameters.parsers.converters.primitive.PrimitiveConverterFactory +import com.papsign.ktor.openapigen.parameters.parsers.testSelector +import com.papsign.ktor.openapigen.parameters.parsers.testSelectorFails +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.time.OffsetDateTime +import java.util.UUID +import kotlin.reflect.KType +import kotlin.reflect.full.createType + +class InjectBeforeTest { + @Before + fun before() { + PrimitiveConverterFactory.injectConverterBefore(PrimitiveConverter::class, CustomUuidConverter) + } + + @After + fun after() { + PrimitiveConverterFactory.removeConverter(CustomUuidConverter::class) + } + + @Test + fun testCustomConverter() { + val uuid = "4a5e1ba7-c6fe-49de-abf9-d94614ea3bb8" + val key = "key" + val expected = UUID.fromString(uuid) + val parse = mapOf( + key to listOf(uuid) + ) + FormBuilderFactory.testSelector(expected, key, parse, true) + FormBuilderFactory.testSelectorFails(key, mapOf(key to listOf("not uuid")), true) + } +} + +class InjectAfterAndRemoveTest { + @Before + fun before() { + PrimitiveConverterFactory.injectConverterAfter(PrimitiveConverter::class, AnyToBooleanConverter) + PrimitiveConverterFactory.removeConverter(PrimitiveConverter::class) + } + + @After + fun after() { + PrimitiveConverterFactory.injectConverterBefore(AnyToBooleanConverter::class, PrimitiveConverter) + PrimitiveConverterFactory.removeConverter(AnyToBooleanConverter::class) + } + + @Test + fun testConverterRemoval() { + val values = listOf("random", 1, UUID.randomUUID(), OffsetDateTime.now()) + + values.forEach { + val key = "key" + val expected = true + val parse = mapOf( + key to listOf(it.toString()) + ) + + FormBuilderFactory.testSelector(expected, key, parse, true) + } + } +} + +private object AnyToBooleanConverter : ConverterSelector, Converter { + override fun convert(value: String): Any = true + + override fun canHandle(type: KType): Boolean = true + + override fun create(type: KType): Converter = this +} + +private object CustomUuidConverter : ConverterSelector, Converter { + override fun convert(value: String): Any = UUID.fromString(value) + + override fun canHandle(type: KType): Boolean = type == UUID::class.createType() + + override fun create(type: KType): Converter = + if (canHandle(type)) this + else throw RuntimeException("Cannot create converter that can handle $type") +}