diff --git a/server/java/codegen-kotlin/src/main/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGenerator.kt b/server/java/codegen-kotlin/src/main/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGenerator.kt index fa368c9fa..56386efe9 100644 --- a/server/java/codegen-kotlin/src/main/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGenerator.kt +++ b/server/java/codegen-kotlin/src/main/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGenerator.kt @@ -43,16 +43,23 @@ open class KotlinValidatorGenerator : MultiFileDirectGeneratorBase() .filter { !KotlinGenUtil.isAbstractEntity(it) } .filter { it.children.any { c -> c is RdbSource } } .map { entity -> - val shortName = PackageMapping.splitFqn(entity.name).second - entity.name to "${shortName}Table" + val (tablePkg, shortName) = PackageMapping.splitFqn(entity.name) + // (metadata FQN, Table object name, Table's Kotlin package) + Triple(entity.name, "${shortName}Table", tablePkg) } emitValidator(pkg, entries, outRoot) emitHelper(pkg, outRoot) } - protected open fun emitValidator(pkg: String, entries: List>, outRoot: Path) { - val registry = entries.joinToString(",\n ") { (fqn, table) -> "\"$fqn\" to $table" } + protected open fun emitValidator(pkg: String, entries: List>, outRoot: Path) { + val registry = entries.joinToString(",\n ") { (fqn, table, _) -> "\"$fqn\" to $table" } + // The Table objects live in their entity's own package; import any that + // are NOT in this validator's package or the bare reference won't resolve. + val tableImports = entries + .filter { (_, _, tablePkg) -> tablePkg.isNotEmpty() && tablePkg != pkg } + .map { (_, table, tablePkg) -> "$tablePkg.$table" } + .toSortedSet() val source = buildString { if (pkg.isNotEmpty()) { @@ -60,7 +67,9 @@ open class KotlinValidatorGenerator : MultiFileDirectGeneratorBase() } append("import com.metaobjects.loader.MetaDataLoader\n") append("import com.metaobjects.metadata.ktx.metaObjectOrNull\n") - append("import org.jetbrains.exposed.sql.Table\n\n") + append("import org.jetbrains.exposed.sql.Table\n") + for (imp in tableImports) append("import $imp\n") + append("\n") append("/**\n") append(" * GENERATED — runtime drift gate. Call [validate] from a Spring `@PostConstruct` or\n") append(" * `ApplicationReadyEvent` listener to fail-fast when generated Tables drift from metadata.\n") diff --git a/server/java/codegen-kotlin/src/test/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGeneratorTest.kt b/server/java/codegen-kotlin/src/test/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGeneratorTest.kt index 96c0ff9d5..f3db10d60 100644 --- a/server/java/codegen-kotlin/src/test/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGeneratorTest.kt +++ b/server/java/codegen-kotlin/src/test/kotlin/com/metaobjects/generator/kotlin/KotlinValidatorGeneratorTest.kt @@ -46,4 +46,39 @@ class KotlinValidatorGeneratorTest { outDir.toFile().deleteRecursively() } } + + /** Entities in different packages than the validator: their Table objects must be imported. */ + private val multiPkgFixture = """{ + "metadata.root": { "children": [ + { "object.entity": { "name": "acme::blog::Author", "children": [ + { "field.long": { "name": "id" } }, + { "source.rdb": { "@table": "authors" } }, + { "identity.primary": { "name": "pk", "@fields": ["id"] } } + ] } }, + { "object.entity": { "name": "acme::shop::Order", "children": [ + { "field.long": { "name": "id" } }, + { "source.rdb": { "@table": "orders" } }, + { "identity.primary": { "name": "pk", "@fields": ["id"] } } + ] } } + ] } + }""".trimIndent() + + @Test fun `imports Table objects that live in a different package than the validator`() { + val outDir = Files.createTempDirectory("kvld-") + try { + val gen = KotlinValidatorGenerator() + gen.setArgs(mapOf("outputDir" to outDir.toString(), "packageName" to "acme")) + gen.execute(loadString("multi", multiPkgFixture)) + + val vSrc = Files.readString(outDir.resolve("acme/MetadataStartupValidator.kt")) + assertTrue("import acme.blog.AuthorTable" in vSrc, + "expected cross-package import for AuthorTable; saw:\n$vSrc") + assertTrue("import acme.shop.OrderTable" in vSrc, + "expected cross-package import for OrderTable; saw:\n$vSrc") + assertTrue("\"acme::blog::Author\" to AuthorTable" in vSrc, vSrc) + assertTrue("\"acme::shop::Order\" to OrderTable" in vSrc, vSrc) + } finally { + outDir.toFile().deleteRecursively() + } + } }