jimmy 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/Gemfile +3 -0
- data/LICENSE +13 -0
- data/README.md +152 -0
- data/Rakefile +4 -0
- data/circle.yml +11 -0
- data/jimmy.gemspec +25 -0
- data/lib/jimmy/combination.rb +25 -0
- data/lib/jimmy/domain.rb +50 -0
- data/lib/jimmy/schema.rb +70 -0
- data/lib/jimmy/schema_creation.rb +52 -0
- data/lib/jimmy/schema_type.rb +63 -0
- data/lib/jimmy/schema_types/array.rb +29 -0
- data/lib/jimmy/schema_types/boolean.rb +6 -0
- data/lib/jimmy/schema_types/integer.rb +8 -0
- data/lib/jimmy/schema_types/null.rb +6 -0
- data/lib/jimmy/schema_types/number.rb +30 -0
- data/lib/jimmy/schema_types/object.rb +42 -0
- data/lib/jimmy/schema_types/string.rb +18 -0
- data/lib/jimmy/schema_types.rb +42 -0
- data/lib/jimmy/symbol_array.rb +17 -0
- data/lib/jimmy/version.rb +3 -0
- data/lib/jimmy.rb +15 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8467dcce9891fc3ea4098a9e89ec2591c58cf1df
|
4
|
+
data.tar.gz: 76ce29801be14c4d29e640d1820e5ea23eda0bb5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bd47a8d315ea3d3cea8f6faf37441ebb684f0fccb04d8819887bf727dfdefe7d89b810a3d3c09a37d4e2cb0cad50677476220368908da17ec690c29ac00e5d27
|
7
|
+
data.tar.gz: 3b66e4dde7fafab70cb5d52941a4e1d0342509634da90fc9a3f736bbe4b17e5b8d7d2678120c115f9a58dc8a68e178be91c8b382f7188f59760086fb62cd8ae6
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2015 OrionVM Pty Ltd
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
# Jimmy the Gem
|
2
|
+
|
3
|
+
Meet your mate Jimmy. He's a top bloke.
|
4
|
+
|
5
|
+
Writing JSON schemas is as tedious as a tax audit. But now, thanks to everyone's new best friend Jimmy, it's easier than shooting the side of a barn with a very easy-to-use projectile weapon.
|
6
|
+
|
7
|
+
## Getting hooked up
|
8
|
+
|
9
|
+
You guessed it:
|
10
|
+
|
11
|
+
```bash
|
12
|
+
$ gem install jimmy
|
13
|
+
```
|
14
|
+
|
15
|
+
Here's another doozy:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'jimmy'
|
19
|
+
```
|
20
|
+
|
21
|
+
Wasn't that a shock. Let's move on.
|
22
|
+
|
23
|
+
## The DSL
|
24
|
+
|
25
|
+
Jimmy replaces `.json` JSON schemas files with `.rb` files. Here's a simple example:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
# city.rb
|
29
|
+
object do
|
30
|
+
string :name, min_length: 2
|
31
|
+
string :postcode, /^\d{4}$/
|
32
|
+
|
33
|
+
require all
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
This compiles to:
|
38
|
+
|
39
|
+
```json
|
40
|
+
{
|
41
|
+
"$schema": "https://my.domain.kom/city#",
|
42
|
+
"type": "object",
|
43
|
+
"properties": {
|
44
|
+
"name": {
|
45
|
+
"type": "string",
|
46
|
+
"minLength": 2
|
47
|
+
},
|
48
|
+
"postcode": {
|
49
|
+
"type": "string",
|
50
|
+
"pattern": "^\\d{4}$"
|
51
|
+
},
|
52
|
+
"required": ["name", "zipcode"],
|
53
|
+
"additionalProperties": false
|
54
|
+
}
|
55
|
+
}
|
56
|
+
```
|
57
|
+
|
58
|
+
Crikey! That's a bit of a difference. Let's take a look at a more complex (and contrived) example:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
# types/country_code.rb
|
62
|
+
string /^[A-Z]{2}$/
|
63
|
+
|
64
|
+
# types/geopoint.rb
|
65
|
+
object do
|
66
|
+
number :latitude, -90..90
|
67
|
+
number :longitude, -180..180
|
68
|
+
end
|
69
|
+
|
70
|
+
# city.rb
|
71
|
+
object do
|
72
|
+
string :name, min_length: 2
|
73
|
+
string :postcode, /^\d{4}$/
|
74
|
+
integer :population
|
75
|
+
geopoint :location
|
76
|
+
country_code :country
|
77
|
+
array :points_of_interest do
|
78
|
+
object do
|
79
|
+
string :title, 3..150
|
80
|
+
integer :popularity, 1..5
|
81
|
+
geopoint :location
|
82
|
+
boolean :featured
|
83
|
+
require :title
|
84
|
+
end
|
85
|
+
end
|
86
|
+
require all - :location
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
Here's the full result (though we expect you get the gist of it by now):
|
91
|
+
|
92
|
+
```json
|
93
|
+
{
|
94
|
+
"$schema": "https://my.domain.kom/city#",
|
95
|
+
"type": "object",
|
96
|
+
"properties": {
|
97
|
+
"name": {
|
98
|
+
"type": "string",
|
99
|
+
"minLength": 2
|
100
|
+
},
|
101
|
+
"postcode": {
|
102
|
+
"type": "string",
|
103
|
+
"pattern": "^\\d{4}$"
|
104
|
+
},
|
105
|
+
"population": {
|
106
|
+
"type": "integer"
|
107
|
+
},
|
108
|
+
"location": {
|
109
|
+
"$ref": "/types/geopoint#"
|
110
|
+
},
|
111
|
+
"country": {
|
112
|
+
"$ref": "/types/country_code#"
|
113
|
+
},
|
114
|
+
"points_of_interest": {
|
115
|
+
"type": "array",
|
116
|
+
"items": {
|
117
|
+
"type": "object",
|
118
|
+
"properties": {
|
119
|
+
"title": {
|
120
|
+
"type": "string",
|
121
|
+
"minLength": 3,
|
122
|
+
"maxLength": 150
|
123
|
+
},
|
124
|
+
"popularity": {
|
125
|
+
"type": "integer",
|
126
|
+
"minimum": 1,
|
127
|
+
"maximum": 5
|
128
|
+
},
|
129
|
+
"location": {
|
130
|
+
"$ref": "/types/geopoint#"
|
131
|
+
},
|
132
|
+
"featured": {
|
133
|
+
"type": "boolean"
|
134
|
+
}
|
135
|
+
},
|
136
|
+
"required": [
|
137
|
+
"title"
|
138
|
+
],
|
139
|
+
"additionalProperties": false
|
140
|
+
}
|
141
|
+
}
|
142
|
+
},
|
143
|
+
"required": [
|
144
|
+
"name",
|
145
|
+
"postcode",
|
146
|
+
"population",
|
147
|
+
"country",
|
148
|
+
"points_of_interest"
|
149
|
+
],
|
150
|
+
"additionalProperties": false
|
151
|
+
}
|
152
|
+
```
|
data/Rakefile
ADDED
data/circle.yml
ADDED
data/jimmy.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'jimmy/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'jimmy'
|
8
|
+
spec.version = Jimmy::VERSION
|
9
|
+
spec.authors = ['Neil E. Pearson']
|
10
|
+
spec.email = ['neil.pearson@orionvm.com']
|
11
|
+
|
12
|
+
spec.summary = 'Jimmy the JSON Schema DSL'
|
13
|
+
spec.description = 'Jimmy makes it a snap to compose detailed JSON schema documents.'
|
14
|
+
spec.homepage = 'https://github.com/orionvm/jimmy'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = 'bin'
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
spec.license = 'Apache License, Version 2.0'
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.9'
|
23
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
24
|
+
spec.add_development_dependency 'rspec', '~> 3.2'
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative 'schema_types'
|
2
|
+
|
3
|
+
module Jimmy
|
4
|
+
class Combination < Array
|
5
|
+
|
6
|
+
attr_reader :condition, :domain
|
7
|
+
|
8
|
+
# @param [Symbol] condition One of :one, :all, or :any
|
9
|
+
def initialize(condition, domain)
|
10
|
+
@condition = condition
|
11
|
+
@domain = domain
|
12
|
+
end
|
13
|
+
|
14
|
+
def evaluate(types_proc)
|
15
|
+
instance_exec &types_proc
|
16
|
+
end
|
17
|
+
|
18
|
+
def serialize
|
19
|
+
{"#{condition}Of" => map(&:serialize)}
|
20
|
+
end
|
21
|
+
|
22
|
+
SchemaCreation.apply_to(self) { |schema| push schema }
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/lib/jimmy/domain.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
require_relative 'schema_creation'
|
5
|
+
|
6
|
+
module Jimmy
|
7
|
+
class Domain
|
8
|
+
|
9
|
+
attr_reader :root, :types
|
10
|
+
|
11
|
+
def initialize(root)
|
12
|
+
@root = URI(root)
|
13
|
+
@schemas = {}
|
14
|
+
@types = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def domain
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def import_path(path)
|
22
|
+
path = Pathname(path) unless path.is_a? Pathname
|
23
|
+
@types = import_schemas(path + 'types', path).map { |k, v| [k.to_sym, v] }.to_h
|
24
|
+
@schemas = import_schemas(path, path, 'types/')
|
25
|
+
end
|
26
|
+
|
27
|
+
def [](schema_name)
|
28
|
+
@schemas[schema_name.to_s]
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def import_schemas(path, base_path, reject_prefix = nil)
|
34
|
+
result = {}
|
35
|
+
Dir[path + '**/*.rb'].each do |full_path|
|
36
|
+
full_path = Pathname(full_path)
|
37
|
+
relative_path = full_path.relative_path_from(path)
|
38
|
+
next if reject_prefix && relative_path.to_s.start_with?(reject_prefix)
|
39
|
+
base_name = relative_path.to_s[0..-4]
|
40
|
+
schema = instance_eval(full_path.read, full_path.to_s).schema
|
41
|
+
schema.name = full_path.relative_path_from(base_path).to_s[0..-4]
|
42
|
+
result[base_name] = schema
|
43
|
+
end
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
SchemaCreation.apply_to self
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
data/lib/jimmy/schema.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module Jimmy
|
2
|
+
class Schema
|
3
|
+
|
4
|
+
attr_reader :dsl, :attrs, :domain, :type
|
5
|
+
attr_accessor :name
|
6
|
+
|
7
|
+
@argument_handlers = Hash.new { |hash, key| hash[key] = {} }
|
8
|
+
|
9
|
+
def self.create(*args, &block)
|
10
|
+
new(*args).tap do |schema|
|
11
|
+
schema.dsl.evaluate block if block
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.set_argument_handler(schema_class, arg_class, handler)
|
16
|
+
@argument_handlers[schema_class][arg_class] = handler
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.argument_hander(schema_class, argument)
|
20
|
+
handlers = {}
|
21
|
+
until schema_class == SchemaType do
|
22
|
+
handlers = (@argument_handlers[schema_class] || {}).merge(handlers)
|
23
|
+
schema_class = schema_class.superclass
|
24
|
+
end
|
25
|
+
result = handlers.find { |k, _| argument.is_a? k }
|
26
|
+
result && result.last
|
27
|
+
end
|
28
|
+
|
29
|
+
def serialize
|
30
|
+
serializer = nil
|
31
|
+
schema_class = SchemaTypes[type]
|
32
|
+
until schema_class == SchemaType do
|
33
|
+
serializer ||= SchemaTypes.serializers[schema_class]
|
34
|
+
schema_class = schema_class.superclass
|
35
|
+
end
|
36
|
+
{'type' => type.to_s}.tap do |hash|
|
37
|
+
dsl.evaluate serializer, hash if serializer
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_h
|
42
|
+
{}.tap do |hash|
|
43
|
+
hash['$schema'] = "#{domain.root}/#{name}#" if name
|
44
|
+
hash.merge! serialize
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def initialize(type, domain, *args)
|
51
|
+
@attrs = {}
|
52
|
+
@type = type
|
53
|
+
@domain = domain
|
54
|
+
@dsl = SchemaTypes.dsls[type].new(self)
|
55
|
+
args.each do |arg|
|
56
|
+
case arg
|
57
|
+
when Symbol
|
58
|
+
dsl.__send__ arg
|
59
|
+
when Hash
|
60
|
+
arg.each { |k, v| dsl.__send__ k, v }
|
61
|
+
else
|
62
|
+
handler = Schema.argument_hander(SchemaTypes[type], arg)
|
63
|
+
raise "`#{type}` cannot handle arguments of type #{arg.class.name}" unless handler
|
64
|
+
dsl.evaluate handler, arg
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Jimmy
|
2
|
+
class SchemaCreation
|
3
|
+
|
4
|
+
@handlers = {}
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
attr_reader :handlers
|
9
|
+
|
10
|
+
def apply_to(klass, &handler)
|
11
|
+
@handlers[klass] = handler
|
12
|
+
%i(one all any).each do |condition|
|
13
|
+
klass.__send__ :define_method, :"#{condition}_of" do |*args, &inner_block|
|
14
|
+
Combination.new(condition, domain).tap do |combo|
|
15
|
+
combo.evaluate inner_block
|
16
|
+
instance_exec combo, *args, &handler
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
klass.include ResolveMissingToCustomType
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module ResolveMissingToCustomType
|
25
|
+
|
26
|
+
def respond_to_missing?(method, *)
|
27
|
+
SchemaTypes.key?(method) || domain.types.key?(method) || super
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_missing(method, *args, &block)
|
31
|
+
if SchemaTypes.key? method
|
32
|
+
handler = SchemaCreation.handlers[self.class]
|
33
|
+
self.class.__send__ :define_method, method do |*inner_args, &inner_block|
|
34
|
+
handler_args = handler && inner_args.shift(handler.arity - 1)
|
35
|
+
schema = Schema.create(method, domain, *inner_args, &inner_block)
|
36
|
+
instance_exec schema, *handler_args, &handler if handler
|
37
|
+
schema.dsl
|
38
|
+
end
|
39
|
+
return __send__ method, *args, &block
|
40
|
+
end
|
41
|
+
|
42
|
+
if domain.types.key? method
|
43
|
+
return instance_exec method, *args, &SchemaCreation.handlers[self.class]
|
44
|
+
end
|
45
|
+
|
46
|
+
super method, *args, &block
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
require_relative 'schema'
|
4
|
+
|
5
|
+
module Jimmy
|
6
|
+
class SchemaType
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def register!
|
11
|
+
SchemaTypes.register self
|
12
|
+
end
|
13
|
+
|
14
|
+
def trait(name_or_type, &handler)
|
15
|
+
case name_or_type
|
16
|
+
when Symbol
|
17
|
+
handler ||= proc { |value| attrs[name_or_type] = value }
|
18
|
+
self::DSL.__send__ :define_method, name_or_type, handler
|
19
|
+
when Class
|
20
|
+
Schema.set_argument_handler self, name_or_type, handler
|
21
|
+
else
|
22
|
+
raise 'Trait must be a Symbol or a Class'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def nested(&handler)
|
27
|
+
SchemaTypes.nested_handlers[self] = handler
|
28
|
+
end
|
29
|
+
|
30
|
+
def serialize(&handler)
|
31
|
+
SchemaTypes.serializers[self] = handler
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
class DSL
|
37
|
+
extend Forwardable
|
38
|
+
|
39
|
+
attr_reader :schema
|
40
|
+
|
41
|
+
delegate %i(attrs domain) => :schema
|
42
|
+
|
43
|
+
def initialize(schema)
|
44
|
+
@schema = schema
|
45
|
+
end
|
46
|
+
|
47
|
+
def evaluate(proc, *args)
|
48
|
+
instance_exec *args, &proc
|
49
|
+
end
|
50
|
+
|
51
|
+
def camelize_attrs(*args)
|
52
|
+
included_args = args.flatten.reject { |arg| attrs[arg].nil? }
|
53
|
+
included_args.map { |arg| [arg.to_s.gsub(/_([a-z])/) { $1.upcase }, attrs[arg]] }.to_h
|
54
|
+
end
|
55
|
+
|
56
|
+
def serialize_schema(schema)
|
57
|
+
schema.is_a?(Symbol) ? {'$ref' => "/types/#{schema}#"} : schema.serialize
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Jimmy
|
2
|
+
class SchemaTypes::Array < SchemaType
|
3
|
+
register!
|
4
|
+
|
5
|
+
trait :min_items
|
6
|
+
trait :max_items
|
7
|
+
trait Range do |range|
|
8
|
+
min, max = [range.first, range.last].sort
|
9
|
+
min_items min
|
10
|
+
max_items max
|
11
|
+
end
|
12
|
+
trait(Fixnum) { |value| min_items value; max_items value }
|
13
|
+
|
14
|
+
nested do |schema|
|
15
|
+
(attrs[:items] ||= []) << schema
|
16
|
+
end
|
17
|
+
|
18
|
+
serialize do |hash|
|
19
|
+
hash.merge! camelize_attrs(%i[min_items max_items])
|
20
|
+
items = attrs[:items] || []
|
21
|
+
if items.length > 1
|
22
|
+
hash['items'] = {'anyOf' => items.map { |i| serialize_schema i }}
|
23
|
+
elsif items.length == 1
|
24
|
+
hash['items'] = serialize_schema(items.first)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Jimmy
|
2
|
+
class SchemaTypes::Number < SchemaType
|
3
|
+
register!
|
4
|
+
|
5
|
+
trait :multiple_of
|
6
|
+
trait :minimum
|
7
|
+
trait :maximum
|
8
|
+
trait(:<) { |value| maximum value; attrs[:exclusive_maximum] = true; self }
|
9
|
+
trait(:<=) { |value| maximum value; attrs[:exclusive_maximum] = nil; self }
|
10
|
+
trait(:>) { |value| minimum value; attrs[:exclusive_minimum] = true; self }
|
11
|
+
trait(:>=) { |value| minimum value; attrs[:exclusive_minimum] = nil; self }
|
12
|
+
trait(Numeric) { |value| minimum value; maximum value }
|
13
|
+
trait(Range) do |range|
|
14
|
+
if range.first <= range.last
|
15
|
+
minimum range.first
|
16
|
+
maximum range.last
|
17
|
+
attrs[:exclusive_maximum] ||= range.exclude_end? || nil
|
18
|
+
else
|
19
|
+
minimum range.last
|
20
|
+
maximum range.first
|
21
|
+
attrs[:exclusive_minimum] ||= range.exclude_end? || nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
serialize do |hash|
|
26
|
+
hash.merge! camelize_attrs(%i[minimum maximum exclusive_minimum exclusive_maximum multiple_of])
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Jimmy
|
2
|
+
class SchemaTypes::Object < SchemaType
|
3
|
+
register!
|
4
|
+
|
5
|
+
trait :require do |*required_keys|
|
6
|
+
if required_keys == [0]
|
7
|
+
attrs[:required] = SymbolArray.new
|
8
|
+
else
|
9
|
+
attrs[:required] ||= SymbolArray.new
|
10
|
+
attrs[:required] |= required_keys.flatten.map(&:to_s).uniq
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
trait :all do
|
15
|
+
SymbolArray.new(attrs[:properties].keys.select { |x| x.is_a? Symbol })
|
16
|
+
end
|
17
|
+
|
18
|
+
trait(:none) { 0 }
|
19
|
+
|
20
|
+
trait(:allow_additional) { attrs[:additional_properties] = true }
|
21
|
+
|
22
|
+
nested do |schema, property_name|
|
23
|
+
(attrs[:properties] ||= {})[property_name] = schema
|
24
|
+
end
|
25
|
+
|
26
|
+
serialize do |hash|
|
27
|
+
(attrs[:properties] || {}).each do |key, value|
|
28
|
+
collection, key =
|
29
|
+
if key.is_a? Regexp
|
30
|
+
['patternProperties', key.inspect.gsub(%r`^/|/[a-z]*$`, '')]
|
31
|
+
else
|
32
|
+
['properties', key.to_s]
|
33
|
+
end
|
34
|
+
hash[collection] ||= {}
|
35
|
+
hash[collection][key] = serialize_schema(value)
|
36
|
+
end
|
37
|
+
hash['required'] = (attrs[:required] || []).to_a
|
38
|
+
hash['additionalProperties'] = !!attrs[:additional_properties]
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Jimmy
|
2
|
+
class SchemaTypes::String < SchemaType
|
3
|
+
register!
|
4
|
+
|
5
|
+
trait :min_length
|
6
|
+
trait :max_length
|
7
|
+
trait(:pattern) { |regex| attrs[:pattern] = regex.is_a?(Regexp) ? regex.inspect.gsub(%r`^/|/[a-z]*$`, '') : regex }
|
8
|
+
trait(Regexp) { |regex| pattern regex }
|
9
|
+
trait(Range) { |value| attrs[:min_length], attrs[:max_length] = [value.first, value.last].sort }
|
10
|
+
trait(Fixnum) { |value| min_length value; max_length value }
|
11
|
+
trait(Array) { |value| attrs[:enum] = value.map(&:to_s) }
|
12
|
+
|
13
|
+
serialize do |hash|
|
14
|
+
hash.merge! camelize_attrs(%i[min_length max_length pattern enum])
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
require_relative 'schema_type'
|
4
|
+
require_relative 'schema_creation'
|
5
|
+
|
6
|
+
module Jimmy
|
7
|
+
module SchemaTypes
|
8
|
+
|
9
|
+
@types = {}
|
10
|
+
@dsls = {}
|
11
|
+
@nested_handlers = {}
|
12
|
+
@serializers = {}
|
13
|
+
|
14
|
+
class << self
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
delegate %i[each keys values key?] => :@types
|
18
|
+
|
19
|
+
attr_reader :dsls, :nested_handlers, :serializers
|
20
|
+
|
21
|
+
def [](type_name)
|
22
|
+
@types[type_name]
|
23
|
+
end
|
24
|
+
|
25
|
+
def register(type_class)
|
26
|
+
type_name = type_class.name[/\w+$/].downcase.to_sym
|
27
|
+
dsl_class = Class.new(type_class.superclass::DSL)
|
28
|
+
type_class.const_set :DSL, dsl_class
|
29
|
+
@dsls[type_name] = dsl_class
|
30
|
+
@types[type_name] = type_class
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
Dir[ROOT + 'lib/jimmy/schema_types/*.rb'].each do |path|
|
36
|
+
require path
|
37
|
+
end
|
38
|
+
|
39
|
+
nested_handlers.each { |klass, handler| SchemaCreation.apply_to klass::DSL, &handler }
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Jimmy
|
2
|
+
class SymbolArray < Array
|
3
|
+
|
4
|
+
def initialize(*args)
|
5
|
+
super args.flatten.map(&:to_s)
|
6
|
+
end
|
7
|
+
|
8
|
+
%i(<< push unshift).each do |method|
|
9
|
+
define_method(method) { |*args| super *args.map(&:to_s) }
|
10
|
+
end
|
11
|
+
|
12
|
+
%i(+ - | &).each do |method|
|
13
|
+
define_method(method) { |*args| SymbolArray.new(super args.flatten.map(&:to_s)) }
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
data/lib/jimmy.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'jimmy/version'
|
3
|
+
|
4
|
+
module Jimmy
|
5
|
+
ROOT = Pathname(__FILE__).parent.parent
|
6
|
+
end
|
7
|
+
|
8
|
+
require_relative 'jimmy/symbol_array'
|
9
|
+
|
10
|
+
require_relative 'jimmy/domain'
|
11
|
+
require_relative 'jimmy/schema'
|
12
|
+
require_relative 'jimmy/schema_creation'
|
13
|
+
require_relative 'jimmy/schema_types'
|
14
|
+
require_relative 'jimmy/schema_type'
|
15
|
+
require_relative 'jimmy/combination'
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jimmy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Neil E. Pearson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-05-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.9'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.2'
|
55
|
+
description: Jimmy makes it a snap to compose detailed JSON schema documents.
|
56
|
+
email:
|
57
|
+
- neil.pearson@orionvm.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- circle.yml
|
68
|
+
- jimmy.gemspec
|
69
|
+
- lib/jimmy.rb
|
70
|
+
- lib/jimmy/combination.rb
|
71
|
+
- lib/jimmy/domain.rb
|
72
|
+
- lib/jimmy/schema.rb
|
73
|
+
- lib/jimmy/schema_creation.rb
|
74
|
+
- lib/jimmy/schema_type.rb
|
75
|
+
- lib/jimmy/schema_types.rb
|
76
|
+
- lib/jimmy/schema_types/array.rb
|
77
|
+
- lib/jimmy/schema_types/boolean.rb
|
78
|
+
- lib/jimmy/schema_types/integer.rb
|
79
|
+
- lib/jimmy/schema_types/null.rb
|
80
|
+
- lib/jimmy/schema_types/number.rb
|
81
|
+
- lib/jimmy/schema_types/object.rb
|
82
|
+
- lib/jimmy/schema_types/string.rb
|
83
|
+
- lib/jimmy/symbol_array.rb
|
84
|
+
- lib/jimmy/version.rb
|
85
|
+
homepage: https://github.com/orionvm/jimmy
|
86
|
+
licenses:
|
87
|
+
- Apache License, Version 2.0
|
88
|
+
metadata: {}
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 2.4.6
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: Jimmy the JSON Schema DSL
|
109
|
+
test_files: []
|