Skip to content

Releases: Traqueur-dev/Structura

1.6.0

20 Nov 08:15
d0eea93

Choose a tag to compare

🔧 Structura v1.6.0 – Migration to GroupeZ Repository & DateTime Support

Streamlined publishing infrastructure and enhanced temporal type support.


New Features

📅 LocalDateTime Support

Added native support for LocalDateTime conversion, complementing the existing LocalDate functionality.

public record Event(
    LocalDate date,
    LocalDateTime timestamp  // ✨ Now supported!
) implements Loadable {}

YAML:

date: 2023-10-05
timestamp: 2023-10-05T14:30:00
  • Automatic parsing of ISO-8601 datetime strings
  • Seamless integration with existing temporal type handling
  • Full bidirectional conversion support

🏗️ Infrastructure Changes

🔄 Migration to GroupeZ Maven Repository

Structura now publishes to the official GroupeZ Maven repository, replacing JitPack for more reliable and faster artifact distribution.

New Repository Configuration:

repositories {
    maven { 
        url = "https://repo.groupez.dev/releases"  // For stable releases
        // Use "snapshots" instead of "releases" for development versions
    }
}

dependencies {
implementation("fr.traqueur:structura:<VERSION>") // New coordinates
implementation("org.yaml:snakeyaml:2.4")
}

Old Configuration (Deprecated):

repositories {
    maven { url = "https://jitpack.io" }  // ❌ No longer used
}

dependencies {
implementation("com.github.Traqueur-dev:Structura:<VERSION>") // ❌ Deprecated
}

⚙️ Build System Enhancements

GitHub Actions Integration

  • New CI/CD workflow: .github/workflows/build.yml
  • Automated builds: On push to main and develop branches
  • Pull request validation: Builds on PR events (opened, synchronized, reopened)
  • Manual triggers: Via workflow_dispatch
  • Automatic publishing: To GroupeZ Maven on successful builds

Workflow Features:

  • Concurrency control (cancels in-progress builds)
  • Reusable workflow from GroupeZ-dev/actions
  • Maven credential management
  • Discord notifications support (optional)

Gradle Configuration Improvements

  • Shadow JAR: Integrated for uber-jar generation
  • Target folder: Build outputs now go to target/ directory
  • SHA versioning: Support for Git commit-based versioning
  • Custom classifiers: Archive classifier support for variant builds
  • GroupeZ publish plugin: Simplified publishing with re.alwyn974.groupez.publish v1.0.0

Gradle Wrapper Update

  • Version: 8.10 → 8.14
  • Latest features and bug fixes
  • Improved build performance

🔧 Technical Details

ValueConverter Changes

// Added LocalDateTime formatter
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

// New conversion logic
if (targetType == LocalDateTime.class) {
return LocalDateTime.parse(value.toString(), DATE_TIME_FORMATTER);
}

Build Configuration

plugins {
    id("java-library")
    id("re.alwyn974.groupez.publish") version "1.0.0"  // New
    id("com.gradleup.shadow") version "9.0.0-beta11"   // New
}

group = "fr.traqueur"
version = property("version") as String

// SHA-based versioning support
rootProject.extra.properties["sha"]?.let { sha ->
version = sha
}

// Shadow JAR configuration
tasks.shadowJar {
archiveClassifier.set("")
destinationDirectory.set(rootProject.extra["targetFolder"] as File)
}

tasks.build {
dependsOn(tasks.shadowJar)
}

// Publish configuration
publishConfig {
githubOwner = "Traqueur-dev"
useRootProjectName = true
}

Settings Configuration

pluginManagement {
    repositories {
        maven {
            name = "groupezReleases"
            url = uri("https://repo.groupez.dev/releases")
        }
        gradlePluginPortal()
    }
}

🧪 Testing

New Test: LocalDateTime Conversion

@Test
@DisplayName("Should handle LocalDateTime conversion from string")
void shouldHandleLocalDateTimeConversion() {
    var localDate = LocalDateTime.of(2023, 10, 5, 14, 30, 0);
    assertEquals(localDate, valueConverter.convert("2023-10-05T14:30:00", LocalDateTime.class, "test"));
}

📋 Migration Guide

For Existing Users

Step 1: Update your repository configuration

Replace:

repositories {
    maven { url = "https://jitpack.io" }
}

With:

repositories {
    maven { url = "https://repo.groupez.dev/releases" }
}

Step 2: Update your dependency coordinates

Replace:

implementation("com.github.Traqueur-dev:Structura:<VERSION>")

With:

implementation("fr.traqueur:structura:<VERSION>")

Step 3: (Optional) Use LocalDateTime

If you need timestamp precision beyond date-only values:

