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
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'jimmy'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/jimmy.gemspec
CHANGED
@@ -1,28 +1,32 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
require 'jimmy/version'
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/jimmy/version'
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name
|
8
|
-
spec.version
|
9
|
-
spec.authors
|
10
|
-
spec.email
|
6
|
+
spec.name = 'jimmy'
|
7
|
+
spec.version = Jimmy::VERSION
|
8
|
+
spec.authors = ['Neil E. Pearson']
|
9
|
+
spec.email = ['neil@helium.net.au']
|
10
|
+
|
11
|
+
spec.summary = 'Jimmy the Gem'
|
12
|
+
spec.description = 'Jimmy the Gem'
|
13
|
+
spec.homepage = 'https://github.com/hx/jimmy'
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
|
11
15
|
|
12
|
-
spec.
|
13
|
-
spec.
|
14
|
-
spec.
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
17
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
18
|
+
spec.metadata['changelog_uri'] = spec.homepage
|
15
19
|
|
16
|
-
|
17
|
-
|
18
|
-
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added
|
22
|
+
# into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
24
|
+
`git ls-files -z`
|
25
|
+
.split("\x0")
|
26
|
+
.reject { |f| f.start_with? 'spec/' }
|
27
|
+
end
|
28
|
+
spec.bindir = 'exe'
|
29
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
30
|
spec.require_paths = ['lib']
|
20
31
|
spec.license = 'Apache License, Version 2.0'
|
21
|
-
|
22
|
-
spec.add_dependency 'json-schema', '~> 2.5'
|
23
|
-
|
24
|
-
spec.add_development_dependency 'bundler', '~> 1.9'
|
25
|
-
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
-
spec.add_development_dependency 'rspec', '~> 3.2'
|
27
|
-
spec.add_development_dependency 'diff_matcher', '~> 2.7'
|
28
32
|
end
|
data/lib/jimmy.rb
CHANGED
@@ -1,20 +1,28 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jimmy/error'
|
2
4
|
require 'jimmy/version'
|
5
|
+
require 'jimmy/schema'
|
6
|
+
require 'jimmy/macros'
|
7
|
+
require 'jimmy/file_map'
|
8
|
+
require 'jimmy/json/uri'
|
3
9
|
|
10
|
+
# Jimmy makes declaring and validating against JSON schemas a piece of cake.
|
4
11
|
module Jimmy
|
5
|
-
|
6
|
-
end
|
12
|
+
ROOT = Pathname(__dir__).parent
|
7
13
|
|
8
|
-
|
14
|
+
extend Macros
|
9
15
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
# @see SchemerFactory#initialize
|
17
|
+
def self.schemer(*args, **opts)
|
18
|
+
SchemerFactory.new(*args, **opts).schemer
|
19
|
+
end
|
20
|
+
|
21
|
+
# Passes +schema+ to {Schema.new}, unless it is already a {Schema}, in which
|
22
|
+
# case it is returned unmodified.
|
23
|
+
# @param [Schema, Object] schema
|
24
|
+
# @return [Schema]
|
25
|
+
def self.Schema(schema) # rubocop:disable Naming/MethodName
|
26
|
+
schema.is_a?(Schema) ? schema : Schema.new(schema)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jimmy/declaration/composites'
|
4
|
+
require 'jimmy/declaration/number'
|
5
|
+
require 'jimmy/declaration/object'
|
6
|
+
require 'jimmy/declaration/string'
|
7
|
+
require 'jimmy/declaration/types'
|
8
|
+
|
9
|
+
require 'jimmy/declaration/assertion'
|
10
|
+
require 'jimmy/declaration/casting'
|
11
|
+
|
12
|
+
module Jimmy
|
13
|
+
# Contains methods for declaring or modifying schemas.
|
14
|
+
module Declaration
|
15
|
+
# Set the title of the schema.
|
16
|
+
# @param [String] title The title of the schema.
|
17
|
+
# @return [self] self, for chaining
|
18
|
+
def title(title)
|
19
|
+
assert_string title
|
20
|
+
set title: title
|
21
|
+
end
|
22
|
+
|
23
|
+
# Set the description of the schema.
|
24
|
+
# @param [String] description The description of the schema.
|
25
|
+
# @return [self] self, for chaining
|
26
|
+
def description(description)
|
27
|
+
assert_string description
|
28
|
+
set description: description
|
29
|
+
end
|
30
|
+
|
31
|
+
# Set the default value for the schema.
|
32
|
+
# @param [Object] default The default value for the schema.
|
33
|
+
# @return [self] self, for chaining
|
34
|
+
def default(default)
|
35
|
+
set default: default
|
36
|
+
end
|
37
|
+
|
38
|
+
# Set whether the schema is read-only.
|
39
|
+
# @param [true, false] is_read_only
|
40
|
+
# @return [self] self, for chaining
|
41
|
+
def read_only(is_read_only = true)
|
42
|
+
assert_boolean is_read_only
|
43
|
+
set readOnly: is_read_only
|
44
|
+
end
|
45
|
+
|
46
|
+
# Set whether the schema is write-only.
|
47
|
+
# @param [true, false] is_write_only
|
48
|
+
# @return [self] self, for chaining
|
49
|
+
def write_only(is_write_only = true)
|
50
|
+
assert_boolean is_write_only
|
51
|
+
set writeOnly: is_write_only
|
52
|
+
end
|
53
|
+
|
54
|
+
# Set a constant value that will be expected to match exactly.
|
55
|
+
# @param [Object] constant_value The value that will be expected to match
|
56
|
+
# exactly.
|
57
|
+
# @return [self] self, for chaining
|
58
|
+
def const(constant_value)
|
59
|
+
set const: constant_value
|
60
|
+
end
|
61
|
+
|
62
|
+
# Set an enum value for the schema.
|
63
|
+
# @param [Array] allowed_values The allowed values in the enum.
|
64
|
+
# @return [self] self, for chaining
|
65
|
+
def enum(allowed_values)
|
66
|
+
assert_array allowed_values, minimum: 1, unique: true
|
67
|
+
set enum: allowed_values
|
68
|
+
end
|
69
|
+
|
70
|
+
# Add examples to the schema
|
71
|
+
# @param [Array] examples One or more examples to add to the schema.
|
72
|
+
# @return [self] self, for chaining
|
73
|
+
def examples(*examples)
|
74
|
+
getset('examples') { [] }.concat examples
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
alias example examples
|
79
|
+
|
80
|
+
# Add a schema to this schema's +definitions+ property.
|
81
|
+
# @param [String] name The name of the schema definition.
|
82
|
+
# @param [Jimmy::Schema] schema
|
83
|
+
# @yieldparam schema [Jimmy::Schema] The defined schema.
|
84
|
+
# @return [self] self, for chaining
|
85
|
+
def define(name, schema = Schema.new, &block)
|
86
|
+
return definitions name, &block if name.is_a? Hash
|
87
|
+
|
88
|
+
assign_to_schema_hash 'definitions', name, schema, &block
|
89
|
+
end
|
90
|
+
|
91
|
+
# Add definitions to the schema's +definitions+ property.
|
92
|
+
# @param [Hash{String => Jimmy::Schema, nil}] definitions Definitions to be
|
93
|
+
# added to the schema's +definitions+ property.
|
94
|
+
# @yieldparam name [String] The name of a definition that was given a nil
|
95
|
+
# schema.
|
96
|
+
# @yieldparam schema [Jimmy::Schema] A new schema created in place of a
|
97
|
+
# nil hash value.
|
98
|
+
# @return [self] self, for chaining
|
99
|
+
def definitions(definitions, &block)
|
100
|
+
batch_assign_to_schema_hash 'definitions', definitions, &block
|
101
|
+
end
|
102
|
+
|
103
|
+
# Define the schema that this schema must not match.
|
104
|
+
# @param schema [Jimmy::Schema] The schema that must not match.
|
105
|
+
# @return [self] self, for chaining
|
106
|
+
def not(schema)
|
107
|
+
# TODO: combine more nots into an anyOf
|
108
|
+
set not: cast_schema(schema)
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def set(props)
|
114
|
+
s = schema
|
115
|
+
props.each { |k, v| s[k.to_s] = v }
|
116
|
+
s
|
117
|
+
end
|
118
|
+
|
119
|
+
def getset(name)
|
120
|
+
set name => yield unless key? name
|
121
|
+
get name
|
122
|
+
end
|
123
|
+
|
124
|
+
def assign_to_schema_hash(property_name, key, schema)
|
125
|
+
property_name = cast_key(property_name)
|
126
|
+
key = cast_key(key)
|
127
|
+
hash = getset(property_name) { {} }
|
128
|
+
assert !hash.key?(key) do
|
129
|
+
"Property '#{property_name}' already has a member '#{key}'"
|
130
|
+
end
|
131
|
+
schema = cast_schema(schema)
|
132
|
+
yield schema if block_given?
|
133
|
+
hash[key] = schema
|
134
|
+
self
|
135
|
+
end
|
136
|
+
|
137
|
+
def batch_assign_to_schema_hash(property_name, hash)
|
138
|
+
assert_hash hash
|
139
|
+
hash.each do |name, schema|
|
140
|
+
name = cast_key(name)
|
141
|
+
if schema.nil? && block_given?
|
142
|
+
schema = Schema.new
|
143
|
+
yield name, schema
|
144
|
+
end
|
145
|
+
assign_to_schema_hash property_name, name, schema
|
146
|
+
end
|
147
|
+
self
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jimmy/error'
|
4
|
+
|
5
|
+
module Jimmy
|
6
|
+
module Declaration
|
7
|
+
BOOLEANS = Set.new([true, false]).freeze
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def assert(condition = false)
|
12
|
+
raise Error::InvalidSchemaPropertyValue, yield unless condition
|
13
|
+
end
|
14
|
+
|
15
|
+
def assert_numeric(value, minimum: -Float::INFINITY)
|
16
|
+
assert(value.is_a? Numeric) { "Expected #{value.class} to be numeric" }
|
17
|
+
assert(value >= minimum) { "Expected #{value} to be at least #{minimum}" }
|
18
|
+
end
|
19
|
+
|
20
|
+
def assert_string(value)
|
21
|
+
assert(value.is_a? String) { "Expected #{value.class} to be a string" }
|
22
|
+
end
|
23
|
+
|
24
|
+
def assert_simple_type(value)
|
25
|
+
assert_string value
|
26
|
+
assert SIMPLE_TYPES.include?(value) do
|
27
|
+
"Expected #{value.class} to be one of #{SIMPLE_TYPES.to_a.join ', '}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def assert_boolean(value)
|
32
|
+
assert BOOLEANS.include? value do
|
33
|
+
"Expected #{value.class} to be boolean"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def assert_array(value, unique: false, minimum: 0)
|
38
|
+
assert(value.is_a? Array) { "Expected #{value.class} to be an array" }
|
39
|
+
assert(value.uniq == value) { 'Expected a unique array' } if unique
|
40
|
+
assert value.length >= minimum do
|
41
|
+
"Expected an array of at least #{minimum} item(s)"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def assert_hash(value)
|
46
|
+
assert(value.is_a? Hash) { "Expected #{value.class} to be a hash" }
|
47
|
+
end
|
48
|
+
|
49
|
+
def assert_range(value)
|
50
|
+
assert(value.is_a? Range) { "Expected #{value.class} to be a range " }
|
51
|
+
end
|
52
|
+
|
53
|
+
def assert_regexp(value)
|
54
|
+
assert value.is_a? Regexp do
|
55
|
+
"Expected #{value.class} to be regular expression"
|
56
|
+
end
|
57
|
+
assert value.options.zero? do
|
58
|
+
"Expected #{value.inspect} not to have any options"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def valid_for(*types)
|
63
|
+
assert type? *types do
|
64
|
+
"The property is only valid for #{types.join ', '} schemas"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns true if one of the given types is an existing type.
|
69
|
+
# @param [Array<String>] types The type or types to check.
|
70
|
+
# @return [true, false]
|
71
|
+
def type?(*types)
|
72
|
+
types.each &method(:assert_simple_type)
|
73
|
+
existing = get('type', nil)
|
74
|
+
if existing.is_a? Json::Array
|
75
|
+
(existing.to_a & types).any?
|
76
|
+
else
|
77
|
+
types.include? existing
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jimmy
|
4
|
+
module Declaration
|
5
|
+
private
|
6
|
+
|
7
|
+
CASTS = {
|
8
|
+
TrueClass => ->(s, _) { s },
|
9
|
+
FalseClass => ->(s, _) { s.nothing },
|
10
|
+
Regexp => ->(s, v) { s.string.pattern v },
|
11
|
+
Range => ->(s, v) { s.number.range v }
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
CASTABLE_CLASSES = CASTS.keys.freeze
|
15
|
+
|
16
|
+
# Cast the given value to a usable schema.
|
17
|
+
# @param [Object] value
|
18
|
+
# @return [Jimmy::Schema]
|
19
|
+
def cast_schema(value)
|
20
|
+
case value
|
21
|
+
when *CASTABLE_CLASSES then apply_cast(Schema.new, value)
|
22
|
+
when Schema then value
|
23
|
+
else
|
24
|
+
assert { "Expected #{value.class} to be a schema" }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def apply_cast(schema, value)
|
29
|
+
CASTS.each do |klass, proc|
|
30
|
+
return proc.call schema, value if value.is_a? klass
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jimmy
|
4
|
+
module Declaration
|
5
|
+
# Set the +anyOf+ value for the schema.
|
6
|
+
# @param [Array<Jimmy::Schema>] schemas The schemas to set as the value of
|
7
|
+
# +anyOf+.
|
8
|
+
# @return [self] self, for chaining
|
9
|
+
def any_of(*schemas)
|
10
|
+
set_composite 'anyOf', schemas.flatten
|
11
|
+
end
|
12
|
+
|
13
|
+
# Set the +allOf+ value for the schema.
|
14
|
+
# @param [Array<Jimmy::Schema>] schemas The schemas to set as the value of
|
15
|
+
# +allOf+.
|
16
|
+
# @return [self] self, for chaining
|
17
|
+
def all_of(*schemas)
|
18
|
+
set_composite 'allOf', schemas.flatten
|
19
|
+
end
|
20
|
+
|
21
|
+
# Set the +oneOf+ value for the schema.
|
22
|
+
# @param [Array<Jimmy::Schema>] schemas The schemas to set as the value of
|
23
|
+
# +oneOf+.
|
24
|
+
# @return [self] self, for chaining
|
25
|
+
def one_of(*schemas)
|
26
|
+
set_composite 'oneOf', schemas.flatten
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# @return [self]
|
32
|
+
def set_composite(name, schemas)
|
33
|
+
assert_array schemas, minimum: 1
|
34
|
+
schemas = schemas.map(&method(:cast_schema))
|
35
|
+
assert schemas.none? { |s| s.anything? || s.nothing? } do
|
36
|
+
'Absolutes make no sense in composites'
|
37
|
+
end
|
38
|
+
set name => schemas
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jimmy
|
4
|
+
module Declaration
|
5
|
+
# Set the number of which the value should be a multiple.
|
6
|
+
# @param [Numeric] number The number to set as the multipleOf value
|
7
|
+
# @return [self] self, for chaining
|
8
|
+
def multiple_of(number)
|
9
|
+
valid_for 'number', 'integer'
|
10
|
+
assert_numeric number
|
11
|
+
assert(number.positive?) { "Expected #{number} to be positive" }
|
12
|
+
set multipleOf: number
|
13
|
+
end
|
14
|
+
|
15
|
+
# Set minimum and maximum by providing a range.
|
16
|
+
# @param [Range] range The range to use for minimum and maximum values.
|
17
|
+
# @return [self] self, for chaining
|
18
|
+
def range(range)
|
19
|
+
assert_range range
|
20
|
+
schema.minimum range.begin
|
21
|
+
unless range.end.nil?
|
22
|
+
schema.maximum range.end, exclusive: range.exclude_end?
|
23
|
+
end
|
24
|
+
self
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|