diff --git a/cli/src/main/kotlin/tools/samt/cli/CliCompiler.kt b/cli/src/main/kotlin/tools/samt/cli/CliCompiler.kt index b6d10e99..41c57b22 100644 --- a/cli/src/main/kotlin/tools/samt/cli/CliCompiler.kt +++ b/cli/src/main/kotlin/tools/samt/cli/CliCompiler.kt @@ -1,6 +1,7 @@ package tools.samt.cli import tools.samt.codegen.Codegen +import tools.samt.codegen.http.HttpTransportConfigurationParser import tools.samt.common.DiagnosticController import tools.samt.common.DiagnosticException import tools.samt.common.collectSamtFiles @@ -11,6 +12,9 @@ import tools.samt.semantic.SemanticModel import java.io.IOException import kotlin.io.path.isDirectory import kotlin.io.path.notExists +import tools.samt.codegen.kotlin.KotlinTypesGenerator +import tools.samt.codegen.kotlin.ktor.KotlinKtorConsumerGenerator +import tools.samt.codegen.kotlin.ktor.KotlinKtorProviderGenerator internal fun compile(command: CompileCommand, controller: DiagnosticController) { val (configuration ,_) = CliConfigParser.readConfig(command.file, controller) ?: return @@ -65,12 +69,17 @@ internal fun compile(command: CompileCommand, controller: DiagnosticController) return } - for (generator in configuration.generators) { - val files = Codegen.generate(model, generator, controller) + Codegen.registerGenerator(KotlinTypesGenerator) + Codegen.registerGenerator(KotlinKtorProviderGenerator) + Codegen.registerGenerator(KotlinKtorConsumerGenerator) + Codegen.registerTransportParser(HttpTransportConfigurationParser) + + for (generatorConfig in configuration.generators) { + val files = Codegen.generate(model, generatorConfig, controller) try { - OutputWriter.write(generator.output, files) + OutputWriter.write(generatorConfig.output, files) } catch (e: IOException) { - controller.reportGlobalError("Failed to write output for generator '${generator.name}': ${e.message}") + controller.reportGlobalError("Failed to write output for generator '${generatorConfig.name}': ${e.message}") } } } diff --git a/codegen/src/main/kotlin/tools/samt/codegen/Codegen.kt b/codegen/src/main/kotlin/tools/samt/codegen/Codegen.kt index f9f28e6e..65e07a2a 100644 --- a/codegen/src/main/kotlin/tools/samt/codegen/Codegen.kt +++ b/codegen/src/main/kotlin/tools/samt/codegen/Codegen.kt @@ -5,24 +5,33 @@ import tools.samt.api.plugin.Generator import tools.samt.api.plugin.GeneratorParams import tools.samt.api.plugin.TransportConfigurationParser import tools.samt.api.types.SamtPackage -import tools.samt.codegen.http.HttpTransportConfigurationParser -import tools.samt.codegen.kotlin.KotlinTypesGenerator -import tools.samt.codegen.kotlin.ktor.KotlinKtorConsumerGenerator -import tools.samt.codegen.kotlin.ktor.KotlinKtorProviderGenerator import tools.samt.common.DiagnosticController import tools.samt.common.SamtGeneratorConfiguration import tools.samt.semantic.SemanticModel object Codegen { - private val generators: List = listOf( - KotlinTypesGenerator, - KotlinKtorProviderGenerator, - KotlinKtorConsumerGenerator, - ) + private val generators: MutableList = mutableListOf() - private val transports: List = listOf( - HttpTransportConfigurationParser, - ) + /** + * Register the [generator] to be used when generating code. + * The generator is called when a user configures it within their `samt.yaml` configuration, where the generator is referenced by its [Generator.name]. + * Only generators registered here will be considered when calling [generate]. + */ + @JvmStatic + fun registerGenerator(generator: Generator) { + generators += generator + } + + private val transports: MutableList = mutableListOf() + + /** + * Register the [parser] as a transport configuration, which will be used to parse the `transport` section of a SAMT provider. + * Only transport configurations registered here will be considered when calling [generate]. + */ + @JvmStatic + fun registerTransportParser(parser: TransportConfigurationParser) { + transports += parser + } internal class SamtGeneratorParams( semanticModel: SemanticModel, @@ -45,6 +54,12 @@ object Codegen { } } + /** + * Run the appropriate generator for the given [configuration], using the given [semanticModel]. + * To ensure binary compatibility, the types within the [semanticModel] will be mapped to their [tools.samt.api] equivalents. + * @return a list of generated files + */ + @JvmStatic fun generate( semanticModel: SemanticModel, configuration: SamtGeneratorConfiguration, diff --git a/codegen/src/main/kotlin/tools/samt/codegen/PublicApiMapper.kt b/codegen/src/main/kotlin/tools/samt/codegen/PublicApiMapper.kt index a6cd34d2..e24975ae 100644 --- a/codegen/src/main/kotlin/tools/samt/codegen/PublicApiMapper.kt +++ b/codegen/src/main/kotlin/tools/samt/codegen/PublicApiMapper.kt @@ -142,7 +142,10 @@ class PublicApiMapper( else -> controller.reportGlobalError("Multiple transport configuration parsers found for transport '$name'") } - return object : TransportConfiguration {} + return object : TransportConfiguration { + override val name: String + get() = this@toPublicTransport.name + } } private fun tools.samt.semantic.ProviderType.Implements.toPublicImplements() = object : ProvidedService { @@ -169,7 +172,7 @@ class PublicApiMapper( override val name get() = this@toPublicAlias.name override val qualifiedName by unsafeLazy { this@toPublicAlias.getQualifiedName() } override val aliasedType by unsafeLazy { this@toPublicAlias.aliasedType.toPublicTypeReference() } - override val fullyResolvedType by unsafeLazy { this@toPublicAlias.fullyResolvedType.toPublicTypeReference() } + override val runtimeType by unsafeLazy { this@toPublicAlias.fullyResolvedType.toPublicTypeReference() } } private inline fun List.findConstraint() = diff --git a/codegen/src/main/kotlin/tools/samt/codegen/http/HttpTransport.kt b/codegen/src/main/kotlin/tools/samt/codegen/http/HttpTransport.kt index 50fbffaa..94bd94c1 100644 --- a/codegen/src/main/kotlin/tools/samt/codegen/http/HttpTransport.kt +++ b/codegen/src/main/kotlin/tools/samt/codegen/http/HttpTransport.kt @@ -9,8 +9,7 @@ import tools.samt.api.transports.http.SerializationMode import tools.samt.api.transports.http.TransportMode object HttpTransportConfigurationParser : TransportConfigurationParser { - override val transportName: String - get() = "http" + override val transportName: String get() = HttpTransportConfiguration.name private val isValidRegex = Regex("""\w+\s+\S+(\s+\{.*?\s+in\s+\S+})*""") private val methodEndpointRegex = Regex("""(\w+)\s+(\S+)(.*)""") @@ -212,6 +211,8 @@ class HttpTransportConfiguration( override val serializationMode: SerializationMode, val services: List, ) : SamtHttpTransport { + override val name: String = HttpTransportConfiguration.name + class ServiceConfiguration( val name: String, val path: String, @@ -284,4 +285,8 @@ class HttpTransportConfiguration( TransportMode.Body } } + + companion object { + const val name = "http" + } } diff --git a/codegen/src/main/kotlin/tools/samt/codegen/kotlin/ktor/KotlinKtorGeneratorUtilities.kt b/codegen/src/main/kotlin/tools/samt/codegen/kotlin/ktor/KotlinKtorGeneratorUtilities.kt index e9495b3f..50d03a79 100644 --- a/codegen/src/main/kotlin/tools/samt/codegen/kotlin/ktor/KotlinKtorGeneratorUtilities.kt +++ b/codegen/src/main/kotlin/tools/samt/codegen/kotlin/ktor/KotlinKtorGeneratorUtilities.kt @@ -121,16 +121,16 @@ private fun StringBuilder.appendDecodeRecordField(field: RecordField, options: M private fun StringBuilder.appendEncodeAlias(alias: AliasType, options: Map) { appendLine("/** Encode alias ${alias.qualifiedName} to JSON */") appendLine("fun `encode ${alias.name}`(value: ${alias.getQualifiedName(options)}): JsonElement =") - appendLine(" ${encodeJsonElement(alias.fullyResolvedType, options, valueName = "value")}") + appendLine(" ${encodeJsonElement(alias.runtimeType, options, valueName = "value")}") } private fun StringBuilder.appendDecodeAlias(alias: AliasType, options: Map) { appendLine("/** Decode alias ${alias.qualifiedName} from JSON */") - appendLine("fun `decode ${alias.name}`(json: JsonElement): ${alias.fullyResolvedType.getQualifiedName(options)} {") - if (alias.fullyResolvedType.isRuntimeOptional) { + appendLine("fun `decode ${alias.name}`(json: JsonElement): ${alias.runtimeType.getQualifiedName(options)} {") + if (alias.runtimeType.isRuntimeOptional) { appendLine(" if (json is JsonNull) return null") } - appendLine(" return ${decodeJsonElement(alias.fullyResolvedType, options, valueName = "json")}") + appendLine(" return ${decodeJsonElement(alias.runtimeType, options, valueName = "json")}") appendLine("}") } diff --git a/codegen/src/test/kotlin/tools/samt/codegen/CodegenTest.kt b/codegen/src/test/kotlin/tools/samt/codegen/CodegenTest.kt index 1d76b6ff..eeca7c77 100644 --- a/codegen/src/test/kotlin/tools/samt/codegen/CodegenTest.kt +++ b/codegen/src/test/kotlin/tools/samt/codegen/CodegenTest.kt @@ -1,6 +1,7 @@ package tools.samt.codegen import tools.samt.api.plugin.CodegenFile +import tools.samt.codegen.http.HttpTransportConfigurationParser import tools.samt.common.DiagnosticController import tools.samt.common.collectSamtFiles import tools.samt.common.readSamtSource @@ -13,6 +14,9 @@ import kotlin.io.path.Path import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import tools.samt.codegen.kotlin.KotlinTypesGenerator +import tools.samt.codegen.kotlin.ktor.KotlinKtorConsumerGenerator +import tools.samt.codegen.kotlin.ktor.KotlinKtorProviderGenerator class CodegenTest { private val testDirectory = Path("src/test/resources/generator-test-model") @@ -43,6 +47,11 @@ class CodegenTest { assertFalse(controller.hasErrors()) + Codegen.registerGenerator(KotlinTypesGenerator) + Codegen.registerGenerator(KotlinKtorProviderGenerator) + Codegen.registerGenerator(KotlinKtorConsumerGenerator) + Codegen.registerTransportParser(HttpTransportConfigurationParser) + val actualFiles = mutableListOf() for (generator in configuration.generators) { actualFiles += Codegen.generate(model, generator, controller).map { it.copy(filepath = generator.output.resolve(it.filepath).toString()) } diff --git a/public-api/src/main/kotlin/tools/samt/api/plugin/Transport.kt b/public-api/src/main/kotlin/tools/samt/api/plugin/Transport.kt index acaf82ce..40b4dfdc 100644 --- a/public-api/src/main/kotlin/tools/samt/api/plugin/Transport.kt +++ b/public-api/src/main/kotlin/tools/samt/api/plugin/Transport.kt @@ -26,7 +26,13 @@ interface TransportConfigurationParser { * A base interface for transport configurations. * This interface is intended to be sub-typed and extended by transport configuration implementations. */ -interface TransportConfiguration +interface TransportConfiguration { + /** + * The name of this transport protocol. + * Can be used to display to a user. + */ + val name: String +} /** * The parameters for a [TransportConfigurationParser]. diff --git a/public-api/src/main/kotlin/tools/samt/api/types/UserTypes.kt b/public-api/src/main/kotlin/tools/samt/api/types/UserTypes.kt index e7994f43..6c2dbcfc 100644 --- a/public-api/src/main/kotlin/tools/samt/api/types/UserTypes.kt +++ b/public-api/src/main/kotlin/tools/samt/api/types/UserTypes.kt @@ -20,9 +20,9 @@ interface AliasType : UserType { val aliasedType: TypeReference /** - * The fully resolved type, will not contain any type aliases anymore, just the underlying merged type + * The runtime type, which will not contain any type aliases, just the underlying merged type */ - val fullyResolvedType: TypeReference + val runtimeType: TypeReference } /**