From b370490cce167b556378d7b0d62e1034179be27e Mon Sep 17 00:00:00 2001 From: Valentin Laurin Date: Wed, 29 Jan 2025 17:24:35 +0000 Subject: [PATCH 1/5] Add `Metadata` enum --- .../sdk/spring/metadata/Metadata.java | 49 +++++++ .../sdk/spring/metadata/MetadataTest.java | 124 ++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 src/main/java/app/quickcase/sdk/spring/metadata/Metadata.java create mode 100644 src/test/java/app/quickcase/sdk/spring/metadata/MetadataTest.java diff --git a/src/main/java/app/quickcase/sdk/spring/metadata/Metadata.java b/src/main/java/app/quickcase/sdk/spring/metadata/Metadata.java new file mode 100644 index 0000000..a5670b1 --- /dev/null +++ b/src/main/java/app/quickcase/sdk/spring/metadata/Metadata.java @@ -0,0 +1,49 @@ +package app.quickcase.sdk.spring.metadata; + +import lombok.NonNull; + +public enum Metadata { + WORKSPACE("[workspace]"), + TYPE("[type]"), + ID("[id]"), + TITLE("[title]"), + STATE("[state]"), + CLASSIFICATION("[classification]"), + CREATED_AT("[createdAt]"), + LAST_MODIFIED_AT("[lastModifiedAt]"); + + private final String path; + + Metadata(String path) { + this.path = path; + } + + public String getPath() { + return path; + } + + public static Metadata fromPath(@NonNull String path) { + if (path.length() < 2) { + throw new IllegalArgumentException("Invalid metadata path: " + path); + } + + var name = path.substring(1, path.length() - 1).toLowerCase(); + return switch (name) { + case "workspace", "organisation", "jurisdiction" -> + WORKSPACE; + case "type", "case_type" -> + TYPE; + case "id", "reference", "case_reference" -> + ID; + case "title" -> TITLE; + case "state" -> STATE; + case "classification", "security_classification" -> + CLASSIFICATION; + case "createdat", "created", "created_date" -> + CREATED_AT; + case "lastmodifiedat", "modified", "last_modified", "last_modified_date" -> + LAST_MODIFIED_AT; + default -> throw new IllegalArgumentException("Invalid metadata path: " + path); + }; + } +} diff --git a/src/test/java/app/quickcase/sdk/spring/metadata/MetadataTest.java b/src/test/java/app/quickcase/sdk/spring/metadata/MetadataTest.java new file mode 100644 index 0000000..8809db6 --- /dev/null +++ b/src/test/java/app/quickcase/sdk/spring/metadata/MetadataTest.java @@ -0,0 +1,124 @@ +package app.quickcase.sdk.spring.metadata; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class MetadataTest { + + @Nested + class FromPath { + @Test + @DisplayName("should throw error when path not valid") + void shouldThrowErrorWhenPathTooShort() { + var error = assertThrows(IllegalArgumentException.class, () -> Metadata.fromPath("[")); + assertThat(error.getMessage(), equalTo("Invalid metadata path: [")); + } + + @Test + @DisplayName("should throw error when path not valid") + void shouldThrowErrorWhenPathNotValid() { + var error = assertThrows(IllegalArgumentException.class, () -> Metadata.fromPath("[notValid]")); + assertThat(error.getMessage(), equalTo("Invalid metadata path: [notValid]")); + } + + @ParameterizedTest + @ValueSource(strings = { + "[workspace]", + "[WORKSPACE]", // Case insensitive + "[organisation]", // Legacy + "[jurisdiction]", // Legacy + }) + @DisplayName("should return workspace metadata") + void shouldReturnWorkspace(String path) { + assertThat(Metadata.fromPath(path), is(Metadata.WORKSPACE)); + } + + @ParameterizedTest + @ValueSource(strings = { + "[type]", + "[TYPE]", // Case insensitive + "[case_type]", // Legacy + }) + @DisplayName("should return type metadata") + void shouldReturnType(String path) { + assertThat(Metadata.fromPath(path), is(Metadata.TYPE)); + } + + @ParameterizedTest + @ValueSource(strings = { + "[id]", + "[ID]", // Case insensitive + "[reference]", // Legacy + "[case_reference]", // Legacy + }) + @DisplayName("should return id metadata") + void shouldReturnId(String path) { + assertThat(Metadata.fromPath(path), is(Metadata.ID)); + } + + @ParameterizedTest + @ValueSource(strings = { + "[title]", + "[TITLE]", // Case insensitive + }) + @DisplayName("should return title metadata") + void shouldReturnTitle(String path) { + assertThat(Metadata.fromPath(path), is(Metadata.TITLE)); + } + + @ParameterizedTest + @ValueSource(strings = { + "[state]", + "[STATE]", // Case insensitive + }) + @DisplayName("should return state metadata") + void shouldReturnState(String path) { + assertThat(Metadata.fromPath(path), is(Metadata.STATE)); + } + + @ParameterizedTest + @ValueSource(strings = { + "[classification]", + "[CLASSIFICATION]", // Case insensitive + "[security_classification]", // Legacy + }) + @DisplayName("should return classification metadata") + void shouldReturnClassification(String path) { + assertThat(Metadata.fromPath(path), is(Metadata.CLASSIFICATION)); + } + + @ParameterizedTest + @ValueSource(strings = { + "[createdat]", + "[CreatedAt]", // Case insensitive + "[created]", // Legacy + "[created_date]", // Legacy + }) + @DisplayName("should return createdAt metadata") + void shouldReturnCreatedAt(String path) { + assertThat(Metadata.fromPath(path), is(Metadata.CREATED_AT)); + } + + @ParameterizedTest + @ValueSource(strings = { + "[lastmodifiedat]", + "[LastModifiedAt]", // Case insensitive + "[modified]", // Legacy + "[last_modified]", // Legacy + "[last_modified_date]", // Legacy + }) + @DisplayName("should return lastModifiedAt metadata") + void shouldReturnLastModifiedAt(String path) { + assertThat(Metadata.fromPath(path), is(Metadata.LAST_MODIFIED_AT)); + } + } + +} \ No newline at end of file From 57785076b5494d064b1a8b4171c064e3f10d0460 Mon Sep 17 00:00:00 2001 From: Valentin Laurin Date: Thu, 30 Jan 2025 12:57:35 +0000 Subject: [PATCH 2/5] Add field path support --- .../sdk/spring/path/DataFieldPath.java | 13 ++++ .../quickcase/sdk/spring/path/FieldPath.java | 38 +++++++++ .../sdk/spring/path/MetadataFieldPath.java | 20 +++++ .../sdk/spring/path/FieldPathTest.java | 77 +++++++++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 src/main/java/app/quickcase/sdk/spring/path/DataFieldPath.java create mode 100644 src/main/java/app/quickcase/sdk/spring/path/FieldPath.java create mode 100644 src/main/java/app/quickcase/sdk/spring/path/MetadataFieldPath.java create mode 100644 src/test/java/app/quickcase/sdk/spring/path/FieldPathTest.java diff --git a/src/main/java/app/quickcase/sdk/spring/path/DataFieldPath.java b/src/main/java/app/quickcase/sdk/spring/path/DataFieldPath.java new file mode 100644 index 0000000..340ff9c --- /dev/null +++ b/src/main/java/app/quickcase/sdk/spring/path/DataFieldPath.java @@ -0,0 +1,13 @@ +package app.quickcase.sdk.spring.path; + +import java.util.regex.Pattern; + +import lombok.NonNull; + +public final class DataFieldPath extends FieldPath { + public static final Pattern PATTERN = Pattern.compile("^[a-zA-Z0-9_]+(?:\\[(?:id:[a-zA-Z0-9_]+)|(?:[0-9]+)])?(?:\\.[a-zA-Z0-9_]+(?:\\[(?:id:[a-zA-Z0-9_]+)|(?:[0-9]+)])?)*$"); + + DataFieldPath(@NonNull String path) { + super(path); + } +} diff --git a/src/main/java/app/quickcase/sdk/spring/path/FieldPath.java b/src/main/java/app/quickcase/sdk/spring/path/FieldPath.java new file mode 100644 index 0000000..ca37cb6 --- /dev/null +++ b/src/main/java/app/quickcase/sdk/spring/path/FieldPath.java @@ -0,0 +1,38 @@ +package app.quickcase.sdk.spring.path; + +import lombok.EqualsAndHashCode; +import lombok.NonNull; + +@EqualsAndHashCode +abstract public class FieldPath { + protected final String path; + + protected FieldPath(@NonNull String path) { + this.path = path; + } + + public static FieldPath of(@NonNull String path) { + if (MetadataFieldPath.PATTERN.matcher(path).matches()) { + return ofMetadata(path); + } + + return ofData(path); + } + + public static MetadataFieldPath ofMetadata(@NonNull String path) { + return new MetadataFieldPath(path); + } + + public static DataFieldPath ofData(@NonNull String path) { + if (!DataFieldPath.PATTERN.matcher(path).matches()) { + throw new IllegalArgumentException("Invalid data field path: " + path); + } + + return new DataFieldPath(path); + } + + @Override + public String toString() { + return path; + } +} diff --git a/src/main/java/app/quickcase/sdk/spring/path/MetadataFieldPath.java b/src/main/java/app/quickcase/sdk/spring/path/MetadataFieldPath.java new file mode 100644 index 0000000..75dbcc2 --- /dev/null +++ b/src/main/java/app/quickcase/sdk/spring/path/MetadataFieldPath.java @@ -0,0 +1,20 @@ +package app.quickcase.sdk.spring.path; + +import java.util.regex.Pattern; + +import app.quickcase.sdk.spring.metadata.Metadata; +import lombok.Getter; +import lombok.NonNull; + +@Getter +public final class MetadataFieldPath extends FieldPath { + public static final Pattern PATTERN = Pattern.compile("^\\[[a-zA-Z_]+]$"); + + final private Metadata metadata; + + MetadataFieldPath(@NonNull String path) { + super(path); + metadata = Metadata.fromPath(path); + } + +} diff --git a/src/test/java/app/quickcase/sdk/spring/path/FieldPathTest.java b/src/test/java/app/quickcase/sdk/spring/path/FieldPathTest.java new file mode 100644 index 0000000..b4ce3ed --- /dev/null +++ b/src/test/java/app/quickcase/sdk/spring/path/FieldPathTest.java @@ -0,0 +1,77 @@ +package app.quickcase.sdk.spring.path; + +import app.quickcase.sdk.spring.metadata.Metadata; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class FieldPathTest { + + @Nested + class OfMetadata { + @Test + @DisplayName("should throw error when path is not for metadata") + void shouldThrowErrorWhenNotMetadata() { + var error = assertThrows(IllegalArgumentException.class, () -> FieldPath.ofMetadata("field1")); + assertThat(error.getMessage(), equalTo("Invalid metadata path: field1")); + } + + @Test + @DisplayName("should return metadata field path") + void shouldReturnMetadataFieldPath() { + var path = FieldPath.ofMetadata("[state]"); + + assertThat(path.toString(), equalTo("[state]")); + assertThat(path.getMetadata(), is(Metadata.STATE)); + } + } + + @Nested + class OfData { + @Test + @DisplayName("should throw error when path is not for data field") + void shouldThrowErrorWhenNotData() { + var error = assertThrows(IllegalArgumentException.class, () -> FieldPath.ofData("[state]")); + assertThat(error.getMessage(), equalTo("Invalid data field path: [state]")); + } + + @Test + @DisplayName("should return data field path") + void shouldReturnDataFieldPath() { + var path = FieldPath.ofData("field1.child2"); + + assertThat(path.toString(), equalTo("field1.child2")); + } + } + + @Nested + class Of { + @Test + @DisplayName("should return metadata field path") + void shouldReturnMetadataFieldPath() { + var path = FieldPath.of("[state]"); + + assertAll( + () -> assertThat(path.toString(), equalTo("[state]")), + () -> assertThat(path, is(instanceOf(MetadataFieldPath.class))) + ); + } + + @Test + @DisplayName("should return data field path") + void shouldReturnDataFieldPath() { + var path = FieldPath.of("field1.child2"); + + assertAll( + () -> assertThat(path.toString(), equalTo("field1.child2")), + () -> assertThat(path, is(instanceOf(DataFieldPath.class))) + ); + } + } + +} \ No newline at end of file From 97b47d5c1702ccfc71f892300d8ba8b5bac231c0 Mon Sep 17 00:00:00 2001 From: Valentin Laurin Date: Thu, 30 Jan 2025 13:00:05 +0000 Subject: [PATCH 3/5] Add normalised definition model for schema --- .../spring/definition/model/DataField.java | 45 +++++++++++++++++++ .../sdk/spring/definition/model/Field.java | 18 ++++++++ .../definition/model/MetadataField.java | 22 +++++++++ .../spring/definition/model/RecordType.java | 10 +++++ .../sdk/spring/definition/model/Schema.java | 13 ++++++ 5 files changed, 108 insertions(+) create mode 100644 src/main/java/app/quickcase/sdk/spring/definition/model/DataField.java create mode 100644 src/main/java/app/quickcase/sdk/spring/definition/model/Field.java create mode 100644 src/main/java/app/quickcase/sdk/spring/definition/model/MetadataField.java create mode 100644 src/main/java/app/quickcase/sdk/spring/definition/model/RecordType.java create mode 100644 src/main/java/app/quickcase/sdk/spring/definition/model/Schema.java diff --git a/src/main/java/app/quickcase/sdk/spring/definition/model/DataField.java b/src/main/java/app/quickcase/sdk/spring/definition/model/DataField.java new file mode 100644 index 0000000..1c10fbd --- /dev/null +++ b/src/main/java/app/quickcase/sdk/spring/definition/model/DataField.java @@ -0,0 +1,45 @@ +package app.quickcase.sdk.spring.definition.model; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import lombok.Builder; +import lombok.NonNull; +import lombok.Singular; + +@Builder +public record DataField( + @NonNull String id, + @NonNull String name, + String label, + String hint, + @NonNull String type, + @Singular List