Skip to main content

ordinary_config/app/function/
mod.rs

1// Copyright (C) 2026 The Ordinary Authors.
2//
3// SPDX-License-Identifier: BSD-3-Clause
4
5use crate::{Bindgen, StoredCache};
6use ordinary_types::Kind;
7use serde::{Deserialize, Serialize};
8
9/// Configuration parameters for Ordinary Functions.
10#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
11#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
12#[derive(Deserialize, Serialize, Debug, Clone, Default)]
13pub struct FunctionConfig {
14    /// Foreign function interface config
15    #[serde(skip_serializing_if = "Option::is_none")]
16    #[serde(default)]
17    pub ffi: Option<FunctionFfi>,
18    /// Refers to the template config in another location
19    #[serde(skip_serializing_if = "Option::is_none")]
20    #[serde(default, rename = "ref")]
21    pub r#ref: Option<String>,
22
23    /// Function name. Must be unique.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    #[serde(default)]
26    pub name: Option<String>,
27
28    #[serde(skip_serializing_if = "Option::is_none")]
29    #[serde(default)]
30    pub readonly: Option<bool>,
31
32    /// which language the input binding WebAssembly
33    /// Component should be compiled _from_.
34    ///
35    /// Note: because the bindings are compiled _to_
36    /// a WebAssembly Component, with a companion WIT
37    /// file, the resulting bindings can be used by any
38    /// language that has WIT support.
39    ///
40    /// i.e. bindings compiled from Rust can be plugged
41    /// into a Golang WebAssembly component.
42    ///
43    /// **Important**: if your FFI serialization format
44    /// is JSON, or you are comfortable writing your own
45    /// `FlexBuffer` vector accessors it is not necessary
46    /// to generate the bindings (other than for reference
47    /// in the `FlexBuffer` vector accessing case).
48    #[serde(skip_serializing_if = "Option::is_none")]
49    #[serde(default)]
50    pub bindgen: Option<Bindgen>,
51    /// language for which the function builds
52    #[serde(skip_serializing_if = "Option::is_none")]
53    #[serde(default)]
54    pub toolchain: Option<FunctionToolchain>,
55    /// build scripts for ensuring the WebAssembly components
56    /// are composed properly at build time.
57    #[serde(skip_serializing_if = "Option::is_none")]
58    #[serde(default)]
59    pub build: Option<Vec<Vec<String>>>,
60    /// path to the WASM component binary
61    #[serde(skip_serializing_if = "Option::is_none")]
62    #[serde(default)]
63    pub bin: Option<String>,
64
65    /// What to check the token fields against. If unset
66    /// function is public.
67    #[serde(skip_serializing_if = "Option::is_none")]
68    #[serde(default)]
69    pub protected: Option<bool>,
70    /// Which Ordinary Application resources and
71    /// host extensions this function has access to.
72    #[serde(skip_serializing_if = "Option::is_none")]
73    #[serde(default)]
74    pub access: Option<Vec<FunctionAccessPermission>>,
75
76    /// Input definition for the action.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    #[serde(default)]
79    pub input: Option<Kind>,
80    /// Output definition for the action.
81    #[serde(skip_serializing_if = "Option::is_none")]
82    #[serde(default)]
83    pub output: Option<Kind>,
84
85    /// Persistent result cache configuration for `readonly`
86    /// functions.
87    #[serde(skip_serializing_if = "Option::is_none")]
88    #[serde(default)]
89    pub cache: Option<StoredCache>,
90
91    /// List of build time environment variables.
92    ///
93    /// format in template: `{{ YOUR_VAR }}`
94    #[serde(skip_serializing_if = "Option::is_none")]
95    #[serde(default)]
96    pub variables: Option<Vec<String>>,
97
98    /// Max duration for the action.
99    ///
100    /// Unit: seconds
101    #[serde(skip_serializing_if = "Option::is_none")]
102    #[serde(default)]
103    pub timeout: Option<u16>,
104}
105
106impl FunctionConfig {
107    #[allow(clippy::single_match)]
108    pub fn load_bindgen(&mut self) {
109        let function_name = self.name_validated().to_owned();
110
111        match self.bindgen {
112            Some(Bindgen::V1Rust) => {
113                self.ffi = Some(FunctionFfi {
114                    version: FunctionFfiVersion::V1,
115                    serialization: FunctionFfiSerialization::FlexBufferVector,
116                });
117
118                let mut build = self.build.clone().unwrap_or_default();
119                build.push(vec![
120                    "sh".into(),
121                    format!(".ordinary/{function_name}/build.sh"),
122                ]);
123                self.build = Some(build);
124                self.bin = Some(format!(".ordinary/{function_name}/function.wasm"));
125            }
126            _ => (),
127        }
128    }
129
130    /// ## Panics
131    ///
132    /// This method panics if the `ffi` field has not been validated to be present
133    #[must_use]
134    pub fn ffi_validated(&self) -> &FunctionFfi {
135        self.ffi.as_ref().expect("FFI should be present")
136    }
137
138    /// ## Panics
139    ///
140    /// This method panics if the `name` field has not been validated to be present
141    #[must_use]
142    pub fn name_validated(&self) -> &str {
143        self.name.as_deref().expect("Name should be present")
144    }
145
146    /// ## Panics
147    ///
148    /// This method panics if the `accepts` field has not been validated to be present
149    #[must_use]
150    pub fn input_validated(&self) -> &Kind {
151        self.input.as_ref().expect("Input should be present")
152    }
153
154    /// ## Panics
155    ///
156    /// This method panics if the `returns` field has not been validated to be present
157    #[must_use]
158    pub fn output_validated(&self) -> &Kind {
159        self.output.as_ref().expect("Output should be present")
160    }
161}
162
163#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
164#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
165#[derive(Deserialize, Serialize, Debug, Clone, Default)]
166pub enum FunctionFfiVersion {
167    #[default]
168    V1,
169}
170
171/// Input/output serialization for module and host functions.
172#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
173#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
174#[derive(Deserialize, Serialize, Debug, Clone, Default)]
175pub enum FunctionFfiSerialization {
176    #[default]
177    FlexBufferVector,
178    Json,
179}
180
181#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
182#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
183#[derive(Deserialize, Serialize, Debug, Clone, Default)]
184pub struct FunctionFfi {
185    pub version: FunctionFfiVersion,
186    pub serialization: FunctionFfiSerialization,
187}
188
189/// The language toolchain the function is built with.
190#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
191#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
192#[derive(Deserialize, Serialize, Debug, Clone, Default)]
193pub enum FunctionToolchain {
194    /// Function is written in the Rust programming language.
195    ///
196    /// Templates using [Askama](https://askama.rs/en/stable/).
197    #[default]
198    #[serde(rename = "rust")]
199    Rs,
200    /// Function is written in JavaScript.
201    #[serde(rename = "js")]
202    Js,
203    /// Function is written in TypeScript.
204    #[serde(rename = "ts")]
205    Ts,
206    /// Function is written in the Go programming language.
207    ///
208    /// Templates using Go templates.
209    #[serde(rename = "go")]
210    Go,
211    // todo: C,
212    // todo: Zig,
213    // todo: Cs,
214    // todo: Lua,
215    // todo: Cpp,
216}
217
218impl FunctionToolchain {
219    #[must_use]
220    pub fn as_str(&self) -> &'static str {
221        match self {
222            Self::Rs => "rust",
223            Self::Js => "js",
224            Self::Ts => "ts",
225            Self::Go => "go",
226        }
227    }
228}
229
230#[cfg(feature = "cli")]
231impl std::fmt::Display for FunctionToolchain {
232    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233        match self {
234            Self::Rs => write!(f, "rust"),
235            Self::Js => write!(f, "js"),
236            Self::Ts => write!(f, "ts"),
237            Self::Go => write!(f, "go"),
238        }
239    }
240}
241
242#[cfg(feature = "cli")]
243impl clap::ValueEnum for FunctionToolchain {
244    fn value_variants<'a>() -> &'a [Self] {
245        &[Self::Rs, Self::Go, Self::Js, Self::Ts]
246    }
247
248    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
249        match self {
250            Self::Rs => Some(clap::builder::PossibleValue::new("rust")),
251            Self::Js => Some(clap::builder::PossibleValue::new("js")),
252            Self::Ts => Some(clap::builder::PossibleValue::new("ts")),
253            Self::Go => Some(clap::builder::PossibleValue::new("go")),
254        }
255    }
256}
257
258/// Model operations that an function is allowed to make.
259#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
260#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
261#[derive(Deserialize, Serialize, Debug, Clone, PartialOrd, PartialEq, Eq, Hash)]
262pub enum FunctionAccessDatabaseOps {
263    /// Can create an item for this model.
264    Insert,
265    /// Can get an item for this model by UUID or Index.
266    Get,
267    /// Can query an item for this model by queryable field.
268    Query,
269    /// Can update an item for this model.
270    Update,
271    /// Can delete an item for this model.
272    Delete,
273}
274
275/// Auth operations that a function can make.
276#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
277#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
278#[derive(Deserialize, Serialize, Debug, Clone)]
279pub enum FunctionAccessAuthOps {
280    /// Allows the function the ability to set
281    /// a user's access token fields/claims.
282    SetClaims,
283}
284
285/// Defines access permissions that an Ordinary
286/// Function can configure.
287#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
288#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
289#[derive(Deserialize, Serialize, Debug, Clone)]
290pub enum FunctionAccessPermission {
291    /// Provides the function access to a given model.
292    DatabaseModel {
293        /// Name of the model.
294        name: String,
295        /// List of allowed operations that the function
296        /// can take on the model.
297        ops: Vec<FunctionAccessDatabaseOps>,
298    },
299    /// Provides the function access to Ordinary Auth.
300    Auth {
301        /// Which operations the function is allowed to take.
302        ops: Vec<FunctionAccessAuthOps>,
303    },
304}