use crate::error::{EsFluentError, EsFluentResult};
use crate::{CanonicalLanguageIdentifierError, parse_canonical_language_identifier};
use std::path::Path;
pub fn parse_language_entry(
entry: std::fs::DirEntry,
) -> EsFluentResult<Option<unic_langid::LanguageIdentifier>> {
if !entry.file_type()?.is_dir() {
return Ok(None);
}
let raw_name = entry.file_name();
let name = raw_name.into_string().map_err(|raw| {
EsFluentError::IoError(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Assets directory contains a non UTF-8 entry: {:?}", raw),
))
})?;
let lang = parse_canonical_language_identifier(&name).map_err(|err| match err {
CanonicalLanguageIdentifierError::Invalid { source, .. } => {
EsFluentError::invalid_language_identifier(&name, format!("Parse error: {}", source))
},
CanonicalLanguageIdentifierError::NonCanonical { canonical, .. } => {
EsFluentError::invalid_language_identifier(
&name,
format!(
"Locale directory must use canonical BCP-47 casing '{}'",
canonical
),
)
},
})?;
Ok(Some(lang))
}
pub fn validate_assets_dir(assets_dir: &Path) -> EsFluentResult<()> {
if !assets_dir.exists() {
return Err(EsFluentError::assets_not_found(assets_dir));
}
if !assets_dir.is_dir() {
return Err(EsFluentError::IoError(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("Assets path '{}' is not a directory", assets_dir.display()),
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::OsString;
use tempfile::tempdir;
fn read_dir_entry_by_name(parent: &Path, name: &str) -> std::fs::DirEntry {
std::fs::read_dir(parent)
.expect("read_dir")
.filter_map(|entry| entry.ok())
.find(|entry| entry.file_name() == OsString::from(name))
.expect("entry not found")
}
#[test]
fn parse_language_entry_handles_non_directory_and_valid_directory() {
let temp = tempdir().expect("tempdir");
std::fs::write(temp.path().join("file.txt"), "not a directory").expect("write");
std::fs::create_dir_all(temp.path().join("en-US")).expect("create lang");
let file_entry = read_dir_entry_by_name(temp.path(), "file.txt");
assert_eq!(parse_language_entry(file_entry).expect("parse file"), None);
let dir_entry = read_dir_entry_by_name(temp.path(), "en-US");
let parsed = parse_language_entry(dir_entry)
.expect("parse dir")
.expect("language id");
assert_eq!(
parsed,
"en-US"
.parse::<unic_langid::LanguageIdentifier>()
.expect("language")
);
}
#[test]
fn parse_language_entry_rejects_invalid_identifiers_and_accepts_variants() {
let temp = tempdir().expect("tempdir");
std::fs::create_dir_all(temp.path().join("not-a-lang!")).expect("create invalid");
std::fs::create_dir_all(temp.path().join("de-DE-1901")).expect("create variant");
let invalid_entry = read_dir_entry_by_name(temp.path(), "not-a-lang!");
let invalid_err = parse_language_entry(invalid_entry).expect_err("should fail");
assert!(matches!(
invalid_err,
EsFluentError::InvalidLanguageIdentifier { .. }
));
let variant_entry = read_dir_entry_by_name(temp.path(), "de-DE-1901");
let variant_lang = parse_language_entry(variant_entry)
.expect("variant locale should parse")
.expect("language id");
assert_eq!(
variant_lang,
"de-DE-1901"
.parse::<unic_langid::LanguageIdentifier>()
.expect("language")
);
}
#[test]
fn parse_language_entry_rejects_noncanonical_locale_casing() {
let temp = tempdir().expect("tempdir");
std::fs::create_dir_all(temp.path().join("en-us")).expect("create noncanonical");
let entry = read_dir_entry_by_name(temp.path(), "en-us");
let err = parse_language_entry(entry).expect_err("noncanonical locale should fail");
assert!(matches!(
err,
EsFluentError::InvalidLanguageIdentifier { .. }
));
assert!(err.to_string().contains("en-US"));
}
#[test]
fn validate_assets_dir_checks_missing_file_and_directory() {
let temp = tempdir().expect("tempdir");
let missing = temp.path().join("missing");
let file = temp.path().join("file.txt");
let dir = temp.path().join("assets");
std::fs::write(&file, "x").expect("write");
std::fs::create_dir_all(&dir).expect("mkdir");
let missing_err = validate_assets_dir(&missing).expect_err("missing should fail");
assert!(matches!(missing_err, EsFluentError::AssetsNotFound { .. }));
let file_err = validate_assets_dir(&file).expect_err("file should fail");
assert!(matches!(file_err, EsFluentError::IoError(_)));
validate_assets_dir(&dir).expect("directory should validate");
}
}