Skip to main content

trailbase_schema/
registry.rs

1use jsonschema::Validator;
2use lazy_static::lazy_static;
3use schemars::schema_for;
4use std::collections::HashMap;
5use std::sync::Arc;
6use trailbase_extension::jsonschema::SchemaEntry;
7
8use crate::error::Error;
9use crate::file::{FileUpload, FileUploads};
10
11fn builtin_schemas() -> &'static HashMap<String, SchemaEntry> {
12  fn validate_mime_type(value: &serde_json::Value, extra_args: Option<&str>) -> bool {
13    let Some(valid_mime_types) = extra_args else {
14      return true;
15    };
16
17    if let serde_json::Value::Object(map) = value {
18      if let Some(serde_json::Value::String(mime_type)) = map.get("mime_type") {
19        if valid_mime_types.contains(mime_type) {
20          return true;
21        }
22      }
23    }
24
25    return false;
26  }
27
28  lazy_static! {
29    static ref builtins: HashMap<String, SchemaEntry> = HashMap::<String, SchemaEntry>::from([
30      (
31        "std.FileUpload".to_string(),
32        SchemaEntry::from(
33          serde_json::to_value(schema_for!(FileUpload)).expect("infallible"),
34          Some(Arc::new(validate_mime_type))
35        )
36        .expect("infallible")
37      ),
38      (
39        "std.FileUploads".to_string(),
40        SchemaEntry::from(
41          serde_json::to_value(schema_for!(FileUploads)).expect("infallible"),
42          None
43        )
44        .expect("infallible"),
45      )
46    ]);
47  }
48
49  return &builtins;
50}
51
52#[derive(Debug, Clone)]
53pub struct Schema {
54  pub name: String,
55  pub schema: serde_json::Value,
56  pub builtin: bool,
57}
58
59pub fn get_schema(name: &str) -> Option<Schema> {
60  let builtins = builtin_schemas();
61
62  trailbase_extension::jsonschema::get_schema(name).map(|s| Schema {
63    name: name.to_string(),
64    schema: s,
65    builtin: builtins.contains_key(name),
66  })
67}
68
69pub fn get_compiled_schema(name: &str) -> Option<Arc<Validator>> {
70  trailbase_extension::jsonschema::get_compiled_schema(name)
71}
72
73pub fn get_schemas() -> Vec<Schema> {
74  let builtins = builtin_schemas();
75  return trailbase_extension::jsonschema::get_schemas()
76    .into_iter()
77    .map(|(name, value)| {
78      let builtin = builtins.contains_key(&name);
79      return Schema {
80        name,
81        schema: value,
82        builtin,
83      };
84    })
85    .collect();
86}
87
88pub fn set_user_schema(name: &str, pattern: Option<serde_json::Value>) -> Result<(), Error> {
89  let builtins = builtin_schemas();
90  if builtins.contains_key(name) {
91    return Err(Error::BuiltinSchema);
92  }
93
94  if let Some(p) = pattern {
95    let entry = SchemaEntry::from(p, None).map_err(|err| Error::JsonSchema(err.into()))?;
96    trailbase_extension::jsonschema::set_schema(name, Some(entry));
97  } else {
98    trailbase_extension::jsonschema::set_schema(name, None);
99  }
100
101  return Ok(());
102}
103
104lazy_static! {
105  static ref INIT: parking_lot::Mutex<bool> = parking_lot::Mutex::new(false);
106}
107
108pub fn set_user_schemas(schemas: Vec<(String, serde_json::Value)>) -> Result<(), Error> {
109  let mut entries: Vec<(String, SchemaEntry)> = vec![];
110  for (name, entry) in builtin_schemas() {
111    entries.push((name.clone(), entry.clone()));
112  }
113
114  for (name, schema) in schemas {
115    entries.push((
116      name,
117      SchemaEntry::from(schema, None).map_err(|err| Error::JsonSchema(err.into()))?,
118    ));
119  }
120
121  trailbase_extension::jsonschema::set_schemas(Some(entries));
122
123  *INIT.lock() = true;
124
125  return Ok(());
126}
127
128pub fn try_init_schemas() {
129  let mut init = INIT.lock();
130
131  if !*init {
132    let entries = builtin_schemas()
133      .iter()
134      .map(|(name, entry)| (name.clone(), entry.clone()))
135      .collect::<Vec<_>>();
136
137    trailbase_extension::jsonschema::set_schemas(Some(entries));
138    *init = true;
139  }
140}
141
142#[cfg(test)]
143mod tests {
144  use serde_json::json;
145
146  use super::*;
147
148  #[test]
149  fn test_builtin_schemas() {
150    assert!(builtin_schemas().len() > 0);
151
152    for (name, schema) in builtin_schemas() {
153      trailbase_extension::jsonschema::set_schema(&name, Some(schema.clone()));
154    }
155
156    {
157      let schema = get_schema("std.FileUpload").unwrap();
158      let compiled_schema = Validator::new(&schema.schema).unwrap();
159      let input = json!({
160        "id": "foo",
161        "mime_type": "my_foo",
162      });
163      if let Err(err) = compiled_schema.validate(&input) {
164        panic!("{err:?}");
165      };
166    }
167
168    {
169      let schema = get_schema("std.FileUploads").unwrap();
170      let compiled_schema = Validator::new(&schema.schema).unwrap();
171      assert!(
172        compiled_schema
173          .validate(&json!([
174            {
175              "id": "foo0",
176              "mime_type": "my_foo0",
177            },
178            {
179              "id": "foo1",
180              "mime_type": "my_foo1",
181            },
182          ]))
183          .is_ok()
184      );
185    }
186  }
187}