jimmy 0.5.5
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/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/LICENSE +13 -0
- data/README.md +151 -0
- data/Rakefile +4 -0
- data/circle.yml +11 -0
- data/jimmy.gemspec +28 -0
- data/lib/jimmy.rb +20 -0
- data/lib/jimmy/combination.rb +34 -0
- data/lib/jimmy/definitions.rb +38 -0
- data/lib/jimmy/domain.rb +111 -0
- data/lib/jimmy/link.rb +93 -0
- data/lib/jimmy/reference.rb +39 -0
- data/lib/jimmy/schema.rb +114 -0
- data/lib/jimmy/schema_creation.rb +121 -0
- data/lib/jimmy/schema_type.rb +100 -0
- data/lib/jimmy/schema_types.rb +42 -0
- data/lib/jimmy/schema_types/array.rb +30 -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 +34 -0
- data/lib/jimmy/schema_types/object.rb +45 -0
- data/lib/jimmy/schema_types/string.rb +40 -0
- data/lib/jimmy/symbol_array.rb +17 -0
- data/lib/jimmy/transform_keys.rb +39 -0
- data/lib/jimmy/type_reference.rb +14 -0
- data/lib/jimmy/validation_error.rb +20 -0
- data/lib/jimmy/version.rb +3 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 189b5833da044ec29e5cf06d708960564a08a47f
|
4
|
+
data.tar.gz: 30c672d61e39c2e84e131404b6efdf5c4bdcecad
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1e225b091d344c3c72fd660e3672821c49c2be5403a8b43db54f2f22b2b0cf3fe2ce4867c3371d6462fed1ca7395c274fed29ef7f634ebb012ba9e3ebe161204
|
7
|
+
data.tar.gz: 3ee1e87f5329b4296ceaa9b664f8aa13c10ffcf7f72ffdbd1b486829e77b603a8a2e467552057e0801ccd458d566c375b914e82de62d480503fdb56c9ed3fca2
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.5
|
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,151 @@
|
|
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, 3, 5, 7]
|
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
|
+
"enum": [1, 3, 5, 7]
|
127
|
+
},
|
128
|
+
"location": {
|
129
|
+
"$ref": "/types/geopoint#"
|
130
|
+
},
|
131
|
+
"featured": {
|
132
|
+
"type": "boolean"
|
133
|
+
}
|
134
|
+
},
|
135
|
+
"required": [
|
136
|
+
"title"
|
137
|
+
],
|
138
|
+
"additionalProperties": false
|
139
|
+
}
|
140
|
+
}
|
141
|
+
},
|
142
|
+
"required": [
|
143
|
+
"name",
|
144
|
+
"postcode",
|
145
|
+
"population",
|
146
|
+
"country",
|
147
|
+
"points_of_interest"
|
148
|
+
],
|
149
|
+
"additionalProperties": false
|
150
|
+
}
|
151
|
+
```
|
data/Rakefile
ADDED
data/circle.yml
ADDED
data/jimmy.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
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/hx/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_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
|
+
end
|
data/lib/jimmy.rb
ADDED
@@ -0,0 +1,20 @@
|
|
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/reference'
|
13
|
+
require_relative 'jimmy/type_reference'
|
14
|
+
require_relative 'jimmy/schema_creation'
|
15
|
+
require_relative 'jimmy/schema_types'
|
16
|
+
require_relative 'jimmy/schema_type'
|
17
|
+
require_relative 'jimmy/combination'
|
18
|
+
require_relative 'jimmy/link'
|
19
|
+
require_relative 'jimmy/validation_error'
|
20
|
+
require_relative 'jimmy/definitions'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'schema_types'
|
2
|
+
|
3
|
+
module Jimmy
|
4
|
+
class Combination < Array
|
5
|
+
include SchemaCreation::Referencing
|
6
|
+
|
7
|
+
attr_reader :condition, :schema
|
8
|
+
|
9
|
+
# @param [Symbol] condition One of :one, :all, or :any
|
10
|
+
def initialize(condition, schema)
|
11
|
+
@condition = condition
|
12
|
+
@schema = schema
|
13
|
+
end
|
14
|
+
|
15
|
+
def domain
|
16
|
+
schema.domain
|
17
|
+
end
|
18
|
+
|
19
|
+
def evaluate(types_proc)
|
20
|
+
instance_exec &types_proc
|
21
|
+
end
|
22
|
+
|
23
|
+
def compile
|
24
|
+
data.merge "#{condition}Of" => map(&:compile)
|
25
|
+
end
|
26
|
+
|
27
|
+
def data
|
28
|
+
@data ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
SchemaCreation.apply_to(self) { |schema| push schema }
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Jimmy
|
4
|
+
class Definitions
|
5
|
+
include SchemaCreation::Referencing
|
6
|
+
extend Forwardable
|
7
|
+
delegate %i[empty? key? map] => :@values
|
8
|
+
|
9
|
+
attr_reader :schema
|
10
|
+
|
11
|
+
def initialize(schema)
|
12
|
+
@schema = schema
|
13
|
+
@values = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def evaluate(&block)
|
17
|
+
instance_exec &block
|
18
|
+
end
|
19
|
+
|
20
|
+
def domain
|
21
|
+
schema.domain
|
22
|
+
end
|
23
|
+
|
24
|
+
def compile
|
25
|
+
map { |k, v| [k.to_s, v.compile] }.to_h
|
26
|
+
end
|
27
|
+
|
28
|
+
def data
|
29
|
+
schema.data
|
30
|
+
end
|
31
|
+
|
32
|
+
def [](key)
|
33
|
+
@values[key] || (schema.parent && schema.parent.definitions[key])
|
34
|
+
end
|
35
|
+
|
36
|
+
SchemaCreation.apply_to(self) { |schema, name| @values[name] = schema }
|
37
|
+
end
|
38
|
+
end
|
data/lib/jimmy/domain.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'pathname'
|
3
|
+
require 'json'
|
4
|
+
require 'json-schema'
|
5
|
+
|
6
|
+
require_relative 'schema_creation'
|
7
|
+
|
8
|
+
module Jimmy
|
9
|
+
class Domain
|
10
|
+
DEFAULT_OPTIONS = {
|
11
|
+
transform_keys: nil
|
12
|
+
}
|
13
|
+
|
14
|
+
attr_reader :root, :types, :partials, :options, :schemas
|
15
|
+
|
16
|
+
def initialize(root, **options)
|
17
|
+
@root = URI(root)
|
18
|
+
@schemas = {}
|
19
|
+
@types = {}
|
20
|
+
@partials = {}
|
21
|
+
@import_paths = []
|
22
|
+
@uri_formatter = -> _, name { @root + "#{name}.json#" }
|
23
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def domain
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def import(path)
|
31
|
+
path = Pathname(path) unless path.is_a? Pathname
|
32
|
+
@import_paths << path unless @import_paths.include? path
|
33
|
+
|
34
|
+
glob path, only: 'types' do |name, schema|
|
35
|
+
@types[name.to_sym] = schema
|
36
|
+
end
|
37
|
+
|
38
|
+
glob path, only: 'partials' do |name|
|
39
|
+
partial_path = path + 'partials' + "#{name}.rb"
|
40
|
+
@partials[name] = [partial_path.read, partial_path.to_s]
|
41
|
+
end
|
42
|
+
|
43
|
+
glob path, ignore: %r`^(types|partials)/` do |name, schema|
|
44
|
+
@schemas[name] = schema
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def autoload_type(name)
|
49
|
+
# TODO: protect from circular dependency
|
50
|
+
return if types.key? name
|
51
|
+
@import_paths.each do |import_path|
|
52
|
+
path = import_path + "types/#{name}.rb"
|
53
|
+
if path.file?
|
54
|
+
@types[name] = load_schema_from_path(path, name)
|
55
|
+
return true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
def [](schema_name)
|
62
|
+
@schemas[schema_name.to_s]
|
63
|
+
end
|
64
|
+
|
65
|
+
def export(path = nil, &serializer)
|
66
|
+
path = Pathname(path) if path.is_a? String
|
67
|
+
raise 'Please specify an export directory' unless path.is_a?(Pathname) && (path.directory? || !path.exist?)
|
68
|
+
path.mkpath
|
69
|
+
@schemas.each { |name, schema| export_schema schema, path + "#{name.to_s}.json", &serializer }
|
70
|
+
@types.each { |name, schema| export_schema schema, path + 'types' + "#{name.to_s}.json", &serializer }
|
71
|
+
end
|
72
|
+
|
73
|
+
def uri_for(name)
|
74
|
+
@uri_formatter.call root, name
|
75
|
+
end
|
76
|
+
|
77
|
+
def uri_format(&block)
|
78
|
+
@uri_formatter = block
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def glob(base_path, only: '.', ignore: nil, &block)
|
84
|
+
lookup_path = base_path + only
|
85
|
+
Dir[lookup_path + '**/*.rb'].each do |full_path|
|
86
|
+
full_path = Pathname(full_path)
|
87
|
+
relative_path = full_path.relative_path_from(lookup_path)
|
88
|
+
next if ignore === relative_path.to_s
|
89
|
+
args = [relative_path.to_s[0..-4]]
|
90
|
+
args << load_schema_from_path(full_path, full_path.relative_path_from(base_path).to_s[0..-4]) if block.arity == 2
|
91
|
+
yield *args
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def load_schema_from_path(path, name)
|
96
|
+
@schema_name = name
|
97
|
+
instance_eval(path.read, path.to_s).schema.tap do |schema|
|
98
|
+
schema.name = name.to_s
|
99
|
+
JSON::Validator.add_schema JSON::Schema.new(schema.to_h, nil)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def export_schema(schema, target_path)
|
104
|
+
target_path.parent.mkpath
|
105
|
+
target_path.write block_given? ? yield(schema.to_h) : JSON.pretty_generate(schema.to_h)
|
106
|
+
end
|
107
|
+
|
108
|
+
SchemaCreation.apply_to self
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|