media_types 0.6.2 → 2.1.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.
- checksums.yaml +4 -4
- data/.github/workflows/debian.yml +42 -0
- data/.github/workflows/ruby.yml +22 -0
- data/.gitignore +10 -10
- data/CHANGELOG.md +76 -41
- data/Gemfile +6 -6
- data/Gemfile.lock +17 -83
- data/LICENSE +21 -0
- data/README.md +364 -91
- data/Rakefile +12 -12
- data/lib/media_types.rb +58 -2
- data/lib/media_types/constructable.rb +36 -10
- data/lib/media_types/dsl.rb +110 -29
- data/lib/media_types/dsl/errors.rb +18 -0
- data/lib/media_types/errors.rb +19 -0
- data/lib/media_types/scheme.rb +153 -2
- data/lib/media_types/scheme/errors.rb +66 -0
- data/lib/media_types/scheme/links.rb +15 -0
- data/lib/media_types/scheme/missing_validation.rb +12 -4
- data/lib/media_types/scheme/output_empty_guard.rb +5 -4
- data/lib/media_types/scheme/output_iterator_with_predicate.rb +13 -2
- data/lib/media_types/scheme/output_type_guard.rb +1 -1
- data/lib/media_types/scheme/rules.rb +53 -1
- data/lib/media_types/scheme/rules_exhausted_guard.rb +15 -4
- data/lib/media_types/scheme/validation_options.rb +17 -5
- data/lib/media_types/testing/assertions.rb +20 -0
- data/lib/media_types/validations.rb +15 -5
- data/lib/media_types/version.rb +1 -1
- data/media_types.gemspec +4 -7
- metadata +20 -62
- data/.travis.yml +0 -19
- data/lib/media_types/defaults.rb +0 -31
- data/lib/media_types/integrations.rb +0 -32
- data/lib/media_types/integrations/actionpack.rb +0 -21
- data/lib/media_types/integrations/http.rb +0 -47
- data/lib/media_types/minitest/assert_media_type_format.rb +0 -10
- data/lib/media_types/minitest/assert_media_types_registered.rb +0 -166
- data/lib/media_types/registrar.rb +0 -148
data/Rakefile
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'bundler/gem_tasks'
|
4
|
-
require 'rake/testtask'
|
5
|
-
|
6
|
-
Rake::TestTask.new(:test) do |t|
|
7
|
-
t.libs << 'test'
|
8
|
-
t.libs << 'lib'
|
9
|
-
t.test_files = FileList['test/**/*_test.rb']
|
10
|
-
end
|
11
|
-
|
12
|
-
task default: :test
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs << 'test'
|
8
|
+
t.libs << 'lib'
|
9
|
+
t.test_files = FileList['test/**/*_test.rb']
|
10
|
+
end
|
11
|
+
|
12
|
+
task default: :test
|
data/lib/media_types.rb
CHANGED
@@ -7,11 +7,67 @@ require 'media_types/hash'
|
|
7
7
|
require 'media_types/object'
|
8
8
|
require 'media_types/scheme'
|
9
9
|
require 'media_types/dsl'
|
10
|
+
require 'media_types/errors'
|
10
11
|
|
11
12
|
require 'media_types/views'
|
12
|
-
require 'media_types/integrations'
|
13
13
|
|
14
14
|
module MediaTypes
|
15
|
-
|
15
|
+
def self.set_organisation(mod, organisation)
|
16
|
+
@organisation_prefixes ||= {}
|
17
|
+
@organisation_prefixes[mod.name] = organisation
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.expect_string_keys(mod)
|
21
|
+
set_key_expectation(mod, false)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.expect_symbol_keys(mod)
|
25
|
+
set_key_expectation(mod, true)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Keep track of modules setting their key expectations
|
29
|
+
def self.set_key_expectation(mod, expect_symbol_keys)
|
30
|
+
@key_expectations ||= {}
|
31
|
+
@key_expectations_used ||= {}
|
32
|
+
|
33
|
+
raise KeyExpectationSetError.new(mod: mod) unless @key_expectations[mod.name].nil?
|
34
|
+
raise KeyExpectationUsedError.new(mod: mod) if @key_expectations_used[mod.name]
|
35
|
+
|
36
|
+
@key_expectations[mod.name] = expect_symbol_keys
|
37
|
+
end
|
38
|
+
|
39
|
+
SYMBOL_KEYS_DEFAULT = true
|
40
|
+
|
41
|
+
def self.get_key_expectation(mod)
|
42
|
+
@key_expectations ||= {}
|
43
|
+
@key_expectations_used ||= {}
|
16
44
|
|
45
|
+
expect_symbol = find_key_expectation(mod)
|
17
46
|
|
47
|
+
expect_symbol.nil? ? SYMBOL_KEYS_DEFAULT : expect_symbol
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.find_key_expectation(mod)
|
51
|
+
modules = mod.name.split('::')
|
52
|
+
expect_symbol = nil
|
53
|
+
|
54
|
+
while modules.any? && expect_symbol.nil?
|
55
|
+
current_module = modules.join('::')
|
56
|
+
expect_symbol = @key_expectations[current_module]
|
57
|
+
@key_expectations_used[current_module] = true
|
58
|
+
modules.pop
|
59
|
+
end
|
60
|
+
|
61
|
+
expect_symbol
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.get_organisation(mod)
|
65
|
+
name = mod.name
|
66
|
+
prefixes = @organisation_prefixes.keys.select { |p| name.start_with? p }
|
67
|
+
return nil unless prefixes.any?
|
68
|
+
|
69
|
+
best = prefixes.max_by { |p| p.length }
|
70
|
+
|
71
|
+
@organisation_prefixes[best]
|
72
|
+
end
|
73
|
+
end
|
@@ -28,11 +28,6 @@ module MediaTypes
|
|
28
28
|
with(view: view)
|
29
29
|
end
|
30
30
|
|
31
|
-
def suffix(suffix = NO_ARG)
|
32
|
-
return opts[:suffix] if suffix == NO_ARG
|
33
|
-
with(suffix: suffix)
|
34
|
-
end
|
35
|
-
|
36
31
|
def collection
|
37
32
|
view(COLLECTION_VIEW)
|
38
33
|
end
|
@@ -73,20 +68,46 @@ module MediaTypes
|
|
73
68
|
to_str.split(pattern, *limit)
|
74
69
|
end
|
75
70
|
|
71
|
+
def as_key
|
72
|
+
[type, view&.to_s, version]
|
73
|
+
end
|
74
|
+
|
76
75
|
def hash
|
77
|
-
|
76
|
+
as_key.hash
|
77
|
+
end
|
78
|
+
|
79
|
+
def override_suffix(suffix)
|
80
|
+
with(suffix: suffix)
|
81
|
+
end
|
82
|
+
|
83
|
+
def suffix
|
84
|
+
return opts[:suffix] if opts.key?(:suffix)
|
85
|
+
|
86
|
+
schema = schema_for(self)
|
87
|
+
schema.type_attributes.fetch(:suffix, 'json')
|
78
88
|
end
|
79
89
|
|
80
90
|
def to_str(qualifier = nil)
|
81
|
-
# TODO: remove warning by slicing out these arguments if they don't appear in the format
|
82
91
|
qualified(
|
83
92
|
qualifier,
|
84
|
-
|
93
|
+
__getobj__.media_type_name_for.call(
|
94
|
+
type: opts[:type],
|
95
|
+
view: opts[:view],
|
96
|
+
version: opts[:version],
|
97
|
+
suffix: suffix
|
98
|
+
)
|
85
99
|
)
|
86
100
|
end
|
87
101
|
|
102
|
+
def available_validations
|
103
|
+
return [] if !validatable?
|
104
|
+
[self]
|
105
|
+
end
|
106
|
+
|
88
107
|
def valid?(output, **validation_opts)
|
89
|
-
|
108
|
+
raise ArgumentError, "Unable to validate #{to_s} type without a corresponding validation. Please mark objects that should be empty with 'empty'." unless validatable?
|
109
|
+
|
110
|
+
__getobj__.valid_unsafe?(
|
90
111
|
output,
|
91
112
|
self,
|
92
113
|
**validation_opts
|
@@ -94,7 +115,9 @@ module MediaTypes
|
|
94
115
|
end
|
95
116
|
|
96
117
|
def validate!(output, **validation_opts)
|
97
|
-
|
118
|
+
raise ArgumentError, "Unable to validate #{to_s} type without a corresponding validation. Please mark objects that should be empty with 'empty'." unless validatable?
|
119
|
+
|
120
|
+
__getobj__.validate_unsafe!(
|
98
121
|
output,
|
99
122
|
self,
|
100
123
|
**validation_opts
|
@@ -102,11 +125,14 @@ module MediaTypes
|
|
102
125
|
end
|
103
126
|
|
104
127
|
def validatable?
|
128
|
+
return false unless media_type_combinations.include? as_key
|
129
|
+
|
105
130
|
__getobj__.validatable?(self)
|
106
131
|
end
|
107
132
|
|
108
133
|
alias inspect to_str
|
109
134
|
alias to_s to_str
|
135
|
+
alias identifier to_str
|
110
136
|
|
111
137
|
private
|
112
138
|
|
data/lib/media_types/dsl.rb
CHANGED
@@ -1,43 +1,72 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'media_types/constructable'
|
4
|
-
require 'media_types/defaults'
|
5
|
-
require 'media_types/registrar'
|
6
4
|
require 'media_types/validations'
|
7
5
|
|
6
|
+
require 'media_types/dsl/errors'
|
7
|
+
|
8
8
|
module MediaTypes
|
9
9
|
module Dsl
|
10
10
|
def self.included(base)
|
11
11
|
base.extend ClassMethods
|
12
12
|
base.class_eval do
|
13
13
|
class << self
|
14
|
+
attr_accessor :media_type_name_for, :media_type_combinations, :media_type_validations, :symbol_keys
|
15
|
+
|
14
16
|
private
|
15
17
|
|
16
|
-
attr_accessor :media_type_constructable, :symbol_base, :media_type_registrar
|
18
|
+
attr_accessor :media_type_constructable, :symbol_base, :media_type_registrar
|
17
19
|
end
|
20
|
+
base.media_type_combinations = Set.new
|
18
21
|
end
|
19
22
|
end
|
20
23
|
|
21
24
|
module ClassMethods
|
22
|
-
|
23
25
|
def to_constructable
|
26
|
+
raise UninitializedConstructable if media_type_constructable.nil?
|
27
|
+
|
24
28
|
media_type_constructable.dup.tap do |constructable|
|
25
29
|
constructable.__setobj__(self)
|
26
30
|
end
|
27
31
|
end
|
28
32
|
|
29
|
-
def
|
30
|
-
|
33
|
+
def symbol_keys?
|
34
|
+
if symbol_keys.nil?
|
35
|
+
MediaTypes.get_key_expectation(self)
|
36
|
+
else
|
37
|
+
symbol_keys
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def string_keys?
|
42
|
+
!symbol_keys?
|
43
|
+
end
|
44
|
+
|
45
|
+
def valid?(output, **opts)
|
46
|
+
to_constructable.valid?(output, **opts)
|
47
|
+
end
|
48
|
+
|
49
|
+
def valid_unsafe?(output, media_type = to_constructable, **opts)
|
50
|
+
opts[:expected_key_type] = string_keys? ? String : Symbol
|
51
|
+
validations.find(media_type).valid?(output, backtrace: ['.'], **opts)
|
52
|
+
end
|
53
|
+
|
54
|
+
def validate!(output, **opts)
|
55
|
+
assert_sane!
|
56
|
+
to_constructable.validate!(output, **opts)
|
31
57
|
end
|
32
58
|
|
33
|
-
def
|
34
|
-
|
59
|
+
def validate_unsafe!(output, media_type = to_constructable, **opts)
|
60
|
+
opts[:expected_key_type] = string_keys? ? String : Symbol
|
61
|
+
validations.find(media_type).validate(output, backtrace: ['.'], **opts)
|
35
62
|
end
|
36
63
|
|
37
64
|
def validatable?(media_type = to_constructable)
|
38
65
|
return false unless validations
|
39
66
|
|
40
|
-
validations.find(
|
67
|
+
resolved = validations.find(media_type, -> { nil })
|
68
|
+
|
69
|
+
!resolved.nil?
|
41
70
|
end
|
42
71
|
|
43
72
|
def register
|
@@ -47,43 +76,95 @@ module MediaTypes
|
|
47
76
|
end
|
48
77
|
end
|
49
78
|
|
50
|
-
|
79
|
+
def view(v)
|
80
|
+
to_constructable.view(v)
|
81
|
+
end
|
51
82
|
|
52
|
-
def
|
83
|
+
def version(v)
|
84
|
+
to_constructable.version(v)
|
85
|
+
end
|
53
86
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
87
|
+
def identifier_format
|
88
|
+
self.media_type_name_for = proc do |type:, view:, version:, suffix:|
|
89
|
+
yield(type: type, view: view, version: version, suffix: suffix)
|
58
90
|
end
|
91
|
+
end
|
59
92
|
|
60
|
-
|
61
|
-
|
62
|
-
.suffix(defaults.fetch(:suffix) { nil })
|
63
|
-
.view(defaults.fetch(:view) { nil })
|
64
|
-
self
|
93
|
+
def identifier
|
94
|
+
to_constructable.to_s
|
65
95
|
end
|
66
96
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
97
|
+
def available_validations
|
98
|
+
media_type_combinations.map do |a|
|
99
|
+
_, view, version = a
|
100
|
+
view(view).version(version)
|
101
|
+
end
|
102
|
+
end
|
70
103
|
|
71
|
-
|
104
|
+
def schema_for(constructable)
|
105
|
+
validations.find(constructable)
|
72
106
|
end
|
73
107
|
|
74
|
-
def
|
75
|
-
return
|
76
|
-
self.media_type_registrar = Registrar.new(self, symbol: symbol, &block)
|
108
|
+
def assert_sane!
|
109
|
+
return if media_type_validations.scheme.asserted_sane?
|
77
110
|
|
78
|
-
|
111
|
+
media_type_validations.run_fixture_validations(symbol_keys?)
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def use_name(name)
|
117
|
+
if media_type_name_for.nil?
|
118
|
+
self.media_type_name_for = proc do |type:, view:, version:, suffix:|
|
119
|
+
resolved_org = nil
|
120
|
+
if defined?(organisation)
|
121
|
+
resolved_org = organisation
|
122
|
+
else
|
123
|
+
resolved_org = MediaTypes.get_organisation(self)
|
124
|
+
|
125
|
+
if resolved_org.nil?
|
126
|
+
raise OrganisationNotSetError,
|
127
|
+
format('Implement the class method "organisation" in %<klass>s or specify a global organisation using MediaTypes::set_organisation', klass: self)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
raise ArgumentError, 'Unable to create a name for a schema with a nil name.' if type.nil?
|
131
|
+
raise ArgumentError, 'Unable to create a name for a schema with a nil organisation.' if resolved_org.nil?
|
132
|
+
|
133
|
+
result = "application/vnd.#{resolved_org}.#{type}"
|
134
|
+
result += ".v#{version}" unless version.nil?
|
135
|
+
result += ".#{view}" unless view.nil?
|
136
|
+
result += "+#{suffix}" unless suffix.nil?
|
137
|
+
result
|
138
|
+
end
|
139
|
+
end
|
140
|
+
self.media_type_constructable = Constructable.new(self, type: name)
|
141
|
+
end
|
142
|
+
|
143
|
+
def expect_string_keys
|
144
|
+
raise KeyTypeExpectationError, 'Key expectation already set' unless symbol_keys.nil?
|
145
|
+
|
146
|
+
self.symbol_keys = false
|
147
|
+
end
|
148
|
+
|
149
|
+
def expect_symbol_keys
|
150
|
+
raise KeyTypeExpectationError, 'Key expectation already set' unless symbol_keys.nil?
|
151
|
+
|
152
|
+
self.symbol_keys = true
|
79
153
|
end
|
80
154
|
|
81
155
|
def validations(&block)
|
82
|
-
return
|
156
|
+
return lookup_validations unless block_given?
|
157
|
+
|
83
158
|
self.media_type_validations = Validations.new(to_constructable, &block)
|
84
159
|
|
85
160
|
self
|
86
161
|
end
|
162
|
+
|
163
|
+
def lookup_validations
|
164
|
+
raise MissingValidationError, "No validations defined for #{name}" if media_type_validations.nil?
|
165
|
+
|
166
|
+
media_type_validations
|
167
|
+
end
|
87
168
|
end
|
88
169
|
end
|
89
170
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MediaTypes
|
4
|
+
module Dsl
|
5
|
+
class UninitializedConstructable < RuntimeError
|
6
|
+
def message
|
7
|
+
'Unable to generate constructable without a name, make sure to have called `use_name(name)` before.'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Raised when an error occurs during setting expected key type
|
12
|
+
class KeyTypeExpectationError < StandardError; end
|
13
|
+
|
14
|
+
class MissingValidationError < StandardError; end
|
15
|
+
|
16
|
+
class OrganisationNotSetError < StandardError; end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
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
|
data/lib/media_types/scheme.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
4
|
+
|
3
5
|
require 'media_types/scheme/validation_options'
|
4
6
|
require 'media_types/scheme/enumeration_context'
|
5
7
|
require 'media_types/scheme/errors'
|
@@ -18,6 +20,48 @@ require 'media_types/scheme/output_type_guard'
|
|
18
20
|
require 'media_types/scheme/rules_exhausted_guard'
|
19
21
|
|
20
22
|
module MediaTypes
|
23
|
+
class AssertionError < StandardError
|
24
|
+
def initialize(errors)
|
25
|
+
@fixture_errors = errors
|
26
|
+
end
|
27
|
+
|
28
|
+
def message
|
29
|
+
fixture_errors.map(&:message).join(', ')
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :fixture_errors
|
33
|
+
end
|
34
|
+
|
35
|
+
class UnexpectedValidationResultError < StandardError
|
36
|
+
def initialize(fixture_caller, error)
|
37
|
+
self.fixture_caller = fixture_caller
|
38
|
+
self.error = error
|
39
|
+
end
|
40
|
+
|
41
|
+
def message
|
42
|
+
format(
|
43
|
+
'%<caller_path>s:%<caller_line>s -> %<error>s',
|
44
|
+
caller_path: fixture_caller.path,
|
45
|
+
caller_line: fixture_caller.lineno,
|
46
|
+
error: error.is_a?(MediaTypes::Scheme::ValidationError) ? "#{error.class}:#{error.message}" : error
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_accessor :fixture_caller, :error
|
51
|
+
end
|
52
|
+
|
53
|
+
class FixtureData
|
54
|
+
def initialize(caller:, fixture:, expect_to_pass:)
|
55
|
+
self.caller = caller
|
56
|
+
self.fixture = fixture
|
57
|
+
self.expect_to_pass = expect_to_pass
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_accessor :caller, :fixture, :expect_to_pass
|
61
|
+
|
62
|
+
alias expect_to_pass? expect_to_pass
|
63
|
+
end
|
64
|
+
|
21
65
|
##
|
22
66
|
# Media Type Schemes can validate content to a media type, by itself. Used by the `validations` dsl.
|
23
67
|
#
|
@@ -51,10 +95,18 @@ module MediaTypes
|
|
51
95
|
#
|
52
96
|
def initialize(allow_empty: false, expected_type: ::Object, &block)
|
53
97
|
self.rules = Rules.new(allow_empty: allow_empty, expected_type: expected_type)
|
98
|
+
self.type_attributes = {}
|
99
|
+
self.fixtures = []
|
100
|
+
self.asserted_sane = false
|
54
101
|
|
55
102
|
instance_exec(&block) if block_given?
|
56
103
|
end
|
57
104
|
|
105
|
+
attr_accessor :type_attributes, :fixtures
|
106
|
+
attr_reader :rules, :asserted_sane
|
107
|
+
|
108
|
+
alias asserted_sane? asserted_sane
|
109
|
+
|
58
110
|
##
|
59
111
|
# Checks if the +output+ is valid
|
60
112
|
#
|
@@ -93,6 +145,7 @@ module MediaTypes
|
|
93
145
|
#
|
94
146
|
def validate(output, options = nil, **opts)
|
95
147
|
options ||= ValidationOptions.new(**opts)
|
148
|
+
options.context = output
|
96
149
|
|
97
150
|
catch(:end) do
|
98
151
|
validate!(output, options, context: nil)
|
@@ -149,7 +202,11 @@ module MediaTypes
|
|
149
202
|
# MyMedia.valid?({ foo: { bar: 'my-string' }})
|
150
203
|
# # => true
|
151
204
|
#
|
152
|
-
def attribute(key, type =
|
205
|
+
def attribute(key, type = nil, optional: false, **opts, &block)
|
206
|
+
raise ConflictingTypeDefinitionError, 'You cannot apply a block to a non-hash typed attribute, either remove the type or the block' if type != ::Hash && block_given? && !type.nil?
|
207
|
+
|
208
|
+
type ||= ::Object
|
209
|
+
|
153
210
|
if block_given?
|
154
211
|
return collection(key, expected_type: ::Hash, optional: optional, **opts, &block)
|
155
212
|
end
|
@@ -202,6 +259,8 @@ module MediaTypes
|
|
202
259
|
# # => true
|
203
260
|
#
|
204
261
|
def any(scheme = nil, expected_type: ::Hash, allow_empty: false, &block)
|
262
|
+
raise ConflictingTypeDefinitionError, 'You cannot apply a block to a non-hash typed property, either remove the type or the block' if scheme != ::Hash && block_given? && !scheme.nil?
|
263
|
+
|
205
264
|
unless block_given?
|
206
265
|
if scheme.is_a?(Scheme)
|
207
266
|
return rules.default = scheme
|
@@ -289,6 +348,8 @@ module MediaTypes
|
|
289
348
|
# # => true
|
290
349
|
#
|
291
350
|
def collection(key, scheme = nil, allow_empty: false, expected_type: ::Array, optional: false, &block)
|
351
|
+
raise ConflictingTypeDefinitionError, 'You cannot apply a block to a non-hash typed collection, either remove the type or the block' if scheme != ::Hash && block_given? && !scheme.nil?
|
352
|
+
|
292
353
|
unless block_given?
|
293
354
|
return rules.add(
|
294
355
|
key,
|
@@ -346,6 +407,21 @@ module MediaTypes
|
|
346
407
|
end.link(*args, **opts, &block)
|
347
408
|
end
|
348
409
|
|
410
|
+
##
|
411
|
+
# Mark object as a valid empty object
|
412
|
+
#
|
413
|
+
# @example Empty object
|
414
|
+
#
|
415
|
+
# class MyMedia
|
416
|
+
# include MediaTypes::Dsl
|
417
|
+
#
|
418
|
+
# validations do
|
419
|
+
# empty
|
420
|
+
# end
|
421
|
+
# end
|
422
|
+
def empty
|
423
|
+
end
|
424
|
+
|
349
425
|
def inspect(indentation = 0)
|
350
426
|
tabs = ' ' * indentation
|
351
427
|
[
|
@@ -355,8 +431,83 @@ module MediaTypes
|
|
355
431
|
].join("\n")
|
356
432
|
end
|
357
433
|
|
434
|
+
def assert_pass(fixture)
|
435
|
+
reduced_stack = remove_current_dir_from_stack(caller_locations)
|
436
|
+
@fixtures << FixtureData.new(caller: reduced_stack.first, fixture: fixture, expect_to_pass: true)
|
437
|
+
end
|
438
|
+
|
439
|
+
def assert_fail(fixture)
|
440
|
+
reduced_stack = remove_current_dir_from_stack(caller_locations)
|
441
|
+
@fixtures << FixtureData.new(caller: reduced_stack.first, fixture: fixture, expect_to_pass: false)
|
442
|
+
end
|
443
|
+
|
444
|
+
# Removes all calls originating in current dir from given stack
|
445
|
+
# We need this so that we find out the caller of an assert_pass/fail in the caller_locations
|
446
|
+
# Which gets polluted by Scheme consecutively executing blocks within the validation blocks
|
447
|
+
def remove_current_dir_from_stack(stack)
|
448
|
+
stack.reject { |location| location.path.include?(__dir__) }
|
449
|
+
end
|
450
|
+
|
451
|
+
def validate_scheme_fixtures(expect_symbol_keys, backtrace)
|
452
|
+
@fixtures.map do |fixture_data|
|
453
|
+
begin
|
454
|
+
validate_fixture(fixture_data, expect_symbol_keys, backtrace)
|
455
|
+
nil
|
456
|
+
rescue UnexpectedValidationResultError => e
|
457
|
+
e
|
458
|
+
end
|
459
|
+
end.compact
|
460
|
+
end
|
461
|
+
|
462
|
+
def validate_nested_scheme_fixtures(expect_symbol_keys, backtrace)
|
463
|
+
@rules.flat_map do |key, rule|
|
464
|
+
next unless rule.is_a?(Scheme) || rule.is_a?(Links)
|
465
|
+
|
466
|
+
begin
|
467
|
+
rule.run_fixture_validations(expect_symbol_keys, backtrace.dup.append(key))
|
468
|
+
nil
|
469
|
+
rescue AssertionError => e
|
470
|
+
e.fixture_errors
|
471
|
+
end
|
472
|
+
end.compact
|
473
|
+
end
|
474
|
+
|
475
|
+
def validate_default_scheme_fixtures(expect_symbol_keys, backtrace)
|
476
|
+
return [] unless @rules.default.is_a?(Scheme)
|
477
|
+
|
478
|
+
@rules.default.run_fixture_validations(expect_symbol_keys, backtrace.dup.append('*'))
|
479
|
+
[]
|
480
|
+
rescue AssertionError => e
|
481
|
+
e.fixture_errors
|
482
|
+
end
|
483
|
+
|
484
|
+
def run_fixture_validations(expect_symbol_keys, backtrace = [])
|
485
|
+
fixture_errors = validate_scheme_fixtures(expect_symbol_keys, backtrace)
|
486
|
+
fixture_errors += validate_nested_scheme_fixtures(expect_symbol_keys, backtrace)
|
487
|
+
fixture_errors += validate_default_scheme_fixtures(expect_symbol_keys, backtrace)
|
488
|
+
|
489
|
+
raise AssertionError, fixture_errors unless fixture_errors.empty?
|
490
|
+
|
491
|
+
self.asserted_sane = true
|
492
|
+
end
|
493
|
+
|
494
|
+
def validate_fixture(fixture_data, expect_symbol_keys, backtrace = [])
|
495
|
+
json = JSON.parse(fixture_data.fixture, { symbolize_names: expect_symbol_keys })
|
496
|
+
expected_key_type = expect_symbol_keys ? Symbol : String
|
497
|
+
|
498
|
+
begin
|
499
|
+
validate(json, expected_key_type: expected_key_type, backtrace: backtrace)
|
500
|
+
unless fixture_data.expect_to_pass?
|
501
|
+
raise UnexpectedValidationResultError.new(fixture_data.caller, 'No error encounterd whilst expecting to')
|
502
|
+
end
|
503
|
+
rescue MediaTypes::Scheme::ValidationError => e
|
504
|
+
raise UnexpectedValidationResultError.new(fixture_data.caller, e) if fixture_data.expect_to_pass?
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
358
508
|
private
|
359
509
|
|
360
|
-
|
510
|
+
attr_writer :rules, :asserted_sane
|
511
|
+
|
361
512
|
end
|
362
513
|
end
|