Skip to content

Feat/structura writers#25

Merged
Traqueur-dev merged 12 commits into
Traqueur-dev:developfrom
el211:feat/structura-writers
Jun 6, 2026
Merged

Feat/structura writers#25
Traqueur-dev merged 12 commits into
Traqueur-dev:developfrom
el211:feat/structura-writers

Conversation

@Traqueur-dev

Copy link
Copy Markdown
Owner

No description provided.

el211 added 12 commits June 3, 2026 23:09
Adds the structura-writers module with YAML serialization support:
- StructuraWriters: static API to write a Loadable to a file or generate defaults
- LoadableSerializer: converts a record to YAML (kebab-case keys, nested records, collections, custom writers)
- DefaultInstanceFactory: instantiates a record from @default* annotations and zero-values
- CustomWriterRegistry: singleton registry for custom Writer<T> serializers
- Basic test coverage for the write/saveDefault/serializer/factory paths
- LoadableSerializer now handles @options(inline=true) record flattening,
  standard @polymorphic (discriminator inside nested map), @polymorphic(inline=true)
  (discriminator at parent level), and fully-inline polymorphic
  (@options(inline=true) + @polymorphic(inline=true))
- useKey and isKey serialization left as TODO for next iteration
- Add StructuraWriter SPI interface in core; Structura.write() and
  Structura.saveDefault() discover the implementation via ServiceLoader —
  throws StructuraException if structura-writers is absent from classpath
- Add YamlStructuraWriter as the SPI provider + META-INF/services registration
- Expand WriterTestModels with Animal/DbEngine polymorphic fixtures
- Add inline and polymorphic unit tests to LoadableSerializerTest
Resolves the useKey TODO in the serializer: when @polymorphic(useKey = true)
is declared on an interface, the discriminator value becomes the YAML key
rather than a field written under a separate discriminator key.

Three cases handled:
- Single polymorphic field: discriminator value replaces the field name as key
- List<T>: serialized as a YAML map keyed by discriminator value
- Map<String, T>: concrete fields written directly under the existing map key,
  no redundant type discriminator injected

Added ItemMeta/FoodMeta/PotionMeta fixtures and three new tests covering each
of the above cases.
Records annotated with @options(isKey=true) on one of their components are
now serialized as YAML maps when they appear inside a List field. The key
component's value becomes the map key, and the remaining fields are written
as a nested map under it.

Added Permission/PermissionConfig (single non-key field) and Route/RouteConfig
(multiple non-key fields) fixtures, plus two tests verifying that the key
field is correctly promoted and not duplicated inside the nested map.
Optional null fields annotated with @options(optional=true) were being
written as explicit nulls in YAML. They are now silently skipped, matching
the reader's behaviour of treating absent keys as optional.

Also adds test coverage for three previously untested paths in the
serializer: @options(name=...) custom key override, enum → kebab-case
conversion, and LocalDate/LocalDateTime ISO-8601 formatting. Corresponding
fixtures (MixedOptionalConfig, CustomNameConfig, EnvConfig, ScheduleConfig)
added to WriterTestModels.
- LoadableSerializer: handle @options(isKey=true) inside Map values —
  the key component is stripped from each nested field map, symmetric
  with the existing List behaviour. Update class Javadoc to reflect all
  currently supported features (was still listing useKey/isKey as TODO).

- StructuraWriters.write(): create parent directories before writing so
  callers can pass deep paths (e.g. plugins/foo/config.yml) without
  having to create the directory tree themselves.

- StructuraWritersTest: add polymorphic standard + inline round-trip
  tests (write then Structura.load and assert concrete type), a test
  verifying parent directory creation, and saveDefault coverage for
  optional fields with no @default* annotation.

- WriterTestModels: add Endpoint/EndpointMapConfig for Map isKey tests
  and OptionalOnlyConfig for the saveDefault optional-omission test.
Move @options(isKey=true) handling from serializeCollection/serializeMap
into toMap() itself via toMapWithKeyComponent(). This mirrors the approach
in the parallel implementation and fixes two gaps:

- Simple key (String/primitive): toMap() now returns { keyValue: {fields} }
  so serializeCollection() just putAll(), and serializeMap() extracts the
  inner value — no more manual field stripping spread across helpers.
- Complex key (record type as isKey field): key sub-record fields are
  flattened alongside other fields at the same level. Lists of complex-key
  records are serialized as YAML lists (not maps) since there is no
  unique scalar key to distinguish entries.

Also adds isPolymorphicWithUseKey() and findComponentIndex() helpers,
improves the lookupRegisteredName() error message, and covers the new
complex key cases with two tests + ServerCoordinates/ComplexKeyEntry
fixtures.
Demonstrates the full read/write cycle in a real Paper plugin:
- MainConfig — standard config with @default* and an optional field
- DatabaseConfig — @polymorphic(inline=true) driver (MySQL / SQLite)
- RewardsConfig — List<Reward> with @polymorphic (item/money/command)

On first enable, saveDefault() generates all three config files from
annotations. /mctest reload re-reads from disk, /mctest save writes the
current in-memory state back, /mctest info prints all values in chat.
PolymorphicRegistry setup is done once in onEnable() before any load.
saveDefault() cannot determine which concrete type to instantiate for
polymorphic interface fields (DatabaseDriver, Reward). Split loadOrDefault
into two helpers: loadOrSaveDefault for fully-annotated configs and
loadOrWrite for configs that require an explicit fallback instance.
…cycle

5-step console demo: saveDefault, load, modify+write, round-trip verify,
polymorphic backend switch (Local → S3). No Minecraft dependency.
@Traqueur-dev Traqueur-dev merged commit a576a05 into Traqueur-dev:develop Jun 6, 2026
2 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