membrane 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +6 -0
  3. data/LICENSE +58 -6869
  4. data/NOTICE +10 -0
  5. data/README.md +130 -30
  6. data/lib/membrane/schema_parser.rb +25 -24
  7. data/lib/membrane/schemas/any.rb +8 -0
  8. data/lib/membrane/{schema → schemas}/base.rb +1 -6
  9. data/lib/membrane/schemas/bool.rb +31 -0
  10. data/lib/membrane/schemas/class.rb +35 -0
  11. data/lib/membrane/schemas/dictionary.rb +69 -0
  12. data/lib/membrane/schemas/enum.rb +49 -0
  13. data/lib/membrane/schemas/list.rb +63 -0
  14. data/lib/membrane/schemas/record.rb +96 -0
  15. data/lib/membrane/schemas/regexp.rb +60 -0
  16. data/lib/membrane/schemas/tuple.rb +90 -0
  17. data/lib/membrane/schemas/value.rb +37 -0
  18. data/lib/membrane/schemas.rb +5 -0
  19. data/lib/membrane/version.rb +1 -1
  20. data/lib/membrane.rb +1 -1
  21. data/spec/schema_parser_spec.rb +79 -75
  22. data/spec/{any_schema_spec.rb → schemas/any_spec.rb} +2 -2
  23. data/spec/{base_schema_spec.rb → schemas/base_spec.rb} +2 -2
  24. data/spec/{bool_schema_spec.rb → schemas/bool_spec.rb} +2 -3
  25. data/spec/{class_schema_spec.rb → schemas/class_spec.rb} +2 -2
  26. data/spec/{dictionary_schema_spec.rb → schemas/dictionary_spec.rb} +11 -11
  27. data/spec/{enum_schema_spec.rb → schemas/enum_spec.rb} +4 -4
  28. data/spec/{list_schema_spec.rb → schemas/list_spec.rb} +8 -8
  29. data/spec/schemas/record_spec.rb +108 -0
  30. data/spec/{regexp_schema_spec.rb → schemas/regexp_spec.rb} +2 -2
  31. data/spec/{tuple_schema_spec.rb → schemas/tuple_spec.rb} +4 -4
  32. data/spec/{value_schema_spec.rb → schemas/value_spec.rb} +2 -2
  33. metadata +37 -35
  34. data/lib/membrane/schema/any.rb +0 -13
  35. data/lib/membrane/schema/bool.rb +0 -20
  36. data/lib/membrane/schema/class.rb +0 -24
  37. data/lib/membrane/schema/dictionary.rb +0 -40
  38. data/lib/membrane/schema/enum.rb +0 -30
  39. data/lib/membrane/schema/list.rb +0 -37
  40. data/lib/membrane/schema/record.rb +0 -45
  41. data/lib/membrane/schema/regexp.rb +0 -29
  42. data/lib/membrane/schema/tuple.rb +0 -48
  43. data/lib/membrane/schema/value.rb +0 -22
  44. data/lib/membrane/schema.rb +0 -17
  45. data/spec/record_schema_spec.rb +0 -56
