Skip to content

Commit 33dd75d

Browse files
16bit-ykikoclaude
andcommitted
feat(serde): add virtual schema core types and tests
Introduce compile-time virtual schema infrastructure for the serde framework: - field_info.h: type_kind enum, type_info inheritance hierarchy, field_info struct - field_slot.h: type_list, field_slot compile-time descriptors - virtual_schema.h: schema building with full attribute resolution (skip, flatten, rename, alias, rename_all, default_value, literal, deny_unknown, as, with, enum_string, tagged, skip_if) Tests split into 6 files covering kind_of, type_info, schema attrs, behavior attrs, rename_all policies, and field slots (31 test cases). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e7e724d commit 33dd75d

File tree

10 files changed

+2256
-0
lines changed

10 files changed

+2256
-0
lines changed
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <cstdint>
5+
#include <memory>
6+
#include <optional>
7+
#include <ranges>
8+
#include <span>
9+
#include <string_view>
10+
#include <type_traits>
11+
#include <variant>
12+
13+
#include "eventide/common/meta.h"
14+
#include "eventide/common/ranges.h"
15+
#include "eventide/reflection/struct.h"
16+
#include "eventide/serde/serde/annotation.h"
17+
#include "eventide/serde/serde/traits.h"
18+
19+
namespace eventide::serde::schema {
20+
21+
struct field_info;
22+
23+
// ---------------------------------------------------------------------------
24+
// type_kind — unified type classification
25+
// ---------------------------------------------------------------------------
26+
27+
/// Unified type classification replacing the old type_kind + scalar_kind pair.
28+
/// Scalars use fine-grained values; compound types use broad categories.
29+
enum class type_kind : std::uint8_t {
30+
// Scalars (fine-grained)
31+
null = 0,
32+
boolean,
33+
int8,
34+
int16,
35+
int32,
36+
int64,
37+
uint8,
38+
uint16,
39+
uint32,
40+
uint64,
41+
float32,
42+
float64,
43+
character,
44+
string,
45+
bytes,
46+
enumeration,
47+
48+
// Compound types
49+
array,
50+
set,
51+
map,
52+
tuple,
53+
structure,
54+
variant,
55+
optional,
56+
pointer,
57+
58+
// Special
59+
unknown = 254, // traits hook / wire representation unknowable
60+
any = 255, // future: runtime dynamic type
61+
};
62+
63+
// ---------------------------------------------------------------------------
64+
// tag_mode — variant tagging strategy
65+
// ---------------------------------------------------------------------------
66+
67+
enum class tag_mode : std::uint8_t {
68+
none,
69+
external,
70+
internal,
71+
adjacent,
72+
};
73+
74+
// ---------------------------------------------------------------------------
75+
// type_info — base class and subtypes
76+
// ---------------------------------------------------------------------------
77+
78+
/// Base type descriptor. Scalars use this directly; compound types downcast
79+
/// to the appropriate subclass via `kind`.
80+
struct type_info {
81+
type_kind kind;
82+
std::string_view type_name;
83+
84+
constexpr bool is_integer() const {
85+
return kind >= type_kind::int8 && kind <= type_kind::uint64;
86+
}
87+
88+
constexpr bool is_signed_integer() const {
89+
return kind >= type_kind::int8 && kind <= type_kind::int64;
90+
}
91+
92+
constexpr bool is_unsigned_integer() const {
93+
return kind >= type_kind::uint8 && kind <= type_kind::uint64;
94+
}
95+
96+
constexpr bool is_floating() const {
97+
return kind == type_kind::float32 || kind == type_kind::float64;
98+
}
99+
100+
constexpr bool is_numeric() const { return is_integer() || is_floating(); }
101+
102+
constexpr bool is_scalar() const { return kind <= type_kind::enumeration; }
103+
};
104+
105+
/// array / set
106+
struct array_type_info : type_info {
107+
const type_info* element;
108+
};
109+
110+
/// map
111+
struct map_type_info : type_info {
112+
const type_info* key;
113+
const type_info* value;
114+
};
115+
116+
/// struct — fields is a span over the struct's field_info array
117+
struct struct_type_info : type_info {
118+
std::span<const field_info> fields;
119+
};
120+
121+
/// tuple / pair
122+
struct tuple_type_info : type_info {
123+
std::span<const type_info* const> elements;
124+
};
125+
126+
/// variant — includes tagging metadata
127+
struct variant_type_info : type_info {
128+
std::span<const type_info* const> alternatives;
129+
tag_mode tagging = tag_mode::none;
130+
std::string_view tag_field;
131+
std::string_view content_field;
132+
std::span<const std::string_view> alt_names;
133+
};
134+
135+
/// optional / smart_ptr — wraps the inner type
136+
struct optional_type_info : type_info {
137+
const type_info* inner;
138+
};
139+
140+
// ---------------------------------------------------------------------------
141+
// field_info
142+
// ---------------------------------------------------------------------------
143+
144+
struct field_info {
145+
std::string_view name; // canonical wire name
146+
std::span<const std::string_view> aliases; // alias names
147+
std::size_t offset; // byte offset from struct start
148+
std::size_t physical_index; // original C++ struct field index
149+
const type_info* type; // recursive type descriptor (wire view)
150+
151+
// Level 1 flags
152+
bool has_default; // schema::default_value
153+
bool is_literal; // schema::literal
154+
bool has_skip_if; // behavior::skip_if present
155+
bool has_behavior; // with/as/enum_string present
156+
};
157+
158+
// ---------------------------------------------------------------------------
159+
// kind_of<T>() — map C++ types to type_kind values
160+
// ---------------------------------------------------------------------------
161+
162+
namespace detail {
163+
164+
/// Map signed integer types to their exact-width type_kind.
165+
template <typename T>
166+
consteval type_kind signed_int_kind() {
167+
if constexpr(sizeof(T) == 1) {
168+
return type_kind::int8;
169+
} else if constexpr(sizeof(T) == 2) {
170+
return type_kind::int16;
171+
} else if constexpr(sizeof(T) == 4) {
172+
return type_kind::int32;
173+
} else {
174+
static_assert(sizeof(T) == 8);
175+
return type_kind::int64;
176+
}
177+
}
178+
179+
/// Map unsigned integer types to their exact-width type_kind.
180+
template <typename T>
181+
consteval type_kind unsigned_int_kind() {
182+
if constexpr(sizeof(T) == 1) {
183+
return type_kind::uint8;
184+
} else if constexpr(sizeof(T) == 2) {
185+
return type_kind::uint16;
186+
} else if constexpr(sizeof(T) == 4) {
187+
return type_kind::uint32;
188+
} else {
189+
static_assert(sizeof(T) == 8);
190+
return type_kind::uint64;
191+
}
192+
}
193+
194+
/// Map floating-point types to their type_kind.
195+
template <typename T>
196+
consteval type_kind floating_kind() {
197+
if constexpr(sizeof(T) <= 4) {
198+
return type_kind::float32;
199+
} else {
200+
return type_kind::float64;
201+
}
202+
}
203+
204+
} // namespace detail
205+
206+
/// Map a C++ type to its type_kind value.
207+
///
208+
/// Scalars yield fine-grained kinds (int8, float64, etc.);
209+
/// compound types yield broad categories (array, map, structure, etc.).
210+
///
211+
/// The ordering of checks mirrors the dispatch chain in serde.h:
212+
/// annotated_type -> enum -> bool -> int -> uint -> float -> char ->
213+
/// str -> bytes -> null -> optional -> pointer -> variant -> tuple ->
214+
/// range -> reflectable_class
215+
template <typename T>
216+
consteval type_kind kind_of() {
217+
using V = std::remove_cvref_t<T>;
218+
219+
// Unwrap annotation to get the underlying type
220+
if constexpr(serde::annotated_type<V>) {
221+
return kind_of<typename V::annotated_type>();
222+
}
223+
// Enum -> enumeration
224+
else if constexpr(std::is_enum_v<V>) {
225+
return type_kind::enumeration;
226+
}
227+
// Bool
228+
else if constexpr(serde::bool_like<V>) {
229+
return type_kind::boolean;
230+
}
231+
// Signed integers — size-based dispatch
232+
else if constexpr(serde::int_like<V>) {
233+
return detail::signed_int_kind<V>();
234+
}
235+
// Unsigned integers — size-based dispatch
236+
else if constexpr(serde::uint_like<V>) {
237+
return detail::unsigned_int_kind<V>();
238+
}
239+
// Floating-point
240+
else if constexpr(serde::floating_like<V>) {
241+
return detail::floating_kind<V>();
242+
}
243+
// Character
244+
else if constexpr(serde::char_like<V>) {
245+
return type_kind::character;
246+
}
247+
// String
248+
else if constexpr(serde::str_like<V>) {
249+
return type_kind::string;
250+
}
251+
// Bytes
252+
else if constexpr(serde::bytes_like<V>) {
253+
return type_kind::bytes;
254+
}
255+
// Null
256+
else if constexpr(serde::null_like<V>) {
257+
return type_kind::null;
258+
}
259+
// Optional
260+
else if constexpr(is_optional_v<V>) {
261+
return type_kind::optional;
262+
}
263+
// Smart pointers
264+
else if constexpr(is_specialization_of<std::unique_ptr, V> ||
265+
is_specialization_of<std::shared_ptr, V>) {
266+
return type_kind::pointer;
267+
}
268+
// Variant
269+
else if constexpr(is_specialization_of<std::variant, V>) {
270+
return type_kind::variant;
271+
}
272+
// Tuple / pair (before range, since some tuples might satisfy range)
273+
else if constexpr(serde::tuple_like<V>) {
274+
return type_kind::tuple;
275+
}
276+
// Range types: map / set / sequence
277+
else if constexpr(std::ranges::input_range<V>) {
278+
constexpr auto fmt = format_kind<V>;
279+
if constexpr(fmt == range_format::map) {
280+
return type_kind::map;
281+
} else if constexpr(fmt == range_format::set) {
282+
return type_kind::set;
283+
} else {
284+
return type_kind::array;
285+
}
286+
}
287+
// Reflectable struct
288+
else if constexpr(refl::reflectable_class<V>) {
289+
return type_kind::structure;
290+
}
291+
// Unknown — no matching category
292+
else {
293+
return type_kind::unknown;
294+
}
295+
}
296+
297+
} // namespace eventide::serde::schema

0 commit comments

Comments
 (0)