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 symbolized so operations read payload and can splat a nested hash into keyword arguments. Values are not: a Symbol would not survive the round trip, which the JSON-native rule already forbids.

One rescue for the whole JSON layer. Naming what JSON.parse raises has been wrong twice: a report that was not an object raised TypeError past a rescue for JSON::ParserError, and a key holding invalid UTF-8 raised EncodingError past both. The supervisor parses worker reports inside its deadline loop with nothing above it rescuing, so each miss denies service to every request in the cell.

The body is one JSON.parse call, so the rescue cannot hide a bug of our own. NoMemoryError is not a StandardError and stays uncaught: a document that large is the worker's memory verdict, not a bad line.



38
39
40
41
42
# File 'lib/hot_cell/payload.rb', line 38

def parse(json)
  JSON.parse json, symbolize_names: true, max_nesting: MAX_NESTING, allow_nan: false
rescue StandardError => error
  raise MessageError, "#{error.class}: #{Failure.sanitize(error.message)}"
end

.validate!(object, name) ⇒ Object



44
45
46
47
48
49
50
51
# File 'lib/hot_cell/payload.rb', line 44

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