json_schema_rails 0.0.1
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/.editorconfig +16 -0
- data/.gitignore +11 -0
- data/.rspec +1 -0
- data/Gemfile +14 -0
- data/Guardfile +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +121 -0
- data/Rakefile +17 -0
- data/json_schema_rails.gemspec +29 -0
- data/lib/generators/schema/action/USAGE +22 -0
- data/lib/generators/schema/action/action_generator.rb +52 -0
- data/lib/generators/schema/action/templates/schema.yml.erb +31 -0
- data/lib/generators/schema/controller/USAGE +13 -0
- data/lib/generators/schema/controller/controller_generator.rb +27 -0
- data/lib/generators/schema/controller/templates/schema.yml.erb +7 -0
- data/lib/json_schema_rails/errors.rb +44 -0
- data/lib/json_schema_rails/helpers.rb +38 -0
- data/lib/json_schema_rails/loaders/base.rb +72 -0
- data/lib/json_schema_rails/loaders/directory.rb +39 -0
- data/lib/json_schema_rails/loaders/hyper_schema.rb +68 -0
- data/lib/json_schema_rails/loaders.rb +7 -0
- data/lib/json_schema_rails/railtie.rb +25 -0
- data/lib/json_schema_rails/schema_validator.rb +91 -0
- data/lib/json_schema_rails/version.rb +3 -0
- data/lib/json_schema_rails.rb +43 -0
- data/lib/tasks/schema_tasks.rake +37 -0
- data/schemas/core-schema.json +150 -0
- data/schemas/hyper-schema.json +168 -0
- data/schemas/strict/core-schema.json +151 -0
- data/schemas/strict/hyper-schema.json +169 -0
- data/spec/dummy_apps/rails3/README.rdoc +261 -0
- data/spec/dummy_apps/rails3/Rakefile +7 -0
- data/spec/dummy_apps/rails3/app/assets/images/rails.png +0 -0
- data/spec/dummy_apps/rails3/app/assets/javascripts/application.js +13 -0
- data/spec/dummy_apps/rails3/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy_apps/rails3/app/controllers/application_controller.rb +3 -0
- data/spec/dummy_apps/rails3/app/controllers/posts_controller.rb +11 -0
- data/spec/dummy_apps/rails3/app/helpers/application_helper.rb +2 -0
- data/spec/dummy_apps/rails3/app/schemas/posts/create.yml +20 -0
- data/spec/dummy_apps/rails3/app/schemas/posts/update.json +29 -0
- data/spec/dummy_apps/rails3/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy_apps/rails3/config/application.rb +68 -0
- data/spec/dummy_apps/rails3/config/boot.rb +6 -0
- data/spec/dummy_apps/rails3/config/database.yml +25 -0
- data/spec/dummy_apps/rails3/config/environment.rb +5 -0
- data/spec/dummy_apps/rails3/config/environments/development.rb +37 -0
- data/spec/dummy_apps/rails3/config/environments/production.rb +67 -0
- data/spec/dummy_apps/rails3/config/environments/test.rb +37 -0
- data/spec/dummy_apps/rails3/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy_apps/rails3/config/initializers/inflections.rb +15 -0
- data/spec/dummy_apps/rails3/config/initializers/mime_types.rb +5 -0
- data/spec/dummy_apps/rails3/config/initializers/secret_token.rb +7 -0
- data/spec/dummy_apps/rails3/config/initializers/session_store.rb +8 -0
- data/spec/dummy_apps/rails3/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy_apps/rails3/config/locales/en.yml +5 -0
- data/spec/dummy_apps/rails3/config/routes.rb +60 -0
- data/spec/dummy_apps/rails3/config.ru +4 -0
- data/spec/dummy_apps/rails3/db/seeds.rb +7 -0
- data/spec/dummy_apps/rails3/doc/README_FOR_APP +2 -0
- data/spec/dummy_apps/rails3/public/404.html +26 -0
- data/spec/dummy_apps/rails3/public/422.html +26 -0
- data/spec/dummy_apps/rails3/public/500.html +25 -0
- data/spec/dummy_apps/rails3/public/favicon.ico +0 -0
- data/spec/dummy_apps/rails3/public/index.html +241 -0
- data/spec/dummy_apps/rails3/public/robots.txt +5 -0
- data/spec/dummy_apps/rails3/script/rails +6 -0
- data/spec/dummy_apps/rails4/README.rdoc +28 -0
- data/spec/dummy_apps/rails4/Rakefile +6 -0
- data/spec/dummy_apps/rails4/app/assets/images/.keep +0 -0
- data/spec/dummy_apps/rails4/app/assets/javascripts/application.js +13 -0
- data/spec/dummy_apps/rails4/app/assets/javascripts/posts.js +2 -0
- data/spec/dummy_apps/rails4/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy_apps/rails4/app/assets/stylesheets/posts.css +4 -0
- data/spec/dummy_apps/rails4/app/controllers/application_controller.rb +5 -0
- data/spec/dummy_apps/rails4/app/controllers/concerns/.keep +0 -0
- data/spec/dummy_apps/rails4/app/controllers/posts_controller.rb +11 -0
- data/spec/dummy_apps/rails4/app/helpers/application_helper.rb +2 -0
- data/spec/dummy_apps/rails4/app/mailers/.keep +0 -0
- data/spec/dummy_apps/rails4/app/models/.keep +0 -0
- data/spec/dummy_apps/rails4/app/models/concerns/.keep +0 -0
- data/spec/dummy_apps/rails4/app/schemas/posts/create.yml +20 -0
- data/spec/dummy_apps/rails4/app/schemas/posts/update.json +29 -0
- data/spec/dummy_apps/rails4/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy_apps/rails4/bin/bundle +3 -0
- data/spec/dummy_apps/rails4/bin/rails +4 -0
- data/spec/dummy_apps/rails4/bin/rake +4 -0
- data/spec/dummy_apps/rails4/config/application.rb +29 -0
- data/spec/dummy_apps/rails4/config/boot.rb +5 -0
- data/spec/dummy_apps/rails4/config/environment.rb +5 -0
- data/spec/dummy_apps/rails4/config/environments/development.rb +34 -0
- data/spec/dummy_apps/rails4/config/environments/production.rb +80 -0
- data/spec/dummy_apps/rails4/config/environments/test.rb +39 -0
- data/spec/dummy_apps/rails4/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy_apps/rails4/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy_apps/rails4/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy_apps/rails4/config/initializers/inflections.rb +16 -0
- data/spec/dummy_apps/rails4/config/initializers/mime_types.rb +4 -0
- data/spec/dummy_apps/rails4/config/initializers/session_store.rb +3 -0
- data/spec/dummy_apps/rails4/config/initializers/wrap_parameters.rb +9 -0
- data/spec/dummy_apps/rails4/config/locales/en.yml +23 -0
- data/spec/dummy_apps/rails4/config/routes.rb +58 -0
- data/spec/dummy_apps/rails4/config/secrets.yml +22 -0
- data/spec/dummy_apps/rails4/config.ru +4 -0
- data/spec/dummy_apps/rails4/lib/assets/.keep +0 -0
- data/spec/dummy_apps/rails4/log/.keep +0 -0
- data/spec/dummy_apps/rails4/public/404.html +67 -0
- data/spec/dummy_apps/rails4/public/422.html +67 -0
- data/spec/dummy_apps/rails4/public/500.html +66 -0
- data/spec/dummy_apps/rails4/public/favicon.ico +0 -0
- data/spec/fixtures/hyper_schema.yml +42 -0
- data/spec/fixtures/schemas/invalid_schema.yml +15 -0
- data/spec/fixtures/schemas/posts/create.yml +20 -0
- data/spec/fixtures/schemas/posts/search.json +13 -0
- data/spec/fixtures/schemas/posts/update.yaml +20 -0
- data/spec/fixtures/schemas/syntax_error.json +5 -0
- data/spec/lib/json_schema_rails/loaders/directory_spec.rb +59 -0
- data/spec/lib/json_schema_rails/loaders/hyper_schema_spec.rb +40 -0
- data/spec/lib/json_schema_rails/schema_validator_spec.rb +57 -0
- data/spec/lib/json_schema_rails_spec.rb +123 -0
- data/spec/railtie/controllers/posts_controller_spec.rb +43 -0
- data/spec/spec_helper.rb +28 -0
- metadata +406 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9995f6fcf61ca7baf31825818a209be42acc8e9e
|
4
|
+
data.tar.gz: 5a00820d204b5034efbd8fbbf55ae08f87cbd70f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5c75a0b8c7285302b9b567b9a687ec73bcef6d9679a06ff2995d93a8fc3455ce1c6f2ea0b43d7d15a7069f78515c4bfdfefe7f9d50cbee59a5a0a0ff53457b06
|
7
|
+
data.tar.gz: 77289c86271e435e5cec7fecdb31a9342e2da82f852362190a2134a8f9407359604f6810fc5f407ee178593301559a2615b3af4f3245a0365fc463528a4afa5a
|
data/.editorconfig
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# EditorConfig file
|
2
|
+
# http://editorconfig.org/
|
3
|
+
|
4
|
+
root = true
|
5
|
+
|
6
|
+
[*]
|
7
|
+
end_of_line = lf
|
8
|
+
indent_style = space
|
9
|
+
indent_size = 2
|
10
|
+
charset = utf-8
|
11
|
+
trim_trailing_whitespace = true
|
12
|
+
insert_final_newline = true
|
13
|
+
|
14
|
+
[*.md]
|
15
|
+
indent_size = 4
|
16
|
+
trim_trailing_whitespace = false
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
# Declare your gem's dependencies in json_schema_rails.gemspec.
|
4
|
+
# Bundler will treat runtime dependencies like base dependencies, and
|
5
|
+
# development dependencies will be added by default to the :development group.
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
# Declare any dependencies that are still in development here instead of in
|
9
|
+
# your gemspec. These might include edge Rails or gems from your path or
|
10
|
+
# Git. Remember to move these dependencies to your gemspec before releasing
|
11
|
+
# your gem to rubygems.org.
|
12
|
+
|
13
|
+
# To use debugger
|
14
|
+
# gem 'debugger'
|
data/Guardfile
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
2
|
+
watch(%r{^spec/.+_spec\.rb$})
|
3
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
4
|
+
watch('spec/spec_helper.rb') { "spec" }
|
5
|
+
|
6
|
+
watch(%r{^spec/dummy/app/controllers/(.+_controller)\.rb$}) { |m| "spec/dummy/spec/controllers/#{m[1]}_spec.rb" }
|
7
|
+
end
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2014 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
# json_schema_rails
|
2
|
+
|
3
|
+
[JSON Schema v4](http://json-schema.org/) validator and generator for Rails 3+
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'json_schema_rails'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install json_schema_rails
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
### Generate schema file
|
22
|
+
|
23
|
+
You can use generators to generate schema files for your controller.
|
24
|
+
|
25
|
+
$ rails g schema:controller posts create update
|
26
|
+
|
27
|
+
It creates schema files at:
|
28
|
+
|
29
|
+
* app/schemas/posts/create.yml
|
30
|
+
* app/schemas/posts/update.yml
|
31
|
+
|
32
|
+
You can also generate a schema for specific action with some parameters.
|
33
|
+
|
34
|
+
$ rails g schema:action posts::create title body:required published:boolean:required
|
35
|
+
|
36
|
+
It creates a schema file at:
|
37
|
+
|
38
|
+
* app/schemas/posts/create.yml
|
39
|
+
|
40
|
+
And it has title, body and published as parameters.
|
41
|
+
|
42
|
+
### Validate request parameters
|
43
|
+
|
44
|
+
You can validate request parameters with helper method `validate_schema` (which is before_filter):
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
class PostsController < ApplicationController
|
48
|
+
# Validate requests for all actions in this controller
|
49
|
+
validate_schema
|
50
|
+
|
51
|
+
def create
|
52
|
+
end
|
53
|
+
|
54
|
+
def update
|
55
|
+
end
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
You can use options for `before_filter` if you want stop validation for specific actions:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
validate_schema excpet: :get
|
63
|
+
validate_schema only: [:create, :update]
|
64
|
+
```
|
65
|
+
|
66
|
+
If you want to specify a schema file to use, you can use `validate_schema` or `validate_schema!` in action methods:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
class PostsController < ApplicationController
|
70
|
+
def create
|
71
|
+
validate_schema! "my_schema" # uses "app/schemas/my_schema.{yml,yaml,json}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
`validate_schema!` raises `JsonSchemaRails::ValidationError` when the validation failed.
|
77
|
+
|
78
|
+
`validate_schema` just returns `true` or `false` as the validation result.
|
79
|
+
|
80
|
+
### Error handling
|
81
|
+
|
82
|
+
When validation fails, it raises `JsonSchemaRails::ValidationError` which is handled by `schema_validation_failed` method of controllers.
|
83
|
+
|
84
|
+
The default handler re-raises the error in development, and renders 400 response (Bad Request) in any other env.
|
85
|
+
|
86
|
+
If you want to handle errors by yourself, just define `schema_validation_failed` method to your ApplicationController:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
class ApplicationController < ActionController::Base
|
90
|
+
protect_from_forgery
|
91
|
+
|
92
|
+
def schema_validation_failed(exception)
|
93
|
+
# Do It Yourself
|
94
|
+
end
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
### Validate schema files
|
99
|
+
|
100
|
+
json_schema_rails provides a rake task `schema:check` to validate your schema files.
|
101
|
+
|
102
|
+
$ bundle exec rake schema:check
|
103
|
+
|
104
|
+
By default, it uses the strict schema which does not allow you to define unknown properties so that misspelling is checked.
|
105
|
+
|
106
|
+
If you want to use official schemas which is more loose, pass `LOOSE=1` to rake command.
|
107
|
+
|
108
|
+
$ LOOSE=1 bundle exec rake schema:check
|
109
|
+
|
110
|
+
## TODO
|
111
|
+
|
112
|
+
* Hook generators like controller or resource to generate schema files as well
|
113
|
+
* Write about hyper schema support
|
114
|
+
|
115
|
+
## Contributing
|
116
|
+
|
117
|
+
1. Fork it ( https://github.com/kotas/json_schema_rails/fork )
|
118
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
119
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
120
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
121
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'JsonSchemaRails'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
Bundler::GemHelper.install_tasks
|
@@ -0,0 +1,29 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
# Maintain your gem's version:
|
4
|
+
require "json_schema_rails/version"
|
5
|
+
|
6
|
+
# Describe your gem and declare its dependencies:
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "json_schema_rails"
|
9
|
+
s.version = JsonSchemaRails::VERSION
|
10
|
+
s.authors = ["Kota Saito"]
|
11
|
+
s.email = ["kotas@kotas.jp"]
|
12
|
+
s.homepage = "https://github.com/kotas/json_schema_rails"
|
13
|
+
s.summary = "JSON schema validation for Rails"
|
14
|
+
s.license = "MIT"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split($\)
|
17
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
|
+
|
19
|
+
s.add_runtime_dependency "activesupport", [">= 3.0", "< 4.2"]
|
20
|
+
s.add_runtime_dependency "actionpack", [">= 3.0", "< 4.2"]
|
21
|
+
s.add_runtime_dependency "railties", [">= 3.0", "< 4.2"]
|
22
|
+
s.add_runtime_dependency "json_schema", "~> 0.1.4"
|
23
|
+
|
24
|
+
s.add_development_dependency "rails", [">= 3.0", "< 4.2"]
|
25
|
+
s.add_development_dependency "sqlite3"
|
26
|
+
s.add_development_dependency "rspec"
|
27
|
+
s.add_development_dependency "rspec-rails"
|
28
|
+
s.add_development_dependency "guard-rspec"
|
29
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
Description:
|
2
|
+
Generates a new JSON Schema file for signle action.
|
3
|
+
|
4
|
+
See http://json-schema.org/ for JSON Schema syntax.
|
5
|
+
|
6
|
+
Example:
|
7
|
+
|
8
|
+
rails generate schema:action posts::create title body:required published:boolean:required
|
9
|
+
|
10
|
+
This will create:
|
11
|
+
app/schemas/posts/create.yml
|
12
|
+
(with title, body and published properties)
|
13
|
+
|
14
|
+
Valid types are:
|
15
|
+
- array
|
16
|
+
- boolean
|
17
|
+
- integer
|
18
|
+
- number
|
19
|
+
- null
|
20
|
+
- object
|
21
|
+
- string (default)
|
22
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Schema
|
2
|
+
module Generators
|
3
|
+
class ActionGenerator < Rails::Generators::NamedBase
|
4
|
+
argument :parameters, type: :array, default: [], banner: "param[:type][:required] param[:type][:required]"
|
5
|
+
source_root File.expand_path("../templates", __FILE__)
|
6
|
+
|
7
|
+
def initialize(args, *options)
|
8
|
+
super
|
9
|
+
parse_parameters!
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_schema_file
|
13
|
+
template "schema.yml.erb", File.join("app/schemas", class_path, "#{file_name}.yml")
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def schema_title
|
19
|
+
"#{human_name} #{class_path.map(&:singularize).join(' ').titleize}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse_parameters!
|
23
|
+
self.parameters = parameters.map do |param|
|
24
|
+
name, *options = param.split(':')
|
25
|
+
options = options.map(&:to_sym) if options
|
26
|
+
Parameter.new(name, options)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def required_parameters
|
31
|
+
parameters.select(&:required?)
|
32
|
+
end
|
33
|
+
|
34
|
+
class Parameter
|
35
|
+
attr_reader :name, :type
|
36
|
+
alias_method :to_s, :name
|
37
|
+
|
38
|
+
def initialize(name, options)
|
39
|
+
@name = name
|
40
|
+
|
41
|
+
options ||= []
|
42
|
+
@required = !!options.delete(:required)
|
43
|
+
@type = options.shift || :string
|
44
|
+
end
|
45
|
+
|
46
|
+
def required?
|
47
|
+
@required
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
---
|
2
|
+
"$schema": "http://json-schema.org/draft-04/schema#"
|
3
|
+
title: <%= schema_title %>
|
4
|
+
type: object
|
5
|
+
properties:
|
6
|
+
<%= file_name %>:
|
7
|
+
type: object
|
8
|
+
<%- if parameters.empty? -%>
|
9
|
+
required:
|
10
|
+
# - param1
|
11
|
+
# - param2
|
12
|
+
properties:
|
13
|
+
# param1:
|
14
|
+
# type: string
|
15
|
+
# param2:
|
16
|
+
# type: string
|
17
|
+
<%- else -%>
|
18
|
+
required:
|
19
|
+
<%- parameters.each do |param| -%>
|
20
|
+
<%- if param.required? -%>
|
21
|
+
- <%= param %>
|
22
|
+
<%- else -%>
|
23
|
+
# - <%= param %>
|
24
|
+
<%- end -%>
|
25
|
+
<%- end -%>
|
26
|
+
properties:
|
27
|
+
<%- parameters.each do |param| -%>
|
28
|
+
<%= param %>:
|
29
|
+
type: <%= param.type %>
|
30
|
+
<%- end -%>
|
31
|
+
<%- end -%>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Description:
|
2
|
+
Generates new JSON Schema files for controller.
|
3
|
+
|
4
|
+
See http://json-schema.org/ for JSON Schema syntax.
|
5
|
+
|
6
|
+
Example:
|
7
|
+
|
8
|
+
rails generate schema:controller posts create update
|
9
|
+
|
10
|
+
This will create:
|
11
|
+
app/schemas/posts/create.yml
|
12
|
+
app/schemas/posts/update.yml
|
13
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Schema
|
2
|
+
module Generators
|
3
|
+
class ControllerGenerator < Rails::Generators::NamedBase
|
4
|
+
argument :actions, type: :array, banner: "action action"
|
5
|
+
source_root File.expand_path("../templates", __FILE__)
|
6
|
+
|
7
|
+
def create_schema_files
|
8
|
+
actions.each do |act|
|
9
|
+
self.action_name = act.to_s.underscore
|
10
|
+
template "schema.yml.erb", File.join("app/schemas", class_path, file_name, "#{action_name}.yml")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
attr_accessor :action_name
|
17
|
+
|
18
|
+
def model_name
|
19
|
+
file_name.singularize
|
20
|
+
end
|
21
|
+
|
22
|
+
def schema_title
|
23
|
+
"#{action_name.to_s.humanize} #{model_name.titleize}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module JsonSchemaRails
|
2
|
+
class Error < StandardError; end
|
3
|
+
|
4
|
+
class SchemaNotFound < Error
|
5
|
+
def initialize(message = nil)
|
6
|
+
super(message || 'No such schema')
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class SchemaParseError < Error
|
11
|
+
attr_reader :schema
|
12
|
+
|
13
|
+
def initialize(message, schema = nil)
|
14
|
+
super(message)
|
15
|
+
@schema = schema
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ValidationError < Error
|
20
|
+
attr_reader :schema
|
21
|
+
attr_reader :errors
|
22
|
+
|
23
|
+
def initialize(message, schema = nil, errors = nil)
|
24
|
+
super(message)
|
25
|
+
@schema = schema
|
26
|
+
@errors = errors
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.from_errors(errors, schema = nil, schema_name = nil)
|
30
|
+
message = "Validation error"
|
31
|
+
message << " for schema #{schema_name}" if schema_name
|
32
|
+
|
33
|
+
errors.each do |err|
|
34
|
+
if err.is_a?(JsonSchema::ValidationError)
|
35
|
+
message << "\n- #{err.pointer}: failed schema #{err.schema.pointer}: #{err.message}"
|
36
|
+
else
|
37
|
+
message << "\n- #{err.schema.pointer}: #{err.message}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
new(message, schema, errors)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module JsonSchemaRails
|
2
|
+
module Helpers
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
rescue_from JsonSchemaRails::ValidationError, with: :schema_validation_failed
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def validate_schema(options = {})
|
11
|
+
before_filter :validate_schema!, options
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate_schema(schema_name = nil)
|
16
|
+
validate_schema!(schema_name)
|
17
|
+
rescue JsonSchemaRails::ValidationError
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate_schema!(schema_name = nil)
|
22
|
+
schema_name ||= [
|
23
|
+
"#{controller_path}/#{action_name}",
|
24
|
+
"#{request.method}#{request.path}"
|
25
|
+
]
|
26
|
+
::JsonSchemaRails.validate!(schema_name, params)
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def schema_validation_failed(exception = nil)
|
31
|
+
if exception
|
32
|
+
raise exception if Rails.env.development?
|
33
|
+
logger.debug exception.message
|
34
|
+
end
|
35
|
+
render nothing: true, status: 400
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require "erb"
|
2
|
+
require "json"
|
3
|
+
require "yaml"
|
4
|
+
require "json_schema"
|
5
|
+
|
6
|
+
module JsonSchemaRails
|
7
|
+
module Loaders
|
8
|
+
class Base
|
9
|
+
attr_writer :cache
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@cache = options.fetch(:cache) { true }
|
13
|
+
end
|
14
|
+
|
15
|
+
def cache?
|
16
|
+
!!@cache
|
17
|
+
end
|
18
|
+
|
19
|
+
def clear
|
20
|
+
cache_data.clear
|
21
|
+
end
|
22
|
+
|
23
|
+
def load_schema(schema_path)
|
24
|
+
load_schema!(schema_path)
|
25
|
+
rescue SchemaNotFound
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def load_schema!(schema_path)
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def with_cache(name)
|
36
|
+
if cache?
|
37
|
+
cache_data.fetch(name) { cache_data[name] = yield }
|
38
|
+
else
|
39
|
+
yield
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def cache_data
|
44
|
+
@cache_data ||= {}
|
45
|
+
end
|
46
|
+
|
47
|
+
def load_schema_file(file_path)
|
48
|
+
JsonSchema.parse!(load_schema_content(file_path))
|
49
|
+
rescue JsonSchema::SchemaError => e
|
50
|
+
raise SchemaParseError.new(e.mssage, e.schema)
|
51
|
+
end
|
52
|
+
|
53
|
+
def load_schema_content(file_path)
|
54
|
+
contents = ERB.new(IO.read(file_path)).result
|
55
|
+
case File.extname(file_path)
|
56
|
+
when '.json'
|
57
|
+
JSON.load(contents)
|
58
|
+
when '.yaml', '.yml'
|
59
|
+
YAML.load(contents)
|
60
|
+
else
|
61
|
+
raise ArgumentError, "Unknown schema file type"
|
62
|
+
end
|
63
|
+
rescue Errno::ENOENT
|
64
|
+
raise SchemaNotFound
|
65
|
+
rescue JSON::ParserError => e
|
66
|
+
raise SchemaParseError, "Failed to parse schema: #{e.message}"
|
67
|
+
rescue YAML::SyntaxError => e
|
68
|
+
raise SchemaParseError, "Failed to parse schema: #{e.message}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module JsonSchemaRails
|
2
|
+
module Loaders
|
3
|
+
class Directory < Base
|
4
|
+
attr_accessor :path, :extnames
|
5
|
+
|
6
|
+
def initialize(path, options = {})
|
7
|
+
@path = path
|
8
|
+
@extnames = options.delete(:extnames) { %w(.yml .yaml .json) }
|
9
|
+
super(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_schema!(schema_path)
|
13
|
+
with_cache(schema_path) do
|
14
|
+
file_path = lookup_schema_file(schema_path) or raise SchemaNotFound
|
15
|
+
load_schema_file(file_path)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def lookup_schema_file(schema_path)
|
22
|
+
# Protection from directory traversal attack
|
23
|
+
raise ArgumentError, "Invalid schema path" unless schema_path =~ /\A[\/a-zA-Z0-9_-]+\z/
|
24
|
+
|
25
|
+
base_path = File.join(@path, schema_path)
|
26
|
+
Dir.glob("#{base_path}#{extnames_glob}", File::FNM_NOESCAPE).first
|
27
|
+
end
|
28
|
+
|
29
|
+
def extnames_glob
|
30
|
+
if @extnames.respond_to?(:join)
|
31
|
+
glob = @extnames.join(',')
|
32
|
+
@extnames.size > 1 ? "{#{glob}}" : glob
|
33
|
+
else
|
34
|
+
@extnames.to_s
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module JsonSchemaRails
|
2
|
+
module Loaders
|
3
|
+
class HyperSchema < Base
|
4
|
+
attr_accessor :path
|
5
|
+
|
6
|
+
def initialize(path, options = {})
|
7
|
+
@path = path
|
8
|
+
super(options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def load_schema!(schema_path)
|
12
|
+
with_cache(schema_path) do
|
13
|
+
schema_index.find(schema_path) or raise SchemaNotFound
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def schema_index
|
20
|
+
with_cache(:_schema_index) do
|
21
|
+
schema = load_schema_file(@path).tap(&:expand_references!)
|
22
|
+
SchemaIndex.new(schema)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class SchemaIndex
|
27
|
+
def initialize(schema)
|
28
|
+
@index = { _schema: schema }
|
29
|
+
build(schema)
|
30
|
+
end
|
31
|
+
|
32
|
+
def build(schema)
|
33
|
+
schema.links.each do |link|
|
34
|
+
if link.href
|
35
|
+
keys = index_keys("#{link.method || 'GET'}/#{link.href}", true)
|
36
|
+
tail = keys.reduce(@index) { |hash, key| hash[key] ||= {} }
|
37
|
+
tail[:_schema] = schema
|
38
|
+
end
|
39
|
+
end
|
40
|
+
schema.properties.each do |key, subschema|
|
41
|
+
build(subschema)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def find(schema_path)
|
46
|
+
keys = index_keys(schema_path, false)
|
47
|
+
tail = keys.reduce(@index) do |hash, key|
|
48
|
+
if hash.key?(key)
|
49
|
+
hash[key]
|
50
|
+
else
|
51
|
+
next_key = hash.keys.find { |k| k === key } or break
|
52
|
+
hash[next_key]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
tail[:_schema] if tail
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def index_keys(path, for_build)
|
61
|
+
keys = path.to_s.downcase.split('/').reject(&:empty?)
|
62
|
+
keys.map! { |k| k.gsub!(/\{(.*?)\}/, '[^/]+') ? %r/^#{k}$/ : k } if for_build
|
63
|
+
keys
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|