Skip to main content

systemprompt_provider_contracts/
component.rs

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