babl-json 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +1 -1
  4. data/lib/babl/builder/chain_builder.rb +1 -1
  5. data/lib/babl/errors.rb +3 -3
  6. data/lib/babl/nodes/create_pin.rb +1 -1
  7. data/lib/babl/nodes/dep.rb +1 -1
  8. data/lib/babl/nodes/each.rb +2 -2
  9. data/lib/babl/nodes/fixed_array.rb +2 -2
  10. data/lib/babl/nodes/goto_pin.rb +1 -1
  11. data/lib/babl/nodes/internal_value.rb +1 -1
  12. data/lib/babl/nodes/merge.rb +9 -13
  13. data/lib/babl/nodes/nav.rb +1 -1
  14. data/lib/babl/nodes/object.rb +2 -2
  15. data/lib/babl/nodes/parent.rb +3 -3
  16. data/lib/babl/nodes/static.rb +3 -7
  17. data/lib/babl/nodes/switch.rb +2 -2
  18. data/lib/babl/nodes/typed.rb +42 -0
  19. data/lib/babl/nodes/with.rb +1 -1
  20. data/lib/babl/operators/call.rb +2 -2
  21. data/lib/babl/operators/continue.rb +1 -1
  22. data/lib/babl/operators/enter.rb +1 -1
  23. data/lib/babl/operators/object.rb +1 -1
  24. data/lib/babl/operators/partial.rb +2 -2
  25. data/lib/babl/operators/pin.rb +1 -1
  26. data/lib/babl/operators/static.rb +1 -1
  27. data/lib/babl/operators/typed.rb +25 -0
  28. data/lib/babl/rendering/compiled_template.rb +1 -1
  29. data/lib/babl/schema/any_of.rb +36 -47
  30. data/lib/babl/schema/dyn_array.rb +2 -2
  31. data/lib/babl/schema/fixed_array.rb +3 -3
  32. data/lib/babl/schema/object.rb +8 -8
  33. data/lib/babl/schema/static.rb +1 -1
  34. data/lib/babl/schema/typed.rb +16 -0
  35. data/lib/babl/template.rb +2 -0
  36. data/lib/babl/version.rb +1 -1
  37. data/spec/operators/array_spec.rb +1 -1
  38. data/spec/operators/continue_spec.rb +3 -3
  39. data/spec/operators/each_spec.rb +1 -1
  40. data/spec/operators/enter_spec.rb +1 -1
  41. data/spec/operators/extends_spec.rb +1 -1
  42. data/spec/operators/merge_spec.rb +2 -2
  43. data/spec/operators/nav_spec.rb +5 -4
  44. data/spec/operators/nullable_spec.rb +1 -1
  45. data/spec/operators/object_spec.rb +1 -1
  46. data/spec/operators/parent_spec.rb +1 -1
  47. data/spec/operators/partial_spec.rb +1 -1
  48. data/spec/operators/pin_spec.rb +1 -1
  49. data/spec/operators/static_spec.rb +1 -1
  50. data/spec/operators/switch_spec.rb +3 -3
  51. data/spec/operators/typed_spec.rb +84 -0
  52. data/spec/spec_helper/schema_utils.rb +27 -7
  53. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3a7476b8f458e689ec9a365ddb484fdfae227131
4
- data.tar.gz: 6bd1ae10d995e90dfac925f1700b507590b03975
3
+ metadata.gz: 4dec3c593ea00b70b6d191739c4c84b1d78492fa
4
+ data.tar.gz: f9bcac08981d7ed996443692bd7f8752b402bab2
5
5
  SHA512:
6
- metadata.gz: 1879d4cdbb15a27592e6efa36ef6a76b225d17b98f69d3ce38ccbc2fbdd26f890385b082ecd7a2ca8a3c1c7db354181eb2f4278268190ebb4d2abbaf741bdc04
7
- data.tar.gz: 3ba37150e0b32e6e0ce64fdf11a5ffbe3772b937cf2e332e3b1b03cd10ed57233185d8361e44e4f53a14b8d6de95cff0c1560bf9c6cea82d45c347b0752932cc
6
+ metadata.gz: 15ed5dc2e9baf8cb6b94e178e9a8d743c719d73b229d4e33312525f3e743976702bcba8bc7b90069f17e2ddb8b49d4efaccb9a5518e111686847d8adc6c24f3e
7
+ data.tar.gz: d18b19d2550f3cac80d054474c3d85b60eb1be7f4ddf2e43172477f5b05b3f4a01f535adfe2f322e60dee0d69265b1162757d7886159dbd52474e1395fc7d6e8
@@ -1,5 +1,9 @@
1
1
  # BABL Changelog
2
2
 
3
+ ## 0.2.2 (July 13, 2017)
4
+
5
+ - Added four typing operators: `integer`, `number`, `string` and `boolean`.
6
+
3
7
  ## 0.2.1 (July 11, 2017)
4
8
 
5
9
  - Fix: array serialization is broken.
data/README.md CHANGED
@@ -66,7 +66,7 @@ As of today, the only compatible preloader implementation *has not been released
66
66
 
67
67
  ### Automatic documentation
68
68
 
