api_testing_core/graphql/
schema_file.rs1use 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 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 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 unsafe { std::env::set_var("GQL_SCHEMA_FILE", v) };
121 }
122 None => {
123 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 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 unsafe { std::env::set_var("GQL_SCHEMA_FILE", v) };
157 }
158 None => {
159 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 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 unsafe { std::env::set_var("GQL_SCHEMA_FILE", v) };
185 }
186 None => {
187 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 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 unsafe { std::env::set_var("GQL_SCHEMA_FILE", v) };
213 }
214 None => {
215 unsafe { std::env::remove_var("GQL_SCHEMA_FILE") };
217 }
218 }
219 }
220}