Skip to main content

api_testing_core/graphql/
vars.rs

1use std::path::{Path, PathBuf};
2
3use anyhow::Context;
4
5use crate::Result;
6
7#[derive(Debug, Clone, PartialEq)]
8pub struct GraphqlVariablesFile {
9    pub path: PathBuf,
10    pub variables: serde_json::Value,
11    /// Number of numeric `limit` fields bumped to the configured minimum.
12    pub bumped_limit_fields: usize,
13}
14
15fn bump_limits_in_json(value: &mut serde_json::Value, min_limit: u64) -> usize {
16    match value {
17        serde_json::Value::Object(map) => {
18            let mut bumped = 0usize;
19            for (k, v) in map.iter_mut() {
20                if k == "limit"
21                    && let serde_json::Value::Number(n) = v
22                    && let Some(as_f64) = n.as_f64()
23                    && as_f64 < (min_limit as f64)
24                {
25                    *n = serde_json::Number::from(min_limit);
26                    bumped += 1;
27                }
28                bumped += bump_limits_in_json(v, min_limit);
29            }
30            bumped
31        }
32        serde_json::Value::Array(values) => values
33            .iter_mut()
34            .map(|v| bump_limits_in_json(v, min_limit))
35            .sum(),
36        _ => 0,
37    }
38}
39
40impl GraphqlVariablesFile {
41    pub fn load(path: impl AsRef<Path>, min_limit: u64) -> Result<Self> {
42        let path = path.as_ref();
43        let bytes = std::fs::read(path)
44            .with_context(|| format!("read variables file: {}", path.display()))?;
45        let mut variables: serde_json::Value = serde_json::from_slice(&bytes)
46            .with_context(|| format!("Variables file is not valid JSON: {}", path.display()))?;
47
48        let bumped_limit_fields = if min_limit > 0 {
49            bump_limits_in_json(&mut variables, min_limit)
50        } else {
51            0
52        };
53
54        Ok(Self {
55            path: std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()),
56            variables,
57            bumped_limit_fields,
58        })
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65    use pretty_assertions::assert_eq;
66
67    use tempfile::TempDir;
68
69    fn write(path: &Path, contents: &str) {
70        std::fs::create_dir_all(path.parent().expect("parent")).expect("mkdir");
71        std::fs::write(path, contents).expect("write");
72    }
73
74    #[test]
75    fn graphql_vars_bumps_nested_limit_fields() {
76        let tmp = TempDir::new().expect("tmp");
77        let vars_path = tmp.path().join("vars.json");
78        write(
79            &vars_path,
80            r#"{"limit":1,"nested":{"limit":2},"arr":[{"limit":3},{"limit":10}],"str":{"limit":"2"}}"#,
81        );
82
83        let vars = GraphqlVariablesFile::load(&vars_path, 5).expect("load");
84        assert_eq!(vars.bumped_limit_fields, 3);
85        assert_eq!(vars.variables["limit"], 5);
86        assert_eq!(vars.variables["nested"]["limit"], 5);
87        assert_eq!(vars.variables["arr"][0]["limit"], 5);
88        assert_eq!(vars.variables["arr"][1]["limit"], 10);
89        assert_eq!(vars.variables["str"]["limit"], "2");
90    }
91
92    #[test]
93    fn graphql_vars_invalid_json_includes_path() {
94        let tmp = TempDir::new().expect("tmp");
95        let vars_path = tmp.path().join("vars.json");
96        write(&vars_path, "{nope");
97
98        let err = GraphqlVariablesFile::load(&vars_path, 5).unwrap_err();
99        let msg = format!("{err:#}");
100        assert!(msg.contains("Variables file is not valid JSON"));
101        assert!(msg.contains(vars_path.to_string_lossy().as_ref()));
102    }
103}