Skip to main content

context69_contracts/
common.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use utoipa::ToSchema;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema, JsonSchema)]
6pub struct Pagination {
7    pub page: u32,
8    pub page_size: u32,
9    pub total: u64,
10    pub total_pages: u32,
11}
12
13impl Pagination {
14    pub fn try_new(page: u32, page_size: u32, total: u64) -> anyhow::Result<Self> {
15        if page == 0 {
16            return Err(anyhow::anyhow!("page must be greater than 0"));
17        }
18        if !(1..=100).contains(&page_size) {
19            return Err(anyhow::anyhow!("page_size must be between 1 and 100"));
20        }
21        let total_pages = if total == 0 {
22            0
23        } else {
24            u32::try_from(total.div_ceil(u64::from(page_size)))?
25        };
26        Ok(Self {
27            page,
28            page_size,
29            total,
30            total_pages,
31        })
32    }
33
34    pub fn offset(page: u32, page_size: u32) -> anyhow::Result<i64> {
35        if page == 0 {
36            return Err(anyhow::anyhow!("page must be greater than 0"));
37        }
38        if !(1..=100).contains(&page_size) {
39            return Err(anyhow::anyhow!("page_size must be between 1 and 100"));
40        }
41        i64::from(page - 1)
42            .checked_mul(i64::from(page_size))
43            .ok_or_else(|| anyhow::anyhow!("page offset is too large"))
44    }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
48#[serde(rename_all = "snake_case")]
49pub enum HealthStatus {
50    Ok,
51    Degraded,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
55pub struct HealthResponse {
56    pub status: HealthStatus,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub indexed_chunks: Option<u64>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub db_ok: Option<bool>,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub qdrant_ok: Option<bool>,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub library_processing_ready: Option<bool>,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub library_dependency_gates: Option<Vec<crate::LibraryDependencyGateResponse>>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub library_processing_queue: Option<crate::LibraryProcessingQueueHealth>,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
72pub struct ApiErrorResponse {
73    /// Stable machine-readable error code for programmatic handling.
74    pub code: String,
75    /// Human-readable error message.
76    pub message: String,
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub details: Option<serde_json::Value>,
79}
80
81impl ApiErrorResponse {
82    pub fn new(code: &str, message: String) -> Self {
83        Self {
84            code: code.to_string(),
85            message,
86            details: None,
87        }
88    }
89
90    pub fn code_for_status(status: u16) -> &'static str {
91        match status {
92            400 => "invalid_argument",
93            401 => "unauthorized",
94            403 => "forbidden",
95            404 => "not_found",
96            409 => "conflict",
97            413 => "payload_too_large",
98            422 => "unprocessable_entity",
99            429 => "rate_limited",
100            502 => "upstream_error",
101            503 => "unavailable",
102            504 => "upstream_timeout",
103            _ => "internal",
104        }
105    }
106}