jimmy 0.5.3 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.circleci/config.yml +35 -0
- data/.gitignore +4 -3
- data/.rspec +3 -0
- data/.rubocop.yml +61 -0
- data/.ruby-version +1 -1
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +13 -0
- data/LICENSE +1 -1
- data/README.md +22 -134
- data/Rakefile +91 -1
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/jimmy.gemspec +25 -21
- data/lib/jimmy.rb +23 -15
- data/lib/jimmy/declaration.rb +150 -0
- data/lib/jimmy/declaration/assertion.rb +81 -0
- data/lib/jimmy/declaration/casting.rb +34 -0
- data/lib/jimmy/declaration/composites.rb +41 -0
- data/lib/jimmy/declaration/number.rb +27 -0
- data/lib/jimmy/declaration/object.rb +11 -0
- data/lib/jimmy/declaration/string.rb +98 -0
- data/lib/jimmy/declaration/types.rb +57 -0
- data/lib/jimmy/error.rb +9 -0
- data/lib/jimmy/file_map.rb +166 -0
- data/lib/jimmy/index.rb +78 -0
- data/lib/jimmy/json/array.rb +93 -0
- data/lib/jimmy/json/collection.rb +90 -0
- data/lib/jimmy/json/hash.rb +118 -0
- data/lib/jimmy/json/pointer.rb +119 -0
- data/lib/jimmy/json/uri.rb +144 -0
- data/lib/jimmy/loaders/base.rb +30 -0
- data/lib/jimmy/loaders/json.rb +15 -0
- data/lib/jimmy/loaders/ruby.rb +21 -0
- data/lib/jimmy/macros.rb +37 -0
- data/lib/jimmy/schema.rb +107 -87
- data/lib/jimmy/schema/array.rb +95 -0
- data/lib/jimmy/schema/casting.rb +17 -0
- data/lib/jimmy/schema/json.rb +40 -0
- data/lib/jimmy/schema/number.rb +47 -0
- data/lib/jimmy/schema/object.rb +108 -0
- data/lib/jimmy/schema/operators.rb +96 -0
- data/lib/jimmy/schema/string.rb +44 -0
- data/lib/jimmy/schema_with_uri.rb +53 -0
- data/lib/jimmy/schemer_factory.rb +65 -0
- data/lib/jimmy/version.rb +3 -1
- data/schema07.json +172 -0
- metadata +50 -101
- data/circle.yml +0 -11
- data/lib/jimmy/combination.rb +0 -34
- data/lib/jimmy/definitions.rb +0 -38
- data/lib/jimmy/domain.rb +0 -111
- data/lib/jimmy/link.rb +0 -93
- data/lib/jimmy/reference.rb +0 -39
- data/lib/jimmy/schema_creation.rb +0 -121
- data/lib/jimmy/schema_type.rb +0 -100
- data/lib/jimmy/schema_types.rb +0 -42
- data/lib/jimmy/schema_types/array.rb +0 -30
- data/lib/jimmy/schema_types/boolean.rb +0 -6
- data/lib/jimmy/schema_types/integer.rb +0 -8
- data/lib/jimmy/schema_types/null.rb +0 -6
- data/lib/jimmy/schema_types/number.rb +0 -34
- data/lib/jimmy/schema_types/object.rb +0 -45
- data/lib/jimmy/schema_types/string.rb +0 -40
- data/lib/jimmy/symbol_array.rb +0 -17
- data/lib/jimmy/transform_keys.rb +0 -39
- data/lib/jimmy/type_reference.rb +0 -14
- data/lib/jimmy/validation_error.rb +0 -20
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jimmy
|
4
|
+
module Loaders
|
5
|
+
# Base class for all file loaders
|
6
|
+
# @abstract
|
7
|
+
class Base
|
8
|
+
# Load the given file. Intended to be used by a {Jimmy::FileMap}.
|
9
|
+
# @api private
|
10
|
+
# @param [Pathname, String] file Path of the file to load
|
11
|
+
def self.call(file)
|
12
|
+
new(file).load
|
13
|
+
end
|
14
|
+
|
15
|
+
# The source file to be loaded.
|
16
|
+
# @return Pathname
|
17
|
+
attr_reader :source
|
18
|
+
|
19
|
+
# @param [Pathname] source The source file to load.
|
20
|
+
def initialize(source)
|
21
|
+
@source = Pathname(source)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Jimmy::Schema]
|
25
|
+
def load
|
26
|
+
raise NotImplementedError, "Please implement #load on #{self.class}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jimmy/loaders/base'
|
4
|
+
|
5
|
+
module Jimmy
|
6
|
+
module Loaders
|
7
|
+
# Loads a plain .json file
|
8
|
+
class JSON < Base
|
9
|
+
# @return [Jimmy::Schema]
|
10
|
+
def load
|
11
|
+
Schema.new ::JSON.parse(source.read)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jimmy/macros'
|
4
|
+
require 'jimmy/loaders/base'
|
5
|
+
|
6
|
+
module Jimmy
|
7
|
+
module Loaders
|
8
|
+
# Loads .rb files
|
9
|
+
class Ruby < Base
|
10
|
+
include Macros
|
11
|
+
|
12
|
+
# @param [Pathname, string] file
|
13
|
+
# @return [Jimmy::Schema]
|
14
|
+
def load(file = source)
|
15
|
+
file = Pathname(file)
|
16
|
+
file = source.parent + file if file.relative?
|
17
|
+
Jimmy::Schema(instance_eval file.read, file.to_s)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/jimmy/macros.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jimmy/declaration'
|
4
|
+
|
5
|
+
module Jimmy
|
6
|
+
# The +Macros+ module includes methods that can be called directly on the
|
7
|
+
# +Jimmy+ module for quickly making common types of schemas.
|
8
|
+
module Macros
|
9
|
+
include Declaration
|
10
|
+
|
11
|
+
# Make a new schema. Shortcut for +Schema.new+.
|
12
|
+
# @yieldparam schema [Schema] The new schema
|
13
|
+
# @return [Schema] The new schema.
|
14
|
+
def schema(&block)
|
15
|
+
Schema.new &block
|
16
|
+
end
|
17
|
+
|
18
|
+
# Make a schema that never validates.
|
19
|
+
# @return [Schema] The new schema.
|
20
|
+
def nothing
|
21
|
+
schema.nothing
|
22
|
+
end
|
23
|
+
|
24
|
+
# Make a schema that references another schema by URI.
|
25
|
+
# @param [String, URI, Json::URI] uri
|
26
|
+
# @return [Schema] The new schema.
|
27
|
+
def ref(uri)
|
28
|
+
schema.ref uri
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def get(*args, &block)
|
34
|
+
{}.fetch(*args, &block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/jimmy/schema.rb
CHANGED
@@ -1,114 +1,134 @@
|
|
1
|
-
|
2
|
-
class Schema
|
3
|
-
JSON_SCHEMA_URI = 'http://json-schema.org/draft-04/schema#'
|
4
|
-
JSON_HYPER_SCHEMA_URI = 'http://json-schema.org/draft-04/hyper-schema#'
|
5
|
-
|
6
|
-
attr_reader :dsl, :attrs, :domain, :type, :parent
|
7
|
-
attr_writer :name
|
8
|
-
attr_accessor :nullable
|
9
|
-
|
10
|
-
@argument_handlers = Hash.new { |hash, key| hash[key] = {} }
|
11
|
-
|
12
|
-
def self.set_argument_handler(schema_class, arg_class, handler)
|
13
|
-
@argument_handlers[schema_class][arg_class] = handler
|
14
|
-
end
|
1
|
+
# frozen_string_literal: true
|
15
2
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
schema_class = schema_class.superclass
|
21
|
-
end
|
22
|
-
result = handlers.find { |k, _| argument.is_a? k }
|
23
|
-
result && result.last
|
24
|
-
end
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require 'jimmy/json/hash'
|
6
|
+
require 'jimmy/declaration'
|
25
7
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
8
|
+
module Jimmy
|
9
|
+
# Represents a schema as defined by http://json-schema.org/draft-07/schema
|
10
|
+
class Schema < Json::Hash
|
11
|
+
include Declaration
|
12
|
+
|
13
|
+
PROPERTIES = %w[
|
14
|
+
title description default readOnly writeOnly examples
|
15
|
+
multipleOf maximum exclusiveMaximum minimum exclusiveMinimum
|
16
|
+
maxLength minLength pattern
|
17
|
+
additionalItems items maxItems minItems uniqueItems contains
|
18
|
+
maxProperties minProperties required additionalProperties
|
19
|
+
definitions properties patternProperties dependencies propertyNames
|
20
|
+
const enum type format
|
21
|
+
contentMediaType contentEncoding
|
22
|
+
if then else
|
23
|
+
allOf anyOf oneOf
|
24
|
+
not
|
25
|
+
].freeze
|
26
|
+
|
27
|
+
# @yieldparam schema [self] The new schema
|
28
|
+
def initialize(schema = {})
|
29
|
+
@nothing = false
|
30
|
+
case schema
|
31
|
+
when *CASTABLE_CLASSES
|
32
|
+
super({})
|
33
|
+
apply_cast self, schema
|
34
|
+
when Hash then super
|
35
|
+
else raise Error::WrongType, "Unexpected #{schema.class}"
|
32
36
|
end
|
33
|
-
|
34
|
-
hash['type'] = nullable ? ['null', type.to_s] : type.to_s
|
35
|
-
hash['definitions'] = definitions.compile unless definitions.empty?
|
36
|
-
hash['links'] = links.map &:compile unless links.empty?
|
37
|
-
hash.merge! data
|
38
|
-
dsl.evaluate compiler, hash if compiler
|
39
|
-
hash['enum'] |= [nil] if nullable && hash.key?('enum')
|
40
|
-
hash
|
37
|
+
yield self if block_given?
|
41
38
|
end
|
42
39
|
|
43
|
-
|
44
|
-
|
40
|
+
# Returns true when the schema will never validate against anything.
|
41
|
+
# @return [true, false]
|
42
|
+
def nothing?
|
43
|
+
@nothing
|
45
44
|
end
|
46
45
|
|
47
|
-
|
48
|
-
|
46
|
+
# Returns true when the schema will validate against anything.
|
47
|
+
# @return [true, false]
|
48
|
+
def anything?
|
49
|
+
!@nothing && empty?
|
49
50
|
end
|
50
51
|
|
51
|
-
|
52
|
-
|
52
|
+
# Set a property of the schema.
|
53
|
+
# @param [String, Symbol] key Symbols are converted to camel-case strings.
|
54
|
+
# @param [Object] value
|
55
|
+
def []=(key, value)
|
56
|
+
@nothing = false
|
57
|
+
|
58
|
+
case key
|
59
|
+
when '$id' then @id = value # TODO: something, with this
|
60
|
+
when '$ref' then ref value
|
61
|
+
when '$schema'
|
62
|
+
URI(value) == URI(SCHEMA) or
|
63
|
+
raise Error::BadArgument, 'Unsupported JSON schema draft'
|
64
|
+
when '$comment' then @comment = value # TODO: something, with this
|
65
|
+
else super
|
66
|
+
end
|
53
67
|
end
|
54
68
|
|
55
|
-
|
56
|
-
|
69
|
+
# @see ::Object#inspect
|
70
|
+
def inspect
|
71
|
+
"#<#{self.class} #{super}>"
|
57
72
|
end
|
58
73
|
|
59
|
-
|
60
|
-
|
74
|
+
# Turns the schema into a reference to another schema. Freezes the schema
|
75
|
+
# so that no further changes can be made.
|
76
|
+
# @param [Json::URI, URI, String] uri The URI of the JSON schema to
|
77
|
+
# reference.
|
78
|
+
# @return [self]
|
79
|
+
def ref(uri)
|
80
|
+
assert empty? do
|
81
|
+
'Reference schemas cannot have other properties: ' +
|
82
|
+
keys.join(', ')
|
83
|
+
end
|
84
|
+
@members['$ref'] = Json::URI.new(uri)
|
85
|
+
freeze
|
61
86
|
end
|
62
87
|
|
63
|
-
|
64
|
-
|
88
|
+
# Make the schema validate nothing (i.e. everything is invalid).
|
89
|
+
# @return [self] self
|
90
|
+
def nothing
|
91
|
+
clear
|
92
|
+
@nothing = true
|
93
|
+
self
|
65
94
|
end
|
66
95
|
|
67
|
-
|
68
|
-
|
96
|
+
# Get the URI of the schema to which this schema refers, or nil if the
|
97
|
+
# schema is not a reference.
|
98
|
+
# @return [Json::URI, nil]
|
99
|
+
def target
|
100
|
+
self['$ref']
|
69
101
|
end
|
70
102
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
103
|
+
# Returns true if the schema refers to another schema.
|
104
|
+
# @return [true, false]
|
105
|
+
def ref?
|
106
|
+
key? '$ref'
|
76
107
|
end
|
77
108
|
|
78
|
-
|
79
|
-
errors = JSON::Validator.fully_validate(JSON::Validator.schema_for_uri(uri).schema, data, errors_as_objects: true)
|
80
|
-
raise ValidationError.new(self, data, errors) unless errors.empty?
|
81
|
-
end
|
109
|
+
alias get fetch
|
82
110
|
|
83
|
-
|
84
|
-
@attrs = {}
|
85
|
-
@type = type
|
86
|
-
@domain = parent.domain
|
87
|
-
@dsl = SchemaTypes.dsls[type].new(self)
|
88
|
-
@parent = parent if parent.is_a? self.class
|
89
|
-
end
|
111
|
+
PROPERTY_SEQUENCE = PROPERTIES.each.with_index.to_h.freeze
|
90
112
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
when Symbol
|
95
|
-
dsl.__send__ arg
|
96
|
-
when Hash
|
97
|
-
arg.each { |k, v| dsl.__send__ k, v }
|
98
|
-
else
|
99
|
-
handler = Schema.argument_hander(SchemaTypes[type], arg)
|
100
|
-
raise "`#{type}` cannot handle arguments of type #{arg.class.name}" unless handler
|
101
|
-
dsl.evaluate handler, arg
|
102
|
-
end
|
103
|
-
end
|
104
|
-
if block
|
105
|
-
if dsl.respond_to? :with_locals
|
106
|
-
dsl.with_locals(locals) { dsl.evaluate block }
|
107
|
-
else
|
108
|
-
dsl.evaluate block
|
109
|
-
end
|
110
|
-
end
|
113
|
+
# @api private
|
114
|
+
def sort_keys_by(key, _value) # :nodoc:
|
115
|
+
PROPERTY_SEQUENCE.fetch(key) { raise KeyError, 'Not a valid schema key' }
|
111
116
|
end
|
112
117
|
|
118
|
+
protected
|
119
|
+
|
120
|
+
def schema
|
121
|
+
yield self if block_given?
|
122
|
+
self
|
123
|
+
end
|
113
124
|
end
|
114
125
|
end
|
126
|
+
|
127
|
+
require 'jimmy/schema/array'
|
128
|
+
require 'jimmy/schema/number'
|
129
|
+
require 'jimmy/schema/object'
|
130
|
+
require 'jimmy/schema/string'
|
131
|
+
|
132
|
+
require 'jimmy/schema/operators'
|
133
|
+
require 'jimmy/schema/json'
|
134
|
+
require 'jimmy/schema/casting'
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jimmy
|
4
|
+
class Schema
|
5
|
+
# Set whether the array value is required to have unique items.
|
6
|
+
# @param [true, false] unique Whether the array value should have unique
|
7
|
+
# items.
|
8
|
+
# @return [self] self, for chaining
|
9
|
+
def unique_items(unique = true)
|
10
|
+
valid_for 'array'
|
11
|
+
assert_boolean unique
|
12
|
+
set uniqueItems: unique
|
13
|
+
end
|
14
|
+
|
15
|
+
alias unique unique_items
|
16
|
+
|
17
|
+
# Set the maximum items for an array value.
|
18
|
+
# @param [Numeric] count The maximum items for an array value.
|
19
|
+
# @return [self] self, for chaining
|
20
|
+
def max_items(count)
|
21
|
+
valid_for 'array'
|
22
|
+
assert_numeric count, minimum: 0
|
23
|
+
set maxItems: count
|
24
|
+
end
|
25
|
+
|
26
|
+
# Set the minimum items for an array value.
|
27
|
+
# @param [Numeric] count The minimum items for an array value.
|
28
|
+
# @return [self] self, for chaining
|
29
|
+
def min_items(count)
|
30
|
+
valid_for 'array'
|
31
|
+
assert_numeric count, minimum: 0
|
32
|
+
set minItems: count
|
33
|
+
end
|
34
|
+
|
35
|
+
# Set the minimum and maximum items for an array value, using a range.
|
36
|
+
# @param [Range, Integer] range The minimum and maximum items for an array
|
37
|
+
# value. If an integer is given, it is taken to be both.
|
38
|
+
# @return [self] self, for chaining
|
39
|
+
def count(range)
|
40
|
+
range = range..range if range.is_a?(Integer)
|
41
|
+
assert_range range
|
42
|
+
min_items range.min
|
43
|
+
max_items range.max unless range.end.nil?
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Set the schema or schemas for validating items in an array value.
|
48
|
+
# @param [Jimmy::Schema, Array<Jimmy::Schema>] schema_or_schemas A schema
|
49
|
+
# or array of schemas for validating items in an array value. If an
|
50
|
+
# array of schemas is given, the first schema will apply to the first
|
51
|
+
# item, and so on.
|
52
|
+
# @param [Jimmy::Schema, nil] rest_schema The schema to apply to items with
|
53
|
+
# indexes greater than the length of the first argument. Only applicable
|
54
|
+
# when an array is given for the first argument.
|
55
|
+
# @return [self] self, for chaining
|
56
|
+
def items(schema_or_schemas, rest_schema = nil)
|
57
|
+
if schema_or_schemas.is_a? Array
|
58
|
+
item *schema_or_schemas
|
59
|
+
set additionalItems: cast_schema(rest_schema) if rest_schema
|
60
|
+
else
|
61
|
+
match_all_items schema_or_schemas, rest_schema
|
62
|
+
end
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
# Add a single-item schema, or several, to the +items+ array. Only valid
|
67
|
+
# if a match-all schema has not been set.
|
68
|
+
# @param [Array<Jimmy::Schema>] single_item_schemas One or more schemas
|
69
|
+
# to add to the existing +items+ array.
|
70
|
+
# @return [self] self, for chaining
|
71
|
+
def item(*single_item_schemas)
|
72
|
+
valid_for 'array'
|
73
|
+
assert_array(single_item_schemas, minimum: 1)
|
74
|
+
existing = getset('items') { [] }
|
75
|
+
assert !existing.is_a?(Schema) do
|
76
|
+
'Cannot add individual item schemas after adding a match-all schema'
|
77
|
+
end
|
78
|
+
single_item_schemas.each do |schema|
|
79
|
+
existing << cast_schema(schema)
|
80
|
+
end
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def match_all_items(schema, rest_schema)
|
87
|
+
valid_for 'array'
|
88
|
+
assert(rest_schema.nil?) do
|
89
|
+
'You cannot specify an additional items schema when using a '\
|
90
|
+
'match-all schema'
|
91
|
+
end
|
92
|
+
set items: cast_schema(schema)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jimmy
|
4
|
+
class Schema
|
5
|
+
# The JSON Schema draft 7 schema URI
|
6
|
+
SCHEMA = 'http://json-schema.org/draft-07/schema#'
|
7
|
+
|
8
|
+
# Get the schema as a plain Hash. Given an +id+, the +$id+ and +$schema+
|
9
|
+
# keys will also be set.
|
10
|
+
# @param [Json::URI, URI, String] id
|
11
|
+
# @return [Hash, true, false]
|
12
|
+
def as_json(id: '', index: nil)
|
13
|
+
id = Json::URI.new(id)
|
14
|
+
|
15
|
+
if index.nil? && id.absolute?
|
16
|
+
return top_level_json(id) { super index: {}, id: id }
|
17
|
+
end
|
18
|
+
|
19
|
+
return true if anything?
|
20
|
+
return false if nothing?
|
21
|
+
|
22
|
+
super index: index || {}, id: id
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def top_level_json(id)
|
28
|
+
hash = {
|
29
|
+
'$id' => id.to_s,
|
30
|
+
'$schema' => SCHEMA
|
31
|
+
}
|
32
|
+
if nothing?
|
33
|
+
hash['not'] = true
|
34
|
+
else
|
35
|
+
hash.merge! yield
|
36
|
+
end
|
37
|
+
hash
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|