Class: JSONRPC_Rails::Middleware::Validator

Inherits:
Object
  • Object
show all
Defined in:
lib/jsonrpc_rails/middleware/validator.rb

Overview

Rack middleware that validates incoming JSON-RPC 2.0 payloads and injects fully-typed Ruby objects (Request / Notification / Response) into env[:jsonrpc] for easy downstream use.

Validation always runs on the raw Hash/Array first; objects are only instantiated after the structure has been deemed valid, so malformed IDs or empty batches no longer raise before we can return a proper -32600 “Invalid Request”.

Constant Summary collapse

CONTENT_TYPE =
"application/json"
ENV_PAYLOAD_KEY =
:jsonrpc
BATCH_POLICIES =
%i[allow reject].freeze
RESERVED_MEMBERS =
%w[jsonrpc method params id result error].freeze

Instance Method Summary collapse

Constructor Details

#initialize(app, paths = nil, payload_validator: nil, batch_policy: :allow, require_json_content_type: false) ⇒ Validator

Returns a new instance of Validator.

Parameters:

  • app (#call)
  • paths (Array<String, Regexp, Proc>) (defaults to: nil)

    paths to validate

  • payload_validator (#valid?, #call, nil) (defaults to: nil)

    optional protocol validator

  • batch_policy (Symbol) (defaults to: :allow)

    either :allow or :reject

  • require_json_content_type (Boolean) (defaults to: false)

    reject matching POST requests that do not use application/json instead of passing them downstream



30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/jsonrpc_rails/middleware/validator.rb', line 30

def initialize(app, paths = nil, payload_validator: nil, batch_policy: :allow,
               require_json_content_type: false)
  unless BATCH_POLICIES.include?(batch_policy)
    raise ArgumentError, "batch_policy must be one of: #{BATCH_POLICIES.join(", ")}"
  end

  @app = app
  @paths = Array(paths || Rails.configuration.jsonrpc_rails.validated_paths)
  @payload_validator = payload_validator
  @batch_policy = batch_policy
  @require_json_content_type = require_json_content_type
end

Instance Method Details

#call(env) ⇒ Object

Rack entry point



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/jsonrpc_rails/middleware/validator.rb', line 44

def call(env)
  return @app.call(env) unless validate_path?(env["PATH_INFO"])
  return @app.call(env) unless env["REQUEST_METHOD"] == "POST"

  unless json_content_type?(env["CONTENT_TYPE"])
    return @app.call(env) unless @require_json_content_type

    return jsonrpc_error_response(
      :server_error,
      status: 415,
      message: "Unsupported Media Type: Content-Type must be application/json"
    )
  end

  body = env["rack.input"].read
  # Replace consumed input with fresh StringIO for downstream middleware
  # This is Rack 3.0+ compatible and works with all input stream types
  env["rack.input"] = StringIO.new(body)

  begin
    raw_payload = ActiveSupport::JSON.decode(body)
  rescue ActiveSupport::JSON.parse_error
    return jsonrpc_error_response(:parse_error)
  end

  unless raw_payload.is_a?(Hash) || raw_payload.is_a?(Array)
    return jsonrpc_error_response(:invalid_request)
  end

  return invalid_request_response(raw_payload) unless valid_batch_policy?(raw_payload)
  return invalid_request_response(raw_payload) unless valid_payload_structure?(raw_payload)
  return invalid_request_response(raw_payload) unless valid_protocol_payload?(raw_payload)

  begin
    env[ENV_PAYLOAD_KEY] = convert_to_objects(raw_payload)
  rescue JSON_RPC::JsonRpcError, ArgumentError, TypeError
    return invalid_request_response(raw_payload)
  end

  @app.call(env)
end