Skip to main content

txtx_addon_kit/types/
functions.rs

1use super::{
2    diagnostics::Diagnostic,
3    types::{Type, Value},
4    AuthorizationContext,
5};
6
7#[derive(Clone, Debug)]
8pub struct FunctionInput {
9    pub name: String,
10    pub documentation: String,
11    pub typing: Vec<Type>,
12    pub optional: bool,
13}
14
15#[derive(Clone, Debug)]
16pub struct FunctionOutput {
17    pub documentation: String,
18    pub typing: Type,
19}
20
21#[derive(Clone, Debug)]
22pub struct FunctionSpecification {
23    pub name: String,
24    pub documentation: String,
25    pub inputs: Vec<FunctionInput>,
26    pub output: FunctionOutput,
27    pub example: String,
28    pub snippet: String,
29    pub runner: FunctionRunner,
30    pub checker: FunctionChecker,
31}
32
33type FunctionRunner =
34    fn(&FunctionSpecification, &AuthorizationContext, &Vec<Value>) -> Result<Value, Diagnostic>;
35type FunctionChecker =
36    fn(&FunctionSpecification, &AuthorizationContext, &Vec<Type>) -> Result<Type, Diagnostic>;
37
38pub trait FunctionImplementation {
39    fn check_instantiability(
40        _fn_spec: &FunctionSpecification,
41        _auth_ctx: &AuthorizationContext,
42        _args: &Vec<Type>,
43    ) -> Result<Type, Diagnostic>;
44
45    fn run(
46        _fn_spec: &FunctionSpecification,
47        _auth_ctx: &AuthorizationContext,
48        _args: &Vec<Value>,
49    ) -> Result<Value, Diagnostic>;
50}
51
52pub fn fn_diag_with_ctx(
53    namespace: String,
54) -> impl Fn(&FunctionSpecification, String) -> Diagnostic {
55    let fn_diag_with_ctx = move |fn_spec: &FunctionSpecification, e: String| -> Diagnostic {
56        Diagnostic::error_from_string(format!("function '{}::{}': {}", namespace, fn_spec.name, e))
57    };
58    return fn_diag_with_ctx;
59}
60
61pub fn arg_checker_with_ctx(
62    namespace: String,
63) -> impl Fn(&FunctionSpecification, &Vec<Value>) -> Result<(), Diagnostic> {
64    let fn_checker =
65        move |fn_spec: &FunctionSpecification, args: &Vec<Value>| -> Result<(), Diagnostic> {
66            for (i, input) in fn_spec.inputs.iter().enumerate() {
67                if !input.optional {
68                    if let Some(arg) = args.get(i) {
69                        let mut has_type_match = false;
70                        for typing in input.typing.iter() {
71                            let arg_type = arg.get_type();
72                            // special case if both are addons: we don't want to be so strict that
73                            // we check the addon id here
74                            if let Type::Addon(_) = arg_type {
75                                if let Type::Addon(_) = typing {
76                                    has_type_match = true;
77                                    break;
78                                }
79                            }
80                            // special case for empty arrays
81                            if let Type::Array(_) = arg_type {
82                                if arg.expect_array().len() == 0 {
83                                    has_type_match = true;
84                                    break;
85                                }
86                            }
87                            // we don't have an "any" type, so if the array is of type null, we won't check types
88                            if let Type::Array(inner) = typing {
89                                if let Type::Null(_) = **inner {
90                                    has_type_match = true;
91                                    break;
92                                }
93                            }
94                            if arg_type.eq(typing) {
95                                has_type_match = true;
96                                break;
97                            }
98                        }
99                        if !has_type_match {
100                            let expected_types = input
101                                .typing
102                                .iter()
103                                .map(|t| t.to_string())
104                                .collect::<Vec<String>>()
105                                .join(",");
106                            return Err(Diagnostic::error_from_string(format!(
107                            "function '{}::{}' argument #{} ({}) should be of type ({}), found {}",
108                            namespace,
109                            fn_spec.name,
110                            i + 1,
111                            input.name,
112                            expected_types,
113                            arg.get_type().to_string()
114                        )));
115                        }
116                    } else {
117                        return Err(Diagnostic::error_from_string(format!(
118                            "function '{}::{}' missing required argument #{} ({})",
119                            namespace,
120                            fn_spec.name,
121                            i + 1,
122                            input.name,
123                        )));
124                    }
125                }
126            }
127            Ok(())
128        };
129    return fn_checker;
130}