69
- The structure of the JSON produced by a BABL template can be documentated using [JSON-Schema](http://json-schema.org/).
69
+ The structure of the JSON produced by a BABL template can be documented using [JSON-Schema](http://json-schema.org/).
70
70
 
71
71
  ### Rails integration
72
72
 
@@ -41,7 +41,7 @@ module Babl
41
41
  def construct_terminal
42
42
  construct_node do |node, context|
43
43
  unless [Nodes::InternalValue.instance, Nodes::TerminalValue.instance].include?(node)
44
- raise Errors::InvalidTemplateError, 'Chaining is not allowed after a terminal operator'
44
+ raise Errors::InvalidTemplate, 'Chaining is not allowed after a terminal operator'
45
45
  end
46
46
  yield context
47
47
  end
@@ -1,7 +1,7 @@
1
1
  module Babl
2
2
  module Errors
3
- class BablError < StandardError; end
4
- class InvalidTemplateError < BablError; end
5
- class RenderingError < BablError; end
3
+ class Base < StandardError; end
4
+ class InvalidTemplate < Base; end
5
+ class RenderingError < Base; end
6
6
  end
7
7
  end
@@ -3,7 +3,7 @@ require 'values'
3
3
 
4
4
  module Babl
5
5
  module Nodes
6
- class CreatePin < Value.new(:node, :ref)
6
+ class CreatePin < ::Value.new(:node, :ref)
7
7
  def render(ctx)
8
8
  node.render(ctx.create_pin(ref))
9
9
  end
@@ -3,7 +3,7 @@ require 'values'
3
3
 
4
4
  module Babl
5
5
  module Nodes
6
- class Dep < Value.new(:node, :path)
6
+ class Dep < ::Value.new(:node, :path)
7
7
  def initialize(node, path)
8
8
  super(node, canonicalize(path))
9
9
  end
@@ -4,13 +4,13 @@ require 'values'
4
4
 
5
5
  module Babl
6
6
  module Nodes
7
- class Each < Value.new(:node)
7
+ class Each < ::Value.new(:node)
8
8
  def dependencies
9
9
  { __each__: node.dependencies }
10
10
  end
11
11
 
12
12
  def schema
13
- Schema::DynArray.new(node.schema, false)
13
+ Schema::DynArray.new(node.schema)
14
14
  end
15
15
 
16
16
  def pinned_dependencies
@@ -4,9 +4,9 @@ require 'values'
4
4
 
5
5
  module Babl
6
6
  module Nodes
7
- class FixedArray < Value.new(:nodes)
7
+ class FixedArray < ::Value.new(:nodes)
8
8
  def schema
9
- Schema::FixedArray.new(nodes.map(&:schema), false)
9
+ Schema::FixedArray.new(nodes.map(&:schema))
10
10
  end
11
11
 
12
12
  def dependencies
@@ -3,7 +3,7 @@ require 'values'
3
3
 
4
4
  module Babl
5
5
  module Nodes
6
- class GotoPin < Value.new(:node, :ref)
6
+ class GotoPin < ::Value.new(:node, :ref)
7
7
  def dependencies
8
8
  {}
9
9
  end
@@ -12,7 +12,7 @@ module Babl
12
12
  include Singleton
13
13
 
14
14
  def schema
15
- raise Errors::InvalidTemplateError, 'Internal nodes cannot be documented'
15
+ raise Errors::InvalidTemplate, 'Internal nodes cannot be documented'
16
16
  end
17
17
 
18
18
  def dependencies
@@ -4,11 +4,7 @@ require 'values'
4
4
 
5
5
  module Babl
6
6
  module Nodes
7
- class Merge < Value.new(:nodes)
8
- def initialize(nodes)
9
- super
10
- end
11
-
7
+ class Merge < ::Value.new(:nodes)
12
8
  def dependencies
13
9
  nodes.map(&:dependencies).reduce({}) { |a, b| Babl::Utils::Hash.deep_merge(a, b) }
14
10
  end
@@ -45,7 +41,7 @@ module Babl
45
41
  when Schema::Anything === doc1 && Schema::Object === doc2
46
42
  merge_object(Schema::Object::EMPTY_WITH_ADDITIONAL, doc2)
47
43
  else
48
- raise Errors::InvalidTemplateError, 'Only objects can be merged'
44
+ raise Errors::InvalidTemplate, 'Only objects can be merged'
49
45
  end
50
46
  end
51
47
 
@@ -53,11 +49,11 @@ module Babl
53
49
  # on left, right or both sides.
54
50
  def merge_extended(doc1, doc2)
55
51
  # Ensure doc1 & doc2 are both Schema::AnyOf
56
- doc1ext = Schema::AnyOf.new([doc1])
57
- doc2ext = Schema::AnyOf.new([doc2])
52
+ choices1 = Schema::AnyOf === doc1 ? doc1.choices : [doc1]
53
+ choices2 = Schema::AnyOf === doc2 ? doc2.choices : [doc2]
58
54
 
59
55
  # Generate all possible combinations
60
- all_docs = doc1ext.choices.product(doc2ext.choices)
56
+ all_docs = choices1.product(choices2)
61
57
  .map { |choice1, choice2| merge_doc(choice1, choice2) }
62
58
 
63
59
  # Analyze each property accross all combination to
@@ -68,13 +64,13 @@ module Babl
68
64
  .map do |name, properties|
69
65
  Schema::Object::Property.new(
70
66
  name,
71
- Schema::AnyOf.new(properties.map(&:value)).simplify,
67
+ Schema::AnyOf.canonical(properties.map(&:value)),
72
68
  properties.size == all_docs.size && properties.all?(&:required)
73
69
  )
74
70
  end
75
71
 
76
72
  # Generate the final Schema::Object
77
- Schema::Object.new(final_properties, all_docs.any?(&:additional), false)
73
+ Schema::Object.new(final_properties, all_docs.any?(&:additional))
78
74
  end
79
75
 
80
76
  # Merge two Schema::Object
@@ -86,14 +82,14 @@ module Babl
86
82
  doc2.properties
87
83
  ).each_with_object({}) { |property, acc| acc[property.name] = property }.values
88
84
 
89
- Schema::Object.new properties, additional, false
85
+ Schema::Object.new(properties, additional)
90
86
  end
91
87
 
92
88
  # Rewrite a property to allow Schema::Anything as value
93
89
  def allow_anything(property)
94
90
  Schema::Object::Property.new(
95
91
  property.name,
96
- Schema::AnyOf.new([property.value, Schema::Anything.instance]).simplify,
92
+ Schema::AnyOf.canonical([property.value, Schema::Anything.instance]),
97
93
  property.required
98
94
  )
99
95
  end
@@ -2,7 +2,7 @@ require 'values'
2
2
 
3
3
  module Babl
4
4
  module Nodes
5
- class Nav < Value.new(:through, :node)
5
+ class Nav < ::Value.new(:through, :node)
6
6
  def dependencies
7
7
  { through => node.dependencies }
8
8
  end
@@ -4,7 +4,7 @@ require 'values'
4
4
 
5
5
  module Babl
6
6
  module Nodes
7
- class Object < Value.new(:nodes)
7
+ class Object < ::Value.new(:nodes)
8
8
  def dependencies
9
9
  nodes.values.map(&:dependencies).reduce({}) { |a, b| Babl::Utils::Hash.deep_merge(a, b) }
10
10
  end
@@ -15,7 +15,7 @@ module Babl
15
15
 
16
16
  def schema
17
17
  properties = nodes.map { |k, v| Schema::Object::Property.new(k, v.schema, true) }
18
- Schema::Object.new(properties, false, false)
18
+ Schema::Object.new(properties, false)
19
19
  end
20
20
 
21
21
  def render(ctx)
@@ -4,10 +4,10 @@ require 'values'
4
4
 
5
5
  module Babl
6
6
  module Nodes
7
- class Parent < Value.new(:node)
7
+ class Parent < ::Value.new(:node)
8
8
  PARENT_MARKER = Utils::Ref.new
9
9
 
10
- class Resolver < Value.new(:node)
10
+ class Resolver < ::Value.new(:node)
11
11
  def dependencies
12
12
  backpropagate_dependencies(node.dependencies)
13
13
  end
@@ -27,7 +27,7 @@ module Babl
27
27
  private
28
28
 
29
29
  def backpropagate_dependencies(deps)
30
- raise Errors::InvalidTemplateError, 'Out of context parent dependency' if deps.key? PARENT_MARKER
30
+ raise Errors::InvalidTemplate, 'Out of context parent dependency' if deps.key? PARENT_MARKER
31
31
  new_deps = backpropagate_dependencies_one_level(deps)
32
32
  deps == new_deps ? new_deps : backpropagate_dependencies(new_deps)
33
33
  end
@@ -4,17 +4,13 @@ require 'values'
4
4
 
5
5
  module Babl
6
6
  module Nodes
7
- class Static < Value.new(:serialized_value)
8
- def initialize(value)
9
- super(value)
10
- end
11
-
7
+ class Static < ::Value.new(:value)
12
8
  def schema
13
- Schema::Static.new(serialized_value)
9
+ Schema::Static.new(value)
14
10
  end
15
11
 
16
12
  def render(_ctx)
17
- serialized_value
13
+ value
18
14
  end
19
15
 
20
16
  def dependencies
@@ -5,7 +5,7 @@ require 'values'
5
5
 
6
6
  module Babl
7
7
  module Nodes
8
- class Switch < Value.new(:nodes)
8
+ class Switch < ::Value.new(:nodes)
9
9
  def dependencies
10
10
  (nodes.values + nodes.keys).map(&:dependencies)
11
11
  .reduce({}) { |a, b| Babl::Utils::Hash.deep_merge(a, b) }
@@ -17,7 +17,7 @@ module Babl
17
17
  end
18
18
 
19
19
  def schema
20
- Schema::AnyOf.new(nodes.values.map(&:schema)).simplify
20
+ Schema::AnyOf.canonical(nodes.values.map(&:schema))
21
21
  end
22
22
 
23
23
  def render(ctx)
@@ -0,0 +1,42 @@
1
+ require 'values'
2
+ require 'babl/schema/typed'
3
+
4
+ module Babl
5
+ module Nodes
6
+ class Typed < ::Value.new(:types, :schema, :node)
7
+ BOOLEAN = method(:new).curry(3).call([TrueClass, FalseClass], Schema::Typed::BOOLEAN)
8
+ INTEGER = method(:new).curry(3).call([Integer], Schema::Typed::INTEGER)
9
+ NUMBER = method(:new).curry(3).call([Numeric], Schema::Typed::NUMBER)
10
+ STRING = method(:new).curry(3).call([String], Schema::Typed::STRING)
11
+
12
+ def dependencies
13
+ node.dependencies
14
+ end
15
+
16
+ def pinned_dependencies
17
+ node.pinned_dependencies
18
+ end
19
+
20
+ def render(ctx)
21
+ value = node.render(ctx)
22
+ unless types.any? { |type| type === value }
23
+ raise Errors::RenderingError, "Expected type '#{schema.type}': #{value}\n#{ctx.formatted_stack}"
24
+ end
25
+ value
26
+ end
27
+
28
+ private
29
+
30
+ def initialize(*)
31
+ super
32
+ check_type_compatibility
33
+ end
34
+
35
+ def check_type_compatibility
36
+ return if schema == node.schema
37
+ return if node.schema == Schema::Anything.instance
38
+ raise Errors::InvalidTemplate, "Type cannot be '#{schema.type}' in this context"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -3,7 +3,7 @@ require 'values'
3
3
 
4
4
  module Babl
5
5
  module Nodes
6
- class With < Value.new(:node, :nodes, :block)
6
+ class With < ::Value.new(:node, :nodes, :block)
7
7
  def schema
8
8
  node.schema
9
9
  end
@@ -7,7 +7,7 @@ module Babl
7
7
  # Interpret whatever is passed to this method as BABL template. It is idempotent.
8
8
  def call(*args, &block)
9
9
  return with(*args, &block) unless block.nil?
10
- raise Errors::InvalidTemplateError, 'call() expects exactly 1 argument (unless block)' unless args.size == 1
10
+ raise Errors::InvalidTemplate, 'call() expects exactly 1 argument (unless block)' unless args.size == 1
11
11
 
12
12
  arg = args.first
13
13
 
@@ -18,7 +18,7 @@ module Babl
18
18
  when ::Hash then object(**arg.map { |k, v| [:"#{k}", v] }.to_h)
19
19
  when ::Array then array(*arg)
20
20
  when ::String, ::Numeric, ::NilClass, ::TrueClass, ::FalseClass then static(arg)
21
- else raise Errors::InvalidTemplateError, "call() received invalid argument: #{arg}"
21
+ else raise Errors::InvalidTemplate, "call() received invalid argument: #{arg}"
22
22
  end
23
23
  end
24
24
  end
@@ -9,7 +9,7 @@ module Babl
9
9
  def continue
10
10
  construct_terminal { |context|
11
11
  node = context[:continue]
12
- raise Errors::InvalidTemplateError, 'continue() cannot be used outside switch()' unless node
12
+ raise Errors::InvalidTemplate, 'continue() cannot be used outside switch()' unless node
13
13
  node
14
14
  }
15
15
  end
@@ -9,7 +9,7 @@ module Babl
9
9
  def enter
10
10
  construct_node(key: nil, continue: nil) { |node, context|
11
11
  key = context[:key]
12
- raise Errors::InvalidTemplateError, "No key to enter into" unless key
12
+ raise Errors::InvalidTemplate, "No key to enter into" unless key
13
13
  Nodes::Nav.new(key, node)
14
14
  }
15
15
  end
@@ -9,7 +9,7 @@ module Babl
9
9
  # Create a JSON object node with static structure
10
10
  def object(*attrs, **nested)
11
11
  (attrs.map(&:to_sym) + nested.keys).group_by(&:itself).values.each do |keys|
12
- raise Errors::InvalidTemplateError, "Duplicate key in object(): #{keys.first}" if keys.size > 1
12
+ raise Errors::InvalidTemplate, "Duplicate key in object(): #{keys.first}" if keys.size > 1
13
13
  end
14
14
 
15
15
  construct_terminal { |ctx|
@@ -7,10 +7,10 @@ module Babl
7
7
  # Load a partial template given its name
8
8
  # A 'lookup_context' must be defined
9
9
  def partial(partial_name)
10
- raise Errors::InvalidTemplateError, "Cannot use partial without lookup context" unless lookup_context
10
+ raise Errors::InvalidTemplate, "Cannot use partial without lookup context" unless lookup_context
11
11
 
12
12
  path, source, partial_lookup_context = lookup_context.find(partial_name)
13
- raise Errors::InvalidTemplateError, "Cannot find partial '#{partial_name}'" unless path
13
+ raise Errors::InvalidTemplate, "Cannot find partial '#{partial_name}'" unless path
14
14
 
15
15
  with_lookup_context(partial_lookup_context)
16
16
  .source(source, path, 0)
@@ -25,7 +25,7 @@ module Babl
25
25
  # Override TemplateBase#precompile to ensure that all pin dependencies are satisfied.
26
26
  def precompile
27
27
  super.tap do |node|
28
- raise Errors::InvalidTemplateError, 'Unresolved pin' unless node.pinned_dependencies.empty?
28
+ raise Errors::InvalidTemplate, 'Unresolved pin' unless node.pinned_dependencies.empty?
29
29
  end
30
30
  end
31
31
  end
@@ -13,7 +13,7 @@ module Babl
13
13
  else call(Nodes::TerminalValue.instance.render_object(val))
14
14
  end
15
15
  rescue Errors::RenderingError => exception
16
- raise Errors::InvalidTemplateError, exception.message
16
+ raise Errors::InvalidTemplate, exception.message
17
17
  end
18
18
  end
19
19
  end
@@ -0,0 +1,25 @@
1
+ require 'babl/nodes/typed'
2
+
3
+ module Babl
4
+ module Operators
5
+ module Typed
6
+ module DSL
7
+ def integer
8
+ construct_node(continue: nil) { |node| Nodes::Typed::INTEGER.call(node) }
9
+ end
10
+
11
+ def number
12
+ construct_node(continue: nil) { |node| Nodes::Typed::NUMBER.call(node) }
13
+ end
14
+
15
+ def string
16
+ construct_node(continue: nil) { |node| Nodes::Typed::STRING.call(node) }
17
+ end
18
+
19
+ def boolean
20
+ construct_node(continue: nil) { |node| Nodes::Typed::BOOLEAN.call(node) }
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -4,7 +4,7 @@ require 'values'
4
4
 
5
5
  module Babl
6
6
  module Rendering
7
- class CompiledTemplate < Value.new(:node, :dependencies, :preloader, :pretty, :json_schema)
7
+ class CompiledTemplate < ::Value.new(:node, :dependencies, :preloader, :pretty, :json_schema)
8
8
  def json(root)
9
9
  data = render(root)
10
10
  ::Oj.dump(data, indent: pretty ? 4 : 0, mode: :strict)
@@ -5,7 +5,7 @@ require 'babl/schema/object'
5
5
 
6
6
  module Babl
7
7
  module Schema
8
- class AnyOf < Value.new(:choice_set)
8
+ class AnyOf < ::Value.new(:choice_set)
9
9
  attr_reader :choices
10
10
 
11
11
  def initialize(choices)
@@ -30,6 +30,10 @@ module Babl
30
30
  self
31
31
  end
32
32
 
33
+ def self.canonical(choices)
34
+ new(choices).simplify
35
+ end
36
+
33
37
  private
34
38
 
35
39
  # We can completely get rid of the AnyOf element of there is only one possible schema.
@@ -37,52 +41,34 @@ module Babl
37
41
  choices.size == 1 ? choices.first : nil
38
42
  end
39
43
 
40
- # Try to merge nullability into one of the object elements if they support that
41
- # (Object, DynArray and FixedArray).
44
+ # Try to merge null with another Anything
42
45
  def simplify_nullability
43
- if choices.include?(Static::NULL)
44
- others = choices - [Static::NULL]
45
- others.each do |other|
46
- new_other =
47
- case other
48
- when Object then Object.new(other.properties, other.additional, true)
49
- when DynArray then DynArray.new(other.item, true)
50
- when FixedArray then FixedArray.new(other.items, true)
51
- when Anything then other
52
- end
53
- return AnyOf.new(others - [other] + [new_other]).simplify if new_other
54
- end
55
- end
56
-
57
- nil
46
+ AnyOf.canonical(choices - [Static::NULL]) if ([Static::NULL, Anything.instance] - choices).empty?
58
47
  end
59
48
 
60
49
  # An always empty FixedArray is just a special case of a DynArray
61
50
  # We can get rid of the former and only keep the DynArray
62
51
  def simplify_empty_array
63
- fixed_array = choices.find { |s| FixedArray === s && s.items.empty? }
64
- if fixed_array
65
- others = choices - [fixed_array]
66
- others.each do |other|
67
- next unless DynArray === other
68
- new_other = DynArray.new(other.item, other.nullable || fixed_array.nullable)
69
- return AnyOf.new(others - [other] + [new_other]).simplify
70
- end
52
+ return unless choices.include?(FixedArray::EMPTY)
53
+ others = choices - [FixedArray::EMPTY]
54
+ others.each do |other|
55
+ next unless DynArray === other
56
+ new_other = DynArray.new(other.item)
57
+ return AnyOf.canonical(others - [other] + [new_other])
71
58
  end
72
-
73
59
  nil
74
60
  end
75
61
 
76
62
  # If the static array is an instance of another dyn array, then the fixed array can be
77
63
  # removed.
78
64
  def simplify_dyn_and_fixed_array
79
- dyns = choices.select { |s| DynArray === s }
80
- fixeds = choices.select { |s| FixedArray === s && s.items.uniq.size == 1 }
65
+ fixed_arrays = choices.select { |s| FixedArray === s && s.items.uniq.size == 1 }
81
66
 
82
- dyns.each do |dyn|
83
- fixeds.each do |fixed|
84
- new_dyn = DynArray.new(dyn.item, dyn.nullable || fixed.nullable)
85
- return AnyOf.new(choices - [fixed, dyn] + [new_dyn]).simplify if dyn.item == fixed.items.first
67
+ choices.each do |dyn|
68
+ next unless DynArray === dyn
69
+ fixed_arrays.each do |fixed|
70
+ new_dyn = DynArray.new(dyn.item)
71
+ return AnyOf.canonical(choices - [fixed, dyn] + [new_dyn]) if dyn.item == fixed.items.first
86
72
  end
87
73
  end
88
74
 
@@ -92,13 +78,10 @@ module Babl
92
78
  # If two objects have exactly the same structure, with the exception of only one property
93
79
  # having a different type, then AnyOf can be pushed down to this property.
94
80
  def simplify_many_objects_only_one_difference
95
- return unless choices.all? { |s| Object === s }
96
-
97
81
  choices.each_with_index { |obj1, index1|
98
82
  choices.each_with_index { |obj2, index2|
99
83
  next if index2 <= index1
100
- next unless Object === obj1 && Object === obj2
101
- next unless obj1.nullable == obj2.nullable && obj1.additional == obj2.additional
84
+ next unless Object === obj1 && Object === obj2 && obj1.additional == obj2.additional
102
85
  next unless obj1.properties.map { |p| [p.name, p.required] }.to_set ==
103
86
  obj2.properties.map { |p| [p.name, p.required] }.to_set
104
87
 
@@ -108,19 +91,18 @@ module Babl
108
91
  next unless diff1.size == 1 && diff2.size == 1 && diff1.first.name == diff2.first.name
109
92
 
110
93
  merged = Object.new(
111
- obj1.properties.map { |p|
112
- next p unless p == diff1.first
94
+ obj1.properties.map { |property|
95
+ next property unless property == diff1.first
113
96
  Object::Property.new(
114
- p.name,
115
- AnyOf.new([diff1.first.value, diff2.first.value]).simplify,
116
- p.required
97
+ property.name,
98
+ AnyOf.canonical([diff1.first.value, diff2.first.value]),
99
+ property.required
117
100
  )
118
101
  },
119
- obj1.additional,
120
- obj1.nullable
102
+ obj1.additional
121
103
  )
122
104
 
123
- return AnyOf.new(choices - [obj1, obj2] + [merged]).simplify
105
+ return AnyOf.canonical(choices - [obj1, obj2] + [merged])
124
106
  }
125
107
  }
126
108
 
@@ -129,8 +111,15 @@ module Babl
129
111
 
130
112
  # Push down the AnyOf to the item if all outputs are of type DynArray
131
113
  def simplify_push_down_dyn_array
132
- return unless choices.all? { |s| DynArray === s }
133
- DynArray.new(AnyOf.new(choices.map(&:item)).simplify, choices.any?(&:nullable))
114
+ choices.each_with_index { |arr1, index1|
115
+ choices.each_with_index { |arr2, index2|
116
+ next if index2 <= index1
117
+ next unless DynArray === arr1 && DynArray === arr2
118
+ new_arr = DynArray.new(AnyOf.canonical([arr1.item, arr2.item]))
119
+ return AnyOf.canonical(choices - [arr1, arr2] + [new_arr])
120
+ }
121
+ }
122
+ nil
134
123
  end
135
124
  end
136
125
  end
@@ -2,9 +2,9 @@ require 'values'
2
2
 
3
3
  module Babl
4
4
  module Schema
5
- class DynArray < Value.new(:item, :nullable)
5
+ class DynArray < ::Value.new(:item)
6
6
  def json
7
- { type: nullable ? %w[array null] : 'array', items: item.json }
7
+ { type: 'array', items: item.json }
8
8
  end
9
9
  end
10
10
  end
@@ -2,11 +2,11 @@ require 'values'
2
2
 
3
3
  module Babl
4
4
  module Schema
5
- class FixedArray < Value.new(:items, :nullable)
6
- EMPTY = new([], false)
5
+ class FixedArray < ::Value.new(:items)
6
+ EMPTY = new([])
7
7
 
8
8
  def json
9
- { type: nullable ? %w[array null] : 'array', items: items.map(&:json) }
9
+ { type: 'array', items: items.map(&:json) }
10
10
  end
11
11
  end
12
12
  end
@@ -3,25 +3,25 @@ require 'set'
3
3
 
4
4
  module Babl
5
5
  module Schema
6
- class Object < Value.new(:property_set, :additional, :nullable)
6
+ class Object < ::Value.new(:property_set, :additional)
7
7
  attr_reader :properties
8
8
 
9
- def initialize(properties, additional, nullable)
9
+ def initialize(properties, additional)
10
10
  @properties = properties
11
- super(properties.to_set, additional, nullable)
11
+ super(properties.to_set, additional)
12
12
  end
13
13
 
14
- class Property < Value.new(:name, :value, :required)
14
+ EMPTY = new([], false)
15
+ EMPTY_WITH_ADDITIONAL = new([], true)
16
+
17
+ class Property < ::Value.new(:name, :value, :required)
15
18
  def initialize(name, value, required)
16
19
  super(name, value, required)
17
20
  end
18
21
  end
19
22
 
20
- EMPTY = new([], false, false)
21
- EMPTY_WITH_ADDITIONAL = new([], true, false)
22
-
23
23
  def json
24
- { type: nullable ? %w[object null] : 'object' }.tap { |out|
24
+ { type: 'object' }.tap { |out|
25
25
  next if properties.empty?
26
26
  out[:properties] = properties.map { |property| [property.name, property.value.json] }.to_h
27
27
  out[:additionalProperties] = additional
@@ -2,7 +2,7 @@ require 'values'
2
2
 
3
3
  module Babl
4
4
  module Schema
5
- class Static < Value.new(:value)
5
+ class Static < ::Value.new(:value)
6
6
  NULL = new(nil)
7
7
 
8
8
  def json
@@ -0,0 +1,16 @@
1
+ require 'values'
2
+
3
+ module Babl
4
+ module Schema
5
+ class Typed < ::Value.new(:type)
6
+ INTEGER = new('integer')
7
+ BOOLEAN = new('boolean')
8
+ NUMBER = new('number')
9
+ STRING = new('string')
10
+
11
+ def json
12
+ { type: type }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -17,6 +17,7 @@ require 'babl/operators/pin'
17
17
  require 'babl/operators/source'
18
18
  require 'babl/operators/static'
19
19
  require 'babl/operators/switch'
20
+ require 'babl/operators/typed'
20
21
  require 'babl/operators/with'
21
22
 
22
23
  require 'babl/builder/template_base'
@@ -42,6 +43,7 @@ module Babl
42
43
  include Operators::Source::DSL
43
44
  include Operators::Static::DSL
44
45
  include Operators::Switch::DSL
46
+ include Operators::Typed::DSL
45
47
  include Operators::With::DSL
46
48
  end
47
49
  end
@@ -1,3 +1,3 @@
1
1
  module Babl
2
- VERSION = '0.2.1'
2
+ VERSION = '0.2.2'
3
3
  end
@@ -31,7 +31,7 @@ describe Babl::Operators::Array do
31
31
  let(:object) { 1 }
32
32
 
33
33
  it { expect(json).to eq([1]) }
34
- it { expect(schema).to eq s_fixed_array(s_anything, nullable: true) }
34
+ it { expect(schema).to eq s_any_of(s_null, s_fixed_array(s_anything)) }
35
35
  end
36
36
  end
37
37
  end
@@ -7,19 +7,19 @@ describe Babl::Operators::Continue do
7
7
  context 'navigation before continue' do
8
8
  template { nav(:abc).switch(false => 1, default => nav(:lol).continue).object(val: nav(:ok)) }
9
9
 
10
- it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplateError }
10
+ it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplate }
11
11
  end
12
12
 
13
13
  context 'continue without switch' do
14
14
  template { continue }
15
15
 
16
- it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplateError }
16
+ it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplate }
17
17
  end
18
18
 
19
19
  context 'continue in sub-object' do
20
20
  template { object(a: switch(default => object(x: continue))) }
21
21
 
22
- it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplateError }
22
+ it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplate }
23
23
  end
