1use std::{any::Any, collections::HashMap};
2
3use crate::{
4 args::CommandArg, container::ContractError, types, CustomTypeSet, DeployedContractsContainer
5};
6use anyhow::Result;
7use clap::ArgMatches;
8use odra::schema::NamedCLTyped;
9use odra::{casper_types::bytesrepr::FromBytes, host::HostEnv, prelude::OdraError};
10use thiserror::Error;
11
12use super::OdraCommand;
13
14pub trait Scenario: Any {
20 fn args(&self) -> Vec<CommandArg> {
21 vec![]
22 }
23 fn run(
24 &self,
25 env: &HostEnv,
26 container: DeployedContractsContainer,
27 args: ScenarioArgs
28 ) -> core::result::Result<(), ScenarioError>;
29}
30
31pub(crate) struct ScenarioCmd {
35 name: String,
36 scenario: Box<dyn Scenario>
37}
38
39impl ScenarioCmd {
40 pub fn new<S: ScenarioMetadata + Scenario>(scenario: S) -> Self {
41 ScenarioCmd {
42 name: S::NAME.to_string(),
43 scenario: Box::new(scenario)
44 }
45 }
46}
47
48impl OdraCommand for ScenarioCmd {
49 fn name(&self) -> &str {
50 &self.name
51 }
52
53 fn run(&self, env: &HostEnv, args: &ArgMatches, _types: &CustomTypeSet) -> Result<()> {
54 let container = DeployedContractsContainer::load()?;
55 let args = ScenarioArgs::new(self.scenario.args(), args);
56
57 self.scenario.run(env, container, args)?;
58 Ok(())
59 }
60}
61
62#[derive(Debug, Error)]
64pub enum ScenarioError {
65 #[error("Odra error: {message}")]
66 OdraError { message: String },
67 #[error("Contract read error: {0}")]
68 ContractReadError(#[from] ContractError),
69 #[error("Arg error")]
70 ArgError(#[from] ArgError),
71 #[error("Types error")]
72 TypesError(#[from] types::Error)
73}
74
75impl From<OdraError> for ScenarioError {
76 fn from(err: OdraError) -> Self {
77 ScenarioError::OdraError {
78 message: format!("{:?}", err)
79 }
80 }
81}
82
83pub struct ScenarioArgs(HashMap<String, ScenarioArg>);
85
86impl ScenarioArgs {
87 pub(crate) fn new(args: Vec<CommandArg>, matches: &ArgMatches) -> Self {
88 let map = args
89 .into_iter()
90 .filter_map(|arg| {
91 let arg_name = &arg.name;
92 let values = matches.get_many::<String>(arg_name);
93
94 if arg.required && values.is_none() {
95 panic!("Missing argument: {}", arg.name);
96 }
97 values.as_ref()?;
98 let values = values
99 .expect("Arg not found")
100 .map(|v| v.to_string())
101 .collect::<Vec<_>>();
102 let scenario_arg = match arg.is_list_element {
103 true => ScenarioArg::Many(values),
104 false => ScenarioArg::Single(values[0].clone())
105 };
106
107 Some((arg_name.clone(), scenario_arg))
108 })
109 .collect();
110 Self(map)
111 }
112
113 pub fn get_single<T: NamedCLTyped + FromBytes>(&self, name: &str) -> Result<T, ScenarioError> {
114 let arg = self
115 .0
116 .get(name)
117 .ok_or(ArgError::MissingArg(name.to_string()))?;
118
119 let result = match arg {
120 ScenarioArg::Single(value) => {
121 let bytes = types::into_bytes(&T::ty(), value)?;
122 T::from_bytes(&bytes)
123 .map_err(|_| ArgError::Deserialization)
124 .map(|t| t.0)
125 }
126 ScenarioArg::Many(_) => Err(ArgError::SingleExpected)
127 }?;
128 Ok(result)
129 }
130
131 pub fn get_many<T: NamedCLTyped + FromBytes>(
132 &self,
133 name: &str
134 ) -> Result<Vec<T>, ScenarioError> {
135 let arg = self
136 .0
137 .get(name)
138 .ok_or(ArgError::MissingArg(name.to_string()))?;
139 match arg {
140 ScenarioArg::Many(values) => values
141 .iter()
142 .map(|value| {
143 let bytes = types::into_bytes(&T::ty(), value);
144 bytes.map_err(ScenarioError::TypesError).and_then(|bytes| {
145 T::from_bytes(&bytes)
146 .map_err(|_| ScenarioError::ArgError(ArgError::Deserialization))
147 .map(|t| t.0)
148 })
149 })
150 .collect::<Result<Vec<T>, ScenarioError>>(),
151 ScenarioArg::Single(_) => Err(ScenarioError::ArgError(ArgError::ManyExpected))
152 }
153 }
154}
155
156#[derive(Debug, Error)]
158pub enum ArgError {
159 #[error("Arg deserialization failed")]
160 Deserialization,
161 #[error("Multiple values expected")]
162 ManyExpected,
163 #[error("Single value expected")]
164 SingleExpected,
165 #[error("Missing arg: {0}")]
166 MissingArg(String)
167}
168
169enum ScenarioArg {
170 Single(String),
171 Many(Vec<String>)
172}
173
174pub trait ScenarioMetadata {
176 const NAME: &'static str;
177 const DESCRIPTION: &'static str;
178}