Skip to main content

fedimint_meta_client/
cli.rs

1use std::str::FromStr;
2use std::{ffi, iter};
3
4use clap::Parser;
5use fedimint_api_client::api::FederationError;
6use fedimint_meta_common::{DEFAULT_META_KEY, MetaConsensusValue, MetaKey, MetaValue};
7use serde::Serialize;
8use serde_json::json;
9
10use super::MetaClientModule;
11use crate::MetaAdminError;
12use crate::api::MetaFederationApi;
13
14#[derive(Parser, Serialize)]
15enum Opts {
16    /// Get current consensus value
17    Get {
18        #[arg(long, default_value_t = DEFAULT_META_KEY)]
19        key: MetaKey,
20        #[arg(long)]
21        hex: bool,
22    },
23    /// Get current consensus value revision
24    GetRev {
25        #[arg(long, default_value_t = DEFAULT_META_KEY)]
26        key: MetaKey,
27    },
28    /// Get value change submissions
29    GetSubmissions {
30        #[arg(long, default_value_t = DEFAULT_META_KEY)]
31        key: MetaKey,
32        #[arg(long)]
33        hex: bool,
34    },
35    /// Submit value change proposal
36    Submit {
37        #[arg(long, default_value_t = DEFAULT_META_KEY)]
38        key: MetaKey,
39        value: String,
40        #[arg(long)]
41        hex: bool,
42    },
43}
44
45pub(crate) async fn handle_cli_command(
46    meta: &MetaClientModule,
47    args: &[ffi::OsString],
48) -> Result<serde_json::Value, CliCommandError> {
49    let opts = Opts::parse_from(iter::once(&ffi::OsString::from("meta")).chain(args.iter()));
50
51    let res = match opts {
52        Opts::Get { key, hex } => match meta.module_api.get_consensus(key).await? {
53            Some(MetaConsensusValue { revision, value }) => {
54                let value = if hex {
55                    serde_json::to_value(value).expect("can't fail")
56                } else {
57                    value
58                        .to_json_lossy()
59                        .map_err(CliCommandError::ConsensusValueJson)?
60                };
61                json!({
62                    "revision": revision,
63                    "value": value
64                })
65            }
66            _ => serde_json::Value::Null,
67        },
68        Opts::GetRev { key } => match meta.module_api.get_consensus_rev(key).await? {
69            Some(rev) => {
70                json!({
71                    "revision": rev,
72                })
73            }
74            _ => serde_json::Value::Null,
75        },
76        Opts::GetSubmissions { key, hex } => {
77            let submissions = meta
78                .module_api
79                .get_submissions(key, meta.admin_auth()?)
80                .await?;
81            let submissions: serde_json::Map<String, serde_json::Value> = submissions
82                .into_iter()
83                .map(|(peer_id, value)| -> Result<_, CliCommandError> {
84                    let value = if hex {
85                        serde_json::Value::String(value.to_string())
86                    } else {
87                        serde_json::from_reader(value.as_slice())
88                            .map_err(CliCommandError::SubmissionValueJson)?
89                    };
90
91                    Ok((peer_id.to_string(), value))
92                })
93                .collect::<Result<_, _>>()?;
94
95            serde_json::Value::Object(submissions)
96        }
97        Opts::Submit { key, value, hex } => {
98            let value: MetaValue = if hex {
99                MetaValue::from_str(&value).map_err(CliCommandError::InvalidHexValue)?
100            } else {
101                let _valid_json: serde_json::Value =
102                    serde_json::from_str(&value).map_err(CliCommandError::InvalidJsonValue)?;
103                MetaValue::from(value.as_bytes())
104            };
105
106            meta.module_api
107                .submit(key, value, meta.admin_auth()?)
108                .await?;
109
110            serde_json::Value::Bool(true)
111        }
112    };
113
114    Ok(res)
115}
116
117/// A failure of a `meta` module command.
118#[derive(Debug, thiserror::Error)]
119pub(crate) enum CliCommandError {
120    /// The federation did not serve the request.
121    #[error(transparent)]
122    Federation(#[from] FederationError),
123
124    /// The client has no admin credentials for a guardian-only request.
125    #[error(transparent)]
126    Admin(#[from] MetaAdminError),
127
128    /// The consensus value is not valid JSON.
129    #[error("deserializing consensus value as json")]
130    ConsensusValueJson(#[source] serde_json::Error),
131
132    /// A submitted value is not valid JSON.
133    #[error("deserializing submission value")]
134    SubmissionValueJson(#[source] serde_json::Error),
135
136    /// The value to submit is not a valid hex string.
137    #[error("value not a valid hex string")]
138    InvalidHexValue(#[source] <MetaValue as FromStr>::Err),
139
140    /// The value to submit is not a valid JSON string.
141    #[error("value not a valid json string")]
142    InvalidJsonValue(#[source] serde_json::Error),
143}