respect 0.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.
Files changed (97) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +289 -0
  3. data/RELATED_WORK.md +40 -0
  4. data/RELEASE_NOTES.md +23 -0
  5. data/Rakefile +31 -0
  6. data/STATUS_MATRIX.html +137 -0
  7. data/lib/respect.rb +231 -0
  8. data/lib/respect/any_schema.rb +22 -0
  9. data/lib/respect/array_def.rb +28 -0
  10. data/lib/respect/array_schema.rb +203 -0
  11. data/lib/respect/boolean_schema.rb +32 -0
  12. data/lib/respect/composite_schema.rb +86 -0
  13. data/lib/respect/core_statements.rb +206 -0
  14. data/lib/respect/datetime_schema.rb +27 -0
  15. data/lib/respect/def_without_name.rb +6 -0
  16. data/lib/respect/divisible_by_validator.rb +20 -0
  17. data/lib/respect/doc_helper.rb +24 -0
  18. data/lib/respect/doc_parser.rb +37 -0
  19. data/lib/respect/dsl_dumper.rb +181 -0
  20. data/lib/respect/equal_to_validator.rb +20 -0
  21. data/lib/respect/fake_name_proxy.rb +116 -0
  22. data/lib/respect/float_schema.rb +27 -0
  23. data/lib/respect/format_validator.rb +136 -0
  24. data/lib/respect/global_def.rb +79 -0
  25. data/lib/respect/greater_than_or_equal_to_validator.rb +19 -0
  26. data/lib/respect/greater_than_validator.rb +19 -0
  27. data/lib/respect/has_constraints.rb +34 -0
  28. data/lib/respect/hash_def.rb +40 -0
  29. data/lib/respect/hash_schema.rb +218 -0
  30. data/lib/respect/in_validator.rb +19 -0
  31. data/lib/respect/integer_schema.rb +27 -0
  32. data/lib/respect/ip_addr_schema.rb +23 -0
  33. data/lib/respect/ipv4_addr_schema.rb +27 -0
  34. data/lib/respect/ipv6_addr_schema.rb +27 -0
  35. data/lib/respect/items_def.rb +21 -0
  36. data/lib/respect/json_schema_html_formatter.rb +143 -0
  37. data/lib/respect/less_than_or_equal_to_validator.rb +19 -0
  38. data/lib/respect/less_than_validator.rb +19 -0
  39. data/lib/respect/match_validator.rb +19 -0
  40. data/lib/respect/max_length_validator.rb +20 -0
  41. data/lib/respect/min_length_validator.rb +20 -0
  42. data/lib/respect/multiple_of_validator.rb +10 -0
  43. data/lib/respect/null_schema.rb +26 -0
  44. data/lib/respect/numeric_schema.rb +33 -0
  45. data/lib/respect/org3_dumper.rb +213 -0
  46. data/lib/respect/regexp_schema.rb +19 -0
  47. data/lib/respect/schema.rb +285 -0
  48. data/lib/respect/schema_def.rb +16 -0
  49. data/lib/respect/string_schema.rb +21 -0
  50. data/lib/respect/unit_test_helper.rb +37 -0
  51. data/lib/respect/uri_schema.rb +23 -0
  52. data/lib/respect/utc_time_schema.rb +17 -0
  53. data/lib/respect/validator.rb +51 -0
  54. data/lib/respect/version.rb +3 -0
  55. data/test/any_schema_test.rb +79 -0
  56. data/test/array_def_test.rb +113 -0
  57. data/test/array_schema_test.rb +487 -0
  58. data/test/boolean_schema_test.rb +89 -0
  59. data/test/composite_schema_test.rb +30 -0
  60. data/test/datetime_schema_test.rb +83 -0
  61. data/test/doc_helper_test.rb +34 -0
  62. data/test/doc_parser_test.rb +109 -0
  63. data/test/dsl_dumper_test.rb +395 -0
  64. data/test/fake_name_proxy_test.rb +138 -0
  65. data/test/float_schema_test.rb +146 -0
  66. data/test/format_validator_test.rb +224 -0
  67. data/test/hash_def_test.rb +126 -0
  68. data/test/hash_schema_test.rb +613 -0
  69. data/test/integer_schema_test.rb +142 -0
  70. data/test/ip_addr_schema_test.rb +78 -0
  71. data/test/ipv4_addr_schema_test.rb +71 -0
  72. data/test/ipv6_addr_schema_test.rb +71 -0
  73. data/test/json_schema_html_formatter_test.rb +214 -0
  74. data/test/null_schema_test.rb +46 -0
  75. data/test/numeric_schema_test.rb +294 -0
  76. data/test/org3_dumper_test.rb +784 -0
  77. data/test/regexp_schema_test.rb +54 -0
  78. data/test/respect_test.rb +108 -0
  79. data/test/schema_def_test.rb +405 -0
  80. data/test/schema_test.rb +290 -0
  81. data/test/string_schema_test.rb +209 -0
  82. data/test/support/circle.rb +11 -0
  83. data/test/support/color.rb +24 -0
  84. data/test/support/point.rb +11 -0
  85. data/test/support/respect/circle_schema.rb +16 -0
  86. data/test/support/respect/color_def.rb +19 -0
  87. data/test/support/respect/color_schema.rb +33 -0
  88. data/test/support/respect/point_schema.rb +19 -0
  89. data/test/support/respect/rgba_schema.rb +20 -0
  90. data/test/support/respect/universal_validator.rb +25 -0
  91. data/test/support/respect/user_macros.rb +12 -0
  92. data/test/support/rgba.rb +11 -0
  93. data/test/test_helper.rb +90 -0
  94. data/test/uri_schema_test.rb +54 -0
  95. data/test/utc_time_schema_test.rb +63 -0
  96. data/test/validator_test.rb +22 -0
  97. metadata +288 -0
