1use crate::{
10 CommandType, ValueType,
11 config::{
12 CmdInputDescription, CmdOutputDescription, Name, ValueSet, client::NodeData,
13 node::Permissions,
14 },
15 context::CommandContext,
16};
17use futures::future::{Either, LocalBoxFuture, OptionFuture};
18use regex::Regex;
19use serde::{Deserialize, Serialize};
20use std::{borrow::Cow, collections::BTreeMap, future::ready};
21use value::Value;
22
23pub mod builder;
24
25pub mod prelude {
27 pub use crate::{
28 CmdInputDescription, CmdInputDescription as Input, CmdOutputDescription,
29 CmdOutputDescription as Output, FlowId, Name, ValueSet, ValueType,
30 command::{
31 CommandDescription, CommandError, CommandTrait, InstructionInfo,
32 builder::{BuildResult, BuilderCache, BuilderError, CmdBuilder},
33 },
34 config::{client::NodeData, node::Permissions},
35 context::CommandContext,
36 solana::Instructions,
37 };
38 pub use async_trait::async_trait;
39 pub use bytes::Bytes;
40 pub use futures::future::Either;
41 pub use serde::{Deserialize, Serialize};
42 pub use serde_json::Value as JsonValue;
43 pub use serde_with::serde_as;
44 pub use solana_keypair::Keypair;
45 pub use solana_pubkey::Pubkey;
46 pub use solana_signature::Signature;
47 pub use thiserror::Error as ThisError;
48 pub use value::{
49 self, Decimal, Value,
50 with::{AsDecimal, AsKeypair, AsPubkey, AsSignature},
51 };
52}
53
54pub type CommandError = anyhow::Error;
56
57#[async_trait::async_trait(?Send)]
59pub trait CommandTrait: 'static {
60 fn name(&self) -> Name;
62
63 fn inputs(&self) -> Vec<CmdInputDescription>;
65
66 fn outputs(&self) -> Vec<CmdOutputDescription>;
68
69 async fn run(&self, ctx: CommandContext, params: ValueSet) -> Result<ValueSet, CommandError>;
71
72 fn instruction_info(&self) -> Option<InstructionInfo> {
74 None
75 }
76
77 fn permissions(&self) -> Permissions {
79 Permissions::default()
80 }
81
82 async fn destroy(&mut self) {}
84
85 fn read_form_data(&self, data: serde_json::Value) -> ValueSet {
87 let mut result = ValueSet::new();
88 for i in self.inputs() {
89 if let Some(json) = data.get(&i.name) {
90 let value = Value::from(json.clone());
91 result.insert(i.name.clone(), value);
92 }
93 }
94 result
95 }
96
97 fn passthrough_outputs(&self, inputs: &ValueSet) -> ValueSet {
99 let mut res = ValueSet::new();
100 for i in self.inputs() {
101 if i.passthrough {
102 if let Some(value) = inputs.get(&i.name) {
103 if !i.required && matches!(value, Value::Null) {
104 continue;
105 }
106
107 let value = match i.type_bounds.first() {
108 Some(ValueType::Pubkey) => {
109 value::pubkey::deserialize(value.clone()).map(Into::into)
112 }
113 _ => Ok(value.clone()),
114 }
115 .unwrap_or_else(|error| {
116 tracing::warn!("error reading passthrough: {}", error);
117 value.clone()
118 });
119 res.insert(i.name, value);
120 }
121 }
122 }
123 res
124 }
125
126 fn input_is_required(&self, name: &str) -> Option<bool> {
127 self.inputs()
128 .into_iter()
129 .find_map(|i| (i.name == name).then_some(i.required))
130 }
131
132 fn output_is_optional(&self, name: &str) -> Option<bool> {
133 self.outputs()
134 .into_iter()
135 .find_map(|o| (o.name == name).then_some(o.optional))
136 .or_else(|| {
137 self.inputs()
138 .into_iter()
139 .find_map(|i| (i.name == name && i.passthrough).then_some(!i.required))
140 })
141 }
142}
143
144#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
149pub struct InstructionInfo {
150 pub before: Vec<Name>,
151 pub signature: Name,
152 pub after: Vec<Name>,
153}
154
155impl InstructionInfo {
156 pub fn simple<C: CommandTrait>(cmd: &C, signature: &str) -> Self {
160 let before = cmd
161 .inputs()
162 .into_iter()
163 .filter(|i| i.passthrough)
164 .map(|i| i.name)
165 .chain(
166 cmd.outputs()
167 .into_iter()
168 .filter(|o| o.name != signature)
169 .map(|o| o.name),
170 )
171 .collect();
172 Self {
173 before,
174 after: Vec::new(),
175 signature: signature.into(),
176 }
177 }
178}
179
180#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, bincode::Encode)]
181pub enum MatchName {
182 Exact(Cow<'static, str>),
183 Regex(Cow<'static, str>),
184}
185
186impl<__Context> ::bincode::Decode<__Context> for MatchName {
188 fn decode<__D: ::bincode::de::Decoder<Context = __Context>>(
189 decoder: &mut __D,
190 ) -> core::result::Result<Self, ::bincode::error::DecodeError> {
191 let variant_index = <u32 as ::bincode::Decode<__D::Context>>::decode(decoder)?;
192 match variant_index {
193 0u32 => core::result::Result::Ok(Self::Exact(
194 ::bincode::Decode::<__D::Context>::decode(decoder)?,
195 )),
196 1u32 => core::result::Result::Ok(Self::Regex(
197 ::bincode::Decode::<__D::Context>::decode(decoder)?,
198 )),
199 variant => {
200 core::result::Result::Err(::bincode::error::DecodeError::UnexpectedVariant {
201 found: variant,
202 type_name: "MatchName",
203 allowed: &::bincode::error::AllowedEnumVariants::Range { min: 0, max: 1 },
204 })
205 }
206 }
207 }
208}
209
210impl<'de, C> bincode::BorrowDecode<'de, C> for MatchName {
211 fn borrow_decode<D: bincode::de::BorrowDecoder<'de, Context = C>>(
212 decoder: &mut D,
213 ) -> Result<Self, bincode::error::DecodeError> {
214 bincode::Decode::decode(decoder)
215 }
216}
217
218impl std::fmt::Debug for MatchName {
219 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220 match self {
221 Self::Exact(arg0) => arg0.fmt(f),
222 Self::Regex(arg0) => {
223 f.write_str("/")?;
224 f.write_str(arg0)?;
225 f.write_str("/")
226 }
227 }
228 }
229}
230
231impl std::fmt::Display for MatchName {
232 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233 match self {
234 MatchName::Exact(cow) => cow.fmt(f),
235 MatchName::Regex(cow) => cow.fmt(f),
236 }
237 }
238}
239
240#[derive(Clone, bincode::Encode, bincode::Decode, PartialEq, Eq, PartialOrd, Ord)]
241pub struct MatchCommand {
242 pub r#type: CommandType,
243 pub name: MatchName,
244}
245
246impl std::fmt::Debug for MatchCommand {
247 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248 self.r#type.fmt(f)?;
249 f.write_str(":")?;
250 self.name.fmt(f)
251 }
252}
253
254impl MatchCommand {
255 pub fn is_match(&self, ty: CommandType, name: &str) -> bool {
256 self.r#type == ty
257 && match &self.name {
258 MatchName::Exact(cow) => cow == name,
259 MatchName::Regex(cow) => Regex::new(cow) .map(|re| re.is_match(name))
261 .ok()
262 .unwrap_or(false),
263 }
264 }
265}
266
267pub type FnNewResult = Result<Box<dyn CommandTrait>, CommandError>;
268
269#[derive(Clone)]
271pub struct CommandDescription {
272 pub matcher: MatchCommand,
273 pub fn_new:
275 Either<fn(&NodeData) -> FnNewResult, fn(&NodeData) -> LocalBoxFuture<'static, FnNewResult>>,
276}
277
278impl CommandDescription {
279 pub const fn new(name: &'static str, fn_new: fn(&NodeData) -> FnNewResult) -> Self {
280 Self {
281 matcher: MatchCommand {
282 r#type: CommandType::Native,
283 name: MatchName::Exact(Cow::Borrowed(name)),
284 },
285 fn_new: Either::Left(fn_new),
286 }
287 }
288}
289
290inventory::collect!(CommandDescription);
291
292pub fn collect_commands() -> BTreeMap<&'static MatchCommand, &'static CommandDescription> {
293 inventory::iter::<CommandDescription>()
294 .map(|c| (&c.matcher, c))
295 .collect()
296}
297
298#[derive(Debug, Clone)]
299pub struct CommandIndex<T> {
300 pub exact_match: BTreeMap<(CommandType, Cow<'static, str>), T>,
301 pub regex: Vec<(CommandType, regex::Regex, T)>,
302}
303
304impl<T> Default for CommandIndex<T> {
305 fn default() -> Self {
306 Self {
307 exact_match: <_>::default(),
308 regex: <_>::default(),
309 }
310 }
311}
312
313impl<T> FromIterator<(MatchCommand, T)> for CommandIndex<T> {
314 fn from_iter<I: IntoIterator<Item = (MatchCommand, T)>>(iter: I) -> Self {
315 let mut this = Self::default();
316 for (matcher, t) in iter {
317 match &matcher.name {
318 MatchName::Exact(cow) => {
319 this.exact_match.insert((matcher.r#type, cow.clone()), t);
320 }
321 MatchName::Regex(cow) => {
322 this.regex
323 .push((matcher.r#type, Regex::new(cow).expect("invalid regex"), t));
324 }
325 }
326 }
327 this
328 }
329}
330
331impl<T> CommandIndex<T> {
332 pub fn get(&self, ty: CommandType, name: &str) -> Option<&T> {
333 if let Some(d) = self.exact_match.get(&(ty, name.to_owned().into())) {
334 Some(d)
335 } else {
336 let mut matched = None;
337 for r in &self.regex {
338 if r.0 == ty && r.1.is_match(name) {
339 matched = Some(&r.2);
340 }
341 }
342 matched
343 }
344 }
345
346 pub fn availables(&self) -> impl Iterator<Item = MatchCommand> {
347 self.exact_match
348 .keys()
349 .cloned()
350 .map(|(r#type, name)| MatchCommand {
351 r#type,
352 name: MatchName::Exact(name),
353 })
354 .chain(self.regex.iter().map(|(ty, regex, _)| MatchCommand {
355 r#type: *ty,
356 name: MatchName::Regex(regex.to_string().into()),
357 }))
358 }
359}
360
361pub struct CommandFactory {
362 index: CommandIndex<&'static CommandDescription>,
363}
364
365impl CommandFactory {
366 pub fn collect() -> Self {
367 Self {
368 index: inventory::iter::<CommandDescription>()
369 .map(|c| (c.matcher.clone(), c))
370 .collect(),
371 }
372 }
373
374 pub fn init(
375 &self,
376 nd: &NodeData,
377 ) -> impl Future<Output = Result<Option<Box<dyn CommandTrait>>, CommandError>> + 'static {
378 let cmd = self.index.get(nd.r#type, &nd.node_id);
379
380 let either = cmd.map(|cmd| match cmd.fn_new {
381 Either::Left(fn_new) => Either::Left(ready(fn_new(nd))),
382 Either::Right(async_fn_new) => Either::Right(async_fn_new(nd)),
383 });
384 async move { OptionFuture::from(either).await.transpose() }
385 }
386
387 pub fn availables(&self) -> impl Iterator<Item = MatchCommand> {
388 self.index.availables()
389 }
390}