Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions cli/src/main/kotlin/tools/samt/cli/CliCompiler.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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}")
}
}
}
39 changes: 27 additions & 12 deletions codegen/src/main/kotlin/tools/samt/codegen/Codegen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<Generator> = listOf(
KotlinTypesGenerator,
KotlinKtorProviderGenerator,
KotlinKtorConsumerGenerator,
)
private val generators: MutableList<Generator> = mutableListOf()

private val transports: List<TransportConfigurationParser> = 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<TransportConfigurationParser> = 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,
Expand All @@ -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,
Expand Down
7 changes: 5 additions & 2 deletions codegen/src/main/kotlin/tools/samt/codegen/PublicApiMapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 <reified T : tools.samt.semantic.ResolvedTypeReference.Constraint> List<tools.samt.semantic.ResolvedTypeReference.Constraint>.findConstraint() =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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+)(.*)""")
Expand Down Expand Up @@ -212,6 +211,8 @@ class HttpTransportConfiguration(
override val serializationMode: SerializationMode,
val services: List<ServiceConfiguration>,
) : SamtHttpTransport {
override val name: String = HttpTransportConfiguration.name

class ServiceConfiguration(
val name: String,
val path: String,
Expand Down Expand Up @@ -284,4 +285,8 @@ class HttpTransportConfiguration(
TransportMode.Body
}
}

companion object {
const val name = "http"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,16 @@ private fun StringBuilder.appendDecodeRecordField(field: RecordField, options: M
private fun StringBuilder.appendEncodeAlias(alias: AliasType, options: Map<String, String>) {
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<String, String>) {
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("}")
}

Expand Down
9 changes: 9 additions & 0 deletions codegen/src/test/kotlin/tools/samt/codegen/CodegenTest.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -43,6 +47,11 @@ class CodegenTest {

assertFalse(controller.hasErrors())

Codegen.registerGenerator(KotlinTypesGenerator)
Codegen.registerGenerator(KotlinKtorProviderGenerator)
Codegen.registerGenerator(KotlinKtorConsumerGenerator)
Codegen.registerTransportParser(HttpTransportConfigurationParser)

val actualFiles = mutableListOf<CodegenFile>()
for (generator in configuration.generators) {
actualFiles += Codegen.generate(model, generator, controller).map { it.copy(filepath = generator.output.resolve(it.filepath).toString()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down
4 changes: 2 additions & 2 deletions public-api/src/main/kotlin/tools/samt/api/types/UserTypes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

/**
Expand Down