spacetimedb_cli/subcommands/
describe.rs1use crate::api::ClientApi;
2use crate::common_args;
3use crate::config::Config;
4use crate::sql::parse_req;
5use crate::util::UNSTABLE_WARNING;
6use anyhow::Context;
7use clap::{Arg, ArgAction, ArgMatches};
8use spacetimedb_lib::sats;
9
10pub fn cli() -> clap::Command {
11 clap::Command::new("describe")
12 .about(format!(
13 "Describe the structure of a database or entities within it. {}",
14 UNSTABLE_WARNING
15 ))
16 .arg(
17 Arg::new("database")
18 .required(true)
19 .help("The name or identity of the database to describe"),
20 )
21 .arg(
22 Arg::new("entity_type")
23 .value_parser(clap::value_parser!(EntityType))
24 .requires("entity_name")
25 .help("Whether to describe a reducer or table"),
26 )
27 .arg(
28 Arg::new("entity_name")
29 .requires("entity_type")
30 .help("The name of the entity to describe"),
31 )
32 .arg(
33 Arg::new("json")
34 .long("json")
35 .action(ArgAction::SetTrue)
36 .required(true)
38 .help(
39 "Output the schema in JSON format. Currently required; in the future, omitting this will \
40 give human-readable output.",
41 ),
42 )
43 .arg(common_args::anonymous())
44 .arg(common_args::server().help("The nickname, host name or URL of the server hosting the database"))
45 .arg(common_args::yes())
46 .after_help("Run `spacetime help describe` for more detailed information.\n")
47}
48
49#[derive(clap::ValueEnum, Clone, Copy)]
50enum EntityType {
51 Reducer,
52 Table,
53}
54
55pub async fn exec(config: Config, args: &ArgMatches) -> Result<(), anyhow::Error> {
56 eprintln!("{}\n", UNSTABLE_WARNING);
57
58 let entity_name = args.get_one::<String>("entity_name");
59 let entity_type = args.get_one::<EntityType>("entity_type");
60 let entity = entity_type.zip(entity_name);
61 let json = args.get_flag("json");
62
63 let conn = parse_req(config, args).await?;
64 let api = ClientApi::new(conn);
65
66 let module_def = api.module_def().await?;
67
68 if json {
69 fn sats_to_json<T: sats::Serialize>(v: &T) -> serde_json::Result<String> {
70 serde_json::to_string_pretty(sats::serde::SerdeWrapper::from_ref(v))
71 }
72 let json = match entity {
73 Some((EntityType::Reducer, reducer_name)) => {
74 let reducer = module_def
75 .reducers
76 .iter()
77 .find(|r| *r.name == **reducer_name)
78 .context("no such reducer")?;
79 sats_to_json(reducer)?
80 }
81 Some((EntityType::Table, table_name)) => {
82 let table = module_def
83 .tables
84 .iter()
85 .find(|t| *t.name == **table_name)
86 .context("no such table")?;
87 sats_to_json(table)?
88 }
89 None => sats_to_json(&module_def)?,
90 };
91
92 println!("{json}");
93 } else {
94 }
96
97 Ok(())
98}