stygian_plugin/ports.rs
1//! Port trait definitions for stygian-plugin
2//!
3//! Defines interfaces that adapters must implement following hexagonal architecture.
4//! The domain layer depends only on these traits, not on concrete implementations.
5
6use crate::domain::{ExtractionRequest, ExtractionResult, ExtractionTemplate, IdempotencyKey};
7use async_trait::async_trait;
8
9/// Port for persisting and retrieving extraction templates
10///
11/// Implementations handle storage (file, database, cloud) and lifecycle management.
12///
13/// # Example
14///
15/// ```no_run
16/// use stygian_plugin::ports::PluginTemplateStore;
17/// use stygian_plugin::domain::ExtractionTemplate;
18/// # async fn example(store: impl PluginTemplateStore) -> Result<(), Box<dyn std::error::Error>> {
19/// let template = ExtractionTemplate::new("Product Extractor");
20/// store.save(&template).await?;
21/// let retrieved = store.get(&template.id).await?;
22/// assert_eq!(template.id, retrieved.id);
23/// # Ok(())
24/// # }
25/// ```
26#[async_trait]
27pub trait PluginTemplateStore: Send + Sync {
28 /// Save or update a template
29 async fn save(&self, template: &ExtractionTemplate) -> crate::Result<()>;
30
31 /// Retrieve a template by ID
32 async fn get(&self, id: &uuid::Uuid) -> crate::Result<ExtractionTemplate>;
33
34 /// List all templates
35 async fn list(&self) -> crate::Result<Vec<ExtractionTemplate>>;
36
37 /// Delete a template by ID
38 async fn delete(&self, id: &uuid::Uuid) -> crate::Result<()>;
39
40 /// Search templates by name (substring match)
41 async fn search(&self, query: &str) -> crate::Result<Vec<ExtractionTemplate>> {
42 let all = self.list().await?;
43 let results = all
44 .into_iter()
45 .filter(|t| t.name.to_lowercase().contains(&query.to_lowercase()))
46 .collect();
47 Ok(results)
48 }
49}
50
51/// Port for executing data extraction on a page
52///
53/// Implementations handle DOM interaction (via browser automation or plugin content script).
54///
55/// # Example
56///
57/// ```no_run
58/// use stygian_plugin::ports::PluginExtractionPort;
59/// use stygian_plugin::domain::{ExtractionRequest, ExtractionTemplate};
60/// # async fn example(port: impl PluginExtractionPort) -> Result<(), Box<dyn std::error::Error>> {
61/// let template = ExtractionTemplate::new("Example");
62/// let request = ExtractionRequest::new(template, "https://example.com", "<html>...</html>");
63/// let result = port.execute(&request).await?;
64/// # Ok(())
65/// # }
66/// ```
67#[async_trait]
68pub trait PluginExtractionPort: Send + Sync {
69 /// Execute extraction using a request
70 async fn execute(&self, request: &ExtractionRequest) -> crate::Result<ExtractionResult>;
71
72 /// Validate a selector against the current/provided DOM
73 async fn validate_selector(
74 &self,
75 _html: &str,
76 _selector_expr: &str,
77 ) -> crate::Result<(bool, usize)> {
78 // Default implementation: always returns (true, 0) for estimation
79 // Concrete implementations should validate against actual DOM
80 Ok((true, 0))
81 }
82}
83
84/// Port for tracking idempotency keys and results
85///
86/// Prevents duplicate extractions by caching results per idempotency key.
87///
88/// # Example
89///
90/// ```no_run
91/// use stygian_plugin::ports::IdempotencyKeyStore;
92/// use stygian_plugin::domain::{IdempotencyKey, ExtractionResult};
93/// # async fn example(store: impl IdempotencyKeyStore) -> Result<(), Box<dyn std::error::Error>> {
94/// let key = IdempotencyKey::new();
95/// let result = ExtractionResult::new(key);
96/// store.store_result(&key, &result).await?;
97/// let retrieved = store.get_result(&key).await?;
98/// # Ok(())
99/// # }
100/// ```
101#[async_trait]
102pub trait IdempotencyKeyStore: Send + Sync {
103 /// Store an extraction result under an idempotency key
104 async fn store_result(
105 &self,
106 key: &IdempotencyKey,
107 result: &ExtractionResult,
108 ) -> crate::Result<()>;
109
110 /// Retrieve a cached result by idempotency key
111 async fn get_result(&self, key: &IdempotencyKey) -> crate::Result<Option<ExtractionResult>>;
112
113 /// Delete an old result (cleanup)
114 async fn delete_result(&self, key: &IdempotencyKey) -> crate::Result<()>;
115
116 /// Clear all results (for testing)
117 async fn clear_all(&self) -> crate::Result<()>;
118}
119
120/// Optional: Recording port for capturing user interactions and generating templates
121///
122/// Implementations handle recording DOM interactions, building selectors, and inferring templates.
123///
124/// Not required for basic extraction; useful for the interactive recording mode.
125#[async_trait]
126pub trait PluginRecordingPort: Send + Sync {
127 /// Start recording user interactions
128 async fn start_recording(&self) -> crate::Result<String>;
129
130 /// Record an element selection
131 async fn record_element_click(
132 &self,
133 session_id: &str,
134 element_info: serde_json::Value,
135 ) -> crate::Result<()>;
136
137 /// Finalize recording and generate a template
138 async fn finalize_recording(
139 &self,
140 session_id: &str,
141 template_name: &str,
142 ) -> crate::Result<ExtractionTemplate>;
143
144 /// Cancel an active recording
145 async fn cancel_recording(&self, session_id: &str) -> crate::Result<()>;
146}