File: base.rb

package info (click to toggle)
ruby-json-schemer 2.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 544 kB
  • sloc: ruby: 7,428; makefile: 4; sh: 4
file content (127 lines) | stat: -rw-r--r-- 4,878 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# frozen_string_literal: true
module JSONSchemer
  module OpenAPI31
    module Vocab
      module Base
        class AllOf < Draft202012::Vocab::Applicator::AllOf
          attr_accessor :skip_ref_once

          def validate(instance, instance_location, keyword_location, context)
            nested = []
            parsed.each_with_index do |subschema, index|
              if ref_schema = subschema.parsed['$ref']&.ref_schema
                next if skip_ref_once == ref_schema.absolute_keyword_location
                ref_schema.parsed['discriminator']&.skip_ref_once = schema.absolute_keyword_location
              end
              nested << subschema.validate_instance(instance, instance_location, join_location(keyword_location, index.to_s), context)
            end
            result(instance, instance_location, keyword_location, nested.all?(&:valid), nested)
          ensure
            self.skip_ref_once = nil
          end
        end

        class AnyOf < Draft202012::Vocab::Applicator::AnyOf
          def validate(*)
            schema.parsed.key?('discriminator') ? nil : super
          end
        end

        class OneOf < Draft202012::Vocab::Applicator::OneOf
          def validate(*)
            schema.parsed.key?('discriminator') ? nil : super
          end
        end

        class Discriminator < Keyword
          # https://spec.openapis.org/oas/v3.1.0#components-object
          FIXED_FIELD_REGEX = /\A[a-zA-Z0-9\.\-_]+$\z/

          attr_accessor :skip_ref_once

          def error(formatted_instance_location:, **)
            "value at #{formatted_instance_location} does not match `discriminator` schema"
          end

          def mapping
            @mapping ||= value['mapping'] || {}
          end

          def subschemas_by_property_value
            @subschemas_by_property_value ||= if schema.parsed.key?('anyOf') || schema.parsed.key?('oneOf')
              subschemas = schema.parsed['anyOf']&.parsed || []
              subschemas += schema.parsed['oneOf']&.parsed || []

              subschemas_by_ref = {}
              subschemas_by_schema_name = {}

              subschemas.each do |subschema|
                subschema_ref = subschema.parsed.fetch('$ref').parsed
                subschemas_by_ref[subschema_ref] = subschema

                if subschema_ref.start_with?('#/components/schemas/')
                  schema_name = subschema_ref.delete_prefix('#/components/schemas/')
                  subschemas_by_schema_name[schema_name] = subschema if FIXED_FIELD_REGEX.match?(schema_name)
                end
              end

              explicit_mapping = mapping.transform_values do |schema_name_or_ref|
                subschemas_by_schema_name.fetch(schema_name_or_ref) { subschemas_by_ref.fetch(schema_name_or_ref) }
              end

              implicit_mapping = subschemas_by_schema_name.reject do |_schema_name, subschema|
                explicit_mapping.value?(subschema)
              end

              implicit_mapping.merge(explicit_mapping)
            else
              Hash.new do |hash, property_value|
                schema_name_or_ref = mapping.fetch(property_value, property_value)

                subschema = nil

                if FIXED_FIELD_REGEX.match?(schema_name_or_ref)
                  subschema = begin
                    schema.ref("#/components/schemas/#{schema_name_or_ref}")
                  rescue InvalidRefPointer
                    nil
                  end
                end

                subschema ||= begin
                  schema.ref(schema_name_or_ref)
                rescue InvalidRefResolution, UnknownRef
                  nil
                end

                hash[property_value] = subschema
              end
            end
          end

          def validate(instance, instance_location, keyword_location, context)
            return result(instance, instance_location, keyword_location, false) unless instance.is_a?(Hash)

            property_name = value.fetch('propertyName')

            return result(instance, instance_location, keyword_location, false) unless instance.key?(property_name)

            property_value = instance.fetch(property_name)
            subschema = subschemas_by_property_value[property_value]

            return result(instance, instance_location, keyword_location, false) unless subschema

            return if skip_ref_once == subschema.absolute_keyword_location
            subschema.parsed['allOf']&.skip_ref_once = schema.absolute_keyword_location

            subschema_result = subschema.validate_instance(instance, instance_location, keyword_location, context)

            result(instance, instance_location, keyword_location, subschema_result.valid, subschema_result.nested)
          ensure
            self.skip_ref_once = nil
          end
        end
      end
    end
  end
end