-
Notifications
You must be signed in to change notification settings - Fork 0
feat: nested sealed hierarchies for oneOf/anyOf subtypes #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
halotukozak
wants to merge
20
commits into
master
Choose a base branch
from
feat/nested-sealed-hierarchies
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
a72f6ea
feat(13-01): restructure polymorphic generation to nested sealed hier…
halotukozak cbf6804
feat(13-01): wire classNameLookup through ClientGenerator and CodeGen…
halotukozak e4030dd
Merge remote-tracking branch 'origin/master' into feat/nested-sealed-…
halotukozak ad24a54
Merge origin/master into feat/nested-sealed-hierarchies
halotukozak 517e2ce
Merge origin/master into feat/nested-sealed-hierarchies
halotukozak bc1e7b9
fix(core): propagate classNameLookup in recursive toTypeName calls
halotukozak 0ba6291
refactor: replace `ModelPackage` with `Hierarchy` for sealed schema h…
halotukozak 9d4347f
refactor: replace lazy properties with constructor initialization in …
halotukozak 3795864
refactor: simplify `resolveSerialName` logic and centralize `SCHEMA_P…
halotukozak f44924c
fix: update all tests for Hierarchy context and fix anyOfWithoutDiscr…
halotukozak ce1c3a8
refactor: simplify Hierarchy and extract shared constructor building …
halotukozak ed99cbf
refactor: simplify `Hierarchy` and `ModelGenerator` with cleaner null…
halotukozak be04037
refactor: enhance `Hierarchy` with caching and incremental schema upd…
halotukozak 8570c44
refactor: replace `CacheGroup` with `MemoScope`, streamline `Hierarch…
halotukozak 7bf5821
refactor: improve `ModelGenerator` and `Memo` with cleaner design and…
halotukozak ec4b207
refactor: update functional test to verify sealed class generation wi…
halotukozak 0cda7c0
refactor: enhance `Hierarchy` with `anyOfParents` cache and doc impro…
halotukozak db8de77
merge: resolve master into feat/nested-sealed-hierarchies
halotukozak 70c4f7d
refactor(core): replace sealed classes with sealed interfaces and int…
halotukozak cfd16f2
test: update assertion to check for sealed interface instead of seale…
halotukozak File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package com.avsystem.justworks.core | ||
|
|
||
| import arrow.atomic.Atomic | ||
| import arrow.atomic.update | ||
|
|
||
| @JvmInline | ||
| value class MemoScope private constructor(private val memos: Atomic<Set<Memo<*>>>) { | ||
| constructor() : this(Atomic(emptySet())) | ||
|
|
||
| fun register(memo: Memo<*>) { | ||
| memos.update { it + memo } | ||
| } | ||
|
|
||
| fun reset() { | ||
| memos.get().forEach { it.reset() } | ||
| } | ||
| } | ||
|
|
||
| class Memo<T>(private val compute: () -> T) { | ||
| private val holder = Atomic(lazy(compute)) | ||
|
|
||
| operator fun getValue(thisRef: Any?, property: Any?): T = holder.get().value | ||
|
|
||
| fun reset() { | ||
| holder.set(lazy(compute)) | ||
| } | ||
| } | ||
|
|
||
| fun <T> memoized(memoScope: MemoScope, compute: () -> T): Memo<T> = Memo(compute).also(memoScope::register) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
core/src/main/kotlin/com/avsystem/justworks/core/gen/Hierarchy.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| package com.avsystem.justworks.core.gen | ||
|
|
||
| import com.avsystem.justworks.core.MemoScope | ||
| import com.avsystem.justworks.core.memoized | ||
| import com.avsystem.justworks.core.model.SchemaModel | ||
| import com.avsystem.justworks.core.model.TypeRef | ||
| import com.squareup.kotlinpoet.ClassName | ||
|
|
||
| internal class Hierarchy(val modelPackage: ModelPackage) { | ||
| private val schemas = mutableSetOf<SchemaModel>() | ||
|
|
||
| private val memoScope = MemoScope() | ||
|
|
||
| /** | ||
| * Updates the underlying schemas and invalidates all cached derived views. | ||
| * This is necessary when schemas are updated (e.g., after inlining types). | ||
| */ | ||
| fun addSchemas(newSchemas: List<SchemaModel>) { | ||
| schemas += newSchemas | ||
| memoScope.reset() | ||
| } | ||
|
|
||
| /** All schemas indexed by name for quick lookup. */ | ||
| val schemasById: Map<String, SchemaModel> by memoized(memoScope) { | ||
| schemas.associateBy { it.name } | ||
| } | ||
|
|
||
| /** Schemas that define polymorphic variants via oneOf or anyOf. */ | ||
| private val polymorphicSchemas: List<SchemaModel> by memoized(memoScope) { | ||
| schemas.filterNot { it.variants().isNullOrEmpty() } | ||
| } | ||
|
|
||
| /** Maps parent schema name to its variant schema names (for both oneOf and anyOf). */ | ||
| val sealedHierarchies: Map<String, List<String>> by memoized(memoScope) { | ||
| polymorphicSchemas | ||
| .associate { schema -> | ||
| schema.name to schema | ||
| .variants() | ||
| ?.filterIsInstance<TypeRef.Reference>() | ||
| ?.map { it.schemaName } | ||
| .orEmpty() | ||
| } | ||
| } | ||
|
|
||
| /** Parent schema names that use anyOf without a discriminator (JsonContentPolymorphicSerializer pattern). */ | ||
| val anyOfWithoutDiscriminator: Set<String> by memoized(memoScope) { | ||
| polymorphicSchemas | ||
| .asSequence() | ||
| .filter { !it.anyOf.isNullOrEmpty() && it.discriminator == null } | ||
| .map { it.name } | ||
| .toSet() | ||
| } | ||
|
|
||
| /** Inverse of [sealedHierarchies] for anyOf-without-discriminator: variant name to its parent names. */ | ||
| val anyOfParents: Map<String, Set<String>> by memoized(memoScope) { | ||
| sealedHierarchies | ||
| .asSequence() | ||
| .filter { (parent, _) -> parent in anyOfWithoutDiscriminator } | ||
| .flatMap { (parent, variants) -> variants.map { it to parent } } | ||
| .groupBy({ it.first }, { it.second }) | ||
| .mapValues { (_, parents) -> parents.toSet() } | ||
| } | ||
|
|
||
| /** Maps schema name to its [ClassName], using nested class for discriminated hierarchy variants. */ | ||
| private val lookup: Map<String, ClassName> by memoized(memoScope) { | ||
| sealedHierarchies | ||
| .asSequence() | ||
| .filterNot { (parent, _) -> parent in anyOfWithoutDiscriminator } | ||
| .flatMap { (parent, variants) -> | ||
| val parentClass = ClassName(modelPackage, parent) | ||
| variants.map { variant -> variant to parentClass.nestedClass(variant.toPascalCase()) } + | ||
| (parent to parentClass) | ||
| }.toMap() | ||
| } | ||
|
|
||
| /** Resolves a schema name to its [ClassName], falling back to a flat top-level class. */ | ||
| operator fun get(name: String): ClassName = lookup[name] ?: ClassName(modelPackage, name) | ||
| } | ||
|
|
||
| private fun SchemaModel.variants() = oneOf ?: anyOf | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.