A11 (C++ runtime)
Native C++ implementation of the A11 streaming action runtime
Loading...
Searching...
No Matches
serialization.h
Go to the documentation of this file.
1/*
2 * Copyright 2026 The A11 Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
37#ifndef A11_DATA_SERIALIZATION_H_
38#define A11_DATA_SERIALIZATION_H_
39
40#include <any>
41#include <cstddef>
42#include <functional>
43#include <string>
44#include <string_view>
45#include <typeindex>
46#include <typeinfo>
47#include <utility>
48#include <vector>
49
50#include <absl/base/nullability.h>
51#include <absl/status/status.h>
52#include <absl/status/status_macros.h>
53#include <absl/status/statusor.h>
54#include <absl/strings/str_cat.h>
55
56#include "a11/data/types.h"
57#include "a11/exception_guard.h"
58
59namespace a11::data {
60
62inline constexpr std::string_view kJsonMimetype = "application/json";
64inline constexpr std::string_view kMsgpackMimetype = "application/x-msgpack";
73inline constexpr std::string_view kTextMimetype = "text/plain";
83inline constexpr std::string_view kBytesMimetype = "application/octet-stream";
84
95
110template <typename T>
111class TypedChunkObject final : public ChunkObject {
112 public:
113 TypedChunkObject(T value, std::string tag, std::string mimetype,
114 std::shared_ptr<const SerializationRegistry> registry)
115 : value_(std::move(value)),
116 tag_(std::move(tag)),
117 mimetype_(std::move(mimetype)),
118 registry_(std::move(registry)) {}
119
120 [[nodiscard]] std::string_view tag() const override { return tag_; }
121
122 [[nodiscard]] std::string_view mimetype() const override { return mimetype_; }
123
124 [[nodiscard]] const void* absl_nonnull address() const override {
125 return &value_;
126 }
127
128 [[nodiscard]] absl::StatusOr<Bytes> Encode() const override;
129
130 [[nodiscard]] size_t ApproxBytes() const override {
131 // A stand-in, not a measurement: this is called for buffer accounting on
132 // every put, and encoding the value to find its size would be the cost the
133 // whole mechanism exists to avoid.
134 return sizeof(T) + 64;
135 }
136
137 private:
138 const T value_;
139 const std::string tag_;
140 const std::string mimetype_;
141 const std::shared_ptr<const SerializationRegistry> registry_;
142};
143
151template <typename T>
152std::optional<T> TryTakeObject(const Chunk& chunk, std::string_view tag) {
153 if (chunk.object == nullptr || chunk.object->tag() != tag) {
154 return std::nullopt;
155 }
156 // Sound because the tags matched, and tags are unique per type across four
157 // languages by construction. This is the only cast of address() anywhere.
158 return *static_cast<const T*>(chunk.object->address());
159}
160
162 public:
168 explicit SerializationRegistry(bool register_defaults = false);
170
173
175 template <typename T>
176 using Serializer = std::function<absl::StatusOr<Chunk>(const T&)>;
177
179 template <typename T>
180 using Deserializer = std::function<absl::StatusOr<T>(const Chunk&)>;
181
191 template <typename T>
192 absl::Status RegisterSerializer(std::string type_name,
193 const std::string& mimetype,
194 Serializer<T> serializer) {
195 if (!serializer) {
196 return absl::InvalidArgumentError("serializer must be callable");
197 }
198 return RegisterSerializerErased(
199 typeid(T), std::move(type_name), mimetype,
200 [serializer = std::move(serializer)](
201 const void* absl_nonnull value) -> absl::StatusOr<Chunk> {
202 if (value == nullptr) {
203 return absl::InvalidArgumentError("serializer value is null");
204 }
205 // Guarded here, in the translation unit that registered the codec,
206 // because that is the one that owns it: if the codec can throw, this
207 // instantiation was compiled with exceptions and catches it.
208 absl::StatusOr<Chunk> chunk;
209 absl::Status raised = exception_guard::Attempt(
210 [&] { chunk = serializer(*static_cast<const T*>(value)); },
211 "serializer");
212 if (!raised.ok()) {
213 return raised;
214 }
215 return chunk;
216 });
217 }
218
228 template <typename T>
229 absl::Status RegisterDeserializer(std::string type_name,
230 const std::string& mimetype,
231 Deserializer<T> deserializer) {
232 if (!deserializer) {
233 return absl::InvalidArgumentError("deserializer must be callable");
234 }
235 return RegisterDeserializerErased(
236 typeid(T), std::move(type_name), mimetype,
237 [deserializer = std::move(deserializer)](
238 const Chunk& chunk) -> absl::StatusOr<std::any> {
239 // See RegisterSerializer above for why the guard sits here.
240 absl::StatusOr<T> result;
241 absl::Status raised = exception_guard::Attempt(
242 [&] { result = deserializer(chunk); }, "deserializer");
243 if (!raised.ok()) {
244 return raised;
245 }
246 if (!result.ok()) {
247 return result.status();
248 }
249 return std::any(std::move(*result));
250 });
251 }
252
267 template <typename T>
268 absl::Status Register(const std::string& type_name,
269 const std::string& mimetype, Serializer<T> serializer,
270 Deserializer<T> deserializer) {
271 ABSL_RETURN_IF_ERROR(
272 RegisterSerializer<T>(type_name, mimetype, std::move(serializer)));
273 absl::Status status =
274 RegisterDeserializer<T>(type_name, mimetype, std::move(deserializer));
275 if (!status.ok()) {
276 RemoveSerializer(typeid(T), type_name, mimetype);
277 return status;
278 }
279 return absl::OkStatus();
280 }
281
290 template <typename T>
291 absl::StatusOr<Chunk> ToChunk(const T& value,
292 std::string_view mimetype = {}) const {
293 return ToChunkErased(typeid(T), &value, mimetype);
294 }
295
307 template <typename T>
308 absl::StatusOr<T> FromChunk(
309 const Chunk& chunk,
310 const std::vector<std::string>& mimetype_patterns = {}) const {
311 ABSL_ASSIGN_OR_RETURN(std::any result,
312 FromChunkErased(chunk, typeid(T), mimetype_patterns));
313 // The pointer form, which reports a mismatch by returning null rather than
314 // by raising.
315 T* absl_nullable value = std::any_cast<T>(&result);
316 if (value == nullptr) {
317 return absl::InternalError(
318 absl::StrCat("A deserializer produced ", result.type().name(),
319 ", which is not the requested ", typeid(T).name()));
320 }
321 return std::move(*value);
322 }
323
325 absl::Status RegisterDefaults();
327 [[nodiscard]] size_t serializer_count() const;
329 [[nodiscard]] size_t deserializer_count() const;
330
331 private:
332 using ErasedSerializer =
333 std::function<absl::StatusOr<Chunk>(const void* absl_nonnull value)>;
334 using ErasedDeserializer =
335 std::function<absl::StatusOr<std::any>(const Chunk& chunk)>;
336
337 struct SerializerRegistration;
338 struct DeserializerRegistration;
339 struct Impl;
340
341 static constexpr size_t kImplSize = 192;
342 static constexpr size_t kImplAlignment = alignof(std::max_align_t);
343
344 Impl* absl_nonnull GetImpl();
345 [[nodiscard]] const Impl* absl_nonnull GetImpl() const;
346
347 absl::Status RegisterSerializerErased(std::type_index type,
348 std::string type_name,
349 const std::string& mimetype,
350 ErasedSerializer serializer);
351 absl::Status RegisterDeserializerErased(std::type_index type,
352 std::string type_name,
353 const std::string& mimetype,
354 ErasedDeserializer deserializer);
355 void RemoveSerializer(std::type_index type, std::string_view type_name,
356 std::string_view mimetype);
357
358 absl::StatusOr<Chunk> ToChunkErased(std::type_index type,
359 const void* absl_nonnull value,
360 std::string_view mimetype) const;
361 absl::StatusOr<std::any> FromChunkErased(
362 const Chunk& chunk, std::type_index requested_type,
363 const std::vector<std::string>& mimetype_patterns) const;
364
365 alignas(kImplAlignment) std::byte impl_[kImplSize];
366};
367
369SerializationRegistry& GlobalSerializationRegistry();
370
371template <typename T>
372absl::StatusOr<Bytes> TypedChunkObject<T>::Encode() const {
373 if (registry_ == nullptr) {
374 return absl::FailedPreconditionError(
375 absl::StrCat("a chunk carrying a ", tag_,
376 " was asked for its bytes, but no serialization registry "
377 "came with it"));
378 }
379 ABSL_ASSIGN_OR_RETURN(Chunk encoded,
380 registry_->ToChunk<T>(value_, mimetype_));
381 return std::move(encoded.data);
382}
383
394template <typename T>
395Chunk MakeChunkObject(T value, std::string tag, std::string mimetype,
396 std::shared_ptr<const SerializationRegistry> registry) {
397 Chunk chunk;
398 // The mimetype is set even though the bytes are not: it is what selects a
399 // codec, what a `| mime "text/*"` stage filters on, and what tells a reader
400 // what it is about to get.
402 chunk.object = std::make_shared<const TypedChunkObject<T>>(
403 std::move(value), std::move(tag), std::move(mimetype),
404 std::move(registry));
405 return chunk;
406}
407
408} // namespace a11::data
409
410#endif // A11_DATA_SERIALIZATION_H_
An in-process value a chunk may carry instead of its encoded bytes.
Definition types.h:146
Definition serialization.h:161
absl::Status RegisterDefaults()
Installs the standard JSON and MessagePack codecs.
Definition serialization.cc:493
size_t serializer_count() const
Number of registered serializers.
Definition serialization.cc:512
std::function< absl::StatusOr< Chunk >(const T &)> Serializer
Callable turning a T into a Chunk.
Definition serialization.h:176
SerializationRegistry & operator=(const SerializationRegistry &)=delete
std::function< absl::StatusOr< T >(const Chunk &)> Deserializer
Callable reconstructing a T from a Chunk.
Definition serialization.h:180
absl::StatusOr< Chunk > ToChunk(const T &value, std::string_view mimetype={}) const
Serializes value into a Chunk.
Definition serialization.h:291
absl::Status RegisterSerializer(std::string type_name, const std::string &mimetype, Serializer< T > serializer)
Registers a serializer for type T and an exact media type.
Definition serialization.h:192
size_t deserializer_count() const
Number of registered deserializers.
Definition serialization.cc:518
absl::Status RegisterDeserializer(std::string type_name, const std::string &mimetype, Deserializer< T > deserializer)
Registers a deserializer for type T and an exact media type.
Definition serialization.h:229
~SerializationRegistry()
Definition serialization.cc:275
absl::Status Register(const std::string &type_name, const std::string &mimetype, Serializer< T > serializer, Deserializer< T > deserializer)
Atomically registers a serializer/deserializer pair for T.
Definition serialization.h:268
absl::StatusOr< T > FromChunk(const Chunk &chunk, const std::vector< std::string > &mimetype_patterns={}) const
Deserializes chunk into a value of type T.
Definition serialization.h:308
SerializationRegistry(const SerializationRegistry &)=delete
The local fast path: a value in a chunk, encoded only if needed.
Definition serialization.h:111
size_t ApproxBytes() const override
A size estimate that does not encode anything.
Definition serialization.h:130
std::string_view tag() const override
The serialisation tag of the held type.
Definition serialization.h:120
TypedChunkObject(T value, std::string tag, std::string mimetype, std::shared_ptr< const SerializationRegistry > registry)
Definition serialization.h:113
std::string_view mimetype() const override
The mimetype the value would be encoded as.
Definition serialization.h:122
absl::StatusOr< Bytes > Encode() const override
Encodes the value, for whoever needs bytes after all.
Definition serialization.h:372
const void *absl_nonnull address() const override
The address of the held value.
Definition serialization.h:124
std::string value
Definition discover.cc:114
Turns what a caller's callable throws into a Status, at the boundary.
std::string type
The type and what follows it: string required, list[a11.Chunk] stream.
Definition format.cc:51
Definition json.cc:35
constexpr std::string_view kBytesMimetype
Media type of opaque bytes carried as themselves.
Definition serialization.h:83
constexpr std::string_view kJsonMimetype
Media type of the built-in JSON codec.
Definition serialization.h:62
constexpr std::string_view kTextMimetype
Media type of UTF-8 text carried as itself.
Definition serialization.h:73
SerializationRegistry & GlobalSerializationRegistry()
Returns the process-wide registry (defaults pre-installed).
Definition serialization.cc:524
constexpr std::string_view kMsgpackMimetype
Media type of the built-in MessagePack codec.
Definition serialization.h:64
Chunk MakeChunkObject(T value, std::string tag, std::string mimetype, std::shared_ptr< const SerializationRegistry > registry)
Builds a chunk carrying value, with no bytes produced.
Definition serialization.h:395
std::optional< T > TryTakeObject(const Chunk &chunk, std::string_view tag)
Takes the value out of chunk, if it is carrying one of type T.
Definition serialization.h:152
absl::Status Attempt(Callable &&callable, std::string_view what)
Runs callable, reporting anything it throws as a Status.
Definition exception_guard.h:114
std::string mimetype
Definition sqlite_chunk_store.cc:191
Descriptive metadata attached to a a11::data::Chunk.
Definition types.h:74
std::string mimetype
Media type of the chunk payload.
Definition types.h:75
A unit of data: bytes plus optional descriptive metadata.
Definition types.h:185
std::optional< ChunkMetadata > metadata
Optional payload metadata.
Definition types.h:186
Bytes data
Inline byte payload.
Definition types.h:188
std::shared_ptr< const ChunkObject > object
The value this chunk stands for, when its bytes have not been produced.
Definition types.h:196
A11's core wire value types: chunks, node fragments and messages.