data/NOTICE ADDED
@@ -0,0 +1,10 @@
1
+ cf-membrane
2
+
3
+ Copyright (c) 2013 Pivotal Software Inc. All Rights Reserved.
4
+
5
+ This product is licensed to you under the Apache License, Version 2.0 (the "License").
6
+ You may not use this product except in compliance with the License.
7
+
8
+ This product may include a number of subcomponents with separate copyright notices
9
+ and license terms. Your use of these subcomponents is subject to the terms and
10
+ conditions of the subcomponent's license, as noted in the LICENSE file.
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Build Status](https://travis-ci.org/cloudfoundry/membrane.png)](https://travis-ci.org/cloudfoundry/membrane)
2
+
1
3
  # Membrane
2
4
 
3
5
  Membrane provides an easy to use DSL for specifying validators declaratively.
@@ -7,38 +9,137 @@ decide what data to let in and what to keep out.
7
9
 
8
10
  ## Overview
9
11
 
10
- The core concept behind Membrane is the ```schema```. A ```schema```
11
- represents an invariant about a piece of data (similar to a type) and is
12
- capable of verifying whether or not a supplied datum satisfies the
13
- invariant. Schemas may be composed together to produce more expressive
14
- constructs.
12
+ The core concept behind Membrane is the ```schema```. A ```schema``` represents
13
+ an invariant about a piece of data (similar to a type) and is capable of
14
+ verifying whether or not a supplied datum satisfies the invariant. Schemas may
15
+ be composed to produce more expressive constructs.
15
16
 
16
17
  Membrane provides a handful of useful schemas out of the box. You should be
17
18
  able to construct the majority of your schemas using only what is provided
18
- by default. The provided schemas are:
19
-
20
- * _Any_ - Accepts all values. Use it sparingly. It is synonymous to
21
- class Object in Ruby.
22
- * _Bool_ - Accepts ```true``` and ```false```.
23
- * _Class_ - Accepts instances of a supplied class.
24
- * _Dictionary_ - Accepts hashes whose keys and values validate against their
25
- respective schemas.
26
- * _Enum_ - Accepts values that validate against _any_ of the supplied
27
- schemas. Similar to a sum type.
28
- * _List_ - Accepts arrays where each element of the array validates
29
- against a supplied schema.
30
- * _Record_ - Accepts hashes with known keys. Each key has a supplied schema,
31
- against which its value must validate.
32
- * _Regexp_ - Accepts strings that match a supplied regular expression.
33
- * _Tuple_ - Accepts arrays of a given length whose elements validate
34
- against their respective schemas.
35
- * _Value_ - Accepts values using ```==```.
19
+ by default.
20
+
21
+
22
+ *Any*
23
+
24
+ The ```Any``` schema accepts all values; use it sparingly. It is synonymous to
25
+ the Object class in Ruby.
26
+
27
+ *Bool*
28
+
29
+ The ```Bool``` schema accepts only the values ```true``` and ```false```.
30
+
31
+ *Class*
32
+
33
+ The ```Class``` schema is parameterized by an instance of
34
+ ```Class```. It accepts any values that are instances of the supplied class.
35
+ This is verified using ```kind_of?```.
36
+
37
+ *Dictionary*
38
+
39
+ The ```Dictionary``` schema is parameterized by a key schema and a
40
+ value schema. It accepts hashes whose keys and values validate against their
41
+ respective schemas.
42
+
43
+ *Enum*
44
+
45
+ The ```Enum``` parameterized by an arbitrary number of value schemas. It
46
+ accepts any values that are accepted by at least one of the supplied schemas.
47
+
48
+ *List*
49
+
50
+ The ```List``` schema is parameterized by a single element schema. It accepts
51
+ arrays whose elements are accepted by the supplied schema.
52
+
53
+ *Record*
54
+
55
+ The ```Record``` schema is parameterized by a set of known keys and their
56
+ respective schemas. It accepts hashes that contain all the supplied keys,
57
+ assuming the corresponding values are accepted by their respective schemas.
58
+
59
+ *Regexp*
60
+
61
+ The ```Regexp``` schema is parameterized by a regular expression. It accepts
62
+ strings that match the supplied regular expression.
63
+
64
+ *Tuple*
65
+
66
+ The ```Tuple``` schema is parameterized by a fixed number of schemas. It accepts
67
+ arrays of the same length, where each element is accepted by its associated
68
+ schema.
69
+
70
+ *Value*
71
+
72
+ The ```Value``` schema is parameterized by a single value. It accepts values
73
+ who are equal to the parameterizing value using ```==```.
74
+
75
+ ## DSL
76
+
77
+ Membrane schemas are typically created using a concise DSL. The aforementioned
78
+ schemas are represented in the DSL as follows:
79
+
80
+ *Any*
81
+
82
+ The ```Any``` schema is represented by the keyword ```any```.
83
+
84
+ *Bool*
85
+
86
+ The ```Bool``` schema is represented by the keyword ```bool```.
87
+
88
+ *Class*
89
+
90
+ The ```Class``` schema is represented by the parameterizing instance of ```Class```.
91
+ For example, an instance of the Class schema that validates strings would be
92
+ represented as ```String```.
93
+
94
+ *Dictionary*
95
+
96
+ The ```Dictionary``` schema is represented by ```dict(key_schema,
97
+ value_schema```, where ```key_schema``` is the schema used to validate keys,
98
+ and ```value_schema``` is the schema used to validate values.
99
+
100
+ *Enum*
101
+
102
+ The ```Enum``` schema is represented by ```enum(schema1, ..., schemaN)```
103
+ where ```schema1``` through ```schemaN``` are the possible value schemas.
104
+
105
+ *List*
106
+
107
+ The ```List``` schema is represented by ```[elem_schema]```, where
108
+ ```elem_schema``` is the schema that all list elements must validate against.
109
+
110
+ *Record*
111
+
112
+ The ```Record``` schema is represented as follows:
113
+
114
+ { "key1" => value1_schema,
115
+ optional("key2") => value2_schema,
116
+ ...
117
+ }
118
+
119
+ Here ```key1``` must be contained in the hash and the corresponding value must
120
+ be accepted by ```value1_schema```. Note that ```key2``` is marked as optional.
121
+ If present, its corresponding value must be accepted by ```value2_schema```.
122
+
123
+ *Regexp*
124
+
125
+ The ```Regexp``` schema is represented by regexp literals. For example,
126
+ ```/foo|bar/``` matches strings containing "foo" or "bar".
127
+
128
+ *Tuple*
129
+
130
+ The ```Tuple``` schema is represented as ```tuple(schema0, ..., schemaN)```,
131
+ where the Ith element of an array must be accepted by ```schemaI```.
132
+
133
+ *Value*
134
+
135
+ The ```Value``` schema is represented by the value to be validated. For example,
136
+ ```"foo"``` accepts only the string "foo".
36
137
 
37
138
  ## Usage
38
139
 
39
- Membrane schemas are typically created using a concise DSL. For example, the
40
- following creates a schema that will validate a hash where the key "ints"
41
- maps to a list of integers and the key "string" maps to a string.
140
+ While the previous section was a bit abstract, the DSL is fairly intuitive.
141
+ For example, the following creates a schema that will validate a hash where the
142
+ key "ints" maps to a list of integers and the key "string" maps to a string.
42
143
 
43
144
  schema = Membrane::SchemaParser.parse do
44
145
  { "ints" => [Integer],
@@ -58,8 +159,8 @@ maps to a list of integers and the key "string" maps to a string.
58
159
  "ints" => "invalid",
59
160
  })
