odra_cli/cmd/
args.rs

1use std::any::Any;
2
3use crate::parser::{CLTypedParser, CsprTokenAmountParser, GasParser};
4use clap::{builder::PathBufValueParser, ArgAction, ArgMatches};
5use odra::schema::casper_contract_schema::NamedCLType;
6
7pub const ARG_ATTACHED_VALUE: &str = "attached_value";
8pub const ARG_GAS: &str = "gas";
9pub const ARG_CONTRACTS: &str = "contracts-toml";
10pub const ARG_PRINT_EVENTS: &str = "print-events";
11pub const ARG_NUMBER: &str = "number";
12pub const ARG_DEPLOY_MODE: &str = "deploy_mode";
13const ARG_DEPLOY_MODE_LONG: &str = "deploy-mode";
14
15pub const DEPLOY_MODE_OVERRIDE: &str = "override";
16pub const DEPLOY_MODE_ARCHIVE: &str = "archive";
17
18#[derive(Debug, thiserror::Error)]
19pub enum ArgsError {
20    #[error("Invalid arg value: {0}")]
21    TypesError(#[from] crate::types::Error),
22    #[error("Decoding error: {0}")]
23    DecodingError(String),
24    #[error("Arg not found: {0}")]
25    ArgNotFound(String),
26    #[error("Arg type not found: {0}")]
27    ArgTypeNotFound(String)
28}
29
30/// A typed command argument.
31#[derive(Debug, PartialEq)]
32pub struct CommandArg {
33    pub name: String,
34    pub required: bool,
35    pub description: String,
36    pub ty: NamedCLType,
37    pub is_list_element: bool
38}
39
40impl CommandArg {
41    pub fn new(name: &str, description: &str, ty: NamedCLType) -> Self {
42        Self {
43            name: name.to_string(),
44            description: description.to_string(),
45            ty,
46            required: false,
47            is_list_element: false
48        }
49    }
50
51    pub fn required(self) -> Self {
52        Self {
53            required: true,
54            ..self
55        }
56    }
57
58    pub fn list(self) -> Self {
59        Self {
60            is_list_element: true,
61            ..self
62        }
63    }
64
65    pub(crate) fn split_name(&self) -> Vec<String> {
66        self.name
67            .split('.')
68            .map(|s| s.to_string())
69            .collect::<Vec<_>>()
70    }
71}
72
73impl From<CommandArg> for clap::Arg {
74    fn from(arg: CommandArg) -> Self {
75        let result = clap::Arg::new(&arg.name)
76            .long(arg.name)
77            .value_name(format!("{:?}", arg.ty))
78            .required(arg.required)
79            .value_parser(CLTypedParser::new(arg.ty))
80            .help(arg.description);
81
82        match arg.is_list_element {
83            true => result.action(ArgAction::Append),
84            false => result.action(ArgAction::Set)
85        }
86    }
87}
88
89pub enum Arg {
90    AttachedValue,
91    Gas,
92    Contracts,
93    EventsNumber,
94    PrintEvents,
95    DeployMode
96}
97
98impl Arg {
99    pub fn name(&self) -> &str {
100        match self {
101            Arg::AttachedValue => ARG_ATTACHED_VALUE,
102            Arg::Gas => ARG_GAS,
103            Arg::Contracts => ARG_CONTRACTS,
104            Arg::EventsNumber => ARG_NUMBER,
105            Arg::PrintEvents => ARG_PRINT_EVENTS,
106            Arg::DeployMode => ARG_DEPLOY_MODE
107        }
108    }
109}
110
111impl From<Arg> for clap::Arg {
112    fn from(arg: Arg) -> Self {
113        match arg {
114            Arg::AttachedValue => arg_attached_value(),
115            Arg::Gas => arg_gas(),
116            Arg::Contracts => arg_contracts(),
117            Arg::EventsNumber => arg_number("Number of events to print"),
118            Arg::PrintEvents => arg_print_events(),
119            Arg::DeployMode => arg_deploy_mode()
120        }
121    }
122}
123
124fn arg_attached_value() -> clap::Arg {
125    clap::Arg::new(ARG_ATTACHED_VALUE)
126        .help("The amount of CSPRs attached to the call")
127        .long(ARG_ATTACHED_VALUE)
128        .required(false)
129        .value_name("CSPR")
130        .value_parser(CsprTokenAmountParser)
131        .action(ArgAction::Set)
132}
133
134fn arg_gas() -> clap::Arg {
135    clap::Arg::new(ARG_GAS)
136        .help("The amount of gas to attach to the call")
137        .long(ARG_GAS)
138        .required(true)
139        .value_name("CSPR")
140        .value_parser(GasParser)
141        .action(ArgAction::Set)
142}
143
144fn arg_contracts() -> clap::Arg {
145    clap::Arg::new(ARG_CONTRACTS)
146        .help("The path to the file with the deployed contracts. Relative to the project root.")
147        .long(ARG_CONTRACTS)
148        .short('c')
149        .required(false)
150        .value_name("PathBuf")
151        .value_parser(PathBufValueParser::new())
152        .action(ArgAction::Set)
153}
154
155fn arg_number(description: &'static str) -> clap::Arg {
156    clap::Arg::new(ARG_NUMBER)
157        .short('n')
158        .long(ARG_NUMBER)
159        .value_name("N")
160        .default_value("10")
161        .value_parser(clap::value_parser!(u32).range(1..50))
162        .help(description)
163}
164
165fn arg_print_events() -> clap::Arg {
166    clap::Arg::new(ARG_PRINT_EVENTS)
167        .long(ARG_PRINT_EVENTS)
168        .short('p')
169        .help("Print events emitted by the contract")
170        .action(ArgAction::SetTrue)
171}
172
173fn arg_deploy_mode() -> clap::Arg {
174    clap::Arg::new(ARG_DEPLOY_MODE)
175        .long(ARG_DEPLOY_MODE_LONG)
176        .help("Deployment mode strategy.")
177        .long_help(
178            "Deployment mode strategy:\n\
179             - default: Use existing contract if available, otherwise deploy a new one\n\
180             - override: Force redeploy, overwrite the existing contract configuration\n\
181             - archive: Redeploy contracts, archive the existing contract configuration and create a new one."
182        )
183        .value_name("MODE")
184        .required(false)
185        .default_value("default")
186        .value_parser(["default", DEPLOY_MODE_OVERRIDE, DEPLOY_MODE_ARCHIVE])
187        .action(ArgAction::Set)
188}
189
190pub fn read_arg<T: ToOwned<Owned = T> + Any + Clone + Send + Sync + 'static>(
191    matches: &ArgMatches,
192    arg: Arg
193) -> Option<T> {
194    matches.get_one::<T>(arg.name()).map(ToOwned::to_owned)
195}