json-schema 1.2.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +21 -32
- data/lib/json-schema/attributes/allof.rb +37 -0
- data/lib/json-schema/attributes/anyof.rb +41 -0
- data/lib/json-schema/attributes/dependencies_v4.rb +20 -0
- data/lib/json-schema/attributes/maxproperties.rb +12 -0
- data/lib/json-schema/attributes/minproperties.rb +12 -0
- data/lib/json-schema/attributes/multipleof.rb +16 -0
- data/lib/json-schema/attributes/not.rb +22 -0
- data/lib/json-schema/attributes/oneof.rb +35 -0
- data/lib/json-schema/attributes/properties.rb +1 -26
- data/lib/json-schema/attributes/properties_v4.rb +23 -0
- data/lib/json-schema/attributes/required.rb +23 -0
- data/lib/json-schema/attributes/type.rb +6 -7
- data/lib/json-schema/attributes/type_v4.rb +54 -0
- data/lib/json-schema/schema.rb +6 -6
- data/lib/json-schema/validator.rb +28 -9
- data/lib/json-schema/validators/draft4.rb +45 -0
- data/resources/draft-04.json +174 -0
- data/test/{test_files.rb → test_files_v3.rb} +8 -8
- data/test/test_jsonschema_draft3.rb +42 -113
- data/test/test_jsonschema_draft4.rb +1227 -0
- metadata +24 -7
- checksums.yaml +0 -15
data/README.textile
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
h1. Ruby JSON Schema Validator
|
2
2
|
|
3
|
-
This library is intended to provide Ruby with an interface for validating JSON objects against a JSON schema conforming to "JSON Schema Draft
|
3
|
+
This library is intended to provide Ruby with an interface for validating JSON objects against a JSON schema conforming to "JSON Schema Draft 4":http://tools.ietf.org/html/draft-zyp-json-schema-04. Legacy support for "JSON Schema Draft 3":http://tools.ietf.org/html/draft-zyp-json-schema-03, "JSON Schema Draft 2":http://tools.ietf.org/html/draft-zyp-json-schema-02, and "JSON Schema Draft 1":http://tools.ietf.org/html/draft-zyp-json-schema-01 is also included.
|
4
|
+
|
5
|
+
h2. Version 2.0.0 Upgrade Notes
|
6
|
+
|
7
|
+
Please be aware that the upgrade to version 2.0.0 will use Draft-04 *by default*, so schemas that do not declare a validator using the `$schema` keyword will use Draft-04 now instead of Draft-03. This is the reason for the major version upgrade.
|
4
8
|
|
5
9
|
h2. Dependencies
|
6
10
|
|
@@ -18,7 +22,7 @@ From the git repo:
|
|
18
22
|
|
19
23
|
<pre>
|
20
24
|
$ gem build json-schema.gemspec
|
21
|
-
$ gem install json-schema-
|
25
|
+
$ gem install json-schema-2.0.0.gem
|
22
26
|
</pre>
|
23
27
|
|
24
28
|
|
@@ -28,7 +32,7 @@ Three base validation methods exist: <code>validate</code>, <code>validate!</cod
|
|
28
32
|
|
29
33
|
All methods take two arguments, which can be either a JSON string, a file containing JSON, or a Ruby object representing JSON data. The first argument to these methods is always the schema, the second is always the data to validate. An optional third options argument is also accepted; available options are used in the examples below.
|
30
34
|
|
31
|
-
By default, the validator uses the "JSON Schema Draft
|
35
|
+
By default, the validator uses the "JSON Schema Draft 4":http://tools.ietf.org/html/draft-zyp-json-schema-04 specification for validation; however, the user is free to specify additional specifications or extend existing ones. Legacy support for Draft 1, Draft 2, and Draft 3 is included by either passing an optional <code>:version</code> parameter to the <code>validate</code> method (set either as <code>:draft1</code> or <code>draft2</code>), or by declaring the <code>$schema</code> attribute in the schema and referencing the appropriate specification URI. Note that the <code>$schema</code> attribute takes precedence over the <code>:version</code> option during parsing and validation.
|
32
36
|
|
33
37
|
h3. Validate Ruby objects against a Ruby schema
|
34
38
|
|
@@ -38,8 +42,9 @@ require 'json-schema'
|
|
38
42
|
|
39
43
|
schema = {
|
40
44
|
"type" => "object",
|
45
|
+
"required" => ["a"],
|
41
46
|
"properties" => {
|
42
|
-
"a" => {"type" => "integer"
|
47
|
+
"a" => {"type" => "integer"}
|
43
48
|
}
|
44
49
|
}
|
45
50
|
|
@@ -69,27 +74,6 @@ data = ['user','user','user']
|
|
69
74
|
JSON::Validator.validate('user.json', data, :list => true)
|
70
75
|
</pre>
|
71
76
|
|
72
|
-
h3. Strictly validate an object's properties
|
73
|
-
|
74
|
-
With the <code>:strict</code>code> option, validation fails when an object contains properties that are not defined in the schema's property list or doesn't match the <code>additionalProperties</code> property. Furthermore, all properties are treated as <code>required</code> by default.
|
75
|
-
|
76
|
-
<pre>
|
77
|
-
require 'rubygems'
|
78
|
-
require 'json-schema'
|
79
|
-
|
80
|
-
schema = {
|
81
|
-
"type" => "object",
|
82
|
-
"properties" => {
|
83
|
-
"a" => {"type" => "integer"},
|
84
|
-
"b" => {"type" => "integer"}
|
85
|
-
}
|
86
|
-
}
|
87
|
-
|
88
|
-
JSON::Validator.validate(schema, {"a" => 1, "b" => 2}, :strict => true) # ==> true
|
89
|
-
JSON::Validator.validate(schema, {"a" => 1, "b" => 2, "c" => 3}, :strict => true) # ==> false
|
90
|
-
JSON::Validator.validate(schema, {"a" => 1}, :strict => true) # ==> false
|
91
|
-
</pre>
|
92
|
-
|
93
77
|
h3. Catch a validation error and print it out
|
94
78
|
|
95
79
|
<pre>
|
@@ -98,8 +82,9 @@ require 'json-schema'
|
|
98
82
|
|
99
83
|
schema = {
|
100
84
|
"type" => "object",
|
85
|
+
"required" => ["a"],
|
101
86
|
"properties" => {
|
102
|
-
"a" => {"type" => "integer"
|
87
|
+
"a" => {"type" => "integer"}
|
103
88
|
}
|
104
89
|
}
|
105
90
|
|
@@ -123,9 +108,10 @@ require 'json-schema'
|
|
123
108
|
|
124
109
|
schema = {
|
125
110
|
"type" => "object",
|
111
|
+
"required" => ["a","b"],
|
126
112
|
"properties" => {
|
127
|
-
"a" => {"type" => "integer"
|
128
|
-
"b" => {"type" => "string"
|
113
|
+
"a" => {"type" => "integer"},
|
114
|
+
"b" => {"type" => "string"}
|
129
115
|
}
|
130
116
|
}
|
131
117
|
|
@@ -146,9 +132,10 @@ require 'json-schema'
|
|
146
132
|
|
147
133
|
schema = {
|
148
134
|
"type" => "object",
|
135
|
+
"required" => ["a","b"],
|
149
136
|
"properties" => {
|
150
|
-
"a" => {"type" => "integer"
|
151
|
-
"b" => {"type" => "string"
|
137
|
+
"a" => {"type" => "integer"},
|
138
|
+
"b" => {"type" => "string"}
|
152
139
|
}
|
153
140
|
}
|
154
141
|
|
@@ -170,8 +157,9 @@ require 'json-schema'
|
|
170
157
|
|
171
158
|
schema = {
|
172
159
|
"type" => "object",
|
160
|
+
"required" => ["a"],
|
173
161
|
"properties" => {
|
174
|
-
"a" => {"type" => "integer"
|
162
|
+
"a" => {"type" => "integer"} # This will fail schema validation!
|
175
163
|
}
|
176
164
|
}
|
177
165
|
|
@@ -193,8 +181,9 @@ require 'json-schema'
|
|
193
181
|
|
194
182
|
schema = {
|
195
183
|
"type" => "object",
|
184
|
+
"required" => ["a"],
|
196
185
|
"properties" => {
|
197
|
-
"a" => {"type" => "integer", "default" => 42
|
186
|
+
"a" => {"type" => "integer", "default" => 42},
|
198
187
|
"b" => {"type" => "integer"}
|
199
188
|
}
|
200
189
|
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module JSON
|
2
|
+
class Schema
|
3
|
+
class AllOfAttribute < Attribute
|
4
|
+
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
5
|
+
# Create an array to hold errors that are generated during validation
|
6
|
+
errors = []
|
7
|
+
valid = true
|
8
|
+
|
9
|
+
current_schema.schema['allOf'].each do |element|
|
10
|
+
schema = JSON::Schema.new(element,current_schema.uri,validator)
|
11
|
+
|
12
|
+
# We're going to add a little cruft here to try and maintain any validation errors that occur in the allOf
|
13
|
+
# We'll handle this by keeping an error count before and after validation, extracting those errors and pushing them onto an error array
|
14
|
+
pre_validation_error_count = validation_errors(processor).count
|
15
|
+
|
16
|
+
begin
|
17
|
+
schema.validate(data,fragments,processor,options)
|
18
|
+
rescue ValidationError
|
19
|
+
valid = false
|
20
|
+
end
|
21
|
+
|
22
|
+
diff = validation_errors(processor).count - pre_validation_error_count
|
23
|
+
while diff > 0
|
24
|
+
diff = diff - 1
|
25
|
+
errors.push(validation_errors(processor).pop)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
if !valid
|
30
|
+
message = "The property '#{build_fragment(fragments)}' of type #{data.class} did not match all of the required schemas"
|
31
|
+
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
32
|
+
validation_errors(processor).last.sub_errors = errors
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module JSON
|
2
|
+
class Schema
|
3
|
+
class AnyOfAttribute < Attribute
|
4
|
+
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
5
|
+
# Create an array to hold errors that are generated during validation
|
6
|
+
errors = []
|
7
|
+
valid = false
|
8
|
+
|
9
|
+
current_schema.schema['anyOf'].each do |element|
|
10
|
+
schema = JSON::Schema.new(element,current_schema.uri,validator)
|
11
|
+
|
12
|
+
# We're going to add a little cruft here to try and maintain any validation errors that occur in the anyOf
|
13
|
+
# We'll handle this by keeping an error count before and after validation, extracting those errors and pushing them onto a union error
|
14
|
+
pre_validation_error_count = validation_errors(processor).count
|
15
|
+
|
16
|
+
begin
|
17
|
+
schema.validate(data,fragments,processor,options)
|
18
|
+
valid = true
|
19
|
+
rescue ValidationError
|
20
|
+
# We don't care that these schemas don't validate - we only care that one validated
|
21
|
+
end
|
22
|
+
|
23
|
+
diff = validation_errors(processor).count - pre_validation_error_count
|
24
|
+
valid = false if diff > 0
|
25
|
+
while diff > 0
|
26
|
+
diff = diff - 1
|
27
|
+
errors.push(validation_errors(processor).pop)
|
28
|
+
end
|
29
|
+
|
30
|
+
break if valid
|
31
|
+
end
|
32
|
+
|
33
|
+
if !valid
|
34
|
+
message = "The property '#{build_fragment(fragments)}' of type #{data.class} did not match one or more of the required schemas"
|
35
|
+
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
36
|
+
validation_errors(processor).last.sub_errors = errors
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module JSON
|
2
|
+
class Schema
|
3
|
+
class DependenciesV4Attribute < Attribute
|
4
|
+
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
5
|
+
if data.is_a?(Hash)
|
6
|
+
current_schema.schema['dependencies'].each do |property,dependency_value|
|
7
|
+
if data.has_key?(property) && dependency_value.is_a?(Array)
|
8
|
+
dependency_value.each do |value|
|
9
|
+
if !data.has_key?(value)
|
10
|
+
message = "The property '#{build_fragment(fragments)}' has a property '#{property}' that depends on a missing property '#{value}'"
|
11
|
+
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module JSON
|
2
|
+
class Schema
|
3
|
+
class MaxPropertiesAttribute < Attribute
|
4
|
+
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
5
|
+
if data.is_a?(Hash) && (data.size > current_schema.schema['maxProperties'])
|
6
|
+
message = "The property '#{build_fragment(fragments)}' did not contain a minimum number of properties #{current_schema.schema['maxProperties']}"
|
7
|
+
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module JSON
|
2
|
+
class Schema
|
3
|
+
class MinPropertiesAttribute < Attribute
|
4
|
+
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
5
|
+
if data.is_a?(Hash) && (data.size < current_schema.schema['minProperties'])
|
6
|
+
message = "The property '#{build_fragment(fragments)}' did not contain a minimum number of properties #{current_schema.schema['minProperties']}"
|
7
|
+
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module JSON
|
2
|
+
class Schema
|
3
|
+
class MultipleOfAttribute < Attribute
|
4
|
+
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
5
|
+
if data.is_a?(Numeric)
|
6
|
+
if current_schema.schema['multipleOf'] == 0 ||
|
7
|
+
current_schema.schema['multipleOf'] == 0.0 ||
|
8
|
+
(BigDecimal.new(data.to_s) % BigDecimal.new(current_schema.schema['multipleOf'].to_s)).to_f != 0
|
9
|
+
message = "The property '#{build_fragment(fragments)}' was not divisible by #{current_schema.schema['multipleOf']}"
|
10
|
+
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module JSON
|
2
|
+
class Schema
|
3
|
+
class NotAttribute < Attribute
|
4
|
+
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
5
|
+
|
6
|
+
schema = JSON::Schema.new(current_schema.schema['not'],current_schema.uri,validator)
|
7
|
+
failed = true
|
8
|
+
begin
|
9
|
+
schema.validate(data,fragments,processor,options)
|
10
|
+
message = "The property '#{build_fragment(fragments)}' of type #{data.class} matched the disallowed schema"
|
11
|
+
failed = false
|
12
|
+
rescue
|
13
|
+
# Yay, we failed validation
|
14
|
+
end
|
15
|
+
|
16
|
+
unless failed
|
17
|
+
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module JSON
|
2
|
+
class Schema
|
3
|
+
class OneOfAttribute < Attribute
|
4
|
+
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
5
|
+
matched = false
|
6
|
+
valid = false
|
7
|
+
|
8
|
+
current_schema.schema['oneOf'].each do |element|
|
9
|
+
schema = JSON::Schema.new(element,current_schema.uri,validator)
|
10
|
+
|
11
|
+
begin
|
12
|
+
schema.validate(data,fragments,processor,options)
|
13
|
+
if matched
|
14
|
+
valid = false
|
15
|
+
else
|
16
|
+
matched = true
|
17
|
+
valid = true
|
18
|
+
end
|
19
|
+
rescue ValidationError
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
if !valid
|
25
|
+
if matched
|
26
|
+
message = "The property '#{build_fragment(fragments)}' of type #{data.class} matched more than one of the required schemas"
|
27
|
+
else
|
28
|
+
message = "The property '#{build_fragment(fragments)}' of type #{data.class} did not match any of the required schemas"
|
29
|
+
end
|
30
|
+
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -9,7 +9,7 @@ module JSON
|
|
9
9
|
data[property] = (default.is_a?(Hash) ? default.clone : default)
|
10
10
|
end
|
11
11
|
|
12
|
-
if
|
12
|
+
if property_schema['required'] and !data.has_key?(property)
|
13
13
|
message = "The property '#{build_fragment(fragments)}' did not contain a required property of '#{property}'"
|
14
14
|
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
15
15
|
end
|
@@ -21,31 +21,6 @@ module JSON
|
|
21
21
|
fragments.pop
|
22
22
|
end
|
23
23
|
end
|
24
|
-
|
25
|
-
# When strict is true, ensure no undefined properties exist in the data
|
26
|
-
if (options[:strict] == true && !current_schema.schema.has_key?('additionalProperties'))
|
27
|
-
diff = data.select do |k,v|
|
28
|
-
if current_schema.schema.has_key?('patternProperties')
|
29
|
-
match = false
|
30
|
-
current_schema.schema['patternProperties'].each do |property,property_schema|
|
31
|
-
r = Regexp.new(property)
|
32
|
-
if r.match(k)
|
33
|
-
match = true
|
34
|
-
break
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
!current_schema.schema['properties'].has_key?(k.to_s) && !current_schema.schema['properties'].has_key?(k.to_sym) && !match
|
39
|
-
else
|
40
|
-
!current_schema.schema['properties'].has_key?(k.to_s) && !current_schema.schema['properties'].has_key?(k.to_sym)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
if diff.size > 0
|
45
|
-
message = "The property '#{build_fragment(fragments)}' contained undefined properties: '#{diff.keys.join(", ")}'"
|
46
|
-
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
47
|
-
end
|
48
|
-
end
|
49
24
|
end
|
50
25
|
end
|
51
26
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module JSON
|
2
|
+
class Schema
|
3
|
+
class PropertiesV4Attribute < Attribute
|
4
|
+
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
5
|
+
if data.is_a?(Hash)
|
6
|
+
current_schema.schema['properties'].each do |property,property_schema|
|
7
|
+
if !data.has_key?(property) and property_schema['default'] and !property_schema['readonly'] and options[:insert_defaults]
|
8
|
+
default = property_schema['default']
|
9
|
+
data[property] = (default.is_a?(Hash) ? default.clone : default)
|
10
|
+
end
|
11
|
+
|
12
|
+
if data.has_key?(property)
|
13
|
+
schema = JSON::Schema.new(property_schema,current_schema.uri,validator)
|
14
|
+
fragments << property
|
15
|
+
schema.validate(data[property],fragments,processor,options)
|
16
|
+
fragments.pop
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module JSON
|
2
|
+
class Schema
|
3
|
+
class RequiredAttribute < Attribute
|
4
|
+
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
5
|
+
if data.is_a?(Hash)
|
6
|
+
current_schema.schema['required'].each do |property,property_schema|
|
7
|
+
if !data.has_key?(property)
|
8
|
+
prop_defaults = options[:insert_defaults] &&
|
9
|
+
current_schema.schema['properties'] &&
|
10
|
+
current_schema.schema['properties'][property] &&
|
11
|
+
!current_schema.schema['properties'][property]["default"].nil? &&
|
12
|
+
!current_schema.schema['properties'][property]["readonly"]
|
13
|
+
if !prop_defaults
|
14
|
+
message = "The property '#{build_fragment(fragments)}' did not contain a required property of '#{property}'"
|
15
|
+
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -3,7 +3,6 @@ module JSON
|
|
3
3
|
class TypeAttribute < Attribute
|
4
4
|
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
5
5
|
union = true
|
6
|
-
|
7
6
|
if options[:disallow]
|
8
7
|
types = current_schema.schema['disallow']
|
9
8
|
else
|
@@ -15,28 +14,28 @@ module JSON
|
|
15
14
|
union = false
|
16
15
|
end
|
17
16
|
valid = false
|
18
|
-
|
17
|
+
|
19
18
|
# Create an array to hold errors that are generated during union validation
|
20
19
|
union_errors = []
|
21
|
-
|
20
|
+
|
22
21
|
types.each do |type|
|
23
22
|
if type.is_a?(String)
|
24
23
|
valid = data_valid_for_type?(data, type)
|
25
24
|
elsif type.is_a?(Hash) && union
|
26
25
|
# Validate as a schema
|
27
26
|
schema = JSON::Schema.new(type,current_schema.uri,validator)
|
28
|
-
|
27
|
+
|
29
28
|
# We're going to add a little cruft here to try and maintain any validation errors that occur in this union type
|
30
29
|
# We'll handle this by keeping an error count before and after validation, extracting those errors and pushing them onto a union error
|
31
30
|
pre_validation_error_count = validation_errors(processor).count
|
32
|
-
|
31
|
+
|
33
32
|
begin
|
34
33
|
schema.validate(data,fragments,processor,options)
|
35
34
|
valid = true
|
36
35
|
rescue ValidationError
|
37
36
|
# We don't care that these schemas don't validate - we only care that one validated
|
38
37
|
end
|
39
|
-
|
38
|
+
|
40
39
|
diff = validation_errors(processor).count - pre_validation_error_count
|
41
40
|
valid = false if diff > 0
|
42
41
|
while diff > 0
|
@@ -66,7 +65,7 @@ module JSON
|
|
66
65
|
message = "The property '#{build_fragment(fragments)}' of type #{data.class} did not match the following type:"
|
67
66
|
types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
|
68
67
|
message.chop!
|
69
|
-
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
68
|
+
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
70
69
|
end
|
71
70
|
end
|
72
71
|
end
|