diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..5a35cd4 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,3 @@ +RELEASE_TYPE: minor + +Change the `Generators.uuids()` return type from `String` to `java.util.UUID`, and expose version configuration as a `uuids().version(v)` method. diff --git a/src/main/java/dev/hegel/Generators.java b/src/main/java/dev/hegel/Generators.java index 17091a5..d476cc6 100644 --- a/src/main/java/dev/hegel/Generators.java +++ b/src/main/java/dev/hegel/Generators.java @@ -37,6 +37,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.function.Function; /** @@ -463,8 +464,9 @@ public static Deferred deferred() { * Derive a generator for {@code type} by reflection. * *

Supports scalar types ({@code int}, {@code long}, {@code boolean}, {@code float}, {@code - * double}, {@code String}, {@code byte[]} and their wrappers), enums, records (recursively), and - * {@code List}, {@code Set}, {@code Optional} and {@code Map} of supported element types. + * double}, {@code String}, {@code byte[]}, {@link UUID}, and their wrappers), enums, records + * (recursively), and {@code List}, {@code Set}, {@code Optional} and {@code Map} of supported + * element types. * * @param type the type to derive a generator for * @param the type @@ -519,9 +521,11 @@ public static IpAddressGenerator ipAddresses() { } /** - * @return a generator of UUID strings + * Generates {@link UUID} values; see {@link UuidGenerator} for configuration capabilities. + * + * @return a UUID generator */ - public static Generator uuids() { + public static UuidGenerator uuids() { return new UuidGenerator(); } diff --git a/src/main/java/dev/hegel/generators/Derive.java b/src/main/java/dev/hegel/generators/Derive.java index 79d94f1..c4efcb0 100644 --- a/src/main/java/dev/hegel/generators/Derive.java +++ b/src/main/java/dev/hegel/generators/Derive.java @@ -67,6 +67,9 @@ private static Generator scalar(Class cls) { if (cls == String.class) { return Generators.text(); } + if (cls == java.util.UUID.class) { + return Generators.uuids(); + } if (cls == byte[].class) { return Generators.binary(); } diff --git a/src/main/java/dev/hegel/generators/UuidGenerator.java b/src/main/java/dev/hegel/generators/UuidGenerator.java index f0b77f7..09ca3ab 100644 --- a/src/main/java/dev/hegel/generators/UuidGenerator.java +++ b/src/main/java/dev/hegel/generators/UuidGenerator.java @@ -3,14 +3,47 @@ import com.upokecenter.cbor.CBORObject; import dev.hegel.Cbor; import dev.hegel.Generator; +import java.util.UUID; /** - * Generates UUID strings. Always basic (one engine call). + * Generates {@link UUID} values. Always basic (one engine call). + * + *

By default generates UUIDs of any version; use {@link #version(int)} to restrict to a + * specific RFC 4122 version (1–5). */ -public final class UuidGenerator implements Generator { +public final class UuidGenerator implements Generator { + private final Integer version; + + public UuidGenerator() { + this((Integer) null); + } + + private UuidGenerator(Integer version) { + this.version = validateVersion(version); + } + + /** + * @param version the UUID version to generate; must be an RFC 4122 version in {@code [1, 5]} + * @return a copy pinned to the requested version + */ + public UuidGenerator version(int version) { + return new UuidGenerator(version); + } + + private static Integer validateVersion(Integer version) { + if (version != null && (version < 1 || version > 5)) { + throw new IllegalArgumentException("uuids: version must be in [1, 5]"); + } + return version; + } + /** @hidden */ @Override - public BasicGenerator asBasic() { - return new BasicGenerator<>(CBORObject.NewMap().Add("type", "uuid"), Cbor::asString); + public BasicGenerator asBasic() { + CBORObject schema = CBORObject.NewMap().Add("type", "uuid"); + if (version != null) { + schema.Add("version", version); + } + return new BasicGenerator<>(schema, raw -> UUID.fromString(Cbor.asString(raw))); } } diff --git a/src/test/java/dev/hegel/ConformanceTest.java b/src/test/java/dev/hegel/ConformanceTest.java index 58638d7..8289867 100644 --- a/src/test/java/dev/hegel/ConformanceTest.java +++ b/src/test/java/dev/hegel/ConformanceTest.java @@ -42,6 +42,7 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.util.List; +import java.util.Objects; import org.junit.jupiter.api.Test; /** Behaviour/conformance suite exercising every generator against the real engine. */ @@ -192,7 +193,7 @@ void formatGenerators() { ipAddresses().v4(), s -> s.chars().filter(c -> c == '.').count() == 3); assertAllExamples(ipAddresses().v6(), s -> s.contains(":")); assertAllExamples(ipAddresses(), s -> s.contains(".") || s.contains(":")); - assertAllExamples(uuids(), s -> s.contains("-")); + assertAllExamples(uuids(), Objects::nonNull); assertAllExamples(fromRegex("[0-9]{3}"), s -> s.matches(".*[0-9]{3}.*")); } diff --git a/src/test/java/dev/hegel/DerivationTest.java b/src/test/java/dev/hegel/DerivationTest.java index cd05193..cf1e3ca 100644 --- a/src/test/java/dev/hegel/DerivationTest.java +++ b/src/test/java/dev/hegel/DerivationTest.java @@ -11,8 +11,10 @@ import dev.hegel.generators.Derive; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.UUID; import org.junit.jupiter.api.Test; class DerivationTest { @@ -96,6 +98,11 @@ void derivesWrapperScalarsAndBytes() { w -> w.l() != null && w.b() != null && w.d() != null && w.data() != null && w.i() != null); } + @Test + void derivesUuidScalars() { + assertAllExamples(forType(UUID.class), Objects::nonNull); + } + record Temporal( java.time.Duration d, java.time.LocalDate date, diff --git a/src/test/java/dev/hegel/UuidGeneratorTest.java b/src/test/java/dev/hegel/UuidGeneratorTest.java new file mode 100644 index 0000000..9081bdc --- /dev/null +++ b/src/test/java/dev/hegel/UuidGeneratorTest.java @@ -0,0 +1,37 @@ +package dev.hegel; + +import static dev.hegel.Generators.uuids; +import static dev.hegel.Utils.assertAllExamples; +import static dev.hegel.Utils.findAny; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.UUID; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class UuidGeneratorTest { + @Test + void defaultGeneratesValidUuids() { + assertAllExamples(uuids(), u -> UUID.fromString(u.toString()).equals(u)); + } + + /** By default the version is unconstrained, so the engine emits non-RFC versions too. */ + @Test + void defaultIsNotPinnedToAnRfcVersion() { + findAny(uuids(), u -> u.version() < 1 || u.version() > 5); + } + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 4, 5}) + void configuredUuidVersionsAreRespected(int version) { + assertAllExamples( + uuids().version(version), u -> UUID.fromString(u.toString()).equals(u) && u.version() == version); + } + + @ParameterizedTest + @ValueSource(ints = {0, 6, 7, 8, 9}) + void invalidVersionsAreRejected(int version) { + assertThrows(IllegalArgumentException.class, () -> uuids().version(version)); + } +}