cli/commands/internal/graphql/
list.rs1use anyhow::{anyhow, Result};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, clap::Args)]
5pub struct List {
6 #[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 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 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 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 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 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 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 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 if !queries.is_empty() {
155 println!("Queries ({}):", queries.len());
156 for query in &queries {
157 println!(" - {}", query);
158 }
159 }
160
161 if !mutations.is_empty() {
163 println!("Mutations ({}):", mutations.len());
164 for mutation in &mutations {
165 println!(" - {}", mutation);
166 }
167 }
168
169 if !subscriptions.is_empty() {
171 println!("Subscriptions ({}):", subscriptions.len());
172 for subscription in &subscriptions {
173 println!(" - {}", subscription);
174 }
175 }
176
177 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}