Skip to main content

api_testing_core/graphql/
schema_file.rs

1use std::path::{Path, PathBuf};
2
3use crate::{Result, env_file};
4
5const FALLBACK_CANDIDATES: &[&str] = &[
6    "schema.gql",
7    "schema.graphql",
8    "schema.graphqls",
9    "api.graphql",
10    "api.gql",
11];
12
13fn resolve_relative_under_setup(setup_dir: &Path, rel: &Path) -> PathBuf {
14    if rel.is_absolute() {
15        return rel.to_path_buf();
16    }
17
18    let parent = rel.parent().unwrap_or_else(|| Path::new("."));
19    let parent_abs = std::fs::canonicalize(setup_dir.join(parent)).unwrap_or_else(|_| {
20        // best-effort: mirror prior script behavior which canonicalizes the directory when possible.
21        setup_dir.join(parent)
22    });
23    let filename = rel.file_name().unwrap_or(rel.as_os_str());
24    parent_abs.join(filename)
25}
26
27pub fn resolve_schema_path(setup_dir: &Path, schema_file_arg: Option<&str>) -> Result<PathBuf> {
28    let schema_file = schema_file_arg
29        .map(str::trim)
30        .filter(|s| !s.is_empty())
31        .map(|s| s.to_string())
32        .or_else(|| {
33            std::env::var("GQL_SCHEMA_FILE").ok().and_then(|s| {
34                let s = s.trim().to_string();
35                (!s.is_empty()).then_some(s)
36            })
37        })
38        .or_else(|| {
39            let schema_local = setup_dir.join("schema.local.env");
40            let schema_env = setup_dir.join("schema.env");
41            let files: Vec<&Path> = vec![&schema_env, &schema_local];
42            env_file::read_var_last_wins("GQL_SCHEMA_FILE", &files)
43                .ok()
44                .flatten()
45        })
46        .or_else(|| {
47            for c in FALLBACK_CANDIDATES {
48                if setup_dir.join(c).is_file() {
49                    return Some((*c).to_string());
50                }
51            }
52            None
53        });
54
55    let Some(schema_file) = schema_file else {
56        anyhow::bail!(
57            "Schema file not configured. Set GQL_SCHEMA_FILE in schema.env (or pass --file)."
58        );
59    };
60
61    let schema_path = resolve_relative_under_setup(setup_dir, Path::new(&schema_file));
62    if !schema_path.is_file() {
63        anyhow::bail!("Schema file not found: {}", schema_path.display());
64    }
65
66    Ok(schema_path)
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use pretty_assertions::assert_eq;
73    use tempfile::TempDir;
74
75    use std::sync::{Mutex, OnceLock};
76
77    fn env_lock() -> &'static Mutex<()> {
78        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
79        LOCK.get_or_init(|| Mutex::new(()))
80    }
81
82    fn write_file(path: &Path, contents: &str) {
83        std::fs::create_dir_all(path.parent().expect("parent")).expect("mkdir");
84        std::fs::write(path, contents).expect("write");
85    }
86
87    #[test]
88    fn schema_file_arg_is_trimmed_and_resolved_under_setup() {
89        let tmp = TempDir::new().expect("tmp");
90        let setup_dir = std::fs::canonicalize(tmp.path()).expect("setup abs");
91
92        write_file(
93            &setup_dir.join("schemas/api.graphql"),
94            "schema { query: Query }\n",
95        );
96
97        let got = resolve_schema_path(&setup_dir, Some("  schemas/api.graphql  ")).expect("path");
98        let expected = std::fs::canonicalize(setup_dir.join("schemas/api.graphql")).expect("abs");
99        assert_eq!(got, expected);
100    }
101
102    #[test]
103    fn schema_file_env_var_is_used_when_no_arg() {
104        let _guard = env_lock().lock().expect("lock env");
105        let old = std::env::var("GQL_SCHEMA_FILE").ok();
106        // SAFETY: tests mutate process env while guarded by env_lock.
107        unsafe { std::env::set_var("GQL_SCHEMA_FILE", "  schema.gql  ") };
108
109        let tmp = TempDir::new().expect("tmp");
110        let setup_dir = std::fs::canonicalize(tmp.path()).expect("setup abs");
111        write_file(&setup_dir.join("schema.gql"), "schema { query: Query }\n");
112
113        let got = resolve_schema_path(&setup_dir, None).expect("path");
114        let expected = std::fs::canonicalize(setup_dir.join("schema.gql")).expect("abs");
115        assert_eq!(got, expected);
116
117        match old {
118            Some(v) => {
119                // SAFETY: tests restore process env while guarded by env_lock.
120                unsafe { std::env::set_var("GQL_SCHEMA_FILE", v) };
121            }
122            None => {
123                // SAFETY: tests restore process env while guarded by env_lock.
124                unsafe { std::env::remove_var("GQL_SCHEMA_FILE") };
125            }
126        }
127    }
128
129    #[test]
130    fn schema_file_schema_env_is_used_when_no_arg_or_env() {
131        let _guard = env_lock().lock().expect("lock env");
132        let old = std::env::var("GQL_SCHEMA_FILE").ok();
133        // SAFETY: tests mutate process env while guarded by env_lock.
134        unsafe { std::env::remove_var("GQL_SCHEMA_FILE") };
135
136        let tmp = TempDir::new().expect("tmp");
137        let setup_dir = std::fs::canonicalize(tmp.path()).expect("setup abs");
138
139        write_file(
140            &setup_dir.join("schema.env"),
141            "export GQL_SCHEMA_FILE=schemas/schema.graphql\n",
142        );
143        write_file(
144            &setup_dir.join("schemas/schema.graphql"),
145            "schema { query: Query }\n",
146        );
147
148        let got = resolve_schema_path(&setup_dir, None).expect("path");
149        let expected =
150            std::fs::canonicalize(setup_dir.join("schemas/schema.graphql")).expect("abs");
151        assert_eq!(got, expected);
152
153        match old {
154            Some(v) => {
155                // SAFETY: tests restore process env while guarded by env_lock.
156                unsafe { std::env::set_var("GQL_SCHEMA_FILE", v) };
157            }
158            None => {
159                // SAFETY: tests restore process env while guarded by env_lock.
160                unsafe { std::env::remove_var("GQL_SCHEMA_FILE") };
161            }
162        }
163    }
164
165    #[test]
166    fn schema_file_falls_back_to_candidate_filenames() {
167        let _guard = env_lock().lock().expect("lock env");
168        let old = std::env::var("GQL_SCHEMA_FILE").ok();
169        // SAFETY: tests mutate process env while guarded by env_lock.
170        unsafe { std::env::remove_var("GQL_SCHEMA_FILE") };
171
172        let tmp = TempDir::new().expect("tmp");
173        let setup_dir = std::fs::canonicalize(tmp.path()).expect("setup abs");
174
175        write_file(&setup_dir.join("schema.gql"), "schema { query: Query }\n");
176
177        let got = resolve_schema_path(&setup_dir, None).expect("path");
178        let expected = std::fs::canonicalize(setup_dir.join("schema.gql")).expect("abs");
179        assert_eq!(got, expected);
180
181        match old {
182            Some(v) => {
183                // SAFETY: tests restore process env while guarded by env_lock.
184                unsafe { std::env::set_var("GQL_SCHEMA_FILE", v) };
185            }
186            None => {
187                // SAFETY: tests restore process env while guarded by env_lock.
188                unsafe { std::env::remove_var("GQL_SCHEMA_FILE") };
189            }
190        }
191    }
192
193    #[test]
194    fn schema_file_errors_when_not_configured() {
195        let _guard = env_lock().lock().expect("lock env");
196        let old = std::env::var("GQL_SCHEMA_FILE").ok();
197        // SAFETY: tests mutate process env while guarded by env_lock.
198        unsafe { std::env::remove_var("GQL_SCHEMA_FILE") };
199
200        let tmp = TempDir::new().expect("tmp");
201        let setup_dir = std::fs::canonicalize(tmp.path()).expect("setup abs");
202
203        let err = resolve_schema_path(&setup_dir, None).unwrap_err();
204        assert!(
205            err.to_string()
206                .contains("Schema file not configured. Set GQL_SCHEMA_FILE")
207        );
208
209        match old {
210            Some(v) => {
211                // SAFETY: tests restore process env while guarded by env_lock.
212                unsafe { std::env::set_var("GQL_SCHEMA_FILE", v) };
213            }
214            None => {
215                // SAFETY: tests restore process env while guarded by env_lock.
216                unsafe { std::env::remove_var("GQL_SCHEMA_FILE") };
217            }
218        }
219    }
220}