From 2ddc80d0f4af7686d11095caf51a8589c002bc6b Mon Sep 17 00:00:00 2001 From: fxlae Date: Sat, 13 Apr 2024 14:49:21 +0200 Subject: [PATCH 01/12] remove java8 variant --- build-conventions/build.gradle.kts | 4 - .../typeid.library-conventions.gradle.kts | 26 -- gradle/libs.versions.toml | 13 +- gradle/wrapper/gradle-wrapper.properties | 2 +- lib/{java17 => }/build.gradle.kts | 5 +- lib/{java17 => }/gradle.properties | 0 lib/java8/build.gradle.kts | 14 -- lib/java8/gradle.properties | 2 - .../src/main/java/de/fxlae/typeid/TypeId.java | 227 ------------------ .../test/java/de/fxlae/typeid/SpecTest8.java | 9 - .../java/de/fxlae/typeid/TypeIdFacade8.java | 72 ------ .../java/de/fxlae/typeid/TypeIdTest8.java | 9 - lib/shared/build.gradle.kts | 24 -- .../jmh/java/de/fxlae/typeid/TypeIdBench.java | 0 .../src/main/java/de/fxlae/typeid/TypeId.java | 0 .../java/de/fxlae/typeid/lib/TypeIdLib.java | 0 .../java/de/fxlae/typeid/util/Validated.java | 0 .../de/fxlae/typeid/AbstractSpecTest.java | 0 .../de/fxlae/typeid/AbstractTypeIdTest.java | 0 .../test/java/de/fxlae/typeid/SpecTest.java | 0 .../java/de/fxlae/typeid/TypeIdFacade.java | 0 .../java/de/fxlae/typeid/TypeIdInstance.java | 0 .../de/fxlae/typeid/TypeIdStaticContext.java | 0 .../test/java/de/fxlae/typeid/TypeIdTest.java | 0 .../de/fxlae/typeid/util/ValidatedTest.java | 0 .../src/test/resources/spec/invalid.yml | 0 .../src/test/resources/spec/valid.yml | 0 settings.gradle.kts | 2 +- 28 files changed, 8 insertions(+), 401 deletions(-) rename lib/{java17 => }/build.gradle.kts (65%) rename lib/{java17 => }/gradle.properties (100%) delete mode 100644 lib/java8/build.gradle.kts delete mode 100644 lib/java8/gradle.properties delete mode 100644 lib/java8/src/main/java/de/fxlae/typeid/TypeId.java delete mode 100644 lib/java8/src/test/java/de/fxlae/typeid/SpecTest8.java delete mode 100644 lib/java8/src/test/java/de/fxlae/typeid/TypeIdFacade8.java delete mode 100644 lib/java8/src/test/java/de/fxlae/typeid/TypeIdTest8.java delete mode 100644 lib/shared/build.gradle.kts rename lib/{java17 => }/src/jmh/java/de/fxlae/typeid/TypeIdBench.java (100%) rename lib/{java17 => }/src/main/java/de/fxlae/typeid/TypeId.java (100%) rename lib/{shared => }/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java (100%) rename lib/{java17 => }/src/main/java/de/fxlae/typeid/util/Validated.java (100%) rename lib/{shared => }/src/test/java/de/fxlae/typeid/AbstractSpecTest.java (100%) rename lib/{shared => }/src/test/java/de/fxlae/typeid/AbstractTypeIdTest.java (100%) rename lib/{java17 => }/src/test/java/de/fxlae/typeid/SpecTest.java (100%) rename lib/{java17 => }/src/test/java/de/fxlae/typeid/TypeIdFacade.java (100%) rename lib/{shared => }/src/test/java/de/fxlae/typeid/TypeIdInstance.java (100%) rename lib/{shared => }/src/test/java/de/fxlae/typeid/TypeIdStaticContext.java (100%) rename lib/{java17 => }/src/test/java/de/fxlae/typeid/TypeIdTest.java (100%) rename lib/{java17 => }/src/test/java/de/fxlae/typeid/util/ValidatedTest.java (100%) rename lib/{shared => }/src/test/resources/spec/invalid.yml (100%) rename lib/{shared => }/src/test/resources/spec/valid.yml (100%) diff --git a/build-conventions/build.gradle.kts b/build-conventions/build.gradle.kts index 35419e0..e4a377c 100644 --- a/build-conventions/build.gradle.kts +++ b/build-conventions/build.gradle.kts @@ -6,7 +6,3 @@ repositories { gradlePluginPortal() mavenCentral() } - -dependencies { - implementation("com.github.johnrengelman.shadow:com.github.johnrengelman.shadow.gradle.plugin:8.1.1") -} diff --git a/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts b/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts index fe8de59..a54bcab 100644 --- a/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts +++ b/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts @@ -2,7 +2,6 @@ plugins { `java-library` `maven-publish` signing - id("com.github.johnrengelman.shadow") } group = "de.fxlae" @@ -41,31 +40,6 @@ tasks.withType { val mavenArtifactId: String by project val mavenArtifactDescription: String by project -tasks { - shadowJar { - configurations = listOf(project.configurations.compileClasspath.get()) - include("de/fxlae/**") - from(project(":lib:shared").sourceSets.main.get().output) - archiveClassifier.set("") - archiveBaseName.set(mavenArtifactId) - } - build { - dependsOn(shadowJar) - } -} - -val providedConfigurationName = "provided" - -configurations { - create(providedConfigurationName) -} - -sourceSets { - main.get().compileClasspath += configurations.getByName(providedConfigurationName) - test.get().compileClasspath += configurations.getByName(providedConfigurationName) - test.get().runtimeClasspath += configurations.getByName(providedConfigurationName) -} - publishing { publications { create("mavenJava") { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 82a6d04..843a8cd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,10 +1,9 @@ [versions] -java-uuid-generator = "4.2.0" -jackson = "2.15.2" -junit = "5.9.1" -assertj = "3.24.2" -shadow = "8.1.1" -jmh = "0.7.1" +java-uuid-generator = "5.0.0" +jackson = "2.17.0" +junit = "5.10.2" +assertj = "3.25.3" +jmh = "0.7.2" [plugins] jmh = { id = "me.champeau.jmh", version.ref = "jmh" } @@ -14,5 +13,3 @@ java-uuid-generator = { module = "com.fasterxml.uuid:java-uuid-generator", versi junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" } assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertj" } jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } -shadow = { module = "com.github.johnrengelman.shadow:com.github.johnrengelman.shadow.gradle.plugin", version.ref = "shadow" } - diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 62f495d..b82aa23 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/lib/java17/build.gradle.kts b/lib/build.gradle.kts similarity index 65% rename from lib/java17/build.gradle.kts rename to lib/build.gradle.kts index 17d9067..0d61d77 100644 --- a/lib/java17/build.gradle.kts +++ b/lib/build.gradle.kts @@ -5,14 +5,11 @@ plugins { } tasks.compileJava { - options.release.set(17) + options.release = 17 } dependencies { - "provided"(project(":lib:shared")) implementation(libs.java.uuid.generator) - testImplementation(project(path = ":lib:shared", configuration = "testArtifacts")) - jmh(project(":lib:shared")) } jmh { diff --git a/lib/java17/gradle.properties b/lib/gradle.properties similarity index 100% rename from lib/java17/gradle.properties rename to lib/gradle.properties diff --git a/lib/java8/build.gradle.kts b/lib/java8/build.gradle.kts deleted file mode 100644 index c2f608a..0000000 --- a/lib/java8/build.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -plugins { - id("typeid.java-conventions") - id("typeid.library-conventions") -} - -tasks.compileJava { - options.release.set(8) -} - -dependencies { - "provided"(project(":lib:shared")) - implementation(libs.java.uuid.generator) - testImplementation(project(path = ":lib:shared", configuration = "testArtifacts")) -} diff --git a/lib/java8/gradle.properties b/lib/java8/gradle.properties deleted file mode 100644 index 6278003..0000000 --- a/lib/java8/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -mavenArtifactId=typeid-java-jdk8 -mavenArtifactDescription=A TypeID implementation for Java (JDK8 compat) \ No newline at end of file diff --git a/lib/java8/src/main/java/de/fxlae/typeid/TypeId.java b/lib/java8/src/main/java/de/fxlae/typeid/TypeId.java deleted file mode 100644 index f88473d..0000000 --- a/lib/java8/src/main/java/de/fxlae/typeid/TypeId.java +++ /dev/null @@ -1,227 +0,0 @@ -package de.fxlae.typeid; - -import de.fxlae.typeid.lib.TypeIdLib; - -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; -import java.util.function.Function; - -import static de.fxlae.typeid.lib.TypeIdLib.encode; - -/** - * A type for representing TypeIDs. - */ -public final class TypeId { - - private final UUID uuid; - - private final String prefix; - - /** - * Creates new {@link TypeId} - * - * @param prefix the prefix - * @param uuid the UUID - * @throws NullPointerException if the prefix and/or UUID is null - * @throws IllegalArgumentException if the prefix is invalid - */ - private TypeId(String prefix, UUID uuid) { - - Objects.requireNonNull(prefix); - Objects.requireNonNull(uuid); - - String err = TypeIdLib.validatePrefixOnInput(prefix, prefix.length()); - if (err != TypeIdLib.VALID_REF) { - throw new IllegalArgumentException(err); - } - - this.prefix = prefix; - this.uuid = uuid; - } - - /** - * Creates a new prefixed {@link TypeId} based on UUIDv7. - * - * @param prefix the prefix to use - * @return the new {@link TypeId} - * @throws NullPointerException if the prefix is null - * @throws IllegalArgumentException if the prefix is invalid - */ - public static TypeId generate(String prefix) { - return of(prefix, TypeIdLib.getUuidV7()); - } - - /** - * Creates a new {@link TypeId} without prefix, based on UUIDv7. - *

Note: "no prefix" means empty string, not null. - * - * @return the new {@link TypeId}. - */ - public static TypeId generate() { - return of("", TypeIdLib.getUuidV7()); - } - - /** - * Creates a new {@link TypeId} without prefix, based on the given {@link UUID}. - *

The {@link UUID} can be of any version. - *

Note: "no prefix" means empty string, not null. - * - * @param uuid the {@link UUID} to use - * @return the new {@link TypeId} - * @throws NullPointerException if the UUID is null - */ - public static TypeId of(UUID uuid) { - return of("", uuid); - } - - /** - * Creates a new {@link TypeId}, based on the given prefix and {@link UUID}. - * - * @param prefix the prefix to use - * @param uuid the {@link UUID} to use - * @return the new {@link TypeId} - * @throws NullPointerException if the prefix and/or UUID is null - * @throws IllegalArgumentException if the prefix is invalid - */ - public static TypeId of(String prefix, UUID uuid) { - return new TypeId(prefix, uuid); - } - - /** - * Parses the textual representation of a TypeID and returns a {@link TypeId} instance. - * - * @param text the textual representation. - * @return the new {@link TypeId}. - * @throws NullPointerException if the text is null - * @throws IllegalArgumentException if the text is invalid - */ - public static TypeId parse(final String text) { - - int separatorIndex = TypeIdLib.findSeparatorIndex(text); - String err = TypeIdLib.validateInput(text, separatorIndex); - - if (err != TypeIdLib.VALID_REF) { - throw new IllegalArgumentException(err); - } - - return new TypeId( - TypeIdLib.extractPrefix(text, separatorIndex), - TypeIdLib.decodeSuffixOnInput(text, separatorIndex)); - } - - /** - * Parses the textual representation of a TypeID and executes a handler {@link Function}, depending - * on the outcome. Both provided functions must have the same return type. - * - * @param text the textual representation of the TypeID - * @param okHandler the {@link Function} that is executed if the TypeID is valid, providing the {@link TypeId} - * @param errorHandler the {@link Function} that is executed if the TypeID could not be parsed, providing the error message - * @param the result type of the handler {@link Function} that was executed - * @return the result of the handler {@link Function} that was executed - * @throws NullPointerException if the okHandler and/or errorHandler is null - */ - public static T parse( - final String text, - Function okHandler, - Function errorHandler) { - - Objects.requireNonNull(okHandler); - Objects.requireNonNull(errorHandler); - - int separatorIndex = TypeIdLib.findSeparatorIndex(text); - String err = TypeIdLib.validateInput(text, separatorIndex); - - if (err != TypeIdLib.VALID_REF) { - return errorHandler.apply(err); - } - - TypeId typeId = new TypeId( - TypeIdLib.extractPrefix(text, separatorIndex), - TypeIdLib.decodeSuffixOnInput(text, separatorIndex)); - - return okHandler.apply(typeId); - } - - /** - * Parses the textual representation of a TypeID and returns an {@link Optional}. - * - * @param text the textual representation of the TypeID - * @return an {@link Optional} containing a {@link TypeId} or an empty {@link TypeId} in case of validation errors - * @throws NullPointerException if the text is null - */ - public static Optional parseToOptional(final String text) { - - int separatorIndex = TypeIdLib.findSeparatorIndex(text); - String err = TypeIdLib.validateInput(text, separatorIndex); - - if (err != TypeIdLib.VALID_REF) { - return Optional.empty(); - } - - return Optional.of(new TypeId( - TypeIdLib.extractPrefix(text, separatorIndex), - TypeIdLib.decodeSuffixOnInput(text, separatorIndex))); - } - - /** - * Returns the prefix of this {@link TypeId}. - * - * @return the prefix - */ - public String getPrefix() { - return prefix; - } - - /** - * Returns the prefix of this {@link TypeId}. - *

This is an alias for {@link #getPrefix()} - * - * @return the prefix - */ - public String prefix() { - return getPrefix(); - } - - /** - * Returns the underlying {@link UUID} of this {@link TypeId}. - * - * @return the {@link UUID} - */ - public UUID getUuid() { - return uuid; - } - - /** - * Returns the underlying {@link UUID} of this {@link TypeId}. - *

This is an alias for {@link #getUuid()} - * - * @return the {@link UUID}. - */ - public UUID uuid() { - return getUuid(); - } - - /** - * Returns the textual representation of this {@link TypeId}. - * - * @return the textual representation. - */ - @Override - public String toString() { - return encode(prefix, uuid); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - TypeId typeId = (TypeId) o; - return Objects.equals(uuid, typeId.uuid) && Objects.equals(prefix, typeId.prefix); - } - - @Override - public int hashCode() { - return Objects.hash(uuid, prefix); - } -} diff --git a/lib/java8/src/test/java/de/fxlae/typeid/SpecTest8.java b/lib/java8/src/test/java/de/fxlae/typeid/SpecTest8.java deleted file mode 100644 index c6fdb78..0000000 --- a/lib/java8/src/test/java/de/fxlae/typeid/SpecTest8.java +++ /dev/null @@ -1,9 +0,0 @@ -package de.fxlae.typeid; - -class SpecTest8 extends AbstractSpecTest { - - @Override - TypeIdStaticContext createStaticFacade() { - return new TypeIdFacade8(); - } -} \ No newline at end of file diff --git a/lib/java8/src/test/java/de/fxlae/typeid/TypeIdFacade8.java b/lib/java8/src/test/java/de/fxlae/typeid/TypeIdFacade8.java deleted file mode 100644 index 563bb47..0000000 --- a/lib/java8/src/test/java/de/fxlae/typeid/TypeIdFacade8.java +++ /dev/null @@ -1,72 +0,0 @@ -package de.fxlae.typeid; - -import java.util.Optional; -import java.util.UUID; -import java.util.function.Function; - -public class TypeIdFacade8 implements TypeIdStaticContext { - - @Override - public TypeIdInstance generate(String prefix) { - return wrap(TypeId.generate(prefix)); - } - - @Override - public TypeIdInstance generate() { - return wrap(TypeId.generate()); - } - - @Override - public TypeIdInstance of(UUID uuid) { - return wrap(TypeId.of(uuid)); - } - - @Override - public TypeIdInstance of(String prefix, UUID uuid) { - return wrap(TypeId.of(prefix, uuid)); - } - - @Override - public TypeIdInstance parse(String text) { - return wrap(TypeId.parse(text)); - } - - @Override - public Optional parseToOptional(String text) { - return TypeId.parseToOptional(text).map(this::wrap); - } - - @Override - public O parse(String text, Function okHandler, Function errorHandler) { - Function wrapAsFunction = (this::wrap); - return TypeId.parse(text, wrapAsFunction.andThen(okHandler), errorHandler); - } - - private TypeIdInstance wrap(final TypeId typeId) { - - return new TypeIdInstance() { - - @Override - public String prefix() { - return typeId.prefix(); - } - - @Override - public UUID uuid() { - return typeId.uuid(); - } - - @Override - public String toString() { - return typeId.toString(); - } - - @Override - public Object getWrapped() { - return typeId; - } - - }; - } - -} diff --git a/lib/java8/src/test/java/de/fxlae/typeid/TypeIdTest8.java b/lib/java8/src/test/java/de/fxlae/typeid/TypeIdTest8.java deleted file mode 100644 index d904878..0000000 --- a/lib/java8/src/test/java/de/fxlae/typeid/TypeIdTest8.java +++ /dev/null @@ -1,9 +0,0 @@ -package de.fxlae.typeid; - -public class TypeIdTest8 extends AbstractTypeIdTest { - - @Override - TypeIdStaticContext createStaticFacade() { - return new TypeIdFacade8(); - } -} diff --git a/lib/shared/build.gradle.kts b/lib/shared/build.gradle.kts deleted file mode 100644 index 6019a6b..0000000 --- a/lib/shared/build.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -plugins { - id("typeid.java-conventions") -} - -tasks.compileJava { - options.release.set(8) -} - -configurations { - create("testArtifacts") -} - -tasks.register("testJar") { - from(sourceSets["test"].output) - archiveClassifier.set("tests") -} - -artifacts { - add("testArtifacts", tasks["testJar"]) -} - -dependencies { - implementation("com.fasterxml.uuid:java-uuid-generator:4.2.0") -} diff --git a/lib/java17/src/jmh/java/de/fxlae/typeid/TypeIdBench.java b/lib/src/jmh/java/de/fxlae/typeid/TypeIdBench.java similarity index 100% rename from lib/java17/src/jmh/java/de/fxlae/typeid/TypeIdBench.java rename to lib/src/jmh/java/de/fxlae/typeid/TypeIdBench.java diff --git a/lib/java17/src/main/java/de/fxlae/typeid/TypeId.java b/lib/src/main/java/de/fxlae/typeid/TypeId.java similarity index 100% rename from lib/java17/src/main/java/de/fxlae/typeid/TypeId.java rename to lib/src/main/java/de/fxlae/typeid/TypeId.java diff --git a/lib/shared/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java b/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java similarity index 100% rename from lib/shared/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java rename to lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java diff --git a/lib/java17/src/main/java/de/fxlae/typeid/util/Validated.java b/lib/src/main/java/de/fxlae/typeid/util/Validated.java similarity index 100% rename from lib/java17/src/main/java/de/fxlae/typeid/util/Validated.java rename to lib/src/main/java/de/fxlae/typeid/util/Validated.java diff --git a/lib/shared/src/test/java/de/fxlae/typeid/AbstractSpecTest.java b/lib/src/test/java/de/fxlae/typeid/AbstractSpecTest.java similarity index 100% rename from lib/shared/src/test/java/de/fxlae/typeid/AbstractSpecTest.java rename to lib/src/test/java/de/fxlae/typeid/AbstractSpecTest.java diff --git a/lib/shared/src/test/java/de/fxlae/typeid/AbstractTypeIdTest.java b/lib/src/test/java/de/fxlae/typeid/AbstractTypeIdTest.java similarity index 100% rename from lib/shared/src/test/java/de/fxlae/typeid/AbstractTypeIdTest.java rename to lib/src/test/java/de/fxlae/typeid/AbstractTypeIdTest.java diff --git a/lib/java17/src/test/java/de/fxlae/typeid/SpecTest.java b/lib/src/test/java/de/fxlae/typeid/SpecTest.java similarity index 100% rename from lib/java17/src/test/java/de/fxlae/typeid/SpecTest.java rename to lib/src/test/java/de/fxlae/typeid/SpecTest.java diff --git a/lib/java17/src/test/java/de/fxlae/typeid/TypeIdFacade.java b/lib/src/test/java/de/fxlae/typeid/TypeIdFacade.java similarity index 100% rename from lib/java17/src/test/java/de/fxlae/typeid/TypeIdFacade.java rename to lib/src/test/java/de/fxlae/typeid/TypeIdFacade.java diff --git a/lib/shared/src/test/java/de/fxlae/typeid/TypeIdInstance.java b/lib/src/test/java/de/fxlae/typeid/TypeIdInstance.java similarity index 100% rename from lib/shared/src/test/java/de/fxlae/typeid/TypeIdInstance.java rename to lib/src/test/java/de/fxlae/typeid/TypeIdInstance.java diff --git a/lib/shared/src/test/java/de/fxlae/typeid/TypeIdStaticContext.java b/lib/src/test/java/de/fxlae/typeid/TypeIdStaticContext.java similarity index 100% rename from lib/shared/src/test/java/de/fxlae/typeid/TypeIdStaticContext.java rename to lib/src/test/java/de/fxlae/typeid/TypeIdStaticContext.java diff --git a/lib/java17/src/test/java/de/fxlae/typeid/TypeIdTest.java b/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java similarity index 100% rename from lib/java17/src/test/java/de/fxlae/typeid/TypeIdTest.java rename to lib/src/test/java/de/fxlae/typeid/TypeIdTest.java diff --git a/lib/java17/src/test/java/de/fxlae/typeid/util/ValidatedTest.java b/lib/src/test/java/de/fxlae/typeid/util/ValidatedTest.java similarity index 100% rename from lib/java17/src/test/java/de/fxlae/typeid/util/ValidatedTest.java rename to lib/src/test/java/de/fxlae/typeid/util/ValidatedTest.java diff --git a/lib/shared/src/test/resources/spec/invalid.yml b/lib/src/test/resources/spec/invalid.yml similarity index 100% rename from lib/shared/src/test/resources/spec/invalid.yml rename to lib/src/test/resources/spec/invalid.yml diff --git a/lib/shared/src/test/resources/spec/valid.yml b/lib/src/test/resources/spec/valid.yml similarity index 100% rename from lib/shared/src/test/resources/spec/valid.yml rename to lib/src/test/resources/spec/valid.yml diff --git a/settings.gradle.kts b/settings.gradle.kts index d4fa996..a98f1a6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,4 +3,4 @@ pluginManagement { } rootProject.name = "typeid-java" -include("lib:shared", "lib:java8", "lib:java17") +include("lib") From 6d3ea2a1e3ffc2b5decf9986361cd765967ac742 Mon Sep 17 00:00:00 2001 From: fxlae Date: Sat, 13 Apr 2024 15:03:58 +0200 Subject: [PATCH 02/12] remove java8 test facade --- .../de/fxlae/typeid/AbstractSpecTest.java | 108 -------- .../de/fxlae/typeid/AbstractTypeIdTest.java | 257 ------------------ .../test/java/de/fxlae/typeid/SpecTest.java | 97 ++++++- .../java/de/fxlae/typeid/TypeIdFacade.java | 70 ----- .../java/de/fxlae/typeid/TypeIdInstance.java | 12 - .../de/fxlae/typeid/TypeIdStaticContext.java | 21 -- .../test/java/de/fxlae/typeid/TypeIdTest.java | 241 +++++++++++++++- 7 files changed, 329 insertions(+), 477 deletions(-) delete mode 100644 lib/src/test/java/de/fxlae/typeid/AbstractSpecTest.java delete mode 100644 lib/src/test/java/de/fxlae/typeid/AbstractTypeIdTest.java delete mode 100644 lib/src/test/java/de/fxlae/typeid/TypeIdFacade.java delete mode 100644 lib/src/test/java/de/fxlae/typeid/TypeIdInstance.java delete mode 100644 lib/src/test/java/de/fxlae/typeid/TypeIdStaticContext.java diff --git a/lib/src/test/java/de/fxlae/typeid/AbstractSpecTest.java b/lib/src/test/java/de/fxlae/typeid/AbstractSpecTest.java deleted file mode 100644 index 1fb5de6..0000000 --- a/lib/src/test/java/de/fxlae/typeid/AbstractSpecTest.java +++ /dev/null @@ -1,108 +0,0 @@ -package de.fxlae.typeid; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.type.CollectionType; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.io.IOException; -import java.util.List; -import java.util.UUID; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * This class tests against the specification published on - * github.com/jetpack-io/typeid, - * especially against valid.yml - * and - * invalid.yml. - */ -abstract class AbstractSpecTest { - - TypeIdStaticContext staticFacade; - - private static Stream provideSpecValid() throws IOException { - return loadSpec("/spec/valid.yml", SpecValid.class) - .stream() - .map(s -> Arguments.of(s.name, s.typeid, s.prefix, UUID.fromString(s.uuid))); - } - - private static Stream provideSpecInvalid() throws IOException { - return loadSpec("/spec/invalid.yml", SpecInvalid.class) - .stream() - .map(s -> Arguments.of(s.name, s.typeid, s.description)); - } - - static List loadSpec(String path, Class clazz) throws IOException { - ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); - CollectionType javaType = mapper.getTypeFactory() - .constructCollectionType(List.class, clazz); - return mapper.readValue(AbstractSpecTest.class.getResourceAsStream(path), javaType); - } - - @BeforeEach - void setupFacade() { - this.staticFacade = createStaticFacade(); - } - - abstract TypeIdStaticContext createStaticFacade(); - - @ParameterizedTest - @MethodSource("provideSpecValid") - void testEncodeAgainstSpecValid(String name, String typeIdAsString, String prefix, UUID uuid) { - TypeIdInstance typeId = staticFacade.of(prefix, uuid); - assertEquals(typeIdAsString, typeId.toString()); - } - - @ParameterizedTest - @MethodSource("provideSpecValid") - void testDecodeAgainstSpecValid(String name, String typeIdAsString, String prefix, UUID uuid) { - TypeIdInstance typeId = staticFacade.parse(typeIdAsString); - assertEquals(prefix, typeId.prefix()); - assertEquals(uuid, typeId.uuid()); - } - - @ParameterizedTest - @MethodSource("provideSpecInvalid") - void testDecodeAgainstSpecInvalid(String name, String typeIdAsString, String description) { - Assertions.assertThrows(IllegalArgumentException.class, () -> staticFacade.parse(typeIdAsString), description); - } - - /** - * Unit tests based on randomness have their pitfalls, however, - * the specification recommends back-and-forth testing with a large number of random ids. - */ - @Test - void testRandomIds() { - for (int i = 0; i < 2000; i++) { - UUID uuid = UUID.randomUUID(); - TypeIdInstance typeId1 = staticFacade.of("test", uuid); - TypeIdInstance typeId2 = staticFacade.parse(typeId1.toString()); - assertEquals("test", typeId2.prefix()); - assertEquals(uuid, typeId2.uuid()); - } - } - - @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) - static class SpecValid { - String name; - String typeid; - String prefix; - String uuid; - } - - @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) - static class SpecInvalid { - String name; - String typeid; - String description; - } -} \ No newline at end of file diff --git a/lib/src/test/java/de/fxlae/typeid/AbstractTypeIdTest.java b/lib/src/test/java/de/fxlae/typeid/AbstractTypeIdTest.java deleted file mode 100644 index 8be5153..0000000 --- a/lib/src/test/java/de/fxlae/typeid/AbstractTypeIdTest.java +++ /dev/null @@ -1,257 +0,0 @@ -package de.fxlae.typeid; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import java.util.Optional; -import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; - -/** - * Note: the actual encoding and decoding of TypeIDs is mainly tested by {@link AbstractSpecTest}. - *

This class is indented to test auxiliary methods, e.g. regarding the construction of TypeId instances. - */ -public abstract class AbstractTypeIdTest { - - static final UUID SOME_UUID = UUID.fromString("01890a5d-ac96-774b-bcce-b302099a8057"); - static final String SOME_PREFIX = "theprefix"; - static final String SOME_SUFFIX = "01h455vb4pex5vsknk084sn02q"; - static final String SOME_TYPE_ID = SOME_PREFIX + "_" + SOME_SUFFIX; - - TypeIdStaticContext staticFacade; - - @BeforeEach - void setupFacade() { - this.staticFacade = createStaticFacade(); - } - - abstract TypeIdStaticContext createStaticFacade(); - - @Test - void generateShouldReturnTypeIdForUuidV7() { - TypeIdInstance typeId = staticFacade.generate(); - assertNotNull(typeId); - assertAll( - () -> assertEquals("", typeId.prefix()), - () -> assertEquals('7', typeId.uuid().toString().charAt(14))); - } - - @Test - void generateWithPrefixShouldReturnTypeIdForUuidV7() { - TypeIdInstance typeId = staticFacade.generate(SOME_PREFIX); - assertNotNull(typeId); - assertAll( - () -> assertEquals(SOME_PREFIX, typeId.prefix()), - () -> assertEquals('7', typeId.uuid().toString().charAt(14))); - } - - @Test - void generateWithInvalidPrefixShouldFail() { - assertAll( - () -> assertThrows( - IllegalArgumentException.class, - () -> staticFacade.generate("i think this prefix is not allowed"))); - } - - @Test - void generateWithNullPrefixShouldFail() { - assertAll( - () -> assertThrows( - NullPointerException.class, - () -> staticFacade.generate(null))); - } - - @Test - void ofWithUuidShouldReturnTypeId() { - TypeIdInstance typeId = staticFacade.of(SOME_UUID); - assertNotNull(typeId); - assertAll( - () -> assertEquals("", typeId.prefix()), - () -> assertEquals(SOME_UUID, typeId.uuid())); - } - - @Test - void ofWithNullUuidShouldFail() { - assertThrows( - NullPointerException.class, - () -> staticFacade.of(null)); - } - - @Test - void ofWithPrefixAndUuidShouldReturnTypeId() { - TypeIdInstance typeId = staticFacade.of(SOME_PREFIX, SOME_UUID); - assertNotNull(typeId); - assertAll( - () -> assertEquals(SOME_PREFIX, typeId.prefix()), - () -> assertEquals(SOME_UUID, typeId.uuid())); - } - - @Test - void ofWithInvalidPrefixOrInvalidUuidShouldFail() { - assertAll( - () -> assertThrows( - IllegalArgumentException.class, - () -> staticFacade.of("i think this prefix is not allowed", SOME_UUID)) - ); - } - - @Test - void ofWithNullPrefixOrNullUuidShouldFail() { - assertAll( - () -> assertThrows( - NullPointerException.class, - () -> staticFacade.of(null, SOME_UUID)), - () -> assertThrows( - NullPointerException.class, - () -> staticFacade.of(SOME_PREFIX, null)) - ); - } - - @Test - void parseWithoutPrefixWithSuffixShouldReturnTypeId() { - TypeIdInstance typeId = staticFacade.parse(SOME_SUFFIX); - assertNotNull(typeId); - assertAll( - () -> assertEquals("", typeId.prefix()), - () -> assertEquals(SOME_UUID, typeId.uuid())); - } - - @Test - void parseWithPrefixWithSuffixShouldReturnTypeId() { - TypeIdInstance typeId = staticFacade.parse(SOME_PREFIX + "_" + SOME_SUFFIX); - assertNotNull(typeId); - assertAll( - () -> assertEquals(SOME_PREFIX, typeId.prefix()), - () -> assertEquals(SOME_UUID, typeId.uuid())); - } - - @ParameterizedTest - @ValueSource(strings = { - "01h455vb4pex5vsknk084sn02q", // suffix only - "abcdefghijklmnopqrstuvw_01h455vb4pex5vsknk084sn02q", // prefix with all allowed chars - "sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss_01h455vb4pex5vsknk084sn02q" // prefix with 63 chars - }) - void parseWithValidInputsShouldReturnTypeId(String input) { - TypeIdInstance typeId = staticFacade.parse(input); - assertNotNull(typeId); - } - - @ParameterizedTest - @ValueSource(strings = { - "", - "_", - "someprefix_", // no suffix at all - "_01h455vb4pex5vsknk084sn02q", // suffix only, but with preceding underscore - "sömeprefix_01h455vb4pex5vsknk084sn02q", // prefix with 'ö' - "someprefix_01h455öb4pex5vsknk084sn02q", // suffix with 'ö' - "sOmeprefix_01h455vb4pex5vsknk084sn02q", // prefix with 'O' - "someprefix_01h455Vb4pex5vsknk084sn02q", // suffix with 'V' - "someprefix_01h455lb4pex5vsknk084sn02q", // suffix with 'l' - "ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss_01h455vb4pex5vsknk084sn02q", // prefix with 64 chars - "someprefix_01h455vb4pex5vsknk084sn02", // suffix with 25 chars - "someprefix_01h455vb4pex5vsknk084sn02q2", // suffix with 27 chars - "someprefix_81h455vb4pex5vsknk084sn02q" // leftmost suffix char is != 0-7 - }) - void parseWithInvalidInputShouldFail(String input) { - assertThrows( - IllegalArgumentException.class, - () -> staticFacade.parse(input)); - } - - @Test - void parseWithNullInputShouldFail() { - assertThrows( - NullPointerException.class, - () -> staticFacade.parse(null)); - } - - @Test - void parseWithHandlersShouldReturnTypeIdOnSuccess() { - - String result = staticFacade.parse(SOME_TYPE_ID, - TypeIdInstance::toString, - error -> error); - - assertNotNull(result); - assertEquals(SOME_TYPE_ID, result); - } - - @Test - void parseWithHandlersShouldReturnMessageOnFailure() { - - String result = staticFacade.parse("?_" + SOME_SUFFIX, - TypeIdInstance::toString, - error -> error); - - assertNotNull(result); - assertEquals("Illegal character in prefix, must be one of [a-z]", result); - } - - @Test - void parseToOptionalShouldReturnNonEmptyOptionalOnParseSuccess() { - Optional result = staticFacade.parseToOptional(SOME_TYPE_ID); - assertThat(result).isNotEmpty(); - assertThat(result.get().prefix()).isEqualTo(SOME_PREFIX); - assertThat(result.get().uuid()).isEqualTo(SOME_UUID); - } - - @Test - void parseToOptionalShouldReturnEmptyOptionalOnParseFailure() { - Optional result = staticFacade.parseToOptional("some invalid typeid"); - assertThat(result).isEmpty(); - } - - @Test - void toStringShouldReturnTypeIdAsString() { - TypeIdInstance typeId = staticFacade.of(SOME_PREFIX, SOME_UUID); - assertNotNull(typeId); - assertEquals(SOME_PREFIX + "_" + SOME_SUFFIX, typeId.toString()); - } - - @Test - void toStringWithoutPrefixShouldReturnTypeIdAsStringWithoutUnderscore() { - TypeIdInstance typeId = staticFacade.of(SOME_UUID); - assertNotNull(typeId); - assertEquals(SOME_SUFFIX, typeId.toString()); - } - - @Test - void equalsShouldSucceedForEqualTypeIds() { - - TypeIdInstance typeIdA = staticFacade.of(SOME_UUID); - TypeIdInstance typeIdB = staticFacade.of(SOME_UUID); - - // reflexivity - assertEquals(typeIdA.getWrapped(), typeIdA.getWrapped()); - - // symmetry - assertEquals(typeIdA.getWrapped(), typeIdB.getWrapped()); - assertEquals(typeIdB.getWrapped(), typeIdA.getWrapped()); - } - - @Test - void equalsShouldFailForAnythingElse() { - - TypeIdInstance typeIdA = staticFacade.of(SOME_UUID); - TypeIdInstance typeIdB = staticFacade.of("different", SOME_UUID); - TypeIdInstance typeIdC = staticFacade.of(UUID.fromString("00000000-0000-0000-0000-000000000000")); - Object otherType = new Object(); - - assertNotEquals(typeIdA.getWrapped(), typeIdB.getWrapped()); - assertNotEquals(typeIdA.getWrapped(), typeIdC.getWrapped()); - assertNotEquals(null, typeIdA.getWrapped()); - assertNotEquals(typeIdA.getWrapped(), otherType); - } - - @Test - void hashCodeShouldBeTheSameForSameTypeIds() { - TypeIdInstance typeIdA = staticFacade.of(SOME_UUID); - TypeIdInstance typeIdB = staticFacade.of(SOME_UUID); - assertEquals(typeIdA.getWrapped().hashCode(), typeIdB.getWrapped().hashCode()); - } - -} \ No newline at end of file diff --git a/lib/src/test/java/de/fxlae/typeid/SpecTest.java b/lib/src/test/java/de/fxlae/typeid/SpecTest.java index 6beddf6..4b80eff 100644 --- a/lib/src/test/java/de/fxlae/typeid/SpecTest.java +++ b/lib/src/test/java/de/fxlae/typeid/SpecTest.java @@ -1,9 +1,98 @@ package de.fxlae.typeid; -class SpecTest extends AbstractSpecTest { +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.CollectionType; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; - @Override - TypeIdStaticContext createStaticFacade() { - return new TypeIdFacade(); +import java.io.IOException; +import java.util.List; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * This class tests against the specification published on + * github.com/jetpack-io/typeid, + * especially against valid.yml + * and + * invalid.yml. + */ +class SpecTest { + + private static Stream provideSpecValid() throws IOException { + return loadSpec("/spec/valid.yml", SpecValid.class) + .stream() + .map(s -> Arguments.of(s.name, s.typeid, s.prefix, UUID.fromString(s.uuid))); + } + + private static Stream provideSpecInvalid() throws IOException { + return loadSpec("/spec/invalid.yml", SpecInvalid.class) + .stream() + .map(s -> Arguments.of(s.name, s.typeid, s.description)); + } + + static List loadSpec(String path, Class clazz) throws IOException { + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + CollectionType javaType = mapper.getTypeFactory() + .constructCollectionType(List.class, clazz); + return mapper.readValue(SpecTest.class.getResourceAsStream(path), javaType); + } + + @ParameterizedTest + @MethodSource("provideSpecValid") + void testEncodeAgainstSpecValid(String name, String typeIdAsString, String prefix, UUID uuid) { + var typeId = TypeId.of(prefix, uuid); + assertEquals(typeIdAsString, typeId.toString()); + } + + @ParameterizedTest + @MethodSource("provideSpecValid") + void testDecodeAgainstSpecValid(String name, String typeIdAsString, String prefix, UUID uuid) { + var typeId = TypeId.parse(typeIdAsString); + assertEquals(prefix, typeId.prefix()); + assertEquals(uuid, typeId.uuid()); + } + + @ParameterizedTest + @MethodSource("provideSpecInvalid") + void testDecodeAgainstSpecInvalid(String name, String typeIdAsString, String description) { + Assertions.assertThrows(IllegalArgumentException.class, () -> TypeId.parse(typeIdAsString), description); + } + + /** + * Unit tests based on randomness have their pitfalls, however, + * the specification recommends back-and-forth testing with a large number of random ids. + */ + @Test + void testRandomIds() { + for (int i = 0; i < 2000; i++) { + UUID uuid = UUID.randomUUID(); + var typeId1 = TypeId.of("test", uuid); + var typeId2 = TypeId.parse(typeId1.toString()); + assertEquals("test", typeId2.prefix()); + assertEquals(uuid, typeId2.uuid()); + } + } + + @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) + static class SpecValid { + String name; + String typeid; + String prefix; + String uuid; + } + + @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) + static class SpecInvalid { + String name; + String typeid; + String description; } } \ No newline at end of file diff --git a/lib/src/test/java/de/fxlae/typeid/TypeIdFacade.java b/lib/src/test/java/de/fxlae/typeid/TypeIdFacade.java deleted file mode 100644 index fdfcf7f..0000000 --- a/lib/src/test/java/de/fxlae/typeid/TypeIdFacade.java +++ /dev/null @@ -1,70 +0,0 @@ -package de.fxlae.typeid; - -import java.util.Optional; -import java.util.UUID; -import java.util.function.Function; - -public class TypeIdFacade implements TypeIdStaticContext { - @Override - public TypeIdInstance generate(String prefix) { - return wrap(TypeId.generate(prefix)); - } - - @Override - public TypeIdInstance generate() { - return wrap(TypeId.generate()); - } - - @Override - public TypeIdInstance of(UUID uuid) { - return wrap(TypeId.of(uuid)); - } - - @Override - public TypeIdInstance of(String prefix, UUID uuid) { - return wrap(TypeId.of(prefix, uuid)); - } - - @Override - public TypeIdInstance parse(String text) { - return wrap(TypeId.parse(text)); - } - - @Override - public Optional parseToOptional(String text) { - return TypeId.parseToOptional(text).map(this::wrap); - } - - @Override - public O parse(String text, Function okHandler, Function errorHandler) { - Function wrapAsFunction = (this::wrap); - return TypeId.parse(text, wrapAsFunction.andThen(okHandler), errorHandler); - } - - private TypeIdInstance wrap(final TypeId typeId) { - - return new TypeIdInstance() { - - @Override - public String prefix() { - return typeId.prefix(); - } - - @Override - public UUID uuid() { - return typeId.uuid(); - } - - @Override - public String toString() { - return typeId.toString(); - } - - @Override - public Object getWrapped() { - return typeId; - } - - }; - } -} diff --git a/lib/src/test/java/de/fxlae/typeid/TypeIdInstance.java b/lib/src/test/java/de/fxlae/typeid/TypeIdInstance.java deleted file mode 100644 index f0f2de2..0000000 --- a/lib/src/test/java/de/fxlae/typeid/TypeIdInstance.java +++ /dev/null @@ -1,12 +0,0 @@ -package de.fxlae.typeid; - -import java.util.UUID; - -/** - * A test facade for testing both Java 8 and Java 17 variants with a common test set - */ -public interface TypeIdInstance { - String prefix(); - UUID uuid(); - Object getWrapped(); -} diff --git a/lib/src/test/java/de/fxlae/typeid/TypeIdStaticContext.java b/lib/src/test/java/de/fxlae/typeid/TypeIdStaticContext.java deleted file mode 100644 index 46505fa..0000000 --- a/lib/src/test/java/de/fxlae/typeid/TypeIdStaticContext.java +++ /dev/null @@ -1,21 +0,0 @@ -package de.fxlae.typeid; - -import java.util.Optional; -import java.util.UUID; -import java.util.function.Function; - -/** - * A test facade for testing both Java 8 and Java 17 variants with a common test set - */ -public interface TypeIdStaticContext { - TypeIdInstance generate(String prefix); - TypeIdInstance generate(); - TypeIdInstance of(UUID uuid); - TypeIdInstance of(String prefix, UUID uuid); - TypeIdInstance parse(final String text); - Optional parseToOptional(final String text); - O parse( - final String text, - Function okHandler, - Function errorHandler); -} diff --git a/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java b/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java index 7103c4c..c87c882 100644 --- a/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java +++ b/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java @@ -2,19 +2,203 @@ import de.fxlae.typeid.util.Validated; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Optional; +import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Note: the actual encoding and decoding of TypeIDs is mainly tested by {@link SpecTest}. + *

This class is indented to test auxiliary methods, e.g. regarding the construction of TypeId instances. + */ +class TypeIdTest { + + static final UUID SOME_UUID = UUID.fromString("01890a5d-ac96-774b-bcce-b302099a8057"); + static final String SOME_PREFIX = "theprefix"; + static final String SOME_SUFFIX = "01h455vb4pex5vsknk084sn02q"; + static final String SOME_TYPE_ID = SOME_PREFIX + "_" + SOME_SUFFIX; + + @Test + void generateShouldReturnTypeIdForUuidV7() { + var typeId = TypeId.generate(); + assertNotNull(typeId); + assertAll( + () -> assertEquals("", typeId.prefix()), + () -> assertEquals('7', typeId.uuid().toString().charAt(14))); + } + + @Test + void generateWithPrefixShouldReturnTypeIdForUuidV7() { + var typeId = TypeId.generate(SOME_PREFIX); + assertNotNull(typeId); + assertAll( + () -> assertEquals(SOME_PREFIX, typeId.prefix()), + () -> assertEquals('7', typeId.uuid().toString().charAt(14))); + } + + @Test + void generateWithInvalidPrefixShouldFail() { + assertAll( + () -> assertThrows( + IllegalArgumentException.class, + () -> TypeId.generate("i think this prefix is not allowed"))); + } + + @Test + void generateWithNullPrefixShouldFail() { + assertAll( + () -> assertThrows( + NullPointerException.class, + () -> TypeId.generate(null))); + } + + @Test + void ofWithUuidShouldReturnTypeId() { + var typeId = TypeId.of(SOME_UUID); + assertNotNull(typeId); + assertAll( + () -> assertEquals("", typeId.prefix()), + () -> assertEquals(SOME_UUID, typeId.uuid())); + } + + @Test + void ofWithNullUuidShouldFail() { + assertThrows( + NullPointerException.class, + () -> TypeId.of(null)); + } + + @Test + void ofWithPrefixAndUuidShouldReturnTypeId() { + var typeId = TypeId.of(SOME_PREFIX, SOME_UUID); + assertNotNull(typeId); + assertAll( + () -> assertEquals(SOME_PREFIX, typeId.prefix()), + () -> assertEquals(SOME_UUID, typeId.uuid())); + } + + @Test + void ofWithInvalidPrefixOrInvalidUuidShouldFail() { + assertAll( + () -> assertThrows( + IllegalArgumentException.class, + () -> TypeId.of("i think this prefix is not allowed", SOME_UUID)) + ); + } + + @Test + void ofWithNullPrefixOrNullUuidShouldFail() { + assertAll( + () -> assertThrows( + NullPointerException.class, + () -> TypeId.of(null, SOME_UUID)), + () -> assertThrows( + NullPointerException.class, + () -> TypeId.of(SOME_PREFIX, null)) + ); + } + + @Test + void parseWithoutPrefixWithSuffixShouldReturnTypeId() { + var typeId = TypeId.parse(SOME_SUFFIX); + assertNotNull(typeId); + assertAll( + () -> assertEquals("", typeId.prefix()), + () -> assertEquals(SOME_UUID, typeId.uuid())); + } + + @Test + void parseWithPrefixWithSuffixShouldReturnTypeId() { + var typeId = TypeId.parse(SOME_PREFIX + "_" + SOME_SUFFIX); + assertNotNull(typeId); + assertAll( + () -> assertEquals(SOME_PREFIX, typeId.prefix()), + () -> assertEquals(SOME_UUID, typeId.uuid())); + } + + @ParameterizedTest + @ValueSource(strings = { + "01h455vb4pex5vsknk084sn02q", // suffix only + "abcdefghijklmnopqrstuvw_01h455vb4pex5vsknk084sn02q", // prefix with all allowed chars + "sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss_01h455vb4pex5vsknk084sn02q" // prefix with 63 chars + }) + void parseWithValidInputsShouldReturnTypeId(String input) { + var typeId = TypeId.parse(input); + assertNotNull(typeId); + } -public class TypeIdTest extends AbstractTypeIdTest { + @ParameterizedTest + @ValueSource(strings = { + "", + "_", + "someprefix_", // no suffix at all + "_01h455vb4pex5vsknk084sn02q", // suffix only, but with preceding underscore + "sömeprefix_01h455vb4pex5vsknk084sn02q", // prefix with 'ö' + "someprefix_01h455öb4pex5vsknk084sn02q", // suffix with 'ö' + "sOmeprefix_01h455vb4pex5vsknk084sn02q", // prefix with 'O' + "someprefix_01h455Vb4pex5vsknk084sn02q", // suffix with 'V' + "someprefix_01h455lb4pex5vsknk084sn02q", // suffix with 'l' + "ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss_01h455vb4pex5vsknk084sn02q", // prefix with 64 chars + "someprefix_01h455vb4pex5vsknk084sn02", // suffix with 25 chars + "someprefix_01h455vb4pex5vsknk084sn02q2", // suffix with 27 chars + "someprefix_81h455vb4pex5vsknk084sn02q" // leftmost suffix char is != 0-7 + }) + void parseWithInvalidInputShouldFail(String input) { + assertThrows( + IllegalArgumentException.class, + () -> TypeId.parse(input)); + } + + @Test + void parseWithNullInputShouldFail() { + assertThrows( + NullPointerException.class, + () -> TypeId.parse(null)); + } + + @Test + void parseWithHandlersShouldReturnTypeIdOnSuccess() { + + String result = TypeId.parse(SOME_TYPE_ID, + TypeId::toString, + error -> error); - @Override - TypeIdStaticContext createStaticFacade() { - return new TypeIdFacade(); + assertNotNull(result); + assertEquals(SOME_TYPE_ID, result); + } + + @Test + void parseWithHandlersShouldReturnMessageOnFailure() { + + String result = TypeId.parse("?_" + SOME_SUFFIX, + TypeId::toString, + error -> error); + + assertNotNull(result); + assertEquals("Illegal character in prefix, must be one of [a-z]", result); + } + + @Test + void parseToOptionalShouldReturnNonEmptyOptionalOnParseSuccess() { + Optional result = TypeId.parseToOptional(SOME_TYPE_ID); + assertThat(result).isNotEmpty(); + assertThat(result.get().prefix()).isEqualTo(SOME_PREFIX); + assertThat(result.get().uuid()).isEqualTo(SOME_UUID); + } + + @Test + void parseToOptionalShouldReturnEmptyOptionalOnParseFailure() { + Optional result = TypeId.parseToOptional("some invalid typeid"); + assertThat(result).isEmpty(); } @Test void shouldReturnAValidValidatedOnParseSuccess() { - Validated validated = TypeId.parseToValidated(AbstractTypeIdTest.SOME_TYPE_ID); + Validated validated = TypeId.parseToValidated(TypeIdTest.SOME_TYPE_ID); assertThat(validated.isValid()).isTrue(); assertThat(validated.get().prefix()).isEqualTo(SOME_PREFIX); assertThat(validated.get().uuid()).isEqualTo(SOME_UUID); @@ -26,4 +210,51 @@ void shouldReturnAnInvalidValidatedOnParseFailure() { assertThat(validated.isValid()).isFalse(); assertThat(validated.message()).contains("illegal length"); } + + @Test + void toStringShouldReturnTypeIdAsString() { + var typeId = TypeId.of(SOME_PREFIX, SOME_UUID); + assertNotNull(typeId); + assertEquals(SOME_PREFIX + "_" + SOME_SUFFIX, typeId.toString()); + } + + @Test + void toStringWithoutPrefixShouldReturnTypeIdAsStringWithoutUnderscore() { + var typeId = TypeId.of(SOME_UUID); + assertNotNull(typeId); + assertEquals(SOME_SUFFIX, typeId.toString()); + } + + @Test + void equalsShouldSucceedForEqualTypeIds() { + + var typeIdA = TypeId.of(SOME_UUID); + var typeIdB = TypeId.of(SOME_UUID); + + // symmetry + assertEquals(typeIdA, typeIdB); + assertEquals(typeIdB, typeIdA); + } + + @Test + void equalsShouldFailForAnythingElse() { + + var typeIdA = TypeId.of(SOME_UUID); + var typeIdB = TypeId.of("different", SOME_UUID); + var typeIdC = TypeId.of(UUID.fromString("00000000-0000-0000-0000-000000000000")); + Object otherType = new Object(); + + assertNotEquals(typeIdA, typeIdB); + assertNotEquals(typeIdA, typeIdC); + assertNotEquals(null, typeIdA); + assertNotEquals(typeIdA, otherType); + } + + @Test + void hashCodeShouldBeTheSameForSameTypeIds() { + var typeIdA = TypeId.of(SOME_UUID); + var typeIdB = TypeId.of(SOME_UUID); + assertEquals(typeIdA.hashCode(), typeIdB.hashCode()); + } + } From a93207f4810c807800b5a0fd47ce9775fe3c9929 Mon Sep 17 00:00:00 2001 From: fxlae Date: Sat, 13 Apr 2024 15:19:39 +0200 Subject: [PATCH 03/12] update github actions --- .github/workflows/publish-on-release.yml | 6 +++--- lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java | 6 +++--- lib/src/test/java/de/fxlae/typeid/util/ValidatedTest.java | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish-on-release.yml b/.github/workflows/publish-on-release.yml index bc7caa3..f4e6352 100644 --- a/.github/workflows/publish-on-release.yml +++ b/.github/workflows/publish-on-release.yml @@ -7,12 +7,12 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 - - uses: gradle/gradle-build-action@v2.5.1 + - uses: gradle/setup-gradle@v3 with: gradle-version: wrapper arguments: build publishAllPublicationsToOSSRHRepository diff --git a/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java b/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java index c7e85ec..803c357 100644 --- a/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java +++ b/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java @@ -174,12 +174,12 @@ public static String validateInput(String input, int separatorIndex) { return "TypeId with empty prefix must not contain the separator '_'"; } - String suffixErr = TypeIdLib.validateSuffixOnInput(input, separatorIndex); - if (suffixErr != TypeIdLib.VALID_REF) { + String suffixErr = validateSuffixOnInput(input, separatorIndex); + if (suffixErr != VALID_REF) { return suffixErr; } - return TypeIdLib.validatePrefixOnInput(input, separatorIndex); + return validatePrefixOnInput(input, separatorIndex); } // validates the suffix without creating an intermediary object for it diff --git a/lib/src/test/java/de/fxlae/typeid/util/ValidatedTest.java b/lib/src/test/java/de/fxlae/typeid/util/ValidatedTest.java index 8e2218b..12da66f 100644 --- a/lib/src/test/java/de/fxlae/typeid/util/ValidatedTest.java +++ b/lib/src/test/java/de/fxlae/typeid/util/ValidatedTest.java @@ -197,5 +197,4 @@ void shouldThrowWhenCalledWithNullParams() { assertThatThrownBy(() -> VALID_VALIDATED.ifInvalid(null)).isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> INVALID_VALIDATED.ifInvalid(null)).isInstanceOf(NullPointerException.class); } - } From 12f5622a69dc34336cd5ba399e06406b6cd9e756 Mon Sep 17 00:00:00 2001 From: fxlae Date: Sat, 13 Apr 2024 15:21:33 +0200 Subject: [PATCH 04/12] update github actions --- .github/workflows/build-on-push.yml | 6 +++--- .github/workflows/publish-on-release.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-on-push.yml b/.github/workflows/build-on-push.yml index 9abc0f8..1213a16 100644 --- a/.github/workflows/build-on-push.yml +++ b/.github/workflows/build-on-push.yml @@ -5,12 +5,12 @@ jobs: build-gradle-project: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 - - uses: gradle/gradle-build-action@v2.5.1 + - uses: gradle/actions/setup-gradle@v3 with: gradle-version: wrapper arguments: build diff --git a/.github/workflows/publish-on-release.yml b/.github/workflows/publish-on-release.yml index f4e6352..8efb43d 100644 --- a/.github/workflows/publish-on-release.yml +++ b/.github/workflows/publish-on-release.yml @@ -12,7 +12,7 @@ jobs: with: distribution: temurin java-version: 17 - - uses: gradle/setup-gradle@v3 + - uses: gradle/actions/setup-gradle@v3 with: gradle-version: wrapper arguments: build publishAllPublicationsToOSSRHRepository From 39c9012b75ed2550cfdb9e32c60e5dcd26ea3f75 Mon Sep 17 00:00:00 2001 From: fxlae Date: Sat, 13 Apr 2024 15:33:53 +0200 Subject: [PATCH 05/12] update github actions --- .github/workflows/build-on-push.yml | 4 +--- .github/workflows/publish-on-release.yml | 5 ++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-on-push.yml b/.github/workflows/build-on-push.yml index 1213a16..86b423f 100644 --- a/.github/workflows/build-on-push.yml +++ b/.github/workflows/build-on-push.yml @@ -11,6 +11,4 @@ jobs: distribution: temurin java-version: 17 - uses: gradle/actions/setup-gradle@v3 - with: - gradle-version: wrapper - arguments: build + - run: ./gradlew build diff --git a/.github/workflows/publish-on-release.yml b/.github/workflows/publish-on-release.yml index 8efb43d..0b38e2c 100644 --- a/.github/workflows/publish-on-release.yml +++ b/.github/workflows/publish-on-release.yml @@ -13,9 +13,8 @@ jobs: distribution: temurin java-version: 17 - uses: gradle/actions/setup-gradle@v3 - with: - gradle-version: wrapper - arguments: build publishAllPublicationsToOSSRHRepository + - run: ./gradlew build + - run: ./gradlew publishAllPublicationsToOSSRHRepository env: ORG_GRADLE_PROJECT_OSSRHUsername: ${{ secrets.OSSRH_USERNAME }} ORG_GRADLE_PROJECT_OSSRHPassword: ${{ secrets.OSSRH_TOKEN }} From f02cfa2fd8f2918552c135b5f53f530c5893ecf0 Mon Sep 17 00:00:00 2001 From: fxlae Date: Sat, 13 Apr 2024 17:24:19 +0200 Subject: [PATCH 06/12] simplify code --- lib/src/main/java/de/fxlae/typeid/TypeId.java | 103 +++++++----------- .../java/de/fxlae/typeid/lib/TypeIdLib.java | 4 +- 2 files changed, 39 insertions(+), 68 deletions(-) diff --git a/lib/src/main/java/de/fxlae/typeid/TypeId.java b/lib/src/main/java/de/fxlae/typeid/TypeId.java index 0c2d675..dcd19f8 100644 --- a/lib/src/main/java/de/fxlae/typeid/TypeId.java +++ b/lib/src/main/java/de/fxlae/typeid/TypeId.java @@ -83,70 +83,6 @@ public static TypeId of(String prefix, UUID uuid) { return new TypeId(prefix, uuid); } - /** - * Parses the textual representation of a TypeID and returns a {@link TypeId} instance. - * - * @param text the textual representation. - * @return the new {@link TypeId}. - * @throws NullPointerException if the text is null - * @throws IllegalArgumentException if the text is invalid - */ - public static TypeId parse(final String text) { - - int separatorIndex = TypeIdLib.findSeparatorIndex(text); - String err = TypeIdLib.validateInput(text, separatorIndex); - - if (err != TypeIdLib.VALID_REF) { - throw new IllegalArgumentException(err); - } - - return new TypeId( - TypeIdLib.extractPrefix(text, separatorIndex), - TypeIdLib.decodeSuffixOnInput(text, separatorIndex)); - } - - /** - * Parses the textual representation of a TypeID and returns an {@link Optional}. - * - * @param text the textual representation of the TypeID - * @return an {@link Optional} containing a {@link TypeId} or an empty {@link TypeId} in case of validation errors - * @throws NullPointerException if the text is null - */ - public static Optional parseToOptional(final String text) { - - int separatorIndex = TypeIdLib.findSeparatorIndex(text); - String err = TypeIdLib.validateInput(text, separatorIndex); - - if (err != TypeIdLib.VALID_REF) { - return Optional.empty(); - } - - return Optional.of(new TypeId( - TypeIdLib.extractPrefix(text, separatorIndex), - TypeIdLib.decodeSuffixOnInput(text, separatorIndex))); - } - - /** - * Parses the textual representation of a TypeID and returns a {@link Validated}. - * - * @param text the textual representation of the TypeID - * @return a valid {@link Validated} containing a {@link TypeId} or an invalid {@link Validated} with an error message - * @throws NullPointerException if the text is null - */ - public static Validated parseToValidated(final String text) { - - int separatorIndex = TypeIdLib.findSeparatorIndex(text); - String err = TypeIdLib.validateInput(text, separatorIndex); - - if (err != TypeIdLib.VALID_REF) { - return Validated.invalid(err); - } - - return Validated.valid(new TypeId( - TypeIdLib.extractPrefix(text, separatorIndex), - TypeIdLib.decodeSuffixOnInput(text, separatorIndex))); - } - /** * Parses the textual representation of a TypeID and executes a handler {@link Function}, depending * on the outcome. Both provided functions must have the same return type. @@ -180,6 +116,42 @@ public static T parse( return okHandler.apply(typeId); } + /** + * Parses the textual representation of a TypeID and returns a {@link TypeId} instance. + * + * @param text the textual representation. + * @return the new {@link TypeId}. + * @throws NullPointerException if the text is null + * @throws IllegalArgumentException if the text is invalid + */ + public static TypeId parse(final String text) { + return parse(text, Function.identity(), + message -> { + throw new IllegalArgumentException(message); + }); + } + + /** + * Parses the textual representation of a TypeID and returns an {@link Optional}. + * + * @param text the textual representation of the TypeID + * @return an {@link Optional} containing a {@link TypeId} or an empty {@link TypeId} in case of validation errors + * @throws NullPointerException if the text is null + */ + public static Optional parseToOptional(final String text) { + return parse(text, Optional::of, message -> Optional.empty()); + } + + /** + * Parses the textual representation of a TypeID and returns a {@link Validated}. + * + * @param text the textual representation of the TypeID + * @return a valid {@link Validated} containing a {@link TypeId} or an invalid {@link Validated} with an error message + * @throws NullPointerException if the text is null + */ + public static Validated parseToValidated(final String text) { + return parse(text, Validated::valid, Validated::invalid); + } /** * Returns the textual representation of this {@link TypeId}. @@ -190,5 +162,4 @@ public static T parse( public String toString() { return encode(prefix, uuid); } - -} \ No newline at end of file +} diff --git a/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java b/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java index 803c357..9b3734b 100644 --- a/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java +++ b/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java @@ -8,7 +8,7 @@ public final class TypeIdLib { - public static final String VALID_REF = "VALID_REF"; + public static final String VALID_REF = ""; private static final char SEPARATOR = '_'; private static final int PREFIX_MAX_LENGTH = 63; private static final String SUFFIX_ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz"; @@ -219,7 +219,7 @@ public static String validatePrefixOnInput(final String input, final int separat for (int i = 0; i < separatorIndex; i++) { char c = input.charAt(i); - if (!(c >= 'a' && c <= 'z')) { + if (!(c >= 'a' && c <= 'z') /* && c != '_' */) { return "Illegal character in prefix, must be one of [a-z]"; } } From 08e6a458e8d43244c2d3d49c3f50f5555ab4a2cf Mon Sep 17 00:00:00 2001 From: fxlae Date: Sat, 13 Apr 2024 17:25:46 +0200 Subject: [PATCH 07/12] change version to 0.3.0-SNAPSHOT --- .../de/fxlae/typeid/typeid.library-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts b/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts index a54bcab..1c284d7 100644 --- a/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts +++ b/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts @@ -5,7 +5,7 @@ plugins { } group = "de.fxlae" -version = "0.2.0" +version = "0.3.0-SNAPSHOT" java { withJavadocJar() From 5bdd591a0662983dd1a02b525684245d5bb5dd80 Mon Sep 17 00:00:00 2001 From: fxlae Date: Sat, 13 Apr 2024 22:34:51 +0200 Subject: [PATCH 08/12] implement 0.3.0 spec --- .../jmh/java/de/fxlae/typeid/TypeIdBench.java | 58 +++++++++----- lib/src/main/java/de/fxlae/typeid/TypeId.java | 79 ++++++++---------- .../java/de/fxlae/typeid/lib/TypeIdLib.java | 80 ++++++++++++------- .../test/java/de/fxlae/typeid/TypeIdTest.java | 4 +- lib/src/test/resources/spec/invalid.yml | 19 ++++- lib/src/test/resources/spec/valid.yml | 9 ++- 6 files changed, 148 insertions(+), 101 deletions(-) diff --git a/lib/src/jmh/java/de/fxlae/typeid/TypeIdBench.java b/lib/src/jmh/java/de/fxlae/typeid/TypeIdBench.java index 67ce821..34eb0ca 100644 --- a/lib/src/jmh/java/de/fxlae/typeid/TypeIdBench.java +++ b/lib/src/jmh/java/de/fxlae/typeid/TypeIdBench.java @@ -3,16 +3,10 @@ import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; -import java.util.Optional; import java.util.UUID; public class TypeIdBench { - @Benchmark - public void parse(Blackhole bh, Inputs inputs) { - bh.consume(TypeId.parse(inputs.validTypeId)); - } - @Benchmark public void of(Blackhole bh, Inputs inputs) { bh.consume(TypeId.of(inputs.prefix, inputs.uuid)); @@ -41,31 +35,51 @@ public void ofAndToString(Blackhole bh, Inputs inputs) { } @Benchmark - public void parseWithErrorAsException(Blackhole bh, Inputs inputs) { + public void parseSuccess(Blackhole bh, Inputs inputs) { + bh.consume(TypeId.parse(inputs.validTypeId)); + } + + @Benchmark + public void parseWithHandlersSuccess(Blackhole bh, Inputs inputs) { + bh.consume(TypeId.parse(inputs.validTypeId, + typeId -> typeId, + error -> error)); + } + + @Benchmark + public void parseToOptionalSuccess(Blackhole bh, Inputs inputs) { + bh.consume(TypeId.parseToOptional(inputs.validTypeId)); + } + + @Benchmark + public void parseToValidatedSuccess(Blackhole bh, Inputs inputs) { + bh.consume(TypeId.parseToValidated(inputs.validTypeId)); + } + + @Benchmark + public void parseError(Blackhole bh, Inputs inputs) { try { TypeId.parse(inputs.invalidTypeId); - } catch (IllegalArgumentException e) { - bh.consume(e.getMessage()); + } catch (Exception e) { + bh.consume(e); } } @Benchmark - public void parseWithErrorAsValue(Blackhole bh, Inputs inputs) { - String result = TypeId.parse(inputs.invalidTypeId, - typeId -> null, - message -> message); - bh.consume(result); + public void parseWithHandlersError(Blackhole bh, Inputs inputs) { + bh.consume(TypeId.parse(inputs.invalidTypeId, + typeId -> typeId, + error -> error)); } + @Benchmark + public void parseToOptionalError(Blackhole bh, Inputs inputs) { + bh.consume(TypeId.parseToOptional(inputs.invalidTypeId)); + } - public void xx(Blackhole bh, Inputs inputs) { - var maybeTypeId = TypeId.parse(inputs.invalidTypeId, - Optional::of, - message -> { - System.out.println(message); - return Optional.empty(); - }); - bh.consume(maybeTypeId); + @Benchmark + public void parseToValidatedError(Blackhole bh, Inputs inputs) { + bh.consume(TypeId.parseToValidated(inputs.invalidTypeId)); } @State(Scope.Benchmark) diff --git a/lib/src/main/java/de/fxlae/typeid/TypeId.java b/lib/src/main/java/de/fxlae/typeid/TypeId.java index dcd19f8..d21132c 100644 --- a/lib/src/main/java/de/fxlae/typeid/TypeId.java +++ b/lib/src/main/java/de/fxlae/typeid/TypeId.java @@ -8,7 +8,6 @@ import java.util.UUID; import java.util.function.Function; -import static de.fxlae.typeid.lib.TypeIdLib.encode; /** * A {@code record} for representing TypeIDs. @@ -25,14 +24,8 @@ public record TypeId(String prefix, UUID uuid) { * @throws IllegalArgumentException if the prefix is invalid */ public TypeId { - - Objects.requireNonNull(prefix); + TypeIdLib.requireValidPrefix(prefix); Objects.requireNonNull(uuid); - - String err = TypeIdLib.validatePrefixOnInput(prefix, prefix.length()); - if (err != TypeIdLib.VALID_REF) { - throw new IllegalArgumentException(err); - } } /** @@ -83,39 +76,6 @@ public static TypeId of(String prefix, UUID uuid) { return new TypeId(prefix, uuid); } - /** - * Parses the textual representation of a TypeID and executes a handler {@link Function}, depending - * on the outcome. Both provided functions must have the same return type. - * - * @param text the textual representation of the TypeID - * @param okHandler the {@link Function} that is executed if the TypeID is valid, providing the {@link TypeId} - * @param errorHandler the {@link Function} that is executed if the TypeID could not be parsed, providing the error message - * @param the result type of the handler {@link Function} that was executed - * @return the result of the handler {@link Function} that was executed - * @throws NullPointerException if the okHandler and/or errorHandler is null - */ - public static T parse( - final String text, - Function okHandler, - Function errorHandler) { - - Objects.requireNonNull(okHandler); - Objects.requireNonNull(errorHandler); - - int separatorIndex = TypeIdLib.findSeparatorIndex(text); - String err = TypeIdLib.validateInput(text, separatorIndex); - - if (err != TypeIdLib.VALID_REF) { - return errorHandler.apply(err); - } - - TypeId typeId = new TypeId( - TypeIdLib.extractPrefix(text, separatorIndex), - TypeIdLib.decodeSuffixOnInput(text, separatorIndex)); - - return okHandler.apply(typeId); - } - /** * Parses the textual representation of a TypeID and returns a {@link TypeId} instance. * @@ -125,12 +85,35 @@ public static T parse( * @throws IllegalArgumentException if the text is invalid */ public static TypeId parse(final String text) { - return parse(text, Function.identity(), + return TypeIdLib.parse( + text, + TypeId::of, message -> { throw new IllegalArgumentException(message); }); } + /** + * Parses the textual representation of a TypeID and executes a handler {@link Function}, depending + * on the outcome. Both provided functions must have the same return type. + * + * @param text the textual representation of the TypeID + * @param successHandler the {@link Function} that is executed if the TypeID is valid, providing the {@link TypeId} + * @param errorHandler the {@link Function} that is executed if the TypeID could not be parsed, providing the error message + * @param the result type of the handler {@link Function} that was executed + * @return the result of the handler {@link Function} that was executed + * @throws NullPointerException if the successHandler and/or errorHandler is null + */ + public static T parse( + final String text, + Function successHandler, + Function errorHandler) { + + return TypeIdLib.parse(text, + (prefix, uuid) -> successHandler.apply(of(prefix, uuid)), + errorHandler); + } + /** * Parses the textual representation of a TypeID and returns an {@link Optional}. * @@ -139,7 +122,10 @@ public static TypeId parse(final String text) { * @throws NullPointerException if the text is null */ public static Optional parseToOptional(final String text) { - return parse(text, Optional::of, message -> Optional.empty()); + return TypeIdLib.parse( + text, + (prefix, uuid) -> Optional.of(of(prefix, uuid)), + error -> Optional.empty()); } /** @@ -150,7 +136,10 @@ public static Optional parseToOptional(final String text) { * @throws NullPointerException if the text is null */ public static Validated parseToValidated(final String text) { - return parse(text, Validated::valid, Validated::invalid); + return TypeIdLib.parse( + text, + (prefix, uuid) -> Validated.valid(of(prefix, uuid)), + Validated::invalid); } /** @@ -160,6 +149,6 @@ public static Validated parseToValidated(final String text) { */ @Override public String toString() { - return encode(prefix, uuid); + return TypeIdLib.encode(prefix, uuid); } } diff --git a/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java b/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java index 9b3734b..6886a77 100644 --- a/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java +++ b/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java @@ -3,8 +3,12 @@ import com.fasterxml.uuid.Generators; import com.fasterxml.uuid.impl.TimeBasedEpochGenerator; +import static java.util.Objects.requireNonNull; + import java.util.Objects; import java.util.UUID; +import java.util.function.BiFunction; +import java.util.function.Function; public final class TypeIdLib { @@ -56,7 +60,7 @@ public final class TypeIdLib { private TypeIdLib() { } - public static UUID decodeSuffixOnInput(final String input, final int separatorIndex) { + private static UUID decodeSuffixOnInput(final String input, final int separatorIndex) { final int start = (separatorIndex == -1) ? 0 : separatorIndex + 1; @@ -149,43 +153,52 @@ public static String encode(final String prefix, final UUID uuid) { return sb.toString(); } - public static int findSeparatorIndex(String input) { - return (input == null) ? -1 : input.lastIndexOf(SEPARATOR); - } + public static T parse( + String text, + BiFunction successHandler, + Function errorHandler) { - public static String extractPrefix(String input, int separatorIndex) { - if (separatorIndex == -1) { - return ""; - } else { - return input.substring(0, separatorIndex); - } - } - - public static String validateInput(String input, int separatorIndex) { - - Objects.requireNonNull(input); + requireNonNull(successHandler); + requireNonNull(errorHandler); - if (input.isEmpty()) { - return "Provided TypeId must not be empty"; + if (text == null || text.isEmpty()) { + return errorHandler.apply("Provided TypeId must not be null or empty"); } + var separatorIndex = text.lastIndexOf(SEPARATOR); + // empty prefix, but with unexpected separator if (separatorIndex == 0) { - return "TypeId with empty prefix must not contain the separator '_'"; + return errorHandler.apply("TypeId with empty prefix must not contain the separator '_'"); + } + + var suffixValidation = validateSuffixOnInput(text, separatorIndex); + if (suffixValidation != VALID_REF) { + return errorHandler.apply(suffixValidation); } - String suffixErr = validateSuffixOnInput(input, separatorIndex); - if (suffixErr != VALID_REF) { - return suffixErr; + var prefixValidation = validatePrefixOnInput(text, separatorIndex); + if (prefixValidation != TypeIdLib.VALID_REF) { + return errorHandler.apply(prefixValidation); } - return validatePrefixOnInput(input, separatorIndex); + return successHandler.apply( + extractPrefix(text, separatorIndex), + decodeSuffixOnInput(text, separatorIndex)); + } + + private static String extractPrefix(String input, int separatorIndex) { + if (separatorIndex == -1) { + return ""; + } else { + return input.substring(0, separatorIndex); + } } // validates the suffix without creating an intermediary object for it private static String validateSuffixOnInput(final String input, final int separatorIndex) { - final int start = (separatorIndex != -1) ? separatorIndex + 1 : 0; + final var start = (separatorIndex != -1) ? separatorIndex + 1 : 0; if (input.length() - start != SUFFIX_LENGTH) { return "Suffix with illegal length, must be " + SUFFIX_LENGTH; @@ -196,7 +209,7 @@ private static String validateSuffixOnInput(final String input, final int separa } for (int i = start; i < input.length(); i++) { - char c = input.charAt(i); + var c = input.charAt(i); if (c >= SUFFIX_LOOKUP.length || SUFFIX_LOOKUP[c] == NOOP) { return "Illegal character in suffix, must be one of [" + SUFFIX_ALPHABET + "]"; } @@ -205,8 +218,17 @@ private static String validateSuffixOnInput(final String input, final int separa return VALID_REF; } + public static void requireValidPrefix(final String prefix) { + Objects.requireNonNull(prefix); + if (prefix.isEmpty()) return; + var prefixValidation = validatePrefixOnInput(prefix, prefix.length()); + if (prefixValidation != TypeIdLib.VALID_REF) { + throw new IllegalArgumentException(prefixValidation); + } + } + // validates the prefix without creating an intermediary object for it - public static String validatePrefixOnInput(final String input, final int separatorIndex) { + private static String validatePrefixOnInput(final String input, final int separatorIndex) { // empty prefix, no separator if (separatorIndex == -1) { @@ -217,10 +239,14 @@ public static String validatePrefixOnInput(final String input, final int separat return "Prefix with illegal length, must not have more than " + PREFIX_MAX_LENGTH + " characters"; } + if (input.charAt(0) == SEPARATOR || input.charAt(input.length() - 1) == SEPARATOR) { + return "Prefix must not start or end with '" + SEPARATOR + "'"; + } + for (int i = 0; i < separatorIndex; i++) { char c = input.charAt(i); - if (!(c >= 'a' && c <= 'z') /* && c != '_' */) { - return "Illegal character in prefix, must be one of [a-z]"; + if (!(c >= 'a' && c <= 'z') && c != SEPARATOR) { + return "Illegal character in prefix, must be one of [a-z" + SEPARATOR + "]"; } } diff --git a/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java b/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java index c87c882..8978c0e 100644 --- a/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java +++ b/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java @@ -156,7 +156,7 @@ void parseWithInvalidInputShouldFail(String input) { @Test void parseWithNullInputShouldFail() { assertThrows( - NullPointerException.class, + IllegalArgumentException.class, () -> TypeId.parse(null)); } @@ -179,7 +179,7 @@ void parseWithHandlersShouldReturnMessageOnFailure() { error -> error); assertNotNull(result); - assertEquals("Illegal character in prefix, must be one of [a-z]", result); + assertEquals("Illegal character in prefix, must be one of [a-z_]", result); } @Test diff --git a/lib/src/test/resources/spec/invalid.yml b/lib/src/test/resources/spec/invalid.yml index 3f288e7..1e19c33 100644 --- a/lib/src/test/resources/spec/invalid.yml +++ b/lib/src/test/resources/spec/invalid.yml @@ -4,7 +4,7 @@ # Each example contains an invalid TypeID string. Implementations are expected # to throw an error when attempting to parse/validate these strings. # -# Last updated: 2023-07-05 +# Last updated: 2024-04-10 (for version 0.3.0 of the spec) - name: prefix-uppercase typeid: "PREFIX_00000000000000000000000000" @@ -18,9 +18,10 @@ typeid: "pre.fix_00000000000000000000000000" description: "The prefix can't have symbols, it needs to be alphabetic" -- name: prefix-underscore - typeid: "pre_fix_00000000000000000000000000" - description: "The prefix can't have symbols, it needs to be alphabetic" +# Test removed in v0.3.0 – we now allow underscores in the prefix +# - name: prefix-underscore +# typeid: "pre_fix_00000000000000000000000000" +# description: "The prefix can't have symbols, it needs to be alphabetic" - name: prefix-non-ascii typeid: "préfix_00000000000000000000000000" @@ -86,3 +87,13 @@ # This is the first suffix that overflows into 129 bits typeid: "prefix_8zzzzzzzzzzzzzzzzzzzzzzzzz" description: "The suffix should encode at most 128-bits" + +# Tests below were added in v0.3.0 when we started allowing '_' within the +# type prefix. +- name: prefix-underscore-start + typeid: "_prefix_00000000000000000000000000" + description: "The prefix can't start with an underscore" + +- name: prefix-underscore-end + typeid: "prefix__00000000000000000000000000" + description: "The prefix can't end with an underscore" diff --git a/lib/src/test/resources/spec/valid.yml b/lib/src/test/resources/spec/valid.yml index 8f63250..47c5328 100644 --- a/lib/src/test/resources/spec/valid.yml +++ b/lib/src/test/resources/spec/valid.yml @@ -23,7 +23,7 @@ # note that not all of them are UUIDv7s. When *generating* new random typeids, # implementations should always use UUIDv7s. # -# Last updated: 2023-07-05 +# Last updated: 2024-04-10 (for version 0.3.0 of the spec) - name: nil typeid: "00000000000000000000000000" @@ -64,3 +64,10 @@ typeid: "prefix_01h455vb4pex5vsknk084sn02q" prefix: "prefix" uuid: "01890a5d-ac96-774b-bcce-b302099a8057" + +# Tests below were added in v0.3.0 when we started allowing '_' within the +# type prefix. +- name: prefix-underscore + typeid: "pre_fix_00000000000000000000000000" + prefix: "pre_fix" + uuid: "00000000-0000-0000-0000-000000000000" From ce450b9810ea2de5a4c23ac4497372e36fcbab19 Mon Sep 17 00:00:00 2001 From: fxlae Date: Sun, 14 Apr 2024 19:30:44 +0200 Subject: [PATCH 09/12] update readme --- README.md | 123 +++++++++--------- .../test/java/de/fxlae/typeid/SpecTest.java | 2 +- .../test/java/de/fxlae/typeid/TypeIdTest.java | 7 +- 3 files changed, 66 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 827970c..7a1b626 100644 --- a/README.md +++ b/README.md @@ -11,25 +11,27 @@ Read more about TypeIDs in their [spec](https://github.com/jetpack-io/typeid). ## Installation -This library is designed to support all current LTS versions, including Java 8, whilst also making use of the features provided by the latest or upcoming Java versions. As a result, it is offered in two variants: +Starting with version `0.3.0`, `typeid-java` requires at least Java 17. -- `typeid-java`: Requires at least Java 17. Opt for this one if the Java version is not a concern -- *OR* `typeid-java-jdk8`: Supports all versions from Java 8 onwards. It handles all relevant use cases, albeit with less syntactic sugar +

+(Details on Java 8+ support) +Up to version 0.2.0, a separate artifact called `typeid-java-jdk8` was published, supporting Java versions 8 and higher, and covering all relevant use cases, albeit with less syntactic sugar. If you are running Java 8 through 16, you can still use `typeid-java-jdk8:0.2.0`, which is still available and remains fully functional. However, it will no longer receive updates and is limited to the TypeId spec version 0.2.0. +
To install via Maven: ```xml de.fxlae - typeid-java - 0.2.0 + typeid-java + 0.3.0 ``` For installation via Gradle: ```kotlin -implementation("de.fxlae:typeid-java:0.2.0") // or ...typeid-java-jdk8:0.2.0 +implementation("de.fxlae:typeid-java:0.3.0") ``` ## Usage @@ -38,6 +40,9 @@ implementation("de.fxlae:typeid-java:0.2.0") // or ...typeid-java-jdk8:0.2.0 ### Generating new TypeIDs + +#### generate + To generate a new `TypeId`, based on UUIDv7 as per specification: ```java @@ -47,6 +52,8 @@ typeId.prefix(); // "user" typeId.uuid(); // java.util.UUID(01890a5d-ac96-774b-bcce-b302099a8057), based on UUIDv7 ``` +#### of + To construct (or reconstruct) a `TypeId` from existing arguments, which can also be used as an "extension point" to plug-in custom UUID generators: ```java @@ -55,36 +62,28 @@ var typeId = TypeId.of("user", UUID.randomUUID()); // a TypeId based on UUIDv4 ### Parsing TypeID strings For parsing, the library supports both an imperative programming model and a more functional style. + +#### parse The most straightforward way to parse the textual representation of a TypeID: ```java var typeId = TypeId.parse("user_01h455vb4pex5vsknk084sn02q"); ``` -Invalid inputs will result in an `IllegalArgumentException`, with a message explaining the cause of the parsing failure. If you prefer working with errors modeled as return values rather than exceptions, this is also possible (and is *much* more performant for untrusted input, as no stacktrace is involved at all): +Invalid inputs will result in an `IllegalArgumentException`, with a message explaining the cause of the parsing failure. + +#### parseToOptional + +It's also possible to obtain an `Optional` in cases where the concrete error message is not relevant. ```java -var maybeTypeId = TypeId.parseToOptional("user_01h455vb4pex5vsknk084sn02q"); - -// or, if you are interested in possible errors, provide handlers for success and failure -var maybeTypeId = TypeId.parse("...", - Optional::of, // (1) Function, called on success - message -> { // (2) Function, called on failure - log.warn("Parsing failed: {}", message); - return Optional.empty(); - }); +var maybeTypeId = TypeId.parseToOptional("user_01h455vb4pex5vsknk084sn02q"); ``` -**Everything shown so far works for both artifacts, `typeid-java` as well as `typeid-java-jdk8`. The following section is about features that are only available when using `typeid-java`**. -When using `typeid-java`: -- the type `TypeId` is implemented as a Java `record` -- it has an additional method that *can* be used for parsing, `TypeId.parseToValidated`, which returns a "monadic-like" structure: `Validated`, or in this particular context, `Validated` +#### parseToValidated -`Validated` can be of subtype: -- `Valid`: encapsulates a successfully parsed `TypeId` -- or otherwise `Invalid`: contains an error message +If you prefer working with errors modeled as return values rather than exceptions, this is also possible (and is *much* more performant for untrusted input with high error rates, as no stacktrace is involved): -A simplistic method to interact with `Validated` is to manually unwrap it, analogous to `java.util.Optional.get`: ```java var validated = TypeId.parseToValidated("user_01h455vb4pex5vsknk084sn02q"); @@ -99,14 +98,27 @@ if(validated.isValid) { ``` Note: Checking `validated.isValid` is advisable for untrusted input. Similar to `Optional.get`, invoking `Validated.get` for invalid TypeIds (or `Validated.message` for valid TypeIds) will lead to a `NoSuchElementException`. -A safe alternative involves methods that can be called without risk, namely: +`Validated` and its implementations `Valid` and `Invalid` form a sealed type hierarchy. This feature becomes especially useful in more recent Java versions, beginning with Java 21, which facilitates Record Patterns (destructuring) and Pattern Matching for switch (yes, `TypeId` is a `record`): + +```java +// this compiles and runs from Java 21 onwards + +var report = switch(TypeId.parseToValidated("...")) { + case Valid(TypeId(var prefix, var uuid)) when "user".equals(prefix) -> "user with UUID" + uuid; + case Valid(TypeId(var prefix, _)) -> "Not a user, ignore the UUID. Prefix is " + prefix; + case Invalid(var message) -> "Parsing failed :( ... " + message; +} +``` +Note the absent (and superfluous) default case. Exhaustiveness is checked during compilation! + +Another safe alternative for working with `Validated` involves methods that can be called without risk, namely: - For transformations: `map`, `flatMap`, `filter`, `orElse` - For implementing side effects: `ifValid` and `ifInvalid` ```java // transform -var mappedToPrefix = TypeId.parseToValidated("user_01h455vb4pex5vsknk084sn02q"); +var mappedToPrefix = TypeId.parseToValidated("dog_01h455vb4pex5vsknk084sn02q"); .map(TypeId::prefix) // Validated -> Validated .filter("Not a cat! :(", prefix -> !"cat".equals(prefix)); // the predicate fails @@ -115,18 +127,7 @@ mappedToPrefix.ifValid(prefix -> log.info(prefix)) // called on success, so not mappedToPrefix.ifInvalid(message -> log.warn(message)) // logs "Not a cat! :(" ``` -`Validated` and its implementations `Valid` and `Invalid` form a sealed type hierarchy. This feature becomes especially useful in future Java versions, beginning with Java 21, which will facilitate Record Patterns (destructuring) and Pattern Matching for switch: -```java -// this compiles and runs with oracle openjdk-21-ea+30 (preview enabled) - -var report = switch(TypeId.parseToValidated("...")) { - case Valid(TypeId(var prefix, var uuid)) when "user".equals(prefix) -> "user with UUID" + uuid; - case Valid(TypeId(var prefix, _)) -> "Not a user, ignore the UUID. Prefix is " + prefix; - case Invalid(var message) -> "Parsing failed :( ... " + message; -} -``` -Note the absent (and superfluous) default case. Exhaustiveness is checked during compilation! ## But wait, isn't this less type-safe than it could be?
@@ -134,43 +135,38 @@ Note the absent (and superfluous) default case. Exhaustiveness is checked during That's correct. The prefix of a TypeId is currently just a simple `String`. If you want to validate the prefix against a specific "type" of prefix, this subtly means you'll have to perform a string comparison. -Here's how a more type-safe variant could look, which I have implemented experimentally (currently not included in the artifact): +Here's how more type-safe variants could look like, which I have implemented experimentally (**currently not included in the artifact**): ```java -TypeId typeId = TypeId<>.generate(USER); -TypeId anotherTypeId = TypeId<>.parse(USER, "user_01h455vb4pex5vsknk084sn02q"); +TypeId typeId = TypeId.generate(USER); +TypeId anotherTypeId = TypeId.parse(USER, "user_01h455vb4pex5vsknk084sn02q"); ``` -The downside to this approach is that each possible prefix type has to be defined manually. In particular, one must ensure that the embedded prefix name is syntactically correct: +The downside to this approach is that each possible prefix has to be defined manually as its own type that contains the prefix' string representation, e.g.: ```java -static final User USER = new User(); -record User() implements TypedPrefix { +final class User implements TypedPrefix { @Override public String name() { return "user"; } } -``` - -This method would still be an improvement, as it allows `TypeId`s to be passed around in the code in a type-safe manner. However, the preferred solution would be to validate the names of the prefix types at compile time. This solution is somewhat more complex and might require, for instance, the use of an annotation processor. - -If I find the motivation, I will complete the experimental version and integrate it as a separate variant into its own package (e.g., `..typed`), which can be used alternatively. -
-## A word on UUIDv7 -
- Details - -TypeIDs are purposefully based on UUIDv7, one of several new UUID versions. UUIDs of version 7 begin with the current timestamp represented in the most significant bits, enabling their generation in a monotonically increasing order. This feature presents certain advantages, such as when using indexes in a database. Indexes based on B-Trees significantly benefit from monotonically ascending values. +static final User USER = new User(); +``` -However, the [IETF specification for the new UUID versions](https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis) is a draft yet to be finalized, meaning modifications can still be introduced, including to UUIDv7. Additionally, the specification grants certain liberties in regards to the structure of a version 7 UUID. It must always commence with a timestamp (with a minimum precision of a millisecond, but potentially more if necessary), but in the least significant bits, aside from random values, it may or may not optionally include a counter and an InstanceId. +Another solution is to validate the names of the prefix types at compile time. This solution is somewhat more complex as it requires an annotation processor. -For these reasons, this library uses a robust implementation of UUIDs for Java (as its only runtime-dependency) , specifically [java-uuid-generator (JUG)](https://github.com/cowtowncoder/java-uuid-generator). It adheres closely to the specification and, for instance, utilizes `SecureRandom` for generating random numbers, as strongly recommended by the specification (see [section 6.8](https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#section-6.8) of the sepcification). +```java +@TypeId(name = "UserId", prefix = "user") +class MyApp {} -Nevertheless, as stated earlier, it is possible to use any other UUID generator implementation and/or UUID version by invoking `TypeId.of` instead of `TypeId.generate`. +UserId userId = UserId.generate(); +UserId anotherUserId = UserId.parse("user_01h455vb4pex5vsknk084sn02q"); +``` -
+If I find the motivation, I will complete the experimental version and integrate it as a separate variant into its own package (e.g., `..typed`), which can be used alternatively. + ## Building From Source & Benchmarks
@@ -187,12 +183,11 @@ There is a small [JMH](https://github.com/openjdk/jmh) microbenchmark included: foo@bar:~/typeid-java$ ./gradlew jmh ``` -In a single-threaded run, all operations perform in the range of millions of calls per second, which should be sufficient for most use cases (used setup: Eclipse Temurin 17 OpenJDK 64-Bit Server VM, AMD 2019gen CPU @ 3.6Ghz, 16GiB memory). +In a single-threaded run, all operations perform in the range of millions of calls per second, which should be sufficient for most use cases (used setup: Eclipse Temurin 17 OpenJDK Server VM, 2021 AMD mid-range notebook CPU). -| method | op/s | -|----------------------------------|----------------------:| -| `TypeId.generate` + `toString` | 9.1M | -| `TypeId.parse` | 9.8M | +| method | op/s | +|--------------------------------|------:| +| `TypeId.generate` + `toString` | 10.2M | +| `TypeId.parse` | 9.8M | -The library strives to avoid heap allocations as much as possible. The only allocations made are for return values and data from `SecureRandom`.
\ No newline at end of file diff --git a/lib/src/test/java/de/fxlae/typeid/SpecTest.java b/lib/src/test/java/de/fxlae/typeid/SpecTest.java index 4b80eff..fbc2a42 100644 --- a/lib/src/test/java/de/fxlae/typeid/SpecTest.java +++ b/lib/src/test/java/de/fxlae/typeid/SpecTest.java @@ -20,7 +20,7 @@ /** * This class tests against the specification published on * github.com/jetpack-io/typeid, - * especially against valid.yml + * specifically against valid.yml * and * invalid.yml. */ diff --git a/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java b/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java index 8978c0e..daecad8 100644 --- a/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java +++ b/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java @@ -123,7 +123,8 @@ void parseWithPrefixWithSuffixShouldReturnTypeId() { @ParameterizedTest @ValueSource(strings = { "01h455vb4pex5vsknk084sn02q", // suffix only - "abcdefghijklmnopqrstuvw_01h455vb4pex5vsknk084sn02q", // prefix with all allowed chars + "abcdefghijklmnopqrstuvw_01h455vb4pex5vsknk084sn02q", // prefix with allowed chars + "some_prefix_01h455vb4pex5vsknk084sn02q", // prefix with underscore "sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss_01h455vb4pex5vsknk084sn02q" // prefix with 63 chars }) void parseWithValidInputsShouldReturnTypeId(String input) { @@ -137,6 +138,10 @@ void parseWithValidInputsShouldReturnTypeId(String input) { "_", "someprefix_", // no suffix at all "_01h455vb4pex5vsknk084sn02q", // suffix only, but with preceding underscore + "__01h455vb4pex5vsknk084sn02q", // prefix is single underscore + "_someprefix_01h455vb4pex5vsknk084sn02q", // prefix starts with underscore + "someprefix__01h455vb4pex5vsknk084sn02q", // prefix ends with underscore + "_someprefix__01h455vb4pex5vsknk084sn02q", // prefix starts and ends with underscore "sömeprefix_01h455vb4pex5vsknk084sn02q", // prefix with 'ö' "someprefix_01h455öb4pex5vsknk084sn02q", // suffix with 'ö' "sOmeprefix_01h455vb4pex5vsknk084sn02q", // prefix with 'O' From c58cc200082dd2396527a1897867ae704c7d37d0 Mon Sep 17 00:00:00 2001 From: fxlae Date: Sun, 14 Apr 2024 19:44:37 +0200 Subject: [PATCH 10/12] update readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7a1b626..1fe622a 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,12 @@ typeId.uuid(); // java.util.UUID(01890a5d-ac96-774b-bcce-b302099a8057), based on #### of -To construct (or reconstruct) a `TypeId` from existing arguments, which can also be used as an "extension point" to plug-in custom UUID generators: +To construct (or reconstruct) a `TypeId` from existing arguments: ```java -var typeId = TypeId.of("user", UUID.randomUUID()); // a TypeId based on UUIDv4 +var typeId = TypeId.of("user", someUuid); ``` +As a side effect, `of` can also be used as an "extension point" to plug-in custom UUID generators. ### Parsing TypeID strings For parsing, the library supports both an imperative programming model and a more functional style. From f81a7e64fb7ba68339eeeaa89770b5cde88f5b3f Mon Sep 17 00:00:00 2001 From: fxlae Date: Sun, 14 Apr 2024 19:53:57 +0200 Subject: [PATCH 11/12] update readme --- .../de/fxlae/typeid/typeid.library-conventions.gradle.kts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts b/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts index 1c284d7..5c3f7a4 100644 --- a/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts +++ b/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts @@ -29,14 +29,6 @@ tasks.javadoc { } } -tasks.named("jar").configure { - enabled = false -} - -tasks.withType { - enabled = false -} - val mavenArtifactId: String by project val mavenArtifactDescription: String by project From 23f298335307d69c0c8a0c4687c0c399f64cce4e Mon Sep 17 00:00:00 2001 From: fxlae Date: Sun, 14 Apr 2024 22:43:25 +0200 Subject: [PATCH 12/12] extend tests --- README.md | 6 +- .../typeid.library-conventions.gradle.kts | 2 +- .../java/de/fxlae/typeid/lib/TypeIdLib.java | 2 +- .../test/java/de/fxlae/typeid/SpecTest.java | 66 ++++++----- .../test/java/de/fxlae/typeid/TypeIdTest.java | 76 +++++++----- .../de/fxlae/typeid/lib/TypeIdLibTest.java | 108 ++++++++++++++++++ 6 files changed, 195 insertions(+), 65 deletions(-) create mode 100644 lib/src/test/java/de/fxlae/typeid/lib/TypeIdLibTest.java diff --git a/README.md b/README.md index 1fe622a..78e609b 100644 --- a/README.md +++ b/README.md @@ -106,9 +106,9 @@ Note: Checking `validated.isValid` is advisable for untrusted input. Similar to var report = switch(TypeId.parseToValidated("...")) { case Valid(TypeId(var prefix, var uuid)) when "user".equals(prefix) -> "user with UUID" + uuid; - case Valid(TypeId(var prefix, _)) -> "Not a user, ignore the UUID. Prefix is " + prefix; + case Valid(TypeId(var prefix, var ignored)) -> "Not a user. Prefix is " + prefix; case Invalid(var message) -> "Parsing failed :( ... " + message; -} +}; ``` Note the absent (and superfluous) default case. Exhaustiveness is checked during compilation! @@ -119,7 +119,7 @@ Another safe alternative for working with `Validated` involves methods t ```java // transform -var mappedToPrefix = TypeId.parseToValidated("dog_01h455vb4pex5vsknk084sn02q"); +var mappedToPrefix = TypeId.parseToValidated("dog_01h455vb4pex5vsknk084sn02q") .map(TypeId::prefix) // Validated -> Validated .filter("Not a cat! :(", prefix -> !"cat".equals(prefix)); // the predicate fails diff --git a/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts b/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts index 5c3f7a4..14afe40 100644 --- a/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts +++ b/build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts @@ -5,7 +5,7 @@ plugins { } group = "de.fxlae" -version = "0.3.0-SNAPSHOT" +version = "0.3.0" java { withJavadocJar() diff --git a/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java b/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java index 6886a77..eab444a 100644 --- a/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java +++ b/lib/src/main/java/de/fxlae/typeid/lib/TypeIdLib.java @@ -239,7 +239,7 @@ private static String validatePrefixOnInput(final String input, final int separa return "Prefix with illegal length, must not have more than " + PREFIX_MAX_LENGTH + " characters"; } - if (input.charAt(0) == SEPARATOR || input.charAt(input.length() - 1) == SEPARATOR) { + if (input.charAt(0) == SEPARATOR || input.charAt(separatorIndex - 1) == SEPARATOR) { return "Prefix must not start or end with '" + SEPARATOR + "'"; } diff --git a/lib/src/test/java/de/fxlae/typeid/SpecTest.java b/lib/src/test/java/de/fxlae/typeid/SpecTest.java index fbc2a42..7f2c2e8 100644 --- a/lib/src/test/java/de/fxlae/typeid/SpecTest.java +++ b/lib/src/test/java/de/fxlae/typeid/SpecTest.java @@ -6,9 +6,11 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; import java.io.IOException; import java.util.List; @@ -24,44 +26,25 @@ * and * invalid.yml. */ -class SpecTest { - - private static Stream provideSpecValid() throws IOException { - return loadSpec("/spec/valid.yml", SpecValid.class) - .stream() - .map(s -> Arguments.of(s.name, s.typeid, s.prefix, UUID.fromString(s.uuid))); - } - - private static Stream provideSpecInvalid() throws IOException { - return loadSpec("/spec/invalid.yml", SpecInvalid.class) - .stream() - .map(s -> Arguments.of(s.name, s.typeid, s.description)); - } - - static List loadSpec(String path, Class clazz) throws IOException { - ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); - CollectionType javaType = mapper.getTypeFactory() - .constructCollectionType(List.class, clazz); - return mapper.readValue(SpecTest.class.getResourceAsStream(path), javaType); - } +public class SpecTest { @ParameterizedTest - @MethodSource("provideSpecValid") - void testEncodeAgainstSpecValid(String name, String typeIdAsString, String prefix, UUID uuid) { + @ArgumentsSource(SpecValidProvider.class) + void testEncodeAgainstSpecValid(String name, String expectedTypeIdAsString, String prefix, UUID uuid) { var typeId = TypeId.of(prefix, uuid); - assertEquals(typeIdAsString, typeId.toString()); + assertEquals(expectedTypeIdAsString, typeId.toString()); } @ParameterizedTest - @MethodSource("provideSpecValid") - void testDecodeAgainstSpecValid(String name, String typeIdAsString, String prefix, UUID uuid) { + @ArgumentsSource(SpecValidProvider.class) + void testDecodeAgainstSpecValid(String name, String typeIdAsString, String expectedPrefix, UUID expectedUuid) { var typeId = TypeId.parse(typeIdAsString); - assertEquals(prefix, typeId.prefix()); - assertEquals(uuid, typeId.uuid()); + assertEquals(expectedPrefix, typeId.prefix()); + assertEquals(expectedUuid, typeId.uuid()); } @ParameterizedTest - @MethodSource("provideSpecInvalid") + @ArgumentsSource(SpecInvalidProvider.class) void testDecodeAgainstSpecInvalid(String name, String typeIdAsString, String description) { Assertions.assertThrows(IllegalArgumentException.class, () -> TypeId.parse(typeIdAsString), description); } @@ -81,6 +64,31 @@ void testRandomIds() { } } + public static class SpecValidProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) throws IOException { + return loadSpec("/spec/valid.yml", SpecValid.class) + .stream() + .map(s -> Arguments.of(s.name, s.typeid, s.prefix, UUID.fromString(s.uuid))); + } + } + + public static class SpecInvalidProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) throws IOException { + return loadSpec("/spec/invalid.yml", SpecInvalid.class) + .stream() + .map(s -> Arguments.of(s.name, s.typeid, s.description)); + } + } + + static List loadSpec(String path, Class clazz) throws IOException { + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + CollectionType javaType = mapper.getTypeFactory() + .constructCollectionType(List.class, clazz); + return mapper.readValue(SpecTest.class.getResourceAsStream(path), javaType); + } + @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) static class SpecValid { String name; diff --git a/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java b/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java index daecad8..12b6766 100644 --- a/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java +++ b/lib/src/test/java/de/fxlae/typeid/TypeIdTest.java @@ -1,10 +1,12 @@ package de.fxlae.typeid; +import de.fxlae.typeid.lib.TypeIdLibTest; import de.fxlae.typeid.util.Validated; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.ArgumentsSource; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.UUID; @@ -121,41 +123,53 @@ void parseWithPrefixWithSuffixShouldReturnTypeId() { } @ParameterizedTest - @ValueSource(strings = { - "01h455vb4pex5vsknk084sn02q", // suffix only - "abcdefghijklmnopqrstuvw_01h455vb4pex5vsknk084sn02q", // prefix with allowed chars - "some_prefix_01h455vb4pex5vsknk084sn02q", // prefix with underscore - "sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss_01h455vb4pex5vsknk084sn02q" // prefix with 63 chars - }) - void parseWithValidInputsShouldReturnTypeId(String input) { - var typeId = TypeId.parse(input); - assertNotNull(typeId); + @ArgumentsSource(TypeIdLibTest.ValidTypeIdProvider.class) + void parseWithValidInputsShouldReturnTypeId(String typeIdAsString, String expectedPrefix, UUID expectedUuid) { + var typeId = TypeId.parse(typeIdAsString); + assertThat(typeId.prefix()).isEqualTo(expectedPrefix); + assertThat(typeId.uuid()).isEqualTo(expectedUuid); } @ParameterizedTest - @ValueSource(strings = { - "", - "_", - "someprefix_", // no suffix at all - "_01h455vb4pex5vsknk084sn02q", // suffix only, but with preceding underscore - "__01h455vb4pex5vsknk084sn02q", // prefix is single underscore - "_someprefix_01h455vb4pex5vsknk084sn02q", // prefix starts with underscore - "someprefix__01h455vb4pex5vsknk084sn02q", // prefix ends with underscore - "_someprefix__01h455vb4pex5vsknk084sn02q", // prefix starts and ends with underscore - "sömeprefix_01h455vb4pex5vsknk084sn02q", // prefix with 'ö' - "someprefix_01h455öb4pex5vsknk084sn02q", // suffix with 'ö' - "sOmeprefix_01h455vb4pex5vsknk084sn02q", // prefix with 'O' - "someprefix_01h455Vb4pex5vsknk084sn02q", // suffix with 'V' - "someprefix_01h455lb4pex5vsknk084sn02q", // suffix with 'l' - "ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss_01h455vb4pex5vsknk084sn02q", // prefix with 64 chars - "someprefix_01h455vb4pex5vsknk084sn02", // suffix with 25 chars - "someprefix_01h455vb4pex5vsknk084sn02q2", // suffix with 27 chars - "someprefix_81h455vb4pex5vsknk084sn02q" // leftmost suffix char is != 0-7 - }) - void parseWithInvalidInputShouldFail(String input) { + @ArgumentsSource(TypeIdLibTest.InvalidTypeIdProvider.class) + void parseWithInvalidInputShouldThrow(String typeIdAsString) { assertThrows( IllegalArgumentException.class, - () -> TypeId.parse(input)); + () -> TypeId.parse(typeIdAsString)); + } + + @ParameterizedTest + @ArgumentsSource(TypeIdLibTest.ValidTypeIdProvider.class) + void parseToOptionalWithValidInputsShouldReturnOptionalWithTypeId(String typeIdAsString, String expectedPrefix, UUID expectedUuid) { + var maybeTypeId = TypeId.parseToOptional(typeIdAsString); + assertThat(maybeTypeId).isNotEmpty(); + assertThat(maybeTypeId.get().prefix()).isEqualTo(expectedPrefix); + assertThat(maybeTypeId.get().uuid()).isEqualTo(expectedUuid); + } + + @ParameterizedTest + @ArgumentsSource(TypeIdLibTest.InvalidTypeIdProvider.class) + void parseToOptionalWithInvalidInputShouldReturnEmptyOptional(String typeIdAsString) { + assertThat(TypeId.parseToOptional(typeIdAsString)).isEmpty(); + } + + @ParameterizedTest + @ArgumentsSource(TypeIdLibTest.ValidTypeIdProvider.class) + void parseToValidatedWithValidInputsShouldReturnValid(String typeIdAsString, String expectedPrefix, UUID expectedUuid) { + var validatedTypeId = TypeId.parseToValidated(typeIdAsString); + assertThat(validatedTypeId.isValid()).isTrue(); + assertThat(validatedTypeId.get().prefix()).isEqualTo(expectedPrefix); + assertThat(validatedTypeId.get().uuid()).isEqualTo(expectedUuid); + assertThrows(NoSuchElementException.class, validatedTypeId::message); + } + + @ParameterizedTest + @ArgumentsSource(TypeIdLibTest.InvalidTypeIdProvider.class) + void parseToValidatedWithInvalidInputsShouldReturnInvalid(String typeIdAsString) { + var validatedTypeId = TypeId.parseToValidated(typeIdAsString); + assertThat(validatedTypeId.isValid()).isFalse(); + assertThat(validatedTypeId.message()).isNotEmpty(); + assertThrows(NoSuchElementException.class, validatedTypeId::get); } @Test diff --git a/lib/src/test/java/de/fxlae/typeid/lib/TypeIdLibTest.java b/lib/src/test/java/de/fxlae/typeid/lib/TypeIdLibTest.java new file mode 100644 index 0000000..07ec52b --- /dev/null +++ b/lib/src/test/java/de/fxlae/typeid/lib/TypeIdLibTest.java @@ -0,0 +1,108 @@ +package de.fxlae.typeid.lib; + + +import de.fxlae.typeid.SpecTest; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TypeIdLibTest { + + static final UUID TEST_UUID = UUID.fromString("01890a5d-ac96-774b-bcce-b302099a8057"); + + @ParameterizedTest + @ArgumentsSource(InvalidTypeIdProvider.class) + void parseInvalid(String typeIdAsString) { + Optional result = TypeIdLib.parse( + typeIdAsString, + (prefix, uuid) -> Optional.empty(), + Optional::of); + assertThat(result).isNotEmpty(); + } + + public static class InvalidTypeIdProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + "", + "_", + "someprefix_", // no suffix at all + "_01h455vb4pex5vsknk084sn02q", // suffix only, but with preceding underscore + "__01h455vb4pex5vsknk084sn02q", // prefix is single underscore + "_someprefix_01h455vb4pex5vsknk084sn02q", // prefix starts with underscore + "someprefix__01h455vb4pex5vsknk084sn02q", // prefix ends with underscore + "_someprefix__01h455vb4pex5vsknk084sn02q", // prefix starts and ends with underscore + "sömeprefix_01h455vb4pex5vsknk084sn02q", // prefix with 'ö' + "someprefix_01h455öb4pex5vsknk084sn02q", // suffix with 'ö' + "sOmeprefix_01h455vb4pex5vsknk084sn02q", // prefix with 'O' + "someprefix_01h455Vb4pex5vsknk084sn02q", // suffix with 'V' + "someprefix_01h455lb4pex5vsknk084sn02q", // suffix with 'l' + "ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss_01h455vb4pex5vsknk084sn02q", // prefix with 64 chars + "someprefix_01h455vb4pex5vsknk084sn02", // suffix with 25 chars + "someprefix_01h455vb4pex5vsknk084sn02q2", // suffix with 27 chars + "someprefix_81h455vb4pex5vsknk084sn02q" // leftmost suffix char is != 0-7 + ).map(Arguments::of); + } + } + + @ParameterizedTest + @ArgumentsSource(ValidTypeIdProvider.class) + void parseValid(String typeIdAsString, String expectedPrefix, UUID expectedUuid) { + Optional> result = TypeIdLib.parse( + typeIdAsString, + (prefix, uuid) -> Optional.of(new Tuple<>(prefix, uuid)), + (message) -> Optional.empty()); + assertThat(result).hasValue(new Tuple<>(expectedPrefix, expectedUuid)); + } + + public static class ValidTypeIdProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of("01h455vb4pex5vsknk084sn02q", "", TEST_UUID), // suffix only + Arguments.of("abcdefghijklmnopqrstuvw_01h455vb4pex5vsknk084sn02q", "abcdefghijklmnopqrstuvw", TEST_UUID), // prefix with allowed chars + Arguments.of("some_prefix_01h455vb4pex5vsknk084sn02q", "some_prefix", TEST_UUID), // prefix with underscore + Arguments.of("sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss_01h455vb4pex5vsknk084sn02q", "sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss", TEST_UUID) // prefix with 63 chars + ); + } + } + + @ParameterizedTest + @ArgumentsSource(SpecTest.SpecValidProvider.class) + void parseValidAgainstSpec(String name, String typeIdAsString, String expectedPrefix, UUID expectedUuid) { + Optional> result = TypeIdLib.parse( + typeIdAsString, + (prefix, uuid) -> Optional.of(new Tuple<>(prefix, uuid)), + (message) -> Optional.empty()); + assertThat(result).hasValue(new Tuple<>(expectedPrefix, expectedUuid)); + } + + @ParameterizedTest + @ArgumentsSource(SpecTest.SpecValidProvider.class) + void encodeValidAgainstSpec(String name, String expectedTypeIdAsString, String prefix, UUID uuid) { + var typeIdAsString = TypeIdLib.encode(prefix, uuid); + assertEquals(expectedTypeIdAsString, typeIdAsString); + } + + @ParameterizedTest + @ArgumentsSource(SpecTest.SpecInvalidProvider.class) + void parseInvalidAgainstSpec(String name, String typeIdAsString, String description) { + Optional result = TypeIdLib.parse( + typeIdAsString, + (prefix, uuid) -> Optional.empty(), + Optional::of); + assertThat(result).isNotEmpty(); + } + + record Tuple(A first, B second) { + } +} \ No newline at end of file