// Before
public record Event(LocalDate timestamp) implements Loadable {}

// After
public record Event(LocalDateTime timestamp) implements Loadable {}

# Before
timestamp: 2023-10-05

# After
timestamp: 2023-10-05T14:30:00

📊 Comparison: Repository Hosting

Feature JitPack (Old) GroupeZ Maven (New)
Build Speed Slow (on-demand) Fast (pre-built)
Reliability Variable High
Cache Performance Poor Excellent
Custom Versioning Limited Full support
CI/CD Integration Manual Automated
Snapshot Support Basic Native

🎯 Benefits

For Users

  • Faster downloads: Pre-built artifacts with better CDN distribution
  • Better reliability: Dedicated infrastructure with guaranteed uptime
  • Snapshot support: Access to development versions via snapshots repository
  • Enhanced DateTime: More precise temporal data with LocalDateTime

For Contributors

  • Automated publishing: No manual release process
  • Better testing: Integrated CI/CD with PR validation
  • Version management: Git SHA-based versioning for development builds
  • Simplified workflows: Reusable GitHub Actions

⚠️ Breaking Changes

Repository Migration

  • Old URL: https://jitpack.io No longer maintained
  • Old coordinates: com.github.Traqueur-dev:Structura Deprecated
  • New URL: https://repo.groupez.dev/releases
  • New coordinates: fr.traqueur:structura

Note: Version 1.6.0 is the first version available on GroupeZ Maven. Previous versions remain on JitPack but won't receive updates.


📝 Version

1.5.0 → 1.6.0

Changes:

  • ✨ Added LocalDateTime support with ISO-8601 parsing
  • 🏗️ Migrated to GroupeZ Maven repository
  • 🔄 Updated dependency coordinates (fr.traqueur:structura)
  • ⚙️ Added GitHub Actions CI/CD workflow
  • 📦 Integrated Shadow plugin for uber-JAR generation
  • 🔧 Gradle 8.10 → 8.14
  • 🔧 Added GroupeZ publish plugin
  • 📂 Build outputs now go to target/ directory
  • 🧪 New test for LocalDateTime conversion
  • 📖 Updated README with new repository URLs

Full Changelog: https://github.com/Traqueur-dev/Structura/compare/1.5.0...1.6.0


🚀 Getting Started

Quick Start with v1.6.0

build.gradle.kts:

repositories {
    mavenCentral()
    maven { url = uri("https://repo.groupez.dev/releases") }
}

dependencies {
implementation("fr.traqueur:structura:1.6.0")
implementation("org.yaml:snakeyaml:2.4")
}

Example Usage:

public record ScheduledTask(
    String name,
    LocalDateTime scheduledAt,
    LocalDate deadline
) implements Loadable {}
tasks:
  - name: "Database...
Read more

1.5.0

05 Nov 12:09
2e06074

Choose a tag to compare

What's Changed

Full Changelog: 1.4.0...1.5.0

1.4.0

22 Oct 13:41
825678a

Choose a tag to compare

🔧 Structura v1.4.0 – Key-Based Polymorphic Discriminators

Use YAML keys as type discriminators for cleaner polymorphic configurations.

New Feature

🎯 Key-as-Discriminator Mode for Polymorphic Types

Introducing @ Polymorphic(useKey = true) - a new mode that uses YAML map keys as discriminator values, eliminating redundant type fields and creating more intuitive configuration syntax.

@ Polymorphic(key = "type", useKey = true, inline = true)
public interface ItemMetadata extends Loadable {}

public record FoodMetadata(int nutrition, double saturation) implements ItemMetadata {}
public record PotionMetadata(String color, String basePotionType) implements ItemMetadata {}
public record TrimMetadata(String material, String pattern) implements ItemMetadata {}

YAML - Traditional Mode (useKey = false):

metadata:
  - type: food        # Explicit discriminator
    nutrition: 8
  - type: potion      # Explicit discriminator
    color: "#FF0000"

YAML - Key-as-Discriminator Mode (useKey = true):

metadata:
  food:              # Key IS the discriminator
    nutrition: 8
  potion:            # Key IS the discriminator
    color: "#FF0000"

🎮 Use Cases

1. Simple Fields

public record Config(ItemMetadata trim) implements Loadable {}
trim:                # "trim" becomes the discriminator value
  material: DIAMOND
  pattern: VEX

2. Collections (Lists)

metadata:
  food:              # Each key identifies the type
    nutrition: 8
    saturation: 9.6
  potion:
    color: "#00FF00"
    base-potion-type: HEALING

3. Maps

