Skip to content

Metamodel: stamp a schema in a block, or a chain - #106

Merged
jimador merged 2 commits into
feat/metamodel-autoconfigurefrom
feat/metamodel-fluent-stamping
Sep 9, 2026
Merged

jimador merged 2 commits into
feat/metamodel-autoconfigurefrom
feat/metamodel-fluent-stamping

Conversation

@jimador

@jimador jimador commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Answers the review note on #83: MetamodelVersion.from(dictionary).governedBy(governed).

Two entries off the class, one per language. Both take the same three inputs a stamp needs and both delegate to the existing from overloads, so every spelling produces the same stamp.

Kotlin

A receiver block, so the call site is a sequence of statements with no prefix and no chain:

val version = MetamodelVersion(dictionary) {
    governedBy("Person", "Company")
    aliases {
        type("Organisation", formerly = setOf("Company"))
        property("Person", "emailAddress", formerly = setOf("email"))
    }
}

The entry is operator fun invoke on the companion, which is how embabel-agent shapes its own configured constructors (ActionContext, OperationContext, Tool.Definition) and the same shape as Json { } and HttpClient { }. It reads as a constructor with a trailing block and stays on the class. DeclaredSchema(dictionary) { } is the same block finishing as a declaration, so both halves of a declaration come from one set of arguments and cannot disagree about what is governed. An empty block is the whole-schema stamp.

governedBy takes a selector, a set of names, or names written out. aliases { } declares renames inline through type(...) and property(...), since SchemaAliases is two levels of map and writing those literals is the least readable part of declaring a rename. Names accumulate there, so a type renamed A to B to C declares both older names, which is what a comparison across non-adjacent stamps needs.

@DslMarker scopes the two builders, so reaching the outer one from inside aliases { } is a compile error. The builders are mutable, their constructors are internal, and their lifetime is the block. What comes back is immutable.

Java

MetamodelStamping, an immutable value carrying the same three inputs, reached through MetamodelVersion.stamping(dictionary). Every step returns a new value, so a partly built stamping can be held in a field and finished more than once, as a stamp or as a declaration.

MetamodelVersion version = MetamodelVersion.stamping(dictionary)
        .governedBy(Set.of("Person", "Company"))
        .withAliases(aliases)
        .stamp();

The block entries are @JvmSynthetic, so Java sees only the chain. A receiver lambda from Java means returning Unit.INSTANCE by hand, which is why the two surfaces stay separate rather than one serving both.

Why from stays

from(dictionary) already returns a stamp, so it cannot become the entry to either form without breaking every caller compiled against it. The three MetamodelVersion.from overloads and both DeclaredSchema.from overloads are untouched. They are the short forms.

Compatibility

Additive. Two companion entries, one value type, two builders. Nothing existing moves. MetamodelStamping carries @JvmOverloads on its constructor, so Java keeps the one, two and three-argument arities; a test pins that.

Nothing is validated while chaining or inside a block. The alias rules stay with the factories, so a bad declaration fails on the terminal call, or when the block returns, with the message the three-argument form gives.

Verification

./mvnw clean install at this branch, all modules green. dice-metamodel carries 11 new tests on the block, 9 on the chain, and 16 in the Java compat suite, including one asserting the block and the chain reach the identical stamp.

@jimador
jimador requested a review from igordayen September 4, 2026 02:52
@jimador jimador changed the title Metamodel: chained stamping Metamodel: stamp a schema in a block, or a chain Sep 4, 2026
@jimador
jimador force-pushed the feat/metamodel-fluent-stamping branch from 79d6e2c to 564a31d Compare September 4, 2026 03:15
@jimador
jimador force-pushed the feat/metamodel-fluent-stamping branch from 564a31d to 78b8e04 Compare September 4, 2026 03:47
@jimador
jimador force-pushed the feat/metamodel-fluent-stamping branch from 78b8e04 to 81fdc87 Compare September 4, 2026 04:31

@igordayen igordayen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jimador - looks very good, thank you

Could you please rebase?

@jimador
jimador force-pushed the feat/metamodel-fluent-stamping branch from 81fdc87 to 20a959f Compare September 7, 2026 03:35
@jimador
jimador force-pushed the feat/metamodel-fluent-stamping branch from 20a959f to 55a4358 Compare September 8, 2026 20:30
@jimador
jimador force-pushed the feat/metamodel-fluent-stamping branch from 55a4358 to 4041f80 Compare September 8, 2026 20:47
@jimador
jimador force-pushed the feat/metamodel-fluent-stamping branch from 4041f80 to e67629b Compare September 8, 2026 20:59
MetamodelVersion.stamping(dictionary) starts an immutable MetamodelStamping that names each input as it is set and finishes as either a stamp or a declaration. Every step copies, so a partly built stamping can be held and finished more than once. The from overloads stay and the chain delegates to them, so both spellings produce the same stamp and no compiled caller moves. Java gets the same chain, tested, with the constructor arities pinned.

Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
MetamodelVersion(dictionary) { } and DeclaredSchema(dictionary) { } take a receiver block, so the Kotlin call site is statements with no prefix and no chain. The entry is invoke on the companion, the shape embabel-agent uses for its configured constructors, and is JvmSynthetic so Java sees only the chain. An aliases { } block declares renames inline and accumulates names, which is what a rename chain needs. The builders are scoped to the block by DslMarker and internal constructors; what comes back is immutable.

Signed-off-by: James Dunnam <7660553+jimador@users.noreply.github.com>
@jimador
jimador force-pushed the feat/metamodel-fluent-stamping branch from e67629b to 72d46cf Compare September 9, 2026 00:08
@jimador
jimador merged commit 55c5bb4 into main Sep 9, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants