diff --git a/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/dto/PatchSchemaRequest.java b/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/dto/PatchSchemaRequest.java
new file mode 100644
index 0000000..201008d
--- /dev/null
+++ b/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/dto/PatchSchemaRequest.java
@@ -0,0 +1,26 @@
+package cloud.nstart.terravault.ordinis.restapi.dto;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
+/**
+ * Inline single-field schema edit per backend #4 в pending-endpoints brief.
+ *
+ *
Structured op (вместо JSON Patch RFC 6902) — более ergonomic для UI
+ * Fields tab inline edit. Frontend каждый field row кликает "edit" → form
+ * показывает только allowed (non-breaking) changes → submits эти deltas.
+ *
+ * @param expectedHeadVersion optimistic-lock — schema_version текущий
+ * head. Если в БД другой → 409 version_mismatch.
+ * @param field property name внутри schema.properties (например
+ * "altitude").
+ * @param changes key→value pairs изменения. Allowed keys:
+ * description, title, minimum, maximum, enum (только
+ * additions). Breaking keys (type, format, x-references)
+ * → 422.
+ */
+public record PatchSchemaRequest(
+ @NotBlank String expectedHeadVersion,
+ @NotBlank String field,
+ @NotNull JsonNode changes) {}
diff --git a/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/error/OrdinisException.java b/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/error/OrdinisException.java
index 2f1afcb..1d50610 100644
--- a/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/error/OrdinisException.java
+++ b/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/error/OrdinisException.java
@@ -31,4 +31,8 @@ public class OrdinisException extends RuntimeException {
public static OrdinisException forbidden(String code, String message) {
return new OrdinisException(HttpStatus.FORBIDDEN, code, message);
}
+
+ public static OrdinisException unprocessableEntity(String code, String message) {
+ return new OrdinisException(HttpStatus.UNPROCESSABLE_ENTITY, code, message);
+ }
}
diff --git a/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/service/SchemaPatchService.java b/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/service/SchemaPatchService.java
new file mode 100644
index 0000000..676fdee
--- /dev/null
+++ b/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/service/SchemaPatchService.java
@@ -0,0 +1,237 @@
+package cloud.nstart.terravault.ordinis.restapi.service;
+
+import cloud.nstart.terravault.ordinis.auth.ScopeContext;
+import cloud.nstart.terravault.ordinis.domain.definition.DictionaryDefinition;
+import cloud.nstart.terravault.ordinis.domain.definition.DictionaryDefinitionRepository;
+import cloud.nstart.terravault.ordinis.restapi.audit.AuditLogger;
+import cloud.nstart.terravault.ordinis.restapi.dto.PatchSchemaRequest;
+import cloud.nstart.terravault.ordinis.restapi.error.OrdinisException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Inline non-breaking schema patch — backend #4 из pending-endpoints brief.
+ *
+ *
Применяет узкий allow-list mutations к одному property без breaking
+ * existing records. Bumps schema_version в patch component (1.4.0 → 1.4.1).
+ * Cascades через DictionaryDefinitionService.updateSchema чтобы переиспользовать
+ * outbox emit + audit logging.
+ *
+ *
Allowed change keys:
+ *
+ * - {@code description} — JsonNode (localized object или plain string)
+ * - {@code title} — localized object
+ * - {@code minimum} / {@code maximum} — numeric (expansion only —
+ * narrowing rejected т.к. может invalidate existing records)
+ * - {@code enum} — array. Mutation должна только ADD values, not remove
+ * (rejecting → 422 breaking_change_requires_draft)
+ *
+ *
+ * Breaking change keys (type, format, required, x-references, *Of) →
+ * 422 {@code breaking_change_requires_draft}.
+ */
+@Service
+public class SchemaPatchService {
+
+ private static final Logger log = LoggerFactory.getLogger(SchemaPatchService.class);
+
+ private static final Set ALLOWED_KEYS = Set.of(
+ "description", "title", "minimum", "maximum", "enum");
+
+ private final DictionaryDefinitionRepository repository;
+ private final DictionaryDefinitionService definitionService;
+ private final ScopeContext scopeContext;
+ /** Optional — null в test ctor'е. */
+ private final AuditLogger auditLogger;
+
+ @Autowired
+ public SchemaPatchService(
+ DictionaryDefinitionRepository repository,
+ DictionaryDefinitionService definitionService,
+ ScopeContext scopeContext,
+ AuditLogger auditLogger) {
+ this.repository = repository;
+ this.definitionService = definitionService;
+ this.scopeContext = scopeContext;
+ this.auditLogger = auditLogger;
+ }
+
+ public SchemaPatchService(
+ DictionaryDefinitionRepository repository,
+ DictionaryDefinitionService definitionService,
+ ScopeContext scopeContext) {
+ this(repository, definitionService, scopeContext, null);
+ }
+
+ /**
+ * Apply patch. Throws OrdinisException на validation issues:
+ *
+ * - 404 {@code dictionary_not_found}
+ * - 404 {@code field_not_found} — field отсутствует в schema.properties
+ * - 409 {@code version_mismatch} — expectedHeadVersion stale
+ * - 422 {@code breaking_change_requires_draft} — patch trying breaking key
+ * - 422 {@code validation_failed} — numeric narrowing / enum removal
+ *
+ */
+ @Transactional
+ public PatchSchemaResponse patchSchema(String dictName, PatchSchemaRequest req) {
+ DictionaryDefinition d = definitionService.findByName(dictName);
+ if (!scopeContext.canAccess(d.getScope())) {
+ throw OrdinisException.notFound(
+ "dictionary_not_found", "Dictionary not found: " + dictName);
+ }
+ if (!d.getSchemaVersion().equals(req.expectedHeadVersion())) {
+ throw OrdinisException.conflict(
+ "version_mismatch",
+ "expectedHeadVersion=" + req.expectedHeadVersion()
+ + " but current is " + d.getSchemaVersion());
+ }
+
+ JsonNode schema = d.getSchemaJson();
+ if (!(schema instanceof ObjectNode schemaObj)) {
+ throw OrdinisException.badRequest(
+ "invalid_schema", "Schema is not a JSON object");
+ }
+ JsonNode propertiesNode = schemaObj.get("properties");
+ if (!(propertiesNode instanceof ObjectNode props)) {
+ throw OrdinisException.notFound(
+ "field_not_found", "Schema has no properties");
+ }
+ JsonNode targetProp = props.get(req.field());
+ if (!(targetProp instanceof ObjectNode targetObj)) {
+ throw OrdinisException.notFound(
+ "field_not_found", "Field not in schema.properties: " + req.field());
+ }
+
+ JsonNode changesNode = req.changes();
+ if (!changesNode.isObject()) {
+ throw OrdinisException.badRequest(
+ "invalid_changes", "changes must be a JSON object");
+ }
+
+ // Validate каждый change key + apply на копию target prop.
+ ObjectNode patched = targetObj.deepCopy();
+ Iterator> it = changesNode.fields();
+ while (it.hasNext()) {
+ Map.Entry entry = it.next();
+ String key = entry.getKey();
+ JsonNode newValue = entry.getValue();
+ if (!ALLOWED_KEYS.contains(key)) {
+ throw OrdinisException.unprocessableEntity(
+ "breaking_change_requires_draft",
+ "Key '" + key + "' requires workflow draft, not inline patch."
+ + " Allowed inline keys: " + ALLOWED_KEYS);
+ }
+ JsonNode oldValue = targetObj.get(key);
+ validateChange(key, oldValue, newValue);
+ patched.set(key, newValue);
+ }
+
+ // Apply patched property в новый schemaJson + bump version.
+ ObjectNode newProps = props.deepCopy();
+ newProps.set(req.field(), patched);
+ ObjectNode newSchema = schemaObj.deepCopy();
+ newSchema.set("properties", newProps);
+
+ String oldVersion = d.getSchemaVersion();
+ String newVersion = bumpPatchVersion(oldVersion);
+
+ JsonNode prevSchema = d.getSchemaJson();
+ d.setSchemaJson(newSchema);
+ d.setSchemaVersion(newVersion);
+ DictionaryDefinition saved = repository.save(d);
+
+ if (auditLogger != null) {
+ auditLogger.definitionUpdated(saved, prevSchema);
+ }
+ log.info("Schema patched: dict={} field={} oldVersion={} newVersion={}",
+ dictName, req.field(), oldVersion, newVersion);
+
+ return new PatchSchemaResponse(newVersion, saved.getSchemaJson());
+ }
+
+ /** Per-key non-breaking validation. */
+ private static void validateChange(String key, JsonNode oldValue, JsonNode newValue) {
+ switch (key) {
+ case "description":
+ case "title":
+ // Free-form text/object — всегда non-breaking.
+ return;
+ case "minimum": {
+ if (oldValue != null && oldValue.isNumber() && newValue.isNumber()) {
+ double oldNum = oldValue.doubleValue();
+ double newNum = newValue.doubleValue();
+ if (newNum > oldNum) {
+ throw OrdinisException.unprocessableEntity(
+ "validation_failed",
+ "minimum tighter (" + newNum + " > " + oldNum
+ + ") могло бы invalidate existing records. Use draft.");
+ }
+ }
+ return;
+ }
+ case "maximum": {
+ if (oldValue != null && oldValue.isNumber() && newValue.isNumber()) {
+ double oldNum = oldValue.doubleValue();
+ double newNum = newValue.doubleValue();
+ if (newNum < oldNum) {
+ throw OrdinisException.unprocessableEntity(
+ "validation_failed",
+ "maximum tighter (" + newNum + " < " + oldNum
+ + ") могло бы invalidate existing records. Use draft.");
+ }
+ }
+ return;
+ }
+ case "enum": {
+ if (oldValue != null && oldValue.isArray() && newValue.isArray()) {
+ // new enum должен содержать все старые values (only-additions).
+ java.util.Set oldSet = new java.util.HashSet<>();
+ oldValue.forEach(v -> oldSet.add(v.asText()));
+ java.util.Set newSet = new java.util.HashSet<>();
+ newValue.forEach(v -> newSet.add(v.asText()));
+ if (!newSet.containsAll(oldSet)) {
+ oldSet.removeAll(newSet);
+ throw OrdinisException.unprocessableEntity(
+ "validation_failed",
+ "enum removes existing values: " + oldSet + ". Use draft.");
+ }
+ }
+ return;
+ }
+ default:
+ // Shouldn't happen — ALLOWED_KEYS check выше.
+ throw OrdinisException.unprocessableEntity(
+ "breaking_change_requires_draft", "Key not allowed: " + key);
+ }
+ }
+
+ /**
+ * Bump semver patch component (1.4.0 → 1.4.1). Non-semver versions
+ * → append ".1" / "+1". Best-effort, не throws.
+ */
+ static String bumpPatchVersion(String version) {
+ if (version == null || version.isBlank()) return "1.0.1";
+ String[] parts = version.split("\\.");
+ if (parts.length != 3) return version + ".1";
+ try {
+ int patch = Integer.parseInt(parts[2]);
+ return parts[0] + "." + parts[1] + "." + (patch + 1);
+ } catch (NumberFormatException e) {
+ return version + ".1";
+ }
+ }
+
+ // === DTOs ===
+
+ public record PatchSchemaResponse(String newVersion, JsonNode schemaJson) {}
+}
diff --git a/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/web/DictionaryDefinitionController.java b/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/web/DictionaryDefinitionController.java
index fab05be..12182a1 100644
--- a/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/web/DictionaryDefinitionController.java
+++ b/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/web/DictionaryDefinitionController.java
@@ -5,11 +5,14 @@ import cloud.nstart.terravault.ordinis.domain.DataScope;
import cloud.nstart.terravault.ordinis.domain.record.DictionaryRecordRepository;
import cloud.nstart.terravault.ordinis.restapi.dto.CreateDictionaryRequest;
import cloud.nstart.terravault.ordinis.restapi.dto.DictionaryResponse;
+import cloud.nstart.terravault.ordinis.restapi.dto.PatchSchemaRequest;
import cloud.nstart.terravault.ordinis.restapi.error.OrdinisException;
import cloud.nstart.terravault.ordinis.restapi.service.DictionaryDefinitionService;
+import cloud.nstart.terravault.ordinis.restapi.service.SchemaPatchService;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
@@ -32,14 +35,17 @@ public class DictionaryDefinitionController {
private final DictionaryDefinitionService service;
private final ScopeContext scopeContext;
private final DictionaryRecordRepository recordRepository;
+ private final SchemaPatchService schemaPatchService;
public DictionaryDefinitionController(
DictionaryDefinitionService service,
ScopeContext scopeContext,
- DictionaryRecordRepository recordRepository) {
+ DictionaryRecordRepository recordRepository,
+ SchemaPatchService schemaPatchService) {
this.service = service;
this.scopeContext = scopeContext;
this.recordRepository = recordRepository;
+ this.schemaPatchService = schemaPatchService;
}
@GetMapping
@@ -102,4 +108,25 @@ public class DictionaryDefinitionController {
}
return DictionaryResponse.from(service.updateSchema(name, req));
}
+
+ /**
+ * Inline non-breaking field patch — backend #4 из pending-endpoints brief.
+ * Bumps patch component (1.4.0 → 1.4.1). Только allow-listed keys
+ * (description/title/minimum/maximum/enum).
+ *
+ * Errors:
+ *
+ * - 404 {@code dictionary_not_found}
+ * - 404 {@code field_not_found}
+ * - 409 {@code version_mismatch}
+ * - 422 {@code breaking_change_requires_draft} / {@code validation_failed}
+ *
+ */
+ @PatchMapping("/{name}/schema")
+ public SchemaPatchService.PatchSchemaResponse patchSchema(
+ @PathVariable String name,
+ @org.springframework.web.bind.annotation.RequestBody
+ @jakarta.validation.Valid PatchSchemaRequest req) {
+ return schemaPatchService.patchSchema(name, req);
+ }
}
diff --git a/ordinis-rest-api/src/test/java/cloud/nstart/terravault/ordinis/restapi/service/SchemaPatchServiceTest.java b/ordinis-rest-api/src/test/java/cloud/nstart/terravault/ordinis/restapi/service/SchemaPatchServiceTest.java
new file mode 100644
index 0000000..3ca9c2b
--- /dev/null
+++ b/ordinis-rest-api/src/test/java/cloud/nstart/terravault/ordinis/restapi/service/SchemaPatchServiceTest.java
@@ -0,0 +1,181 @@
+package cloud.nstart.terravault.ordinis.restapi.service;
+
+import cloud.nstart.terravault.ordinis.auth.ScopeContext;
+import cloud.nstart.terravault.ordinis.domain.DataScope;
+import cloud.nstart.terravault.ordinis.domain.definition.DictionaryDefinition;
+import cloud.nstart.terravault.ordinis.domain.definition.DictionaryDefinitionRepository;
+import cloud.nstart.terravault.ordinis.restapi.dto.PatchSchemaRequest;
+import cloud.nstart.terravault.ordinis.restapi.error.OrdinisException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class SchemaPatchServiceTest {
+
+ private static final ObjectMapper OM = new ObjectMapper();
+
+ private DictionaryDefinitionRepository repo;
+ private DictionaryDefinitionService defService;
+ private TestScopeContext scopeCtx;
+ private SchemaPatchService service;
+
+ @BeforeEach
+ void setUp() {
+ repo = mock(DictionaryDefinitionRepository.class);
+ defService = new DictionaryDefinitionService(repo, null);
+ scopeCtx = new TestScopeContext();
+ service = new SchemaPatchService(repo, defService, scopeCtx);
+ when(repo.save(org.mockito.ArgumentMatchers.any(DictionaryDefinition.class)))
+ .thenAnswer(inv -> inv.getArgument(0));
+ }
+
+ static final class TestScopeContext extends ScopeContext {
+ private DataScope blockedScope;
+ TestScopeContext() { super(null); }
+ @Override public boolean canAccess(DataScope dataScope) {
+ return dataScope != blockedScope;
+ }
+ @Override public java.util.Set currentScopes() {
+ return java.util.Set.of(DataScope.PUBLIC, DataScope.INTERNAL, DataScope.RESTRICTED);
+ }
+ void block(DataScope s) { this.blockedScope = s; }
+ }
+
+ @Test
+ void patch_versionMismatch_409() throws Exception {
+ var d = def("dict", "1.4.0",
+ "{\"properties\":{\"altitude\":{\"type\":\"integer\"}}}");
+ when(repo.findByName("dict")).thenReturn(Optional.of(d));
+ var req = new PatchSchemaRequest("1.0.0", "altitude",
+ OM.readTree("{\"description\":\"updated\"}"));
+ assertThatThrownBy(() -> service.patchSchema("dict", req))
+ .isInstanceOfSatisfying(OrdinisException.class,
+ e -> assertThat(e.getCode()).isEqualTo("version_mismatch"));
+ }
+
+ @Test
+ void patch_fieldNotFound_404() throws Exception {
+ var d = def("dict", "1.4.0",
+ "{\"properties\":{\"altitude\":{\"type\":\"integer\"}}}");
+ when(repo.findByName("dict")).thenReturn(Optional.of(d));
+ var req = new PatchSchemaRequest("1.4.0", "mass",
+ OM.readTree("{\"description\":\"x\"}"));
+ assertThatThrownBy(() -> service.patchSchema("dict", req))
+ .isInstanceOfSatisfying(OrdinisException.class,
+ e -> assertThat(e.getCode()).isEqualTo("field_not_found"));
+ }
+
+ @Test
+ void patch_breakingKey_422() throws Exception {
+ var d = def("dict", "1.4.0",
+ "{\"properties\":{\"altitude\":{\"type\":\"integer\"}}}");
+ when(repo.findByName("dict")).thenReturn(Optional.of(d));
+ var req = new PatchSchemaRequest("1.4.0", "altitude",
+ OM.readTree("{\"type\":\"string\"}"));
+ assertThatThrownBy(() -> service.patchSchema("dict", req))
+ .isInstanceOfSatisfying(OrdinisException.class,
+ e -> assertThat(e.getCode()).isEqualTo("breaking_change_requires_draft"));
+ }
+
+ @Test
+ void patch_descriptionUpdate_bumpsPatchVersion() throws Exception {
+ var d = def("dict", "1.4.0",
+ "{\"properties\":{\"altitude\":{\"type\":\"integer\"}}}");
+ when(repo.findByName("dict")).thenReturn(Optional.of(d));
+ var req = new PatchSchemaRequest("1.4.0", "altitude",
+ OM.readTree("{\"description\":{\"ru\":\"Высота орбиты\",\"en\":\"Altitude\"}}"));
+
+ var result = service.patchSchema("dict", req);
+
+ assertThat(result.newVersion()).isEqualTo("1.4.1");
+ JsonNode altitude = result.schemaJson().get("properties").get("altitude");
+ assertThat(altitude.get("description").get("ru").asText()).isEqualTo("Высота орбиты");
+ }
+
+ @Test
+ void patch_minimumTighter_422() throws Exception {
+ var d = def("dict", "1.4.0",
+ "{\"properties\":{\"altitude\":{\"type\":\"integer\",\"minimum\":0}}}");
+ when(repo.findByName("dict")).thenReturn(Optional.of(d));
+ var req = new PatchSchemaRequest("1.4.0", "altitude",
+ OM.readTree("{\"minimum\":100}"));
+ assertThatThrownBy(() -> service.patchSchema("dict", req))
+ .isInstanceOfSatisfying(OrdinisException.class,
+ e -> assertThat(e.getCode()).isEqualTo("validation_failed"));
+ }
+
+ @Test
+ void patch_minimumExpanded_ok() throws Exception {
+ var d = def("dict", "1.4.0",
+ "{\"properties\":{\"altitude\":{\"type\":\"integer\",\"minimum\":100}}}");
+ when(repo.findByName("dict")).thenReturn(Optional.of(d));
+ var req = new PatchSchemaRequest("1.4.0", "altitude",
+ OM.readTree("{\"minimum\":0}"));
+
+ var result = service.patchSchema("dict", req);
+
+ assertThat(result.newVersion()).isEqualTo("1.4.1");
+ }
+
+ @Test
+ void patch_enumRemove_422() throws Exception {
+ var d = def("dict", "1.4.0", """
+ {"properties":{"status":{"type":"string","enum":["live","draft","archived"]}}}""");
+ when(repo.findByName("dict")).thenReturn(Optional.of(d));
+ var req = new PatchSchemaRequest("1.4.0", "status",
+ OM.readTree("{\"enum\":[\"live\",\"draft\"]}"));
+ assertThatThrownBy(() -> service.patchSchema("dict", req))
+ .isInstanceOfSatisfying(OrdinisException.class,
+ e -> assertThat(e.getCode()).isEqualTo("validation_failed"));
+ }
+
+ @Test
+ void patch_enumAdd_ok() throws Exception {
+ var d = def("dict", "1.4.0",
+ "{\"properties\":{\"status\":{\"type\":\"string\",\"enum\":[\"live\"]}}}");
+ when(repo.findByName("dict")).thenReturn(Optional.of(d));
+ var req = new PatchSchemaRequest("1.4.0", "status",
+ OM.readTree("{\"enum\":[\"live\",\"draft\",\"archived\"]}"));
+
+ var result = service.patchSchema("dict", req);
+
+ assertThat(result.newVersion()).isEqualTo("1.4.1");
+ assertThat(result.schemaJson().get("properties").get("status").get("enum"))
+ .hasSize(3);
+ }
+
+ @Test
+ void bumpPatchVersion_basic() {
+ assertThat(SchemaPatchService.bumpPatchVersion("1.4.0")).isEqualTo("1.4.1");
+ assertThat(SchemaPatchService.bumpPatchVersion("0.0.99")).isEqualTo("0.0.100");
+ assertThat(SchemaPatchService.bumpPatchVersion("2.0.0")).isEqualTo("2.0.1");
+ }
+
+ @Test
+ void bumpPatchVersion_nonSemver() {
+ assertThat(SchemaPatchService.bumpPatchVersion("v1")).isEqualTo("v1.1");
+ assertThat(SchemaPatchService.bumpPatchVersion("")).isEqualTo("1.0.1");
+ assertThat(SchemaPatchService.bumpPatchVersion(null)).isEqualTo("1.0.1");
+ }
+
+ private static DictionaryDefinition def(String name, String version, String schemaJson) {
+ JsonNode schema;
+ try {
+ schema = OM.readTree(schemaJson);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ var d = new DictionaryDefinition(UUID.randomUUID(), name, DataScope.PUBLIC, schema);
+ d.setSchemaVersion(version);
+ return d;
+ }
+}