Skip to content

Commit 57dd469

Browse files
committed
Add more serde vs. surreal examples
1 parent d40096e commit 57dd469

1 file changed

Lines changed: 142 additions & 4 deletions

File tree

src/content/reference/rust/concepts/rust-after-3.0.mdx

Lines changed: 142 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -492,13 +492,35 @@ async fn main() {
492492

493493
## Enum and struct attributes
494494

495-
The `SurrealValue` trait can be customised using the `surreal` attribute. These are used in a similar way to [Serde attributes](https://serde.rs/attributes.html), though created in order to interact with SurrealQL in particular and thus often somewhat different.
495+
The `SurrealValue` derive uses its own `#[surreal(...)]` attribute that is inspired by [Serde](https://serde.rs/attributes.html) (familiar names, similar enum tagging ideas), but it is **not** Serde and not an inheritance of `#[serde(...)]` attributes.
496496

497-
The currently available attributes are as follows.
497+
In SurrealDB 2.x, many SDK types used Serde’s `Serialize` / `Deserialize` derives. In 3.0, conversion goes through `SurrealValue` and a dedicated derive macro. If you previously reached for Serde docs to customise field names or flattening, use the matching `#[surreal(...)]` form below instead.
498+
499+
### Relationship to Serde
500+
501+
| Idea | Serde | `SurrealValue` (`#[surreal(...)]`) |
502+
| --- | --- | --- |
503+
| Rename one field / variant | `rename = "..."` | `rename = "..."` |
504+
| Rename all fields / variants | `rename_all = "..."` | `rename_all = "..."` (same case strings as Serde) |
505+
| Flatten nested object | `flatten` | `flatten` |
506+
| Enum tagging | `tag` / `content` / `untagged` | `tag` / `content` / `untagged` |
507+
| Missing field default | `default` / `default = "path"` | `default` / `default = "path"` (also on the container) |
508+
| Catch-all unit variant | `other` | `other` |
509+
| Skip a field | `skip`, `skip_serializing`, `skip_serializing_if` | Not supported on ordinary fields |
510+
| Conditionally omit enum payload | (use field `skip_serializing_if`) | `skip_content` / `skip_content_if = "..."` on tagged enums |
511+
| Serde-only types | (n/a) | `wrap` for `Serialize + Deserialize` types that do not implement `SurrealValue` |
512+
| Tuple struct as array | (n/a) | `tuple` |
513+
| Literal substitute for a unit | (n/a) | `value = ...` |
514+
515+
Supported `rename_all` values match Serde’s usual set (`lowercase`, `UPPERCASE`, `PascalCase`, `camelCase`, `snake_case`, `SCREAMING_SNAKE_CASE`, `kebab-case`, `SCREAMING-KEBAB-CASE`). An explicit `rename` on a field or variant wins over the container `rename_all`.
516+
517+
`#[surreal(uppercase)]` and `#[surreal(lowercase)]` on enums are legacy aliases for `rename_all = "UPPERCASE"` and `rename_all = "lowercase"`. Prefer `rename_all` in new code. Do not combine them with `rename_all` on the same enum.
518+
519+
The attributes below are the ones the derive currently recognises.
498520

499521
### `surreal(default)`
500522

501-
The `surreal(default)` attribute is used to default to certain values when these values are not present when deserializing. The `Default` trait is required to use this.
523+
The `surreal(default)` attribute fills in values when fields are missing during deserialization. On a struct container, missing fields come from the type’s `Default` implementation. On a single field, use `#[surreal(default)]` for `<T as Default>::default()`, or `#[surreal(default = "path")]` for a custom function path.
502524

503525
```rust
504526
use surrealdb::engine::any::connect;
@@ -597,9 +619,65 @@ Before rename: { num: 555 }
597619
After rename: { user_num: 555 }
598620
```
599621

622+
### `surreal(rename_all)`
623+
624+
Apply a case transform to every field name on a struct, or every variant name on an enum, unless a field or variant sets its own `rename`.
625+
626+
```rust
627+
use surrealdb_types::{SurrealValue, ToSql};
628+
629+
#[derive(SurrealValue)]
630+
#[surreal(rename_all = "camelCase")]
631+
struct UserProfile {
632+
full_name: String,
633+
years_old: i64,
634+
}
635+
636+
fn main() {
637+
let profile = UserProfile {
638+
full_name: "Ada".into(),
639+
years_old: 36,
640+
};
641+
// { fullName: 'Ada', yearsOld: 36 }
642+
println!("{}", profile.into_value().to_sql());
643+
}
644+
```
645+
646+
### `surreal(flatten)`
647+
648+
Merge a nested object’s fields into the parent object instead of nesting them under one key. This is the Surreal equivalent of Serde’s `#[serde(flatten)]`.
649+
650+
```rust
651+
use surrealdb_types::{SurrealValue, ToSql};
652+
653+
#[derive(SurrealValue)]
654+
struct Coords {
655+
x: i64,
656+
y: i64,
657+
}
658+
659+
#[derive(SurrealValue)]
660+
struct Point {
661+
name: String,
662+
#[surreal(flatten)]
663+
coords: Coords,
664+
}
665+
666+
fn main() {
667+
let point = Point {
668+
name: "origin".into(),
669+
coords: Coords { x: 0, y: 0 },
670+
};
671+
// { name: 'origin', x: 0, y: 0 }
672+
println!("{}", point.into_value().to_sql());
673+
}
674+
```
675+
676+
`flatten` cannot be combined with `rename` on the same field: there is no single key left to rename.
677+
600678
### `surreal(uppercase)` and `surreal(lowercase)`
601679

602-
These two attributes are similar to `surreal(rename)` except that they apply to the casing of enum variants.
680+
These two attributes are legacy aliases for `rename_all = "UPPERCASE"` and `rename_all = "lowercase"` on enums. Prefer `rename_all` when writing new types.
603681

604682
```rust
605683
use surrealdb_types::{SurrealValue, ToSql};
@@ -779,6 +857,66 @@ Before content: { Debug: 'User1' }
779857
After content: { log_level: 'Debug', user: 'User1' }
780858
```
781859

860+
### `surreal(skip_content)` and `surreal(skip_content_if)`
861+
862+
On enums that use adjacent tagging (`tag` plus `content`), these control whether the content field is written (and whether it may be absent when reading).
863+
864+
They are the closest Surreal analogues to Serde’s `skip_serializing_if`, but they apply to the enum content field, not to arbitrary struct fields.
865+
866+
| Attribute | Effect |
867+
| --- | --- |
868+
| `skip_content` | Never emit the content field for that enum or variant |
869+
| `skip_content_if = "path"` | Emit content only when the predicate returns false (for example `Value::is_empty`) |
870+
871+
```rust
872+
use surrealdb_types::{SurrealValue, ToSql, Value};
873+
874+
#[derive(SurrealValue)]
875+
#[surreal(tag = "kind", content = "details", skip_content_if = "Value::is_empty")]
876+
enum ApiStatus {
877+
Ok,
878+
Error { message: String },
879+
}
880+
881+
fn main() {
882+
// Unit variant: content omitted when empty
883+
// { kind: 'Ok' }
884+
println!("{}", ApiStatus::Ok.into_value().to_sql());
885+
886+
// Named variant: content present when there is data
887+
// { kind: 'Error', details: { message: 'boom' } }
888+
println!(
889+
"{}",
890+
ApiStatus::Error {
891+
message: "boom".into()
892+
}
893+
.into_value()
894+
.to_sql()
895+
);
896+
}
897+
```
898+
899+
You can also put `skip_content` or `skip_content_if` on individual variants. They only apply to enums that already declare a `tag` (with or without `content`).
900+
901+
### `surreal(other)`
902+
903+
On a **unit** variant, `other` marks a deserialization catch-all: if no other variant matches, that variant is chosen instead of returning an error. At most one variant per enum should use it. It cannot be combined with `rename` or `value` on the same variant.
904+
905+
```rust
906+
use surrealdb_types::SurrealValue;
907+
908+
#[derive(Debug, PartialEq, SurrealValue)]
909+
#[surreal(untagged)]
910+
enum WireFlag {
911+
#[surreal(value = true)]
912+
On,
913+
#[surreal(value = false)]
914+
Off,
915+
#[surreal(other)]
916+
Unknown,
917+
}
918+
```
919+
782920
### `surreal(value)`
783921

784922
This attribute can be used on the fields of an enum marked with `surreal(untagged)` to give it a substitute value. The value that follows this attribute can be a NONE, NULL, bool, string, int, or float.

0 commit comments

Comments
 (0)