jimmy 0.1.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 +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: []
|