Skip to main content

api_testing_core/graphql/
schema_file.rs

1use std::path::{Path, PathBuf};
2
3use crate::{Result, env_file};
4use nils_common::env as shared_env;
5
6const FALLBACK_CANDIDATES: &[&str] = &[
7    "schema.gql",
8    "schema.graphql",
9    "schema.graphqls",
10    "api.graphql",
11    "api.gql",
12];
13
14fn resolve_relative_under_setup(setup_dir: &Path, rel: &Path) -> PathBuf {
15    if rel.is_absolute() {
16        return rel.to_path_buf();
17    }
18
19    let parent = rel.parent().unwrap_or_else(|| Path::new("."));
20    let parent_abs = std::fs::canonicalize(setup_dir.join(parent)).unwrap_or_else(|_| {
21        // best-effort: mirror prior script behavior which canonicalizes the directory when possible.
22        setup_dir.join(parent)
23    });
24    let filename = rel.file_name().unwrap_or(rel.as_os_str());
25    parent_abs.join(filename)
26}
27
28pub fn resolve_schema_path(setup_dir: &Path, schema_file_arg: Option<&str>) -> Result<PathBuf> {
29    let schema_file = schema_file_arg
30        .map(str::trim)
31        .filter(|s| !s.is_empty())
32        .map(|s| s.to_string())
33        .or_else(|| shared_env::env_non_empty("GQL_SCHEMA_FILE"))
34        .or_else(|| {
35            let schema_local = setup_dir.join("schema.local.env");
36            let schema_env = setup_dir.join("schema.env");
37            let files: Vec<&Path> = vec![&schema_env, &schema_local];
38            env_file::read_var_last_wins("GQL_SCHEMA_FILE", &files)
39                .ok()
40                .flatten()
41        })
42        .or_else(|| {
43            for c in FALLBACK_CANDIDATES {
44                if setup_dir.join(c).is_file() {
45                    return Some((*c).to_string());
46                }
47            }
48            None
49        });
50
51    let Some(schema_file) = schema_file else {
52        anyhow::bail!(
53            "Schema file not configured. Set GQL_SCHEMA_FILE in schema.env (or pass --file)."
54        );
55    };
56
57    let schema_path = resolve_relative_under_setup(setup_dir, Path::new(&schema_file));
58    if !schema_path.is_file() {
59        anyhow::bail!("Schema file not found: {}", schema_path.display());
60    }
61
62    Ok(schema_path)
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use nils_test_support::{EnvGuard, GlobalStateLock};
69    use pretty_assertions::assert_eq;
70    use tempfile::TempDir;
71
72    fn write_file(path: &Path, contents: &str) {
73        std::fs::create_dir_all(path.parent().expect("parent")).expect("mkdir");
74        std::fs::write(path, contents).expect("write");
75    }
76
77    #[test]
78    fn schema_file_arg_is_trimmed_and_resolved_under_setup() {
79        let tmp = TempDir::new().expect("tmp");
80        let setup_dir = std::fs::canonicalize(tmp.path()).expect("setup abs");
81
82        write_file(
83            &setup_dir.join("schemas/api.graphql"),
84            "schema { query: Query }\n",
85        );
86
87        let got = resolve_schema_path(&setup_dir, Some("  schemas/api.graphql  ")).expect("path");
88        let expected = std::fs::canonicalize(setup_dir.join("schemas/api.graphql")).expect("abs");
89        assert_eq!(got, expected);
90    }
91
92    #[test]
93    fn schema_file_env_var_is_used_when_no_arg() {
94        let lock = GlobalStateLock::new();
95        let _guard = EnvGuard::set(&lock, "GQL_SCHEMA_FILE", "  schema.gql  ");
96
97        let tmp = TempDir::new().expect("tmp");
98        let setup_dir = std::fs::canonicalize(tmp.path()).expect("setup abs");
99        write_file(&setup_dir.join("schema.gql"), "schema { query: Query }\n");
100
101        let got = resolve_schema_path(&setup_dir, None).expect("path");
102        let expected = std::fs::canonicalize(setup_dir.join("schema.gql")).expect("abs");
103        assert_eq!(got, expected);
104    }
105
106    #[test]
107    fn schema_file_schema_env_is_used_when_no_arg_or_env() {
108        let lock = GlobalStateLock::new();
109        let _guard = EnvGuard::remove(&lock, "GQL_SCHEMA_FILE");
110
111        let tmp = TempDir::new().expect("tmp");
112        let setup_dir = std::fs::canonicalize(tmp.path()).expect("setup abs");
113
114        write_file(
115            &setup_dir.join("schema.env"),
116            "export GQL_SCHEMA_FILE=schemas/schema.graphql\n",
117        );
118        write_file(
119            &setup_dir.join("schemas/schema.graphql"),
120            "schema { query: Query }\n",
121        );
122
123        let got = resolve_schema_path(&setup_dir, None).expect("path");
124        let expected =
125            std::fs::canonicalize(setup_dir.join("schemas/schema.graphql")).expect("abs");
126        assert_eq!(got, expected);
127    }
128
129    #[test]
130    fn schema_file_falls_back_to_candidate_filenames() {
131        let lock = GlobalStateLock::new();
132        let _guard = EnvGuard::remove(&lock, "GQL_SCHEMA_FILE");
133
134        let tmp = TempDir::new().expect("tmp");
135        let setup_dir = std::fs::canonicalize(tmp.path()).expect("setup abs");
136
137        write_file(&setup_dir.join("schema.gql"), "schema { query: Query }\n");
138
139        let got = resolve_schema_path(&setup_dir, None).expect("path");
140        let expected = std::fs::canonicalize(setup_dir.join("schema.gql")).expect("abs");
141        assert_eq!(got, expected);
142    }
143
144    #[test]
145    fn schema_file_errors_when_not_configured() {
146        let lock = GlobalStateLock::new();
147        let _guard = EnvGuard::remove(&lock, "GQL_SCHEMA_FILE");
148
149        let tmp = TempDir::new().expect("tmp");
150        let setup_dir = std::fs::canonicalize(tmp.path()).expect("setup abs");
151
152        let err = resolve_schema_path(&setup_dir, None).unwrap_err();
153        assert!(
154            err.to_string()
155                .contains("Schema file not configured. Set GQL_SCHEMA_FILE")
156        );
157    }
158}