|
| 1 | +class InlineSingleUseRef final : public SchemaTransformRule { |
| 2 | +public: |
| 3 | + using mutates = std::true_type; |
| 4 | + using reframe_after_transform = std::true_type; |
| 5 | + InlineSingleUseRef() : SchemaTransformRule{"inline_single_use_ref", ""} {}; |
| 6 | + |
| 7 | + [[nodiscard]] auto |
| 8 | + condition(const sourcemeta::core::JSON &schema, |
| 9 | + const sourcemeta::core::JSON &root, |
| 10 | + const sourcemeta::core::Vocabularies &vocabularies, |
| 11 | + const sourcemeta::core::SchemaFrame &frame, |
| 12 | + const sourcemeta::core::SchemaFrame::Location &location, |
| 13 | + const sourcemeta::core::SchemaWalker &, |
| 14 | + const sourcemeta::core::SchemaResolver &) const |
| 15 | + -> SchemaTransformRule::Result override { |
| 16 | + ONLY_CONTINUE_IF(schema.is_object() && schema.defines("$ref") && |
| 17 | + schema.at("$ref").is_string() && schema.size() == 1); |
| 18 | + |
| 19 | + if (!location.parent.has_value()) { |
| 20 | + return false; |
| 21 | + } |
| 22 | + { |
| 23 | + const auto &parent_pointer{location.parent.value()}; |
| 24 | + const auto relative{location.pointer.resolve_from(parent_pointer)}; |
| 25 | + ONLY_CONTINUE_IF(!relative.empty() && relative.at(0).is_property() && |
| 26 | + relative.at(0).to_property() == "allOf" && |
| 27 | + relative.size() >= 2 && relative.at(1).is_index()); |
| 28 | + const auto &parent_schema{sourcemeta::core::get(root, parent_pointer)}; |
| 29 | + ONLY_CONTINUE_IF(parent_schema.is_object() && |
| 30 | + parent_schema.defines("allOf") && |
| 31 | + parent_schema.at("allOf").is_array()); |
| 32 | + const auto current_index{relative.at(1).to_index()}; |
| 33 | + bool has_typed_sibling{false}; |
| 34 | + for (std::size_t index = 0; index < parent_schema.at("allOf").size(); |
| 35 | + ++index) { |
| 36 | + if (index == current_index) { |
| 37 | + continue; |
| 38 | + } |
| 39 | + const auto &sibling{parent_schema.at("allOf").at(index)}; |
| 40 | + if (sibling.is_object() && |
| 41 | + (sibling.defines("type") || sibling.defines("enum"))) { |
| 42 | + has_typed_sibling = true; |
| 43 | + break; |
| 44 | + } |
| 45 | + } |
| 46 | + ONLY_CONTINUE_IF(has_typed_sibling); |
| 47 | + } |
| 48 | + |
| 49 | + ONLY_CONTINUE_IF(vocabularies.contains_any( |
| 50 | + {Vocabularies::Known::JSON_Schema_2020_12_Core, |
| 51 | + Vocabularies::Known::JSON_Schema_2019_09_Core, |
| 52 | + Vocabularies::Known::JSON_Schema_Draft_7, |
| 53 | + Vocabularies::Known::JSON_Schema_Draft_6, |
| 54 | + Vocabularies::Known::JSON_Schema_Draft_4})); |
| 55 | + |
| 56 | + const auto target{frame.traverse(schema.at("$ref").to_string())}; |
| 57 | + ONLY_CONTINUE_IF(target.has_value()); |
| 58 | + const auto &target_pointer{target->get().pointer}; |
| 59 | + |
| 60 | + if (target_pointer.size() < 2 || !target_pointer.at(0).is_property()) { |
| 61 | + return false; |
| 62 | + } |
| 63 | + const auto &container{target_pointer.at(0).to_property()}; |
| 64 | + ONLY_CONTINUE_IF(container == "definitions" || container == "$defs"); |
| 65 | + |
| 66 | + std::size_t ref_count{0}; |
| 67 | + for (const auto &reference : frame.references()) { |
| 68 | + const auto dest{frame.traverse(reference.second.destination)}; |
| 69 | + if (!dest.has_value()) { |
| 70 | + continue; |
| 71 | + } |
| 72 | + if (dest->get().pointer.starts_with(target_pointer) || |
| 73 | + target_pointer.starts_with(dest->get().pointer)) { |
| 74 | + ++ref_count; |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + ONLY_CONTINUE_IF(ref_count == 1); |
| 79 | + |
| 80 | + const auto &target_schema{sourcemeta::core::get(root, target_pointer)}; |
| 81 | + ONLY_CONTINUE_IF(!target_schema.is_boolean()); |
| 82 | + ONLY_CONTINUE_IF(target_schema.is_object() && |
| 83 | + !target_schema.defines("type") && |
| 84 | + !target_schema.defines("enum")); |
| 85 | + ONLY_CONTINUE_IF((!target_schema.defines("$id") && |
| 86 | + !target_schema.defines("id") && |
| 87 | + !target_schema.defines("$anchor") && |
| 88 | + !target_schema.defines("$dynamicAnchor") && |
| 89 | + !target_schema.defines("$recursiveAnchor"))); |
| 90 | + |
| 91 | + this->target_pointer_ = sourcemeta::core::to_pointer(target_pointer); |
| 92 | + this->target_copy_ = target_schema; |
| 93 | + return true; |
| 94 | + } |
| 95 | + |
| 96 | + auto transform(JSON &schema, const Result &) const -> void override { |
| 97 | + schema.into(std::move(this->target_copy_)); |
| 98 | + } |
| 99 | + |
| 100 | + [[nodiscard]] auto rereference(const std::string_view, const Pointer &, |
| 101 | + const Pointer &target, |
| 102 | + const Pointer ¤t) const |
| 103 | + -> Pointer override { |
| 104 | + if (target.starts_with(this->target_pointer_)) { |
| 105 | + const auto relative{target.resolve_from(this->target_pointer_)}; |
| 106 | + return current.concat(relative); |
| 107 | + } |
| 108 | + return target; |
| 109 | + } |
| 110 | + |
| 111 | +private: |
| 112 | + mutable Pointer target_pointer_; |
| 113 | + mutable sourcemeta::core::JSON target_copy_{sourcemeta::core::JSON{nullptr}}; |
| 114 | +}; |
0 commit comments