1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
use super::*;
use crate::error::{Error, Result};
use serde::{Deserialize, Serialize};
use std::default::Default;
/// "Enum" validator that selects a validator based on the value's enum variant.
///
/// This validator expects a serialized Rust enum. A serialized enum consists of either a single
/// string (a unit variant) or a map with a single key-value pair, where the key is the name of the
/// enum variant and the value is the associated data. The associated data is validated against the
/// matching validator in the contained `BTreeMap`. If there is no match, validation fails.
///
/// For unit variants, there is no validator, and they pass as long as their name is a key in the
/// `BTreeMap`.
///
/// # Query Checking
///
/// The query validator must be an Any or an Enum validator, and the maps are directly checked against
/// each other. The query validator may use a subset of the enum list. For unit variants, both the
/// query validator and schema validator must have `None` instead of a validator. As an example,
/// see the following:
///
/// ```
/// # use fog_pack::{
/// # validator::*,
/// # schema::*,
/// # document::*,
/// # entry::*,
/// # query::*,
/// # types::*,
/// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
///
/// // Say we have a enum like this:
/// enum ExampleEnum {
/// Empty,
/// Integer(Integer),
/// String(String),
/// }
///
/// // Let's use this as our schema-side validator
/// let entry_validator = EnumValidator::new()
/// .insert("Empty", None)
/// .insert("Integer", Some(IntValidator::new().build()))
/// .insert("String", Some(StrValidator::new().build()))
/// .build();
///
/// // We'll build a full schema, so we can do query validation
/// let schema_doc = SchemaBuilder::new(Validator::Null)
/// .entry_add("item", entry_validator, None)
/// .build()
/// .unwrap();
/// let schema = Schema::from_doc(&schema_doc).unwrap();
///
/// // This query is accepted because all enum validators match. Note how
/// // String isn't present, because the query doesn't need to have all
/// // possible enums.
/// let query_validator = EnumValidator::new()
/// .insert("Empty", None)
/// .insert("Integer", Some(IntValidator::new().build()))
/// .build();
/// let query = NewQuery::new("item", query_validator);
/// assert!(schema.encode_query(query).is_ok());
///
/// // This query, however, has a validator for "Empty", so it doesn't work:
/// let query_validator = EnumValidator::new()
/// .insert("Empty", Some(Validator::Null))
/// .insert("Integer", Some(IntValidator::new().build()))
/// .build();
/// let query = NewQuery::new("item", query_validator);
/// assert!(schema.encode_query(query).is_err());
///
/// # Ok(())
/// # }
/// ```
///
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct EnumValidator(pub BTreeMap<String, Option<Validator>>);
impl Default for EnumValidator {
fn default() -> Self {
Self(BTreeMap::new())
}
}
impl EnumValidator {
/// Make a new validator with the default configuration.
pub fn new() -> Self {
Self::default()
}
/// Add a new enum to the set.
pub fn insert(mut self, variant: impl Into<String>, validator: Option<Validator>) -> Self {
self.0.insert(variant.into(), validator);
self
}
/// Build this into a [`Validator`] enum.
pub fn build(self) -> Validator {
Validator::Enum(self)
}
pub fn iter(&self) -> std::collections::btree_map::Iter<String, Option<Validator>> {
self.0.iter()
}
pub fn values(&self) -> std::collections::btree_map::Values<String, Option<Validator>> {
self.0.values()
}
pub(crate) fn validate<'de, 'c>(
&'c self,
types: &'c BTreeMap<String, Validator>,
mut parser: Parser<'de>,
checklist: Option<Checklist<'c>>,
) -> Result<(Parser<'de>, Option<Checklist<'c>>)> {
// Get the enum itself, which should be a map with 1 key-value pair or a string.
let elem = parser
.next()
.ok_or_else(|| Error::FailValidate("expected a enum".to_string()))??;
let (key, has_value) = match elem {
Element::Str(v) => (v, false),
Element::Map(1) => {
let key = parser
.next()
.ok_or_else(|| Error::FailValidate("expected a string".to_string()))??;
if let Element::Str(key) = key {
(key, true)
} else {
return Err(Error::FailValidate("expected a string".to_string()));
}
}
_ => return Err(Error::FailValidate("expected an enum".to_string())),
};
// Find the matching validator and verify the (possible) content against it
let validator = self
.0
.get(key)
.ok_or_else(|| Error::FailValidate(format!("{} is not in enum list", key)))?;
match (validator, has_value) {
(None, false) => Ok((parser, checklist)),
(None, true) => Err(Error::FailValidate(format!(
"enum {} shouldn't have any associated value",
key
))),
(Some(_), false) => Err(Error::FailValidate(format!(
"enum {} should have an associated value",
key
))),
(Some(validator), true) => validator.validate(types, parser, checklist),
}
}
pub(crate) fn query_check(
&self,
types: &BTreeMap<String, Validator>,
other: &Validator,
) -> bool {
match other {
Validator::Enum(other) => {
// For each entry in the query's enum, make sure it:
// 1. Has a corresponding entry in our enum
// 2. That our enum's matching validator would allow the query's validator
// for that enum.
// 3. If both have a "None" instead of a validator, that's also OK
other
.0
.iter()
.all(|(other_k, other_v)| match (self.0.get(other_k), other_v) {
(Some(Some(validator)), Some(other_v)) => {
validator.query_check(types, other_v)
}
(Some(None), None) => true,
_ => false,
})
}
Validator::Any => true,
_ => false,
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn example_schema() {
use crate::schema::{Schema, SchemaBuilder};
// Let's use this as our schema-side validator, and make a full schema.
let entry_validator = EnumValidator::new()
.insert("Empty", None)
.insert("Integer", Some(IntValidator::new().build()))
.insert("String", Some(StrValidator::new().build()))
.build();
let schema_doc = SchemaBuilder::new(Validator::Null)
.entry_add("item", entry_validator, None)
.build()
.unwrap();
Schema::from_doc(&schema_doc).unwrap();
}
}