24
24
  end
25
25
  end
@@ -36,7 +36,7 @@ describe Babl::Operators::Each do
36
36
  let(:object) { [1, nil] }
37
37
 
38
38
  it { expect(json).to eq [{}, nil] }
39
- it { expect(schema).to eq s_dyn_array(s_object(nullable: true), nullable: true) }
39
+ it { expect(schema).to eq s_any_of(s_dyn_array(s_any_of(s_object, s_null)), s_null) }
40
40
  end
41
41
  end
42
42
  end
@@ -7,7 +7,7 @@ describe Babl::Operators::Enter do
7
7
  context 'invalid usage' do
8
8
  template { enter }
9
9
 
10
- it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplateError }
10
+ it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplate }
11
11
  end
12
12
 
13
13
  context 'valid usage' do
@@ -25,6 +25,6 @@ describe Babl::Operators::Extends do
25
25
 
26
26
  context 'extend a non-object and try to add properties' do
27
27
  template { each.extends('string_partial', test: 1) }
28
- it { expect { schema }.to raise_error Babl::Errors::InvalidTemplateError }
28
+ it { expect { schema }.to raise_error Babl::Errors::InvalidTemplate }
29
29
  end
30
30
  end
@@ -66,13 +66,13 @@ describe Babl::Operators::Merge do
66
66
  )
