Repository navigation
Adds time-based id generation with an injectable byte source - #4
Merged
Merged
Conversation
Uxid.generate() and Uxid.generate(UxidOptions) build the time-based ids the uxid wire format defines: an optional prefix and delimiter, a standard or compact time field and a random field in Crockford Base32, at the five sizes, in either letter case. The options builder takes the prefix, size, random width, compact time, letter case, delimiter, a fixed time and fixed random bytes for test vectors. Random bytes come from a ByteSource, a functional interface taking a count; the default draws from one shared SecureRandom, so callers can inject another secure source without global configuration. A request the wire format refuses throws UxidException, an IllegalArgumentException carrying an ErrorCode from the format's closed set of generation errors; an option wrong on its own is refused by its setter instead. The tests carry a hand-picked subset of the corpus encode and generate-refusal cases at v2.10.0. The package-private anchor type goes: the package now exports real types, so javac no longer needs it. Refs: uxjv-94x
Re-running the mutation that makes generate draw width + 1 bytes turns the test red on its id assertion, which runs before the counts assertion, so the note now names the mutation's form and that assertion. Refs: uxjv-94x
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What
Time-based id generation, the first public API of
systems.riddler:uxid.Uxid.generate()andUxid.generate(UxidOptions)build the ids the uxid wire format (uxid-exguides/wire-format.mdatv2.10.0) defines: an optional prefix and delimiter, a standard (48-bit) or compact (low 40 bits) time field, and a random field, in Crockford Base32 with the short group first in the time and last in the random field.UxidOptions.builder()takesprefix(""is no prefix),size,randSize,compactTime,letterCase,delimiter,time,rand(fixed random bytes, documented as test vectors only) andbyteSource.Size(XS,S,M,L,XL; defaultXL), withSize.parsereading the short spellings and the long ones (xsmall,small,medium,large,xlarge) andwireName()giving the corpus spelling.LetterCase(LOWER, the default, andUPPER), applied to the body only; the prefix is emitted as given.ByteSource, a functional interface taking a count and returning that many bytes. The default,ByteSource.secureRandom(), draws from one sharedSecureRandom;java.util.Randomis not used anywhere.UxidException, anIllegalArgumentException, whosecode()is anErrorCode. The enum is the wire format's closed set of generation errors, each constant'swireName()the corpus spelling:compact_time_out_of_range,rand_width_mismatch,rand_size_too_large,from_with_monotonic,rand_with_from,rand_with_monotonic. This change raisescompact_time_out_of_range,rand_width_mismatchandrand_size_too_large;from_with_monotonic,rand_with_fromandrand_with_monotonicbelong to deterministic ids and the monotonic generator, which later changes add, and are declared here so the set is complete from the start.changelog.d/uxjv-94x.mdrecords the additions.Behaviour worth reading
randSize, or the size's width for the time form (the wire format's size table, held inSize.randBytes). Widths and fixed random bytes are counted in bytes.generaterefuses in the reference's order (uxid-exUXID.Encoder.resolveandUXID.newatv2.10.0): a width above 10 bytes, then fixed bytes of the wrong count, then a compact time whose first character would be the marker. Every refusal comes before the byte source is called.IllegalStateException.System.currentTimeMillis()is read inside the call.Provenance
Uxidclass with static methods, aUxidOptionsbuilder,ByteSourcea functional interface,UxidExceptionanIllegalArgumentExceptionwith anErrorCodeenum) was decided by the conductor under a standing consent, 2026-10-21.randSize, a time outside 0 to 2^48 - 1) is refused by its setter with a plainIllegalArgumentExceptionorNullPointerException, soErrorCodestays exactly the wire format's closed set;UxidExceptionis kept for requests that contradict the format.caseis a Java keyword, so the option isletterCase(LetterCase).SecureRandomin astatic finalinstance, the shapejava.util.UUID.randomUUID()uses. It holds no configuration and nothing a caller can observe or change; a newSecureRandomper id would seed a new generator on every call. The "no global state" rule names static mutable counters, caches and configuration, and this is none of them.Size.parsematches the short and long lower-case spellings exactly and refuses anything else.Tests
GenerateCorpusTestcopies a hand-picked subset of the corpus atv2.10.0into parameterized tests, each case named by its corpus id: encode cases across every size, both time forms and both cases, the all-ones random fields, time zero and the 48-bit maximum, the compact guard's last valid millisecond, no prefix, the empty prefix and a prefix containing the delimiter; and every generate refusal this change raises (the compact-guard cases, the width mismatches, the random width above 10). The full corpus runner comes with a later change.UxidGenerateTestcovers the defaults, the clock read, the byte source contract, explicit widths, compact widths per size, refusal ordering, letter case, delimiters, the prefix, defensive copies and the setter refusals.SizeAndErrorCodeTestcovers the spellings, the wire names and the closed error set.Gate
mise run gateis green on this head (Temurin 25.0.4, Maven 3.9.16): the enforcer rules, the compile with every javac lint and Error Prone, the tests, Spotless, the JaCoCo line check, and the sources and javadoc jars with doclint. The commit is the exact tree that run passed on, and main had not moved.Refs: uxjv-94x