Skip to main content

falco_plugin/base/
schema.rs

1use schemars::{schema_for, JsonSchema};
2use serde::de::DeserializeOwned;
3use std::any::TypeId;
4use std::collections::BTreeMap;
5use std::ffi::{CStr, CString};
6use std::sync::Mutex;
7use thiserror::Error;
8
9#[derive(Error, Debug)]
10pub enum SchemaError {
11    #[error("JSON deserialization error: {0}")]
12    JsonError(#[from] serde_json::Error),
13}
14
15pub type SchemaResult<T> = Result<T, SchemaError>;
16
17#[derive(Debug)]
18pub enum ConfigSchemaType {
19    None,
20    Json(&'static CStr),
21}
22
23/// A wrapper to mark a configuration schema as JSON-encoded
24///
25/// Using this type as the configuration type in your plugin automatically generates
26/// the schema describing the configuration format.
27#[derive(Debug)]
28pub struct Json<T: JsonSchema + DeserializeOwned>(pub T);
29
30pub trait ConfigSchema: Sized {
31    fn get_schema() -> ConfigSchemaType;
32
33    fn from_str(s: &str) -> SchemaResult<Self>;
34}
35
36impl<T: JsonSchema + DeserializeOwned + 'static> ConfigSchema for Json<T> {
37    fn get_schema() -> ConfigSchemaType {
38        static CONFIG_SCHEMA: Mutex<BTreeMap<TypeId, CString>> = Mutex::new(BTreeMap::new());
39
40        let ty = TypeId::of::<Self>();
41        let mut schema_map = CONFIG_SCHEMA.lock().unwrap();
42        // Safety:
43        //
44        // we only generate the string once and never change or delete it
45        // so the pointer should remain valid for the static lifetime
46        // hence the dance of converting a reference to a raw pointer and back
47        // to erase the lifetime
48        let ptr = unsafe {
49            CStr::from_ptr(
50                schema_map
51                    .entry(ty)
52                    .or_insert_with(|| {
53                        let schema = schema_for!(T);
54                        let schema = serde_json::to_string_pretty(&schema)
55                            .expect("failed to serialize config schema");
56                        CString::new(schema.into_bytes())
57                            .expect("failed to add NUL to config schema")
58                    })
59                    .as_ptr(),
60            )
61        };
62
63        ConfigSchemaType::Json(ptr)
64    }
65
66    fn from_str(s: &str) -> SchemaResult<Self> {
67        let target: T = serde_json::from_str(s)?;
68        Ok(Json(target))
69    }
70}
71
72impl ConfigSchema for String {
73    fn get_schema() -> ConfigSchemaType {
74        ConfigSchemaType::None
75    }
76
77    fn from_str(s: &str) -> SchemaResult<Self> {
78        Ok(s.to_string())
79    }
80}
81
82impl ConfigSchema for () {
83    fn get_schema() -> ConfigSchemaType {
84        ConfigSchemaType::None
85    }
86
87    fn from_str(_: &str) -> SchemaResult<Self> {
88        Ok(())
89    }
90}