txtx_addon_kit/types/
functions.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use super::{
    diagnostics::Diagnostic,
    types::{Type, Value},
    AuthorizationContext,
};

#[derive(Clone, Debug)]
pub struct FunctionInput {
    pub name: String,
    pub documentation: String,
    pub typing: Vec<Type>,
    pub optional: bool,
}

#[derive(Clone, Debug)]
pub struct FunctionOutput {
    pub documentation: String,
    pub typing: Type,
}

#[derive(Clone, Debug)]
pub struct FunctionSpecification {
    pub name: String,
    pub documentation: String,
    pub inputs: Vec<FunctionInput>,
    pub output: FunctionOutput,
    pub example: String,
    pub snippet: String,
    pub runner: FunctionRunner,
    pub checker: FunctionChecker,
}

type FunctionRunner =
    fn(&FunctionSpecification, &AuthorizationContext, &Vec<Value>) -> Result<Value, Diagnostic>;
type FunctionChecker =
    fn(&FunctionSpecification, &AuthorizationContext, &Vec<Type>) -> Result<Type, Diagnostic>;

pub trait FunctionImplementation {
    fn check_instantiability(
        _fn_spec: &FunctionSpecification,
        _auth_ctx: &AuthorizationContext,
        _args: &Vec<Type>,
    ) -> Result<Type, Diagnostic>;

    fn run(
        _fn_spec: &FunctionSpecification,
        _auth_ctx: &AuthorizationContext,
        _args: &Vec<Value>,
    ) -> Result<Value, Diagnostic>;
}

pub fn fn_diag_with_ctx(
    namespace: String,
) -> impl Fn(&FunctionSpecification, String) -> Diagnostic {
    let fn_diag_with_ctx = move |fn_spec: &FunctionSpecification, e: String| -> Diagnostic {
        Diagnostic::error_from_string(format!("function '{}:{}': {}", namespace, fn_spec.name, e))
    };
    return fn_diag_with_ctx;
}

pub fn arg_checker_with_ctx(
    namespace: String,
) -> impl Fn(&FunctionSpecification, &Vec<Value>) -> Result<(), Diagnostic> {
    let fn_checker =
        move |fn_spec: &FunctionSpecification, args: &Vec<Value>| -> Result<(), Diagnostic> {
            for (i, input) in fn_spec.inputs.iter().enumerate() {
                if !input.optional {
                    if let Some(arg) = args.get(i) {
                        let mut has_type_match = false;
                        for typing in input.typing.iter() {
                            let arg_type = arg.get_type();
                            // special case if both are addons: we don't want to be so strict that
                            // we check the addon id here
                            if let Type::Addon(_) = arg_type {
                                if let Type::Addon(_) = typing {
                                    has_type_match = true;
                                    break;
                                }
                            }
                            // special case for empty arrays
                            if let Type::Array(_) = arg_type {
                                if arg.expect_array().len() == 0 {
                                    has_type_match = true;
                                    break;
                                }
                            }
                            // we don't have an "any" type, so if the array is of type null, we won't check types
                            if let Type::Array(inner) = typing {
                                if let Type::Null = **inner {
                                    has_type_match = true;
                                    break;
                                }
                            }
                            if arg_type.eq(typing) {
                                has_type_match = true;
                                break;
                            }
                        }
                        if !has_type_match {
                            let expected_types = input
                                .typing
                                .iter()
                                .map(|t| t.to_string())
                                .collect::<Vec<String>>()
                                .join(",");
                            return Err(Diagnostic::error_from_string(format!(
                            "function '{}:{}' argument #{} ({}) should be of type ({}), found {}",
                            namespace,
                            fn_spec.name,
                            i + 1,
                            input.name,
                            expected_types,
                            arg.get_type().to_string()
                        )));
                        }
                    } else {
                        return Err(Diagnostic::error_from_string(format!(
                            "function '{}:{}' missing required argument #{} ({})",
                            namespace,
                            fn_spec.name,
                            i + 1,
                            input.name,
                        )));
                    }
                }
            }
            Ok(())
        };
    return fn_checker;
}