Skip to main content

systemprompt_provider_contracts/
page.rs

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