public record Config(Map<String, ItemMetadata> namedItems) implements Loadable {}
named-items:
  food:              # Key serves as both map key AND discriminator
    nutrition: 10
    saturation: 12.0
  trim:
    material: NETHERITE
    pattern: SILENCE

🔧 Implementation Details

Polymorphic Annotation Enhancement

  • New parameter: boolean useKey() default false - Enables key-based discriminator mode
  • Comprehensive JavaDoc: Detailed examples for simple fields, collections, and maps

ValueConverter Changes

  • Enhanced: convertCollection() - Detects useKey mode and processes map entries as separate elements
  • New method: isPolymorphicWithKeyAsDiscriminator() - Checks if a type uses key-based discrimination
  • New method: enrichWithDiscriminator() - Injects discriminator value into item data
  • New method: shouldTreatMapAsMultipleRecords() - Determines if map should be split into multiple records
  • Improved logic: Handles both traditional and key-based polymorphic deserialization

RecordInstanceFactory Changes

  • Enhanced: loadPolymorphicData() - Supports useKey mode by extracting discriminator from YAML keys
  • New logic: Automatically enriches data with discriminator when using useKey = true

Validator Enhancement

  • New validation: Prevents invalid configuration where both inline = true and useKey = true are set
  • Error message: Clear feedback when incompatible flags are combined

🧪 Testing

New Test Suite: KeyAsDiscriminatorTest

  • Coverage: 485+ lines, 9 comprehensive test cases
  • Test scenarios:
    • Simple field with key-as-discriminator
    • List of polymorphic items
    • Map with polymorphic values
    • Complex nested configurations
    • Mixed traditional and key-based modes
    • All three polymorphic implementations (Food, Potion, Trim)

Enhanced Validation Tests

  • New test for useKey + inline conflict detection
  • Ensures proper error handling for invalid configurations

📋 Migration Guide

✅ Non-Breaking Change - Existing configurations work unchanged.

To Adopt Key-Based Discriminators:

Before:

@ Polymorphic(key = "type")
public interface ItemMetadata extends Loadable {}
items:
  - type: food
    nutrition: 8
  - type: potion
    color: "#FF0000"

After:

@ Polymorphic(key = "type", useKey = true)
public interface ItemMetadata extends Loadable {}
items:
  food:
    nutrition: 8
  potion:
    color: "#FF0000"

📊 Comparison Table

Mode Discriminator Location Syntax Best For Flags Required
Traditional Inside object as field type: value Explicit type marking @ Polymorphic(key="type")
Inline Discriminator Parent level as field Parent has type field Flat structures @ Polymorphic(inline=true)
Key-as-Discriminator ✨ YAML key itself keyName: {...} Clean, intuitive configs @ Polymorphic(useKey=true)

⚠️ Important Constraints

Cannot combine: @ Polymorphic(useKey = true, inline = true)

  • These modes are mutually exclusive
  • Validator will throw ValidationException if both are enabled
  • Choose one approach based on your use case

🎯 Best Practices

✅ Use Key-as-Discriminator When:

  • Configuration structure naturally groups by type
  • Working with named collections (maps)
  • Readability is prioritized over explicitness
  • Type names are short and descriptive

❌ Avoid When:

  • Need inline discriminators at parent level
  • Type values contain special characters or spaces
  • Lists require explicit ordering with type markers
  • Configuration must be extremely explicit

📝 Version

1.3.1 → 1.4.0

Changes:

  • ✨ New @ Polymorphic(useKey) parameter
  • 🔧 Enhanced ValueConverter with key-based logic
  • 🛡️ New validation for incompatible flag combinations
  • 🧪 Comprehensive test suite (+485 lines)

Full Changelog: https://github.com/Traqueur-dev/Structura/compare/1.3.1...1.4.0

1.3.1

20 Oct 19:56
abfd268

Choose a tag to compare

🔧 Structura v1.3.1 – Fully Inline Polymorphic Fields

Complete field flattening for polymorphic interfaces.

New Feature

🎯 Fully Inline Polymorphic Fields

Combine @Options(inline = true) with @Polymorphic(inline = true) to flatten all fields (discriminator + implementation fields) to parent level.

@Polymorphic(key = "type", inline = true)
public interface DatabaseConfig extends Loadable {}

public record AppConfig(
    String appName,
    @Options(inline = true) DatabaseConfig database  // FULLY inline
) implements Loadable {}

YAML - Before v1.3.1:

app-name: MyApp
type: mysql        # Discriminator at parent (v1.3.0)
database:          # Fields still nested
  host: localhost
  port: 3306

YAML - After v1.3.1:

app-name: MyApp
type: mysql        # Everything at parent level
host: localhost
port: 3306

🔧 Implementation

