Skip to main content

rsynko_memory/
extraction.rs

1use crate::{Extraction, ExtractorKey};
2use rsynko_media::{ExtractionApplyAlg, ExtractionCatalogAlg, MediaSorts};
3use std::cell::RefCell;
4use thiserror::Error;
5
6/// Denotes the result a reference extractor assigns to an accepted URL.
7#[derive(Clone, Debug, PartialEq)]
8pub enum ReferenceExtractionOutcome {
9    /// Denotes successful extraction.
10    Success(Extraction),
11    /// Denotes an extractor failure with a stable message.
12    Failure(String),
13}
14
15/// Interprets one extractor as inspectable prefix matching and a fixed outcome.
16#[derive(Clone, Debug, PartialEq)]
17pub struct ReferenceExtractor {
18    key: ExtractorKey,
19    accepted_prefixes: Vec<String>,
20    outcome: ReferenceExtractionOutcome,
21}
22
23impl ReferenceExtractor {
24    /// Constructs a deterministic extractor interpretation.
25    #[must_use]
26    pub fn new(
27        key: impl Into<String>,
28        accepted_prefixes: impl IntoIterator<Item = impl Into<String>>,
29        outcome: ReferenceExtractionOutcome,
30    ) -> Self {
31        Self {
32            key: ExtractorKey::new(key),
33            accepted_prefixes: accepted_prefixes.into_iter().map(Into::into).collect(),
34            outcome,
35        }
36    }
37
38    /// Constructs a successful deterministic extractor.
39    #[must_use]
40    pub fn succeeds(key: impl Into<String>, accepted_prefix: impl Into<String>, extraction: Extraction) -> Self {
41        Self::new(key, [accepted_prefix], ReferenceExtractionOutcome::Success(extraction))
42    }
43
44    /// Constructs a failing deterministic extractor.
45    #[must_use]
46    pub fn fails(key: impl Into<String>, accepted_prefix: impl Into<String>, message: impl Into<String>) -> Self {
47        Self::new(key, [accepted_prefix], ReferenceExtractionOutcome::Failure(message.into()))
48    }
49}
50
51/// Denotes failures of the deterministic extraction interpreter.
52#[derive(Debug, Error, PartialEq, Eq)]
53pub enum ReferenceExtractionError {
54    /// Denotes application of a key absent from the catalog.
55    #[error("unknown extractor {0:?}")]
56    UnknownExtractor(ExtractorKey),
57    /// Denotes a failure reported by a reference extractor.
58    #[error("extractor {extractor:?} failed: {message}")]
59    ExtractorFailed {
60        /// Names the failing extractor.
61        extractor: ExtractorKey,
62        /// Preserves its failure message.
63        message: String,
64    },
65}
66
67/// Interprets an ordered extractor catalog in memory.
68#[derive(Debug, Default)]
69pub struct ReferenceExtractorRegistry {
70    extractors: Vec<ReferenceExtractor>,
71    applications: RefCell<Vec<ExtractorKey>>,
72}
73
74impl ReferenceExtractorRegistry {
75    /// Appends an extractor at the next catalog selection position.
76    pub fn push(&mut self, extractor: ReferenceExtractor) {
77        self.extractors.push(extractor);
78    }
79
80    /// Observes extractor applications in application order.
81    #[must_use]
82    pub fn applications(&self) -> Vec<ExtractorKey> {
83        self.applications.borrow().clone()
84    }
85}
86
87impl MediaSorts for ReferenceExtractorRegistry {
88    type Value = ();
89    type Metadata = ();
90    type Format = ();
91    type Artifact = ();
92    type Media = ();
93    type Extraction = Extraction;
94    type Extractor = ExtractorKey;
95    type Predicate = ();
96    type Selection = ();
97    type Output = ();
98}
99
100impl ExtractionCatalogAlg for ReferenceExtractorRegistry {
101    fn extractor_keys(&self) -> impl Iterator<Item = &ExtractorKey> {
102        self.extractors.iter().map(|extractor| &extractor.key)
103    }
104
105    fn extractor_accepts(&self, extractor: &ExtractorKey, url: &str) -> bool {
106        self.extractors
107            .iter()
108            .find(|candidate| candidate.key == *extractor)
109            .is_some_and(|candidate| candidate.accepted_prefixes.iter().any(|prefix| url.starts_with(prefix)))
110    }
111}
112
113impl ExtractionApplyAlg for ReferenceExtractorRegistry {
114    type Error = ReferenceExtractionError;
115
116    fn extract_with(&self, extractor: &ExtractorKey, _url: &str) -> Result<Extraction, Self::Error> {
117        let candidate = self
118            .extractors
119            .iter()
120            .find(|candidate| candidate.key == *extractor)
121            .ok_or_else(|| ReferenceExtractionError::UnknownExtractor(extractor.clone()))?;
122        self.applications.borrow_mut().push(extractor.clone());
123        match &candidate.outcome {
124            ReferenceExtractionOutcome::Success(extraction) => Ok(extraction.clone()),
125            ReferenceExtractionOutcome::Failure(message) => Err(ReferenceExtractionError::ExtractorFailed {
126                extractor: extractor.clone(),
127                message: message.clone(),
128            }),
129        }
130    }
131}