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