module JSONAPI::Responders
def ensure_jsonapi_accept_header
def ensure_jsonapi_accept_header # Allow requests without Accept header or with */* (browser defaults) # Only validate when Accept header is explicitly set to non-JSON:API media types accept_header = request.headers["Accept"] # Allow blank Accept header (browser default) return if accept_header.blank? # Allow */* Accept header (browser default) return if accept_header == "*/*" # Check if request accepts */* (wildcard) return if request.accepts.any? { |mime| mime.to_s == "*/*" } # Check if JSON:API media type is explicitly requested return if jsonapi_requested? # If Accept header is present and doesn't include JSON:API, return 406 # This ensures we honor explicit Accept preferences while allowing defaults render_not_acceptable_error end
def ensure_jsonapi_content_type
def ensure_jsonapi_content_type return if request.content_type&.include?("application/vnd.api+json") render json: { errors: [ { status: "415", title: "Unsupported Media Type", detail: "Content-Type must be application/vnd.api+json", }, ], }, status: :unsupported_media_type end
def jsonapi_requested?
def jsonapi_requested? request.accepts.any? { |mime| mime.to_s.include?("application/vnd.api+json") } end
def render_jsonapi_error(status:, title:, detail: nil, source: nil)
def render_jsonapi_error(status:, title:, detail: nil, source: nil) render_jsonapi_errors([{ status:, title:, detail:, source: }], status:) end
def render_jsonapi_errors(errors, status:)
def render_jsonapi_errors(errors, status:) normalized_errors = Array(errors).map do |error| normalized = error.compact normalized_status = normalized[:status] || status normalized[:status] = status_code_for(normalized_status) normalized end render json: { errors: normalized_errors }, status: end
def render_not_acceptable_error
def render_not_acceptable_error render_parameter_errors( [nil], title: "Not Acceptable", detail_proc: ->(_) { "Accept header must include application/vnd.api+json or be omitted" }, status: :not_acceptable, ) end
def render_parameter_errors(values, title:, detail_proc:, source_proc: nil, status: :bad_request)
def render_parameter_errors(values, title:, detail_proc:, source_proc: nil, status: :bad_request) errors = Array(values).map do |value| error = { title:, detail: detail_proc.call(value), } error[:source] = source_proc.call(value) if source_proc error end render_jsonapi_errors(errors, status:) end
def status_code_for(status)
def status_code_for(status) return status if status.is_a?(String) && status.match?(/\A\d+\z/) ::Rack::Utils::SYMBOL_TO_STATUS_CODE.fetch(status, status).to_s end