diff --git a/checkstyle-project-fqn.xml b/checkstyle-project-fqn.xml
index 001e0e7a..8532d42f 100644
--- a/checkstyle-project-fqn.xml
+++ b/checkstyle-project-fqn.xml
@@ -128,6 +128,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -173,7 +189,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/field/definition/Field.java b/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/field/definition/Field.java
index 309e81ea..9be9a10e 100644
--- a/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/field/definition/Field.java
+++ b/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/field/definition/Field.java
@@ -14,6 +14,7 @@ public final class Field {
private final String name;
private final FieldType type;
private final Set fieldExamples;
+ private final Set configuredExamples;
private boolean fieldIsOptional;
// default value for the field
@@ -50,6 +51,7 @@ public Field(final String name, final FieldType type) {
truncateStringIfTooLong = false;
truncatedStringLength = -1;
fieldExamples = new HashSet<>();
+ configuredExamples = new HashSet<>();
if (type == FieldType.INTEGER) {
typeValidationRule = new IntegerValidationRule();
@@ -270,6 +272,7 @@ public String getAsTruncatedString(FieldValue value) {
}
public Field withExample(final String anExample) {
+ configuredExamples.add(anExample);
fieldExamples.add(anExample);
if (type == FieldType.ENUM) {
typeValidationRule = new EnumValidationRule(getExamples());
@@ -336,6 +339,22 @@ public List getExamples() {
return new ArrayList<>(buildExamples);
}
+ public List getConfiguredExamples() {
+ return new ArrayList<>(configuredExamples);
+ }
+
+ public String getConfiguredDefaultValue() {
+ return defaultValue;
+ }
+
+ public int getTruncatedStringLength() {
+ return truncatedStringLength;
+ }
+
+ public ValidationRule getTypeValidationRule() {
+ return typeValidationRule;
+ }
+
public String getRandomExampleValue() {
final List examples = getExamples();
diff --git a/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/EnumValidationRule.java b/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/EnumValidationRule.java
index 28fb31e3..bdb89fcb 100644
--- a/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/EnumValidationRule.java
+++ b/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/EnumValidationRule.java
@@ -1,5 +1,6 @@
package uk.co.compendiumdev.thingifier.core.domain.definitions.validation;
+import java.util.ArrayList;
import java.util.List;
import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.FieldType;
import uk.co.compendiumdev.thingifier.core.domain.definitions.field.instance.FieldValue;
@@ -36,4 +37,8 @@ public String getErrorMessage(FieldValue value) {
public String getExplanation() {
return String.format("Value must be an Enum (%s) value", valuesAsCsv());
}
+
+ public List getValidValues() {
+ return new ArrayList<>(validValues);
+ }
}
diff --git a/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/FindsRegexValidationRule.java b/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/FindsRegexValidationRule.java
index c473e396..fde703fd 100644
--- a/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/FindsRegexValidationRule.java
+++ b/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/FindsRegexValidationRule.java
@@ -33,4 +33,8 @@ public String getExplanation() {
return String.format(
"Value must contain text that matches the regex %s", this.regexToMatch);
}
+
+ public String getRegexToFind() {
+ return regexToMatch;
+ }
}
diff --git a/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/MatchesRegexValidationRule.java b/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/MatchesRegexValidationRule.java
index d73dfd7b..bae90e59 100644
--- a/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/MatchesRegexValidationRule.java
+++ b/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/MatchesRegexValidationRule.java
@@ -31,4 +31,8 @@ public String getErrorMessage(final FieldValue value) {
public String getExplanation() {
return String.format("Value must match the regex %s", this.regexToMatch);
}
+
+ public String getRegexToMatch() {
+ return regexToMatch;
+ }
}
diff --git a/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/MaximumLengthValidationRule.java b/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/MaximumLengthValidationRule.java
index 839380cf..057d0d15 100644
--- a/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/MaximumLengthValidationRule.java
+++ b/ercoremodel/src/main/java/uk/co/compendiumdev/thingifier/core/domain/definitions/validation/MaximumLengthValidationRule.java
@@ -36,4 +36,8 @@ public String getErrorMessage(final FieldValue value) {
public String getExplanation() {
return "Maximum length allowed is %d".formatted(maxLength);
}
+
+ public int getMaximumLength() {
+ return maxLength;
+ }
}
diff --git a/pom.xml b/pom.xml
index eff25411..cc527e3e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,6 +17,7 @@
2.2.27
2.8.6
3.53.2.0
+ 2.6
5.6.2
16
5.5.0
@@ -133,6 +134,7 @@
ercoremodel
thingifier
+ thingifier-yaml
examplemodels
todoManagerRestAuto
challenger
diff --git a/thingifier-yaml/pom.xml b/thingifier-yaml/pom.xml
new file mode 100644
index 00000000..8e45389d
--- /dev/null
+++ b/thingifier-yaml/pom.xml
@@ -0,0 +1,87 @@
+
+ 4.0.0
+
+
+ uk.co.compendiumdev.thingifier
+ thingifier-root
+ 1.5.6-SNAPSHOT
+
+
+ uk.co.compendiumdev
+ thingifier-yaml
+ jar
+
+ thingifier yaml adapter
+ https://compendiumdev.co.uk
+
+
+
+ uk.co.compendiumdev
+ thingifier
+ ${thingifier.version}
+
+
+ org.yaml
+ snakeyaml
+ ${snakeyaml-version}
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.jupiter.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit.jupiter.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit.jupiter.version}
+ test
+
+
+ uk.co.compendiumdev.thingifier
+ examplemodels
+ ${thingifier.version}
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.3
+
+ ${java.version}
+ ${java.version}
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.0.0-M4
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+
+
+ checkstyle-check
+ verify
+
+ check
+
+
+
+
+
+
+
diff --git a/thingifier-yaml/src/main/java/uk/co/compendiumdev/thingifier/yaml/ThingifierYamlException.java b/thingifier-yaml/src/main/java/uk/co/compendiumdev/thingifier/yaml/ThingifierYamlException.java
new file mode 100644
index 00000000..ebeb8f59
--- /dev/null
+++ b/thingifier-yaml/src/main/java/uk/co/compendiumdev/thingifier/yaml/ThingifierYamlException.java
@@ -0,0 +1,12 @@
+package uk.co.compendiumdev.thingifier.yaml;
+
+public class ThingifierYamlException extends RuntimeException {
+
+ public ThingifierYamlException(final String message) {
+ super(message);
+ }
+
+ public ThingifierYamlException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/thingifier-yaml/src/main/java/uk/co/compendiumdev/thingifier/yaml/ThingifierYamlExporter.java b/thingifier-yaml/src/main/java/uk/co/compendiumdev/thingifier/yaml/ThingifierYamlExporter.java
new file mode 100644
index 00000000..f3261051
--- /dev/null
+++ b/thingifier-yaml/src/main/java/uk/co/compendiumdev/thingifier/yaml/ThingifierYamlExporter.java
@@ -0,0 +1,35 @@
+package uk.co.compendiumdev.thingifier.yaml;
+
+import java.util.Map;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.Yaml;
+import uk.co.compendiumdev.thingifier.Thingifier;
+import uk.co.compendiumdev.thingifier.application.schema.definition.ThingifierModelDefinition;
+import uk.co.compendiumdev.thingifier.application.schema.definition.ThingifierModelExporter;
+import uk.co.compendiumdev.thingifier.yaml.internal.YamlThingifierDocumentMapper;
+
+public final class ThingifierYamlExporter {
+
+ private final YamlThingifierDocumentMapper mapper;
+
+ public ThingifierYamlExporter() {
+ mapper = new YamlThingifierDocumentMapper();
+ }
+
+ public String export(final Thingifier thingifier) {
+ return export(new ThingifierModelExporter().export(thingifier));
+ }
+
+ public String export(final ThingifierModelDefinition definition) {
+ final Map document = mapper.toYamlMap(definition);
+ return yaml().dump(document);
+ }
+
+ private Yaml yaml() {
+ final DumperOptions options = new DumperOptions();
+ options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
+ options.setPrettyFlow(true);
+ options.setSplitLines(false);
+ return new Yaml(options);
+ }
+}
diff --git a/thingifier-yaml/src/main/java/uk/co/compendiumdev/thingifier/yaml/ThingifierYamlLoader.java b/thingifier-yaml/src/main/java/uk/co/compendiumdev/thingifier/yaml/ThingifierYamlLoader.java
new file mode 100644
index 00000000..9ef0608d
--- /dev/null
+++ b/thingifier-yaml/src/main/java/uk/co/compendiumdev/thingifier/yaml/ThingifierYamlLoader.java
@@ -0,0 +1,92 @@
+package uk.co.compendiumdev.thingifier.yaml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.yaml.snakeyaml.LoaderOptions;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.SafeConstructor;
+import org.yaml.snakeyaml.error.YAMLException;
+import uk.co.compendiumdev.thingifier.Thingifier;
+import uk.co.compendiumdev.thingifier.application.schema.definition.SchemaDefinitionValidationReport;
+import uk.co.compendiumdev.thingifier.application.schema.definition.ThingifierModelAssembler;
+import uk.co.compendiumdev.thingifier.application.schema.definition.ThingifierModelDefinition;
+import uk.co.compendiumdev.thingifier.yaml.internal.YamlThingifierDocument;
+import uk.co.compendiumdev.thingifier.yaml.internal.YamlThingifierDocumentMapper;
+
+public final class ThingifierYamlLoader {
+
+ private final ThingifierModelAssembler assembler;
+ private final YamlThingifierDocumentMapper mapper;
+
+ public ThingifierYamlLoader() {
+ assembler = new ThingifierModelAssembler();
+ mapper = new YamlThingifierDocumentMapper();
+ }
+
+ public ThingifierModelDefinition loadDefinition(final Path path) throws IOException {
+ try (InputStream input = Files.newInputStream(path)) {
+ return loadDefinition(input);
+ }
+ }
+
+ public ThingifierModelDefinition loadDefinition(final InputStream input) {
+ return definitionFrom(loadDocument(input));
+ }
+
+ public ThingifierModelDefinition loadDefinition(final String yamlText) {
+ return definitionFrom(loadDocument(yamlText));
+ }
+
+ public Thingifier loadThingifier(final Path path) throws IOException {
+ return assemble(loadDefinition(path));
+ }
+
+ public Thingifier loadThingifier(final InputStream input) {
+ return assemble(loadDefinition(input));
+ }
+
+ public Thingifier loadThingifier(final String yamlText) {
+ return assemble(loadDefinition(yamlText));
+ }
+
+ private Thingifier assemble(final ThingifierModelDefinition definition) {
+ final SchemaDefinitionValidationReport report = assembler.validate(definition);
+ if (!report.isValid()) {
+ throw new ThingifierYamlException(report.combinedMessages());
+ }
+ return assembler.assemble(definition);
+ }
+
+ private YamlThingifierDocument loadDocument(final InputStream input) {
+ try {
+ return YamlThingifierDocument.fromObject(yaml().load(input));
+ } catch (YAMLException | ClassCastException | IllegalArgumentException e) {
+ throw new ThingifierYamlException("Could not parse YAML schema", e);
+ }
+ }
+
+ private YamlThingifierDocument loadDocument(final String yamlText) {
+ try {
+ return YamlThingifierDocument.fromObject(yaml().load(yamlText));
+ } catch (YAMLException | ClassCastException | IllegalArgumentException e) {
+ throw new ThingifierYamlException("Could not parse YAML schema", e);
+ }
+ }
+
+ private ThingifierModelDefinition definitionFrom(final YamlThingifierDocument document) {
+ try {
+ return mapper.toDefinition(document);
+ } catch (ClassCastException | IllegalArgumentException e) {
+ throw new ThingifierYamlException("Could not map YAML schema", e);
+ }
+ }
+
+ private Yaml yaml() {
+ final LoaderOptions options = new LoaderOptions();
+ options.setAllowDuplicateKeys(false);
+ options.setMaxAliasesForCollections(20);
+ return new Yaml(new SafeConstructor(options));
+ }
+}
diff --git a/thingifier-yaml/src/main/java/uk/co/compendiumdev/thingifier/yaml/internal/YamlEntityDocument.java b/thingifier-yaml/src/main/java/uk/co/compendiumdev/thingifier/yaml/internal/YamlEntityDocument.java
new file mode 100644
index 00000000..ff891a5b
--- /dev/null
+++ b/thingifier-yaml/src/main/java/uk/co/compendiumdev/thingifier/yaml/internal/YamlEntityDocument.java
@@ -0,0 +1,65 @@
+package uk.co.compendiumdev.thingifier.yaml.internal;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public final class YamlEntityDocument {
+
+ private final String name;
+ private final String pluralName;
+ private final Integer maxInstances;
+ private final String primaryKey;
+ private final Map fields;
+
+ private YamlEntityDocument(
+ final String name,
+ final String pluralName,
+ final Integer maxInstances,
+ final String primaryKey,
+ final Map fields) {
+ this.name = name;
+ this.pluralName = pluralName;
+ this.maxInstances = maxInstances;
+ this.primaryKey = primaryKey;
+ this.fields = fields;
+ }
+
+ static YamlEntityDocument fromEntry(final String name, final Object source) {
+ final Map map = YamlMapSupport.asMap(source);
+ return new YamlEntityDocument(
+ name,
+ YamlMapSupport.stringValue(map.get("plural")),
+ YamlMapSupport.integerValue(map.get("maxInstances")),
+ YamlMapSupport.stringValue(map.get("primaryKey")),
+ fieldsFrom(map.get("fields")));
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public String pluralName() {
+ return pluralName;
+ }
+
+ public Integer maxInstances() {
+ return maxInstances;
+ }
+
+ public String primaryKey() {
+ return primaryKey;
+ }
+
+ public Map fields() {
+ return fields;
+ }
+
+ private static Map fieldsFrom(final Object source) {
+ final Map fields = new LinkedHashMap<>();
+ for (Map.Entry entry : YamlMapSupport.stringMap(source).entrySet()) {
+ fields.put(
+ entry.getKey(), YamlFieldDocument.fromEntry(entry.getKey(), entry.getValue()));
+ }
+ return fields;
+ }
+}
diff --git a/thingifier-yaml/src/main/java/uk/co/compendiumdev/thingifier/yaml/internal/YamlFieldDocument.java b/thingifier-yaml/src/main/java/uk/co/compendiumdev/thingifier/yaml/internal/YamlFieldDocument.java
new file mode 100644
index 00000000..5a63d3dc
--- /dev/null
+++ b/thingifier-yaml/src/main/java/uk/co/compendiumdev/thingifier/yaml/internal/YamlFieldDocument.java
@@ -0,0 +1,122 @@
+package uk.co.compendiumdev.thingifier.yaml.internal;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public final class YamlFieldDocument {
+
+ private final String name;
+ private final String type;
+ private final boolean required;
+ private final boolean unique;
+ private final String defaultValue;
+ private final String description;
+ private final List examples;
+ private final Integer truncateTo;
+ private final String minValue;
+ private final String maxValue;
+ private final List