Skip to main content

unifly_api/integration/types/
common.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use uuid::Uuid;
6
7/// Generic pagination wrapper returned by all list endpoints.
8///
9/// Items are decoded individually: a record the model cannot parse is
10/// dropped with a warning instead of failing the whole page, so one
11/// unexpected payload shape cannot blank an entire collection.
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14#[serde(bound(deserialize = "T: serde::de::DeserializeOwned"))]
15pub struct Page<T> {
16    pub offset: i64,
17    pub limit: i32,
18    pub count: i32,
19    pub total_count: i64,
20    #[serde(deserialize_with = "lenient_items")]
21    pub data: Vec<T>,
22}
23
24fn lenient_items<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
25where
26    D: serde::Deserializer<'de>,
27    T: serde::de::DeserializeOwned,
28{
29    let raw = Vec::<Value>::deserialize(deserializer)?;
30    Ok(raw
31        .into_iter()
32        .filter_map(|item| match serde_json::from_value::<T>(item) {
33            Ok(parsed) => Some(parsed),
34            Err(error) => {
35                // Log the error category only: serde_json messages can quote
36                // record field values, and controller records may carry
37                // hostnames or other data that must stay out of logs.
38                tracing::warn!(
39                    item_type = std::any::type_name::<T>(),
40                    category = ?error.classify(),
41                    "skipping list item that failed to parse"
42                );
43                None
44            }
45        })
46        .collect())
47}
48
49/// Site overview — from `GET /v1/sites`.
50#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51#[serde(rename_all = "camelCase")]
52pub struct SiteResponse {
53    pub id: Uuid,
54    pub name: String,
55    /// Used as the Session API site name (`/api/s/{internalReference}/`).
56    pub internal_reference: String,
57}
58
59/// Application info — from `GET /v1/info`.
60#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub struct ApplicationInfoResponse {
63    #[serde(flatten)]
64    pub fields: HashMap<String, Value>,
65}
66
67/// Error response returned by the Integration API.
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct ErrorResponse {
71    pub message: Option<String>,
72    #[serde(flatten)]
73    pub extra: HashMap<String, Value>,
74}