RecordInstanceFactory Changes:

  • New method: enrichParentDataWithDiscriminator() - Validates discriminator presence and prepares data for polymorphic resolution
  • Enhanced: isInlineField() - Now detects polymorphic interfaces with @Polymorphic(inline = true)
  • Enhanced: resolveComponentValue() - Handles both concrete records and polymorphic interfaces separately

🎮 Use Cases

Simple Configuration:

app-name: MyApp
type: mysql
host: db.example.com
port: 3307

Custom Discriminator Key:

app-name: MyApp
provider: s3
bucket: my-data
region: us-west-2

Mixed Inline/Nested:

app-name: MyApp
type: mysql          # database (fully inline)
host: db.local
cache:               # cache (traditional nested)
  type: redis
  host: cache.local

🧪 Testing

  • New test suite: FullyInlinePolymorphicTest (+205 lines, 8 tests)
  • Tests cover: full flattening, multiple types, custom keys, defaults, mixed configs, error scenarios

📋 Migration Guide

No Breaking Changes - This is an opt-in enhancement.

To adopt:

// Add @Options(inline = true) to your polymorphic field
public record Config(
    @Options(inline = true) DatabaseConfig database  // Add this annotation
) implements Loadable {}

📊 Comparison

Mode Discriminator Fields Flags Required
Traditional Inside field Inside field None
Inline Discriminator Parent level Inside field @Polymorphic(inline=true)
Fully Inline Parent level Parent level @Polymorphic(inline=true) + @Options(inline=true)

🎯 Best Practices

✅ Use when:

  • Simple polymorphic configurations
  • Maximum YAML readability needed
  • No field name conflicts between inline fields

❌ Avoid when:

  • Multiple inline polymorphic fields share field names
  • Complex implementations with many fields
  • Need clear visual separation

📝 Version

1.3.0 → 1.3.1

Full Changelog: 1.3.0...1.3.1

1.3.0

20 Oct 12:53
f2351eb

Choose a tag to compare

🚀 Structura v1.3.0 – Inline Fields & Generic Type Readers

Advanced field flattening and generic type conversion for cleaner configurations and better external library integration.


New Features

📦 Inline Fields (Field Flattening)

  • @Options(inline = true): Flatten nested record fields to parent level, eliminating unnecessary nesting
  • Multiple inline fields: Support for multiple flattened records in the same configuration
  • Mixed configurations: Combine inline and nested fields freely
  • Nested inline support: Chain multiple inline levels for maximum flexibility
  • Default value integration: Works seamlessly with @Default* annotations
// Before: Nested structure
public record AppConfig(
    String appName,
    ServerInfo server  // Nested under "server" key
) implements Loadable {}

// YAML with nesting:
// app-name: MyApp
// server:
//   host: localhost
//   port: 8080

// After: Flattened structure
public record AppConfig(
    String appName,
    @Options(inline = true) ServerInfo server  // Fields at root level
) implements Loadable {}

// YAML without nesting:
// app-name: MyApp
// host: localhost
// port: 8080

🎯 Generic Type Readers with TypeToken

  • TypeToken<T> class: Capture generic type information at compile time (inspired by Gson)
  • Generic type support: Register custom readers for List<T>, Optional<T>, Map<K,V>, etc.
  • Type-specific readers: Different readers for Box<String> vs Box<Integer>
  • Priority system: TypeToken readers take precedence over Class readers with automatic fallback
  • Thread-safe: Built on the existing CustomReaderRegistry infrastructure
// Register reader for List<Component> specifically
CustomReaderRegistry.getInstance().register(
    new TypeToken<List<Component>>() {},  // Note the {} - creates anonymous class
    str -> parseComponentList(str)
);

// Register different reader for List<String>
CustomReaderRegistry.getInstance().register(
    new TypeToken<List<String>>() {},
    str -> Arrays.asList(str.split(","))
);

// Use in configuration
public record ServerConfig(
    List<Component> messages,  // Uses List<Component> reader
    List<String> tags          // Uses List<String> reader
) implements Loadable {}

🔄 Polymorphic Inline Discriminator

  • @Polymorphic(inline = true): Place discriminator key at parent level instead of inside field value
  • Custom key names: Works with @Polymorphic(key = "provider", inline = true)
  • Improved readability: Cleaner YAML structure for plugin systems and dynamic configurations
  • Backward compatible: Default inline = false preserves existing behavior
// Before: Discriminator inside field
@Polymorphic(key = "type")
public interface DatabaseConfig extends Loadable {}

// YAML:
// database:
//   type: mysql
//   host: localhost

// After: Discriminator at parent level
@Polymorphic(key = "type", inline = true)
public interface DatabaseConfig extends Loadable {}

