media_types 2.3.0 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/debian.yml +43 -43
  3. data/.github/workflows/publish-bookworm.yml +34 -33
  4. data/.github/workflows/publish-sid.yml +34 -33
  5. data/.github/workflows/ruby.yml +22 -22
  6. data/.gitignore +20 -20
  7. data/.rubocop.yml +29 -29
  8. data/CHANGELOG.md +183 -175
  9. data/Gemfile +6 -6
  10. data/Gemfile.lock +43 -43
  11. data/LICENSE +21 -21
  12. data/README.md +666 -666
  13. data/Rakefile +12 -12
  14. data/bin/console +15 -15
  15. data/bin/setup +8 -8
  16. data/lib/media_types/constructable.rb +161 -161
  17. data/lib/media_types/dsl/errors.rb +18 -18
  18. data/lib/media_types/dsl.rb +172 -172
  19. data/lib/media_types/errors.rb +25 -25
  20. data/lib/media_types/formatter.rb +56 -56
  21. data/lib/media_types/hash.rb +21 -21
  22. data/lib/media_types/object.rb +35 -35
  23. data/lib/media_types/scheme/allow_nil.rb +30 -30
  24. data/lib/media_types/scheme/any_of.rb +41 -41
  25. data/lib/media_types/scheme/attribute.rb +46 -46
  26. data/lib/media_types/scheme/enumeration_context.rb +18 -18
  27. data/lib/media_types/scheme/enumeration_of_type.rb +80 -80
  28. data/lib/media_types/scheme/errors.rb +87 -87
  29. data/lib/media_types/scheme/links.rb +54 -54
  30. data/lib/media_types/scheme/missing_validation.rb +41 -41
  31. data/lib/media_types/scheme/not_strict.rb +15 -15
  32. data/lib/media_types/scheme/output_empty_guard.rb +45 -45
  33. data/lib/media_types/scheme/output_iterator_with_predicate.rb +66 -66
  34. data/lib/media_types/scheme/output_type_guard.rb +39 -39
  35. data/lib/media_types/scheme/rules.rb +186 -186
  36. data/lib/media_types/scheme/rules_exhausted_guard.rb +75 -73
  37. data/lib/media_types/scheme/validation_options.rb +44 -44
  38. data/lib/media_types/scheme.rb +535 -535
  39. data/lib/media_types/testing/assertions.rb +20 -20
  40. data/lib/media_types/validations.rb +118 -118
  41. data/lib/media_types/version.rb +5 -5
  42. data/lib/media_types/views.rb +12 -12
  43. data/lib/media_types.rb +73 -73
  44. data/media_types.gemspec +33 -33
  45. metadata +6 -6
