cli/commands/internal/graphql/
list.rs

1use anyhow::{anyhow, Result};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, clap::Args)]
5pub struct List {
6    /// GraphQL server URL
7    #[arg(long, default_value = "http://localhost:3000/graphql")]
8    pub node: String,
9}
10
11#[derive(Debug, Serialize)]
12struct GraphQLRequest {
13    query: String,
14}
15
16#[derive(Debug, Deserialize)]
17struct GraphQLResponse {
18    data: Option<serde_json::Value>,
19    errors: Option<Vec<GraphQLError>>,
20}
21
22#[derive(Debug, Deserialize)]
23struct GraphQLError {
24    message: String,
25}
26
27impl List {
28    pub fn run(self) -> Result<()> {
29        // GraphQL introspection query to get all queries, mutations, and subscriptions
30        let introspection_query = r#"
31            query IntrospectSchema {
32                __schema {
33                    queryType { name }
34                    mutationType { name }
35                    subscriptionType { name }
36                    types {
37                        name
38                        kind
39                        fields {
40                            name
41                        }
42                    }
43                }
44            }
45        "#;
46
47        let request = GraphQLRequest {
48            query: introspection_query.to_string(),
49        };
50
51        // Make the introspection request
52        let client = reqwest::blocking::Client::new();
53        let response = client
54            .post(&self.node)
55            .json(&request)
56            .send()
57            .map_err(|e| anyhow!("Failed to connect to GraphQL server: {}", e))?;
58
59        if !response.status().is_success() {
60            return Err(anyhow!(
61                "GraphQL server returned error: {}",
62                response.status()
63            ));
64        }
65
66        let graphql_response: GraphQLResponse = response
67            .json()
68            .map_err(|e| anyhow!("Failed to parse GraphQL response: {}", e))?;
69
70        if let Some(errors) = graphql_response.errors {
71            for error in errors {
72                eprintln!("GraphQL Error: {}", error.message);
73            }
74            return Err(anyhow!("GraphQL introspection failed"));
75        }
76
77        let data = graphql_response
78            .data
79            .ok_or_else(|| anyhow!("No data in GraphQL response"))?;
80
81        // Parse and display the schema
82        self.display_endpoints(&data)?;
83
84        Ok(())
85    }
86
87    fn display_endpoints(&self, schema_data: &serde_json::Value) -> Result<()> {
88        let schema = schema_data
89            .get("__schema")
90            .ok_or_else(|| anyhow!("Invalid schema response"))?;
91
92        let types = schema
93            .get("types")
94            .and_then(|v| v.as_array())
95            .ok_or_else(|| anyhow!("Invalid types in schema"))?;
96
97        // Helper function to get and display endpoints
98        let get_endpoints = |type_name: &str| -> Vec<String> {
99            types
100                .iter()
101                .find(|t| {
102                    t.get("name")
103                        .and_then(|n| n.as_str())
104                        .map(|n| n == type_name)
105                        .unwrap_or(false)
106                })
107                .and_then(|t| t.get("fields"))
108                .and_then(|f| f.as_array())
109                .map(|fields| {
110                    let mut names: Vec<String> = fields
111                        .iter()
112                        .filter_map(|f| f.get("name").and_then(|n| n.as_str()).map(String::from))
113                        .collect();
114                    names.sort();
115                    names
116                })
117                .unwrap_or_default()
118        };
119
120        // Get queries
121        let queries = if let Some(query_type_name) = schema
122            .get("queryType")
123            .and_then(|v| v.get("name"))
124            .and_then(|v| v.as_str())
125        {
126            get_endpoints(query_type_name)
127        } else {
128            Vec::new()
129        };
130
131        // Get mutations
132        let mutations = if let Some(mutation_type_name) = schema
133            .get("mutationType")
134            .and_then(|v| v.get("name"))
135            .and_then(|v| v.as_str())
136        {
137            get_endpoints(mutation_type_name)
138        } else {
139            Vec::new()
140        };
141
142        // Get subscriptions
143        let subscriptions = if let Some(subscription_type_name) = schema
144            .get("subscriptionType")
145            .and_then(|v| v.get("name"))
146            .and_then(|v| v.as_str())
147        {
148            get_endpoints(subscription_type_name)
149        } else {
150            Vec::new()
151        };
152
153        // Display queries
154        if !queries.is_empty() {
155            println!("Queries ({}):", queries.len());
156            for query in &queries {
157                println!("  - {}", query);
158            }
159        }
160
161        // Display mutations
162        if !mutations.is_empty() {
163            println!("Mutations ({}):", mutations.len());
164            for mutation in &mutations {
165                println!("  - {}", mutation);
166            }
167        }
168
169        // Display subscriptions
170        if !subscriptions.is_empty() {
171            println!("Subscriptions ({}):", subscriptions.len());
172            for subscription in &subscriptions {
173                println!("  - {}", subscription);
174            }
175        }
176
177        // Display example curl command
178        let node_url = &self.node;
179        println!(
180            "\nExample: curl -s -X POST {} -H 'Content-Type: application/json' \\",
181            node_url
182        );
183        println!("         -d '{{\"query\": \"{{ daemonStatus {{ chainId blockchainLength numAccounts }} }}\"}}'");
184
185        Ok(())
186    }
187}