/** @file * Type-safe bridge between serialized (`STBase`-derived) types and native C++ * types for reading and writing `STObject` fields. * * Application code works with plain C++ types (integers, `Slice`, `Buffer`), * while the wire protocol stores everything in serialized form (`STInteger`, * `STBlob`, etc.). The `STExchange` traits struct centralizes the conversion * mappings so callers never need to perform manual `dynamic_cast` or construct * heap-allocated serialized objects directly. The free functions `get`, `set`, * and `erase` are the primary interface for field access on `STObject`. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { /** Traits adapter that maps a serialized type @p U to a native C++ type @p T. * * Each specialization provides: * - `value_type` — the canonical C++ representation for the serialized type. * - `get(optional&, U const&)` — extracts a native value from the * serialized object. * - `set(field, T const&)` — constructs a heap-allocated serialized object * ready for insertion into an `STObject`. * * All conversions are resolved at compile time; there is no runtime * polymorphism in the type mapping itself. Adding support for a new C++ view * of an existing wire type requires only a new specialization here — the * serialization infrastructure is not touched. * * @tparam U The serialized type (e.g. `STInteger`, `STBlob`). * @tparam T The desired native C++ type (e.g. `uint32_t`, `Slice`, `Buffer`). */ template struct STExchange; /** `STExchange` specialization covering the full family of integer types. * * A single partial specialization handles `STUInt8`, `STUInt16`, `STUInt32`, * `STUInt64`, and `STInt32` uniformly. `get` extracts the integer via * `STInteger::value()` and `set` constructs a new `STInteger` on the heap. * * @tparam U The underlying integer type (e.g. `uint32_t`, `uint64_t`). * @tparam T The desired C++ integer type to convert to/from. */ template struct STExchange, T> { explicit STExchange() = default; /** The canonical C++ integer type for this serialized field. */ using value_type = U; /** Populate @p t with the integer value stored in @p u. * * @param t Output optional to receive the extracted value. * @param u The serialized integer object to read from. */ static void get(std::optional& t, STInteger const& u) { t = u.value(); } /** Construct a heap-allocated `STInteger` initialized to @p t. * * @param f The field descriptor for the new serialized object. * @param t The integer value to store. * @return Owning pointer to the newly constructed serialized integer. */ static std::unique_ptr> set(SField const& f, T const& t) { return std::make_unique>(f, t); } }; /** `STExchange` specialization for reading an `STBlob` field as a `Slice`. * * `Slice` is a non-owning view, so both `get` and `set` always copy the * underlying bytes — `get` via `emplace(data, size)` and `set` via the * `STBlob(field, data, size)` constructor. */ template <> struct STExchange { explicit STExchange() = default; /** Non-owning byte view. */ using value_type = Slice; /** Populate @p t with a `Slice` pointing into a copy of @p u's bytes. * * @param t Output optional to receive the `Slice`. * @param u The serialized blob object to read from. */ static void get(std::optional& t, STBlob const& u) { t.emplace(u.data(), u.size()); } /** Construct a heap-allocated `STBlob` by copying the bytes of @p t. * * @param f The field descriptor for the new serialized object. * @param t The source byte view to copy into the blob. * @return Owning pointer to the newly constructed `STBlob`. */ static std::unique_ptr set(TypedField const& f, Slice const& t) { return std::make_unique(f, t.data(), t.size()); } }; /** `STExchange` specialization for reading an `STBlob` field as a `Buffer`. * * `Buffer` owns its memory. The lvalue `set` overload copies bytes into the * new `STBlob`; the rvalue `set` overload moves the `Buffer` directly into * the `STBlob`, avoiding an extra heap allocation on hot paths that build * transaction objects. */ template <> struct STExchange { explicit STExchange() = default; /** Owning byte container. */ using value_type = Buffer; /** Populate @p t with a `Buffer` containing a copy of @p u's bytes. * * @param t Output optional to receive the `Buffer`. * @param u The serialized blob object to read from. */ static void get(std::optional& t, STBlob const& u) { t.emplace(u.data(), u.size()); } /** Construct a heap-allocated `STBlob` by copying the bytes of @p t. * * @param f The field descriptor for the new serialized object. * @param t The source buffer to copy into the blob. * @return Owning pointer to the newly constructed `STBlob`. */ static std::unique_ptr set(TypedField const& f, Buffer const& t) { return std::make_unique(f, t.data(), t.size()); } /** Construct a heap-allocated `STBlob` by moving @p t's storage. * * Preferred over the lvalue overload when the caller no longer needs * the `Buffer`, as it avoids an extra heap allocation. * * @param f The field descriptor for the new serialized object. * @param t The source buffer to move into the blob. * @return Owning pointer to the newly constructed `STBlob`. */ static std::unique_ptr set(TypedField const& f, Buffer&& t) { return std::make_unique(f, std::move(t)); } }; //------------------------------------------------------------------------------ /** Read a field from an `STObject` as native C++ type @p T. * * Uses `STObject::peekAtPField` (non-mutating — does not insert a default for * absent fields) and checks two distinct absence conditions: a null pointer * (the field was never registered in the object's schema) and * `STI_NOTPRESENT` (the field exists in the schema but has been explicitly * marked absent). A `dynamic_cast` failure on the non-null, present field * indicates a programming error and throws rather than returning empty. * * @tparam T The desired native C++ type to extract (e.g. `Slice` or `Buffer` * for an `STBlob` field). * @tparam U The serialized field type, inferred from @p f. * @param st The object to read from. * @param f The typed field descriptor identifying the field. * @return The field value, or `std::nullopt` if the field is absent. * @throws std::runtime_error If the field is present but its dynamic type * does not match @p U — this indicates a programming error. * @see get(STObject const&, TypedField const&) for the type-inferring * overload that avoids spelling out @p T explicitly. */ /** @{ */ template std::optional get(STObject const& st, TypedField const& f) { std::optional t; STBase const* const b = st.peekAtPField(f); if (!b) return t; auto const id = b->getSType(); if (id == STI_NOTPRESENT) return t; auto const u = dynamic_cast(b); // This should never happen if (!u) Throw("Wrong field type"); STExchange::get(t, *u); return t; } /** Read a field from an `STObject`, inferring the native type from the field * descriptor's `value_type`. * * This is the ergonomic default: callers write `get(st, sfSequence)` rather * than `get(st, sfSequence)`. Use the explicit-`T` overload when a * different C++ view of the same wire type is needed (e.g. reading an `STBlob` * as `Slice` for temporary inspection vs. `Buffer` for ownership). * * @tparam U The serialized field type, inferred from @p f. * @param st The object to read from. * @param f The typed field descriptor identifying the field. * @return The field value as `U::value_type`, or `std::nullopt` if absent. */ template std::optional::value_type> get(STObject const& st, TypedField const& f) { return get(st, f); } /** @} */ /** Write a value into a field of an `STObject`. * * Uses `std::decay` to strip cv-qualifiers and references before selecting the * `STExchange` specialization, and `std::forward` to preserve value category * so the move-semantic `Buffer&&` overload fires when an rvalue is passed. * * @tparam U The serialized field type, inferred from @p f. * @tparam T The native C++ value type, inferred from @p t. May be an rvalue * reference to trigger move-optimized specializations (e.g. * `STExchange::set(f, Buffer&&)`). * @param st The object to write into. * @param f The typed field descriptor identifying the field. * @param t The value to store; forwarded to the appropriate `STExchange` * specialization. */ template void set(STObject& st, TypedField const& f, T&& t) { st.set(STExchange>::set(f, std::forward(t))); } /** Write a blob field whose contents are populated by a callable. * * Constructs an `STBlob` of @p size bytes and invokes @p init to fill it * in-place, avoiding an intermediate copy for large blobs. * * @tparam Init A callable with signature compatible with the `STBlob` * in-place initialization constructor. * @param st The object to write into. * @param f The field descriptor for the blob field. * @param size The desired byte length of the blob. * @param init Callable invoked to populate the blob's storage. */ template void set(STObject& st, TypedField const& f, std::size_t size, Init&& init) { st.set(std::make_unique(f, size, init)); } /** Write a blob field from a raw pointer and length. * * Convenience overload for C-style interop. Copies @p size bytes from * @p data into a newly constructed `STBlob`. * * @param st The object to write into. * @param f The field descriptor for the blob field. * @param data Pointer to the source bytes. * @param size Number of bytes to copy. */ template void set(STObject& st, TypedField const& f, void const* data, std::size_t size) { st.set(std::make_unique(f, data, size)); } /** Mark a field as absent in an `STObject` without removing it from the schema. * * Delegates to `STObject::makeFieldAbsent`, which sets the field's type to * `STI_NOTPRESENT`. The field slot is retained in the object's declared * schema but contributes nothing to the wire encoding or canonical * serialization. * * @tparam U The serialized field type, inferred from @p f. * @param st The object to modify. * @param f The typed field descriptor identifying the field to erase. */ template void erase(STObject& st, TypedField const& f) { st.makeFieldAbsent(f); } } // namespace xrpl