|
| 1 | +class FlattenNestedAllOf final : public SchemaTransformRule { |
| 2 | +public: |
| 3 | + using mutates = std::true_type; |
| 4 | + using reframe_after_transform = std::true_type; |
| 5 | + FlattenNestedAllOf() |
| 6 | + : SchemaTransformRule{ |
| 7 | + "flatten_nested_allof", |
| 8 | + "An `allOf` branch that only contains another `allOf` can " |
| 9 | + "be flattened into the parent `allOf`"} {}; |
| 10 | + |
| 11 | + [[nodiscard]] auto |
| 12 | + condition(const sourcemeta::core::JSON &schema, |
| 13 | + const sourcemeta::core::JSON &, |
| 14 | + const sourcemeta::core::Vocabularies &vocabularies, |
| 15 | + const sourcemeta::core::SchemaFrame &frame, |
| 16 | + const sourcemeta::core::SchemaFrame::Location &location, |
| 17 | + const sourcemeta::core::SchemaWalker &, |
| 18 | + const sourcemeta::core::SchemaResolver &) const |
| 19 | + -> SchemaTransformRule::Result override { |
| 20 | + static const JSON::String KEYWORD{"allOf"}; |
| 21 | + ONLY_CONTINUE_IF(vocabularies.contains_any( |
| 22 | + {Vocabularies::Known::JSON_Schema_2020_12_Applicator, |
| 23 | + Vocabularies::Known::JSON_Schema_2019_09_Applicator, |
| 24 | + Vocabularies::Known::JSON_Schema_Draft_7, |
| 25 | + Vocabularies::Known::JSON_Schema_Draft_6, |
| 26 | + Vocabularies::Known::JSON_Schema_Draft_4}) && |
| 27 | + schema.is_object() && schema.defines(KEYWORD) && |
| 28 | + schema.at(KEYWORD).is_array()); |
| 29 | + |
| 30 | + this->flatten_indices_.clear(); |
| 31 | + const auto &branches{schema.at(KEYWORD)}; |
| 32 | + for (std::size_t index = 0; index < branches.size(); ++index) { |
| 33 | + const auto &branch{branches.at(index)}; |
| 34 | + if (branch.is_object() && branch.size() == 1 && branch.defines(KEYWORD) && |
| 35 | + branch.at(KEYWORD).is_array()) { |
| 36 | + this->flatten_indices_.push_back(index); |
| 37 | + } |
| 38 | + } |
| 39 | + |
| 40 | + ONLY_CONTINUE_IF(!this->flatten_indices_.empty()); |
| 41 | + ONLY_CONTINUE_IF(!frame.has_references_through( |
| 42 | + location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); |
| 43 | + return APPLIES_TO_KEYWORDS(KEYWORD); |
| 44 | + } |
| 45 | + |
| 46 | + auto transform(JSON &schema, const Result &) const -> void override { |
| 47 | + static const JSON::String KEYWORD{"allOf"}; |
| 48 | + this->index_mapping_.clear(); |
| 49 | + const auto &original{schema.at(KEYWORD)}; |
| 50 | + auto result{JSON::make_array()}; |
| 51 | + std::size_t new_index{0}; |
| 52 | + std::size_t flatten_cursor{0}; |
| 53 | + |
| 54 | + for (std::size_t index = 0; index < original.size(); ++index) { |
| 55 | + if (flatten_cursor < this->flatten_indices_.size() && |
| 56 | + this->flatten_indices_[flatten_cursor] == index) { |
| 57 | + this->collect_leaves_(original.at(index), KEYWORD, index, result, |
| 58 | + new_index); |
| 59 | + ++flatten_cursor; |
| 60 | + } else { |
| 61 | + this->index_mapping_.emplace_back(index, std::nullopt, new_index); |
| 62 | + result.push_back(original.at(index)); |
| 63 | + ++new_index; |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + schema.assign(KEYWORD, std::move(result)); |
| 68 | + } |
| 69 | + |
| 70 | + [[nodiscard]] auto rereference(const std::string_view, const Pointer &, |
| 71 | + const Pointer &target, |
| 72 | + const Pointer ¤t) const |
| 73 | + -> Pointer override { |
| 74 | + static const JSON::String KEYWORD{"allOf"}; |
| 75 | + const auto prefix{current.concat({KEYWORD})}; |
| 76 | + if (!target.starts_with(prefix)) { |
| 77 | + return target; |
| 78 | + } |
| 79 | + const auto relative{target.resolve_from(prefix)}; |
| 80 | + if (relative.empty() || !relative.at(0).is_index()) { |
| 81 | + return target; |
| 82 | + } |
| 83 | + const auto old_index{relative.at(0).to_index()}; |
| 84 | + for (const auto &[outer, inner, mapped] : this->index_mapping_) { |
| 85 | + if (outer == old_index && inner.has_value()) { |
| 86 | + const Pointer old_prefix{ |
| 87 | + prefix.concat({old_index, KEYWORD, inner.value()})}; |
| 88 | + if (target.starts_with(old_prefix)) { |
| 89 | + const Pointer new_prefix{prefix.concat({mapped})}; |
| 90 | + return target.rebase(old_prefix, new_prefix); |
| 91 | + } |
| 92 | + } else if (outer == old_index) { |
| 93 | + const Pointer old_prefix{prefix.concat({old_index})}; |
| 94 | + const Pointer new_prefix{prefix.concat({mapped})}; |
| 95 | + return target.rebase(old_prefix, new_prefix); |
| 96 | + } |
| 97 | + } |
| 98 | + return target; |
| 99 | + } |
| 100 | + |
| 101 | +private: |
| 102 | + auto collect_leaves_(const JSON &node, const JSON::String &keyword, |
| 103 | + std::size_t outer_index, JSON &result, |
| 104 | + std::size_t &new_index) const -> void { |
| 105 | + const auto &inner{node.at(keyword)}; |
| 106 | + for (std::size_t inner_index = 0; inner_index < inner.size(); |
| 107 | + ++inner_index) { |
| 108 | + const auto &child{inner.at(inner_index)}; |
| 109 | + if (child.is_object() && child.size() == 1 && child.defines(keyword) && |
| 110 | + child.at(keyword).is_array()) { |
| 111 | + this->collect_leaves_(child, keyword, outer_index, result, new_index); |
| 112 | + } else { |
| 113 | + this->index_mapping_.emplace_back(outer_index, inner_index, new_index); |
| 114 | + result.push_back(child); |
| 115 | + ++new_index; |
| 116 | + } |
| 117 | + } |
| 118 | + } |
| 119 | + |
| 120 | + mutable std::vector<std::size_t> flatten_indices_; |
| 121 | + mutable std::vector< |
| 122 | + std::tuple<std::size_t, std::optional<std::size_t>, std::size_t>> |
| 123 | + index_mapping_; |
| 124 | +}; |
0 commit comments