// YAML:
// type: mysql
// database:
//   host: localhost

🔧 API Enhancements

CustomReaderRegistry

CustomReaderRegistry registry = CustomReaderRegistry.getInstance();

// NEW: Register with TypeToken for generic types
<T> void register(TypeToken<T> typeToken, Reader<T> reader)

// NEW: Check/unregister with TypeToken
boolean hasReader(TypeToken<?> typeToken)
boolean unregister(TypeToken<?> typeToken)

// ENHANCED: Convert with generic type information
<T> Optional<T> convert(Object value, Type genericType, Class<T> targetClass)

// Existing methods still work
<T> void register(Class<T> targetClass, Reader<T> reader)
boolean hasReader(Class<?> targetClass)
boolean unregister(Class<?> targetClass)
<T> Optional<T> convert(Object value, Class<T> targetClass)
void clear()
int size()

TypeToken API

// Create TypeToken for generic types (MUST use {} to create anonymous class)
TypeToken<List<String>> token = new TypeToken<List<String>>() {};

// Factory methods
TypeToken<String> token = TypeToken.of(String.class);
TypeToken<?> token = TypeToken.of(type);  // from reflection

// Get type information
Type getType()                    // Full generic type
Class<? super T> getRawType()     // Erased class type

Enhanced Annotations

@Options - New inline parameter:

@Options(
    name = "custom-field-name",    // Override field name
    optional = true,               // Mark as optional
    isKey = true,                  // Use for key-based mapping
    inline = false                 // NEW: Flatten record fields (default: false)
)

@Polymorphic - New inline parameter:

@Polymorphic(
    key = "type",      // Discriminator field name (default: "type")
    inline = false     // NEW: Place discriminator at parent level (default: false)
)

📦 New Classes

  • fr.traqueur.structura.types.TypeToken<T> (150 lines)
    • Generic type information capture
    • Factory methods for Class and Type
    • Proper equals/hashCode implementation
    • Validation for anonymous class instantiation

🎮 Use Cases

1. Cleaner Configuration Structure

// Instead of deeply nested YAML
public record AppConfig(
    @Options(inline = true) ServerInfo server,
    @Options(inline = true) AuthInfo auth
) implements Loadable {}

// YAML becomes flat and readable
// host: api.example.com
// port: 8080
// username: admin
// password: secret

2. Adventure API Integration with Lists

// Register once at startup
CustomReaderRegistry.getInstance().register(
    new TypeToken<List<Component>>() {},
    str -> lines.stream()
        .map(line -> MiniMessage.miniMessage().deserialize(line))
        .toList()
);

// Use in config
public record Messages(
    List<Component> welcomeMessages,
    List<Component> helpText
) implements Loadable {}

3. Comma-Separated Lists

CustomReaderRegistry.getInstance().register(
    new TypeToken<List<String>>() {},
    str -> Arrays.stream(str.split(","))
        .map(String::trim)
        .toList()
);

// YAML: tags: "java, yaml, configuration"
// Becomes: ["java", "yaml", "configuration"]

4. Optional Values

CustomReaderRegistry.getInstance().register(
    new TypeToken<Optional<String>>() {},
    str -> str.isEmpty() ? Optional.empty() : Optional.of(str)
);

5. Plugin Systems with Inline Discriminators

@Polymorphic(key = "provider", inline = true)
public interface StorageProvider extends Loadable {}

// YAML:
// provider: s3          # Clear and concise
// storage:
//   bucket: my-bucket
//   region: us-east-1

🧪 Testing

New Comprehensive Test Suites

  • InlineFieldsTest (463 lines)

    • Basic inline field behavior
    • Multiple inline fields handling
    • Mixed inline and nested configurations
    • Nested inline configurations
    • Edge cases and validation
    • Backward compatibility verification
  • InlinePolymorphicTest (501 lines)

    • Basic inline polymorphic behavior
    • Inline vs non-inline comparison
    • Custom discriminator key names
    • Error handling scenarios
    • Collections with polymorphic inline types
    • Backward compatibility
  • GenericTypeReaderIntegrationTest (290 lines)

    • Generic type reader registration
    • Type-specific conversion
    • Backward compatibility with Class-based API
    • Complex generic types (Optional<T>, List<T>, Map<K,V>)
    • Registry management
    • Priority and fallback behavior
  • TypeTokenTest (249 lines)

    • TypeToken creation for various types
    • Factory method validation
    • Equality and hashCode contracts
    • Edge cases and error scenarios
    • toString() representation

Total: 1,503+ new test lines with 100+ test cases


📋 Migration Guide

No Breaking Changes

