Skip to content

Commit c655030

Browse files
authored
Rework def ref system to fix various bugs (#571)
1 parent b6eff4c commit c655030

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+1086
-959
lines changed

src/build_context.rs

Lines changed: 0 additions & 212 deletions
This file was deleted.

src/definitions.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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

Comments
 (0)