67
67
  }
68
68
 
69
- it { expect { schema }.to raise_error Babl::Errors::InvalidTemplateError }
69
+ it { expect { schema }.to raise_error Babl::Errors::InvalidTemplate }
70
70
  end
71
71
 
72
72
  context 'merge only one static value' do
73
73
  template { merge(42) }
74
74
 
75
- it { expect { schema }.to raise_error Babl::Errors::InvalidTemplateError }
75
+ it { expect { schema }.to raise_error Babl::Errors::InvalidTemplate }
76
76
  end
77
77
 
78
78
  context 'merge only one dynamic value' do
@@ -7,12 +7,13 @@ describe Babl::Operators::Nav do
7
7
  template { nav(:a) }
8
8
 
9
9
  context 'hash navigation' do
10
- let(:object) { { a: 42 } }
11
- it { expect(json).to eq(42) }
10
+ let(:object) { { a: '42' } }
11
+ it { expect(json).to eq('42') }
12
12
  it { expect(dependencies).to eq(a: {}) }
13
13
 
14
- context 'block navigation propagate dependency chain' do
14
+ context 'method navigation propagate dependency chain' do
15
15
  template { nav(:a).nav(:to_i) }
16
+ it { expect(json).to eq(42) }
16
17
  it { expect(dependencies).to eq(a: { to_i: {} }) }