@@ -1,186 +1,186 @@
1
- # frozen_string_literal: true
2
-
3
- require 'media_types/scheme/missing_validation'
4
-
5
- module MediaTypes
6
- class Scheme
7
- class Rules < DelegateClass(::Hash)
8
-
9
- attr_reader :expected_type
10
-
11
- def initialize(allow_empty:, expected_type:)
12
- super({})
13
-
14
- self.allow_empty = allow_empty
15
- self.expected_type = expected_type
16
- self.optional_keys = []
17
- self.strict_keys = []
18
- self.original_key_type = {}
19
-
20
- self.default = MissingValidation.new
21
- end
22
-
23
- def allow_empty?
24
- allow_empty
25
- end
26
-
27
- def [](key)
28
- __getobj__[normalize_key(key)]
29
- end
30
-
31
- def add(key, val, optional: false)
32
- validate_input(key, val)
33
-
34
- normalized_key = normalize_key(key)
35
- __getobj__[normalized_key] = val
36
- if optional == :loose
37
- strict_keys << normalized_key
38
- else
39
- optional_keys << normalized_key if optional
40
- end
41
- original_key_type[normalized_key] = key.class
42
-
43
- self
44
- end
45
-
46
- def validate_input(key, val)
47
- raise KeyTypeError, "Unexpected key type #{key.class.name}, please use either a symbol or string." unless key.is_a?(String) || key.is_a?(Symbol)
48
-
49
- validate_key_name(key, val)
50
- end
51
-
52
- def validate_key_name(key, val)
53
- return unless has_key?(key)
54
-
55
- if key.is_a?(Symbol)
56
- duplicate_symbol_key_name(key, val)
57
- else
58
- duplicate_string_key_name(key, val)
59
- end
60
- end
61
-
62
- def duplicate_symbol_key_name(key, val)
63
- raise DuplicateSymbolKeyError.new(val.class.name.split('::').last, key) if get_original_key_type(key) == Symbol
64
-
65
- raise SymbolOverwritingStringError.new(val.class.name.split('::').last, key)
66
- end
67
-
68
- def duplicate_string_key_name(key, val)
69
- raise DuplicateStringKeyError.new(val.class.name.split('::').last, key) if get_original_key_type(key) == String
70
-
71
- raise StringOverwritingSymbolError.new(val.class.name.split('::').last, key)
72
- end
73
-
74
- def []=(key, val)
75
- add(key, val, optional: false)
76
- end
77
-
78
- def fetch(key, &block)
79
- __getobj__.fetch(normalize_key(key), &block)
80
- end
81
-
82
- def delete(key)
83
- __getobj__.delete(normalize_key(key))
84
- self
85
- end
86
-
87
- ##
88
- # Returns the keys that are not options
89
- #
90
- # @see #add
91
- # #see #merge
92
- #
93
- # @return [Array<Symbol>] required keys
94
- #
95
- def required(loose:)
96
- clone.tap do |cloned|
97
- optional_keys.each do |key|
98
- cloned.delete(key)
99
- end
100
- if loose
101
- strict_keys.each do |key|
102
- cloned.delete(key)
103
- end
104
- end
105
- end
106
- end
107
-
108
- def clone
109
- super.tap do |cloned|
110
- cloned.__setobj__(__getobj__.clone)
111
- end
112
- end
113
-
114
- ##
115
- # Merges another set of rules into a clone of this one
116
- #
117
- # @param [Rules, ::Hash] the other rules
118
- # @return [Rules] a clone
119
- #
120
- def merge(rules)
121
- clone.instance_exec do
122
- __setobj__(__getobj__.merge(rules))
123
- if rules.respond_to?(:optional_keys, true)
124
- optional_keys.push(*rules.send(:optional_keys))
125
- end
126
- if rules.respond_to?(:strict_keys, true)
127
- strict_keys.push(*rules.send(:strict_keys))
128
- end
129
-
130
- self
131
- end
132
- end
133
-
134
- def inspect(indent = 0)
135
- prefix = ' ' * indent
136
- return "#{prefix}[Error]Depth limit reached[/Error]" if indent > 5_000
137
-
138
- [
139
- "#{prefix}[Rules n=#{keys.length} optional=#{optional_keys.length} strict=#{strict_keys.length} allow_empty=#{allow_empty?}]",
140
- "#{prefix} #{inspect_format_attribute(indent, '*', default)}",
141
- *keys.map { |key| "#{prefix} #{inspect_format_attribute(indent, key)}" },
142
- "#{prefix}[/Rules]"
143
- ].join("\n")
144
- end
145
-
146
- def inspect_format_attribute(indent, key, value = self[key])
147
- [
148
- [key.to_s, optional_keys.include?(key) && '(optional)' || nil].compact.join(' '),
149
- value.is_a?(Scheme) || value.is_a?(Rules) ? "\n#{value.inspect(indent + 2)}" : value.inspect
150
- ].join(': ')
151
- end
152
-
153
- def has_key?(key)
154
- __getobj__.key?(normalize_key(key))
155
- end
156
-
157
- def get_original_key_type(key)
158
- raise format('Key %<key>s does not exist', key: key) unless has_key?(key)
159
-
160
- original_key_type[normalize_key(key)]
161
- end
162
-
163
- def default=(input_default)
164
- unless default.nil?
165
- raise DuplicateAnyRuleError if !(default.is_a?(MissingValidation) || default.is_a?(NotStrict)) && !(input_default.is_a?(MissingValidation) || input_default.is_a?(NotStrict))
166
- raise DuplicateNotStrictRuleError if default.is_a?(NotStrict) && input_default.is_a?(NotStrict)
167
- raise NotStrictOverwritingAnyError if !(default.is_a?(MissingValidation) || default.is_a?(NotStrict)) && input_default.is_a?(NotStrict)
168
- raise AnyOverwritingNotStrictError if default.is_a?(NotStrict) && !(input_default.is_a?(MissingValidation) || input_default.is_a?(NotStrict))
169
- end
170
- super(input_default)
171
- end
172
-
173
- alias get []
174
- alias remove delete
175
-
176
- private
177
-
178
- attr_accessor :allow_empty, :strict_keys, :optional_keys, :original_key_type
179
- attr_writer :expected_type
180
-
181
- def normalize_key(key)
182
- String(key).to_sym
183
- end
184
- end
185
- end
186
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'media_types/scheme/missing_validation'
4
+
5
+ module MediaTypes
6
+ class Scheme
7
+ class Rules < DelegateClass(::Hash)
8
+
9
+ attr_reader :expected_type
10
+
11
+ def initialize(allow_empty:, expected_type:)
12
+ super({})
13
+
14
+ self.allow_empty = allow_empty
15
+ self.expected_type = expected_type
16
+ self.optional_keys = []
17
+ self.strict_keys = []
18
+ self.original_key_type = {}
19
+
20
+ self.default = MissingValidation.new
21
+ end
22
+
23
+ def allow_empty?
24
+ allow_empty
25
+ end
26
+
27
+ def [](key)
28
+ __getobj__[normalize_key(key)]
29
+ end
30
+
31
+ def add(key, val, optional: false)
32
+ validate_input(key, val)
33
+
34
+ normalized_key = normalize_key(key)
35
+ __getobj__[normalized_key] = val
36
+ if optional == :loose
37
+ strict_keys << normalized_key
38
+ else
39
+ optional_keys << normalized_key if optional
40
+ end
41
+ original_key_type[normalized_key] = key.class
42
+
43
+ self
44
+ end
45
+
46
+ def validate_input(key, val)
47
+ raise KeyTypeError, "Unexpected key type #{key.class.name}, please use either a symbol or string." unless key.is_a?(String) || key.is_a?(Symbol)
48
+
49
+ validate_key_name(key, val)
50
+ end
51
+
52
+ def validate_key_name(key, val)
53
+ return unless has_key?(key)
54
+
55
+ if key.is_a?(Symbol)
56
+ duplicate_symbol_key_name(key, val)
57
+ else
58
+ duplicate_string_key_name(key, val)
59
+ end
60
+ end
61
+
62
+ def duplicate_symbol_key_name(key, val)
63
+ raise DuplicateSymbolKeyError.new(val.class.name.split('::').last, key) if get_original_key_type(key) == Symbol
64
+
65
+ raise SymbolOverwritingStringError.new(val.class.name.split('::').last, key)
66
+ end
67
+
68
+ def duplicate_string_key_name(key, val)
69
+ raise DuplicateStringKeyError.new(val.class.name.split('::').last, key) if get_original_key_type(key) == String
70
+
71
+ raise StringOverwritingSymbolError.new(val.class.name.split('::').last, key)
72
+ end
73
+
74
+ def []=(key, val)
75
+ add(key, val, optional: false)
76
+ end
77
+
78
+ def fetch(key, &block)
79
+ __getobj__.fetch(normalize_key(key), &block)
80
+ end
81
+
82
+ def delete(key)
83
+ __getobj__.delete(normalize_key(key))
84
+ self
85
+ end
86
+
87
+ ##
88
+ # Returns the keys that are not options
89
+ #
90
+ # @see #add
91
+ # #see #merge
92
+ #
93
+ # @return [Array<Symbol>] required keys
94
+ #
95
+ def required(loose:)
96
+ clone.tap do |cloned|
97
+ optional_keys.each do |key|
98
+ cloned.delete(key)
99
+ end
100
+ if loose
101
+ strict_keys.each do |key|
102
+ cloned.delete(key)
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ def clone
109
+ super.tap do |cloned|
110
+ cloned.__setobj__(__getobj__.clone)
111
+ end
112
+ end
113
+
114
+ ##
115
+ # Merges another set of rules into a clone of this one
116
+ #
117
+ # @param [Rules, ::Hash] the other rules
118
+ # @return [Rules] a clone
119
+ #
120
+ def merge(rules)
121
+ clone.instance_exec do
122
+ __setobj__(__getobj__.merge(rules))
123
+ if rules.respond_to?(:optional_keys, true)
124
+ optional_keys.push(*rules.send(:optional_keys))
125
+ end
126
+ if rules.respond_to?(:strict_keys, true)
127
+ strict_keys.push(*rules.send(:strict_keys))
128
+ end
129
+
130
+ self
131
+ end
132
+ end
133
+
134
+ def inspect(indent = 0)
135
+ prefix = ' ' * indent
136
+ return "#{prefix}[Error]Depth limit reached[/Error]" if indent > 5_000
137
+
138
+ [
139
+ "#{prefix}[Rules n=#{keys.length} optional=#{optional_keys.length} strict=#{strict_keys.length} allow_empty=#{allow_empty?}]",
140
+ "#{prefix} #{inspect_format_attribute(indent, '*', default)}",
141
+ *keys.map { |key| "#{prefix} #{inspect_format_attribute(indent, key)}" },
142
+ "#{prefix}[/Rules]"
143
+ ].join("\n")
144
+ end
145
+
146
+ def inspect_format_attribute(indent, key, value = self[key])
147
+ [
148
+ [key.to_s, optional_keys.include?(key) && '(optional)' || nil].compact.join(' '),
149
+ value.is_a?(Scheme) || value.is_a?(Rules) ? "\n#{value.inspect(indent + 2)}" : value.inspect
150
+ ].join(': ')
151
+ end
152
+
153
+ def has_key?(key)
154
+ __getobj__.key?(normalize_key(key))
155
+ end
156
+
157
+ def get_original_key_type(key)
158
+ raise format('Key %<key>s does not exist', key: key) unless has_key?(key)
159
+
160
+ original_key_type[normalize_key(key)]
161
+ end
162
+
163
+ def default=(input_default)
164
+ unless default.nil?
165
+ raise DuplicateAnyRuleError if !(default.is_a?(MissingValidation) || default.is_a?(NotStrict)) && !(input_default.is_a?(MissingValidation) || input_default.is_a?(NotStrict))
166
+ raise DuplicateNotStrictRuleError if default.is_a?(NotStrict) && input_default.is_a?(NotStrict)
167
+ raise NotStrictOverwritingAnyError if !(default.is_a?(MissingValidation) || default.is_a?(NotStrict)) && input_default.is_a?(NotStrict)
168
+ raise AnyOverwritingNotStrictError if default.is_a?(NotStrict) && !(input_default.is_a?(MissingValidation) || input_default.is_a?(NotStrict))
169
+ end
170
+ super(input_default)
171
+ end
172
+
173
+ alias get []
174
+ alias remove delete
175
+
176
+ private
177
+
178
+ attr_accessor :allow_empty, :strict_keys, :optional_keys, :original_key_type
179
+ attr_writer :expected_type
180
+
181
+ def normalize_key(key)
182
+ String(key).to_sym
183
+ end
184
+ end
185
+ end
186
+ end
@@ -1,73 +1,75 @@
1
- # frozen_string_literal: true
2
-
3
- require 'media_types/scheme/errors'
4
- require 'media_types/scheme/output_iterator_with_predicate'
5
-
6
- module MediaTypes
7
- class Scheme
8
- class RulesExhaustedGuard
9
-
10
- EMPTY_MARK = ->(_) {}
11
-
12
- class << self
13
- def call(*args, **opts, &block)
14
- new(*args, **opts).call(&block)
15
- end
16
- end
17
-
18
- def initialize(output, options, rules:)
19
- self.rules = rules
20
- self.output = output
21
- self.options = options
22
- end
23
-
24
- def call
25
- unless options.exhaustive
26
- return iterate(EMPTY_MARK)
27
- end
28
-
29
- required_rules = rules.required(loose: options.loose)
30
- # noinspection RubyScope
31
- result = iterate(->(key) { required_rules.remove(key) })
32
- return result if required_rules.empty?
33
-
34
- raise_exhausted!(missing_keys: required_rules.keys, backtrace: options.backtrace, found: output)
35
- end
36
-
37
- def iterate(mark)
38
- OutputIteratorWithPredicate.call(output, options, rules: rules) do |key, value, options:, context:|
39
- unless key.class == options.expected_key_type || rules.get(key).class == NotStrict
40
- raise ValidationError,
41
- format(
42
- 'Expected key as %<type>s, got %<actual>s at [%<backtrace>s]',
43
- type: options.expected_key_type,
44
- actual: key.class,
45
- backtrace: options.trace(key).backtrace.join('->')
46
- )
47
- end
48
-
49
- mark.call(key)
50
-
51
- rules.get(key).validate!(
52
- value,
53
- options.trace(key),
54
- context: context
55
- )
56
- end
57
- end
58
-
59
- private
60
-
61
- attr_accessor :rules, :options, :output
62
-
63
- def raise_exhausted!(missing_keys:, backtrace:, found:)
64
- raise ExhaustedOutputError, format(
65
- 'Missing keys in output: %<missing_keys>s at [%<backtrace>s]. I did find: %<found>s',
66
- missing_keys: missing_keys,
67
- backtrace: backtrace.join('->'),
68
- found: (found.is_a? Hash) ? found.keys : found.class.name,
69
- )
70
- end
71
- end
72
- end
73
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'media_types/scheme/errors'
4
+ require 'media_types/scheme/output_iterator_with_predicate'
5
+
6
+ module MediaTypes
7
+ class Scheme
8
+ class RulesExhaustedGuard
9
+
10
+ EMPTY_MARK = ->(_) {}
11
+
12
+ class << self
13
+ def call(*args, **opts, &block)
14
+ new(*args, **opts).call(&block)
15
+ end
16
+ end
17
+
18
+ def initialize(output, options, rules:)
19
+ self.rules = rules
20
+ self.output = output
21
+ self.options = options
22
+ end
23
+
24
+ def call
25
+ unless options.exhaustive
26
+ return iterate(EMPTY_MARK)
27
+ end
28
+
29
+ required_rules = rules.required(loose: options.loose)
30
+ # noinspection RubyScope
31
+ result = iterate(->(key) { required_rules.remove(key) })
32
+ return result if required_rules.empty?
33
+
34
+ raise_exhausted!(missing_keys: required_rules.keys, backtrace: options.backtrace, found: output)
35
+ end
36
+
37
+ def iterate(mark)
38
+ OutputIteratorWithPredicate.call(output, options, rules: rules) do |key, value, options:, context:|
39
+ unless key.class == options.expected_key_type || rules.get(key).class == NotStrict
40
+ raise ValidationError,
41
+ format(
42
+ 'Expected key as %<type>s, got %<actual>s at [%<backtrace>s]',
43
+ type: options.expected_key_type,
44
+ actual: key.class,
45
+ backtrace: options.trace(key).backtrace.join('->')
46
+ )
47
+ end
48
+
49
+ mark.call(key)
50
+
51
+ catch(:end) do
52
+ rules.get(key).validate!(
53
+ value,
54
+ options.trace(key),
55
+ context: context
56
+ )
57
+ end
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ attr_accessor :rules, :options, :output
64
+
65
+ def raise_exhausted!(missing_keys:, backtrace:, found:)
66
+ raise ExhaustedOutputError, format(
67
+ 'Missing keys in output: %<missing_keys>s at [%<backtrace>s]. I did find: %<found>s',
68
+ missing_keys: missing_keys,
69
+ backtrace: backtrace.join('->'),
70
+ found: (found.is_a? Hash) ? found.keys : found.class.name,
71
+ )
72
+ end
73
+ end
74
+ end
75
+ end
@@ -1,44 +1,44 @@
1
- # frozen_string_literal: true
2
-
3
- module MediaTypes
4
- class Scheme
5
- class ValidationOptions
6
- attr_accessor :exhaustive, :strict, :backtrace, :context, :expected_key_type, :loose
7
-
8
- def initialize(context = {}, exhaustive: true, strict: true, backtrace: [], loose: false, expected_key_type:)
9
- self.exhaustive = exhaustive
10
- self.strict = strict
11
- self.backtrace = backtrace
12
- self.context = context
13
- self.expected_key_type = expected_key_type
14
- self.loose = loose
15
- end
16
-
17
- def inspect
18
- "backtrack: #{backtrace.inspect}, strict: #{strict.inspect}, loose: #{loose}, exhaustive: #{exhaustive}, current_obj: #{scoped_output.to_json}"
19
- end
20
-
21
- def scoped_output
22
- current = context
23
-
24
- backtrace.drop(1).first([0, backtrace.size - 2].max).each do |e|
25
- current = current[e] unless current.nil?
26
- end
27
-
28
- current
29
- end
30
-
31
- def with_backtrace(backtrace)
32
- ValidationOptions.new(context, exhaustive: exhaustive, strict: strict, backtrace: backtrace, expected_key_type: expected_key_type, loose: loose)
33
- end
34
-
35
- def trace(*traces)
36
- with_backtrace(backtrace.dup.concat(traces))
37
- end
38
-
39
- def exhaustive!
40
- ValidationOptions.new(context, exhaustive: true, strict: strict, backtrace: backtrace, expected_key_type: expected_key_type, loose: loose)
41
- end
42
- end
43
- end
44
- end
1
+ # frozen_string_literal: true
2
+
3
+ module MediaTypes
4
+ class Scheme
5
+ class ValidationOptions
6
+ attr_accessor :exhaustive, :strict, :backtrace, :context, :expected_key_type, :loose
7
+
8
+ def initialize(context = {}, exhaustive: true, strict: true, backtrace: [], loose: false, expected_key_type:)
9
+ self.exhaustive = exhaustive
10
+ self.strict = strict
11
+ self.backtrace = backtrace
12
+ self.context = context
13
+ self.expected_key_type = expected_key_type
14
+ self.loose = loose
15
+ end
16
+
17
+ def inspect
18
+ "backtrack: #{backtrace.inspect}, strict: #{strict.inspect}, loose: #{loose}, exhaustive: #{exhaustive}, current_obj: #{scoped_output.to_json}"
19
+ end
20
+
21
+ def scoped_output
22
+ current = context
23
+
24
+ backtrace.drop(1).first([0, backtrace.size - 2].max).each do |e|
25
+ current = current[e] unless current.nil?
26
+ end
27
+
28
+ current
29
+ end
30
+
31
+ def with_backtrace(backtrace)
32
+ ValidationOptions.new(context, exhaustive: exhaustive, strict: strict, backtrace: backtrace, expected_key_type: expected_key_type, loose: loose)
33
+ end
34
+
35
+ def trace(*traces)
36
+ with_backtrace(backtrace.dup.concat(traces))
37
+ end
38
+
39
+ def exhaustive!
40
+ ValidationOptions.new(context, exhaustive: true, strict: strict, backtrace: backtrace, expected_key_type: expected_key_type, loose: loose)
41
+ end
42
+ end
43
+ end
44
+ end