membrane 0.0.2 → 0.0.4

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 (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