All new features are opt-in with defaults preserving existing behavior:

  • @Options(inline = false) by default
  • @Polymorphic(inline = false) by default
  • Existing Class-based CustomReader API unchanged
  • All v1.2.0 code works identically in v1.3.0

Adopting New Features

1. Enable Inline Fields:

// Change from:
public record Config(ServerInfo server) implements Loadable {}

// To:
public record Config(
    @Options(inline = true) ServerInfo server
) implements Loadable {}

// Update YAML from nested to flat structure

2. Use Generic Type Readers:

// At application startup, register once:
CustomReaderRegistry.getInstance().register(
    new TypeToken<List<Component>>() {},
    str -> parseComponents(str)
);

// Then use List<Component> in any config record

3. Enable Inline Discriminators:

// Add inline = true to @Polymorphic:
@Polymorphic(key = "type", inline = true)
public interface MyInterface extends Loadable {}

// Move discriminator key from inside field to parent level in YAML

⚙️ Technical Details

Inline Fields Implementation

  • Validation: Only works with records implementing Loadable
  • Processing: Fields are extracted from parent-level data during record construction
  • Default values: Fully compatible with @Default* annotations
  • Multiple inline: All inline fields share the parent data map
  • Location: RecordInstanceFactory.isInlineField(), RecordInstanceFactory.resolveComponentValue()

TypeToken Implementation

  • Type capture: Uses anonymous class trick to preserve generic type information at runtime
  • Storage: ConcurrentHashMap<TypeToken<?>, Reader<?>> with unified type handling
  • Lookup strategy:...
Read more

1.2.0

20 Oct 07:31
807b3e7

Choose a tag to compare

🔧 Structura v1.2.0 – Custom Reader System

Extensible type conversion system for external libraries and custom types.

New Features

  • Custom Reader System: Register custom converters for types not natively supported by Structura
  • Reader<T> interface: Simple functional interface for String → T conversion
  • CustomReaderRegistry: Thread-safe singleton registry for managing custom readers
  • Automatic conversion: Registered readers are automatically used during YAML parsing
  • Priority conversion: Custom readers are checked before standard type conversion

🎯 Key Components

// Register a custom reader for Adventure API Component
CustomReaderRegistry.getInstance().register(
    Component.class,
    str -> MiniMessage.miniMessage().deserialize(str)
);

// Use in configuration records
public record MessageConfig(
    Component welcomeMessage,  // Automatically converted
    Component errorMessage
) implements Loadable {}

// Parse YAML with automatic conversion
String yaml = """
    welcome-message: "<green>Welcome!</green>"
    error-message: "<red>Error!</red>"
    """;
MessageConfig config = Structura.parse(yaml, MessageConfig.class);

🔧 API

CustomReaderRegistry methods:

  • register(Class<T> targetClass, Reader<T> reader) - Register a custom reader
  • unregister(Class<?> targetClass) - Remove a registered reader
  • hasReader(Class<?> targetClass) - Check if a reader exists
  • convert(Object value, Class<T> targetClass) - Convert a value using registered reader
  • clear() - Remove all readers (useful for testing)
  • size() - Get count of registered readers

📦 New Classes

  • fr.traqueur.structura.readers.Reader<T> - Functional interface for custom conversion
  • fr.traqueur.structura.registries.CustomReaderRegistry - Registry for managing readers

🔄 Integration

  • ValueConverter enhancement: Custom readers are checked first in the conversion pipeline
  • Type safety: Uses Class.cast() for runtime type verification
  • String-only: Readers only work with String YAML values (use records/polymorphic for complex objects)
  • Thread-safe: ConcurrentHashMap-based storage for concurrent access

🎮 Use Cases

  • Adventure API Components: Convert MiniMessage strings to Component objects
  • Custom Color types: Parse color strings into custom Color classes
  • External libraries: Integrate any library requiring custom String parsing
  • Domain-specific types: Convert formatted strings to business objects

🧪 Testing

  • Complete test suite: 30+ unit tests covering all registry operations
  • Integration tests: End-to-end scenarios with mock Adventure API
  • Thread safety tests: Concurrent registration validation
  • Error handling tests: Comprehensive exception scenario coverage

📋 Migration Guide

No breaking changes - this is a purely additive feature. Existing code continues to work unchanged.

New users can immediately:

  1. Register readers at application startup
  2. Use custom types in configuration records
  3. Parse YAML with automatic conversion

⚙️ Technical Details

  • Conversion priority: Custom readers → Polymorphic types → Standard types
  • Exception handling: Reader failures wrapped in StructuraException
  • Type checking: Runtime verification via Class.cast()
  • Storage: ConcurrentHashMap<Class<?>, Reader<?>> for thread-safe access

