mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 03:57:51 +02:00
Add VKey to String and String to VKey methods (#1396)
* Add stringify and parse methods to SerializeUTils * Improve comments and test cases * Fix comments and test strings * Fix dependency warning
This commit is contained in:
parent
230daeeab7
commit
adb82565db
6 changed files with 325 additions and 1 deletions
|
@ -18,10 +18,13 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||||
|
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.googlecode.objectify.Key;
|
import com.googlecode.objectify.Key;
|
||||||
import google.registry.model.BackupGroupRoot;
|
import google.registry.model.BackupGroupRoot;
|
||||||
import google.registry.model.ImmutableObject;
|
import google.registry.model.ImmutableObject;
|
||||||
import google.registry.model.translators.VKeyTranslatorFactory;
|
import google.registry.model.translators.VKeyTranslatorFactory;
|
||||||
|
import google.registry.util.SerializeUtils;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
@ -36,6 +39,15 @@ public class VKey<T> extends ImmutableObject implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = -5291472863840231240L;
|
private static final long serialVersionUID = -5291472863840231240L;
|
||||||
|
|
||||||
|
// Info that's stored in in vkey string generated via stringify().
|
||||||
|
private static final String SQL_LOOKUP_KEY = "sql";
|
||||||
|
private static final String OFY_LOOKUP_KEY = "ofy";
|
||||||
|
private static final String CLASS_TYPE = "kind";
|
||||||
|
|
||||||
|
// Web safe delimiters that won't be used in base 64.
|
||||||
|
private static final String KV_SEPARATOR = ":";
|
||||||
|
private static final String DELIMITER = "@";
|
||||||
|
|
||||||
// The SQL key for the referenced entity.
|
// The SQL key for the referenced entity.
|
||||||
Serializable sqlKey;
|
Serializable sqlKey;
|
||||||
|
|
||||||
|
@ -114,6 +126,47 @@ public class VKey<T> extends ImmutableObject implements Serializable {
|
||||||
return new VKey<T>(kind, Key.create(kind, name), name);
|
return new VKey<T>(kind, Key.create(kind, name), name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@link VKey} from the string representation of a vkey.
|
||||||
|
*
|
||||||
|
* <p>There are two types of string representations: 1) existing ofy key string handled by
|
||||||
|
* fromWebsafeKey() and 2) string encoded via stringify() where @ separates the substrings and
|
||||||
|
* each of the substrings contains a look up key, ":", and its corresponding value. The key info
|
||||||
|
* is encoded via Base64. The string begins with "kind:" and it must contains at least ofy key or
|
||||||
|
* sql key.
|
||||||
|
*
|
||||||
|
* <p>Example of a Vkey string by fromWebsafeKey(): "agR0ZXN0chYLEgpEb21haW5CYXNlIgZST0lELTEM"
|
||||||
|
*
|
||||||
|
* <p>Example of a vkey string by stringify(): "google.registry.testing.TestObject@sql:rO0ABX" +
|
||||||
|
* "QAA2Zvbw@ofy:agR0ZXN0cjELEg9FbnRpdHlHcm91cFJvb3QiCWNyb3NzLXRsZAwLEgpUZXN0T2JqZWN0IgNmb28M",
|
||||||
|
* where sql key and ofy key are values are encoded in Base64.
|
||||||
|
*/
|
||||||
|
public static <T> VKey<T> create(String keyString) throws Exception {
|
||||||
|
if (!keyString.startsWith(CLASS_TYPE + KV_SEPARATOR)) {
|
||||||
|
// to handle the existing ofy key string
|
||||||
|
return fromWebsafeKey(keyString);
|
||||||
|
} else {
|
||||||
|
ImmutableMap<String, String> kvs =
|
||||||
|
ImmutableMap.copyOf(
|
||||||
|
Splitter.on(DELIMITER).withKeyValueSeparator(KV_SEPARATOR).split(keyString));
|
||||||
|
Class classType = Class.forName(kvs.get(CLASS_TYPE));
|
||||||
|
|
||||||
|
if (kvs.containsKey(SQL_LOOKUP_KEY) && kvs.containsKey(OFY_LOOKUP_KEY)) {
|
||||||
|
return VKey.create(
|
||||||
|
classType,
|
||||||
|
SerializeUtils.parse(Serializable.class, kvs.get(SQL_LOOKUP_KEY)),
|
||||||
|
Key.create(kvs.get(OFY_LOOKUP_KEY)));
|
||||||
|
} else if (kvs.containsKey(SQL_LOOKUP_KEY)) {
|
||||||
|
return VKey.createSql(
|
||||||
|
classType, SerializeUtils.parse(Serializable.class, kvs.get(SQL_LOOKUP_KEY)));
|
||||||
|
} else if (kvs.containsKey(OFY_LOOKUP_KEY)) {
|
||||||
|
return VKey.createOfy(classType, Key.create(kvs.get(OFY_LOOKUP_KEY)));
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(String.format("Cannot parse key string: %s", keyString));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a clone with an ofy key restored from {@code ancestors}.
|
* Returns a clone with an ofy key restored from {@code ancestors}.
|
||||||
*
|
*
|
||||||
|
@ -233,4 +286,29 @@ public class VKey<T> extends ImmutableObject implements Serializable {
|
||||||
public static <T> VKey<T> fromWebsafeKey(String ofyKeyRepr) {
|
public static <T> VKey<T> fromWebsafeKey(String ofyKeyRepr) {
|
||||||
return from(Key.create(ofyKeyRepr));
|
return from(Key.create(ofyKeyRepr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the string representation of a {@link VKey}.
|
||||||
|
*
|
||||||
|
* <p>The string representation of a vkey contains its type, and sql key or ofy key, or both. Each
|
||||||
|
* of the keys is first serialized into a byte array then encoded via Base64 into a web safe
|
||||||
|
* string.
|
||||||
|
*
|
||||||
|
* <p>The string representation of a vkey contains key values pairs separated by delimiter "@".
|
||||||
|
* Another delimiter ":" is put in between each key and value. The following is the complete
|
||||||
|
* format of the string: "kind:class_name@sql:encoded_sqlKey@ofy:encoded_ofyKey", where kind is
|
||||||
|
* required. The string representation may contain an encoded ofy key, or an encoded sql key, or
|
||||||
|
* both.
|
||||||
|
*/
|
||||||
|
public String stringify() {
|
||||||
|
// class type is required to create a vkey
|
||||||
|
String key = CLASS_TYPE + KV_SEPARATOR + getKind().getName();
|
||||||
|
if (maybeGetSqlKey().isPresent()) {
|
||||||
|
key += DELIMITER + SQL_LOOKUP_KEY + KV_SEPARATOR + SerializeUtils.stringify(getSqlKey());
|
||||||
|
}
|
||||||
|
if (maybeGetOfyKey().isPresent()) {
|
||||||
|
key += DELIMITER + OFY_LOOKUP_KEY + KV_SEPARATOR + getOfyKey().getString();
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,10 @@ import com.googlecode.objectify.annotation.Entity;
|
||||||
import google.registry.model.billing.BillingEvent.OneTime;
|
import google.registry.model.billing.BillingEvent.OneTime;
|
||||||
import google.registry.model.domain.DomainBase;
|
import google.registry.model.domain.DomainBase;
|
||||||
import google.registry.model.registrar.RegistrarContact;
|
import google.registry.model.registrar.RegistrarContact;
|
||||||
|
import google.registry.model.translators.VKeyTranslatorFactory;
|
||||||
import google.registry.testing.AppEngineExtension;
|
import google.registry.testing.AppEngineExtension;
|
||||||
import google.registry.testing.TestObject;
|
import google.registry.testing.TestObject;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
|
@ -39,6 +41,11 @@ class VKeyTest {
|
||||||
.withOfyTestEntities(TestObject.class)
|
.withOfyTestEntities(TestObject.class)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
VKeyTranslatorFactory.addTestEntityClass(TestObject.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testOptionalAccessors() {
|
void testOptionalAccessors() {
|
||||||
VKey<TestObject> key =
|
VKey<TestObject> key =
|
||||||
|
@ -130,6 +137,176 @@ class VKeyTest {
|
||||||
assertThat(vkey.getSqlKey()).isEqualTo("ROID-1");
|
assertThat(vkey.getSqlKey()).isEqualTo("ROID-1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Test stringify() with vkey created via different ways. */
|
||||||
|
@Test
|
||||||
|
void testStringify_sqlOnlyVKey() throws Exception {
|
||||||
|
assertThat(VKey.createSql(TestObject.class, "foo").stringify())
|
||||||
|
.isEqualTo("kind:google.registry.testing.TestObject@sql:rO0ABXQAA2Zvbw");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringify_ofyOnlyVKey() throws Exception {
|
||||||
|
assertThat(VKey.createOfy(TestObject.class, Key.create(TestObject.class, "foo")).stringify())
|
||||||
|
.isEqualTo(
|
||||||
|
"kind:google.registry.testing.TestObject@ofy:agR0ZXN0chMLEgpUZXN0T2JqZWN0IgNmb28M");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringify_vkeyFromWebsafeKey() throws Exception {
|
||||||
|
DomainBase domain = newDomainBase("example.com", "ROID-1", persistActiveContact("contact-1"));
|
||||||
|
Key<DomainBase> key = Key.create(domain);
|
||||||
|
VKey<DomainBase> vkey = VKey.fromWebsafeKey(key.getString());
|
||||||
|
assertThat(vkey.stringify())
|
||||||
|
.isEqualTo(
|
||||||
|
"kind:google.registry.model.domain.DomainBas"
|
||||||
|
+ "e@sql:rO0ABXQABlJPSUQtMQ"
|
||||||
|
+ "@ofy:agR0ZXN0chYLEgpEb21haW5CYXNlIgZST0lELTEM");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringify_sqlAndOfyVKey() throws Exception {
|
||||||
|
assertThat(
|
||||||
|
VKey.create(TestObject.class, "foo", Key.create(TestObject.create("foo"))).stringify())
|
||||||
|
.isEqualTo(
|
||||||
|
"kind:google.registry.testing.TestObject@sql:rO0ABXQAA2Zvbw@ofy:agR0ZXN0cjELEg9FbnRpdH"
|
||||||
|
+ "lHcm91cFJvb3QiCWNyb3NzLXRsZAwLEgpUZXN0T2JqZWN0IgNmb28M");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringify_asymmetricVKey() throws Exception {
|
||||||
|
assertThat(
|
||||||
|
VKey.create(TestObject.class, "test", Key.create(TestObject.create("foo"))).stringify())
|
||||||
|
.isEqualTo(
|
||||||
|
"kind:google.registry.testing.TestObject@sql:rO0ABXQABHRlc3Q@ofy:agR0ZXN0cjELEg9FbnRpd"
|
||||||
|
+ "HlHcm91cFJvb3QiCWNyb3NzLXRsZAwLEgpUZXN0T2JqZWN0IgNmb28M");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Test create() via different vkey string representations. */
|
||||||
|
@Test
|
||||||
|
void testCreate_stringifedVKey_sqlOnlyVKeyString() throws Exception {
|
||||||
|
assertThat(VKey.create("kind:google.registry.testing.TestObject@sql:rO0ABXQAA2Zvbw"))
|
||||||
|
.isEqualTo(VKey.createSql(TestObject.class, "foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreate_stringifedVKey_ofyOnlyVKeyString() throws Exception {
|
||||||
|
assertThat(
|
||||||
|
VKey.create(
|
||||||
|
"kind:google.registry.testing.TestObject@ofy:agR0ZXN0chMLEgpUZXN0T2JqZWN0IgNmb28M"))
|
||||||
|
.isEqualTo(VKey.createOfy(TestObject.class, Key.create(TestObject.class, "foo")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreate_stringifedVKey_asymmetricVKeyString() throws Exception {
|
||||||
|
assertThat(
|
||||||
|
VKey.create(
|
||||||
|
"kind:google.registry.testing.TestObject@sql:rO0ABXQABHRlc3Q@ofy:agR0ZXN0cjELEg9Fb"
|
||||||
|
+ "nRpdHlHcm91cFJvb3QiCWNyb3NzLXRsZAwLEgpUZXN0T2JqZWN0IgNmb28M"))
|
||||||
|
.isEqualTo(VKey.create(TestObject.class, "test", Key.create(TestObject.create("foo"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreate_stringifedVKey_sqlAndOfyVKeyString() throws Exception {
|
||||||
|
assertThat(
|
||||||
|
VKey.create(
|
||||||
|
"kind:google.registry.testing.TestObject@sql:rO0ABXQAA2Zvbw@ofy:agR0ZXN0cjELEg9Fbn"
|
||||||
|
+ "RpdHlHcm91cFJvb3QiCWNyb3NzLXRsZAwLEgpUZXN0T2JqZWN0IgNmb28M"))
|
||||||
|
.isEqualTo(VKey.create(TestObject.class, "foo", Key.create(TestObject.create("foo"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreate_stringifyVkey_fromWebsafeKey() throws Exception {
|
||||||
|
assertThat(
|
||||||
|
VKey.create(
|
||||||
|
"kind:google.registry.model.domain.DomainBase@sql:rO0ABXQABlJPSUQtMQ"
|
||||||
|
+ "@ofy:agR0ZXN0chYLEgpEb21haW5CYXNlIgZST0lELTEM"))
|
||||||
|
.isEqualTo(
|
||||||
|
VKey.fromWebsafeKey(
|
||||||
|
Key.create(
|
||||||
|
newDomainBase("example.com", "ROID-1", persistActiveContact("contact-1")))
|
||||||
|
.getString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreate_stringifedVKey_websafeKey() throws Exception {
|
||||||
|
assertThat(VKey.create("agR0ZXN0chYLEgpEb21haW5CYXNlIgZST0lELTEM"))
|
||||||
|
.isEqualTo(VKey.fromWebsafeKey("agR0ZXN0chYLEgpEb21haW5CYXNlIgZST0lELTEM"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreate_invalidStringifiedVKey_failure() throws Exception {
|
||||||
|
IllegalArgumentException thrown =
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> VKey.create("kind:google.registry.testing.TestObject@sq:l@ofya:bc"));
|
||||||
|
assertThat(thrown)
|
||||||
|
.hasMessageThat()
|
||||||
|
.contains("Cannot parse key string: kind:google.registry.testing.TestObject@sq:l@ofya:bc");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreate_invalidOfyKeyString_failure() throws Exception {
|
||||||
|
IllegalArgumentException thrown =
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> VKey.create("invalid"));
|
||||||
|
assertThat(thrown).hasMessageThat().contains("Could not parse Reference");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Test stringify() then create() flow. */
|
||||||
|
@Test
|
||||||
|
void testStringifyThenCreate_sqlOnlyVKey_testObject_stringKey_success() throws Exception {
|
||||||
|
VKey<TestObject> vkey = VKey.createSql(TestObject.class, "foo");
|
||||||
|
VKey<TestObject> newVkey = VKey.create(vkey.stringify());
|
||||||
|
assertThat(newVkey).isEqualTo(vkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringifyThenCreate_sqlOnlyVKey_testObject_longKey_success() throws Exception {
|
||||||
|
VKey<TestObject> vkey = VKey.createSql(TestObject.class, (long) 12345);
|
||||||
|
VKey<TestObject> newVkey = VKey.create(vkey.stringify());
|
||||||
|
assertThat(newVkey).isEqualTo(vkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreate_createFromExistingOfyKey_success() throws Exception {
|
||||||
|
String keyString =
|
||||||
|
Key.create(newDomainBase("example.com", "ROID-1", persistActiveContact("contact-1")))
|
||||||
|
.getString();
|
||||||
|
assertThat(VKey.fromWebsafeKey(keyString)).isEqualTo(VKey.create(keyString));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringifyThenCreate_ofyOnlyVKey_testObject_success() throws Exception {
|
||||||
|
VKey<TestObject> vkey =
|
||||||
|
VKey.createOfy(TestObject.class, Key.create(TestObject.class, "tmpKey"));
|
||||||
|
assertThat(VKey.create(vkey.stringify())).isEqualTo(vkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringifyThenCreate_ofyOnlyVKey_testObject_websafeString_success() throws Exception {
|
||||||
|
VKey<TestObject> vkey = VKey.fromWebsafeKey(Key.create(TestObject.create("foo")).getString());
|
||||||
|
assertThat(VKey.create(vkey.stringify())).isEqualTo(vkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringifyThenCreate_sqlAndOfyVKey_success() throws Exception {
|
||||||
|
VKey<TestObject> vkey =
|
||||||
|
VKey.create(TestObject.class, "foo", Key.create(TestObject.create("foo")));
|
||||||
|
assertThat(VKey.create(vkey.stringify())).isEqualTo(vkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringifyThenCreate_asymmetricVKey_success() throws Exception {
|
||||||
|
VKey<TestObject> vkey =
|
||||||
|
VKey.create(TestObject.class, "sqlKey", Key.create(TestObject.create("foo")));
|
||||||
|
assertThat(VKey.create(vkey.stringify())).isEqualTo(vkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringifyThenCreate_symmetricVKey_success() throws Exception {
|
||||||
|
VKey<TestObject> vkey = TestObject.create("foo").key();
|
||||||
|
assertThat(VKey.create(vkey.stringify())).isEqualTo(vkey);
|
||||||
|
}
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
static class OtherObject {}
|
static class OtherObject {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,7 @@ ext {
|
||||||
'com.googlecode.json-simple:json-simple:1.1.1',
|
'com.googlecode.json-simple:json-simple:1.1.1',
|
||||||
'com.ibm.icu:icu4j:68.2',
|
'com.ibm.icu:icu4j:68.2',
|
||||||
'com.jcraft:jsch:0.1.55',
|
'com.jcraft:jsch:0.1.55',
|
||||||
|
'commons-codec:commons-codec:1.15',
|
||||||
'com.squareup:javapoet:1.13.0',
|
'com.squareup:javapoet:1.13.0',
|
||||||
'com.sun.activation:javax.activation:1.2.0',
|
'com.sun.activation:javax.activation:1.2.0',
|
||||||
'com.sun.xml.bind:jaxb-impl:2.3.3',
|
'com.sun.xml.bind:jaxb-impl:2.3.3',
|
||||||
|
|
|
@ -31,6 +31,7 @@ dependencies {
|
||||||
compile deps['com.google.protobuf:protobuf-java']
|
compile deps['com.google.protobuf:protobuf-java']
|
||||||
compile deps['com.google.re2j:re2j']
|
compile deps['com.google.re2j:re2j']
|
||||||
compile deps['com.ibm.icu:icu4j']
|
compile deps['com.ibm.icu:icu4j']
|
||||||
|
compile deps['commons-codec:commons-codec']
|
||||||
compile deps['javax.inject:javax.inject']
|
compile deps['javax.inject:javax.inject']
|
||||||
compile deps['javax.mail:mail']
|
compile deps['javax.mail:mail']
|
||||||
compile deps['javax.xml.bind:jaxb-api']
|
compile deps['javax.xml.bind:jaxb-api']
|
||||||
|
|
|
@ -22,7 +22,9 @@ import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
|
||||||
/** Utilities for easy serialization with informative error messages. */
|
/** Utilities for easy serialization with informative error messages. */
|
||||||
public final class SerializeUtils {
|
public final class SerializeUtils {
|
||||||
|
@ -47,7 +49,7 @@ public final class SerializeUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns a byte array into an object.
|
* Turns a byte string into an object.
|
||||||
*
|
*
|
||||||
* @return deserialized object or {@code null} if {@code objectBytes} is {@code null}
|
* @return deserialized object or {@code null} if {@code objectBytes} is {@code null}
|
||||||
*/
|
*/
|
||||||
|
@ -71,4 +73,19 @@ public final class SerializeUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
private SerializeUtils() {}
|
private SerializeUtils() {}
|
||||||
|
|
||||||
|
/** Turns an object into an encoded string that can be used safely as a URI query parameter. */
|
||||||
|
public static String stringify(Serializable object) {
|
||||||
|
checkNotNull(object, "Object cannot be null");
|
||||||
|
return Base64.encodeBase64URLSafeString(SerializeUtils.serialize(object));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Turns a string encoded by stringify() into an object. */
|
||||||
|
@Nullable
|
||||||
|
public static <T> T parse(Class<T> type, String objectString) {
|
||||||
|
checkNotNull(type, "Class type is not specified");
|
||||||
|
checkNotNull(objectString, "Object string cannot be null");
|
||||||
|
|
||||||
|
return SerializeUtils.deserialize(type, Base64.decodeBase64(objectString));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,12 @@ package google.registry.util;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.util.SerializeUtils.deserialize;
|
import static google.registry.util.SerializeUtils.deserialize;
|
||||||
|
import static google.registry.util.SerializeUtils.parse;
|
||||||
import static google.registry.util.SerializeUtils.serialize;
|
import static google.registry.util.SerializeUtils.serialize;
|
||||||
|
import static google.registry.util.SerializeUtils.stringify;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
/** Unit tests for {@link SerializeUtils}. */
|
/** Unit tests for {@link SerializeUtils}. */
|
||||||
|
@ -61,4 +64,51 @@ class SerializeUtilsTest {
|
||||||
() -> deserialize(String.class, new byte[] {(byte) 0xff}));
|
() -> deserialize(String.class, new byte[] {(byte) 0xff}));
|
||||||
assertThat(thrown).hasMessageThat().contains("Unable to deserialize: objectBytes=FF");
|
assertThat(thrown).hasMessageThat().contains("Unable to deserialize: objectBytes=FF");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringify_string_returnsBase64EncodedString() {
|
||||||
|
assertThat(stringify("foo")).isEqualTo("rO0ABXQAA2Zvbw");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testParse_stringClass_returnsObject() {
|
||||||
|
assertThat(parse(String.class, "rO0ABXQAA2Zvbw")).isEqualTo("foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringifyParse_stringValue_maintainsValue() {
|
||||||
|
assertThat(parse(Serializable.class, stringify("hello"))).isEqualTo("hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringifyParse_longValue_maintainsValue() {
|
||||||
|
assertThat(parse(Serializable.class, stringify((long) 12345))).isEqualTo((long) 12345);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringify_nullValue_throwsException() {
|
||||||
|
NullPointerException thrown = assertThrows(NullPointerException.class, () -> stringify(null));
|
||||||
|
assertThat(thrown).hasMessageThat().contains("Object cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testParse_nullClass_throwsException() {
|
||||||
|
NullPointerException thrown =
|
||||||
|
assertThrows(NullPointerException.class, () -> parse(null, "test"));
|
||||||
|
assertThat(thrown).hasMessageThat().contains("Class type is not specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testParse_invalidBase64String_throwsException() {
|
||||||
|
IllegalArgumentException thrown =
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> parse(String.class, "abcde:atest"));
|
||||||
|
assertThat(thrown).hasMessageThat().contains("Unable to deserialize");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testParse_nullObjectStringValue_throwsException() {
|
||||||
|
NullPointerException thrown =
|
||||||
|
assertThrows(NullPointerException.class, () -> parse(String.class, null));
|
||||||
|
assertThat(thrown).hasMessageThat().contains("Object string cannot be null");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue