Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package app.quickcase.sdk.spring.condition.validate;

import java.util.Set;

import app.quickcase.sdk.spring.condition.Condition;

public interface ConditionValidator {
Set<String> validate(Condition condition);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package app.quickcase.sdk.spring.condition.validate;

import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import app.quickcase.sdk.spring.condition.Condition;
import app.quickcase.sdk.spring.condition.Criteria;
import app.quickcase.sdk.spring.definition.DefinitionExtractor;
import app.quickcase.sdk.spring.path.FieldPath;

public class SemanticConditionValidator implements ConditionValidator {

private final DefinitionExtractor fieldExtractor;

public SemanticConditionValidator(DefinitionExtractor fieldExtractor) {
this.fieldExtractor = fieldExtractor;
}

@Override
public Set<String> validate(Condition condition) {
var allPaths = Arrays.stream(condition.disjunctions())
.flatMap(Arrays::stream)
.map(Criteria::getPath)
.collect(Collectors.toSet());

return allPaths.stream()
.flatMap(this::validate)
.collect(Collectors.toSet());
}

private Stream<String> validate(String path) {
try {
var fieldPath = FieldPath.of(path);

if (fieldExtractor.extractField(fieldPath).isEmpty()) {
return Stream.of("Path `" + path + "` does not exist");
}

return Stream.empty();
} catch (IllegalArgumentException e) {
return Stream.of("Path `" + path + "` is not valid");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package app.quickcase.sdk.spring.definition;

import java.util.ArrayDeque;
import java.util.Map;
import java.util.Optional;

import app.quickcase.sdk.spring.definition.model.DataField;
import app.quickcase.sdk.spring.definition.model.Field;
import app.quickcase.sdk.spring.definition.model.MetadataField;
import app.quickcase.sdk.spring.definition.model.RecordType;
import app.quickcase.sdk.spring.path.DataFieldPath;
import app.quickcase.sdk.spring.path.FieldPath;
import app.quickcase.sdk.spring.path.MetadataFieldPath;

public class DefinitionExtractor {

private final RecordType type;

public DefinitionExtractor(RecordType type) {
this.type = type;
}

public Optional<? extends Field> extractField(FieldPath path) {
return switch (path) {
case MetadataFieldPath metadataFieldPath -> Optional.of(extractField(metadataFieldPath));
case DataFieldPath dataFieldPath -> extractField(dataFieldPath);
default -> throw new IllegalArgumentException("Unsupported field path: " + path);
};
}

public MetadataField extractField(MetadataFieldPath path) {
return switch (path.getMetadata()) {
case WORKSPACE -> MetadataField.builder()
.id(path.toString())
.name("Workspace")
.label("Workspace")
.build();
case TYPE -> MetadataField.builder()
.id(path.toString())
.name("Type")
.label("Record type")
.build();
case ID -> MetadataField.builder()
.id(path.toString())
.name("ID")
.label("Record ID")
.build();
case TITLE -> MetadataField.builder()
.id(path.toString())
.name("Title")
.label("Record title")
.build();
case STATE -> MetadataField.builder()
.id(path.toString())
.name("State")
.label("Record state")
.build();
case CREATED_AT -> MetadataField.builder()
.id(path.toString())
.name("Created at")
.label("Date and time of creation")
.build();
case LAST_MODIFIED_AT -> MetadataField.builder()
.id(path.toString())
.name("Last modified at")
.label("Date and time of last modification")
.build();
case CLASSIFICATION -> MetadataField.builder()
.id(path.toString())
.name("Classification")
.label("Default record classification")
.build();
};
}

public Optional<DataField> extractField(DataFieldPath path) {
return extractDataField(type.schema().fields(), new ArrayDeque<>(path.elements()));
}

private Optional<DataField> extractDataField(Map<String, DataField> fields, ArrayDeque<DataFieldPath.Element> pathElements) {
var element = pathElements.removeFirst();
var field = fields.get(element.getIdentifier());

if (field == null && element.isCollectionItem()) {
return extractDataFieldCollectionItem(fields, element.getCollectionIdentifier(), pathElements);
}

if (field == null) {
return Optional.empty();
}

if (!pathElements.isEmpty()) {
if (field.members() == null) {
return Optional.empty();
}

return extractDataField(field.members(), pathElements);
}

return Optional.of(field);
}

private Optional<DataField> extractDataFieldCollectionItem(Map<String, DataField> fields, String collectionId, ArrayDeque<DataFieldPath.Element> elements) {
var field = fields.get(collectionId);

if (field == null) {
return Optional.empty();
}

if (!elements.isEmpty()) {
var itemFieldBuilder = DataField.builder()
.id(field.id() + "[].value")
.name(field.name())
.label(field.label())
.type(field.content().type())
.acl(field.acl())
.classification(field.classification());
if (field.content().members() != null) {
itemFieldBuilder.members(field.content().members());
}

if (field.content().options() != null) {
itemFieldBuilder.options(field.content().options());
}

return extractDataField(Map.of("value", itemFieldBuilder.build()), elements);
}

return Optional.of(field);
}
}
Original file line number Diff line number Diff line change
@@ -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<Option> options,
Content content,
@Singular Map<String, DataField> members,
Validation validation,
Field.Display display,
@NonNull @Singular("acl") Map<String, Short> acl,
@NonNull String classification
) implements Field {
@Builder
public record Validation(
Integer min,
Optional<Integer> max,
Optional<String> pattern
) {}

@Builder
public record Option(
@NonNull String code,
@NonNull String label
) {}

@Builder
public record Content(
@NonNull String type,
@Singular List<Option> options,
@Singular Map<String, DataField> members
) {}
}
18 changes: 18 additions & 0 deletions src/main/java/app/quickcase/sdk/spring/definition/model/Field.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package app.quickcase.sdk.spring.definition.model;

import java.util.Map;
import java.util.Optional;

import lombok.NonNull;

public interface Field {
@NonNull String id();
@NonNull String name();
String label();
Field.Display display();

interface Display {
String mode();
Map<String, String> parameters();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package app.quickcase.sdk.spring.definition.model;

import java.util.List;

import lombok.Builder;
import lombok.NonNull;
import lombok.Singular;

@Builder
public record MetadataField(
@NonNull String id,
@NonNull String name,
String label,
@Singular List<MetadataField.Option> options,
Field.Display display
) implements Field{
@Builder
public record Option(
@NonNull String code,
@NonNull String label
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package app.quickcase.sdk.spring.definition.model;

import lombok.Builder;
import lombok.NonNull;

@Builder
public record RecordType(
@NonNull Schema schema
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package app.quickcase.sdk.spring.definition.model;

import java.util.Map;

import lombok.Builder;
import lombok.NonNull;
import lombok.Singular;

@Builder
public record Schema(
@NonNull @Singular Map<String, DataField> fields
) {
}
49 changes: 49 additions & 0 deletions src/main/java/app/quickcase/sdk/spring/metadata/Metadata.java
Original file line number Diff line number Diff line change
@@ -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);
};
}
}
44 changes: 44 additions & 0 deletions src/main/java/app/quickcase/sdk/spring/path/DataFieldPath.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package app.quickcase.sdk.spring.path;

import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

import lombok.Getter;
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]+))?])?)*$");
public static final Pattern COLLECTION_ITEM_PATTERN = Pattern.compile("^(?<collectionId>[a-zA-Z0-9_]+)\\[(?:(?:id:(?<itemId>[a-zA-Z0-9_]+))|(?<itemIndex>[0-9]+))?]$");

DataFieldPath(@NonNull String path) {
super(path);
}

public List<Element> elements() {
return Arrays.stream(path.split("\\."))
.map(Element::new)
.toList();
}

@Getter
public static class Element {
private final String identifier;
private final boolean collectionItem;
private final String collectionIdentifier;
private final String itemId;
private final Integer itemIndex;

public Element(@NonNull String identifier) {
this.identifier = identifier;

var collectionItemMatcher = DataFieldPath.COLLECTION_ITEM_PATTERN.matcher(identifier);
collectionItem = collectionItemMatcher.find();

collectionIdentifier = collectionItem ? collectionItemMatcher.group("collectionId") : null;
itemId = collectionItem ? collectionItemMatcher.group("itemId") : null;
var index = collectionItem ? collectionItemMatcher.group("itemIndex") : null;
itemIndex = (index != null) ? Integer.parseInt(index) : null;
}
}
}
Loading