lib/json_api/controllers/concerns/controller_helpers/parsing.rb



# frozen_string_literal: true

module JSONAPI
  module ControllerHelpers
    module Parsing
      extend ActiveSupport::Concern

      included do
        before_action :parse_jsonapi_body, if: -> { modifying_request? }
      end

      protected

      def parse_jsonapi_body
        return unless jsonapi_content_type?
        return if params[:data].present?

        parse_and_apply_json_body
      rescue JSON::ParserError
        # Invalid JSON - will be handled by validation
      end

      def modifying_request?
        request.post? || request.patch? || request.put? || request.delete?
      end

      def jsonapi_content_type?
        request.content_type&.include?("application/vnd.api+json")
      end

      def parse_and_apply_json_body
        body = request.body.read
        request.body.rewind
        return if body.blank?

        parsed = JSON.parse(body)
        parsed.deep_transform_keys!(&:to_sym)
        request.env["action_dispatch.request.request_parameters"] = parsed
      end

      def jsonapi_params
        data = params.require(:data)
        return data if data.is_a?(Array)

        permitted = data.permit(:type, :id, attributes: {})
        permitted[:relationships] = permit_relationships(data) if data[:relationships].present?
        permitted
      end

      def permit_relationships(data)
        result = {}
        data[:relationships].each do |key, value|
          result[key] = value.permit(data: %i[type id]) if value.is_a?(ActionController::Parameters)
        end
        result
      end

      def jsonapi_attributes
        (jsonapi_params[:attributes] || {}).to_h
      end

      def jsonapi_relationships
        jsonapi_params[:relationships] || {}
      end

      def jsonapi_type
        data = jsonapi_params
        return data.first[:type] if data.is_a?(Array)

        data[:type]
      end

      def jsonapi_id
        data = jsonapi_params
        return data.first[:id].to_s.presence if data.is_a?(Array)

        data[:id].to_s.presence
      end

      def parse_include_param
        return [] unless params[:include]

        params[:include].to_s.split(",").map(&:strip)
      end

      def parse_fields_param
        return {} unless params[:fields]

        params[:fields].permit!.to_h.each_with_object({}) do |(type, fields), hash|
          hash[type.to_sym] = fields.to_s.split(",").map(&:strip)
        end
      end

      def parse_filter_param
        return {} unless params[:filter]

        raw_filters = params[:filter].permit!.to_h
        JSONAPI::ParamHelpers.flatten_filter_hash(raw_filters)
      end

      def parse_sort_param
        return [] unless params[:sort]

        params[:sort].to_s.split(",").map(&:strip)
      end

      def parse_page_param
        return {} unless params[:page]

        params[:page].permit(:number, :size).to_h
      end

      def invalid_sort_fields_for_columns(sorts, available_columns)
        sorts.filter_map do |sort_field|
          field = JSONAPI::RelationshipHelpers.extract_sort_field_name(sort_field)
          field unless available_columns.include?(field.to_s)
        end
      end

      def valid_sort_fields_for_resource(resource_class, model_class)
        model_columns = model_class.column_names.map(&:to_sym)
        resource_sortable_fields = resource_class.permitted_sortable_fields.map(&:to_sym)
        (model_columns + resource_sortable_fields).uniq.map(&:to_s)
      end
    end
  end
end