systemprompt_models/api/responses/
markdown.rs1use serde::{Deserialize, Serialize};
8
9#[cfg(feature = "web")]
10use axum::http::StatusCode;
11#[cfg(feature = "web")]
12use axum::response::IntoResponse;
13#[cfg(feature = "web")]
14use http::header;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct MarkdownFrontmatter {
18 pub title: String,
19 pub slug: String,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub description: Option<String>,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub author: Option<String>,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub published_at: Option<String>,
26 #[serde(default, skip_serializing_if = "Vec::is_empty")]
27 pub tags: Vec<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub url: Option<String>,
30}
31
32impl MarkdownFrontmatter {
33 pub fn new(title: impl Into<String>, slug: impl Into<String>) -> Self {
34 Self {
35 title: title.into(),
36 slug: slug.into(),
37 description: None,
38 author: None,
39 published_at: None,
40 tags: Vec::new(),
41 url: None,
42 }
43 }
44
45 #[must_use]
46 pub fn with_description(mut self, description: impl Into<String>) -> Self {
47 self.description = Some(description.into());
48 self
49 }
50
51 #[must_use]
52 pub fn with_author(mut self, author: impl Into<String>) -> Self {
53 self.author = Some(author.into());
54 self
55 }
56
57 #[must_use]
58 pub fn with_published_at(mut self, published_at: impl Into<String>) -> Self {
59 self.published_at = Some(published_at.into());
60 self
61 }
62
63 #[must_use]
64 pub fn with_tags(mut self, tags: Vec<String>) -> Self {
65 self.tags = tags;
66 self
67 }
68
69 #[must_use]
70 pub fn with_url(mut self, url: impl Into<String>) -> Self {
71 self.url = Some(url.into());
72 self
73 }
74
75 pub fn to_yaml(&self) -> Result<String, serde_yaml::Error> {
76 serde_yaml::to_string(self)
77 }
78}
79
80#[derive(Debug, Clone)]
81pub struct MarkdownResponse {
82 pub frontmatter: MarkdownFrontmatter,
83 pub body: String,
84}
85
86impl MarkdownResponse {
87 pub fn new(frontmatter: MarkdownFrontmatter, body: impl Into<String>) -> Self {
88 Self {
89 frontmatter,
90 body: body.into(),
91 }
92 }
93
94 pub fn to_markdown(&self) -> Result<String, serde_yaml::Error> {
95 let yaml = self.frontmatter.to_yaml()?;
96 Ok(format!("---\n{}---\n\n{}", yaml, self.body))
97 }
98}
99
100#[cfg(feature = "web")]
101impl IntoResponse for MarkdownResponse {
102 fn into_response(self) -> axum::response::Response {
103 match self.to_markdown() {
104 Ok(body) => (
105 StatusCode::OK,
106 [(header::CONTENT_TYPE, "text/markdown; charset=utf-8")],
107 body,
108 )
109 .into_response(),
110 Err(e) => {
111 tracing::error!(error = %e, "MarkdownResponse frontmatter serialization failed");
112 StatusCode::INTERNAL_SERVER_ERROR.into_response()
113 },
114 }
115 }
116}