Skip to content

Commit

Permalink
Merge pull request #324 from eiiches/feature/20240205-add-uuid3-and-u…
Browse files Browse the repository at this point in the history
…uid5

uuid3/1,uuid5/1: add {uuid3,uuid5}/1 to the extras module
  • Loading branch information
eiiches authored Feb 5, 2024
2 parents ad9dba1 + 7b5e2ab commit 6a453ef
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package net.thisptr.jackson.jq.extra;

import com.google.auto.service.AutoService;

import net.thisptr.jackson.jq.BuiltinFunction;
import net.thisptr.jackson.jq.Function;
import net.thisptr.jackson.jq.extra.functions.HostnameFunction;
Expand All @@ -11,6 +10,7 @@
import net.thisptr.jackson.jq.extra.functions.TimestampFunction;
import net.thisptr.jackson.jq.extra.functions.UriDecodeFunction;
import net.thisptr.jackson.jq.extra.functions.UriParseFunction;
import net.thisptr.jackson.jq.extra.functions.Uuid35Function;
import net.thisptr.jackson.jq.extra.functions.Uuid4Function;
import net.thisptr.jackson.jq.module.BuiltinModule;
import net.thisptr.jackson.jq.module.Module;
Expand All @@ -29,6 +29,8 @@ public ModuleImpl() {
addFunction(new UriDecodeFunction());
addFunction(new UriParseFunction());
addFunction(new Uuid4Function());
addFunction("uuid3/1", new Uuid35Function(3));
addFunction("uuid5/1", new Uuid35Function(5));
}

private void addFunction(final Function f) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package net.thisptr.jackson.jq.extra.functions;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.TextNode;
import net.thisptr.jackson.jq.Expression;
import net.thisptr.jackson.jq.Function;
import net.thisptr.jackson.jq.PathOutput;
import net.thisptr.jackson.jq.Scope;
import net.thisptr.jackson.jq.Version;
import net.thisptr.jackson.jq.exception.JsonQueryException;
import net.thisptr.jackson.jq.exception.JsonQueryTypeException;
import net.thisptr.jackson.jq.extra.internal.misc.Preconditions;
import net.thisptr.jackson.jq.extra.internal.misc.UuidUtils;
import net.thisptr.jackson.jq.path.Path;

