activity_sdk_structure_validation.cpp 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. #include <array>
  2. #include "validation_internal.h"
  3. namespace sunrise::state::activity_sdk::validation {
  4. namespace {
  5. /** @return True when one row range stays inside a section of `size` rows. */
  6. [[nodiscard]] constexpr bool range(format::Range value, std::size_t size) noexcept {
  7. return value.first <= size && value.count <= size - value.first;
  8. }
  9. template <typename Row, typename Id>
  10. [[nodiscard]] bool required_ids(std::span<const Row> rows, const Catalog& catalog, Id id) noexcept {
  11. for (const Row& row : rows) {
  12. const std::string_view value = catalog.string(id(row));
  13. if (value.empty() || value.find('\0') != std::string_view::npos) {
  14. return false;
  15. }
  16. }
  17. return true;
  18. }
  19. /** @return True when every directive element has a valid description or progress label. */
  20. [[nodiscard]] bool directive_texts(const Catalog& catalog) noexcept {
  21. for (const format::DirectiveElement& row : catalog.directive_elements()) {
  22. const std::string_view description = catalog.string(row.description);
  23. const std::string_view progress = catalog.string(row.progress);
  24. if ((description.empty() && progress.empty())
  25. || description.find('\0') != std::string_view::npos
  26. || progress.find('\0') != std::string_view::npos) {
  27. return false;
  28. }
  29. }
  30. return true;
  31. }
  32. /** @return True when every activity's row ranges stay inside the sections they index. */
  33. [[nodiscard]] bool activity_ranges(const Catalog& catalog) noexcept {
  34. for (const format::Activity& row : catalog.activities()) {
  35. if (!range(row.aliases, catalog.texts().size())
  36. || !range(row.capabilities, catalog.capabilities().size())
  37. || !range(row.activityRootCandidateTags, catalog.activity_binding_tags().size())
  38. || !range(row.scenarioNameCandidateTags, catalog.activity_binding_tags().size())
  39. || !range(row.evidenceRootTags, catalog.activity_binding_tags().size())
  40. || !range(row.bindingLocators, catalog.activity_binding_locators().size())) {
  41. return false;
  42. }
  43. }
  44. return true;
  45. }
  46. /** @return True when every topology row range stays inside the sections it indexes. */
  47. [[nodiscard]] bool topology_ranges(const Catalog& catalog) noexcept {
  48. for (const format::Scenario& row : catalog.scenarios()) {
  49. if (!range(row.bubbles, catalog.bubbles().size())
  50. || !range(row.states, catalog.states().size())
  51. || !range(row.occurrences, catalog.occurrences().size())) {
  52. return false;
  53. }
  54. }
  55. for (const format::Object& row : catalog.objects()) {
  56. if (!range(row.slots, catalog.slots().size())) {
  57. return false;
  58. }
  59. }
  60. for (const format::Squad& row : catalog.squads()) {
  61. if (!range(row.members, catalog.squad_members().size())
  62. || !range(row.anchors, catalog.squad_anchors().size())) {
  63. return false;
  64. }
  65. }
  66. return true;
  67. }
  68. [[nodiscard]] bool behavior_ranges(const Catalog& catalog) noexcept {
  69. for (const format::BehaviorProgram& row : catalog.behavior_programs()) {
  70. if (!range(row.inputs, catalog.behavior_inputs().size())
  71. || !range(row.channelWrites, catalog.behavior_channel_writes().size())) {
  72. return false;
  73. }
  74. }
  75. return true;
  76. }
  77. /** @return True when every runtime row range stays inside the sections it indexes. */
  78. [[nodiscard]] bool runtime_ranges(const Catalog& catalog) noexcept {
  79. for (const format::RuntimeSchema& row : catalog.runtime_schemas()) {
  80. if (!range(row.fields, catalog.runtime_fields().size())) {
  81. return false;
  82. }
  83. }
  84. for (const format::SobjectRsat& row : catalog.sobject_rsats()) {
  85. if (!range(row.descriptors, catalog.sobject_rsat_descriptors().size())) {
  86. return false;
  87. }
  88. }
  89. return true;
  90. }
  91. /** @return True when every actor command carries a resolvable, unique name. */
  92. [[nodiscard]] bool actor_command_names(const Catalog& catalog) noexcept {
  93. for (const format::ActorCommandDefinition& row : catalog.actor_command_definitions()) {
  94. const bool setFaction = row.effect == format::ActorCommandEffect::setFaction;
  95. const std::array factionNames{
  96. row.factionNoneName, row.factionRemovedName, row.factionHostileToAllName};
  97. for (const format::StringRef reference : factionNames) {
  98. const std::string_view value = catalog.string(reference);
  99. if ((setFaction && (value.empty() || value.find('\0') != std::string_view::npos))
  100. || (!setFaction && (reference.offset != 0 || reference.length != 0))) {
  101. return false;
  102. }
  103. }
  104. }
  105. return true;
  106. }
  107. } // namespace
  108. /** Checks that every section is non-empty, uniquely keyed, and indexed inside its bounds. */
  109. bool structure(const Catalog& catalog) {
  110. return !catalog.activities().empty() && !catalog.scenarios().empty()
  111. && required_ids(catalog.activities(), catalog, [](const auto& row) { return row.id; })
  112. && required_ids(catalog.scenarios(), catalog, [](const auto& row) { return row.id; })
  113. && required_ids(catalog.objects(), catalog, [](const auto& row) { return row.id; })
  114. && required_ids(catalog.slots(), catalog, [](const auto& row) { return row.id; })
  115. && required_ids(catalog.squads(), catalog, [](const auto& row) { return row.id; })
  116. && required_ids(
  117. catalog.actor_message_schemas(), catalog, [](const auto& row) { return row.name; })
  118. && required_ids(catalog.actor_command_definitions(),
  119. catalog,
  120. [](const auto& row) { return row.name; })
  121. && actor_command_names(catalog)
  122. && required_ids(catalog.simulation_event_definitions(),
  123. catalog,
  124. [](const auto& row) { return row.name; })
  125. && required_ids(
  126. catalog.entity_type_definitions(), catalog, [](const auto& row) { return row.name; })
  127. && required_ids(catalog.task_targets(), catalog, [](const auto& row) { return row.id; })
  128. && required_ids(
  129. catalog.dialogue_cue_texts(), catalog, [](const auto& row) { return row.id; })
  130. && required_ids(
  131. catalog.dialogue_cue_texts(), catalog, [](const auto& row) { return row.text; })
  132. && required_ids(
  133. catalog.directive_elements(), catalog, [](const auto& row) { return row.id; })
  134. && required_ids(
  135. catalog.directive_elements(), catalog, [](const auto& row) { return row.title; })
  136. && directive_texts(catalog) && activity_ranges(catalog) && topology_ranges(catalog)
  137. && behavior_ranges(catalog) && runtime_ranges(catalog);
  138. }
  139. } // namespace sunrise::state::activity_sdk::validation