载入中...
搜索中...
未找到
EvaManifest.cpp
浏览该文件的文档.
1#include "asset/EvaManifest.h"
2
3#include <algorithm>
4#include <set>
5
6namespace eve::asset {
7namespace {
8
9const Value* required(const Value::Object& object, std::string_view key) {
10 const auto found = object.find(std::string(key));
11 return found == object.end() ? nullptr : &found->second;
12}
13
14Result<AssetRef> parseAssetRef(const Value& value, std::string path) {
15 if (!value.isString())
17 DiagnosticCode::ParseError, "asset reference must be a string", path, {}, "asset.eva.manifest"));
18 auto parsed = AssetRef::parse(value.asString());
19 if (!parsed)
21 DiagnosticCode::ParseError, "asset reference is not canonical", path, {}, "asset.eva.manifest"));
22 return Result<AssetRef>::success(std::move(parsed).takeValue());
23}
24
25Result<std::string> parseString(const Value::Object& object, std::string_view key,
26 std::string_view pathPrefix) {
27 const Value* value = required(object, key);
28 const std::string path = std::string(pathPrefix) + std::string(key);
29 if (!value || !value->isString())
31 DiagnosticCode::ParseError, "required manifest field must be a string", path, {}, "asset.eva.manifest"));
32 if (value->asString().empty())
34 "required manifest string must not be empty", path, {},
35 "asset.eva.manifest"));
36 return Result<std::string>::success(value->asString());
37}
38
39Result<SchemaVersion> parseVersion(const Value::Object& object, std::string_view key,
40 std::string_view pathPrefix) {
41 const Value* value = required(object, key);
42 const std::string path = std::string(pathPrefix) + std::string(key);
43 if (!value || !value->isInt64() || value->asInt() <= 0)
45 DiagnosticCode::ParseError, "schema version must be a positive integer", path, {}, "asset.eva.manifest"));
46 return Result<SchemaVersion>::success(SchemaVersion(static_cast<std::uint64_t>(value->asInt())));
47}
48
49Result<EvaDependencyKind> parseDependencyKind(const Value& value, std::string path) {
50 if (!value.isString())
52 DiagnosticCode::ParseError, "dependency kind must be a string", path, {}, "asset.eva.manifest"));
53 const std::string& text = value.asString();
61 path, {}, "asset.eva.manifest"));
62}
63
64Value assetRefValue(const AssetRef& reference) { return Value(reference.format()); }
65
66bool canonicalSha256(std::string_view hash) {
67 if (hash.size() != 71 || !hash.starts_with("sha256:")) return false;
68 return std::all_of(hash.begin() + 7, hash.end(), [](unsigned char character) {
69 return (character >= '0' && character <= '9') || (character >= 'a' && character <= 'f');
70 });
71}
72
73bool canonicalExpectedType(std::string_view type) {
74 const auto separator = type.rfind('/');
75 if (separator == std::string_view::npos || separator == 0 || separator + 1 == type.size()) return false;
76 if (type.substr(0, separator).find('/') != std::string_view::npos) return false;
77 if (type[separator + 1] == '0') return false;
78 return std::all_of(type.begin() + static_cast<std::ptrdiff_t>(separator + 1), type.end(),
79 [](unsigned char value) { return value >= '0' && value <= '9'; });
80}
81
82Result<Value::Object> parseFallback(const Value::Object& entry, EvaDependencyKind kind,
83 const std::string& prefix) {
84 const Value* value = required(entry, "fallback");
85 if (!value) {
88 DiagnosticCode::InvalidArgument, "runtime-optional dependency requires an explicit fallback policy",
89 prefix + "fallback", {}, "asset.eva.manifest"));
91 }
92 const auto* object = value->getIf<Value::Object>();
93 const Value* behavior = object ? required(*object, "behavior") : nullptr;
94 const Value* observableCode = object ? required(*object, "observableCode") : nullptr;
95 if (!object || !behavior || !behavior->isString() ||
96 (behavior->asString() != "omit-feature" && behavior->asString() != "use-default" &&
97 behavior->asString() != "use-asset") || !observableCode ||
98 !observableCode->isString() || observableCode->asString().empty())
100 DiagnosticCode::ParseError, "fallback requires a supported behavior and non-empty observableCode",
101 prefix + "fallback", {}, "asset.eva.manifest"));
102 const Value* asset = required(*object, "asset");
103 if (behavior->asString() == "use-asset") {
104 if (!asset)
106 Diagnostic::error(DiagnosticCode::ParseError, "use-asset fallback requires asset",
107 prefix + "fallback.asset", {}, "asset.eva.manifest"));
108 auto parsed = parseAssetRef(*asset, prefix + "fallback.asset");
109 if (!parsed) return Result<Value::Object>::failure(parsed.status());
110 } else if (asset) {
112 "only use-asset fallback may declare asset",
113 prefix + "fallback.asset", {}, "asset.eva.manifest"));
114 }
115 return Result<Value::Object>::success(*object);
116}
117
118} // namespace
119
120std::string_view evaDependencyKindName(EvaDependencyKind kind) noexcept {
121 switch (kind) {
122 case EvaDependencyKind::RuntimeRequired: return "runtime-required";
123 case EvaDependencyKind::RuntimeOptional: return "runtime-optional";
124 case EvaDependencyKind::Build: return "build";
125 case EvaDependencyKind::Editor: return "editor";
126 case EvaDependencyKind::Source: return "source";
127 case EvaDependencyKind::Platform: return "platform";
128 }
129 return "unknown";
130}
131
132Result<EvaManifest> parseEvaManifest(std::string_view json) {
133 auto parsed = Value::fromJson(json);
134 if (!parsed) return Result<EvaManifest>::failure(parsed.status());
135 Value root = std::move(parsed).takeValue();
136 const auto* object = root.getIf<Value::Object>();
137 if (!object)
139 DiagnosticCode::ParseError, "manifest root must be an object", "$", {}, "asset.eva.manifest"));
140
141 auto schema = parseString(*object, "schema", "$");
142 if (!schema) return Result<EvaManifest>::failure(schema.status());
143 if (std::move(schema).takeValue() != EvaManifest::kSchema)
145 DiagnosticCode::Unsupported, "unsupported manifest schema", "$.schema", {}, "asset.eva.manifest"));
146
147 auto envelopeVersion = parseVersion(*object, "schemaVersion", "$");
148 if (!envelopeVersion) return Result<EvaManifest>::failure(envelopeVersion.status());
149 if (std::move(envelopeVersion).takeValue().value() != EvaManifest::kVersion)
151 "unsupported manifest version", "$.schemaVersion", {},
152 "asset.eva.manifest"));
153
154 auto packageName = parseString(*object, "packageName", "$");
156 auto packageVersion = parseString(*object, "packageVersion", "$");
158 auto packageIdText = parseString(*object, "packageId", "$");
159 if (!packageIdText) return Result<EvaManifest>::failure(packageIdText.status());
160 auto packageId = PersistentId::parse(std::move(packageIdText).takeValue());
161 if (!packageId || packageId->isNil())
163 "packageId must be a non-nil canonical UUID",
164 "$.packageId", {}, "asset.eva.manifest"));
165
166 const Value* unknownPolicy = required(*object, "unknownFields");
167 if (!unknownPolicy || !unknownPolicy->isString() || unknownPolicy->asString() != "preserve")
169 "eva v1 requires unknownFields=preserve",
170 "$.unknownFields", {}, "asset.eva.manifest"));
171
172 EvaManifest manifest;
173 manifest.packageId = *packageId;
174 manifest.packageName = std::move(packageName).takeValue();
175 manifest.packageVersion = std::move(packageVersion).takeValue();
176
177 const Value* assets = required(*object, "assets");
178 const auto* assetArray = assets ? assets->getIf<Value::Array>() : nullptr;
179 if (!assetArray)
181 "$.assets", {}, "asset.eva.manifest"));
182 std::set<PersistentId> identities;
183 for (std::size_t index = 0; index < assetArray->size(); ++index) {
184 const std::string prefix = "$.assets[" + std::to_string(index) + "].";
185 const auto* entry = (*assetArray)[index].getIf<Value::Object>();
186 if (!entry)
188 Diagnostic::error(DiagnosticCode::ParseError, "asset entry must be an object",
189 prefix.substr(0, prefix.size() - 1), {}, "asset.eva.manifest"));
190 const Value* refValue = required(*entry, "asset");
191 if (!refValue)
193 prefix + "asset", {}, "asset.eva.manifest"));
194 auto reference = parseAssetRef(*refValue, prefix + "asset");
195 if (!reference) return Result<EvaManifest>::failure(reference.status());
196 AssetRef assetRef = std::move(reference).takeValue();
197 if (!identities.emplace(assetRef.id()).second)
199 prefix + "asset", {}, "asset.eva.manifest"));
200 auto type = parseString(*entry, "type", prefix);
201 if (!type) return Result<EvaManifest>::failure(type.status());
202 auto version = parseVersion(*entry, "schemaVersion", prefix);
203 if (!version) return Result<EvaManifest>::failure(version.status());
204 auto definition = parseString(*entry, "definition", prefix);
205 if (!definition) return Result<EvaManifest>::failure(definition.status());
206 auto contentHash = parseString(*entry, "contentHash", prefix);
207 if (!contentHash) return Result<EvaManifest>::failure(contentHash.status());
208 if (!canonicalSha256(contentHash.value()))
210 "contentHash must be canonical lowercase sha256",
211 prefix + "contentHash", {}, "asset.eva.manifest"));
212
213 std::vector<std::string> tags;
214 if (const Value* tagValue = required(*entry, "tags")) {
215 const auto* tagArray = tagValue->getIf<Value::Array>();
216 if (!tagArray)
218 DiagnosticCode::ParseError, "tags must be an array", prefix + "tags", {}, "asset.eva.manifest"));
219 for (std::size_t tagIndex = 0; tagIndex < tagArray->size(); ++tagIndex) {
220 if (!(*tagArray)[tagIndex].isString())
222 Diagnostic::error(DiagnosticCode::ParseError, "tag must be a string",
223 prefix + "tags[" + std::to_string(tagIndex) + "]", {}, "asset.eva.manifest"));
224 tags.push_back((*tagArray)[tagIndex].asString());
225 }
226 }
227 manifest.assets.push_back({std::move(assetRef), std::move(type).takeValue(),
228 std::move(version).takeValue(), std::move(definition).takeValue(),
229 std::move(contentHash).takeValue(), std::move(tags)});
230 }
231
232 const Value* dependencies = required(*object, "dependencies");
233 const auto* dependencyArray = dependencies ? dependencies->getIf<Value::Array>() : nullptr;
234 if (!dependencyArray)
236 DiagnosticCode::ParseError, "dependencies must be an array", "$.dependencies", {}, "asset.eva.manifest"));
237 for (std::size_t index = 0; index < dependencyArray->size(); ++index) {
238 const std::string prefix = "$.dependencies[" + std::to_string(index) + "].";
239 const auto* entry = (*dependencyArray)[index].getIf<Value::Object>();
240 if (!entry)
242 Diagnostic::error(DiagnosticCode::ParseError, "dependency must be an object",
243 prefix.substr(0, prefix.size() - 1), {}, "asset.eva.manifest"));
244 const Value* fromValue = required(*entry, "from");
245 const Value* toValue = required(*entry, "to");
246 const Value* kindValue = required(*entry, "kind");
247 if (!fromValue || !toValue || !kindValue)
249 DiagnosticCode::ParseError, "dependency requires from, to and kind", prefix, {}, "asset.eva.manifest"));
250 auto from = parseAssetRef(*fromValue, prefix + "from");
251 if (!from) return Result<EvaManifest>::failure(from.status());
252 auto to = parseAssetRef(*toValue, prefix + "to");
253 if (!to) return Result<EvaManifest>::failure(to.status());
254 auto kind = parseDependencyKind(*kindValue, prefix + "kind");
255 if (!kind) return Result<EvaManifest>::failure(kind.status());
256 std::string path;
257 if (const Value* pathValue = required(*entry, "path")) {
258 if (!pathValue->isString())
260 "dependency path must be a string",
261 prefix + "path", {}, "asset.eva.manifest"));
262 path = pathValue->asString();
263 }
264 Value::Object predicate;
265 if (const Value* predicateValue = required(*entry, "predicate")) {
266 const auto* predicateObject = predicateValue->getIf<Value::Object>();
267 if (!predicateObject)
269 "dependency predicate must be an object",
270 prefix + "predicate", {}, "asset.eva.manifest"));
271 predicate = *predicateObject;
272 }
273 if (kind.value() == EvaDependencyKind::Platform && predicate.empty())
275 "platform dependency requires a predicate",
276 prefix + "predicate", {}, "asset.eva.manifest"));
277 std::string expectedType;
278 if (const Value* expected = required(*entry, "expectedType")) {
279 if (!expected->isString() || !canonicalExpectedType(expected->asString()))
281 Diagnostic::error(DiagnosticCode::ParseError, "expectedType must be a canonical type/version pair",
282 prefix + "expectedType", {}, "asset.eva.manifest"));
283 expectedType = expected->asString();
284 }
285 auto fallback = parseFallback(*entry, kind.value(), prefix);
286 if (!fallback) return Result<EvaManifest>::failure(fallback.status());
287 manifest.dependencies.push_back({std::move(from).takeValue(), std::move(to).takeValue(),
288 std::move(kind).takeValue(), std::move(path), std::move(predicate),
289 std::move(expectedType), std::move(fallback).takeValue()});
290 }
291
292 if (const Value* entrypoints = required(*object, "entrypoints")) {
293 const auto* entries = entrypoints->getIf<Value::Object>();
294 if (!entries)
296 "entrypoints must be an object", "$.entrypoints", {},
297 "asset.eva.manifest"));
298 for (const auto& [name, referenceValue] : *entries) {
299 auto reference = parseAssetRef(referenceValue, "$.entrypoints." + name);
300 if (!reference) return Result<EvaManifest>::failure(reference.status());
301 manifest.entrypoints.emplace(name, std::move(reference).takeValue());
302 }
303 }
304 if (const Value* provenance = required(*object, "provenance")) {
305 const auto* value = provenance->getIf<Value::Object>();
306 if (!value)
308 DiagnosticCode::ParseError, "provenance must be an object", "$.provenance", {}, "asset.eva.manifest"));
309 manifest.provenance = *value;
310 }
311
312 static const std::set<std::string> known = {"schema", "schemaVersion", "packageId",
313 "packageName", "packageVersion", "unknownFields",
314 "assets", "dependencies", "entrypoints",
315 "provenance"};
316 for (const auto& [key, value] : *object)
317 if (!known.contains(key)) manifest.extensions.emplace(key, value);
318 return Result<EvaManifest>::success(std::move(manifest));
319}
320
322 Value::Object root = manifest.extensions;
323 root["schema"] = Value(std::string(EvaManifest::kSchema));
324 root["schemaVersion"] = Value(static_cast<std::int64_t>(EvaManifest::kVersion));
325 root["packageId"] = Value(manifest.packageId.format());
326 root["packageName"] = Value(manifest.packageName);
327 root["packageVersion"] = Value(manifest.packageVersion);
328 root["unknownFields"] = Value("preserve");
329
330 Value::Array assets;
331 for (const EvaAssetEntry& entry : manifest.assets) {
333 object["asset"] = assetRefValue(entry.asset);
334 object["type"] = Value(entry.type);
335 object["schemaVersion"] = Value(static_cast<std::int64_t>(entry.schemaVersion.value()));
336 object["definition"] = Value(entry.definition);
337 object["contentHash"] = Value(entry.contentHash);
338 Value::Array tags;
339 for (const std::string& tag : entry.tags) tags.emplace_back(tag);
340 object["tags"] = Value(std::move(tags));
341 assets.emplace_back(std::move(object));
342 }
343 root["assets"] = Value(std::move(assets));
344
345 Value::Array dependencies;
346 for (const EvaDependency& dependency : manifest.dependencies) {
348 object["from"] = assetRefValue(dependency.from);
349 object["to"] = assetRefValue(dependency.to);
350 object["kind"] = Value(std::string(evaDependencyKindName(dependency.kind)));
351 if (!dependency.path.empty()) object["path"] = Value(dependency.path);
352 if (!dependency.predicate.empty()) object["predicate"] = Value(dependency.predicate);
353 if (!dependency.expectedType.empty()) object["expectedType"] = Value(dependency.expectedType);
354 if (!dependency.fallback.empty()) object["fallback"] = Value(dependency.fallback);
355 dependencies.emplace_back(std::move(object));
356 }
357 root["dependencies"] = Value(std::move(dependencies));
358
359 Value::Object entrypoints;
360 for (const auto& [name, reference] : manifest.entrypoints) entrypoints[name] = assetRefValue(reference);
361 root["entrypoints"] = Value(std::move(entrypoints));
362 root["provenance"] = Value(manifest.provenance);
363 return Value(std::move(root)).toJson();
364}
365
366} // namespace eve::asset
double value
int root
Definition AnimSmr.cpp:119
std::string packageName
std::string packageVersion
std::string packageId
std::string from
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
std::unordered_map< std::string, QuestRuntime > entries
Canonical manifest model for EVEngine Asset Archive (.eva) version 1.
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
std::uint32_t key
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::string text
TokenKind kind
bool required
std::string name
const std::string * tag
std::string path
Definition PlayHost.cpp:110
bool found
Json object
uint32_t index
Stable asset identity backed by PersistentId.
Definition ResourceRef.h:53
static Result< AssetRef > parse(std::string_view text)
Parse an asset://<canonical UUID> reference.
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
Result< std::string > toJson() const
Serialize this value as deterministic compact JSON.
Definition Value.cpp:67
const std::string & asString() const
Return the string payload; the caller must have checked the kind.
Definition Value.cpp:87
static Result< Value > fromJson(std::string_view json)
Parse one strict JSON value into an owning Value.
Definition Value.cpp:57
std::map< std::string, Value > Object
Definition Value.h:34
bool isString() const noexcept
Return true when this value is a string.
Definition Value.h:95
std::vector< Value > Array
Definition Value.h:33
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
std::string format() const
Formats the ID in lower-case canonical UUID text.
Definition Identity.h:173
static std::optional< Id128 > parse(std::string_view text) noexcept
Parses canonical UUID text.
Definition Identity.h:151
std::string_view evaDependencyKindName(EvaDependencyKind kind) noexcept
Return the stable manifest spelling for a dependency role.
Result< EvaManifest > parseEvaManifest(std::string_view json)
Parse and validate canonical .eva manifest JSON.
EvaDependencyKind
Dependency role recorded by a source asset archive.
Definition EvaManifest.h:21
Result< std::string > serializeEvaManifest(const EvaManifest &manifest)
Serialize a validated manifest as deterministic compact JSON.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
WidgetDesc separator(std::string id)
Horizontal separator line.
Definition Widget.cpp:345
detail::StrongUint64< detail::SchemaVersionTag > SchemaVersion
Persistent data-format version; not a runtime replacement generation.
One typed asset definition in an .eva archive.
Definition EvaManifest.h:34
One directed dependency between stable asset identities.
Definition EvaManifest.h:44
Validated, owning .eva manifest.
Definition EvaManifest.h:62
std::vector< EvaAssetEntry > assets
Definition EvaManifest.h:69
std::vector< EvaDependency > dependencies
Definition EvaManifest.h:70
static constexpr std::string_view kSchema
Definition EvaManifest.h:63
std::string packageVersion
Definition EvaManifest.h:68
static constexpr std::uint64_t kVersion
Definition EvaManifest.h:64
Value::Object extensions
Definition EvaManifest.h:73
Value::Object provenance
Definition EvaManifest.h:72
std::map< std::string, AssetRef > entrypoints
Definition EvaManifest.h:71