Skip to main content

systemprompt_provider_contracts/
component.rs

1//! [`ComponentRenderer`] contract for emitting one named template component.
2
3use std::path::PathBuf;
4
5use async_trait::async_trait;
6use serde_json::Value;
7
8use crate::error::ProviderResult;
9use crate::web_config::WebConfig;
10
11#[derive(Debug, Clone)]
12pub enum PartialSource {
13    Embedded(&'static str),
14    File(PathBuf),
15}
16
17#[derive(Debug, Clone)]
18pub struct PartialTemplate {
19    pub name: String,
20    pub source: PartialSource,
21}
22
23impl PartialTemplate {
24    #[must_use]
25    pub fn embedded(name: impl Into<String>, content: &'static str) -> Self {
26        Self {
27            name: name.into(),
28            source: PartialSource::Embedded(content),
29        }
30    }
31
32    #[must_use]
33    pub fn file(name: impl Into<String>, path: impl Into<PathBuf>) -> Self {
34        Self {
35            name: name.into(),
36            source: PartialSource::File(path.into()),
37        }
38    }
39}
40
41#[derive(Debug)]
42pub struct ComponentContext<'a> {
43    pub web_config: &'a WebConfig,
44    pub item: Option<&'a Value>,
45    pub all_items: Option<&'a [Value]>,
46    pub popular_ids: Option<&'a [String]>,
47}
48
49impl<'a> ComponentContext<'a> {
50    #[must_use]
51    pub const fn for_page(web_config: &'a WebConfig) -> Self {
52        Self {
53            web_config,
54            item: None,
55            all_items: None,
56            popular_ids: None,
57        }
58    }
59
60    #[must_use]
61    pub const fn for_content(
62        web_config: &'a WebConfig,
63        item: &'a Value,
64        all_items: &'a [Value],
65        popular_ids: &'a [String],
66    ) -> Self {
67        Self {
68            web_config,
69            item: Some(item),
70            all_items: Some(all_items),
71            popular_ids: Some(popular_ids),
72        }
73    }
74
75    #[must_use]
76    pub const fn for_list(web_config: &'a WebConfig, all_items: &'a [Value]) -> Self {
77        Self {
78            web_config,
79            item: None,
80            all_items: Some(all_items),
81            popular_ids: None,
82        }
83    }
84}
85
86#[derive(Debug)]
87pub struct RenderedComponent {
88    pub html: String,
89    pub variable_name: String,
90}
91
92impl RenderedComponent {
93    #[must_use]
94    pub fn new(variable_name: impl Into<String>, html: impl Into<String>) -> Self {
95        Self {
96            html: html.into(),
97            variable_name: variable_name.into(),
98        }
99    }
100}
101
102// Why: renderer is consumed as a trait object by the generator crate; an
103// async fn in a bare trait is not dyn-compatible, so #[async_trait] is
104// required.
105#[async_trait]
106pub trait ComponentRenderer: Send + Sync {
107    fn component_id(&self) -> &'static str;
108
109    fn variable_name(&self) -> &'static str;
110
111    fn applies_to(&self) -> Vec<String> {
112        vec![]
113    }
114
115    fn partial_template(&self) -> Option<PartialTemplate> {
116        None
117    }
118
119    async fn render(&self, ctx: &ComponentContext<'_>) -> ProviderResult<RenderedComponent>;
120
121    fn priority(&self) -> u32 {
122        100
123    }
124}