Skip to content

Implement UUID builtins - #155

Open
Aayush10016 wants to merge 3 commits into
open-policy-agent:mainfrom
Aayush10016:issue-134-uuid-builtins
Open

Implement UUID builtins#155
Aayush10016 wants to merge 3 commits into
open-policy-agent:mainfrom
Aayush10016:issue-134-uuid-builtins

Conversation

@Aayush10016

Copy link
Copy Markdown
Contributor

Summary

Implements the UUID builtins requested in #134:

  • uuid.parse, including RFC 4122 metadata for version 1 and 2 UUIDs
  • uuid.rfc4122, including stable repeated values for the same key during evaluation
  • registration with the evaluator builtin registry
  • focused tests for parsing, supported input formats, invalid values, and generated RFC 4122 UUIDs

Validation

  • .\gradlew.bat --no-daemon --console=plain :opa-evaluator:test --tests io.github.open_policy_agent.opa.ast.builtin.impls.UUIDBuiltinsTest
  • .\gradlew.bat --no-daemon --console=plain :opa-evaluator:test --tests io.github.open_policy_agent.opa.ast.builtin.impls.UUIDBuiltinsTest --tests io.github.open_policy_agent.opa.ast.builtin.CapabilitiesGeneratorTest :opa-evaluator:checkstyleMain :opa-evaluator:checkstyleTest

I also tried :opa-evaluator:check; it did not complete cleanly because of unrelated existing local failures in GoTimeLayoutConverterTest and FileSystemBundleLoaderTest.

Fixes #134.

Signed-off-by: Aayush Tiwari <aayushtiwari1001@gmail.com>
@Aayush10016
Aayush10016 marked this pull request as ready for review July 17, 2026 15:05
@Aayush10016
Aayush10016 requested a review from a team as a code owner July 17, 2026 15:05

@sspaink sspaink left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the work on this!

return buffer.array();
}

private String deterministicVersion4Uuid(String key) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHA-256(key) makes this a hash of the key, not a UUID generator. Upstream internal/uuid.New reads 16 random bytes from bctx.Seed and forces the version/variant bits; the key is only a per-query cache key (bctx.Cache), so the contract is "consistent throughout a query evaluation" — not identical in every query, process and machine, and not derivable from the key by anyone who can guess it.

Closest parity with upstream is to keep this method's shape and swap the digest for a static final SecureRandom:

byte[] bytes = new byte[16];
RANDOM.nextBytes(bytes);
bytes[6] = (byte) ((bytes[6] & 0x0f) | 0x40);
bytes[8] = (byte) ((bytes[8] & 0x3f) | 0x80);

That matches internal/uuid.New's bit-fiddling exactly, and UUID.toString() produces the same lowercase canonical form as its fmt.Sprintf("%x-%x-%x-%x-%x", ...). See the note on line 80 for the caching half of this.

result = @OpaType(type = "string", name = "uuid", description = "RFC 4122 UUID"),
nondeterministic = true)
public RegoString rfc4122(EvaluationContext ctx, RegoValue[] args) {
if (ctx != null && ctx.getNdBuiltinCache() != null) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upstream caches uuid in bctx.Cache — a plain map built fresh per query (v1/topdown/query.go:424), no bound, no eviction — so k pins the value inside one evaluation and nothing beyond it. NdBuiltinCache is inter-query-shaped (LRU cap + stale-entry eviction), so once it's wired up the same key would return the same UUID to every request until an eviction, where upstream mints a fresh one per query. Note this branch is inert today, so the same-key stability actually comes from the digest on line 206.

A small per-evaluation map would match upstream; alternatively split rfc4122 into a follow-up and merge uuid.parse here, which needs none of this.

String input = getArg(args, 0, RegoString.class).getValue();
UUID uuid = parseUuid(input);
if (uuid == null) {
return RegoUndefined.INSTANCE;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builtins signal undefined by returning null — that's what invokeBuiltin checks (ir/Evaluator.java:918). A RegoUndefined is stored as an ordinary value instead, so p := uuid.parse("bad") comes out as {"p": null} rather than undefined.

Suggested change
return RegoUndefined.INSTANCE;
return null;

}
}

private String normalize(String input) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UUID.fromString is lenient about field widths, so this defines results where upstream is undefined — verified UUID.fromString("1-1-1-1-1")00000001-0001-0001-0001-000000000001, which google/uuid.Parse (used upstream via internal/uuid) rejects. Worth switching on length like upstream (36 / 38 / 45 / 32) and regex-checking the canonical form before fromString.


if (version == 2) {
result.setProperty("domain", new RegoString(domain(bytes[9])));
result.setProperty("id", RegoInt32.of(ByteBuffer.wrap(bytes, 0, 4).getInt()));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id is unsigned upstream (int(u.ID()) over a uint32), but ByteBuffer.getInt() is signed — ffffffff-48b9-21ee-b200-325096b39f47 gives -1 here vs 4294967295 in OPA.

Suggested change
result.setProperty("id", RegoInt32.of(ByteBuffer.wrap(bytes, 0, 4).getInt()));
result.setProperty(
"id", new RegoBigInt(Integer.toUnsignedLong(ByteBuffer.wrap(bytes, 0, 4).getInt())));

case 2:
return "Org";
default:
return "Invalid";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upstream's internal/uuid wraps google/uuid, and its Domain.String() returns "Domain" + N for values outside 0–2, not "Invalid".

Suggested change
return "Invalid";
return "Domain" + Byte.toUnsignedInt(value);


private UUID parseUuid(String input) {
String normalized = normalize(input);
if (normalized == null) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead branch — normalize never returns null.

description = "Parses a UUID string into its RFC 4122 metadata.",
categories = {"uuid"},
args = {@OpaType(type = "string", name = "uuid", description = "UUID string to parse")},
result = @OpaType(type = "object", name = "result", description = "parsed UUID metadata"))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upstream declares uuid.parse's result as types.NewObject(nil, types.NewDynamicProperty(types.S, types.A)), so this should carry @OpaDynamic(keyType = "string", valueType = "any") — as OpaBuiltins.runtime does — rather than an object with no properties.

Signed-off-by: Aayush Tiwari <aayushtiwari1001@gmail.com>
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.

Implement UUID builtins (uuid.*)

2 participants