argo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: eec272ff117353e33f1c251074a13a9b80ee0133
4
+ data.tar.gz: 8849f7c278b5f83c27699d5e10a39bc3e32e68fa
5
+ SHA512:
6
+ metadata.gz: d936009d4cb7595e92127e2b0ed8d47ec391a53b780fd66f59f68e12144ed00e72eb79beb648112167b84c634767fc24fe7283975b510f06b6b7164afd2fd2e6
7
+ data.tar.gz: adf136d679c8b065c63cc6ad9f388f907db1905468c44dbb9112d39dd8bdd1de1e8d88f20e154c8a4193334aa0e034a780a0a750d39074303df828fa55c94154
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2015 Paul Battley
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
9
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13
+ PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,12 @@
1
+ require 'argo/property'
2
+
3
+ module Argo
4
+ class ArrayProperty < Property
5
+ def initialize(items:, **kwargs)
6
+ super(**kwargs)
7
+ @items = items
8
+ end
9
+
10
+ attr_reader :items
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ require 'argo/property'
2
+
3
+ module Argo
4
+ class BooleanProperty < Property
5
+ end
6
+ end
@@ -0,0 +1,53 @@
1
+ require 'forwardable'
2
+
3
+ module Argo
4
+ class ConstraintProcessor
5
+ extend Forwardable
6
+
7
+ NON_CONSTRAINT_PROPERTIES = %w[
8
+ description
9
+ items
10
+ type
11
+ ]
12
+
13
+ def initialize(dereferencer)
14
+ @dereferencer = dereferencer
15
+ end
16
+
17
+ def process(hash)
18
+ process_hash(
19
+ hash.reject { |k, _| NON_CONSTRAINT_PROPERTIES.include?(k) }
20
+ )
21
+ end
22
+
23
+ private
24
+
25
+ def_delegators :@dereferencer, :dereference, :reference?
26
+
27
+ def symbolize_key(k)
28
+ k.gsub(/[A-Z]/) { |m| "_#{m.downcase}" }.to_sym
29
+ end
30
+
31
+ def process_object(obj)
32
+ case obj
33
+ when Hash
34
+ process_hash(obj)
35
+ when Array
36
+ obj.map { |v| process_object(v) }.freeze
37
+ else
38
+ obj.freeze
39
+ end
40
+ end
41
+
42
+ def process_hash(hash)
43
+ if reference?(hash)
44
+ dereference(hash)
45
+ else
46
+ hash.
47
+ map { |k, v| [symbolize_key(k), process_object(v)] }.
48
+ to_h.
49
+ freeze
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,23 @@
1
+ require 'delegate'
2
+
3
+ module Argo
4
+ class DeferredObject < Delegator
5
+ instance_methods.each do |m|
6
+ unless m =~ /^__|^(respond_to\?|method_missing|object_id)$/
7
+ undef_method m
8
+ end
9
+ end
10
+
11
+ def initialize(&block)
12
+ @__delegator_block__ = block
13
+ @__mutex__ = Mutex.new
14
+ end
15
+
16
+ def __getobj__
17
+ @__mutex__.synchronize do
18
+ @__getobj__ ||= @__delegator_block__.call
19
+ end
20
+ @__getobj__
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ require 'argo/deferred_object'
2
+
3
+ module Argo
4
+ class Dereferencer
5
+ def initialize(&block)
6
+ @block = block
7
+ end
8
+
9
+ def dereference(hash)
10
+ path = hash.fetch('$ref')
11
+ fragments = path.split(/\//)
12
+ unless fragments[0] == '#'
13
+ raise "Can't dereference non-root-anchored path '#{path}'"
14
+ end
15
+ DeferredObject.new {
16
+ fragments.drop(1).inject(@block.call) { |schema, fragment|
17
+ schema.schemas.fetch(fragment)
18
+ }
19
+ }
20
+ end
21
+
22
+ def reference?(hash)
23
+ hash.key?('$ref')
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ module Argo
2
+ class ImmutableKeywordStruct
3
+ # Disable these metrics here as gnarly metaprogramming is inherently ugly
4
+ # and splitting it up won't help much.
5
+ #
6
+ # rubocop:disable Metrics/AbcSize
7
+ # rubocop:disable Metrics/MethodLength
8
+ def self.new(default_parameters, &block)
9
+ Class.new do
10
+ define_method :initialize do |**kwargs|
11
+ unknown_keys = kwargs.keys - __defaults__.keys
12
+ if unknown_keys.any?
13
+ raise ArgumentError, "unknown keyword: #{unknown_keys.first}"
14
+ end
15
+ __defaults__.each do |key, default|
16
+ instance_variable_set("@#{key}", kwargs.fetch(key, default).freeze)
17
+ end
18
+ end
19
+
20
+ attr_reader(*default_parameters.keys)
21
+
22
+ class_eval(&block) if block
23
+
24
+ private
25
+
26
+ define_method :__defaults__ do
27
+ default_parameters
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+ require 'argo/property'
2
+
3
+ module Argo
4
+ class IntegerProperty < Property
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ require 'argo/property'
2
+
3
+ module Argo
4
+ class NumberProperty < Property
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ require 'argo/property'
2
+
3
+ module Argo
4
+ class ObjectProperty < Property
5
+ end
6
+ end
@@ -0,0 +1,20 @@
1
+ require 'argo/dereferencer'
2
+ require 'argo/schema_factory'
3
+
4
+ module Argo
5
+ class Parser
6
+ def initialize(source)
7
+ @source = source
8
+ end
9
+
10
+ def root
11
+ @root ||= SchemaFactory.new(dereferencer).build(@source)
12
+ end
13
+
14
+ private
15
+
16
+ def dereferencer
17
+ @dereferencer ||= Dereferencer.new { root }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ require 'argo/immutable_keyword_struct'
2
+
3
+ module Argo
4
+ Property = ImmutableKeywordStruct.new(
5
+ constraints: {},
6
+ description: nil,
7
+ required: false
8
+ ) do
9
+ alias_method :required?, :required
10
+ end
11
+ end
@@ -0,0 +1,91 @@
1
+ require 'forwardable'
2
+
3
+ require 'argo/array_property'
4
+ require 'argo/boolean_property'
5
+ require 'argo/integer_property'
6
+ require 'argo/number_property'
7
+ require 'argo/object_property'
8
+ require 'argo/string_property'
9
+
10
+ require 'argo/constraint_processor'
11
+
12
+ module Argo
13
+ class PropertyFactory
14
+ extend Forwardable
15
+
16
+ TYPE_MAP = {
17
+ 'array' => ArrayProperty,
18
+ 'boolean' => BooleanProperty,
19
+ 'integer' => IntegerProperty,
20
+ 'number' => NumberProperty,
21
+ 'object' => ObjectProperty,
22
+ 'string' => StringProperty
23
+ }
24
+
25
+ def initialize(dereferencer, required_fields = [])
26
+ @dereferencer = dereferencer
27
+ @required_fields = required_fields
28
+ end
29
+
30
+ def_delegators :@dereferencer, :dereference, :reference?
31
+
32
+ def build(body, name: nil)
33
+ class_for_type(body).new(
34
+ constraints: constraints(body),
35
+ description: body['description'],
36
+ required: required?(name),
37
+ **additional_properties(body)
38
+ )
39
+ end
40
+
41
+ private
42
+
43
+ def class_for_type(body)
44
+ klass = explicit_class(body) || implicit_class(body)
45
+ raise "No type found in #{body.inspect}" unless klass
46
+ klass
47
+ end
48
+
49
+ def explicit_class(body)
50
+ return nil unless body.key?('type')
51
+ type = body.fetch('type')
52
+ raise "Unknown property type '#{type}'" unless TYPE_MAP.key?(type)
53
+ TYPE_MAP.fetch(type)
54
+ end
55
+
56
+ def implicit_class(body)
57
+ if body.key?('enum')
58
+ StringProperty
59
+ elsif (body.keys & %w[ oneOf anyOf ]).any?
60
+ ObjectProperty
61
+ end
62
+ end
63
+
64
+ def additional_properties(body)
65
+ if class_for_type(body) == ArrayProperty
66
+ additional_properties_for_array(body)
67
+ else
68
+ {}
69
+ end
70
+ end
71
+
72
+ def additional_properties_for_array(body)
73
+ items = [body.fetch('items')].flatten.first
74
+ if reference?(items)
75
+ value = dereference(items)
76
+ else
77
+ factory = PropertyFactory.new(@dereferencer)
78
+ value = factory.build(items)
79
+ end
80
+ { items: value }
81
+ end
82
+
83
+ def required?(name)
84
+ @required_fields.include?(name)
85
+ end
86
+
87
+ def constraints(hash)
88
+ ConstraintProcessor.new(@dereferencer).process(hash)
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,13 @@
1
+ require 'argo/immutable_keyword_struct'
2
+
3
+ module Argo
4
+ Schema = ImmutableKeywordStruct.new(
5
+ description: nil,
6
+ properties: [],
7
+ schemas: {},
8
+ spec: nil,
9
+ title: nil,
10
+ type: :object,
11
+ route: nil
12
+ )
13
+ end
@@ -0,0 +1,71 @@
1
+ require 'forwardable'
2
+ require 'argo/schema'
3
+ require 'argo/property_factory'
4
+
5
+ module Argo
6
+ class SchemaFactory
7
+ extend Forwardable
8
+
9
+ def initialize(dereferencer)
10
+ @dereferencer = dereferencer
11
+ end
12
+
13
+ def_delegators :@dereferencer, :dereference, :reference?
14
+
15
+ def build(subgraph, route = [])
16
+ Schema.new(
17
+ description: extract_one(subgraph, :description),
18
+ title: extract_one(subgraph, :title),
19
+ schemas: subschemas(subgraph, route),
20
+ type: type(subgraph),
21
+ properties: properties(subgraph),
22
+ spec: extract_one(subgraph, :$schema),
23
+ route: route
24
+ )
25
+ end
26
+
27
+ NON_SUBSCHEMA_PROPERTIES = %w[
28
+ $schema
29
+ additionalItems
30
+ additionalProperties
31
+ description
32
+ id
33
+ properties
34
+ required
35
+ title
36
+ type
37
+ ]
38
+
39
+ def element_kind(key)
40
+ NON_SUBSCHEMA_PROPERTIES.include?(key) ? key.to_sym : :subschema
41
+ end
42
+
43
+ def extract(subgraph, kind)
44
+ subgraph.select { |k, _| element_kind(k) == kind }
45
+ end
46
+
47
+ def extract_one(subgraph, kind, default: nil)
48
+ _, v = subgraph.find { |k, _| element_kind(k) == kind }
49
+ v || default
50
+ end
51
+
52
+ def subschemas(subgraph, route)
53
+ extract(subgraph, :subschema).
54
+ inject({}) { |h, (k, v)| h.merge(k => build(v, route + [k])) }
55
+ end
56
+
57
+ def type(subgraph)
58
+ extract_one(subgraph, :type, default: :object).to_sym
59
+ end
60
+
61
+ def properties(subgraph)
62
+ required_fields = extract_one(subgraph, :required, default: [])
63
+ factory = PropertyFactory.new(@dereferencer, required_fields)
64
+ extract_one(subgraph, :properties, default: {}).
65
+ map { |k, v|
66
+ [k, reference?(v) ? dereference(v) : factory.build(v, name: k)]
67
+ }.
68
+ to_h
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,6 @@
1
+ require 'argo/property'
2
+
3
+ module Argo
4
+ class StringProperty < Property
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module Argo
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,37 @@
1
+ require 'argo/parser'
2
+ require 'json'
3
+
4
+ RSpec.describe 'array reference' do
5
+ subject {
6
+ json = read_fixture('array_reference.json')
7
+ Argo::Parser.new(JSON.parse(json)).root
8
+ }
9
+
10
+ describe 'property with reference' do
11
+ subject { super().properties.fetch('a') }
12
+
13
+ it { is_expected.to be_kind_of(Argo::ArrayProperty) }
14
+
15
+ describe 'items' do
16
+ subject { super().items }
17
+
18
+ it 'has one property' do
19
+ expect(subject.properties.keys).to eq(%w[ thing_id ])
20
+ end
21
+ end
22
+ end
23
+
24
+ describe 'property with array containing one reference' do
25
+ subject { super().properties.fetch('b') }
26
+
27
+ it { is_expected.to be_kind_of(Argo::ArrayProperty) }
28
+
29
+ describe 'items' do
30
+ subject { super().items }
31
+
32
+ it 'has one property' do
33
+ expect(subject.properties.keys).to eq(%w[ thing_id ])
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,61 @@
1
+ require 'argo/parser'
2
+ require 'json'
3
+
4
+ RSpec.describe 'Example schemata' do
5
+ # See http://json-schema.org/examples.html
6
+
7
+ subject {
8
+ json = read_fixture('basic_example.json')
9
+ Argo::Parser.new(JSON.parse(json)).root
10
+ }
11
+
12
+ it 'has a title' do
13
+ expect(subject.title).
14
+ to eq('Example Schema')
15
+ end
16
+
17
+ it 'is an object' do
18
+ expect(subject.type).
19
+ to eq(:object)
20
+ end
21
+
22
+ describe 'properties' do
23
+ subject { super().properties }
24
+
25
+ it 'has three items' do
26
+ expect(subject.length).to eq(3)
27
+ end
28
+
29
+ describe 'firstName' do
30
+ subject { super().fetch('firstName') }
31
+
32
+ it { is_expected.to be_kind_of(Argo::StringProperty) }
33
+
34
+ it 'has no description' do
35
+ expect(subject.description).to be_nil
36
+ end
37
+
38
+ it 'is required' do
39
+ expect(subject).to be_required
40
+ end
41
+ end
42
+
43
+ describe 'age' do
44
+ subject { super().fetch('age') }
45
+
46
+ it { is_expected.to be_kind_of(Argo::IntegerProperty) }
47
+
48
+ it 'has a description' do
49
+ expect(subject.description).to eq('Age in years')
50
+ end
51
+
52
+ it 'is not required' do
53
+ expect(subject).not_to be_required
54
+ end
55
+
56
+ it 'has constraints' do
57
+ expect(subject.constraints).to eq(minimum: 0)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,48 @@
1
+ require 'argo/parser'
2
+ require 'json'
3
+
4
+ RSpec.describe 'Core spec section 3.4 nested schema' do
5
+ # See http://json-schema.org/latest/json-schema-core.html
6
+
7
+ subject {
8
+ json = read_fixture('core_nested_schema.json')
9
+ Argo::Parser.new(JSON.parse(json)).root
10
+ }
11
+
12
+ it 'has the title "root"' do
13
+ expect(subject.title).to eq('root')
14
+ end
15
+
16
+ it 'has an empty route' do
17
+ expect(subject.route).to eq([])
18
+ end
19
+
20
+ describe 'nested schema' do
21
+ subject { super().schemas.fetch('otherSchema') }
22
+
23
+ it 'has the title "nested"' do
24
+ expect(subject.title).to eq('nested')
25
+ end
26
+
27
+ it 'has a one-element route' do
28
+ expect(subject.route).to eq(%w[ otherSchema ])
29
+ end
30
+
31
+ describe 'doubly nested schema' do
32
+ subject { super().schemas.fetch('anotherSchema') }
33
+
34
+ it 'has the title "alsoNested"' do
35
+ expect(subject.title).to eq('alsoNested')
36
+ end
37
+
38
+ it 'has a two-element route' do
39
+ expect(subject.route).to eq(%w[ otherSchema anotherSchema ])
40
+ end
41
+ end
42
+ end
43
+
44
+ it 'is an object' do
45
+ expect(subject.type).
46
+ to eq(:object)
47
+ end
48
+ end
@@ -0,0 +1,19 @@
1
+ require 'argo/parser'
2
+ require 'json'
3
+
4
+ RSpec.describe 'Core spec section 3.4 simple schema' do
5
+ # See http://json-schema.org/latest/json-schema-core.html
6
+
7
+ subject {
8
+ json = read_fixture('core_simple_schema.json')
9
+ Argo::Parser.new(JSON.parse(json)).root
10
+ }
11
+
12
+ it 'has a title' do
13
+ expect(subject.title).to eq('root')
14
+ end
15
+
16
+ it 'is an object' do
17
+ expect(subject.type).to eq(:object)
18
+ end
19
+ end
@@ -0,0 +1,51 @@
1
+ require 'argo/parser'
2
+ require 'json'
3
+
4
+ RSpec.describe 'diskDevice' do
5
+ # See http://json-schema.org/example2.html
6
+
7
+ subject {
8
+ json = read_fixture('diskDevice.json')
9
+ Argo::Parser.new(JSON.parse(json)).root
10
+ }
11
+
12
+ it 'has no title' do
13
+ expect(subject.title).to be_nil
14
+ end
15
+
16
+ describe 'properties' do
17
+ subject { super().properties }
18
+
19
+ it 'has two items' do
20
+ expect(subject.length).to eq(2)
21
+ end
22
+
23
+ describe 'type' do
24
+ subject { super().fetch('type') }
25
+
26
+ it { is_expected.to be_kind_of(Argo::StringProperty) }
27
+
28
+ it 'is required' do
29
+ expect(subject).to be_required
30
+ end
31
+
32
+ it 'has an enum constraint' do
33
+ expect(subject.constraints).to eq(enum: %w[ disk ])
34
+ end
35
+ end
36
+
37
+ describe 'device' do
38
+ subject { super().fetch('device') }
39
+
40
+ it { is_expected.to be_kind_of(Argo::StringProperty) }
41
+
42
+ it 'is required' do
43
+ expect(subject).to be_required
44
+ end
45
+
46
+ it 'has a pattern constraint' do
47
+ expect(subject.constraints).to eq(pattern: '^/dev/[^/]+(/[^/]+)*$')
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,53 @@
1
+ require 'argo/parser'
2
+ require 'json'
3
+
4
+ RSpec.describe 'diskUUID' do
5
+ # See http://json-schema.org/example2.html
6
+
7
+ subject {
8
+ json = read_fixture('diskUUID.json')
9
+ Argo::Parser.new(JSON.parse(json)).root
10
+ }
11
+
12
+ it 'has no title' do
13
+ expect(subject.title).to be_nil
14
+ end
15
+
16
+ describe 'properties' do
17
+ subject { super().properties }
18
+
19
+ it 'has two items' do
20
+ expect(subject.length).to eq(2)
21
+ end
22
+
23
+ describe 'type' do
24
+ subject { super().fetch('type') }
25
+
26
+ it { is_expected.to be_kind_of(Argo::StringProperty) }
27
+
28
+ it 'is required' do
29
+ expect(subject).to be_required
30
+ end
31
+
32
+ it 'has an enum constraint' do
33
+ expect(subject.constraints).to eq(enum: %w[ disk ])
34
+ end
35
+ end
36
+
37
+ describe 'label' do
38
+ subject { super().fetch('label') }
39
+
40
+ it { is_expected.to be_kind_of(Argo::StringProperty) }
41
+
42
+ it 'is required' do
43
+ expect(subject).to be_required
44
+ end
45
+
46
+ it 'has a pattern constraint' do
47
+ expect(subject.constraints).to eq(
48
+ pattern: '^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$'
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,115 @@
1
+ require 'argo/parser'
2
+ require 'json'
3
+
4
+ RSpec.describe 'entry-schema' do
5
+ # See http://json-schema.org/example2.html
6
+
7
+ subject {
8
+ json = read_fixture('entry-schema.json')
9
+ Argo::Parser.new(JSON.parse(json)).root
10
+ }
11
+
12
+ it 'has a description' do
13
+ expect(subject.description).
14
+ to eq('schema for an fstab entry')
15
+ end
16
+
17
+ describe 'properties' do
18
+ subject { super().properties }
19
+
20
+ it 'has four items' do
21
+ expect(subject.length).to eq(4)
22
+ end
23
+
24
+ describe 'storage' do
25
+ subject { super().fetch('storage') }
26
+
27
+ it { is_expected.to be_kind_of(Argo::ObjectProperty) }
28
+
29
+ it 'is required' do
30
+ expect(subject).to be_required
31
+ end
32
+
33
+ describe 'one_of constraint' do
34
+ subject { super().constraints.fetch(:one_of) }
35
+
36
+ it 'has four entries' do
37
+ expect(subject.length).to eq(4)
38
+ end
39
+
40
+ describe 'first' do
41
+ subject { super().fetch(0) }
42
+
43
+ it { is_expected.to be_kind_of(Argo::Schema) }
44
+
45
+ describe 'properties' do
46
+ subject { super().properties }
47
+
48
+ it 'has two items' do
49
+ expect(subject.length).to eq(2)
50
+ end
51
+
52
+ describe 'type' do
53
+ subject { super().fetch('type') }
54
+
55
+ it { is_expected.to be_kind_of(Argo::StringProperty) }
56
+ end
57
+
58
+ describe 'device' do
59
+ subject { super().fetch('device') }
60
+
61
+ it { is_expected.to be_kind_of(Argo::StringProperty) }
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ describe 'fstype' do
69
+ subject { super().fetch('fstype') }
70
+
71
+ it { is_expected.to be_kind_of(Argo::StringProperty) }
72
+
73
+ it 'is not required' do
74
+ expect(subject).not_to be_required
75
+ end
76
+
77
+ it 'has an enum constraint' do
78
+ expect(subject.constraints).to eq(enum: %w[ ext3 ext4 btrfs ])
79
+ end
80
+ end
81
+
82
+ describe 'options' do
83
+ subject { super().fetch('options') }
84
+
85
+ it { is_expected.to be_kind_of(Argo::ArrayProperty) }
86
+
87
+ describe 'items' do
88
+ subject { super().items }
89
+
90
+ it { is_expected.to be_kind_of(Argo::StringProperty) }
91
+ end
92
+
93
+ it 'is not required' do
94
+ expect(subject).not_to be_required
95
+ end
96
+
97
+ it 'has minimum items and uniqueness constraints' do
98
+ expect(subject.constraints).to eq(
99
+ min_items: 1,
100
+ unique_items: true
101
+ )
102
+ end
103
+ end
104
+
105
+ describe 'readonly' do
106
+ subject { super().fetch('readonly') }
107
+
108
+ it { is_expected.to be_kind_of(Argo::BooleanProperty) }
109
+
110
+ it 'is not required' do
111
+ expect(subject).not_to be_required
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,71 @@
1
+ require 'argo/parser'
2
+ require 'json'
3
+
4
+ RSpec.describe 'nfs' do
5
+ # See http://json-schema.org/example2.html
6
+
7
+ subject {
8
+ json = read_fixture('nfs.json')
9
+ Argo::Parser.new(JSON.parse(json)).root
10
+ }
11
+
12
+ it 'has no title' do
13
+ expect(subject.title).to be_nil
14
+ end
15
+
16
+ describe 'properties' do
17
+ subject { super().properties }
18
+
19
+ it 'has three items' do
20
+ expect(subject.length).to eq(3)
21
+ end
22
+
23
+ describe 'type' do
24
+ subject { super().fetch('type') }
25
+
26
+ it { is_expected.to be_kind_of(Argo::StringProperty) }
27
+
28
+ it 'is required' do
29
+ expect(subject).to be_required
30
+ end
31
+
32
+ it 'has an enum constraint' do
33
+ expect(subject.constraints).to eq(enum: %w[ nfs ])
34
+ end
35
+ end
36
+
37
+ describe 'remotePath' do
38
+ subject { super().fetch('remotePath') }
39
+
40
+ it { is_expected.to be_kind_of(Argo::StringProperty) }
41
+
42
+ it 'is required' do
43
+ expect(subject).to be_required
44
+ end
45
+
46
+ it 'has a pattern constraint' do
47
+ expect(subject.constraints).to eq(pattern: '^(/[^/]+)+$')
48
+ end
49
+ end
50
+
51
+ describe 'server' do
52
+ subject { super().fetch('server') }
53
+
54
+ it { is_expected.to be_kind_of(Argo::StringProperty) }
55
+
56
+ it 'is required' do
57
+ expect(subject).to be_required
58
+ end
59
+
60
+ it 'has a one-of constraint' do
61
+ expect(subject.constraints).to eq(
62
+ one_of: [
63
+ { format: 'host-name' },
64
+ { format: 'ipv4' },
65
+ { format: 'ipv6' }
66
+ ]
67
+ )
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,27 @@
1
+ require 'argo/parser'
2
+ require 'json'
3
+
4
+ RSpec.describe 'property reference' do
5
+ subject {
6
+ json = read_fixture('property_reference.json')
7
+ Argo::Parser.new(JSON.parse(json)).root
8
+ }
9
+
10
+ describe 'a' do
11
+ subject { super().properties.fetch('a') }
12
+
13
+ it { is_expected.to be_kind_of(Argo::Schema) }
14
+ end
15
+
16
+ describe 'b' do
17
+ subject { super().properties.fetch('b') }
18
+
19
+ it { is_expected.to be_kind_of(Argo::ArrayProperty) }
20
+ end
21
+
22
+ describe 'c' do
23
+ subject { super().properties.fetch('c') }
24
+
25
+ it { is_expected.to be_kind_of(Argo::ArrayProperty) }
26
+ end
27
+ end
@@ -0,0 +1,114 @@
1
+ require 'argo/parser'
2
+ require 'json'
3
+
4
+ RSpec.describe 'Example schemata' do
5
+ subject {
6
+ json = read_fixture('simple_example.json')
7
+ Argo::Parser.new(JSON.parse(json)).root
8
+ }
9
+
10
+ it 'has a title' do
11
+ expect(subject.title).to eq('Product')
12
+ end
13
+
14
+ it 'is an object' do
15
+ expect(subject.type).to eq(:object)
16
+ end
17
+
18
+ it 'refers to the draft specification v4' do
19
+ expect(subject.spec).to eq('http://json-schema.org/draft-04/schema#')
20
+ end
21
+
22
+ it 'has a description' do
23
+ expect(subject.description).to eq("A product from Acme's catalog")
24
+ end
25
+
26
+ describe 'properties' do
27
+ subject { super().properties }
28
+
29
+ it 'has four items' do
30
+ expect(subject.length).to eq(4)
31
+ end
32
+
33
+ describe 'id' do
34
+ subject { super().fetch('id') }
35
+
36
+ it { is_expected.to be_kind_of(Argo::IntegerProperty) }
37
+
38
+ it 'has a description' do
39
+ expect(subject.description).
40
+ to eq('The unique identifier for a product')
41
+ end
42
+
43
+ it 'is required' do
44
+ expect(subject).to be_required
45
+ end
46
+
47
+ it 'has no constraints' do
48
+ expect(subject.constraints).to be_empty
49
+ end
50
+ end
51
+
52
+ describe 'name' do
53
+ subject { super().fetch('name') }
54
+
55
+ it { is_expected.to be_kind_of(Argo::StringProperty) }
56
+
57
+ it 'has a description' do
58
+ expect(subject.description).to eq('Name of the product')
59
+ end
60
+
61
+ it 'is required' do
62
+ expect(subject).to be_required
63
+ end
64
+
65
+ it 'has no constraints' do
66
+ expect(subject.constraints).to be_empty
67
+ end
68
+ end
69
+
70
+ describe 'price' do
71
+ subject { super().fetch('price') }
72
+
73
+ it { is_expected.to be_kind_of(Argo::NumberProperty) }
74
+
75
+ it 'has no description' do
76
+ expect(subject.description).to be_nil
77
+ end
78
+
79
+ it 'is required' do
80
+ expect(subject).to be_required
81
+ end
82
+
83
+ it 'has constraints' do
84
+ expect(subject.constraints).
85
+ to eq(minimum: 0, exclusive_minimum: true)
86
+ end
87
+ end
88
+
89
+ describe 'tags' do
90
+ subject { super().fetch('tags') }
91
+
92
+ it { is_expected.to be_kind_of(Argo::ArrayProperty) }
93
+
94
+ it 'has no description' do
95
+ expect(subject.description).to be_nil
96
+ end
97
+
98
+ it 'is not required' do
99
+ expect(subject).not_to be_required
100
+ end
101
+
102
+ it 'has constraints' do
103
+ expect(subject.constraints).
104
+ to eq(min_items: 1, unique_items: true)
105
+ end
106
+
107
+ describe 'items' do
108
+ subject { super().items }
109
+
110
+ it { is_expected.to be_kind_of(Argo::StringProperty) }
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,51 @@
1
+ require 'argo/parser'
2
+ require 'json'
3
+
4
+ RSpec.describe 'tmpfs' do
5
+ # See http://json-schema.org/example2.html
6
+
7
+ subject {
8
+ json = read_fixture('tmpfs.json')
9
+ Argo::Parser.new(JSON.parse(json)).root
10
+ }
11
+
12
+ it 'has no title' do
13
+ expect(subject.title).to be_nil
14
+ end
15
+
16
+ describe 'properties' do
17
+ subject { super().properties }
18
+
19
+ it 'has two items' do
20
+ expect(subject.length).to eq(2)
21
+ end
22
+
23
+ describe 'type' do
24
+ subject { super().fetch('type') }
25
+
26
+ it { is_expected.to be_kind_of(Argo::StringProperty) }
27
+
28
+ it 'is required' do
29
+ expect(subject).to be_required
30
+ end
31
+
32
+ it 'has constraints' do
33
+ expect(subject.constraints).to eq(enum: %w[ tmpfs ])
34
+ end
35
+ end
36
+
37
+ describe 'sizeInMB' do
38
+ subject { super().fetch('sizeInMB') }
39
+
40
+ it { is_expected.to be_kind_of(Argo::IntegerProperty) }
41
+
42
+ it 'is required' do
43
+ expect(subject).to be_required
44
+ end
45
+
46
+ it 'has constraints' do
47
+ expect(subject.constraints).to eq(minimum: 16, maximum: 512)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,25 @@
1
+ RSpec.configure do |config|
2
+ config.filter_run :focus
3
+ config.run_all_when_everything_filtered = true
4
+
5
+ if config.files_to_run.one?
6
+ config.default_formatter = 'doc'
7
+ end
8
+
9
+ config.order = :random
10
+
11
+ Kernel.srand config.seed
12
+
13
+ config.expect_with :rspec do |expectations|
14
+ expectations.syntax = :expect
15
+ end
16
+
17
+ config.mock_with :rspec do |mocks|
18
+ mocks.syntax = :expect
19
+ mocks.verify_partial_doubles = true
20
+ end
21
+
22
+ def read_fixture(name)
23
+ File.read(File.expand_path("../fixtures/#{name}", __FILE__))
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ require 'argo/deferred_object'
2
+
3
+ RSpec.describe Argo::DeferredObject do
4
+ subject { described_class.new { receiver.delegee } }
5
+ let(:receiver) { double(delegee: delegee) }
6
+ let(:delegee) { double(message: nil) }
7
+
8
+ it 'does not call the block at initialization time' do
9
+ expect(receiver).to receive(:delegee).never
10
+ subject
11
+ end
12
+
13
+ it 'calls the block when a method is called' do
14
+ expect(delegee).to receive(:message)
15
+ subject.message
16
+ end
17
+
18
+ it 'delegates the value to the receiver' do
19
+ allow(delegee).to receive(:message).and_return('VALUE')
20
+ expect(subject.message).to eq('VALUE')
21
+ end
22
+
23
+ it 'evaluates the block only once' do
24
+ expect(receiver).to receive(:delegee).once.and_return(delegee)
25
+ 2.times do
26
+ subject.message
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,54 @@
1
+ require 'argo/immutable_keyword_struct'
2
+
3
+ RSpec.describe Argo::ImmutableKeywordStruct do
4
+ let(:klass) {
5
+ described_class.new(a: nil, b: 'foo') do
6
+ def hello
7
+ "hello #{b}"
8
+ end
9
+ end
10
+ }
11
+
12
+ context 'with defaults' do
13
+ subject { klass.new }
14
+
15
+ it 'assigns properties with defaults' do
16
+ expect(subject.a).to be_nil
17
+ expect(subject.b).to eq('foo')
18
+ end
19
+
20
+ it 'freezes values' do
21
+ expect {
22
+ subject.b << ' oops'
23
+ }.to raise_exception(RuntimeError)
24
+ end
25
+
26
+ it 'allows method definition in a block' do
27
+ expect(subject.hello).to eq('hello foo')
28
+ end
29
+ end
30
+
31
+ context 'with assigned values' do
32
+ subject { klass.new(b: 'bar') }
33
+
34
+ it 'overrides defaults' do
35
+ expect(subject.b).to eq('bar')
36
+ end
37
+
38
+ it 'freezes values' do
39
+ expect {
40
+ subject.b << ' oops'
41
+ }.to raise_exception(RuntimeError)
42
+ end
43
+
44
+ it 'allows method definition in a block' do
45
+ expect(subject.hello).to eq('hello bar')
46
+ end
47
+ end
48
+
49
+ it 'raises an exception with an unrecognised keyword' do
50
+ expect {
51
+ klass.new(c: 1)
52
+ }.to raise_exception(ArgumentError)
53
+ end
54
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: argo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul Battley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.9'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.32'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.32'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop-rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.3'
69
+ description: Expand JSON Schema(s) into introspectable Ruby objects
70
+ email: pbattley@gmail.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files:
74
+ - COPYING.txt
75
+ files:
76
+ - COPYING.txt
77
+ - lib/argo/array_property.rb
78
+ - lib/argo/boolean_property.rb
79
+ - lib/argo/constraint_processor.rb
80
+ - lib/argo/deferred_object.rb
81
+ - lib/argo/dereferencer.rb
82
+ - lib/argo/immutable_keyword_struct.rb
83
+ - lib/argo/integer_property.rb
84
+ - lib/argo/number_property.rb
85
+ - lib/argo/object_property.rb
86
+ - lib/argo/parser.rb
87
+ - lib/argo/property.rb
88
+ - lib/argo/property_factory.rb
89
+ - lib/argo/schema.rb
90
+ - lib/argo/schema_factory.rb
91
+ - lib/argo/string_property.rb
92
+ - lib/argo/version.rb
93
+ - spec/integration/array_reference_spec.rb
94
+ - spec/integration/basic_example_spec.rb
95
+ - spec/integration/core_nested_schema_spec.rb
96
+ - spec/integration/core_simple_schema_spec.rb
97
+ - spec/integration/disk_device_spec.rb
98
+ - spec/integration/disk_uuid_spec.rb
99
+ - spec/integration/entry_schema_spec.rb
100
+ - spec/integration/nfs_spec.rb
101
+ - spec/integration/property_reference_spec.rb
102
+ - spec/integration/simple_example_spec.rb
103
+ - spec/integration/tmpfs_spec.rb
104
+ - spec/spec_helper.rb
105
+ - spec/unit/deferred_object_spec.rb
106
+ - spec/unit/immutable_keyword_struct_spec.rb
107
+ homepage: https://github.com/threedaymonk/argo
108
+ licenses:
109
+ - ISC
110
+ metadata: {}
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: 2.1.0
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 2.4.5
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: JSON Schema expander
131
+ test_files: []