oxi/internal_urls/
rule_handler.rs1use std::sync::Arc;
4
5use async_trait::async_trait;
6use oxi_sdk::SdkError;
7use oxi_sdk::ports::{ProtocolHandler, ResolveContext, ResolvedUrl, RuleRegistry};
8
9pub struct RuleProtocolHandler {
11 registry: Arc<dyn RuleRegistry>,
12}
13
14impl RuleProtocolHandler {
15 pub fn new(registry: Arc<dyn RuleRegistry>) -> Self {
17 Self { registry }
18 }
19}
20
21#[async_trait]
22impl ProtocolHandler for RuleProtocolHandler {
23 fn scheme(&self) -> &str {
24 "rule"
25 }
26 fn immutable(&self) -> bool {
27 true
28 }
29
30 async fn resolve(
31 &self,
32 url: &str,
33 _selector: Option<&str>,
34 _ctx: &ResolveContext,
35 ) -> Result<ResolvedUrl, SdkError> {
36 let rule_name = url
37 .strip_prefix("rule://")
38 .unwrap_or(url)
39 .trim_end_matches('/');
40 if rule_name.is_empty() {
41 return Err(SdkError::ExecutionFailed {
42 reason: "rule:// URL requires a rule name".into(),
43 });
44 }
45
46 let rules = self.registry.rules().await;
47 let rule = rules.iter().find(|r| r.name == rule_name).ok_or_else(|| {
48 let available: Vec<String> = rules.iter().map(|r| r.name.clone()).collect();
49 SdkError::ExecutionFailed {
50 reason: format!(
51 "Unknown rule: '{rule_name}'. Available: {}",
52 if available.is_empty() {
53 "none".to_string()
54 } else {
55 available.join(", ")
56 }
57 ),
58 }
59 })?;
60
61 Ok(ResolvedUrl {
62 url: format!("rule://{rule_name}"),
63 content: rule.content.clone(),
64 content_type: "text/markdown".into(),
65 size: None,
66 source_path: None,
67 notes: vec![],
68 immutable: true,
69 })
70 }
71}