|
| 1 | +/// Definition / reference management |
| 2 | +/// Our definitions system is very similar to json schema's: there's ref strings and a definitions section |
| 3 | +/// Unlike json schema we let you put definitions inline, not just in a single '#/$defs/' block or similar. |
| 4 | +/// We use DefinitionsBuilder to collect the references / definitions into a single vector |
| 5 | +/// and then get a definition from a reference using an integer id (just for performance of not using a HashMap) |
| 6 | +use std::collections::hash_map::Entry; |
| 7 | + |
| 8 | +use pyo3::prelude::*; |
| 9 | + |
| 10 | +use ahash::AHashMap; |
| 11 | + |
| 12 | +use crate::build_tools::py_err; |
| 13 | + |
| 14 | +// An integer id for the reference |
| 15 | +pub type ReferenceId = usize; |
| 16 | + |
| 17 | +/// Definitions are validators and serializers that are |
| 18 | +/// shared by reference. |
| 19 | +/// They come into play whenever there is recursion, e.g. |
| 20 | +/// if you have validators A -> B -> A then A will be shared |
| 21 | +/// by reference so that the SchemaValidator itself can own it. |
| 22 | +/// These primarily get used by DefinitionRefValidator and DefinitionRefSerializer, |
| 23 | +/// other validators / serializers primarily pass them around without interacting with them. |
| 24 | +/// They get indexed by a ReferenceId, which are integer identifiers |
| 25 | +/// that are handed out and managed by DefinitionsBuilder when the Schema{Validator,Serializer} |
| 26 | +/// gets build. |
| 27 | +pub type Definitions<T> = [T]; |
| 28 | + |
| 29 | +#[derive(Clone, Debug)] |
| 30 | +struct Definition<T> { |
| 31 | + pub id: ReferenceId, |
| 32 | + pub value: Option<T>, |
| 33 | +} |
| 34 | + |
| 35 | +#[derive(Clone, Debug)] |
| 36 | +pub struct DefinitionsBuilder<T> { |
| 37 | + definitions: AHashMap<String, Definition<T>>, |
| 38 | +} |
| 39 | + |
| 40 | +impl<T: Clone + std::fmt::Debug> DefinitionsBuilder<T> { |
| 41 | + pub fn new() -> Self { |
| 42 | + Self { |
| 43 | + definitions: AHashMap::new(), |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + /// Get a ReferenceId for the given reference string. |
| 48 | + // This ReferenceId can later be used to retrieve a definition |
| 49 | + pub fn get_reference_id(&mut self, reference: &str) -> ReferenceId { |
| 50 | + let next_id = self.definitions.len(); |
| 51 | + // We either need a String copy or two hashmap lookups |
| 52 | + // Neither is better than the other |
| 53 | + // We opted for the easier outward facing API |
| 54 | + match self.definitions.entry(reference.to_string()) { |
| 55 | + Entry::Occupied(entry) => entry.get().id, |
| 56 | + Entry::Vacant(entry) => { |
| 57 | + entry.insert(Definition { |
| 58 | + id: next_id, |
| 59 | + value: None, |
| 60 | + }); |
| 61 | + next_id |
| 62 | + } |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + /// Add a definition, returning the ReferenceId that maps to it |
| 67 | + pub fn add_definition(&mut self, reference: String, value: T) -> PyResult<ReferenceId> { |
| 68 | + let next_id = self.definitions.len(); |
| 69 | + match self.definitions.entry(reference.clone()) { |
| 70 | + Entry::Occupied(mut entry) => match entry.get_mut().value.replace(value) { |
| 71 | + Some(_) => py_err!("Duplicate ref: `{}`", reference), |
| 72 | + None => Ok(entry.get().id), |
| 73 | + }, |
| 74 | + Entry::Vacant(entry) => { |
| 75 | + entry.insert(Definition { |
| 76 | + id: next_id, |
| 77 | + value: Some(value), |
| 78 | + }); |
| 79 | + Ok(next_id) |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + /// Retrieve an item definition using a ReferenceId |
| 85 | + /// Will raise an error if the definition for that reference does not yet exist |
| 86 | + pub fn get_definition(&self, reference_id: ReferenceId) -> PyResult<&T> { |
| 87 | + let (reference, def) = match self.definitions.iter().find(|(_, def)| def.id == reference_id) { |
| 88 | + Some(v) => v, |
| 89 | + None => return py_err!("Definitions error: no definition for ReferenceId `{}`", reference_id), |
| 90 | + }; |
| 91 | + match def.value.as_ref() { |
| 92 | + Some(v) => Ok(v), |
| 93 | + None => py_err!( |
| 94 | + "Definitions error: attempted to use `{}` before it was filled", |
| 95 | + reference |
| 96 | + ), |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + /// Consume this Definitions into a vector of items, indexed by each items ReferenceId |
| 101 | + pub fn finish(self) -> PyResult<Vec<T>> { |
| 102 | + // We need to create a vec of defs according to the order in their ids |
| 103 | + let mut defs: Vec<(usize, T)> = Vec::new(); |
| 104 | + for (reference, def) in self.definitions.into_iter() { |
| 105 | + match def.value { |
| 106 | + None => return py_err!("Definitions error: definition {} was never filled", reference), |
| 107 | + Some(v) => defs.push((def.id, v)), |
| 108 | + } |
| 109 | + } |
| 110 | + defs.sort_by_key(|(id, _)| *id); |
| 111 | + Ok(defs.into_iter().map(|(_, v)| v).collect()) |
| 112 | + } |
| 113 | +} |
0 commit comments