class Contract
class Invalid < StandardError; end
def require!(hash, *keys)
keys.each { |k| raise Invalid, "Missing #{k}" unless hash.key?(k) }
hash
end
def integer!(value, name)
Integer(value)
rescue ArgumentError, TypeError
raise Invalid, "Invalid integer for #{name}"
end
end
class Api::IngestController < Api::BaseController
def create
payload = JSON.parse(request.raw_post)
c = Contract.new
c.require!(payload, 'event', 'member_id')
member_id = c.integer!(payload['member_id'], 'member_id')
IngestJob.perform_later(member_id, payload)
head :accepted
rescue Contract::Invalid => e
render_problem(status: 422, type: 'invalid_payload', title: 'Invalid payload', detail: e.message)
end
end
Even without extra gems, you can validate incoming JSON payloads with small “contracts” that coerce and validate keys. It’s a strong reliability upgrade for webhook and API ingestion.