use std::path::PathBuf;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DomainDiscovery {
pub enabled: bool,
pub root_dir: String,
}
#[derive(Debug, Clone)]
pub struct Domain {
pub name: String,
pub path: PathBuf,
}
impl DomainDiscovery {
pub fn resolve_domains(&self) -> Result<Vec<Domain>> {
if !self.enabled {
return Ok(Vec::new());
}
let root = PathBuf::from(&self.root_dir);
if !root.is_dir() {
anyhow::bail!("Domain discovery root not found: {}", self.root_dir);
}
let mut domains = Vec::new();
for entry in std::fs::read_dir(&root)
.context(format!("Failed to read domain root: {}", self.root_dir))?
{
let entry = entry.context("Failed to read directory entry")?;
let path = entry.path();
if path.is_dir() {
let name = path
.file_name()
.and_then(|n| n.to_str())
.map(std::string::ToString::to_string)
.ok_or_else(|| anyhow::anyhow!("Invalid domain name: {}", path.display()))?;
domains.push(Domain { name, path });
}
}
domains.sort_by(|a, b| a.name.cmp(&b.name));
Ok(domains)
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct SchemaIncludes {
pub types: Vec<String>,
pub queries: Vec<String>,
pub mutations: Vec<String>,
}
impl SchemaIncludes {
pub fn is_empty(&self) -> bool {
self.types.is_empty() && self.queries.is_empty() && self.mutations.is_empty()
}
pub fn resolve_globs(&self) -> Result<ResolvedIncludes> {
Ok(ResolvedIncludes {
types: Self::resolve_group(&self.types, "types")?,
queries: Self::resolve_group(&self.queries, "queries")?,
mutations: Self::resolve_group(&self.mutations, "mutations")?,
})
}
fn resolve_group(patterns: &[String], group: &str) -> Result<Vec<PathBuf>> {
let mut paths = Vec::new();
for pattern in patterns {
let before = paths.len();
for entry in glob::glob(pattern)
.context(format!("Invalid glob pattern for {group}: {pattern}"))?
{
match entry {
Ok(path) => paths.push(path),
Err(e) => {
anyhow::bail!("Error resolving {group} glob pattern '{pattern}': {e}");
},
}
}
if paths.len() == before {
anyhow::bail!(
"[includes] {group} pattern '{pattern}' matched no files. Fix the path or \
remove the entry — compiling without it would silently produce a schema \
missing whatever it was meant to contribute."
);
}
}
paths.sort();
paths.dedup();
Ok(paths)
}
}
#[derive(Debug, Clone)]
pub struct ResolvedIncludes {
pub types: Vec<PathBuf>,
pub queries: Vec<PathBuf>,
pub mutations: Vec<PathBuf>,
}