media_types 2.2.0 → 2.3.0

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/debian.yml +43 -43
  3. data/.github/workflows/publish-bookworm.yml +33 -0
  4. data/.github/workflows/publish-sid.yml +33 -0
  5. data/.github/workflows/ruby.yml +22 -22
  6. data/.gitignore +20 -10
  7. data/.rubocop.yml +29 -29
  8. data/CHANGELOG.md +175 -164
  9. data/Gemfile +6 -6
  10. data/LICENSE +21 -21
  11. data/README.md +666 -664
  12. data/Rakefile +12 -12
  13. data/bin/console +15 -15
  14. data/bin/setup +8 -8
  15. data/lib/media_types/constructable.rb +161 -160
  16. data/lib/media_types/dsl/errors.rb +18 -18
  17. data/lib/media_types/dsl.rb +172 -172
  18. data/lib/media_types/errors.rb +25 -19
  19. data/lib/media_types/formatter.rb +56 -56
  20. data/lib/media_types/hash.rb +21 -21
  21. data/lib/media_types/object.rb +35 -35
  22. data/lib/media_types/scheme/allow_nil.rb +30 -30
  23. data/lib/media_types/scheme/any_of.rb +41 -41
  24. data/lib/media_types/scheme/attribute.rb +46 -46
  25. data/lib/media_types/scheme/enumeration_context.rb +18 -18
  26. data/lib/media_types/scheme/enumeration_of_type.rb +80 -80
  27. data/lib/media_types/scheme/errors.rb +87 -87
  28. data/lib/media_types/scheme/links.rb +54 -54
  29. data/lib/media_types/scheme/missing_validation.rb +41 -41
  30. data/lib/media_types/scheme/not_strict.rb +15 -15
  31. data/lib/media_types/scheme/output_empty_guard.rb +45 -45
  32. data/lib/media_types/scheme/output_iterator_with_predicate.rb +66 -66
  33. data/lib/media_types/scheme/output_type_guard.rb +39 -39
  34. data/lib/media_types/scheme/rules.rb +186 -173
  35. data/lib/media_types/scheme/rules_exhausted_guard.rb +73 -73
  36. data/lib/media_types/scheme/validation_options.rb +44 -43
  37. data/lib/media_types/scheme.rb +535 -513
  38. data/lib/media_types/testing/assertions.rb +20 -20
  39. data/lib/media_types/validations.rb +118 -105
  40. data/lib/media_types/version.rb +5 -5
  41. data/lib/media_types/views.rb +12 -12
  42. data/lib/media_types.rb +73 -73
  43. data/media_types.gemspec +33 -33
  44. metadata +8 -6
