Skip to main content

fakecloud_appsync/
schema.rs

1//! Derived GraphQL-schema representations for `GetIntrospectionSchema`.
2//!
3//! AppSync's real `GetIntrospectionSchema` returns the API's schema either as
4//! SDL text or as a JSON introspection document. fakecloud stores the raw SDL
5//! ingested by `StartSchemaCreation`; this module derives the requested
6//! representation from it:
7//!
8//! * `SDL` -> the stored SDL document verbatim.
9//! * `JSON` -> a deterministic JSON *skeleton* that lists the type names parsed
10//!   out of the SDL under a `__schema.types` array. This is a faithful,
11//!   honestly-scoped projection of the stored schema, NOT a full GraphQL
12//!   introspection response (field/arg/directive graphs are not reconstructed).
13
14use serde_json::{json, Value};
15
16/// Extract the top-level type names declared in an SDL document. Recognises the
17/// `type`, `input`, `enum`, `interface`, `union`, and `scalar` keywords. This is
18/// a lightweight lexical scan, sufficient to project a deterministic type list.
19pub fn type_names(sdl: &str) -> Vec<String> {
20    let mut names = Vec::new();
21    for line in sdl.lines() {
22        let t = line.trim();
23        for kw in [
24            "type ",
25            "input ",
26            "enum ",
27            "interface ",
28            "union ",
29            "scalar ",
30        ] {
31            if let Some(rest) = t.strip_prefix(kw) {
32                if let Some(name) = rest
33                    .split(|c: char| c.is_whitespace() || c == '{' || c == '(' || c == '=')
34                    .next()
35                {
36                    if !name.is_empty() {
37                        names.push(name.to_string());
38                    }
39                }
40            }
41        }
42    }
43    names
44}
45
46/// Build the schema representation bytes for the requested `format`.
47///
48/// `format` is the `OutputType` query param (`SDL` or `JSON`). Any other value
49/// falls back to SDL.
50pub fn introspection_bytes(sdl: &str, format: &str) -> Vec<u8> {
51    if format.eq_ignore_ascii_case("JSON") {
52        let types: Vec<Value> = type_names(sdl)
53            .into_iter()
54            .map(|n| json!({ "kind": "OBJECT", "name": n }))
55            .collect();
56        let doc = json!({
57            "__schema": {
58                "queryType": { "name": "Query" },
59                "types": types,
60            }
61        });
62        serde_json::to_vec(&doc).unwrap_or_default()
63    } else {
64        sdl.as_bytes().to_vec()
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn parses_type_names() {
74        let sdl =
75            "type Query { hello: String }\ntype Post { id: ID! }\ninput NewPost { t: String }";
76        let names = type_names(sdl);
77        assert!(names.contains(&"Query".to_string()));
78        assert!(names.contains(&"Post".to_string()));
79        assert!(names.contains(&"NewPost".to_string()));
80    }
81
82    #[test]
83    fn sdl_format_is_verbatim() {
84        let sdl = "type Query { hello: String }";
85        assert_eq!(introspection_bytes(sdl, "SDL"), sdl.as_bytes());
86    }
87
88    #[test]
89    fn json_format_lists_types() {
90        let sdl = "type Query { hello: String }";
91        let bytes = introspection_bytes(sdl, "JSON");
92        let v: Value = serde_json::from_slice(&bytes).unwrap();
93        assert_eq!(v["__schema"]["types"][0]["name"], json!("Query"));
94    }
95}