17
18
  end
18
19
  end
@@ -57,7 +58,7 @@ describe Babl::Operators::Nav do
57
58
 
58
59
  context '#nav should stop key propagation for #enter' do
59
60
  template { object(a: nav._) }
60
- it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplateError }
61
+ it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplate }
61
62
  end
62
63
 
63
64
  context 'nav to array of complex objects' do
@@ -19,7 +19,7 @@ describe Babl::Operators::Nullable do
19
19
  expect(schema).to eq(
20
20
  s_object(
21
21
  s_property(:nullprop, s_anything),
22
- s_property(:notnullprop, s_object(s_property(:abc, s_anything), nullable: true))
22
+ s_property(:notnullprop, s_any_of(s_object(s_property(:abc, s_anything)), s_null))
23
23
  )
24
24
  )
25
25
  }
@@ -24,7 +24,7 @@ describe Babl::Operators::Object do
24
24
  context 'misused (chaining after object)' do
25
25
  template { object(:a).object(:b) }
26
26
 
27
- it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplateError }
27
+ it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplate }
28
28
  end
29
29
  end
30
30
  end
@@ -25,7 +25,7 @@ describe Babl::Operators::Parent do
25
25
  context 'invalid usage' do
26
26
  template { parent }
27
27
 
28
- it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplateError }
28
+ it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplate }
29
29
  end
