use std::{
collections::HashMap,
sync::{Arc, Mutex, OnceLock},
};
use regex::Regex;
use serde_json::Value;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}: failed to convert to {1}")]
Cast(String, String),
#[error("{0}")]
Clap(#[from] clap::Error),
#[error("Configuration error: {0}")]
Config(String),
#[error("{0}: dest is not empty, use --overwrite to overwrite existing files")]
DestNotEmpty(String),
#[error("$filter type error: {0}")]
FilterType(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON serialization error: {0}")]
SerdeJson(#[from] serde_json::Error),
#[error("SQLite error: {0}")]
Rusqlite(#[from] rusqlite::Error),
#[error("{0}: Unknown serializer")]
UnknownSerializer(String),
}
pub mod config;
mod context;
pub mod filter;
pub mod orchestrator;
pub mod serializers;
mod static_files;
pub use config::{Config, Layout, SerializerConfig, StaticConfig, StaticSpec};
pub use orchestrator::run;
pub use serializers::{JSONSerializer, Serializer, SqliteSerializer, TypescriptSerializer};
static REGEX_CACHE: OnceLock<Mutex<HashMap<String, Arc<Regex>>>> = OnceLock::new();
pub(crate) fn compile_regex(pattern: &str) -> std::result::Result<Arc<Regex>, regex::Error> {
let cache = REGEX_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
if let Ok(guard) = cache.lock()
&& let Some(re) = guard.get(pattern)
{
return Ok(Arc::clone(re));
}
let re = Arc::new(Regex::new(pattern)?);
if let Ok(mut guard) = cache.lock() {
guard.insert(pattern.to_string(), Arc::clone(&re));
}
Ok(re)
}
pub(crate) fn value_kind(v: &Value) -> &'static str {
match v {
Value::Null => "null",
Value::Bool(_) => "bool",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_identical_patterns_share_one_compiled_regex() {
let first = compile_regex("^ab+c$").expect("valid pattern");
let second = compile_regex("^ab+c$").expect("valid pattern");
assert!(Arc::ptr_eq(&first, &second));
}
#[test]
fn test_distinct_patterns_are_compiled_separately() {
let a = compile_regex("^shared-cache-a$").expect("valid pattern");
let b = compile_regex("^shared-cache-b$").expect("valid pattern");
assert!(!Arc::ptr_eq(&a, &b));
assert!(a.is_match("shared-cache-a"));
assert!(b.is_match("shared-cache-b"));
}
#[test]
fn test_invalid_pattern_returns_the_regex_error() {
assert!(compile_regex("([unclosed").is_err());
}
}