31#include <welder/vocabulary.hpp>
41 "How a validation finding affects the file's fitness for the "
45 "Suspicious, but real client files ship it; the file "
47 Error [[=welder::doc(
"The client would misread or crash on a file written "
54 "One validate() finding: where it is, how bad it is, and what "
58 "Whether the client would misread the file (error) or merely "
59 "find it unusual (warning).")]]
62 [[=welder::doc(R
"(Member path from the validated entity, e.g.
63 "groups[3].body.indices".)")]]
66 [[=welder::doc("What is wrong, with the numbers involved.")]]
75 Everything one validate() pass found, in member order. Validation is
76 linter-style — it never stops at the first finding, so a broken entity
77 reports every violated contract at once. A report is ok() when it holds
78 no errors; warnings mark states real client files ship.)")
92 [[=welder::mark::exclude]]
98 _issues.push_back({severity, std::move(path), std::move(message)});
103 [[nodiscard]] [[=welder::mark::exclude]]
108 =welder::doc(
"Whether findings were dropped because the report hit its "
110 bool truncated()
const {
return _truncated; }
115 [[=welder::mark::exclude]]
116 void addError(std::string path, std::string message) {
123 [[=welder::mark::exclude]]
124 void addWarning(std::string path, std::string message) {
130 =welder::doc(
"Whether the entity is fit to write: no error-severity "
131 "findings (warnings do not fail a report).")]]
133 return std::ranges::none_of(_issues, [](
const ValidationIssue& issue) {
139 [[=welder::getter, =welder::doc(
"The number of error-severity findings.")]]
140 std::size_t errorCount()
const {
141 return static_cast<std::size_t
>(
146 [[=welder::getter, =welder::doc(
"The number of warning-severity findings.")]
148 std::size_t warningCount()
const {
149 return _issues.size() - errorCount();
154 =welder::doc(
"Every finding, in the order the walk recorded them.")]]
155 const std::vector<ValidationIssue>& issues()
const {
return _issues; }
159 =welder::doc(
"The total finding count (errors and warnings).")]]
160 std::size_t size()
const {
return _issues.size(); }
169 [[=welder::mark::exclude]]
170 void prefixFrom(std::size_t mark, std::string_view prefix) {
171 for (std::size_t i = mark; i < _issues.size(); ++i) {
173 issue.
path = issue.path.empty()
174 ? std::string{prefix}
175 : issue.path.front() ==
'['
176 ? std::format(
"{}{}", prefix, issue.path)
177 : std::format(
"{}.{}", prefix, issue.path);
186 [[nodiscard]] [[=welder::mark::exclude]]
190 constexpr std::size_t maxListed = 8;
191 std::string message =
193 "entity fails validation with {} error(s), {} warning(s){}:",
195 warningCount(), _truncated ?
" (report truncated)" :
"");
196 std::size_t listed = 0;
198 if (listed == maxListed) {
199 message += std::format(
"\n ... and {} more",
200 _issues.size() - listed);
203 message += std::format(
"\n {}: {}: {}",
207 issue.path, issue.message);
216 std::vector<ValidationIssue> _issues;
217 bool _truncated =
false;
223 template <
typename E>
225 { E::Version } -> std::convertible_to<ClientVersion>;
231 template <
typename T>
233 typename T::value_type;
234 { value.size() } -> std::convertible_to<std::size_t>;
235 value[std::size_t{0}];
243 template <
typename T>
245 { T::ValidationSlots } -> std::convertible_to<bool>;
246 } && T::ValidationSlots;
250 template <
typename T>
253 value.validateExtra(report);
258 for (std::meta::info spec :
269 if (!std::meta::annotations_of_with_type(member, spec).empty())
286 template <
typename T>
290 else if constexpr (std::is_trivially_copyable_v<T>)
294 else if constexpr (std::is_class_v<T> && !
isStdType<T>()) {
316 template <
typename Values>
318 std::size_t targetCount,
319 std::string_view member,
320 std::string_view target,
322 constexpr std::size_t maxReported = 8;
324 for (std::size_t i = 0; i < values.size(); ++i)
325 if (
static_cast<std::size_t
>(values[i]) >= targetCount)
326 if (++bad <= maxReported)
327 report.
addError(std::format(
"{}[{}]", member, i),
329 "index {} out of range: {} holds {} element(s)",
330 values[i], target, targetCount));
331 if (bad > maxReported)
332 report.
addError(std::string{member},
334 "... and {} more out-of-range indices into {}",
335 bad - maxReported, target));
343 template <std::
integral T>
345 if constexpr (std::is_signed_v < T >)
348 return value == std::numeric_limits < T > ::max();
359 template <
typename Values>
361 std::size_t targetCount,
362 std::string_view member,
363 std::string_view target,
365 constexpr std::size_t maxReported = 8;
367 for (std::size_t i = 0; i < values.size(); ++i) {
370 if (
static_cast<std::size_t
>(values[i]) >= targetCount)
371 if (++bad <= maxReported)
372 report.
addError(std::format(
"{}[{}]", member, i),
374 "index {} out of range: {} holds {} element(s)",
375 values[i], target, targetCount));
377 if (bad > maxReported)
378 report.
addError(std::string{member},
380 "... and {} more out-of-range indices into {}",
381 bad - maxReported, target));
394 std::size_t targetCount,
395 std::string_view member,
396 std::string_view target,
398 if (count == 0 || count * scale == targetCount)
400 report.
addError(std::string{member},
402 ? std::format(
"count {} != {} count {}", count, target,
404 : std::format(
"count {} x {} != {} count {}", count,
409 template <ClientVersion V,
typename T>
419 template <ClientVersion V,
typename T>
422 template for (
constexpr auto m : Members) {
424 constexpr const char* ident = std::define_static_string(
425 std::meta::identifier_of(m));
426 using M = [:std::meta::type_of(m):];
427 const M& member = value.[:m:];
431 static_assert(std::is_integral_v<M>,
432 "expected_value applies to integral members");
433 if (
static_cast<std::uint64_t
>(member) != ev->value)
436 std::format(
"value {} != required {}", member, ev->value));
440 static_assert(
requires { member.empty(); },
441 "nonempty applies to members with observable emptiness")
444 report.
addError(ident,
"must not be empty");
450 "count_exactly applies to sequence members");
451 if (!member.empty() && member.size() != ce->count)
453 "count {} != required {}", member.size(),
457 if constexpr (
constexpr auto cmo =
annotation<
461 "count_multiple_of applies to sequence members");
462 if (!member.empty() && member.size() % cmo->divisor != 0)
464 "count {} is not a multiple of {}",
465 member.size(), cmo->divisor));
471 static_assert(sibling != std::meta::info{},
472 "count_matches names no member of this entity");
473 using S = [:std::meta::type_of(sibling):];
475 "count_matches sibling must be a sequence");
477 "count_matches applies to sequence members");
478 const S& target = value.[:sibling:];
482 for (std::size_t slot = 0; slot < member.size(); ++slot)
485 std::format(
"{}[{}]", ident, slot),
499 for (std::size_t i = 0; i < member.size() && i < target.size();
501 if (member[i].size() != target[i].size())
502 report.
addError(std::format(
"{}[{}]", ident, i),
504 "count {} != {}[{}] count {}",
506 cm->view(), i, target[i].size()));
513 IsVectorV<M> && std::is_integral_v < typename M::value_type >,
514 "indexes applies to integral vector members");
516 static_assert(sibling != std::meta::info{},
517 "indexes names no member of this entity");
518 using S = [:std::meta::type_of(sibling):];
520 "indexes sibling must be a sequence");
525 if constexpr (
constexpr auto ixo =
annotation<
529 IsVectorV<M> && std::is_integral_v < typename M::value_type >,
530 "indexes_optional applies to integral vector members");
532 static_assert(sibling != std::meta::info{},
533 "indexes_optional names no member of this entity");
534 using S = [:std::meta::type_of(sibling):];
536 "indexes_optional sibling must be a sequence");
545 for (std::size_t i = 0; i < member.size() && !report.
full(); ++
547 const std::size_t mark = report.size();
549 report.prefixFrom(mark, std::format(
"{}[{}]", ident, i));
553 const std::size_t mark = report.size();
555 report.prefixFrom(mark, ident);
569 template <ClientVersion V,
typename T>
574 for (std::size_t i = 0; i < value.size() && !report.
full(); ++i) {
575 const std::size_t mark = report.size();
577 report.prefixFrom(mark, std::format(
"[{}]", i));
588 value.validateExtra(report);
596 template <VersionedEntity E>
Client version identity, the flavor axis that separates a client's CONTENT version from the engine ge...
The reflection helpers every format engine shares: reading a member's annotations,...
The error-handling vocabulary: ErrorCode, Error and the Result<T> alias every fallible wowlib operati...
std::expected< T, Error > Result
Every fallible wowlib operation returns Result<T>; bindings translate the error branch into a target-...
std::unexpected< Error > makeError(ErrorCode code, std::string message, std::uint32_t nativeError=0)
Shorthand for constructing the error branch of a Result.
@ InvalidEntityState
An entity's members disagree (e.g.
A wowlib operation failure: a machine-readable code, a human-readable message, and the originating na...