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}