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.
Files changed (123) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +16 -0
  3. data/.gitignore +11 -0
  4. data/.rspec +1 -0
  5. data/Gemfile +14 -0
  6. data/Guardfile +7 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +121 -0
  9. data/Rakefile +17 -0
  10. data/json_schema_rails.gemspec +29 -0
  11. data/lib/generators/schema/action/USAGE +22 -0
  12. data/lib/generators/schema/action/action_generator.rb +52 -0
  13. data/lib/generators/schema/action/templates/schema.yml.erb +31 -0
  14. data/lib/generators/schema/controller/USAGE +13 -0
  15. data/lib/generators/schema/controller/controller_generator.rb +27 -0
  16. data/lib/generators/schema/controller/templates/schema.yml.erb +7 -0
  17. data/lib/json_schema_rails/errors.rb +44 -0
  18. data/lib/json_schema_rails/helpers.rb +38 -0
  19. data/lib/json_schema_rails/loaders/base.rb +72 -0
  20. data/lib/json_schema_rails/loaders/directory.rb +39 -0
  21. data/lib/json_schema_rails/loaders/hyper_schema.rb +68 -0
  22. data/lib/json_schema_rails/loaders.rb +7 -0
  23. data/lib/json_schema_rails/railtie.rb +25 -0
  24. data/lib/json_schema_rails/schema_validator.rb +91 -0
  25. data/lib/json_schema_rails/version.rb +3 -0
  26. data/lib/json_schema_rails.rb +43 -0
  27. data/lib/tasks/schema_tasks.rake +37 -0
  28. data/schemas/core-schema.json +150 -0
  29. data/schemas/hyper-schema.json +168 -0
  30. data/schemas/strict/core-schema.json +151 -0
  31. data/schemas/strict/hyper-schema.json +169 -0
  32. data/spec/dummy_apps/rails3/README.rdoc +261 -0
  33. data/spec/dummy_apps/rails3/Rakefile +7 -0
  34. data/spec/dummy_apps/rails3/app/assets/images/rails.png +0 -0
  35. data/spec/dummy_apps/rails3/app/assets/javascripts/application.js +13 -0
  36. data/spec/dummy_apps/rails3/app/assets/stylesheets/application.css +13 -0
  37. data/spec/dummy_apps/rails3/app/controllers/application_controller.rb +3 -0
  38. data/spec/dummy_apps/rails3/app/controllers/posts_controller.rb +11 -0
  39. data/spec/dummy_apps/rails3/app/helpers/application_helper.rb +2 -0
  40. data/spec/dummy_apps/rails3/app/schemas/posts/create.yml +20 -0
  41. data/spec/dummy_apps/rails3/app/schemas/posts/update.json +29 -0
  42. data/spec/dummy_apps/rails3/app/views/layouts/application.html.erb +14 -0
  43. data/spec/dummy_apps/rails3/config/application.rb +68 -0
  44. data/spec/dummy_apps/rails3/config/boot.rb +6 -0
  45. data/spec/dummy_apps/rails3/config/database.yml +25 -0
  46. data/spec/dummy_apps/rails3/config/environment.rb +5 -0
  47. data/spec/dummy_apps/rails3/config/environments/development.rb +37 -0
  48. data/spec/dummy_apps/rails3/config/environments/production.rb +67 -0
  49. data/spec/dummy_apps/rails3/config/environments/test.rb +37 -0
  50. data/spec/dummy_apps/rails3/config/initializers/backtrace_silencers.rb +7 -0
  51. data/spec/dummy_apps/rails3/config/initializers/inflections.rb +15 -0
  52. data/spec/dummy_apps/rails3/config/initializers/mime_types.rb +5 -0
  53. data/spec/dummy_apps/rails3/config/initializers/secret_token.rb +7 -0
  54. data/spec/dummy_apps/rails3/config/initializers/session_store.rb +8 -0
  55. data/spec/dummy_apps/rails3/config/initializers/wrap_parameters.rb +14 -0
  56. data/spec/dummy_apps/rails3/config/locales/en.yml +5 -0
  57. data/spec/dummy_apps/rails3/config/routes.rb +60 -0
  58. data/spec/dummy_apps/rails3/config.ru +4 -0
  59. data/spec/dummy_apps/rails3/db/seeds.rb +7 -0
  60. data/spec/dummy_apps/rails3/doc/README_FOR_APP +2 -0
  61. data/spec/dummy_apps/rails3/public/404.html +26 -0
  62. data/spec/dummy_apps/rails3/public/422.html +26 -0
  63. data/spec/dummy_apps/rails3/public/500.html +25 -0
  64. data/spec/dummy_apps/rails3/public/favicon.ico +0 -0
  65. data/spec/dummy_apps/rails3/public/index.html +241 -0
  66. data/spec/dummy_apps/rails3/public/robots.txt +5 -0
  67. data/spec/dummy_apps/rails3/script/rails +6 -0
  68. data/spec/dummy_apps/rails4/README.rdoc +28 -0
  69. data/spec/dummy_apps/rails4/Rakefile +6 -0
  70. data/spec/dummy_apps/rails4/app/assets/images/.keep +0 -0
  71. data/spec/dummy_apps/rails4/app/assets/javascripts/application.js +13 -0
  72. data/spec/dummy_apps/rails4/app/assets/javascripts/posts.js +2 -0
  73. data/spec/dummy_apps/rails4/app/assets/stylesheets/application.css +15 -0
  74. data/spec/dummy_apps/rails4/app/assets/stylesheets/posts.css +4 -0
  75. data/spec/dummy_apps/rails4/app/controllers/application_controller.rb +5 -0
  76. data/spec/dummy_apps/rails4/app/controllers/concerns/.keep +0 -0
  77. data/spec/dummy_apps/rails4/app/controllers/posts_controller.rb +11 -0
  78. data/spec/dummy_apps/rails4/app/helpers/application_helper.rb +2 -0
  79. data/spec/dummy_apps/rails4/app/mailers/.keep +0 -0
  80. data/spec/dummy_apps/rails4/app/models/.keep +0 -0
  81. data/spec/dummy_apps/rails4/app/models/concerns/.keep +0 -0
  82. data/spec/dummy_apps/rails4/app/schemas/posts/create.yml +20 -0
  83. data/spec/dummy_apps/rails4/app/schemas/posts/update.json +29 -0
  84. data/spec/dummy_apps/rails4/app/views/layouts/application.html.erb +14 -0
  85. data/spec/dummy_apps/rails4/bin/bundle +3 -0
  86. data/spec/dummy_apps/rails4/bin/rails +4 -0
  87. data/spec/dummy_apps/rails4/bin/rake +4 -0
  88. data/spec/dummy_apps/rails4/config/application.rb +29 -0
  89. data/spec/dummy_apps/rails4/config/boot.rb +5 -0
  90. data/spec/dummy_apps/rails4/config/environment.rb +5 -0
  91. data/spec/dummy_apps/rails4/config/environments/development.rb +34 -0
  92. data/spec/dummy_apps/rails4/config/environments/production.rb +80 -0
  93. data/spec/dummy_apps/rails4/config/environments/test.rb +39 -0
  94. data/spec/dummy_apps/rails4/config/initializers/backtrace_silencers.rb +7 -0
  95. data/spec/dummy_apps/rails4/config/initializers/cookies_serializer.rb +3 -0
  96. data/spec/dummy_apps/rails4/config/initializers/filter_parameter_logging.rb +4 -0
  97. data/spec/dummy_apps/rails4/config/initializers/inflections.rb +16 -0
  98. data/spec/dummy_apps/rails4/config/initializers/mime_types.rb +4 -0
  99. data/spec/dummy_apps/rails4/config/initializers/session_store.rb +3 -0
  100. data/spec/dummy_apps/rails4/config/initializers/wrap_parameters.rb +9 -0
  101. data/spec/dummy_apps/rails4/config/locales/en.yml +23 -0
  102. data/spec/dummy_apps/rails4/config/routes.rb +58 -0
  103. data/spec/dummy_apps/rails4/config/secrets.yml +22 -0
  104. data/spec/dummy_apps/rails4/config.ru +4 -0
  105. data/spec/dummy_apps/rails4/lib/assets/.keep +0 -0
  106. data/spec/dummy_apps/rails4/log/.keep +0 -0
  107. data/spec/dummy_apps/rails4/public/404.html +67 -0
  108. data/spec/dummy_apps/rails4/public/422.html +67 -0
  109. data/spec/dummy_apps/rails4/public/500.html +66 -0
  110. data/spec/dummy_apps/rails4/public/favicon.ico +0 -0
  111. data/spec/fixtures/hyper_schema.yml +42 -0
  112. data/spec/fixtures/schemas/invalid_schema.yml +15 -0
  113. data/spec/fixtures/schemas/posts/create.yml +20 -0
  114. data/spec/fixtures/schemas/posts/search.json +13 -0
  115. data/spec/fixtures/schemas/posts/update.yaml +20 -0
  116. data/spec/fixtures/schemas/syntax_error.json +5 -0
  117. data/spec/lib/json_schema_rails/loaders/directory_spec.rb +59 -0
  118. data/spec/lib/json_schema_rails/loaders/hyper_schema_spec.rb +40 -0
  119. data/spec/lib/json_schema_rails/schema_validator_spec.rb +57 -0
  120. data/spec/lib/json_schema_rails_spec.rb +123 -0
  121. data/spec/railtie/controllers/posts_controller_spec.rb +43 -0
  122. data/spec/spec_helper.rb +28 -0
  123. 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
@@ -0,0 +1,11 @@
1
+ .bundle/
2
+ log/*.log
3
+ rdoc/
4
+ pkg/
5
+ spec/dummy_apps/*/db/*.sqlite3
6
+ spec/dummy_apps/*/db/*.sqlite3-journal
7
+ spec/dummy_apps/*/log/*.log
8
+ spec/dummy_apps/*/tmp/
9
+ spec/dummy_apps/*/.sass-cache
10
+ vendor/
11
+ Gemfile.lock
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,7 @@
1
+ ---
2
+ "$schema": "http://json-schema.org/draft-04/schema#"
3
+ title: <%= schema_title %>
4
+ type: object
5
+ properties:
6
+ <%= model_name %>:
7
+ type: object
@@ -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
@@ -0,0 +1,7 @@
1
+ module JsonSchemaRails
2
+ module Loaders
3
+ autoload :Base, 'json_schema_rails/loaders/base'
4
+ autoload :Directory, 'json_schema_rails/loaders/directory'
5
+ autoload :HyperSchema, 'json_schema_rails/loaders/hyper_schema'
6
+ end
7
+ end