Type & serde conventions
SkillDev toolsConventions for adding or changing types in filen-types / filen-sdk-rs — the permissive_u64 serde helpers and their two gotchas, and the #[js_type] macro arguments including the externally-managed-serde pattern. Read before adding a field to an API type, adding a new API type, or touching a type that carries #[js_type].
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Type & serde conventions skill
What this skill tells your AI
The instructions your AI receives, as published by filenclouddienste/filen-rs in .claude/skills/types-serde-conventions/SKILL.md and read by ahel’s review.
permissive_u64 — every deserialized u64
filen-types/src/serde/number.rs defines permissive_u64, which deserializes a u64 from
a JSON integer, float, or string (via crate::conversions::{f64_to_u64, str_to_u64});
serialization just forwards to the default. The API is inconsistent about number encoding,
so this is not optional politeness — it is what keeps responses parsing.
Convention: every deserialized u64 field in filen-types (i.e. in a type deriving
Deserialize, or carrying #[js_type] without no_deser) gets:
#[serde(with = "crate::serde::number::permissive_u64")]
pub size: u64,
Two gotchas:
-
Option<u64>needsdefaultas well:#[serde(default, with = "crate::serde::number::permissive_u64_opt")] pub timestamp: Option<u64>,A custom
deserialize_withdisables serde's automatic "missing field →None" forOption, so withoutdefaulta missing field is a hard error. -
Do not swap
crate::serde::option::defaultforpermissive_u64_opton outgoingRequesttypes.option::default::serializeemitsNone → 0(T::default()), which the wire format relies on;permissive_u64_optemitsNone → null. Example:v3/dir/sizeRequest.{sharer_id, receiver_id}keepoption::default. Outgoing requests do not need permissive deserialization anyway.
Scope rule: apply only to types that derive Deserialize. Serialize-only types (e.g. the
hand-written Serialize impl in shared/out_root.rs) are skipped.
#[js_type] — one type, three platforms
filen-macros' #[js_type] generates platform-specific derives so a single type serves:
- Native — plain struct/enum with
Debug, Clone, PartialEq, Eq - WASM —
tsify::Tsify+ serde + tsify ABI annotations, all under#[cfg_attr(<wasm cond>, …)] - UniFFI —
uniffi::Record(structs) /uniffi::Enum(enums) under#[cfg_attr(feature = "uniffi", …)]
| Argument | Effect |
|---|---|
import | tsify(from_wasm_abi) — type can be passed into WASM |
export | tsify(into_wasm_abi, large_number_types_as_bigints, hashmap_as_object) — passed out |
wasm_all | WASM condition becomes all(target_family = "wasm", target_os = "unknown") (default also requires feature = "wasm-full") |
wasm_worker | WASM condition includes feature = "wasm-worker" |
no_ser / no_deser | Suppress the derive(serde::Serialize) / Deserialize inside the cfg_attr — use when serde is provided unconditionally |
no_default | Suppress the default #[derive(Debug, Clone, PartialEq, Eq)] |
tagged | Force tagged enum mode |
Key invariant: no_ser suppresses only the serde derive, not tsify(into_wasm_abi, …).
That is what makes the pattern below work.
Externally managed serde
When a type needs Serialize/Deserialize on all platforms, not just WASM, derive them
unconditionally and tell the macro to stay out of it:
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
#[js_type(import, export, wasm_all, no_ser, no_deser)]
pub struct MyType { … }
Canonical example: StringifiedClient in filen-sdk-rs/src/auth/mod.rs.
Common mistakes:
- Adding an unconditional
serde(rename_all = "camelCase")withoutno_ser, no_deser— it duplicates the macro's own attribute on WASM. (With both flags set, the macro omits it, which is why the pattern above is consistent.) - Leaving a field-level
serde(default)inside a wasmcfg_attrwhen serde is unconditional — the field attribute must be unconditional too. - Writing a parallel "serializable" workaround type (e.g. a hand-rolled
SerializableClientConfig). Never needed —no_ser, no_deserplus unconditional derives is the supported route.
Tagged variants
If struct fields carry #[js_type(tagged)], the macro emits a companion {Name}Tagged
struct for WASM with those field types replaced by their tagged equivalents, gated on the
wasm condition; UniFFI gets a type {Name}Tagged = {Name} alias. For enums with export,
the macro emits a {Name}Tagged enum for WASM serialization while the main enum takes the
uniffi::Enum derive.
Signals
- GitHub stars
- 92
- Forks
- 8
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
types-serde-conventions- Source
- github.com/filenclouddienste/filen-rs