60
161
 
61
- This is a more complicated example that illustrate the entire DSL. It should
62
- be self-explanatory.
162
+ This is a more complicated example that illustrate the entire DSL. Hopefully
163
+ it is self-explanatory:
63
164
 
64
165
  Membrane::SchemaParser.parse do
65
166
  { "ints" => [Integer]
@@ -87,5 +188,4 @@ can be used as a schema:
87
188
  def validate(object)
88
189
 
89
190
  If you wish to include your new schema as part of the DSL, you'll need to
90
- modify ```membrane/schema_parser.rb``` and have your class inherit from
91
- ```Membrane::Schema::Base```
191
+ modify ```membrane/schema_parser.rb``` and have your class inherit from ```Membrane::Schemas::Base```
@@ -1,4 +1,4 @@
1
- require "membrane/schema"
1
+ require "membrane/schemas"
2
2
 
3
3
  module Membrane
4
4
  end
@@ -13,11 +13,11 @@ class Membrane::SchemaParser
13
13
  TupleMarker = Struct.new(:elem_schemas)
14
14
 
15
15
  def any
16
- Membrane::Schema::Any.new
16
+ Membrane::Schemas::Any.new
17
17
  end
18
18
 
19
19
  def bool
20
- Membrane::Schema::Bool.new
20
+ Membrane::Schemas::Bool.new
21
21
  end
22
22
 
23
23
  def enum(*elem_schemas)
@@ -53,31 +53,31 @@ class Membrane::SchemaParser
53
53
 
54
54
  def deparse(schema)
55
55
  case schema
56
- when Membrane::Schema::Any
56
+ when Membrane::Schemas::Any
57
57
  "any"
58
- when Membrane::Schema::Bool
58
+ when Membrane::Schemas::Bool
59
59
  "bool"
60
- when Membrane::Schema::Class
60
+ when Membrane::Schemas::Class
61
61
  schema.klass.name
62
- when Membrane::Schema::Dictionary
62
+ when Membrane::Schemas::Dictionary
63
63
  "dict(%s, %s)" % [deparse(schema.key_schema),
64
64
  deparse(schema.value_schema)]
65
- when Membrane::Schema::Enum
65
+ when Membrane::Schemas::Enum
66
66
  "enum(%s)" % [schema.elem_schemas.map { |es| deparse(es) }.join(", ")]
67
- when Membrane::Schema::List
67
+ when Membrane::Schemas::List
68
68
  "[%s]" % [deparse(schema.elem_schema)]
69
- when Membrane::Schema::Record
69
+ when Membrane::Schemas::Record
70
70
  deparse_record(schema)
71
- when Membrane::Schema::Regexp
71
+ when Membrane::Schemas::Regexp
72
72
  schema.regexp.inspect
73
- when Membrane::Schema::Tuple
73
+ when Membrane::Schemas::Tuple
74
74
  "tuple(%s)" % [schema.elem_schemas.map { |es| deparse(es) }.join(", ")]
75
- when Membrane::Schema::Value
75
+ when Membrane::Schemas::Value
76
76
  schema.value.inspect
77
- when Membrane::Schema::Base
77
+ when Membrane::Schemas::Base
78
78
  schema.inspect
79
79
  else
80
- emsg = "Expected instance of Membrane::Schema::Base, given instance of" \
80
+ emsg = "Expected instance of Membrane::Schemas::Base, given instance of" \
81
81
  + " #{schema.class}"
82
82
  raise ArgumentError.new(emsg)
83
83
  end
@@ -92,22 +92,22 @@ class Membrane::SchemaParser
92
92
  when Array
93
93
  parse_list(object)
94
94
  when Class
95
- Membrane::Schema::Class.new(object)
95
+ Membrane::Schemas::Class.new(object)
96
96
  when Regexp
97
- Membrane::Schema::Regexp.new(object)
97
+ Membrane::Schemas::Regexp.new(object)
98
98
  when Dsl::DictionaryMarker
99
- Membrane::Schema::Dictionary.new(do_parse(object.key_schema),
99
+ Membrane::Schemas::Dictionary.new(do_parse(object.key_schema),
100
100
  do_parse(object.value_schema))
101
101
  when Dsl::EnumMarker
102
102
  elem_schemas = object.elem_schemas.map { |s| do_parse(s) }
103
- Membrane::Schema::Enum.new(*elem_schemas)
103
+ Membrane::Schemas::Enum.new(*elem_schemas)
104
104
  when Dsl::TupleMarker
105
105
  elem_schemas = object.elem_schemas.map { |s| do_parse(s) }
106
- Membrane::Schema::Tuple.new(*elem_schemas)
107
- when Membrane::Schema::Base
106
+ Membrane::Schemas::Tuple.new(*elem_schemas)
107
+ when Membrane::Schemas::Base
108
108
  object
109
109
  else
110
- Membrane::Schema::Value.new(object)
110
+ Membrane::Schemas::Value.new(object)
111
111
  end
112
112
  end
113
113
 
@@ -118,7 +118,7 @@ class Membrane::SchemaParser
118
118
  raise ArgumentError.new("Lists can only match a single schema.")
119
119
  end
120
120
 
121
- Membrane::Schema::List.new(do_parse(schema[0]))
121
+ Membrane::Schemas::List.new(do_parse(schema[0]))
122
122
  end
123
123
 
124
124
  def parse_record(schema)
@@ -139,7 +139,7 @@ class Membrane::SchemaParser
139
139
  parsed[key] = do_parse(value_schema)
140
140
  end
141
141
 
142
- Membrane::Schema::Record.new(parsed, optional_keys)
142
+ Membrane::Schemas::Record.new(parsed, optional_keys)
143
143
  end
144
144
 
145
145
  def deparse_record(schema)
@@ -153,6 +153,7 @@ class Membrane::SchemaParser
153
153
  dep_key = key.inspect
154
154
  end
155
155
 
156
+ dep_key = DEPARSE_INDENT + dep_key
156
157
  dep_val_schema_lines = deparse(val_schema).split("\n")
157
158
 
158
159
  dep_val_schema_lines.each_with_index do |line, line_idx|
@@ -0,0 +1,8 @@
1
+ require "membrane/errors"
2
+ require "membrane/schemas/base"
3
+
4
+ class Membrane::Schemas::Any < Membrane::Schemas::Base
5
+ def validate(object)
6
+ nil
7
+ end
8
+ end
@@ -1,9 +1,4 @@
1
- module Membrane
2
- module Schema
3
- end
4
- end
5
-
6
- class Membrane::Schema::Base
1
+ class Membrane::Schemas::Base
7
2
  # Verifies whether or not the supplied object conforms to this schema
8
3
  #
9
4
  # @param [Object] The object being validated
@@ -0,0 +1,31 @@
1
+ require "set"
2
+
3
+ require "membrane/errors"
4
+ require "membrane/schemas/base"
5
+
6
+ class Membrane::Schemas::Bool < Membrane::Schemas::Base
7
+ def validate(object)
8
+ BoolValidator.new(object).validate
9
+ end
10
+
11
+ class BoolValidator
12
+ TRUTH_VALUES = Set.new([true, false])
13
+
14
+ def initialize(object)
15
+ @object = object
16
+ end
17
+
18
+ def validate
19
+ if !TRUTH_VALUES.include?(@object)
20
+ fail!
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def fail!
27
+ emsg = "Expected instance of true or false, given #{@object}"
28
+ raise Membrane::SchemaValidationError.new(emsg)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ require "membrane/errors"
2
+ require "membrane/schemas/base"
3
+
4
+ class Membrane::Schemas::Class < Membrane::Schemas::Base
5
+ attr_reader :klass
6
+
7
+ def initialize(klass)
8
+ @klass = klass
9
+ end
10
+
11
+ # Validates whether or not the supplied object is derived from klass
12
+ def validate(object)
13
+ ClassValidator.new(@klass, object).validate
14
+ end
15
+
16
+ class ClassValidator
17
+
18
+ def initialize(klass, object)
19
+ @klass = klass
20
+ @object = object
21
+ end
22
+
23
+ def validate
24
+ fail! if !@object.kind_of?(@klass)
25
+ end
26
+
27
+ private
28
+
29
+ def fail!
30
+ emsg = "Expected instance of #{@klass}," \
31
+ + " given an instance of #{@object.class}"
32
+ raise Membrane::SchemaValidationError.new(emsg)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,69 @@
1
+ require "membrane/errors"
2
+ require "membrane/schemas/base"
3
+
4
+ module Membrane
5
+ module Schema
6
+ end
7
+ end
8
+
9
+ class Membrane::Schemas::Dictionary < Membrane::Schemas::Base
10
+ attr_reader :key_schema
11
+ attr_reader :value_schema
12
+
13
+ def initialize(key_schema, value_schema)
14
+ @key_schema = key_schema
15
+ @value_schema = value_schema
16
+ end
17
+
18
+ def validate(object)
19
+ HashValidator.new(object).validate
20
+ MembersValidator.new(@key_schema, @value_schema, object).validate
21
+ end
22
+
23
+ class HashValidator
24
+ def initialize(object)
25
+ @object = object
26
+ end
27
+
28
+ def validate
29
+ fail!(@object.class) if !@object.kind_of?(Hash)
30
+ end
31
+
32
+ private
33
+
34
+ def fail!(klass)
35
+ emsg = "Expected instance of Hash, given instance of #{klass}."
36
+ raise Membrane::SchemaValidationError.new(emsg)
37
+ end
38
+ end
39
+
40
+ class MembersValidator
41
+ def initialize(key_schema, value_schema, object)
42
+ @key_schema = key_schema
43
+ @value_schema = value_schema
44
+ @object = object
45
+ end
46
+
47
+ def validate
48
+ errors = {}
49
+
50
+ @object.each do |k, v|
51
+ begin
52
+ @key_schema.validate(k)
53
+ @value_schema.validate(v)
54
+ rescue Membrane::SchemaValidationError => e
55
+ errors[k] = e.to_s
56
+ end
57
+ end
58
+
59
+ fail!(errors) if errors.size > 0
60
+ end
61
+
62
+ private
63
+
64
+ def fail!(errors)
65
+ emsg = "{ " + errors.map { |k, e| "#{k} => #{e}" }.join(", ") + " }"
66
+ raise Membrane::SchemaValidationError.new(emsg)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,49 @@
1
+ require "membrane/errors"
2
+ require "membrane/schemas/base"
3
+
4
+ module Membrane
5
+ module Schema
6
+ end
7
+ end
8
+
9
+ class Membrane::Schemas::Enum < Membrane::Schemas::Base
10
+ attr_reader :elem_schemas
11
+
12
+ def initialize(*elem_schemas)
13
+ @elem_schemas = elem_schemas
14
+ end
15
+
16
+ def validate(object)
17
+ EnumValidator.new(@elem_schemas, object).validate
18
+ end
19
+
20
+ class EnumValidator
21
+ def initialize(elem_schemas, object)
22
+ @elem_schemas = elem_schemas
23
+ @object = object
24
+ end
25
+
26
+ def validate
27
+ @elem_schemas.each do |schema|
28
+ begin
29
+ schema.validate(@object)
30
+ return nil
31
+ rescue Membrane::SchemaValidationError
32
+ end
33
+ end
34
+
35
+ fail!(@elem_schemas)
36
+ end
37
+
38
+ private
39
+
40
+ def fail!(elem_schemas)
41
+ elem_schema_str = elem_schemas.map { |s| s.to_s }.join(", ")
42
+
43
+ emsg = "Object #{@object} doesn't validate" \
44
+ + " against any of #{elem_schema_str}"
45
+ raise Membrane::SchemaValidationError.new(emsg)
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,63 @@
1
+ require "membrane/errors"
2
+ require "membrane/schemas/base"
3
+
4
+ module Membrane
5
+ module Schema
6
+ end
7
+ end
8
+
9
+ class Membrane::Schemas::List < Membrane::Schemas::Base
10
+ attr_reader :elem_schema
11
+
12
+ def initialize(elem_schema)
13
+ @elem_schema = elem_schema
14
+ end
15
+
16
+ def validate(object)
17
+ ArrayValidator.new(object).validate
18
+ MemberValidator.new(@elem_schema, object).validate
19
+ end
20
+
21
+ class ArrayValidator
22
+ def initialize(object)
23
+ @object = object
24
+ end
25
+
26
+ def validate
27
+ fail! if !@object.kind_of?(Array)
28
+ end
29
+
30
+ private
31
+
32
+ def fail!
33
+ emsg = "Expected instance of Array, given instance of #{@object.class}"
34
+ raise Membrane::SchemaValidationError.new(emsg)
35
+ end
36
+ end
37
+
38
+ class MemberValidator
39
+ def initialize(elem_schema, object)
40
+ @elem_schema = elem_schema
41
+ @object = object
42
+ end
43
+
44
+ def validate
45
+ errors = {}
46
+
47
+ @object.each_with_index do |elem, ii|
48
+ begin
49
+ @elem_schema.validate(elem)
50
+ rescue Membrane::SchemaValidationError => e
51
+ errors[ii] = e.to_s
52
+ end
53
+ end
54
+
55
+ fail!(errors) if errors.size > 0
56
+ end
57
+
58
+ def fail!(errors)
59
+ emsg = errors.map { |ii, e| "At index #{ii}: #{e}" }.join(", ")
60
+ raise Membrane::SchemaValidationError.new(emsg)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,96 @@
1
+ require "set"
2
+
3
+ require "membrane/errors"
4
+ require "membrane/schemas/base"
5
+
6
+ module Membrane
7
+ module Schema
8
+ end
9
+ end
10
+
11
+ class Membrane::Schemas::Record < Membrane::Schemas::Base
12
+ attr_reader :schemas
13
+ attr_reader :optional_keys
14
+
15
+ def initialize(schemas, optional_keys = [], strict_checking = false)
16
+ @optional_keys = Set.new(optional_keys)
17
+ @schemas = schemas
18
+ @strict_checking = strict_checking
19
+ end
20
+
21
+ def validate(object)
22
+ HashValidator.new(object).validate
23
+ KeyValidator.new(@optional_keys, @schemas, @strict_checking, object).validate
24
+ end
25
+
26
+ def parse(&blk)
27
+ other_record = Membrane::SchemaParser.parse(&blk)
28
+ @schemas.merge!(other_record.schemas)
29
+ @optional_keys << other_record.optional_keys
30
+
31
+ self
32
+ end
33
+
34
+ class KeyValidator
35
+ def initialize(optional_keys, schemas, strict_checking, object)
36
+ @optional_keys = optional_keys
37
+ @schemas = schemas
38
+ @strict_checking = strict_checking
39
+ @object = object
40
+ end
41
+
42
+ def validate
43
+ key_errors = {}
44
+ schema_keys = []
45
+ @schemas.each do |k, schema|
46
+ if @object.has_key?(k)
47
+ schema_keys << k
48
+ begin
49
+ schema.validate(@object[k])
50
+ rescue Membrane::SchemaValidationError => e
51
+ key_errors[k] = e.to_s
52
+ end
53
+ elsif !@optional_keys.include?(k)
54
+ key_errors[k] = "Missing key"
55
+ end
56
+ end
57
+
58
+ key_errors.merge!(validate_extra_keys(@object.keys - schema_keys)) if @strict_checking
59
+
60
+ fail!(key_errors) if key_errors.size > 0
61
+ end
62
+
63
+ private
64
+
65
+ def validate_extra_keys(extra_keys)
66
+ extra_key_errors = {}
67
+ extra_keys.each do |k|
68
+ extra_key_errors[k] = "was not specified in the schema"
69
+ end
70
+
71
+ extra_key_errors
72
+ end
73
+
74
+ def fail!(errors)
75
+ emsg = "{ " + errors.map { |k, e| "#{k} => #{e}" }.join(", ") + " }"
76
+ raise Membrane::SchemaValidationError.new(emsg)
77
+ end
78
+ end
79
+
80
+ class HashValidator
81
+ def initialize(object)
82
+ @object = object
83
+ end
84
+
85
+ def validate
86
+ fail! unless @object.kind_of?(Hash)
87
+ end
88
+
89
+ private
90
+
91
+ def fail!
92
+ emsg = "Expected instance of Hash, given instance of #{@object.class}"
93
+ raise Membrane::SchemaValidationError.new(emsg)
94
+ end
95
+ end
96
+ end