# typed: false # frozen_string_literal: true require "openapi_first" module Hephaestus module Middleware class OpenapiValidation API_PATH_PREFIX = "/api/" API_SPEC_PATH = Rails.root.join("lib/schemas/api/2023-03-06/openapi.json") def initialize(app, ignore_path: "/settings", match_path: API_PATH_PREFIX, limit_methods_to: nil, spec: API_SPEC_PATH) @app = app @ignore_path = ignore_path @match_path = match_path @limit_methods_to = limit_methods_to @spec = OpenapiFirst.load(spec) end def call(env) request = Rack::Request.new(env) return @app.call(env) if request.path.include?(@ignore_path) return @app.call(env) unless request.path.starts_with?(@match_path) return @app.call(env) if @limit_methods_to.present? && @limit_methods_to.exclude?(request.request_method) begin # force content-type to JSON env["CONTENT_TYPE"] = "application/json" if env["CONTENT_TYPE"] != "application/json" validated_request = @spec.validate_request(request) return @app.call(env) if validated_request.valid? case validated_request.error when OpenapiFirst::Schema::ValidationError error_arr = format_arr(validated_request.error.errors.map(&:message)) Rails.logger.error(error_arr) if print_user_api_errors? [Hephaestus::HTTP::BAD_REQUEST_I, { "Content-Type" => "application/json" }, [error_arr]] else case validated_request.error.type when :not_found [Hephaestus::HTTP::NOT_FOUND_I, { "Content-Type" => "application/json" }, [format_str("Not Found")]] else error_message = if validated_request.error.errors.present? format_arr(validated_request.error.errors.map(&:message)) else format_str(validated_request.error.message) end Rails.logger.error(error_message) if print_user_api_errors? [Hephaestus::HTTP::BAD_REQUEST_I, { "Content-Type" => "application/json" }, [error_message]] end end rescue StandardError => e raise e unless Rails.env.production? Rails.logger.error( "openapi.request_validation.error", path: request.path, method: request.env["REQUEST_METHOD"], error_message: e.message, ) end end private def format_str(error) { errors: [ { message: error, }, ], }.to_json end private def format_arr(errors) { errors: errors.each_with_object([]) do |error, arr| arr << { message: error, } end, }.to_json end end end end