class JSONAPI::RequestParser
Attributes
context[RW]
controller_module_path[RW]
errors[RW]
fields[RW]
filters[RW]
include[RW]
include_directives[RW]
paginator[RW]
params[RW]
server_error_callbacks[RW]
sort_criteria[RW]
source_id[RW]
source_klass[RW]
warnings[RW]
Public Class Methods
new(params = nil, options = {})
click to toggle source
# File lib/jsonapi/request_parser.rb, line 7 def initialize(params = nil, options = {}) @params = params if params controller_path = params.fetch(:controller, '') @controller_module_path = controller_path.include?('/') ? controller_path.rpartition('/').first + '/' : '' else @controller_module_path = '' end @context = options[:context] @key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter) @errors = [] @warnings = [] @server_error_callbacks = options.fetch(:server_error_callbacks, []) end
Public Instance Methods
check_include(resource_klass, include_parts)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 336 def check_include(resource_klass, include_parts) relationship_name = unformat_key(include_parts.first) relationship = resource_klass._relationship(relationship_name) if relationship && format_key(relationship_name) == include_parts.first unless relationship.allow_include?(context) fail JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type), include_parts.first) end unless include_parts.last.empty? check_include(Resource.resource_klass_for(resource_klass.module_path + relationship.class_name.to_s.underscore), include_parts.last.partition('.')) end else fail JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type), include_parts.first) end true end
check_sort_criteria(resource_klass, sort_criteria)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 442 def check_sort_criteria(resource_klass, sort_criteria) sort_field = sort_criteria[:field] unless resource_klass.sortable_field?(sort_field.to_sym, context) fail JSONAPI::Exceptions::InvalidSortCriteria.new(format_key(resource_klass._type), sort_field) end end
each(_response_document) { |operation| ... }
click to toggle source
# File lib/jsonapi/request_parser.rb, line 27 def each(_response_document) operation = setup_base_op(params) if @errors.any? fail JSONAPI::Exceptions::Errors.new(@errors) else yield operation end rescue ActionController::ParameterMissing => e fail JSONAPI::Exceptions::ParameterMissing.new(e.param, error_object_overrides) end
error_object_overrides()
click to toggle source
# File lib/jsonapi/request_parser.rb, line 23 def error_object_overrides {} end
format_key(key)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 732 def format_key(key) @key_formatter.format(key) end
parse_add_relationship_operation(resource_klass, verified_params, relationship, parent_key)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 667 def parse_add_relationship_operation(resource_klass, verified_params, relationship, parent_key) if relationship.is_a?(JSONAPI::Relationship::ToMany) return JSONAPI::Operation.new( :create_to_many_relationships, resource_klass, context: @context, resource_id: parent_key, relationship_type: relationship.name, data: verified_params[:to_many].values[0] ) end end
parse_fields(resource_klass, fields)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 282 def parse_fields(resource_klass, fields) extracted_fields = {} return extracted_fields if fields.nil? # Extract the fields for each type from the fields parameters if fields.is_a?(ActionController::Parameters) fields.each do |field, value| if value.is_a?(Array) resource_fields = value else resource_fields = value.split(',') unless value.nil? || value.empty? end extracted_fields[field] = resource_fields end else fail JSONAPI::Exceptions::InvalidFieldFormat.new(error_object_overrides) end # Validate the fields validated_fields = {} extracted_fields.each do |type, values| underscored_type = unformat_key(type) validated_fields[type] = [] begin if type != format_key(type) fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides) end type_resource = Resource.resource_klass_for(resource_klass.module_path + underscored_type.to_s) rescue NameError fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides) end if type_resource.nil? fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides) else unless values.nil? valid_fields = type_resource.fields.collect { |key| format_key(key) } values.each do |field| if valid_fields.include?(field) validated_fields[type].push unformat_key(field) else fail JSONAPI::Exceptions::InvalidField.new(type, field, error_object_overrides) end end else fail JSONAPI::Exceptions::InvalidField.new(type, 'nil', error_object_overrides) end end end validated_fields.deep_transform_keys { |key| unformat_key(key) } end
parse_filters(resource_klass, filters)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 378 def parse_filters(resource_klass, filters) parsed_filters = {} # apply default filters resource_klass._allowed_filters.each do |filter, opts| next if opts[:default].nil? || !parsed_filters[filter].nil? parsed_filters[filter] = opts[:default] end return parsed_filters unless filters unless filters.class.method_defined?(:each) @errors.concat(JSONAPI::Exceptions::InvalidFiltersSyntax.new(filters).errors) return {} end unless JSONAPI.configuration.allow_filter fail JSONAPI::Exceptions::ParameterNotAllowed.new(:filter) end filters.each do |key, value| filter = unformat_key(key) if resource_klass._allowed_filter?(filter) parsed_filters[filter] = value else fail JSONAPI::Exceptions::FilterNotAllowed.new(key) end end parsed_filters end
parse_include_directives(resource_klass, raw_include)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 355 def parse_include_directives(resource_klass, raw_include) raw_include ||= '' included_resources = [] begin included_resources += raw_include.is_a?(Array) ? raw_include : CSV.parse_line(raw_include) || [] rescue CSV::MalformedCSVError fail JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type), raw_include) end begin result = included_resources.compact.map do |included_resource| check_include(resource_klass, included_resource.partition('.')) unformat_key(included_resource).to_s end return JSONAPI::IncludeDirectives.new(resource_klass, result) rescue JSONAPI::Exceptions::InvalidInclude => e @errors.concat(e.errors) return {} end end
parse_modify_relationship_action(modification_type, params, resource_klass)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 254 def parse_modify_relationship_action(modification_type, params, resource_klass) relationship_type = params.require(:relationship) parent_key = params.require(resource_klass._as_parent_key) relationship = resource_klass._relationship(relationship_type) # Removals of to-one relationships are done implicitly and require no specification of data data_required = !(modification_type == :remove && relationship.is_a?(JSONAPI::Relationship::ToOne)) if data_required data = params.fetch(:data) object_params = { relationships: { format_key(relationship.name) => { data: data } } } verified_params = parse_params(resource_klass, object_params, resource_klass.updatable_fields(@context)) parse_arguments = [resource_klass, verified_params, relationship, parent_key] else parse_arguments = [resource_klass, params, relationship, parent_key] end send(:"parse_#{modification_type}_relationship_operation", *parse_arguments) end
parse_pagination(resource_klass, page)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 277 def parse_pagination(resource_klass, page) paginator_name = resource_klass._paginator JSONAPI::Paginator.paginator_for(paginator_name).new(page) unless paginator_name == :none end
parse_params(resource_klass, params, allowed_fields)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 493 def parse_params(resource_klass, params, allowed_fields) verify_permitted_params(params, allowed_fields) checked_attributes = {} checked_to_one_relationships = {} checked_to_many_relationships = {} params.each do |key, value| case key.to_s when 'relationships' value.each do |link_key, link_value| param = unformat_key(link_key) relationship = resource_klass._relationship(param) if relationship.is_a?(JSONAPI::Relationship::ToOne) checked_to_one_relationships[param] = parse_to_one_relationship(resource_klass, link_value, relationship) elsif relationship.is_a?(JSONAPI::Relationship::ToMany) parse_to_many_relationship(resource_klass, link_value, relationship) do |result_val| checked_to_many_relationships[param] = result_val end end end when 'id' checked_attributes['id'] = unformat_value(resource_klass, :id, value) when 'attributes' value.each do |key, value| param = unformat_key(key) checked_attributes[param] = unformat_value(resource_klass, param, value) end end end return { 'attributes' => checked_attributes, 'to_one' => checked_to_one_relationships, 'to_many' => checked_to_many_relationships }.deep_transform_keys { |key| unformat_key(key) } end
parse_remove_relationship_operation(resource_klass, params, relationship, parent_key)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 708 def parse_remove_relationship_operation(resource_klass, params, relationship, parent_key) operation_base_args = [resource_klass].push( context: @context, resource_id: parent_key, relationship_type: relationship.name ) if relationship.is_a?(JSONAPI::Relationship::ToMany) operation_args = operation_base_args.dup keys = params[:to_many].values[0] operation_args[1] = operation_args[1].merge(associated_keys: keys) JSONAPI::Operation.new(:remove_to_many_relationships, *operation_args) else JSONAPI::Operation.new(:remove_to_one_relationship, *operation_base_args) end end
parse_sort_criteria(resource_klass, sort_criteria)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 410 def parse_sort_criteria(resource_klass, sort_criteria) return unless sort_criteria.present? unless JSONAPI.configuration.allow_sort fail JSONAPI::Exceptions::ParameterNotAllowed.new(:sort) end if sort_criteria.is_a?(Array) sorts = sort_criteria elsif sort_criteria.is_a?(String) begin raw = URI.decode_www_form_component(sort_criteria) sorts = CSV.parse_line(raw) rescue CSV::MalformedCSVError fail JSONAPI::Exceptions::InvalidSortCriteria.new(format_key(resource_klass._type), raw) end end @sort_criteria = sorts.collect do |sort| if sort.start_with?('-') criteria = { field: unformat_key(sort[1..-1]).to_s } criteria[:direction] = :desc else criteria = { field: unformat_key(sort).to_s } criteria[:direction] = :asc end check_sort_criteria(resource_klass, criteria) criteria end end
parse_to_many_links_object(raw)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 477 def parse_to_many_links_object(raw) fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides) if raw.nil? links_object = {} if raw.is_a?(Array) raw.each do |link| link_object = parse_to_one_links_object(link) links_object[link_object[:type]] ||= [] links_object[link_object[:type]].push(link_object[:id]) end else fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides) end links_object end
parse_to_many_relationship(resource_klass, link_value, relationship, &add_result)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 558 def parse_to_many_relationship(resource_klass, link_value, relationship, &add_result) if (link_value.is_a?(Hash) || link_value.is_a?(ActionController::Parameters)) linkage = link_value[:data] else fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides) end links_object = parse_to_many_links_object(linkage) if links_object.length == 0 add_result.call([]) else if relationship.polymorphic? polymorphic_results = [] links_object.each_pair do |type, keys| type_name = unformat_key(type).to_s relationship_resource_klass = resource_klass.resource_klass_for(relationship.class_name) relationship_klass = relationship_resource_klass._model_class linkage_object_resource_klass = resource_klass.resource_klass_for(type_name) linkage_object_klass = linkage_object_resource_klass._model_class unless linkage_object_klass == relationship_klass || linkage_object_klass.in?(relationship_klass.subclasses) fail JSONAPI::Exceptions::TypeMismatch.new(type_name) end relationship_ids = relationship_resource_klass.verify_keys(keys, @context) polymorphic_results << { type: type, ids: relationship_ids } end add_result.call polymorphic_results else relationship_type = unformat_key(relationship.type).to_s if links_object.length > 1 || !links_object.has_key?(relationship_type) fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type]) end relationship_resource_klass = Resource.resource_klass_for(resource_klass.module_path + relationship_type) add_result.call relationship_resource_klass.verify_keys(links_object[relationship_type], @context) end end end
parse_to_one_links_object(raw)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 458 def parse_to_one_links_object(raw) if raw.nil? return { type: nil, id: nil } end if !(raw.is_a?(Hash) || raw.is_a?(ActionController::Parameters)) || raw.keys.length != 2 || !(raw.key?('type') && raw.key?('id')) fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides) end { type: unformat_key(raw['type']).to_s, id: raw['id'] } end
parse_to_one_relationship(resource_klass, link_value, relationship)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 532 def parse_to_one_relationship(resource_klass, link_value, relationship) if link_value.nil? linkage = nil else linkage = link_value[:data] end links_object = parse_to_one_links_object(linkage) if !relationship.polymorphic? && links_object[:type] && (links_object[:type].to_s != relationship.type.to_s) fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type], error_object_overrides) end unless links_object[:id].nil? resource = resource_klass || Resource relationship_resource = resource.resource_klass_for(unformat_key(relationship.options[:class_name] || links_object[:type]).to_s) relationship_id = relationship_resource.verify_key(links_object[:id], @context) if relationship.polymorphic? { id: relationship_id, type: unformat_key(links_object[:type].to_s) } else relationship_id end else nil end end
parse_update_relationship_operation(resource_klass, verified_params, relationship, parent_key)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 680 def parse_update_relationship_operation(resource_klass, verified_params, relationship, parent_key) options = { context: @context, resource_id: parent_key, relationship_type: relationship.name } if relationship.is_a?(JSONAPI::Relationship::ToOne) if relationship.polymorphic? options[:key_value] = verified_params[:to_one].values[0][:id] options[:key_type] = verified_params[:to_one].values[0][:type] operation_type = :replace_polymorphic_to_one_relationship else options[:key_value] = verified_params[:to_one].values[0] operation_type = :replace_to_one_relationship end elsif relationship.is_a?(JSONAPI::Relationship::ToMany) unless relationship.acts_as_set fail JSONAPI::Exceptions::ToManySetReplacementForbidden.new end options[:data] = verified_params[:to_many].values[0] operation_type = :replace_to_many_relationships end JSONAPI::Operation.new(operation_type, resource_klass, options) end
resolve_singleton_id(params, resource_klass)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 725 def resolve_singleton_id(params, resource_klass) if resource_klass.singleton? && params[:id].nil? key = resource_klass.singleton_key(context) params[:id] = key end end
setup_base_op(params)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 47 def setup_base_op(params) return if params.nil? resource_klass = Resource.resource_klass_for(params[:controller]) if params[:controller] setup_action_method_name = "setup_#{params[:action]}_action" if respond_to?(setup_action_method_name) raise params[:_parser_exception] if params[:_parser_exception] send(setup_action_method_name, params, resource_klass) end rescue ActionController::ParameterMissing => e @errors.concat(JSONAPI::Exceptions::ParameterMissing.new(e.param, error_object_overrides).errors) rescue JSONAPI::Exceptions::Error => e e.error_object_overrides.merge! error_object_overrides @errors.concat(e.errors) end
setup_create_action(params, resource_klass)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 172 def setup_create_action(params, resource_klass) fields = parse_fields(resource_klass, params[:fields]) include_directives = parse_include_directives(resource_klass, params[:include]) data = params.require(:data) unless data.respond_to?(:each_pair) fail JSONAPI::Exceptions::InvalidDataFormat.new(error_object_overrides) end verify_type(data[:type], resource_klass) data = parse_params(resource_klass, data, resource_klass.creatable_fields(@context)) JSONAPI::Operation.new( :create_resource, resource_klass, context: @context, data: data, fields: fields, include_directives: include_directives, warnings: @warnings ) end
setup_create_relationship_action(params, resource_klass)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 197 def setup_create_relationship_action(params, resource_klass) resolve_singleton_id(params, resource_klass) parse_modify_relationship_action(:add, params, resource_klass) end
setup_destroy_action(params, resource_klass)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 240 def setup_destroy_action(params, resource_klass) resolve_singleton_id(params, resource_klass) JSONAPI::Operation.new( :remove_resource, resource_klass, context: @context, resource_id: resource_klass.verify_key(params.require(:id), @context)) end
setup_destroy_relationship_action(params, resource_klass)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 249 def setup_destroy_relationship_action(params, resource_klass) resolve_singleton_id(params, resource_klass) parse_modify_relationship_action(:remove, params, resource_klass) end
setup_index_action(params, resource_klass)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 64 def setup_index_action(params, resource_klass) fields = parse_fields(resource_klass, params[:fields]) include_directives = parse_include_directives(resource_klass, params[:include]) filters = parse_filters(resource_klass, params[:filter]) sort_criteria = parse_sort_criteria(resource_klass, params[:sort]) paginator = parse_pagination(resource_klass, params[:page]) JSONAPI::Operation.new( :find, resource_klass, context: context, filters: filters, include_directives: include_directives, sort_criteria: sort_criteria, paginator: paginator, fields: fields ) end
setup_show_action(params, resource_klass)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 132 def setup_show_action(params, resource_klass) resolve_singleton_id(params, resource_klass) fields = parse_fields(resource_klass, params[:fields]) include_directives = parse_include_directives(resource_klass, params[:include]) id = params[:id] JSONAPI::Operation.new( :show, resource_klass, context: @context, id: id, include_directives: include_directives, fields: fields, allowed_resources: params[:allowed_resources] ) end
setup_show_relationship_action(params, resource_klass)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 149 def setup_show_relationship_action(params, resource_klass) resolve_singleton_id(params, resource_klass) relationship_type = params[:relationship] parent_key = params.require(resource_klass._as_parent_key) include_directives = parse_include_directives(resource_klass, params[:include]) filters = parse_filters(resource_klass, params[:filter]) sort_criteria = parse_sort_criteria(resource_klass, params[:sort]) paginator = parse_pagination(resource_klass, params[:page]) JSONAPI::Operation.new( :show_relationship, resource_klass, context: @context, relationship_type: relationship_type, parent_key: resource_klass.verify_key(parent_key), filters: filters, sort_criteria: sort_criteria, paginator: paginator, fields: fields, include_directives: include_directives ) end
setup_update_action(params, resource_klass)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 206 def setup_update_action(params, resource_klass) resolve_singleton_id(params, resource_klass) fields = parse_fields(resource_klass, params[:fields]) include_directives = parse_include_directives(resource_klass, params[:include]) data = params.require(:data) key = params[:id] fail JSONAPI::Exceptions::InvalidDataFormat.new(error_object_overrides) unless data.respond_to?(:each_pair) fail JSONAPI::Exceptions::MissingKey.new(error_object_overrides) if data[:id].nil? resource_id = data.require(:id) # Singleton resources may not have the ID set in the URL if key fail JSONAPI::Exceptions::KeyNotIncludedInURL.new(resource_id) if key.to_s != resource_id.to_s end data.delete(:id) verify_type(data[:type], resource_klass) JSONAPI::Operation.new( :replace_fields, resource_klass, context: @context, resource_id: resource_id, data: parse_params(resource_klass, data, resource_klass.updatable_fields(@context)), fields: fields, include_directives: include_directives, warnings: @warnings ) end
setup_update_relationship_action(params, resource_klass)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 202 def setup_update_relationship_action(params, resource_klass) parse_modify_relationship_action(:update, params, resource_klass) end
transactional?()
click to toggle source
# File lib/jsonapi/request_parser.rb, line 38 def transactional? case params[:action] when 'index', 'show_related_resource', 'index_related_resources', 'show', 'show_relationship' return false else return true end end
unformat_key(key)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 736 def unformat_key(key) unformatted_key = @key_formatter.unformat(key) unformatted_key.nil? ? nil : unformatted_key.to_sym end
unformat_value(resource_klass, attribute, value)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 604 def unformat_value(resource_klass, attribute, value) value_formatter = JSONAPI::ValueFormatter.value_formatter_for(resource_klass._attribute_options(attribute)[:format]) value_formatter.unformat(value) end
verify_permitted_params(params, allowed_fields)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 609 def verify_permitted_params(params, allowed_fields) formatted_allowed_fields = allowed_fields.collect { |field| format_key(field).to_sym } params_not_allowed = [] params.each do |key, value| case key.to_s when 'relationships' value.keys.each do |links_key| unless formatted_allowed_fields.include?(links_key.to_sym) if JSONAPI.configuration.raise_if_parameters_not_allowed fail JSONAPI::Exceptions::ParameterNotAllowed.new(links_key, error_object_overrides) else params_not_allowed.push(links_key) value.delete links_key end end end when 'attributes' value.each do |attr_key, _attr_value| unless formatted_allowed_fields.include?(attr_key.to_sym) if JSONAPI.configuration.raise_if_parameters_not_allowed fail JSONAPI::Exceptions::ParameterNotAllowed.new(attr_key, error_object_overrides) else params_not_allowed.push(attr_key) value.delete attr_key end end end when 'type' when 'id' unless formatted_allowed_fields.include?(:id) if JSONAPI.configuration.raise_if_parameters_not_allowed fail JSONAPI::Exceptions::ParameterNotAllowed.new(:id, error_object_overrides) else params_not_allowed.push(:id) params.delete :id end end else if JSONAPI.configuration.raise_if_parameters_not_allowed fail JSONAPI::Exceptions::ParameterNotAllowed.new(key, error_object_overrides) else params_not_allowed.push(key) params.delete key end end end if params_not_allowed.length > 0 params_not_allowed_warnings = params_not_allowed.map do |param| JSONAPI::Warning.new(code: JSONAPI::PARAM_NOT_ALLOWED, title: 'Param not allowed', detail: "#{param} is not allowed.") end self.warnings.concat(params_not_allowed_warnings) end end
verify_type(type, resource_klass)
click to toggle source
# File lib/jsonapi/request_parser.rb, line 450 def verify_type(type, resource_klass) if type.nil? fail JSONAPI::Exceptions::ParameterMissing.new(:type) elsif unformat_key(type).to_sym != resource_klass._type fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides) end end