1use std::path::{Path, PathBuf};
4
5use anyhow::{Context, Result};
6use ron2::schema::{Schema, storage::read_schema};
7use walkdir::WalkDir;
8
9#[derive(Debug)]
11pub struct DiscoveredSchema {
12 pub path: PathBuf,
14 pub type_path: String,
16 pub schema: Schema,
18}
19
20pub fn discover_schemas(input: &Path) -> Result<Vec<DiscoveredSchema>> {
22 if input.is_file() {
23 let schema = read_schema(input)
25 .with_context(|| format!("failed to read schema from {}", input.display()))?;
26 let type_path = file_path_to_type_path(input, input.parent().unwrap_or(input));
27 return Ok(vec![DiscoveredSchema {
28 path: input.to_path_buf(),
29 type_path,
30 schema,
31 }]);
32 }
33
34 let mut schemas = Vec::new();
36
37 for entry in WalkDir::new(input)
38 .follow_links(true)
39 .into_iter()
40 .filter_map(|e| e.ok())
41 {
42 let path = entry.path();
43 if path.is_file() && path.to_string_lossy().ends_with(".schema.ron") {
44 let schema = read_schema(path)
45 .with_context(|| format!("failed to read schema from {}", path.display()))?;
46 let type_path = file_path_to_type_path(path, input);
47 schemas.push(DiscoveredSchema {
48 path: path.to_path_buf(),
49 type_path,
50 schema,
51 });
52 }
53 }
54
55 schemas.sort_by(|a, b| a.type_path.cmp(&b.type_path));
57
58 Ok(schemas)
59}
60
61fn file_path_to_type_path(path: &Path, base: &Path) -> String {
65 let relative = path
67 .strip_prefix(base)
68 .unwrap_or(path)
69 .with_extension("") .with_extension(""); relative
74 .components()
75 .map(|c| c.as_os_str().to_string_lossy().to_string())
76 .collect::<Vec<_>>()
77 .join("::")
78}
79
80#[cfg(test)]
81mod tests {
82 use ron2::schema::{Field, TypeKind, write_schema};
83
84 use super::*;
85
86 #[test]
87 fn test_file_path_to_type_path() {
88 let base = Path::new("/schemas");
89 let path = Path::new("/schemas/my_crate/config/AppConfig.schema.ron");
90 assert_eq!(
91 file_path_to_type_path(path, base),
92 "my_crate::config::AppConfig"
93 );
94 }
95
96 #[test]
97 fn test_file_path_to_type_path_single_component() {
98 let base = Path::new("/schemas");
99 let path = Path::new("/schemas/Config.schema.ron");
100 assert_eq!(file_path_to_type_path(path, base), "Config");
101 }
102
103 #[test]
104 fn test_discover_schemas_preserves_generic_typeref() {
105 let temp_dir = tempfile::tempdir().expect("temp dir");
106 let schema = Schema::new(TypeKind::Struct {
107 fields: vec![Field::new(
108 "value",
109 TypeKind::TypeRef("my_crate::Wrapper<T>".to_string()),
110 )],
111 });
112
113 write_schema("my_crate::Generic", &schema, Some(temp_dir.path())).expect("write schema");
114
115 let discovered = discover_schemas(temp_dir.path()).expect("discover schemas");
116 assert_eq!(discovered.len(), 1);
117 assert_eq!(discovered[0].type_path, "my_crate::Generic");
118 assert_eq!(discovered[0].schema, schema);
119 }
120}