hydrate_pipeline/
project.rs

1use hydrate_data::PathReferenceNamespaceResolver;
2use serde::{Deserialize, Serialize};
3use std::error::Error;
4use std::path::{Path, PathBuf};
5
6#[derive(Serialize, Deserialize)]
7pub struct NamePathPairJson {
8    pub name: String,
9    pub path: String,
10}
11
12#[derive(Serialize, Deserialize)]
13pub struct SchemaCodegenJobsJson {
14    name: String,
15    schema_path: String,
16    included_schema_paths: Vec<String>,
17    outfile: String,
18}
19
20#[derive(Serialize, Deserialize)]
21pub struct HydrateProjectConfigurationJson {
22    pub schema_def_paths: Vec<String>,
23    pub import_data_path: String,
24    pub build_data_path: String,
25    pub job_data_path: String,
26    pub id_based_asset_sources: Vec<NamePathPairJson>,
27    pub path_based_asset_sources: Vec<NamePathPairJson>,
28    pub source_file_locations: Vec<NamePathPairJson>,
29    pub schema_codegen_jobs: Vec<SchemaCodegenJobsJson>,
30}
31
32#[derive(Debug, Clone)]
33pub struct NamePathPair {
34    pub name: String,
35    pub path: PathBuf,
36}
37
38#[derive(Debug, Clone)]
39pub struct SchemaCodegenJobs {
40    pub name: String,
41    pub schema_path: PathBuf,
42    pub included_schema_paths: Vec<PathBuf>,
43    pub outfile: PathBuf,
44}
45
46#[derive(Debug, Clone)]
47pub struct HydrateProjectConfiguration {
48    // Directories to all schema files that should be used
49    pub schema_def_paths: Vec<PathBuf>,
50
51    // Path to where all import data will be stored (this is bulk data extracted from source files)
52    pub import_data_path: PathBuf,
53
54    // Path to where all built data will be stored (this is what the game consumes)
55    pub build_data_path: PathBuf,
56
57    // Unused for now, but it will be a cache for intermediate build data later
58    pub job_data_path: PathBuf,
59
60    // Asset storage location that uses file system paths for names/asset references
61    pub id_based_asset_sources: Vec<NamePathPair>,
62    // Asset storage location that uses IDs for file names/asset references
63    pub path_based_asset_sources: Vec<NamePathPair>,
64    // When importing data, if it is coming from within one of these paths on disk the location of
65    // the source file will be tracked relative to that path
66    pub source_file_locations: Vec<NamePathPair>,
67
68    pub schema_codegen_jobs: Vec<SchemaCodegenJobs>,
69}
70
71impl PathReferenceNamespaceResolver for HydrateProjectConfiguration {
72    fn namespace_root(
73        &self,
74        namespace: &str,
75    ) -> Option<PathBuf> {
76        for src in &self.id_based_asset_sources {
77            if src.name == namespace {
78                return Some(src.path.clone());
79            }
80        }
81
82        for src in &self.path_based_asset_sources {
83            if src.name == namespace {
84                return Some(src.path.clone());
85            }
86        }
87
88        for src in &self.source_file_locations {
89            if src.name == namespace {
90                return Some(src.path.clone());
91            }
92        }
93
94        None
95    }
96
97    fn simplify_path(
98        &self,
99        path: &Path,
100    ) -> Option<(String, PathBuf)> {
101        for src in &self.id_based_asset_sources {
102            if let Ok(path) = path.strip_prefix(&src.path) {
103                return Some((src.name.clone(), path.to_path_buf()));
104            }
105        }
106
107        for src in &self.path_based_asset_sources {
108            if let Ok(path) = path.strip_prefix(&src.path) {
109                return Some((src.name.clone(), path.to_path_buf()));
110            }
111        }
112
113        for src in &self.source_file_locations {
114            if let Ok(path) = path.strip_prefix(&src.path) {
115                return Some((src.name.clone(), path.to_path_buf()));
116            }
117        }
118
119        None
120    }
121}
122
123impl HydrateProjectConfiguration {
124    pub fn unverified_absolute_path(
125        root_path: &Path,
126        json_path: &str,
127    ) -> PathBuf {
128        if Path::new(json_path).is_absolute() {
129            PathBuf::from(json_path)
130        } else {
131            root_path.join(json_path)
132        }
133    }
134
135    // root_path is the path the json file is in, json_path is the string in json that is meant
136    // to be parsed/converted to a canonicalized path
137    pub fn parse_dir_path(
138        root_path: &Path,
139        json_path: &str,
140    ) -> Result<PathBuf, Box<dyn Error>> {
141        // If it's not an absolute path, join it onto the path containing the project file
142        let joined_path = Self::unverified_absolute_path(root_path, json_path);
143
144        // Create the dir (and it's parent dirs) if it doesn't exist
145        if !joined_path.exists() {
146            std::fs::create_dir_all(&joined_path)?;
147        }
148
149        // Canonicalize the path
150        Ok(dunce::canonicalize(&joined_path).map_err(|e| e.to_string())?)
151    }
152
153    pub fn read_from_path(path: &Path) -> Result<Self, Box<dyn Error>> {
154        let root_path = dunce::canonicalize(
155            path.parent()
156                .ok_or_else(|| "Parent of project file path could not be found".to_string())?,
157        )?;
158        let file_contents = std::fs::read_to_string(path)?;
159        let project_file: HydrateProjectConfigurationJson = serde_json::from_str(&file_contents)?;
160
161        let import_data_path = Self::parse_dir_path(&root_path, &project_file.import_data_path)?;
162        let build_data_path = Self::parse_dir_path(&root_path, &project_file.build_data_path)?;
163        let job_data_path = Self::parse_dir_path(&root_path, &project_file.job_data_path)?;
164
165        let mut schema_def_paths = Vec::default();
166        for path in &project_file.schema_def_paths {
167            schema_def_paths.push(Self::parse_dir_path(&root_path, path)?)
168        }
169
170        let mut id_based_asset_sources = Vec::default();
171        for pair in project_file.id_based_asset_sources {
172            id_based_asset_sources.push(NamePathPair {
173                name: pair.name,
174                path: Self::parse_dir_path(&root_path, &pair.path)?,
175            });
176        }
177
178        let mut path_based_asset_sources = Vec::default();
179        for pair in project_file.path_based_asset_sources {
180            path_based_asset_sources.push(NamePathPair {
181                name: pair.name,
182                path: Self::parse_dir_path(&root_path, &pair.path)?,
183            });
184        }
185
186        let mut source_file_locations = Vec::default();
187        for pair in project_file.source_file_locations {
188            source_file_locations.push(NamePathPair {
189                name: pair.name,
190                path: Self::parse_dir_path(&root_path, &pair.path)?,
191            });
192        }
193
194        // We don't canonicalize/verify the codegen paths
195        let mut schema_codegen_jobs = Vec::default();
196        for schema_codegen_job in project_file.schema_codegen_jobs {
197            let mut included_schema_paths = Vec::default();
198            for included_schema_path in &schema_codegen_job.included_schema_paths {
199                included_schema_paths.push(Self::unverified_absolute_path(
200                    &root_path,
201                    included_schema_path,
202                ));
203            }
204
205            schema_codegen_jobs.push(SchemaCodegenJobs {
206                name: schema_codegen_job.name,
207                schema_path: Self::unverified_absolute_path(
208                    &root_path,
209                    &schema_codegen_job.schema_path,
210                ),
211                included_schema_paths,
212                outfile: Self::unverified_absolute_path(&root_path, &schema_codegen_job.outfile),
213            })
214        }
215
216        Ok(HydrateProjectConfiguration {
217            schema_def_paths,
218            import_data_path,
219            build_data_path,
220            job_data_path,
221            id_based_asset_sources,
222            path_based_asset_sources,
223            source_file_locations,
224            schema_codegen_jobs,
225        })
226    }
227
228    pub fn locate_project_file(search_location: &Path) -> Result<Self, Box<dyn Error>> {
229        let mut path = Some(search_location.to_path_buf());
230        while let Some(p) = path {
231            let joined_path = p.join("hydrate_project.json");
232            if joined_path.exists() {
233                log::info!("Using project configuration at {:?}", joined_path);
234                return Self::read_from_path(&joined_path);
235            }
236
237            path = p.parent().map(|x| x.to_path_buf());
238        }
239
240        Err(format!(
241            "hydrate_project.json could not be located at {:?} or in any of its parent directories",
242            search_location
243        ))?
244    }
245}