Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,11 @@ public <T> DataResult<Pair<List<A>, T>> decode(@NotNull final DynamicOps<T> ops,
* GsonOps.INSTANCE, jsonElement);
* }</pre>
*
* <p><b>Null handling:</b> The inner codec must never decode to {@code null}.
* If the inner codec successfully decodes but produces a null value, this codec
* returns a {@link DataResult} error. To handle absent values, the inner codec
* should fail (returning an error), which this codec maps to {@code Optional.empty()}.</p>
*
* @param codec the base codec for the optional value, must not be {@code null}
* @param <A> the type of the optional value
* @return a new codec for {@code Optional<A>}, never {@code null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,18 @@ default T createMap(@NotNull final Map<T, T> map) {
/**
* Converts a value from another DynamicOps representation.
*
* <p><b>Cross-format edge cases:</b> Converting between different format representations
* may lose information or fail for format-specific features:</p>
* <ul>
* <li><b>Null values:</b> TOML has no null representation; nulls may be lost or cause errors</li>
* <li><b>Array types:</b> TOML requires homogeneous arrays; heterogeneous arrays will fail</li>
* <li><b>XML attributes:</b> Attribute/element distinction is lost through Dynamic round-trips</li>
* <li><b>Numeric precision:</b> BigDecimal handling varies between implementations</li>
* <li><b>Key types:</b> SnakeYAML uses raw strings, Jackson uses TextNode for map keys</li>
* </ul>
* <p>For maximum compatibility, use data shapes that are valid across all target formats:
* string/number/boolean primitives, homogeneous arrays, and non-null values.</p>
*
* @param ops the source ops
* @param input the input value
* @param <U> the source value type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -960,7 +960,7 @@ public Optional<A> partialResult() {
if (result.isSuccess()) {
return new Error<>(this.message, result.result().orElse(null));
}
return new Error<>(this.message + "; " + result.error().orElse(""), result.partialResult().orElse(null));
return new Error<>(this.message + "\n caused by: " + result.error().orElse(""), result.partialResult().orElse(null));
}
return (DataResult<B>) this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@
* }</pre>
*
* <h2>Thread Safety</h2>
* <p>Implementations should be thread-safe for concurrent access.</p>
* <p>Implementations must be thread-safe for concurrent <b>reads</b> after
* {@code freeze()} has been called. Registration methods are not required to be
* thread-safe and should only be called during single-threaded initialization.</p>
*
* @author Erik Pförtner
* @see Schema
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,13 +378,17 @@ public class MigrateCommand implements Callable<Integer> {
* <li>Full stack traces for errors</li>
* </ul>
*
* <p><b>Security note:</b> Full stack traces may expose internal implementation
* details, file paths, and class names. Do not expose verbose output to untrusted
* users if the CLI is wrapped in a service or web interface.</p>
*
* <p>Default value: {@code false}</p>
*
* <p>CLI usage: {@code -v} or {@code --verbose}</p>
*/
@Option(
names = {"-v", "--verbose"},
description = "Enable verbose output."
description = "Enable verbose output (includes stack traces; see security note in docs)."
)
private boolean verbose;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@
* become child elements, but this can be customized with Jackson annotations or mapper
* configuration.</p>
*
* <p><b>Important:</b> The {@code DynamicOps<JsonNode>} model treats all properties uniformly
* as map entries. XML attribute information is <b>not preserved</b> through Dynamic round-trips.
* Attributes become nested map entries indistinguishable from child elements, and re-serialized
* XML may differ structurally from the original. For use cases requiring attribute fidelity,
* process XML directly with Jackson's XML annotations rather than through the Dynamic API.</p>
*
* <h3>Arrays and Repeated Elements</h3>
* <p>XML has no native array type. Arrays are typically represented as repeated elements
* with the same name or wrapped in a container element. The {@link XmlMapper}'s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,16 @@ public <U> Object convertTo(@NotNull final DynamicOps<U> sourceOps,
* are immutable in Java)</li>
* </ul>
*
* <p><b>Performance Note:</b> Unlike Jackson-based implementations which delegate to
* the optimized {@code JsonNode.deepCopy()}, this method performs a manual recursive
* copy using Java reflection-free iteration. For very large data structures, this may
* be measurably slower. Consider using Jackson-based implementations
* ({@link de.splatgames.aether.datafixers.codec.yaml.jackson.JacksonYamlOps}) for
* performance-sensitive use cases.</p>
*
* <ul>
* </ul>
*
* <p><b>Performance Note</b></p>
* <p>Deep copying has O(n) time and space complexity where n is the total number of
* elements in the structure. For large data structures, this can be significant.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@
* }</pre>
*
* <h2>Thread Safety</h2>
* <p>This implementation is not thread-safe. For concurrent access, external
* synchronization is required.</p>
* <p>Thread-safe for concurrent reads after {@link #freeze()} is called.
* Registration methods ({@link #register}) are not thread-safe and should
* only be called during single-threaded initialization.</p>
*
* @author Erik Pförtner
* @see SchemaRegistry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,9 +451,13 @@ private void analyzeStepCoverage(
* but no DataFix is registered to handle the type at this version,
* a coverage gap is recorded.</p>
*
* <p><b>Note:</b> This implementation checks for the presence of any fix
* for the type. A more sophisticated implementation could verify that
* the fix actually handles all specific field changes.</p>
* <p><b>Known Limitation:</b> This implementation only checks whether <i>any</i> fix
* exists for the type at this version. It does <b>not</b> verify that the fix handles
* all specific field changes (e.g., a fix may handle field 'armor' but not 'health').
* This can produce false negatives where a gap exists at the field level but is not
* reported because a type-level fix is present. Verifying field-level coverage would
* require metadata in {@link DataFix} about which fields it modifies, which is not
* currently part of the API.</p>
*
* @param type the type being analyzed, must not be {@code null}
* @param sourceSchema the source schema, must not be {@code null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ public static TypeStructure introspect(@NotNull final Type<?> type) {
* <li>Primitive: Returns an empty list</li>
* </ul>
*
* <p><b>Recursive behavior:</b> Fields are extracted recursively from nested types.
* The result includes both parent fields and their nested children using dot-notation
* paths. For example, a type with field "player" containing nested field "position"
* with sub-field "x" produces entries: {@code ["player", "player.position",
* "player.position.x"]}.</p>
*
* @param type the type to extract fields from, must not be {@code null}
* @return a list of fields found in the type, never {@code null}
* @throws NullPointerException if {@code type} is {@code null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@ private ValidationResult(@NotNull final List<ValidationIssue> issues) {
this.infoCount = infos;
}

private ValidationResult(
@NotNull final List<ValidationIssue> issues,
final int errorCount,
final int warningCount,
final int infoCount
) {
this.issues = List.copyOf(Preconditions.checkNotNull(issues, "issues must not be null"));
this.errorCount = errorCount;
this.warningCount = warningCount;
this.infoCount = infoCount;
}

/**
* Returns an empty validation result (no issues).
*
Expand Down Expand Up @@ -321,7 +333,10 @@ public ValidationResult merge(@NotNull final ValidationResult other) {
final List<ValidationIssue> merged = new ArrayList<>(this.issues.size() + other.issues.size());
merged.addAll(this.issues);
merged.addAll(other.issues);
return new ValidationResult(merged);
return new ValidationResult(merged,
this.errorCount + other.errorCount,
this.warningCount + other.warningCount,
this.infoCount + other.infoCount);
}

@Override
Expand Down
Loading