30
30
 
31
31
  context 'deeply nested parent chain' do
@@ -25,7 +25,7 @@ describe Babl::Operators::Partial do
25
25
  context 'missing partial' do
26
26
  template { partial('i_do_not_exist') }
27
27
 
28
- it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplateError }
28
+ it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplate }
29
29
  end
30
30
 
31
31
  context 'found partial' do
@@ -60,7 +60,7 @@ describe Babl::Operators::Pin do
60
60
  )
61
61
  }
62
62
 
63
- it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplateError }
63
+ it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplate }
64
64
  end
65
65
 
66
66
  context 'when pinning is mixed with a "with" context' do
@@ -21,7 +21,7 @@ describe Babl::Operators::Static do
21
21
  context 'invalid' do
22
22
  template { static(test: Object.new) }
23
23
 
24
- it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplateError }
24
+ it { expect { compiled }.to raise_error Babl::Errors::InvalidTemplate }
25
25
  end
26
26
  end
27
27
  end
@@ -76,7 +76,7 @@ describe Babl::Operators::Switch do
76
76
  context 'switch between fixed array and a dyn array producing identical output' do
77
77
  template { switch(1 => nullable.each.static(1), 2 => [1]) }
78
78
 
79
- it { expect(schema).to eq s_dyn_array(s_static(1), nullable: true) }
79
+ it { expect(schema).to eq s_any_of(s_null, s_dyn_array(s_static(1))) }
80
80
  end