@@ -0,0 +1,23 @@
1
+ require 'ipaddr'
2
+
3
+ module Respect
4
+ # Validate a string containing an IPv4 or IPv6 address but also accept IPAddr object.
5
+ class IPAddrSchema < StringSchema
6
+
7
+ def validate_type(object)
8
+ case object
9
+ when NilClass
10
+ if allow_nil?
11
+ nil
12
+ else
13
+ raise ValidationError, "object is nil but this #{self.class} does not allow nil"
14
+ end
15
+ when IPAddr
16
+ object
17
+ else
18
+ FormatValidator.new(:ip_addr).validate(object)
19
+ end
20
+ end
21
+
22
+ end # class IPAddrSchema
23
+ end # module Respect
@@ -0,0 +1,27 @@
1
+ require 'ipaddr'
2
+
3
+ module Respect
4
+ # Validate a string containing an IPv4 address but also accept IPAddr object.
5
+ class Ipv4AddrSchema < StringSchema
6
+
7
+ def validate_type(object)
8
+ case object
9
+ when NilClass
10
+ if allow_nil?
11
+ nil
12
+ else
13
+ raise ValidationError, "object is nil but this #{self.class} does not allow nil"
14
+ end
15
+ when IPAddr
16
+ if object.ipv4?
17
+ object
18
+ else
19
+ raise ValidationError, "IPAddr object '#{object}' is not IPv4"
20
+ end
21
+ else
22
+ FormatValidator.new(:ipv4_addr).validate(object)
23
+ end
24
+ end
25
+
26
+ end # class Ipv4AddrSchema
27
+ end # module Respect
@@ -0,0 +1,27 @@
1
+ require 'ipaddr'
2
+
3
+ module Respect
4
+ # Validate a string containing an IPv6 address but also accept IPAddr object.
5
+ class Ipv6AddrSchema < StringSchema
6
+
7
+ def validate_type(object)
8
+ case object
9
+ when NilClass
10
+ if allow_nil?
11
+ nil
12
+ else
13
+ raise ValidationError, "object is nil but this #{self.class} does not allow nil"
14
+ end
15
+ when IPAddr
16
+ if object.ipv6?
17
+ object
18
+ else
19
+ raise ValidationError, "IPAddr object '#{object}' is not IPv6"
20
+ end
21
+ else
22
+ FormatValidator.new(:ipv6_addr).validate(object)
23
+ end
24
+ end
25
+
26
+ end # class Ipv6AddrSchema
27
+ end # module Respect
@@ -0,0 +1,21 @@
1
+ module Respect
2
+ class ItemsDef < GlobalDef
3
+ include_core_statements
4
+ include DefWithoutName
5
+
6
+ def initialize
7
+ @items = []
8
+ end
9
+
10
+ private
11
+
12
+ def evaluation_result
13
+ @items
14
+ end
15
+
16
+ def update_context(name, schema)
17
+ @items << schema
18
+ schema
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,143 @@
1
+ module Respect
2
+ # Format a JSON schema in HTML with CSS class to allow highlighting.
3
+ class JSONSchemaHTMLFormatter
4
+ def initialize(json_schema)
5
+ @indent_level = 0
6
+ @indent_size = 2
7
+ @json_schema = json_schema
8
+ @css_class ||= {
9
+ json_highlight: "json_highlight",
10
+ plain: "plain",
11
+ key: "key",
12
+ keyword: "keyword",
13
+ string: "string",
14
+ numeric: "numeric",
15
+ comment: "comment",
16
+ }
17
+ end
18
+
19
+ attr_accessor :css_class
20
+
21
+ def dump(output = "")
22
+ @output = output
23
+ @output ||= String.new
24
+ @output << "<div class=\"#{css_class[:json_highlight]}\"><pre>"
25
+ @output << dump_json(@json_schema)
26
+ @output << "</pre></div>\n"
27
+ @output
28
+ end
29
+
30
+ private
31
+
32
+ def indent(&block)
33
+ @indent_level += 1
34
+ block.call
35
+ @indent_level -= 1
36
+ end
37
+
38
+ def newline
39
+ "\n#{indentation}"
40
+ end
41
+
42
+ def indentation
43
+ " " * @indent_level * @indent_size
44
+ end
45
+
46
+ def dump_json(json)
47
+ case json
48
+ when Hash
49
+ dump_hash(json)
50
+ when Array
51
+ dump_array(json)
52
+ else
53
+ dump_terminal(json)
54
+ end
55
+ end
56
+
57
+ def dump_hash(json)
58
+ result = plain_text("{")
59
+ indent do
60
+ result << newline
61
+ keys = json.keys
62
+ keys.each_with_index do |key, i|
63
+ if json[key].is_a? Hash
64
+ doc = ""
65
+ if json[key].key? "title"
66
+ doc << json[key]["title"]
67
+ json[key].delete("title")
68
+ end
69
+ if json[key].key? "description"
70
+ doc << "\n\n"
71
+ doc << json[key]["description"]
72
+ json[key].delete("description")
73
+ end
74
+ unless doc.empty?
75
+ result << comment(doc)
76
+ result << newline
77
+ end
78
+ end
79
+ result << span(css_class[:key], key.to_s.inspect) << plain_text(":") << " "
80
+ result << dump_json(json[key])
81
+ if i < keys.size - 1
82
+ result << plain_text(",")
83
+ result << newline
84
+ end
85
+ end
86
+ end
87
+ result << newline
88
+ result << plain_text("}")
89
+ result
90
+ end
91
+
92
+ def dump_array(json)
93
+ result = plain_text("[")
94
+ indent do
95
+ result << newline
96
+ json.each_with_index do |item, i|
97
+ result << dump_json(item)
98
+ if i < json.size - 1
99
+ result << plain_text(",")
100
+ result << newline
101
+ end
102
+ end
103
+ end
104
+ result << newline
105
+ result << plain_text("]")
106
+ result
107
+ end
108
+
109
+ def dump_terminal(json)
110
+ css = (case json
111
+ when TrueClass, FalseClass
112
+ css_class[:keyword]
113
+ when String
114
+ css_class[:string]
115
+ when Numeric
116
+ css_class[:numeric]
117
+ else
118
+ css_class[:plain]
119
+ end)
120
+ span(css, json.inspect)
121
+ end
122
+
123
+ def plain_text(text)
124
+ span(css_class[:plain], text)
125
+ end
126
+
127
+ def tag(tag, klass, value)
128
+ "<#{tag} class=\"#{klass}\">#{value}</#{tag}>"
129
+ end
130
+
131
+ def span(klass, value)
132
+ tag("span", klass, value)
133
+ end
134
+
135
+ def comment(text)
136
+ s = text.dup
137
+ s.sub!(/\n*\Z/m, '')
138
+ s.gsub!(/\n/m, "\n#{indentation}// ")
139
+ s.gsub!(/\s*\n/m, "\n")
140
+ span(css_class[:comment], "// #{s}")
141
+ end
142
+ end
143
+ end # module Respect
@@ -0,0 +1,19 @@
1
+ module Respect
2
+ class LessThanOrEqualToValidator < Validator
3
+ def initialize(max)
4
+ @max = max
5
+ end
6
+
7
+ def validate(value)
8
+ unless value <= @max
9
+ raise ValidationError, "#{value} is not less than or equal to #@max"
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def to_h_org3
16
+ { "maximum" => @max }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Respect
2
+ class LessThanValidator < Validator
3
+ def initialize(max)
4
+ @max = max
5
+ end
6
+
7
+ def validate(value)
8
+ unless value < @max
9
+ raise ValidationError, "#{value} is not less than #@max"
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def to_h_org3
16
+ { "maximum" => @max, "exclusiveMaximum" => true }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Respect
2
+ class MatchValidator < Validator
3
+ def initialize(pattern)
4
+ @pattern = pattern
5
+ end
6
+
7
+ def validate(value)
8
+ unless value =~ @pattern
9
+ raise ValidationError, "#{value.inspect} does not match #{@pattern.inspect}"
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def to_h_org3
16
+ { "pattern" => @pattern.source }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module Respect
2
+ class MaxLengthValidator < Validator
3
+ def initialize(max_length)
4
+ @max_length = max_length
5
+ end
6
+
7
+ def validate(value)
8
+ unless value.length <= @max_length
9
+ raise ValidationError,
10
+ "#{value.inspect} must be at most #@max_length long but is #{value.length}"
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def to_h_org3
17
+ { 'maxLength' => @max_length }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Respect
2
+ class MinLengthValidator < Validator
3
+ def initialize(min_length)
4
+ @min_length = min_length
5
+ end
6
+
7
+ def validate(value)
8
+ unless value.length >= @min_length
9
+ raise ValidationError,
10
+ "#{value.inspect} must be at least #@min_length long but is #{value.length}"
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def to_h_org3
17
+ { 'minLength' => @min_length }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ module Respect
2
+ class MultipleOfValidator < DivisibleByValidator
3
+ def validate(value)
4
+ super
5
+ rescue ValidationError => e
6
+ raise ValidationError,
7
+ e.message.sub(/\bdivisible by\b/, "a multiple of")
8
+ end
9
+ end
10
+ end # module Respect
@@ -0,0 +1,26 @@
1
+ module Respect
2
+ class NullSchema < Schema
3
+
4
+ public_class_method :new
5
+
6
+ def validate(object)
7
+ case object
8
+ when String
9
+ if object == "null"
10
+ self.sanitized_object = nil
11
+ true
12
+ else
13
+ raise ValidationError,
14
+ "expected 'null' value but got '#{object}:#{object.class}'"
15
+ end
16
+ when NilClass
17
+ self.sanitized_object = nil
18
+ true
19
+ else
20
+ raise ValidationError,
21
+ "object is not of null type but a #{object.class}"
22
+ end
23
+ end
24
+
25
+ end # class NullSchema
26
+ end # module Respect
@@ -0,0 +1,33 @@
1
+ module Respect
2
+ class NumericSchema < Schema
3
+ include HasConstraints
4
+
5
+ public_class_method :new
6
+
7
+ def validate_type(object)
8
+ case object
9
+ when String
10
+ if match_data = /^[-+]?\d+(\.\d+)?$/.match(object)
11
+ if match_data[1]
12
+ object.to_f
13
+ else
14
+ object.to_i
15
+ end
16
+ else
17
+ raise ValidationError, "malformed numeric value: `#{object}'"
18
+ end
19
+ when Integer, Float
20
+ object
21
+ when NilClass
22
+ if allow_nil?
23
+ nil
24
+ else
25
+ raise ValidationError, "object is nil but this #{self.class} does not allow nil"
26
+ end
27
+ else
28
+ raise ValidationError, "object is not a numeric but a '#{object.class}'"
29
+ end
30
+ end
31
+
32
+ end # class NumericSchema
33
+ end # module Respect
@@ -0,0 +1,213 @@
1
+ module Respect
2
+ # Dump a schema to a hash representation following the format specified
3
+ # on {json-schema.org standard draft v3}[http://tools.ietf.org/id/draft-zyp-json-schema-03.html].
4
+ #
5
+ # The current implementation covers all the _Schema_ and _Validator_ classes
6
+ # defined in this package. User-defined {Schema} and {Validator} are not guarantee
7
+ # to work and may never work in the future. The _JSON-Schema_ standard is
8
+ # a general purpose standard and include only primitive type so it is
9
+ # very unlikely that it will include your custom schema and validator
10
+ # out of the box. However, if you can translate your schema/validator
11
+ # as a composition of primitive type mentioned in the standard it will work.
12
+ # That's why it is recommended to sub-class {CompositeSchema} when creating
13
+ # your own schema. User-defined are not properly supported yet as the
14
+ # API of this dumper is *experimental*. However, an easy way to extend
15
+ # both the schema and validator class hierarchies will be added in
16
+ # future releases.
17
+ class Org3Dumper
18
+
19
+ # Translation table mapping DSL options with json-schema.org v3
20
+ # options. The associated hash is injected in the output. Values
21
+ # are interpreted as follow:
22
+ # - :option_value represent the option value passed to the DSL option parameter.
23
+ # - a proc is called with the option value as argument and the result is used
24
+ # as the value for the output key if it is not nil.
25
+ # - other value are inserted verbatim.
26
+ OPTION_MAP = {
27
+ min_size: { 'minItems' => :option_value },
28
+ max_size: { 'maxItems' => :option_value },
29
+ uniq: { "uniqueItems" => Proc.new{|v| v if v } },
30
+ default: { "default" => Proc.new{|v| v unless v.nil? } },
31
+ required: { "required" => Proc.new{|v| true if required? } },
32
+ }.freeze
33
+
34
+ def initialize(schema)
35
+ @schema = schema
36
+ end
37
+
38
+ def dump(output = nil)
39
+ @output = output
40
+ @output ||= Hash.new
41
+ @output = dump_schema(@schema, ignore: [:required])
42
+ @output
43
+ end
44
+
45
+ attr_reader :output
46
+
47
+ def dump_schema(schema, *args)
48
+ dispatch("dump_schema", schema.class, schema, *args)
49
+ end
50
+
51
+ def dump_schema_for_schema(schema, params = {})
52
+ return nil if !schema.documented?
53
+ h = {}
54
+ h['type'] = dump_statement_name(schema)
55
+ # Dump generic options.
56
+ schema.options.each do |opt, opt_value|
57
+ next if params[:ignore] && params[:ignore].include?(opt)
58
+ if validator_class = Respect.validator_for(opt)
59
+ h.merge!(validator_class.new(opt_value).to_h(:org3))
60
+ elsif Org3Dumper::OPTION_MAP.has_key?(opt)
61
+ Org3Dumper::OPTION_MAP[opt].each do |k, v|
62
+ if v == :option_value
63
+ h[k] = (opt_value.is_a?(Numeric) ? opt_value : opt_value.dup)
64
+ elsif v.is_a?(Proc)
65
+ result = schema.instance_exec(opt_value, &v)
66
+ h[k] = result unless result.nil?
67
+ else
68
+ h[k] = v
69
+ end
70
+ end
71
+ end
72
+ end
73
+ h.merge!(dump_options(schema))
74
+ # Dump documentation
75
+ h["title"] = schema.title if schema.title
76
+ h["description"] = schema.description if schema.description
77
+ h
78
+ end
79
+
80
+ def dump_schema_for_hash_schema(schema, params = {})
81
+ h = dump_schema_for_schema(schema, params)
82
+ return nil if h.nil?
83
+ props = {}
84
+ pattern_props = {}
85
+ additional_props = {}
86
+ schema.properties.each do |prop, schema|
87
+ if prop.is_a?(Regexp)
88
+ if schema.optional?
89
+ # FIXME(Nicolas Despres): Find a better warning reporting system.
90
+ warn "pattern properties cannot be optional in json-schema.org draft v3"
91
+ else
92
+ # FIXME(Nicolas Despres): What do we do with regexp options such as 'i'?
93
+ schema_dump = dump_schema(schema)
94
+ pattern_props[prop.source] = schema_dump if schema_dump
95
+ end
96
+ else
97
+ if schema.optional?
98
+ schema_dump = dump_schema(schema)
99
+ additional_props[prop.to_s] = schema_dump if schema_dump
100
+ else
101
+ schema_dump = dump_schema(schema)
102
+ props[prop.to_s] = schema_dump if schema_dump
103
+ end
104
+ end
105
+ end
106
+ h['properties'] = props unless props.empty?
107
+ h['patternProperties'] = pattern_props unless pattern_props.empty?
108
+ if additional_props.empty?
109
+ if schema.options[:strict]
110
+ h['additionalProperties'] = false
111
+ end
112
+ else
113
+ h['additionalProperties'] = additional_props
114
+ end
115
+ h
116
+ end
117
+
118
+ def dump_schema_for_array_schema(schema, params = {})
119
+ h = dump_schema_for_schema(schema, params)
120
+ return nil if h.nil?
121
+ if schema.item
122
+ h['items'] = dump_schema(schema.item, ignore: [:required])
123
+ else
124
+ if schema.items && !schema.items.empty?
125
+ h['items'] = schema.items.map do |x|
126
+ dump_schema(x, ignore: [:required])
127
+ end
128
+ end
129
+ if schema.extra_items && !schema.extra_items.empty?
130
+ h['additionalItems'] = schema.extra_items.map do |x|
131
+ dump_schema(x, ignore: [:required])
132
+ end
133
+ end
134
+ end
135
+ h
136
+ end
137
+
138
+ def dump_schema_for_composite_schema(schema, params = {})
139
+ dump_schema(schema.schema, params)
140
+ end
141
+
142
+ def dump_statement_name(schema, *args)
143
+ dispatch("dump_statement_name", schema.class, schema, *args)
144
+ end
145
+
146
+ def dump_statement_name_for_schema(schema)
147
+ schema.class.statement_name
148
+ end
149
+
150
+ def dump_statement_name_for_hash_schema(schema)
151
+ "object"
152
+ end
153
+
154
+ def dump_statement_name_for_numeric_schema(schema)
155
+ "number"
156
+ end
157
+
158
+ def dump_statement_name_for_integer_schema(schema)
159
+ "integer"
160
+ end
161
+
162
+ def dump_statement_name_for_string_schema(schema)
163
+ "string"
164
+ end
165
+
166
+ def dump_options(schema, *args)
167
+ dispatch("dump_options", schema.class, schema, *args)
168
+ end
169
+
170
+ def dump_options_for_schema(schema)
171
+ {}
172
+ end
173
+
174
+ def dump_options_for_uri_schema(schema)
175
+ { "format" => "uri" }
176
+ end
177
+
178
+ def dump_options_for_regexp_schema(schema)
179
+ { "format" => "regex" }
180
+ end
181
+
182
+ def dump_options_for_datetime_schema(schema)
183
+ { "format" => "date-time" }
184
+ end
185
+
186
+ def dump_options_for_ipv4_addr_schema(schema)
187
+ { "format" => "ip-address" }
188
+ end
189
+
190
+ def dump_options_for_ipv6_addr_schema(schema)
191
+ { "format" => "ipv6" }
192
+ end
193
+
194
+ private
195
+
196
+ # Perform a virtual dispatch on a single object.
197
+ # FIXME(Nicolas Despres): Get me out of here and test me.
198
+ def dispatch(prefix, klass, object, *args, &block)
199
+ symbol = "#{prefix}_for_#{klass.name.demodulize.underscore}"
200
+ if respond_to? symbol
201
+ send(symbol, object, *args, &block)
202
+ else
203
+ if klass == BasicObject
204
+ raise NoMethodError, "undefined method '#{symbol}' for schema class #{object.class}"
205
+ else
206
+ dispatch(prefix, klass.superclass, object, *args, &block)
207
+ end
208
+ end
209
+ end
210
+
211
+ end
212
+
213
+ end