use fluent_fallback::types::ResourceId; pub struct FileSource { pub name: String, pub locales: Vec<String>, pub path_scheme: String, } impl Default for FileSource { fn default() -> Self { Self { name: "default".to_string(), path_scheme: "{locale}/".to_string(), locales: vec!["en-US".to_string()], } } } impl FileSource { pub fn new<S: ToString>(name: S, path_scheme: S, locales: Vec<S>) -> Self { Self { name: name.to_string(), path_scheme: path_scheme.to_string(), locales: locales .iter() .map(|l| l.to_string().parse().unwrap()) .collect(), } } } #[derive(Debug)] pub struct L10nAttribute { pub name: String, pub value: String, } impl L10nAttribute { pub fn new<S: ToString>(name: S, value: S) -> Self { Self { name: name.to_string(), value: value.to_string(), } } } #[derive(Debug)] pub struct L10nMessage { pub value: Option<String>, pub attributes: Option<Vec<L10nAttribute>>, } impl L10nMessage { pub fn new(value: Option<&str>, attributes: Option<Vec<L10nAttribute>>) -> Self { Self { value: value.map(|v| v.to_string()), attributes, } } } impl From<&str> for L10nMessage { fn from(value: &str) -> Self { Self { value: Some(value.to_string()), attributes: None, } } } #[derive(Debug)] pub struct L10nArgument { pub id: String, pub value: String, } impl L10nArgument { pub fn new<S: ToString>(id: S, value: S) -> Self { Self { id: id.to_string(), value: value.to_string(), } } } #[derive(Debug)] pub struct L10nKey { pub id: String, pub args: Option<Vec<L10nArgument>>, } impl L10nKey { pub fn new<S: ToString>(id: S, args: Option<Vec<L10nArgument>>) -> Self { Self { id: id.to_string(), args, } } } impl From<&str> for L10nKey { fn from(input: &str) -> Self { Self { id: input.to_string(), args: None, } } } #[derive(Clone, Copy, Debug)] pub enum ExceptionalContext { /// There is no exceptional context for this query (happy path). None, /// A value is missing from a resource and should cause a fallback. ValueMissingFromResource, /// A value is missing from all resources in all locales. ValueMissingFromAllResources, /// An optional resource is missing from the top locale. OptionalResourceMissingFromLocale, /// An optional resource is missing from all locales. OptionalResourceMissingFromAllLocales, /// A required resource is missing from the top locale. RequiredResourceMissingFromLocale, /// A required resource is missing from all locales. RequiredResourceMissingFromAllLocales, } impl ExceptionalContext { /// This is a query for a value in a missing required resource. pub fn missing_required_resource(self) -> bool { matches!( self, Self::RequiredResourceMissingFromLocale | Self::RequiredResourceMissingFromAllLocales, ) } /// This query should cause a format error to be appended to the errors Vec. pub fn causes_reported_format_error(self) -> bool { matches!( self, Self::ValueMissingFromResource | Self::ValueMissingFromAllResources | Self::OptionalResourceMissingFromLocale | Self::OptionalResourceMissingFromAllLocales | Self::RequiredResourceMissingFromAllLocales, ) } /// This query should cause a failed value lookup. pub fn causes_failed_value_lookup(self) -> bool { matches!( self, Self::ValueMissingFromAllResources | Self::OptionalResourceMissingFromAllLocales | Self::RequiredResourceMissingFromAllLocales, ) } /// This query should result in no bundles being generated. pub fn blocks_bundle_generation(self) -> bool { matches!(self, Self::RequiredResourceMissingFromAllLocales,) } } #[derive(Debug)] pub struct Query { pub input: L10nKey, pub output: Option<L10nMessage>, pub exceptional_context: ExceptionalContext, } impl Query { pub fn new<K: Into<L10nKey>>(input: K, output: Option<L10nMessage>) -> Self { Self { input: input.into(), output, exceptional_context: ExceptionalContext::None, } } } impl From<(&str, &str)> for Query { fn from(i: (&str, &str)) -> Self { Self { input: i.0.into(), output: Some(i.1.into()), exceptional_context: ExceptionalContext::None, } } } impl From<(&str, &str, ExceptionalContext)> for Query { fn from(i: (&str, &str, ExceptionalContext)) -> Self { Self { input: i.0.into(), output: Some(i.1.into()), exceptional_context: i.2, } } } impl From<(&str, L10nMessage)> for Query { fn from(i: (&str, L10nMessage)) -> Self { Self { input: i.0.into(), output: Some(i.1), exceptional_context: ExceptionalContext::None, } } } impl From<(L10nKey, L10nMessage)> for Query { fn from(i: (L10nKey, L10nMessage)) -> Self { Self { input: i.0, output: Some(i.1), exceptional_context: ExceptionalContext::None, } } } impl From<&str> for Query { fn from(i: &str) -> Self { Self { input: i.into(), output: None, exceptional_context: ExceptionalContext::None, } } } impl From<L10nKey> for Query { fn from(key: L10nKey) -> Self { Self { input: key, output: None, exceptional_context: ExceptionalContext::None, } } } pub struct Queries(pub Vec<Query>); impl From<Vec<&str>> for Queries { fn from(input: Vec<&str>) -> Self { Self(input.into_iter().map(|q| q.into()).collect()) } } impl From<Vec<(&str, &str)>> for Queries { fn from(input: Vec<(&str, &str)>) -> Self { Self(input.into_iter().map(|q| q.into()).collect()) } } impl std::ops::Deref for Queries { type Target = Vec<Query>; fn deref(&self) -> &Self::Target { &self.0 } } pub struct Scenario { pub name: String, pub file_sources: Vec<FileSource>, pub locales: Vec<String>, pub res_ids: Vec<ResourceId>, pub queries: Queries, } impl Scenario { pub fn new<S: ToString, R: Into<ResourceId>, Q: Into<Queries>>( name: S, file_sources: Vec<FileSource>, locales: Vec<S>, res_ids: Vec<R>, queries: Q, ) -> Self { Self { name: name.to_string(), file_sources, locales: locales .into_iter() .map(|l| l.to_string().parse().unwrap()) .collect(), res_ids: res_ids.into_iter().map(|id| id.into()).collect(), queries: queries.into(), } } }