Skip to main content

systemprompt_models/api/responses/
specialized.rs

1//! Specialized response envelopes for `201 Created`, `202 Accepted`,
2//! plain success messages, and HATEOAS-style discovery payloads.
3//!
4//! Copyright (c) systemprompt.io — Business Source License 1.1.
5//! See <https://systemprompt.io> for licensing details.
6
7use indexmap::IndexMap;
8use serde::{Deserialize, Serialize};
9
10use super::envelopes::ResponseMeta;
11
12#[cfg(feature = "web")]
13use axum::Json;
14#[cfg(feature = "web")]
15use axum::http::StatusCode;
16#[cfg(feature = "web")]
17use axum::response::IntoResponse;
18
19#[derive(Debug, Serialize, Deserialize)]
20pub struct SuccessResponse {
21    pub message: String,
22
23    pub meta: ResponseMeta,
24}
25
26impl SuccessResponse {
27    pub fn new(message: impl Into<String>) -> Self {
28        Self {
29            message: message.into(),
30            meta: ResponseMeta::new(),
31        }
32    }
33}
34
35#[derive(Debug, Serialize, Deserialize)]
36pub struct CreatedResponse<T>
37where
38    T: 'static,
39{
40    pub data: T,
41
42    pub meta: ResponseMeta,
43
44    pub location: String,
45}
46
47impl<T: Serialize + 'static> CreatedResponse<T> {
48    pub fn new(data: T, location: impl Into<String>) -> Self {
49        Self {
50            data,
51            meta: ResponseMeta::new(),
52            location: location.into(),
53        }
54    }
55}
56
57#[derive(Debug, Serialize, Deserialize)]
58pub struct AcceptedResponse {
59    pub message: String,
60
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub job_id: Option<String>,
63
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub status_url: Option<String>,
66
67    pub meta: ResponseMeta,
68}
69
70impl AcceptedResponse {
71    pub fn new(message: impl Into<String>) -> Self {
72        Self {
73            message: message.into(),
74            job_id: None,
75            status_url: None,
76            meta: ResponseMeta::new(),
77        }
78    }
79
80    #[must_use]
81    pub fn with_job(mut self, job_id: impl Into<String>, status_url: impl Into<String>) -> Self {
82        self.job_id = Some(job_id.into());
83        self.status_url = Some(status_url.into());
84        self
85    }
86}
87
88#[derive(Debug, Serialize, Deserialize)]
89pub struct Link {
90    pub href: String,
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub title: Option<String>,
93}
94
95impl Link {
96    pub fn new(href: impl Into<String>, title: Option<String>) -> Self {
97        Self {
98            href: href.into(),
99            title,
100        }
101    }
102}
103
104#[derive(Debug, Serialize, Deserialize)]
105pub struct DiscoveryResponse<T>
106where
107    T: 'static,
108{
109    pub data: T,
110    pub meta: ResponseMeta,
111    #[serde(rename = "_links")]
112    pub links: IndexMap<String, Link>,
113}
114
115impl<T: Serialize + 'static> DiscoveryResponse<T> {
116    pub fn new(data: T, links: IndexMap<String, Link>) -> Self {
117        Self {
118            data,
119            meta: ResponseMeta::new(),
120            links,
121        }
122    }
123
124    #[must_use]
125    pub fn with_meta(mut self, meta: ResponseMeta) -> Self {
126        self.meta = meta;
127        self
128    }
129}
130
131#[cfg(feature = "web")]
132impl IntoResponse for SuccessResponse {
133    fn into_response(self) -> axum::response::Response {
134        (StatusCode::OK, Json(self)).into_response()
135    }
136}
137
138#[cfg(feature = "web")]
139impl<T: Serialize + 'static> IntoResponse for CreatedResponse<T> {
140    fn into_response(self) -> axum::response::Response {
141        (
142            StatusCode::CREATED,
143            [("Location", self.location.clone())],
144            Json(self),
145        )
146            .into_response()
147    }
148}
149
150#[cfg(feature = "web")]
151impl IntoResponse for AcceptedResponse {
152    fn into_response(self) -> axum::response::Response {
153        (StatusCode::ACCEPTED, Json(self)).into_response()
154    }
155}