babl-json 0.2.1 → 0.2.2

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