public class Uuid35Function implements Function {
private final int uuidVersion;

public Uuid35Function(int uuidVersion) {
this.uuidVersion = uuidVersion;
}

@Override
public void apply(Scope scope, List<Expression> args, JsonNode in, Path path, PathOutput output, Version version) throws JsonQueryException {
Preconditions.checkInputType("uuid5", in, JsonNodeType.STRING, JsonNodeType.BINARY);

args.get(0).apply(scope, in, (namespaceArg) -> {
if (!namespaceArg.isTextual())
throw new JsonQueryTypeException("namespace must be string, but got: %s", namespaceArg.getNodeType());
UUID namespace;
try {
namespace = UUID.fromString(namespaceArg.asText());
} catch (IllegalArgumentException e) {
throw new JsonQueryException("namespace must be a valid UUID", e);
}

UUID uuid;
if (in.isBinary()) {
try {
uuid = UuidUtils.uuid3or5(namespace, in.binaryValue(), this.uuidVersion);
} catch (IOException e) {
throw new JsonQueryException(e);
}
} else {
uuid = UuidUtils.uuid3or5(namespace, in.asText().getBytes(StandardCharsets.UTF_8), this.uuidVersion);
}

output.emit(new TextNode(uuid.toString()), null);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package net.thisptr.jackson.jq.extra.internal.misc;

import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;

public class UuidUtils {

public static UUID uuid3(final UUID namespace, final byte[] name) {
return uuid3or5(namespace, name, 3);
}

public static UUID uuid5(final UUID namespace, final byte[] name) {
return uuid3or5(namespace, name, 5);
}

public static UUID uuid3or5(final UUID namespace, final byte[] name, final int version) {
// https://datatracker.ietf.org/doc/html/rfc4122#section-4.3
MessageDigest md;
try {
switch (version) {
case 3:
md = MessageDigest.getInstance("MD5");
break;
case 5:
md = MessageDigest.getInstance("SHA-1");
break;
default:
throw new IllegalArgumentException("invalid version");
}
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}

md.update(toBytes(namespace));
md.update(name);
byte[] hash = md.digest();

// put in the variant and version bits
hash[6] &= (byte) 0b0000_1111;
hash[6] |= (byte) (version << 4);
hash[8] &= (byte) 0b0011_1111;
hash[8] |= (byte) 0b1000_0000;
return fromBytes(hash);
}

public static byte[] toBytes(UUID uuid) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(uuid.getMostSignificantBits());
bb.putLong(uuid.getLeastSignificantBits());
return bb.array();
}

public static UUID fromBytes(byte[] bytes) {
ByteBuffer bb = ByteBuffer.wrap(bytes);
long mostSigBits = bb.getLong();
long leastSigBits = bb.getLong();
return new UUID(mostSigBits, leastSigBits);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package net.thisptr.jackson.jq.extra;

import java.util.ArrayList;
import java.util.List;

import com.fasterxml.jackson.databind.JsonNode;
import net.thisptr.jackson.jq.JsonQuery;
import net.thisptr.jackson.jq.Scope;
import net.thisptr.jackson.jq.Version;
import net.thisptr.jackson.jq.exception.JsonQueryException;
import net.thisptr.jackson.jq.module.Module;
import net.thisptr.jackson.jq.module.ModuleLoader;

public class TestUtils {
public static List<JsonNode> runQuery(String queryText, JsonNode in, Version version) throws JsonQueryException {
Scope scope = Scope.newEmptyScope();
scope.setModuleLoader(new ModuleLoaderForTest());
JsonQuery query = JsonQuery.compile("import \"jackson-jq/extras\" as extras; " + queryText, version);
List<JsonNode> results = new ArrayList<>();
query.apply(scope, in, results::add);
return results;
}

public static class ModuleLoaderForTest implements ModuleLoader {
@Override
public Module loadModule(Module caller, String path, JsonNode metadata) throws JsonQueryException {
if (path.equals("jackson-jq/extras"))
return new ModuleImpl();
return null;
}

@Override
public JsonNode loadData(Module caller, String path, JsonNode metadata) throws JsonQueryException {
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package net.thisptr.jackson.jq.extra.functions;

import java.nio.charset.StandardCharsets;
import java.util.List;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.BinaryNode;
import com.fasterxml.jackson.databind.node.TextNode;
import net.thisptr.jackson.jq.Versions;
import net.thisptr.jackson.jq.exception.JsonQueryException;
import net.thisptr.jackson.jq.extra.TestUtils;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class Uuid35FunctionTest {

@Test
public void testUuid3() throws JsonQueryException {
List<JsonNode> results = TestUtils.runQuery("extras::uuid3(\"6ba7b810-9dad-11d1-80b4-00c04fd430c8\")", TextNode.valueOf("example.com"), Versions.JQ_1_6);
assertThat(results).containsExactly(TextNode.valueOf("9073926b-929f-31c2-abc9-fad77ae3e8eb"));
}

@Test
public void testUuid5() throws JsonQueryException {
List<JsonNode> results = TestUtils.runQuery("extras::uuid5(\"6ba7b810-9dad-11d1-80b4-00c04fd430c8\")", TextNode.valueOf("example.com"), Versions.JQ_1_6);
assertThat(results).containsExactly(TextNode.valueOf("cfbff0d1-9375-5685-968c-48ce8b15ae17"));
}

@Test
public void testUuid5WithBinaryInput() throws JsonQueryException {
JsonNode in = BinaryNode.valueOf("example.com".getBytes(StandardCharsets.UTF_8));
List<JsonNode> results = TestUtils.runQuery("extras::uuid5(\"6ba7b810-9dad-11d1-80b4-00c04fd430c8\")", in, Versions.JQ_1_6);
assertThat(results).containsExactly(TextNode.valueOf("cfbff0d1-9375-5685-968c-48ce8b15ae17"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package net.thisptr.jackson.jq.extra.internal.misc;

import java.nio.charset.StandardCharsets;
import java.util.UUID;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class UuidUtilsTest {

private static final UUID NAMESPACE_DNS = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8");

@Test
public void testUuid3() {
// https://www.uuidtools.com/v3
assertThat(UuidUtils.uuid3(NAMESPACE_DNS, "example.com".getBytes(StandardCharsets.UTF_8)))
.isEqualTo(UUID.fromString("9073926b-929f-31c2-abc9-fad77ae3e8eb"));
}

@Test
public void testUuid5() {
// https://www.uuidtools.com/v5
assertThat(UuidUtils.uuid5(NAMESPACE_DNS, "example.com".getBytes(StandardCharsets.UTF_8)))
.isEqualTo(UUID.fromString("cfbff0d1-9375-5685-968c-48ce8b15ae17"));
}

@Test
public void testToBytesAndFromBytes() {
byte[] namespaceDnsBytes = new byte[]{
0x6b, (byte) 0xa7, (byte) 0xb8, 0x10,
(byte) 0x9d, (byte) 0xad,
0x11, (byte) 0xd1,
(byte) 0x80, (byte) 0xb4,
0x00, (byte) 0xc0, 0x4f, (byte) 0xd4, 0x30, (byte) 0xc8,
};
assertThat(UuidUtils.toBytes(NAMESPACE_DNS)).isEqualTo(namespaceDnsBytes);
assertThat(UuidUtils.fromBytes(namespaceDnsBytes)).isEqualTo(NAMESPACE_DNS);
}
}

0 comments on commit 6a453ef

Please sign in to comment.