Skip to main content

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}