81
81
 
82
82
  context 'switch between similar objects having only one different property' do
@@ -108,9 +108,9 @@ describe Babl::Operators::Switch do
108
108
  end
109
109
 
110
110
  context 'switch between two possible dyn arrays' do
111
- template { switch(1 => each.static('a'), 2 => each.static('b')) }
111
+ template { switch(1 => each.static('a'), 2 => each.static('b'), 3 => nullable.each.static('c')) }
112
112
 
113
- it { expect(schema).to eq s_dyn_array(s_any_of(s_static('a'), s_static('b'))) }
113
+ it { expect(schema).to eq s_any_of(s_null, s_dyn_array(s_any_of(s_static('a'), s_static('c'), s_static('b')))) }
114
114
  end
115
115
 
116
116
  context 'with dependencies' do
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ describe Babl::Operators::Typed do
4
+ extend SpecHelper::OperatorTesting
5
+
6
+ describe '#integer' do
7
+ template { [integer] }
8
+
9
+ it { expect(schema).to eq s_fixed_array(s_integer) }
10
+
11
+ context do
12
+ let(:object) { 12 }
13
+ it { expect(json).to eq [12] }
14
+ end
15
+
16
+ context do
17
+ let(:object) { 12.5 }
18
+ it { expect { json }.to raise_error Babl::Errors::RenderingError }
19
+ end
20
+ end
21
+
22
+ describe '#number' do
23
+ template { [number] }
24
+
25
+ it { expect(schema).to eq s_fixed_array(s_number) }
26
+
27
+ context do
28
+ let(:object) { 12 }
29
+ it { expect(json).to eq [12] }
30
+ end
31
+
32
+ context do
33
+ let(:object) { 12.5 }
34
+ it { expect(json).to eq [12.5] }
35
+ end
36
+
37
+ context do
38
+ let(:object) { '12' }
39
+ it { expect { json }.to raise_error Babl::Errors::RenderingError }
40
+ end
41
+ end
42
+
43
+ describe '#string' do
44
+ template { [string] }
45
+
46
+ it { expect(schema).to eq s_fixed_array(s_string) }
47
+
48
+ context do
49
+ let(:object) { [12] }
50
+ it { expect { json }.to raise_error Babl::Errors::RenderingError }
51
+ end
52
+
53
+ context do
54
+ let(:object) { '12' }
55
+ it { expect(json).to eq ['12'] }
56
+ end
57
+ end
58
+
59
+ describe '#boolean' do
60
+ template { [boolean] }
61
+
62
+ it { expect(schema).to eq s_fixed_array(s_boolean) }
63
+
64
+ context do
65
+ let(:object) { true }
66
+ it { expect(json).to eq [true] }
67
+ end
68
+
69
+ context do
70
+ let(:object) { false }
71
+ it { expect(json).to eq [false] }
72
+ end
73
+
74
+ context do
75
+ let(:object) { 'true' }
76
+ it { expect { json }.to raise_error Babl::Errors::RenderingError }
77
+ end
78
+ end
79
+
80
+ context 'obviously invalid template' do
81
+ template { integer.string }
82
+ it { expect { schema }.to raise_error Babl::Errors::InvalidTemplate }
83
+ end
84
+ end
@@ -3,23 +3,43 @@ require 'babl'
3
3
  module SpecHelper
