载入中...
搜索中...
未找到
DefinitionTarget.cpp
浏览该文件的文档.
3
4#include <algorithm>
5#include <cctype>
6#include <iterator>
7#include <set>
8#include <utility>
9
11namespace {
12
13const EditorValue* field(const EditorValue& value, const char* key) {
14 const auto* object = value.getIf<EditorValue::Object>();
15 if (!object) return nullptr;
16 const auto found = object->find(key);
17 return found == object->end() ? nullptr : &found->second;
18}
19
20bool looksLikeJsonValue(const std::string& json) {
21 const auto first = std::find_if_not(json.begin(), json.end(), [](unsigned char c) { return std::isspace(c); });
22 if (first == json.end()) return false;
23 const auto last = std::find_if_not(json.rbegin(), json.rend(), [](unsigned char c) { return std::isspace(c); });
24 const char open = *first;
25 const char close = *last;
26 return (open == '{' && close == '}') || (open == '[' && close == ']');
27}
28
29} // namespace
30
31DefinitionDocument::DefinitionDocument(std::string type, std::string id, int version)
32 : type_(std::move(type)), id_(std::move(id)), targetId_("definition:" + type_ + ":" + id_),
33 version_(version > 0 ? version : 1) {}
34
36 return {TargetId(targetId_), "definition-document", revisionValue(), false, {}};
37}
38
40 if (operation.target != TargetId(targetId_) || operation.type != "definition.field.set.v1")
41 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.definition.operation-mismatch"),
42 "Definition field operation targets another document or type");
43 const auto* pathValue = field(operation.payload, "field");
44 const auto* assigned = field(operation.payload, "value");
45 const auto* presentValue = field(operation.payload, "present");
46 const auto* path = pathValue ? pathValue->getIf<std::string>() : nullptr;
47 const auto* present = presentValue ? presentValue->getIf<bool>() : nullptr;
48 auto parsed = editorValueFromJson(json_);
49 auto* object = parsed.ok() ? parsed.value().getIf<EditorValue::Object>() : nullptr;
50 if (!path || path->empty() || !assigned || !present || !object)
51 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.definition.invalid-field-operation"),
52 "Definition field operation requires an object payload and field name");
53 if (*present) (*object)[*path] = *assigned;
54 else object->erase(*path);
55 json_ = editorValueToJson(parsed.value());
57 widenDirty(0, 0);
58 return eve::editing::applied<void>();
59}
60
62 if (!looksLikeJsonValue(json))
63 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.definition.invalid-json-shape"),
64 "Definition payload must be a JSON object or array");
65 json_ = std::move(json);
67 widenDirty(0, 0);
68 return eve::editing::applied<void>();
69}
70
72 if (version <= 0)
73 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.definition.invalid-version"),
74 "Definition schema version must be positive");
75 version_ = version;
77 widenDirty(0, 0);
78 return eve::editing::applied<void>();
79}
80
81Result<void> DefinitionDocument::setReferences(std::vector<DefinitionReferenceField> references) {
82 std::set<std::string> paths;
83 for (const DefinitionReferenceField& reference : references)
84 if (reference.path.empty() || !paths.insert(reference.path).second)
85 return eve::editing::failed<void>(EditorStatus::Rejected,
86 RuleId("editor.definition.invalid-reference-path"),
87 "Definition reference paths must be unique and non-empty");
88 references_ = std::move(references);
90 widenDirty(0, 0);
91 return eve::editing::applied<void>();
92}
93
95 const EditorValue& value) const {
96 if (fieldName.empty())
97 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.definition.empty-field"),
98 "Definition field name must not be empty");
99 auto parsed = editorValueFromJson(json_);
100 const auto* object = parsed.ok() ? parsed.value().getIf<EditorValue::Object>() : nullptr;
101 if (!object)
102 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported,
103 RuleId("editor.definition.form-object-required"),
104 "Schema form editing requires an object definition payload");
105 const auto current = object->find(fieldName);
106 const EditorValue before = current == object->end() ? EditorValue{} : current->second;
108 operation.type = "definition.field.set.v1";
109 operation.inverseType = operation.type;
110 operation.target = TargetId(targetId_);
111 operation.payload = EditorValue::Object{{"field", fieldName}, {"value", value}, {"present", true}};
112 operation.inverse = EditorValue::Object{{"field", fieldName}, {"value", before},
113 {"present", current != object->end()}};
114 operation.hasInverse = true;
115 operation.affectedObjects.push_back({TargetId(targetId_), id_, 0});
116 operation.affectedProperties.push_back(fieldName);
117 operation.mergeKey = targetId_ + ":" + fieldName;
118 return eve::editing::applied<DomainOperation>(std::move(operation));
119}
120
121std::vector<EditorDiagnostic> DefinitionDocument::validate(const SchemaValidator& schema,
122 const ReferenceResolver& references) const {
123 std::vector<EditorDiagnostic> diagnostics;
124 if (type_.empty() || id_.empty() || version_ <= 0)
125 diagnostics.push_back(eve::editing::ruleDiagnostic(
127 RuleId("editor.definition.invalid-identity"), DiagnosticSeverity::Error,
128 "Definition type, id and positive version are required"));
129 if (!looksLikeJsonValue(json_))
130 diagnostics.push_back(eve::editing::ruleDiagnostic(
131 eve::DiagnosticCode::InvalidArgument, RuleId("editor.definition.invalid-json-shape"),
132 DiagnosticSeverity::Error, "Definition payload must be a JSON object or array"));
133 if (schema) {
134 auto schemaDiagnostics = schema(type_, version_, json_);
135 diagnostics.insert(diagnostics.end(), std::make_move_iterator(schemaDiagnostics.begin()),
136 std::make_move_iterator(schemaDiagnostics.end()));
137 }
138 for (const DefinitionReferenceField& reference : references_) {
139 if (reference.type.empty() || reference.id.empty()) {
140 if (reference.required)
141 diagnostics.push_back(eve::editing::ruleDiagnostic(
143 RuleId("editor.definition.reference-required"), DiagnosticSeverity::Error,
144 "Required reference is empty: " + reference.path));
145 continue;
146 }
147 if (references && !references(reference.type, reference.id))
148 diagnostics.push_back(eve::editing::ruleDiagnostic(
149 eve::DiagnosticCode::NotFound, RuleId("editor.definition.reference-not-found"),
150 DiagnosticSeverity::Error,
151 "Definition reference could not be resolved: " + reference.path + " -> " +
152 reference.type + ":" + reference.id));
153 }
154 return diagnostics;
155}
156
158 EditorValue::Array references;
159 for (const DefinitionReferenceField& reference : references_)
160 references.emplace_back(EditorValue::Object{{"path", reference.path}, {"type", reference.type},
161 {"id", reference.id}, {"required", reference.required}});
162 return EditorValue::Object{{"schemaVersion", int64_t{1}}, {"type", type_}, {"id", id_},
163 {"definitionVersion", int64_t{version_}}, {"json", json_},
164 {"references", std::move(references)}};
165}
166
167Result<void> DefinitionDocument::loadSnapshot(const EditorValue& snapshot) {
168 const auto* schemaValue = field(snapshot, "schemaVersion");
169 const auto* typeValue = field(snapshot, "type");
170 const auto* idValue = field(snapshot, "id");
171 const auto* versionValue = field(snapshot, "definitionVersion");
172 const auto* jsonValue = field(snapshot, "json");
173 const auto* referencesValue = field(snapshot, "references");
174 const auto* schema = schemaValue ? schemaValue->getIf<int64_t>() : nullptr;
175 const auto* type = typeValue ? typeValue->getIf<std::string>() : nullptr;
176 const auto* id = idValue ? idValue->getIf<std::string>() : nullptr;
177 const auto* version = versionValue ? versionValue->getIf<int64_t>() : nullptr;
178 const auto* json = jsonValue ? jsonValue->getIf<std::string>() : nullptr;
179 const auto* references = referencesValue ? referencesValue->getIf<EditorValue::Array>() : nullptr;
180 if (!schema || *schema != 1 || !type || type->empty() || !id || id->empty() || !version || *version <= 0 ||
181 !json || !looksLikeJsonValue(*json) || !references)
182 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.definition.invalid-snapshot"),
183 "Definition snapshot is invalid or unsupported");
184 std::vector<DefinitionReferenceField> parsed;
185 for (const EditorValue& value : *references) {
186 const auto* pathValue = field(value, "path");
187 const auto* refTypeValue = field(value, "type");
188 const auto* refIdValue = field(value, "id");
189 const auto* requiredValue = field(value, "required");
190 const auto* path = pathValue ? pathValue->getIf<std::string>() : nullptr;
191 const auto* refType = refTypeValue ? refTypeValue->getIf<std::string>() : nullptr;
192 const auto* refId = refIdValue ? refIdValue->getIf<std::string>() : nullptr;
193 const auto* required = requiredValue ? requiredValue->getIf<bool>() : nullptr;
194 if (!path || !refType || !refId || !required)
195 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.definition.invalid-reference"),
196 "Definition snapshot contains an invalid reference");
197 parsed.push_back({*path, *refType, *refId, *required});
198 }
199 DefinitionDocument candidate(*type, *id, static_cast<int>(*version));
200 if (!candidate.setJson(*json).ok() || !candidate.setReferences(std::move(parsed)).ok())
201 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.definition.invalid-snapshot"),
202 "Definition snapshot could not be applied");
203 type_ = std::move(candidate.type_);
204 id_ = std::move(candidate.id_);
205 version_ = candidate.version_;
206 json_ = std::move(candidate.json_);
207 references_ = std::move(candidate.references_);
208 targetId_ = "definition:" + type_ + ":" + id_;
209 bumpRevision();
210 clearDirtyRegion();
211 return eve::editing::applied<void>();
212}
213
214} // namespace eve::definitions_editing
ActionParameterOperation operation
double value
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
std::uint32_t key
std::int32_t c
std::int32_t first
bool required
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
bool found
double current
std::map< std::string, std::string > paths
bool ok() const noexcept
Whether this result represents a non-failure outcome.
Definition Result.h:480
Move-only operation result carrying either a value or Status.
Definition Result.h:155
UUID-backed identifier adapter for legacy textual boundaries.
Definition Identity.h:314
UI-neutral versioned definition asset and cross-reference model.
const std::string & json() const
Return JSON payload text.
int version() const
Return schema version.
std::vector< EditorDiagnostic > validate(const SchemaValidator &schema, const ReferenceResolver &references) const
Run schema and cross-reference diagnostics without mutating content.
EditorValue snapshotValue() const
Capture deterministic content for DocumentService persistence.
Result< void > setReferences(std::vector< DefinitionReferenceField > references)
Replace editor-extracted cross-reference fields.
Result< void > setVersion(int version)
Replace the schema version with a positive value.
DefinitionDocument(std::string type, std::string id, int version=1)
Construct an empty JSON-object definition with stable identity.
std::function< std::vector< EditorDiagnostic >(const std::string &type, int version, const std::string &json)> SchemaValidator
Result< DomainOperation > makeSetField(const std::string &field, const EditorValue &value) const
Plan a reversible top-level field assignment from a schema form.
TargetDescriptor describe() const override
Describe.
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
Result< void > setJson(std::string json)
Atomically replace canonical payload text after basic JSON-shape validation.
std::function< bool(const std::string &type, const std::string &id)> ReferenceResolver
void bumpRevision()
Advance the revision without touching the dirty region.
Revision revisionValue() const noexcept
Return the revision as the shared protocol revision type.
void widenDirty()
Mark an unknown area dirty without advancing the revision.
Deterministic owning value tree shared by authoring hosts.
std::vector< Value > Array
std::map< std::string, Value > Object
const T * getIf() const
Return an immediately borrowed typed value.
eve::editing::Value EditorValue
std::string editorValueToJson(const EditorValue &value)
Serialize an EditorValue to deterministic compact JSON.
StrongId< TargetIdTag > TargetId
Definition EditingIds.h:57
Result< EditorValue > editorValueFromJson(const std::string &json)
Parse JSON into the pointer-free EditorValue protocol tree.
StrongId< RuleIdTag > RuleId
Definition EditingIds.h:60
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
const EditorValue * field(const EditorValue &value, const char *name)
Stable cross-definition reference exposed to picker and validation hosts.
DomainOperation public API.
Stable discovery metadata exposed by every editable target.