Skip to main content

systemprompt_provider_contracts/
page.rs

1//! [`PageDataProvider`] contract for supplying per-page template data.
2
3use std::any::Any;
4
5use async_trait::async_trait;
6use serde_json::Value;
7use systemprompt_identifiers::LocaleCode;
8
9use crate::error::ProviderResult;
10use crate::web_config::WebConfig;
11
12pub struct PageContext<'a> {
13    pub page_type: &'a str,
14    pub web_config: &'a WebConfig,
15    pub locale: &'a LocaleCode,
16    content_config: &'a (dyn Any + Send + Sync),
17    db_pool: &'a (dyn Any + Send + Sync),
18    content_item: Option<&'a Value>,
19    all_items: Option<&'a [Value]>,
20}
21
22impl std::fmt::Debug for PageContext<'_> {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        f.debug_struct("PageContext")
25            .field("page_type", &self.page_type)
26            .field("web_config", &"<WebConfig>")
27            .field("content_config", &"<dyn Any>")
28            .field("db_pool", &"<dyn Any>")
29            .field("content_item", &self.content_item.is_some())
30            .field("all_items_count", &self.all_items.map(<[_]>::len))
31            .finish()
32    }
33}
34
35impl<'a> PageContext<'a> {
36    #[must_use]
37    pub fn new(
38        page_type: &'a str,
39        web_config: &'a WebConfig,
40        content_config: &'a (dyn Any + Send + Sync),
41        db_pool: &'a (dyn Any + Send + Sync),
42    ) -> Self {
43        Self {
44            page_type,
45            web_config,
46            locale: &web_config.i18n.default_locale,
47            content_config,
48            db_pool,
49            content_item: None,
50            all_items: None,
51        }
52    }
53
54    #[must_use]
55    pub const fn with_locale(mut self, locale: &'a LocaleCode) -> Self {
56        self.locale = locale;
57        self
58    }
59
60    #[must_use]
61    pub const fn with_content_item(mut self, item: &'a Value) -> Self {
62        self.content_item = Some(item);
63        self
64    }
65
66    #[must_use]
67    pub const fn with_all_items(mut self, items: &'a [Value]) -> Self {
68        self.all_items = Some(items);
69        self
70    }
71
72    #[must_use]
73    pub fn content_config<T: 'static>(&self) -> Option<&T> {
74        self.content_config.downcast_ref::<T>()
75    }
76
77    #[must_use]
78    pub fn db_pool<T: 'static>(&self) -> Option<&T> {
79        self.db_pool.downcast_ref::<T>()
80    }
81
82    #[must_use]
83    pub const fn content_item(&self) -> Option<&Value> {
84        self.content_item
85    }
86
87    #[must_use]
88    pub const fn all_items(&self) -> Option<&[Value]> {
89        self.all_items
90    }
91}
92
93// Why: provider is consumed as a trait object by the generator crate; an
94// async fn in a bare trait is not dyn-compatible, so #[async_trait] is
95// required.
96#[async_trait]
97pub trait PageDataProvider: Send + Sync {
98    fn provider_id(&self) -> &'static str;
99
100    fn applies_to_pages(&self) -> Vec<String> {
101        vec![]
102    }
103
104    async fn provide_page_data(&self, ctx: &PageContext<'_>) -> ProviderResult<Value>;
105
106    fn priority(&self) -> u32 {
107        100
108    }
109}