@@ -1,172 +1,172 @@
1
- # frozen_string_literal: true
2
-
3
- require 'set'
4
-
5
- require 'media_types/constructable'
6
- require 'media_types/validations'
7
-
8
- require 'media_types/dsl/errors'
9
-
10
- module MediaTypes
11
- module Dsl
12
- def self.included(base)
13
- base.extend ClassMethods
14
- base.class_eval do
15
- class << self
16
- attr_accessor :media_type_name_for, :media_type_combinations, :media_type_validations, :symbol_keys
17
-
18
- private
19
-
20
- attr_accessor :media_type_constructable, :symbol_base, :media_type_registrar
21
- end
22
- base.media_type_combinations = Set.new
23
- end
24
- end
25
-
26
- module ClassMethods
27
- def to_constructable
28
- raise UninitializedConstructable if media_type_constructable.nil?
29
-
30
- media_type_constructable.dup.tap do |constructable|
31
- constructable.__setobj__(self)
32
- end
33
- end
34
-
35
- def symbol_keys?
36
- if symbol_keys.nil?
37
- MediaTypes.get_key_expectation(self)
38
- else
39
- symbol_keys
40
- end
41
- end
42
-
43
- def string_keys?
44
- !symbol_keys?
45
- end
46
-
47
- def valid?(output, **opts)
48
- to_constructable.valid?(output, **opts)
49
- end
50
-
51
- def valid_unsafe?(output, media_type = to_constructable, **opts)
52
- opts[:expected_key_type] = string_keys? ? String : Symbol
53
- validations.find(media_type).valid?(output, backtrace: ['.'], **opts)
54
- end
55
-
56
- def validate!(output, **opts)
57
- assert_sane!
58
- to_constructable.validate!(output, **opts)
59
- end
60
-
61
- def validate_unsafe!(output, media_type = to_constructable, **opts)
62
- opts[:expected_key_type] = string_keys? ? String : Symbol
63
- validations.find(media_type).validate(output, backtrace: ['.'], **opts)
64
- end
65
-
66
- def validatable?(media_type = to_constructable)
67
- return false unless validations
68
-
69
- resolved = validations.find(media_type, -> { nil })
70
-
71
- !resolved.nil?
72
- end
73
-
74
- def register
75
- registrations.to_a.map do |registerable|
76
- MediaTypes.register(registerable)
77
- registerable
78
- end
79
- end
80
-
81
- def view(v)
82
- to_constructable.view(v)
83
- end
84
-
85
- def version(v)
86
- to_constructable.version(v)
87
- end
88
-
89
- def identifier_format
90
- self.media_type_name_for = proc do |type:, view:, version:, suffix:|
91
- yield(type: type, view: view, version: version, suffix: suffix)
92
- end
93
- end
94
-
95
- def identifier
96
- to_constructable.to_s
97
- end
98
-
99
- def available_validations
100
- media_type_combinations.map do |a|
101
- _, view, version = a
102
- view(view).version(version)
103
- end
104
- end
105
-
106
- def schema_for(constructable)
107
- validations.find(constructable)
108
- end
109
-
110
- def assert_sane!
111
- return if media_type_validations.scheme.asserted_sane?
112
-
113
- media_type_validations.run_fixture_validations(symbol_keys?)
114
- end
115
-
116
- private
117
-
118
- def use_name(name)
119
- if media_type_name_for.nil?
120
- self.media_type_name_for = proc do |type:, view:, version:, suffix:|
121
- resolved_org = nil
122
- if defined?(organisation)
123
- resolved_org = organisation
124
- else
125
- resolved_org = MediaTypes.get_organisation(self)
126
-
127
- if resolved_org.nil?
128
- raise OrganisationNotSetError,
129
- format('Implement the class method "organisation" in %<klass>s or specify a global organisation using MediaTypes::set_organisation', klass: self)
130
- end
131
- end
132
- raise ArgumentError, 'Unable to create a name for a schema with a nil name.' if type.nil?
133
- raise ArgumentError, 'Unable to create a name for a schema with a nil organisation.' if resolved_org.nil?
134
-
135
- result = "application/vnd.#{resolved_org}.#{type}"
136
- result += ".v#{version}" unless version.nil?
137
- result += ".#{view}" unless view.nil?
138
- result += "+#{suffix}" unless suffix.nil?
139
- result
140
- end
141
- end
142
- self.media_type_constructable = Constructable.new(self, type: name)
143
- end
144
-
145
- def expect_string_keys
146
- raise KeyTypeExpectationError, 'Key expectation already set' unless symbol_keys.nil?
147
-
148
- self.symbol_keys = false
149
- end
150
-
151
- def expect_symbol_keys
152
- raise KeyTypeExpectationError, 'Key expectation already set' unless symbol_keys.nil?
153
-
154
- self.symbol_keys = true
155
- end
156
-
157
- def validations(&block)
158
- return lookup_validations unless block_given?
159
-
160
- self.media_type_validations = Validations.new(to_constructable, &block)
161
-
162
- self
163
- end
164
-
165
- def lookup_validations
166
- raise MissingValidationError, "No validations defined for #{name}" if media_type_validations.nil?
167
-
168
- media_type_validations
169
- end
170
- end
171
- end
172
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ require 'media_types/constructable'
6
+ require 'media_types/validations'
7
+
8
+ require 'media_types/dsl/errors'
9
+
10
+ module MediaTypes
11
+ module Dsl
12
+ def self.included(base)
13
+ base.extend ClassMethods
14
+ base.class_eval do
15
+ class << self
16
+ attr_accessor :media_type_name_for, :media_type_combinations, :media_type_validations, :symbol_keys
17
+
18
+ private
19
+
20
+ attr_accessor :media_type_constructable, :symbol_base, :media_type_registrar
21
+ end
22
+ base.media_type_combinations = Set.new
23
+ end
24
+ end
25
+
26
+ module ClassMethods
27
+ def to_constructable
28
+ raise UninitializedConstructable if media_type_constructable.nil?
29
+
30
+ media_type_constructable.dup.tap do |constructable|
31
+ constructable.__setobj__(self)
32
+ end
33
+ end
34
+
35
+ def symbol_keys?
36
+ if symbol_keys.nil?
37
+ MediaTypes.get_key_expectation(self)
38
+ else
39
+ symbol_keys
40
+ end
41
+ end
42
+
43
+ def string_keys?
44
+ !symbol_keys?
45
+ end
46
+
47
+ def valid?(output, **opts)
48
+ to_constructable.valid?(output, **opts)
49
+ end
50
+
51
+ def valid_unsafe?(output, media_type = to_constructable, **opts)
52
+ opts[:expected_key_type] = string_keys? ? String : Symbol
53
+ validations.find(media_type).valid?(output, backtrace: ['.'], **opts)
54
+ end
55
+
56
+ def validate!(output, **opts)
57
+ assert_sane!
58
+ to_constructable.validate!(output, **opts)
59
+ end
60
+
61
+ def validate_unsafe!(output, media_type = to_constructable, **opts)
62
+ opts[:expected_key_type] = string_keys? ? String : Symbol
63
+ validations.find(media_type).validate(output, backtrace: ['.'], **opts)
64
+ end
65
+
66
+ def validatable?(media_type = to_constructable)
67
+ return false unless validations
68
+
69
+ resolved = validations.find(media_type, -> { nil })
70
+
71
+ !resolved.nil?
72
+ end
73
+
74
+ def register
75
+ registrations.to_a.map do |registerable|
76
+ MediaTypes.register(registerable)
77
+ registerable
78
+ end
79
+ end
80
+
81
+ def view(v)
82
+ to_constructable.view(v)
83
+ end
84
+
85
+ def version(v)
86
+ to_constructable.version(v)
87
+ end
88
+
89
+ def identifier_format
90
+ self.media_type_name_for = proc do |type:, view:, version:, suffix:|
91
+ yield(type: type, view: view, version: version, suffix: suffix)
92
+ end
93
+ end
94
+
95
+ def identifier
96
+ to_constructable.to_s
97
+ end
98
+
99
+ def available_validations
100
+ media_type_combinations.map do |a|
101
+ _, view, version = a
102
+ view(view).version(version)
103
+ end
104
+ end
105
+
106
+ def schema_for(constructable)
107
+ validations.find(constructable)
108
+ end
109
+
110
+ def assert_sane!
111
+ return if media_type_validations.scheme.asserted_sane?
112
+
113
+ media_type_validations.run_fixture_validations(symbol_keys?)
114
+ end
115
+
116
+ private
117
+
118
+ def use_name(name)
119
+ if media_type_name_for.nil?
120
+ self.media_type_name_for = proc do |type:, view:, version:, suffix:|
121
+ resolved_org = nil
122
+ if defined?(organisation)
123
+ resolved_org = organisation
124
+ else
125
+ resolved_org = MediaTypes.get_organisation(self)
126
+
127
+ if resolved_org.nil?
128
+ raise OrganisationNotSetError,
129
+ format('Implement the class method "organisation" in %<klass>s or specify a global organisation using MediaTypes::set_organisation', klass: self)
130
+ end
131
+ end
132
+ raise ArgumentError, 'Unable to create a name for a schema with a nil name.' if type.nil?
133
+ raise ArgumentError, 'Unable to create a name for a schema with a nil organisation.' if resolved_org.nil?
134
+
135
+ result = "application/vnd.#{resolved_org}.#{type}"
136
+ result += ".v#{version}" unless version.nil?
137
+ result += ".#{view}" unless view.nil?
138
+ result += "+#{suffix}" unless suffix.nil?
139
+ result
140
+ end
141
+ end
142
+ self.media_type_constructable = Constructable.new(self, type: name)
143
+ end
144
+
145
+ def expect_string_keys
146
+ raise KeyTypeExpectationError, 'Key expectation already set' unless symbol_keys.nil?
147
+
148
+ self.symbol_keys = false
149
+ end
150
+
151
+ def expect_symbol_keys
152
+ raise KeyTypeExpectationError, 'Key expectation already set' unless symbol_keys.nil?
153
+
154
+ self.symbol_keys = true
155
+ end
156
+
157
+ def validations(&block)
158
+ return lookup_validations unless block_given?
159
+
160
+ self.media_type_validations = Validations.new(to_constructable, &block)
161
+
162
+ self
163
+ end
164
+
165
+ def lookup_validations
166
+ raise MissingValidationError, "No validations defined for #{name}" if media_type_validations.nil?
167
+
168
+ media_type_validations
169
+ end
170
+ end
171
+ end
172
+ end
@@ -1,19 +1,25 @@
1
- # frozen_string_literal: true
2
-
3
- module MediaTypes
4
- module Errors
5
- # Raised when trying to set a module key expectation twice
6
- class KeyExpectationSetError < StandardError
7
- def initialize(mod:)
8
- super(format('%<mod>s already has a key expectation set', mod: mod.name))
9
- end
10
- end
11
-
12
- # Raised when trying to set a module key expectation while default expectation already used
13
- class KeyExpectationUsedError < StandardError
14
- def initialize(mod:)
15
- super(format('Unable to change key type expectation for %<mod>s since its current expectation is already used', mod: mod.name))
16
- end
17
- end
18
- end
19
- end
1
+ # frozen_string_literal: true
2
+
3
+ module MediaTypes
4
+ module Errors
5
+ # Raised when trying to set a module key expectation twice
6
+ class KeyExpectationSetError < StandardError
7
+ def initialize(mod:)
8
+ super(format('%<mod>s already has a key expectation set', mod: mod.name))
9
+ end
10
+ end
11
+
12
+ # Raised when trying to set a module key expectation while default expectation already used
13
+ class KeyExpectationUsedError < StandardError
14
+ def initialize(mod:)
15
+ super(format('Unable to change key type expectation for %<mod>s since its current expectation is already used', mod: mod.name))
16
+ end
17
+ end
18
+
19
+ class CollectionDefinitionNotFound < StandardError
20
+ def initialize(current, target)
21
+ super(format('Unable to use %<target>s as a collection inside %<current>s, no such schema has been defined.', current: current, target: target))
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,56 +1,56 @@
1
- # frozen_string_literal: true
2
-
3
- require 'media_types/object'
4
-
5
- module MediaTypes
6
- class Formatter < DelegateClass(Hash)
7
-
8
- class << self
9
- def call(*args, **options)
10
- new(*args, **options).call
11
- end
12
- end
13
-
14
- def call
15
- filtered_arguments = arguments
16
- return template if MediaTypes::Object.new(filtered_arguments).empty?
17
-
18
- format(rework_template(filtered_arguments), filtered_arguments)
19
- end
20
-
21
- private
22
-
23
- def template
24
- fetch(:format)
25
- end
26
-
27
- def rework_template(filtered_arguments)
28
- filtered_arguments.reduce(template) do |reworked, (key, value)|
29
- next reworked if MediaTypes::Object.new(value).present?
30
-
31
- reworked.gsub(/[.+](%<[A-z]+>)/) do |match|
32
- partial_format = "%<#{key}>"
33
- match.include?(partial_format) ? partial_format : match
34
- end
35
- end
36
- end
37
-
38
- def format_view(view)
39
- MediaTypes::Object.new(view).present? && ".#{view}" || ''
40
- end
41
-
42
- def arguments
43
- # noinspection RubyBlockToMethodReference
44
- {
45
- version: self[:version],
46
- suffix: self[:suffix],
47
- type: self[:type],
48
- view: self[:view]
49
- }.select { |argument,| argument_present?(argument) }
50
- end
51
-
52
- def argument_present?(argument)
53
- template.include?("%<#{argument}>")
54
- end
55
- end
56
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'media_types/object'
4
+
5
+ module MediaTypes
6
+ class Formatter < DelegateClass(Hash)
7
+
8
+ class << self
9
+ def call(*args, **options)
10
+ new(*args, **options).call
11
+ end
12
+ end
13
+
14
+ def call
15
+ filtered_arguments = arguments
16
+ return template if MediaTypes::Object.new(filtered_arguments).empty?
17
+
18
+ format(rework_template(filtered_arguments), filtered_arguments)
19
+ end
20
+
21
+ private
22
+
23
+ def template
24
+ fetch(:format)
25
+ end
26
+
27
+ def rework_template(filtered_arguments)
28
+ filtered_arguments.reduce(template) do |reworked, (key, value)|
29
+ next reworked if MediaTypes::Object.new(value).present?
30
+
31
+ reworked.gsub(/[.+](%<[A-z]+>)/) do |match|
32
+ partial_format = "%<#{key}>"
33
+ match.include?(partial_format) ? partial_format : match
34
+ end
35
+ end
36
+ end
37
+
38
+ def format_view(view)
39
+ MediaTypes::Object.new(view).present? && ".#{view}" || ''
40
+ end
41
+
42
+ def arguments
43
+ # noinspection RubyBlockToMethodReference
44
+ {
45
+ version: self[:version],
46
+ suffix: self[:suffix],
47
+ type: self[:type],
48
+ view: self[:view]
49
+ }.select { |argument,| argument_present?(argument) }
50
+ end
51
+
52
+ def argument_present?(argument)
53
+ template.include?("%<#{argument}>")
54
+ end
55
+ end
56
+ end
@@ -1,21 +1,21 @@
1
- module MediaTypes
2
- class Hash < SimpleDelegator
3
- def class
4
- __getobj__.class
5
- end
6
-
7
- def ===(other)
8
- __getobj__ === other # rubocop:disable Style/CaseEquality
9
- end
10
-
11
- def slice(*keep_keys)
12
- if __getobj__.respond_to?(:slice)
13
- return __getobj__.slice(*keep_keys)
14
- end
15
-
16
- h = {}
17
- keep_keys.each { |key| h[key] = fetch(key) if key?(key) }
18
- h
19
- end
20
- end
21
- end
1
+ module MediaTypes
2
+ class Hash < SimpleDelegator
3
+ def class
4
+ __getobj__.class
5
+ end
6
+
7
+ def ===(other)
8
+ __getobj__ === other # rubocop:disable Style/CaseEquality
9
+ end
10
+
11
+ def slice(*keep_keys)
12
+ if __getobj__.respond_to?(:slice)
13
+ return __getobj__.slice(*keep_keys)
14
+ end
15
+
16
+ h = {}
17
+ keep_keys.each { |key| h[key] = fetch(key) if key?(key) }
18
+ h
19
+ end
20
+ end
21
+ end
@@ -1,35 +1,35 @@
1
- # frozen_string_literal: true
2
-
3
- module MediaTypes
4
- class Object < SimpleDelegator
5
- def class
6
- __getobj__.class
7
- end
8
-
9
- def ===(other)
10
- __getobj__ === other # rubocop:disable Style/CaseEquality
11
- end
12
-
13
- def blank?
14
- if __getobj__.respond_to?(:blank?)
15
- return __getobj__.blank?
16
- end
17
-
18
- if __getobj__.respond_to?(:empty?)
19
- return __getobj__.empty?
20
- end
21
-
22
- if __getobj__.respond_to?(:length)
23
- return __getobj__.length.zero?
24
- end
25
-
26
- !__getobj__
27
- end
28
-
29
- alias empty? blank?
30
-
31
- def present?
32
- !blank?
33
- end
34
- end
35
- end
1
+ # frozen_string_literal: true
2
+
3
+ module MediaTypes
4
+ class Object < SimpleDelegator
5
+ def class
6
+ __getobj__.class
7
+ end
8
+
9
+ def ===(other)
10
+ __getobj__ === other # rubocop:disable Style/CaseEquality
11
+ end
12
+
13
+ def blank?
14
+ if __getobj__.respond_to?(:blank?)
15
+ return __getobj__.blank?
16
+ end
17
+
18
+ if __getobj__.respond_to?(:empty?)
19
+ return __getobj__.empty?
20
+ end
21
+
22
+ if __getobj__.respond_to?(:length)
23
+ return __getobj__.length.zero?
24
+ end
25
+
26
+ !__getobj__
27
+ end
28
+
29
+ alias empty? blank?
30
+
31
+ def present?
32
+ !blank?
33
+ end
34
+ end
35
+ end
@@ -1,30 +1,30 @@
1
- # frozen_string_literal: true
2
-
3
- require 'delegate'
4
- require 'media_types/scheme/any_of'
5
-
6
- module MediaTypes
7
- class Scheme
8
- class << self
9
- # noinspection RubyClassMethodNamingConvention
10
- ##
11
- # Allows the wrapped +klazz+ to be nil
12
- #
13
- # @param [Class] klazz the class that +it+ must be the if +it+ is not NilClass
14
- # @return [CaseEqualityWithList]
15
- def AllowNil(klazz) # rubocop:disable Naming/MethodName
16
- AnyOf(NilClass, klazz)
17
- end
18
- end
19
-
20
- # noinspection RubyInstanceMethodNamingConvention
21
- ##
22
- # Allows the wrapped +klazz+ to be nil
23
- #
24
- # @param [Class] klazz the class that +it+ must be the if +it+ is not NilClass
25
- # @return [CaseEqualityWithList]
26
- def AllowNil(klazz) # rubocop:disable Naming/MethodName
27
- self.class.AllowNil(klazz)
28
- end
29
- end
30
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+ require 'media_types/scheme/any_of'
5
+
6
+ module MediaTypes
7
+ class Scheme
8
+ class << self
9
+ # noinspection RubyClassMethodNamingConvention
10
+ ##
11
+ # Allows the wrapped +klazz+ to be nil
12
+ #
13
+ # @param [Class] klazz the class that +it+ must be the if +it+ is not NilClass
14
+ # @return [CaseEqualityWithList]
15
+ def AllowNil(klazz) # rubocop:disable Naming/MethodName
16
+ AnyOf(NilClass, klazz)
17
+ end
18
+ end
19
+
20
+ # noinspection RubyInstanceMethodNamingConvention
21
+ ##
22
+ # Allows the wrapped +klazz+ to be nil
23
+ #
24
+ # @param [Class] klazz the class that +it+ must be the if +it+ is not NilClass
25
+ # @return [CaseEqualityWithList]
26
+ def AllowNil(klazz) # rubocop:disable Naming/MethodName
27
+ self.class.AllowNil(klazz)
28
+ end
29
+ end
30
+ end