🔗 Related Features

  • Works alongside PolymorphicRegistry for complex object hierarchies
  • Complements default value annotations for optional fields
  • Integrates with validation system for converted values

Full Changelog: 1.1.1...1.2.0

1.1.1

11 Aug 06:28

Choose a tag to compare

🔧 Structura v1.1.1 – Polymorphic System Restoration

Critical patch restoring polymorphic configuration features lost in merge conflict.

🚨 Critical Fix

  • Restored polymorphic configuration system: Complete re-implementation of v1.1.0 features lost due to merge conflict resolution
  • Full feature parity: All polymorphic functionality from v1.1.0 has been restored and validated

🔄 Restored Features

  • @Polymorphic annotation: Type resolution based on YAML keys
  • PolymorphicRegistry<T>: Type-safe implementation registries
  • Automatic type detection: Runtime resolution of concrete implementations
  • Registry management: Creation, configuration, and retrieval of polymorphic registries
  • Package reorganization: DefaultValueRegistry properly relocated to registries package

📋 What Was Restored

// All polymorphic functionality restored
@Polymorphic(key = "type")
public interface DatabaseConfig extends Loadable { /* ... */ }

PolymorphicRegistry.create(DatabaseConfig.class, registry -> {
    registry.register("mysql", MySQLConfig.class);
    registry.register("postgres", PostgreSQLConfig.class);
});

🧪 Quality Assurance

  • Complete test suite: All 250+ polymorphic tests restored and passing
  • Feature validation: Comprehensive verification of restored functionality
  • Regression testing: Ensures no additional features were lost

📦 Impact

  • No API changes: Identical functionality to v1.1.0
  • Full compatibility: Existing code using v1.1.0 works unchanged
  • Immediate upgrade: Safe to upgrade from v1.1.0 without modifications

⚠️ Recommendation

Users on v1.1.0 should upgrade immediately to v1.1.1 to ensure access to all polymorphic features.

Full Changelog: 1.1.0...1.1.1

1.1.0

10 Aug 17:47

Choose a tag to compare

🚀 Structura v1.1.0 – Polymorphic Configuration Support

Major feature release introducing polymorphic type resolution and improved registry architecture.

✨ New Features

🔄 Polymorphic Configuration System

  • @Polymorphic annotation: Enable automatic type resolution based on YAML keys
  • Type-safe polymorphic registries: Register multiple implementations for a single interface
  • Automatic implementation detection: Resolve concrete types at runtime based on configuration data
  • Custom key mapping: Configure which YAML field determines the implementation type
@Polymorphic(key = "type")
public interface DatabaseConfig extends Loadable {
    String getHost();
    int getPort();
}

// Register implementations
PolymorphicRegistry.create(DatabaseConfig.class, registry -> {
    registry.register("mysql", MySQLConfig.class);
    registry.register("postgres", PostgreSQLConfig.class);
});

// YAML automatically resolves to correct implementation
// type: mysql
// host: "localhost"
// port: 3306

🏗️ Enhanced Registry Architecture

  • PolymorphicRegistry<T>: Type-safe registry for managing implementation mappings
  • Fluent configuration API: Clean, builder-style registry setup
  • Case-insensitive resolution: Flexible type name matching
  • Automatic class name registration: Register using simple class names
  • Registry isolation: Separate registries per interface type

🔧 Internal Improvements

  • Reorganized package structure: Moved registries to dedicated registries package
  • DefaultValueRegistry relocated to fr.traqueur.structura.registries
  • Enhanced error handling: More descriptive exceptions for registry operations
  • Comprehensive test coverage: 250+ new test cases for polymorphic functionality

📚 API Reference

Registry Management

// Create and configure registry
PolymorphicRegistry.create(MyInterface.class, registry -> {
    registry.register("impl1", Implementation1.class);
    registry.register("impl2", Implementation2.class);
    registry.register(Implementation3.class); // Auto-naming
});

// Retrieve registry
PolymorphicRegistry<MyInterface> registry = PolymorphicRegistry.get(MyInterface.class);

// Query implementations
Optional<Class<? extends MyInterface>> impl = registry.get("impl1");
Set<String> availableTypes = registry.availableNames();

Polymorphic Annotation

@Polymorphic(key = "provider") // Custom key (default: "type")
public interface PaymentProvider extends Loadable {
    void processPayment(double amount);
}

🎯 Use Cases

  • Plugin systems: Dynamically load different implementations based on configuration
  • Service providers: Switch between payment processors, databases, etc.
  • Environment-specific configs: Different implementations for dev/staging/prod
  • Feature flags: Enable/disable functionality through configuration
  • Modular architectures: Clean separation of interface and implementation

