Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OS-17] JSON Schema validation #45

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 42 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,6 @@ def create_params
end
```

#### Relationships

JsonApi::Parameters supports ActiveRecord relationship parameters, including [nested attributes](https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html).

Relationship parameters are being read from two optional trees:

* `relationships`,
* `included`

If you provide any related resources in the `relationships` table, this gem will also look for corresponding, `included` resources and their attributes. Thanks to that this gem supports nested attributes, and will try to translate these included resources and pass them along.

For more examples take a look at [Relationships](https://github.com/visualitypl/jsonapi_parameters/wiki/Relationships) in the wiki documentation.


### Plain Ruby / outside Rails

```ruby
Expand All @@ -88,6 +74,24 @@ translator = Translator.new

translator.jsonapify(params)
```


## Relationships

JsonApi::Parameters supports ActiveRecord relationship parameters, including [nested attributes](https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html).

Relationship parameters are being read from two optional trees:

* `relationships`,
* `included`

If you provide any related resources in the `relationships` table, this gem will also look for corresponding, `included` resources and their attributes. Thanks to that this gem supports nested attributes, and will try to translate these included resources and pass them along.

For more examples take a look at [Relationships](https://github.com/visualitypl/jsonapi_parameters/wiki/Relationships) in the wiki documentation.

If you need custom relationship handling (for instance, if you have a relationship named `scissors` that is plural, but it actually is a single entity), you can use Handlers to define appropriate behaviour.

Read more at [Relationship Handlers](https://github.com/visualitypl/jsonapi_parameters/wiki/Relationship-handlers).

## Mime Type

Expand All @@ -102,7 +106,7 @@ Because of that, it is a potential vector of attack.

For this reason we have introduced a default limit of stack levels that JsonApi::Parameters will go down through while parsing the payloads.

This default limit is 3, and can be overwritten by specifying the custom limit.
This default limit is 3, and can be overwritten by specifying the custom limit. When the limit is exceeded, a `StackLevelTooDeepError` is risen.

#### Ruby
```ruby
Expand Down Expand Up @@ -139,11 +143,31 @@ ensure
end
```

## Customization
## Validations

If you need custom relationship handling (for instance, if you have a relationship named `scissors` that is plural, but it actually is a single entity), you can use Handlers to define appropriate behaviour.
JsonApi::Parameters is validating your payloads **ONLY** when an error occurs. **This means that unless there was an exception, your payload will not be validated.**

Read more at [Relationship Handlers](https://github.com/visualitypl/jsonapi_parameters/wiki/Relationship-handlers).
Reason for that is we prefer to avoid any performance overheads, and in most cases the validation errors will only be useful in the development environments, and mostly in the early parts of the implementation process. Our decision was to leave the validation to happen only in case JsonApi::Parameters failed to accomplish its task.

The validation happens with the use of jsonapi.org's JSON schema draft 6, available [here](https://jsonapi.org/faq/#is-there-a-json-schema-describing-json-api), and a gem called [JSONSchemer](https://github.com/davishmcclurg/json_schemer).

If you would prefer to suppress validation errors, you can do so by declaring it globally in your application:

```ruby
# config/initializers/jsonapi_parameters.rb

JsonApi::Parameters.suppress_schema_validation_errors = true
```

If you would prefer to prevalidate every payload _before_ attempting to fully parse it, you can do so by enforcing prevalidation:

```ruby
# config/initializers/jsonapi_parameters.rb

JsonApi::Parameters.enforce_schema_prevalidation = true
```

It is important to note that setting suppression and prevalidation is exclusive. If both settings are set to `true` no prevalidation will happen.

## License
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
2 changes: 2 additions & 0 deletions jsonapi_parameters.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Gem::Specification.new do |spec|

spec.add_runtime_dependency 'activesupport', '>= 4.1.8'
spec.add_runtime_dependency 'actionpack', '>= 4.1.8'
spec.add_runtime_dependency 'activemodel', '>= 4.1.8'
spec.add_runtime_dependency 'json_schemer', '~> 0.2.14'

spec.add_development_dependency 'nokogiri', '~> 1.10.5'
spec.add_development_dependency 'json', '~> 2.0'
Expand Down
1 change: 1 addition & 0 deletions lib/jsonapi_parameters.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'jsonapi_parameters/parameters'
require 'jsonapi_parameters/handlers'
require 'jsonapi_parameters/validator'
require 'jsonapi_parameters/translator'
require 'jsonapi_parameters/core_ext'
require 'jsonapi_parameters/stack_limit'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ def prepare_relationship_vals
related_id = relationship.dig(:id)
related_type = relationship.dig(:type)

unless related_id && related_type
raise JsonApi::Parameters::TranslatorError.new("relationship has to contain both id and type: #{relationship.inspect}")
end

included_object = find_included_object(
related_id: related_id, related_type: related_type
) || {}
Expand All @@ -36,11 +40,11 @@ def prepare_relationship_vals
@with_inclusion &= !included_object.empty?

if with_inclusion
{ **(included_object[:attributes] || {}), id: related_id }.tap do |body|
{ **(included_object[:attributes] || {}), id: related_id.to_s }.tap do |body|
body[:relationships] = included_object[:relationships] if included_object.key?(:relationships) # Pass nested relationships
end
else
relationship.dig(:id)
related_id.to_s
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ def handle
related_id: related_id, related_type: related_type
) || {}

return ["#{singularize(relationship_key)}_id".to_sym, related_id] if included_object.empty?
# We call `related_id&.to_s` because we want to make sure NOT to end up with `nil.to_s`
# if `related_id` is nil, it should remain nil, to nullify the relationship
return ["#{singularize(relationship_key)}_id".to_sym, related_id&.to_s] if included_object.empty?

included_object = { **(included_object[:attributes] || {}), id: related_id }.tap do |body|
included_object = { **(included_object[:attributes] || {}), id: related_id.to_s }.tap do |body|
body[:relationships] = included_object[:relationships] if included_object.key?(:relationships) # Pass nested relationships
end

Expand Down
Loading