jimmy 0.5.1 → 2.0.0
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.
- 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 +100 -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 +106 -86
- 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 -10
- 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,113 +1,133 @@
|
|
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
|
37
|
+
yield self if block_given?
|
40
38
|
end
|
41
39
|
|
42
|
-
|
43
|
-
|
40
|
+
# Returns true when the schema will never validate against anything.
|
41
|
+
# @return [true, false]
|
42
|
+
def nothing?
|
43
|
+
@nothing
|
44
44
|
end
|
45
45
|
|
46
|
-
|
47
|
-
|
46
|
+
# Returns true when the schema will validate against anything.
|
47
|
+
# @return [true, false]
|
48
|
+
def anything?
|
49
|
+
!@nothing && empty?
|
48
50
|
end
|
49
51
|
|
50
|
-
|
51
|
-
|
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
|
52
67
|
end
|
53
68
|
|
54
|
-
|
55
|
-
|
69
|
+
# @see ::Object#inspect
|
70
|
+
def inspect
|
71
|
+
"#<#{self.class} #{super}>"
|
56
72
|
end
|
57
73
|
|
58
|
-
|
59
|
-
|
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
|
60
86
|
end
|
61
87
|
|
62
|
-
|
63
|
-
|
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
|
64
94
|
end
|
65
95
|
|
66
|
-
|
67
|
-
|
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']
|
68
101
|
end
|
69
102
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
103
|
+
# Returns true if the schema refers to another schema.
|
104
|
+
# @return [true, false]
|
105
|
+
def ref?
|
106
|
+
key? '$ref'
|
75
107
|
end
|
76
108
|
|
77
|
-
|
78
|
-
errors = JSON::Validator.fully_validate(JSON::Validator.schema_for_uri(uri).schema, data, errors_as_objects: true)
|
79
|
-
raise ValidationError.new(self, data, errors) unless errors.empty?
|
80
|
-
end
|
109
|
+
alias get fetch
|
81
110
|
|
82
|
-
|
83
|
-
@attrs = {}
|
84
|
-
@type = type
|
85
|
-
@domain = parent.domain
|
86
|
-
@dsl = SchemaTypes.dsls[type].new(self)
|
87
|
-
@parent = parent if parent.is_a? self.class
|
88
|
-
end
|
111
|
+
PROPERTY_SEQUENCE = PROPERTIES.each.with_index.to_h.freeze
|
89
112
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
when Symbol
|
94
|
-
dsl.__send__ arg
|
95
|
-
when Hash
|
96
|
-
arg.each { |k, v| dsl.__send__ k, v }
|
97
|
-
else
|
98
|
-
handler = Schema.argument_hander(SchemaTypes[type], arg)
|
99
|
-
raise "`#{type}` cannot handle arguments of type #{arg.class.name}" unless handler
|
100
|
-
dsl.evaluate handler, arg
|
101
|
-
end
|
102
|
-
end
|
103
|
-
if block
|
104
|
-
if dsl.respond_to? :with_locals
|
105
|
-
dsl.with_locals(locals) { dsl.evaluate block }
|
106
|
-
else
|
107
|
-
dsl.evaluate block
|
108
|
-
end
|
109
|
-
end
|
113
|
+
# @api private
|
114
|
+
def sort_keys_by(key, _value) # :nodoc:
|
115
|
+
PROPERTY_SEQUENCE.fetch(key) { raise KeyError, 'Not a valid schema key' }
|
110
116
|
end
|
111
117
|
|
118
|
+
protected
|
119
|
+
|
120
|
+
def schema
|
121
|
+
self
|
122
|
+
end
|
112
123
|
end
|
113
124
|
end
|
125
|
+
|
126
|
+
require 'jimmy/schema/array'
|
127
|
+
require 'jimmy/schema/number'
|
128
|
+
require 'jimmy/schema/object'
|
129
|
+
require 'jimmy/schema/string'
|
130
|
+
|
131
|
+
require 'jimmy/schema/operators'
|
132
|
+
require 'jimmy/schema/json'
|
133
|
+
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
|