🛡️ Error Handling

  • Registry validation: Prevents duplicate registrations and null values
  • Type safety: Compile-time guarantees for polymorphic resolution
  • Descriptive exceptions: Clear error messages for configuration issues
  • Graceful fallbacks: Empty optionals for missing implementations

📦 Migration Notes

  • Package changes: DefaultValueRegistry moved to registries package
  • No breaking API changes: Existing configuration code remains compatible
  • Optional feature: Polymorphic support is opt-in via annotations

🧪 Quality Assurance

  • Comprehensive test suite: 250+ tests covering core functionality and edge cases
  • Type safety validation: Ensures registry isolation and type correctness
  • Error scenario coverage: Validates exception handling and error messages
  • Performance testing: Efficient registry lookup and resolution

Full Changelog: 1.0.3...1.1.0

1.0.3

10 Aug 13:15

Choose a tag to compare

🚀 Structura v1.0.3 – Key Mapping & Architecture Refactoring

Major feature addition with key-based YAML mapping and comprehensive architecture improvements.

✨ New Features

  • 🔑 Key-based YAML mapping with @Options(isKey = true)

    • Simple key mapping: YAML keys become field values for primitive types
    • Complex object flattening: Record fields are extracted to the same level as other fields
    • Recursive key mapping: Works at any nesting level in your configuration hierarchy
    • Type-safe key conversion: Automatic conversion between YAML keys and Java field types
  • 🎯 Enhanced validation system

    • @Min/@Max: Numeric range validation with custom error messages
    • @Size: Collection and string size constraints
    • @NotEmpty: Ensures collections, maps, and strings are not empty
    • @Pattern: Regex pattern matching for string fields
    • Nested validation: Automatically validates nested records and enum fields
    • Custom error messages: Placeholder support for dynamic error formatting

🏗️ Architecture Improvements

  • Modular design: Split monolithic StructuraProcessor into specialized components:

    • FieldMapper: Handles name conversion and key mapping logic
    • ValueConverter: Manages type conversions and collection handling
    • RecordInstanceFactory: Creates record instances with key mapping support
    • Validator: Comprehensive validation engine
  • 🔄 Improved type conversion:

    • Better enum case handling (supports lowercase, uppercase, mixed case)
    • Enhanced collection type conversion with proper generics support
    • More robust primitive type conversion with detailed error messages
    • Support for char, byte, short, float types
  • 🧪 Comprehensive test coverage:

    • 1,200+ new test cases covering all features
    • Integration tests for real-world scenarios
    • Performance tests for large configurations
    • Edge case validation and error handling tests

📚 Documentation Updates

  • 📖 Updated README with key mapping examples and complete API reference
  • 🎯 Key mapping documentation: Simple vs. complex key mapping with practical examples
  • 🔧 Validation annotations guide: Complete reference with usage patterns
  • 🏛️ Architecture documentation: Component responsibilities and interaction patterns

🛠️ API Enhancements

  • 🎛️ Custom processor configuration:

    var processor = Structura.builder()
        .withValidation(false)  // Disable validation if needed
        .build();
    Structura.with(processor);
  • 🗂️ Enhanced file operations: Better error messages and resource handling

  • 🔍 Improved debugging: More descriptive exception messages with context paths

📈 Example: Key Mapping

// Simple key mapping
public record DatabaseConnection(
    @Options(isKey = true) String name,
    String host,
    int port
) implements Loadable {}

// YAML: production: { host: "db.prod.com", port: 5432 }
// Result: name="production", host="db.prod.com", port=5432

// Complex object flattening  
public record AppConfig(
    @Options(isKey = true) ServerInfo server,
    String appName
) implements Loadable {}

// YAML: { host: "api.com", port: 8080, app-name: "MyApp" }
// Result: server.host="api.com", server.port=8080, appName="MyApp"

🔧 Breaking Changes

  • 🏗️ Internal architecture: Complete refactoring of internal components (API remains compatible)
  • 📝 Interface updates: All documentation references updated from Settings to Loadable

🐛 Bug Fixes

  • ✅ Fixed collection type conversion edge cases
  • ✅ Improved enum constant initialization reliability
  • ✅ Better handling of null and optional values in nested structures
  • ✅ Enhanced file path resolution for different operating systems

Full Changelog: 1.0.2...1.0.3

1.0.2

30 Jul 16:15

Choose a tag to compare

🐛 Structura v1.0.2 – Patch Release

🔧 Fixes

  • Fixed: Incorrect mapping of enum constant field names from snake_case instead of proper kebab-case when loading from YAML.
    → Enum fields now correctly respect kebab-case mapping like the rest of the configuration system.

Full Changelog: 1.0.1...1.0.2