use std::collections::BTreeMap;
use crate::{ErrorCategory, ErrorCode, PixelFlowError, Rational, Result};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SourceOptionValue {
String(String),
Bool(bool),
Int(i64),
Rational(Rational),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SourceRequest {
path: String,
options: BTreeMap<String, SourceOptionValue>,
}
impl SourceRequest {
#[must_use]
pub fn new(path: impl Into<String>) -> Self {
Self {
path: path.into(),
options: BTreeMap::new(),
}
}
#[must_use]
pub fn path(&self) -> &str {
&self.path
}
#[must_use]
pub const fn options(&self) -> &BTreeMap<String, SourceOptionValue> {
&self.options
}
pub fn try_with_option(
mut self,
name: impl Into<String>,
value: SourceOptionValue,
) -> Result<Self> {
let name = name.into();
validate_option_name(&name)?;
self.options.insert(name, value);
Ok(self)
}
#[must_use]
pub fn with_option(mut self, name: impl Into<String>, value: SourceOptionValue) -> Self {
let name = name.into();
debug_assert!(is_option_name(&name));
self.options.insert(name, value);
self
}
}
fn validate_option_name(name: &str) -> Result<()> {
if is_option_name(name) {
return Ok(());
}
Err(PixelFlowError::new(
ErrorCategory::Source,
ErrorCode::new("source.invalid_option"),
format!("invalid source option name '{name}'"),
))
}
fn is_option_name(name: &str) -> bool {
let mut bytes = name.bytes();
matches!(bytes.next(), Some(first) if first.is_ascii_alphabetic() || first == b'_')
&& bytes.all(|byte| byte.is_ascii_alphanumeric() || byte == b'_')
}
#[cfg(test)]
mod tests {
use crate::{ErrorCategory, ErrorCode, Rational};
use super::{SourceOptionValue, SourceRequest};
#[test]
fn source_request_rejects_invalid_option_name() {
let error = SourceRequest::new("input.mkv")
.try_with_option(
"bad-name",
SourceOptionValue::Rational(Rational {
numerator: 30_000,
denominator: 1_001,
}),
)
.expect_err("invalid option name should fail");
assert_eq!(error.category(), ErrorCategory::Source);
assert_eq!(error.code(), ErrorCode::new("source.invalid_option"));
}
}