Skip to main content

cratestack_core/rpc/
inputs.rs

1//! RPC model-CRUD input envelopes (cratestack#490).
2//!
3//! Moved here from `cratestack-axum::rpc::inputs`, which is where they lived
4//! until this fix — an oversight relative to the parent module's opening
5//! paragraph ("They live in `cratestack-core` so clients can depend on a
6//! single source of truth without pulling in axum") and relative to the
7//! sibling wire shapes in `super` (`RpcErrorBody`/`RpcRequest`/`RpcResponseFrame`),
8//! which already made that move. It went unnoticed until `cratestack-client`
9//! (a facade with genuinely no `cratestack-axum` dependency) tried to compile
10//! a `transport rpc` schema with model CRUD and hit "cannot find
11//! `RpcListInput` in `rpc`" — every previous consumer of
12//! `::cratestack::rpc::RpcListInput` (`cratestack-pg`, `cratestack-api`,
13//! `cratestack-sqlite`) carries `cratestack-axum` regardless, so the wrong
14//! source crate was invisible until a facade without it existed to surface
15//! it. `cratestack-axum::rpc` re-exports these same three types verbatim
16//! (`pub use cratestack_core::rpc::{RpcListInput, RpcListPredicate,
17//! RpcPkInput, RpcUpdateInput};`), so `cratestack-pg`/`cratestack-api`/
18//! `cratestack-sqlite` see no behavior change — same names, same shapes, same
19//! wire format, only the defining crate moved.
20//! ---------------------------------------------------------------------------
21
22use serde::{Deserialize, Serialize};
23/// RPC input for `model.<X>.get` and `model.<X>.delete`. The PK type is
24/// instantiated per-model at the macro emission site.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct RpcPkInput<Pk> {
27    pub id: Pk,
28}
29
30/// RPC input for `model.<X>.update`. Parameterized on both the PK type
31/// and the model's concrete `Update<X>Input` so the patch decodes
32/// straight to its real type — round-tripping through
33/// `serde_json::Value` would corrupt CBOR `Option::None` values (which
34/// `minicbor-serde` encodes as `0xf6` simple-null but `serde_json::Value`
35/// encodes as the CBOR empty-array marker; see comments in
36/// `cratestack-codec-cbor`). The dispatcher re-encodes `patch` through
37/// the same codec before handing it to the existing update handler.
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct RpcUpdateInput<Pk, Patch> {
40    pub id: Pk,
41    pub patch: Patch,
42}
43
44/// Single arbitrary key/value predicate inside [`RpcListInput::filters`].
45/// Models the REST URL form's "anything that isn't a reserved keyword is a
46/// predicate" rule (e.g. `?published=true&authorId=42`).
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct RpcListPredicate {
49    pub key: String,
50    pub value: String,
51}
52
53/// RPC input for `model.<X>.list`. Mirrors the REST URL query 1:1 — every
54/// optional field maps to a query param of the same name, predicates carry
55/// arbitrary `(key, value)` pairs that aren't reserved keywords.
56#[derive(Debug, Clone, Default, Serialize, Deserialize)]
57pub struct RpcListInput {
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub limit: Option<i64>,
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub offset: Option<i64>,
62    /// Selection fields (`?fields=a,b,c`).
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub fields: Option<Vec<String>>,
65    /// Included relations (`?include=author,comments`).
66    #[serde(default, skip_serializing_if = "Option::is_none")]
67    pub include: Option<Vec<String>>,
68    /// Fields per included relation (`?includeFields[author]=id,name`).
69    #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
70    pub include_fields: std::collections::BTreeMap<String, Vec<String>>,
71    /// Order expression (`?sort=name asc`).
72    #[serde(default, skip_serializing_if = "Option::is_none")]
73    pub sort: Option<String>,
74    /// Top-level filter expression (`?where=...`).
75    #[serde(default, skip_serializing_if = "Option::is_none", rename = "where")]
76    pub where_expr: Option<String>,
77    /// Disjunction filter (`?or=...`).
78    #[serde(default, skip_serializing_if = "Option::is_none")]
79    pub or: Option<String>,
80    /// Arbitrary `key=value` predicates (anything not in the reserved set).
81    #[serde(default, skip_serializing_if = "Vec::is_empty")]
82    pub filters: Vec<RpcListPredicate>,
83}