Module: Axn::Internal::AsyncSerialization
- Defined in:
- lib/axn/internal/async_serialization.rb
Overview
Dispatcher for async argument serialization. See lib/axn/internal/async_serialization.rb header comment / docs/superpowers/plans for the design.
Constant Summary collapse
- GENERIC_HINT =
"Async args must be JSON-native values (String, Integer, Float, true/false, nil, " \ "Array/Hash of those) or GlobalID-able objects (e.g. ActiveRecord records, " \ "ActiveStorage attachments)."
Class Method Summary collapse
-
.assert_serializable!(params) ⇒ Object
Validate that every arg will serialize for async, raising a field-aware UnserializableArgument otherwise — without keeping the result.
- .deserialize(params) ⇒ Object
-
.prepare_nested_payload(value) ⇒ Object
A value that rides nested inside another payload which an adapter will itself serialize needs path-specific handling.
-
.restore_nested_payload(value) ⇒ Object
Inverse of prepare_nested_payload.
- .serialize(params) ⇒ Object
Class Method Details
.assert_serializable!(params) ⇒ Object
Validate that every arg will serialize for async, raising a field-aware UnserializableArgument otherwise — without keeping the result. Lets an adapter that serializes natively (the ActiveJob adapter, via perform_later) surface the same enqueue-time contract as the Sidekiq path instead of leaking ActiveJob::SerializationError. The throwaway serialize is a cold-path cost; the adapter still serializes the real payload exactly once (no double-encoding).
50 51 52 53 |
# File 'lib/axn/internal/async_serialization.rb', line 50 def assert_serializable!(params) serialize(params) nil end |
.deserialize(params) ⇒ Object
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/axn/internal/async_serialization.rb', line 26 def deserialize(params) return {} if params.nil? || params.empty? # A fallback payload tags GlobalID args with the `_as_global_id` key suffix, and # GlobalIdSerialization can decode that whether or not ActiveJob is loaded — so route # a fallback-encoded payload there by its marker, covering an enqueue-without-ActiveJob # / perform-with-ActiveJob mismatch. Otherwise use the ActiveJob decoder only when # ActiveJob is actually loaded (its `_aj_*` format is undecodable without it anyway); # a marker-free JSON-native payload decodes identically either way. We deliberately do # NOT sniff for `_aj_*` keys to force the ActiveJob decoder: user data can legitimately # contain `_aj_`-prefixed keys, and forcing ActiveJob where it isn't loaded would raise # a NameError on otherwise-valid fallback data. return Axn::Internal::GlobalIdSerialization.deserialize(params) if _fallback_encoded?(params) return _deserialize_via_active_job(params) if _active_job_available? Axn::Internal::GlobalIdSerialization.deserialize(params) end |
.prepare_nested_payload(value) ⇒ Object
A value that rides nested inside another payload which an adapter will itself serialize needs path-specific handling. On the ActiveJob path the adapter's serializer recurses into nested hashes AND is not idempotent over its own tags, so pre-serializing here would double-encode and raise — pass it through untouched. On the fallback path the serializer is top-level-only and can't reach nested objects, so flatten them now; the resulting all-string hash is left untouched by the adapter's top-level pass.
62 63 64 |
# File 'lib/axn/internal/async_serialization.rb', line 62 def prepare_nested_payload(value) _active_job_available? ? value : serialize(value) end |
.restore_nested_payload(value) ⇒ Object
Inverse of prepare_nested_payload. Decide what to decode from the payload itself, not
this worker's ActiveJob state, so a fallback enqueue performed on an ActiveJob worker
(or vice versa) still restores. ActiveJob-restored static_args arrive with their
original (symbol) keys and live objects already rebuilt — re-running deserialize on
those makes ActiveJob reject the live records ("can only deserialize primitive
arguments"). Fallback-flattened static_args arrive with String keys (and any
_as_global_id suffixes) still needing a decode. So decode only the String-keyed form;
deserialize is itself format-aware, so it picks the right decoder for the markers.
74 75 76 77 78 |
# File 'lib/axn/internal/async_serialization.rb', line 74 def restore_nested_payload(value) return value unless value.is_a?(Hash) && value.keys.any? { |k| k.is_a?(String) } deserialize(value) end |
.serialize(params) ⇒ Object
18 19 20 21 22 23 24 |
# File 'lib/axn/internal/async_serialization.rb', line 18 def serialize(params) return {} if params.nil? || params.empty? return _serialize_via_active_job(params) if _active_job_available? params.each { |key, value| _assert_fallback_serializable!(key, value) } Axn::Internal::GlobalIdSerialization.serialize(params) end |