4
4
  module SchemaUtils
5
5
  def s_any_of(*args)
6
- Babl::Schema::AnyOf.new(args)
6
+ Babl::Schema::AnyOf.canonical(args)
7
7
  end
8
8
 
9
9
  def s_anything
10
10
  Babl::Schema::Anything.instance
11
11
  end
12
12
 
13
- def s_dyn_array(schema, nullable: false)
14
- Babl::Schema::DynArray.new(schema, nullable)
13
+ def s_dyn_array(schema)
14
+ Babl::Schema::DynArray.new(schema)
15
15
  end
16
16
 
17
- def s_fixed_array(*schemas, nullable: false)
18
- Babl::Schema::FixedArray.new(schemas, nullable)
17
+ def s_null
18
+ Babl::Schema::Static::NULL
19
19
  end
20
20
 
21
- def s_object(*properties, additional: false, nullable: false)
22
- Babl::Schema::Object.new(properties, additional, nullable)
21
+ def s_integer
22
+ Babl::Schema::Typed::INTEGER
23
+ end
24
+
25
+ def s_string
26
+ Babl::Schema::Typed::STRING
27
+ end
28
+
29
+ def s_boolean
30
+ Babl::Schema::Typed::BOOLEAN
31
+ end
32
+
33
+ def s_number
34
+ Babl::Schema::Typed::NUMBER
35
+ end
36
+
37
+ def s_fixed_array(*schemas)
38
+ Babl::Schema::FixedArray.new(schemas)
39
+ end
40
+
41
+ def s_object(*properties, additional: false)
42
+ Babl::Schema::Object.new(properties, additional)
23
43
  end
24
44
 
25
45
  def s_static(value)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: babl-json
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frederic Terrazzoni
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-11 00:00:00.000000000 Z
11
+ date: 2017-07-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -127,6 +127,7 @@ files:
127
127
  - lib/babl/nodes/static.rb
128
128
  - lib/babl/nodes/switch.rb
129
129
  - lib/babl/nodes/terminal_value.rb
130
+ - lib/babl/nodes/typed.rb
130
131
  - lib/babl/nodes/with.rb
131
132
  - lib/babl/operators/array.rb
132
133
  - lib/babl/operators/call.rb
@@ -147,6 +148,7 @@ files:
147
148
  - lib/babl/operators/source.rb
148
149
  - lib/babl/operators/static.rb
149
150
  - lib/babl/operators/switch.rb
151
+ - lib/babl/operators/typed.rb
150
152
  - lib/babl/operators/with.rb
151
153
  - lib/babl/railtie.rb
152
154
  - lib/babl/rendering/compiled_template.rb
@@ -182,6 +184,7 @@ files:
182
184
  - spec/operators/source_spec.rb
183
185
  - spec/operators/static_spec.rb
184
186
  - spec/operators/switch_spec.rb
187
+ - spec/operators/typed_spec.rb
185
188
  - spec/operators/with_spec.rb
186
189
  - spec/spec_helper.rb
187
190
  - spec/spec_helper/operator_testing.rb
@@ -230,6 +233,7 @@ test_files:
230
233
  - spec/operators/source_spec.rb
231
234
  - spec/operators/static_spec.rb
232
235
  - spec/operators/switch_spec.rb
236
+ - spec/operators/typed_spec.rb
233
237
  - spec/operators/with_spec.rb
234
238
  - spec/spec_helper.rb
235
239
  - spec/spec_helper/operator_testing.rb