Skip to main content

Crate obj_derive

Crate obj_derive 

Source
Expand description

obj-derive — procedural macros for obj.

This crate provides #[derive(obj::Document)], which emits the obj_core::Document implementation for a user struct. The derive is intentionally small — it fills in the trait’s associated constants (COLLECTION, VERSION) from optional #[obj(...)] attributes and emits an indexes() override whenever any field carries an #[obj(index ...)] attribute.

§Supported attributes

Struct-level (#[obj(...)] directly above the struct keyword):

  • version = N (integer ≥ 0) — sets Document::VERSION.
  • collection = "name" (non-empty string literal) — sets Document::COLLECTION.

Multiple #[obj(...)] attributes compose; the same scalar key (version, collection) declared twice is a compile error.

Struct-level composite (one or more occurrences compose, each adding one Composite IndexSpec):

  • index_composite(fields = ("a", "b"), name = "by_a_b") — emit a Composite IndexSpec spanning the listed fields. name defaults to the field names joined with __. The referenced fields must exist on the struct; fewer than two is a compile error.

    This is a deliberate rename from the syntax shown in design.md (#[obj(index = ("a", "b"))]). The tuple-on-the- right form is ambiguous to parse alongside the field-level index = unique / index = each keyword variants — putting composite indexes under their own index_composite key keeps each attribute’s grammar locally unambiguous. The format-docs reflect the actual syntax; design.md prose will be aligned in a future doc pass.

Field-level (#[obj(...)] on a struct field):

  • index — emit a Standard IndexSpec for this field.
  • index = unique — emit a Unique IndexSpec for this field.
  • index = each — emit an Each IndexSpec for this field. The field type must syntactically be Vec<...> — otherwise the derive errors at compile time.
  • name = "..." — alongside any index = ..., overrides the default index name (which is the field name).

Struct-level historical schema registry (M10 #82):

  • history(v1 = OldType1, v2 = OldType2) — emit a Document::historical_schemas() override that lifts each version into a (version, DynamicSchema) pair via <OldType as ::obj::Schema>::schema(). The keys (v1, v2, …) must be vN for integer N ≥ 1; the values are arbitrary type paths. Each named type must implement ::obj::Schema (hand-impls are accepted; the derive auto-implements Schema for types that opt in via #[obj(history(...))] or #[obj(schema)]). Entries are emitted in ascending version order.
  • schema — explicitly opt the current type into a derived Schema impl WITHOUT declaring any history. Useful when the type is referenced from a future version’s history(...).

When either history(...) or schema is declared, the derive emits a companion impl ::obj::Schema block whose schema() body maps each field to a DynamicSchema variant. Scalar primitives (bool, u*, i*, f*, String) map directly; Vec<T> maps to DynamicSchema::seq(<T as Schema>::schema()); anything else delegates via <T as ::obj::Schema>::schema(), which fails to compile if T lacks a Schema impl.

§Serde requirements

The derive does NOT emit serde::Serialize or serde::Deserialize for you. Users still write #[derive(serde::Serialize, serde::Deserialize)] on the struct alongside #[derive(obj::Document)].

§Power-of-ten posture

  • Rule 4 — every function in this crate is ≤ 60 lines.
  • Rule 7 — every fallible path returns syn::Result<...>; unwrap/expect appear only on infallible primitives.
  • Rule 9 — generated code is minimal and inspectable. Every emitted item is prefixed by a // auto-generated by #[derive(Document)] marker so cargo expand output is easy to spot.

Derive Macros§

Document
Derive macro for obj::Document.