Module: HotCell::Payload
- Defined in:
- lib/hot_cell/payload.rb
Overview
A payload is a JSON object, and the rules are stricter than JSON's in one direction and looser in another.
Values must be JSON-native, because to_json is not a check. It serializes a Symbol to a String, a Time to a String, and an arbitrary object through whatever to_json that object happens to define, all silently and none of it faithfully. So validate the values, then serialize.
Keys may be Strings or Symbols, because both serialize to the same JSON string and both arrive
symbolized. { format: "png" } is how a payload is naturally written and must not be rejected.
Constant Summary collapse
- MAX_DEPTH =
A payload sits one level inside the message envelope, so it gets one level less than the line.
MAX_NESTING - 1
Class Method Summary collapse
Class Method Details
.generate(object, name) ⇒ Object
20 21 22 23 |
# File 'lib/hot_cell/payload.rb', line 20 def generate(object, name) validate! object, name JSON.generate object end |
.parse(json) ⇒ Object
Keys are deep-symbolized here rather than in an operation, so an operation never has to know whether to reach for payload or payload, and a nested hash can be splatted straight into a library's keyword arguments. Only keys: a Symbol value would not survive the round trip, which the JSON-native rule already forbids.
JSON.parse only. Never JSON.load, and never create_additions, both of which instantiate arbitrary classes named by a json_class key in the document.
Every way this can fail becomes one named failure, and the catch-all is the point rather than laziness. Callers used to name what JSON.parse raises, and naming it has now been wrong twice: a report that was not an object raised TypeError past a rescue for JSON::ParserError, and a key holding bytes that are not valid UTF-8 raises EncodingError past both. The supervisor reads worker reports through here inside the loop that enforces every request's deadline, and nothing above it rescues anything, so each miss is a one-line denial of service against every request in the cell.
The body is a single JSON.parse call, so this is scoped to "the JSON layer failed" and cannot swallow a bug in our own code. NoMemoryError is deliberately not caught: it is not a StandardError, and a document large enough to raise it is the worker's own memory verdict rather than a bad line.
43 44 45 46 47 48 |
# File 'lib/hot_cell/payload.rb', line 43 def parse(json) JSON.parse json, symbolize_names: true, max_nesting: MAX_NESTING, allow_nan: false, create_additions: false rescue StandardError => error raise MessageError, "#{error.class}: #{Failure.sanitize(error.)}" end |
.validate!(object, name) ⇒ Object
50 51 52 53 54 55 56 57 |
# File 'lib/hot_cell/payload.rb', line 50 def validate!(object, name) unless object.is_a?(Hash) raise SerializationError, "#{name} is a #{object.class} and must be a Hash" end walk object, name, 1 object end |