1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use TokenStream;
/// A derive macro that generates a `field_type()` associated function for a
/// struct.
///
/// The generated method returns a `FieldType::Map` whose keys are the
/// serialized field names and whose values are the inferred or explicitly
/// overridden [`anda_db_schema::FieldType`] for each field. It is the
/// building block used by `AndaDBSchema` for nested user-defined types.
///
/// # Attributes
///
/// - `#[field_type = "TypeName"]` -- override the inferred type. The string
/// accepts a small DSL: primitives (`Bytes`, `Text`, `U64`, ...), as well
/// as `Array<T>`, `Option<T>`, `Map<String, T>`, `Map<Text, T>` and
/// `Map<Bytes, T>` (where `T` is itself any supported type, including
/// nested wrappers).
/// - `#[serde(rename = "name")]` / `#[serde(rename_all = "...")]` -- the
/// generated map follows the *serialized* field names, so field-level
/// renames and container-level case rules (e.g. `camelCase`) are both
/// honoured, with the same precedence as serde itself.
/// - `#[serde(skip)]` / `#[serde(skip_serializing)]` -- the field never
/// appears in serialized output and is therefore excluded from the
/// generated map.
/// - `#[serde(flatten)]` and `#[serde(transparent)]` are rejected with a
/// compile error: they change the serialized shape in ways a per-field
/// schema cannot describe.
/// - Other serde options are ignored. Note that `#[serde(with = "...")]` /
/// `serialize_with` may change the serialized shape -- combine them with
/// an explicit `#[field_type = "..."]` override when they do.
///
/// # Type inference
///
/// When `#[field_type]` is absent, the type is inferred from the Rust type:
///
/// - `String` / `&str` -> `Text`
/// - integers / floats / `bool` -> their numeric `FieldType`
/// - `Vec<u8>`, `[u8; N]`, `Bytes`, `ByteBuf`, `ByteArray`, `*B64` -> `Bytes`
/// - `Vec<bf16>`, `[bf16; N]` -> `Vector`
/// - `Vec<T>` / `HashSet<T>` / `BTreeSet<T>` -> `Array(T)`
/// - `HashMap<K, V>` / `BTreeMap<K, V>` (string- or bytes-like key) -> `Map`
/// - `Option<T>` -> `Option(T)`
/// - `Box<T>` / `Arc<T>` / `Rc<T>` / `Cow<'_, T>` -> the inner `T` (serde
/// serializes these wrappers transparently)
/// - `serde_json::Value`, `Json` -> `Json`
/// - any other path -> the type's `field_type()` function (so the type must
/// itself derive `FieldTyped`)
///
/// Standalone `bf16` values are intentionally rejected -- vectors, not
/// scalars, are the supported abstraction.
///
/// All diagnostics are spanned at the offending field, type or attribute.
///
/// # Example
///
/// ```rust,ignore
/// use anda_db_schema::{FieldType, FieldTyped};
/// use ic_auth_types::Xid;
///
/// #[derive(FieldTyped)]
/// struct User {
/// #[field_type = "Bytes"]
/// id: Xid,
/// name: String,
/// age: u32,
/// }
/// ```
/// A derive macro that generates a `schema()` associated function for a
/// struct.
///
/// The generated method builds a fully-formed [`anda_db_schema::Schema`]
/// using `Schema::builder()`, with one `FieldEntry` per serialized field
/// (excluding `_id`, which is provided by the builder itself).
///
/// # Attributes
///
/// - `#[field_type = "TypeName"]` -- override the inferred type. Same DSL as
/// for `FieldTyped`; see that macro's docs for the full grammar.
/// - `#[unique]` -- mark the field as having a unique constraint
/// (`FieldEntry::with_unique`).
/// - `#[serde(rename = "name")]` / `#[serde(rename_all = "...")]` -- the
/// schema follows the *serialized* field names, so field-level renames and
/// container-level case rules (e.g. `camelCase`) are both honoured, with
/// the same precedence as serde itself.
/// - `#[serde(skip)]` / `#[serde(skip_serializing)]` -- the field never
/// appears in serialized output and is therefore excluded from the schema.
/// - `#[serde(flatten)]` and `#[serde(transparent)]` are rejected with a
/// compile error: they change the serialized shape in ways a per-field
/// schema cannot describe.
/// - Doc comments (`/// ...`) are concatenated and used as the field
/// description (`FieldEntry::with_description`).
///
/// Two fields that would serialize under the same schema name (e.g. via
/// renames) are rejected at compile time.
///
/// # Special fields
///
/// The `_id: u64` primary-key column is injected by the schema builder
/// automatically, so declaring it on the struct is optional. When declared,
/// it must be of type `u64` and keep serializing as `"_id"` (beware
/// `rename_all` rules: add `#[serde(rename = "_id")]` if needed); it is
/// validated at compile time and skipped during code generation.
///
/// # Example
///
/// ```rust,ignore
/// use anda_db_schema::{FieldEntry, FieldType, Schema, SchemaError};
/// use anda_db_derive::AndaDBSchema;
///
/// #[derive(AndaDBSchema)]
/// struct User {
/// /// AndaDB-managed primary key
/// _id: u64,
/// /// User's unique identifier
/// #[field_type = "Bytes"]
/// #[unique]
/// id: [u8; 12],
/// /// User's display name
/// name: String,
/// /// User's age in years
/// age: Option<u32>,
/// /// Whether the user account is active
/// active: bool,
/// /// User tags for categorization
/// tags: Vec<String>,
/// }
/// ```
///
/// Expands to:
///
/// ```rust,ignore
/// impl User {
/// pub fn schema() -> Result<Schema, SchemaError> {
/// // ... generated schema construction code
/// }
/// }
/// ```