#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Kind {
Text,
Choice(Vec<String>),
Integer,
Flag,
}
impl Kind {
pub(crate) fn describe(&self) -> String {
match self {
Self::Text => "a string".to_owned(),
Self::Choice(choices) => format!("one of {}", choices.join(", ")),
Self::Integer => "a whole number".to_owned(),
Self::Flag => "a boolean".to_owned(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValueKind(pub(crate) Kind);
impl ValueKind {
#[must_use]
pub fn text() -> Self {
Self(Kind::Text)
}
#[must_use]
pub fn choice(choices: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self(Kind::Choice(choices.into_iter().map(Into::into).collect()))
}
#[must_use]
pub fn integer() -> Self {
Self(Kind::Integer)
}
#[must_use]
pub fn flag() -> Self {
Self(Kind::Flag)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Key {
pub(crate) name: String,
pub(crate) kind: Kind,
pub(crate) required: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Shape {
keys: Vec<Key>,
tables: Vec<(String, Shape)>,
arrays: Vec<(String, Shape)>,
}
impl Shape {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn required(self, key: &str, kind: ValueKind) -> Self {
self.declare(key, kind, true)
}
#[must_use]
pub fn optional(self, key: &str, kind: ValueKind) -> Self {
self.declare(key, kind, false)
}
#[must_use]
pub fn table(mut self, key: &str, shape: Shape) -> Self {
self.forget(key);
self.tables.push((key.to_owned(), shape));
self
}
#[must_use]
pub fn entries(mut self, key: &str, shape: Shape) -> Self {
self.forget(key);
self.arrays.push((key.to_owned(), shape));
self
}
fn declare(mut self, key: &str, kind: ValueKind, required: bool) -> Self {
self.forget(key);
self.keys.push(Key { name: key.to_owned(), kind: kind.0, required });
self
}
fn forget(&mut self, key: &str) {
self.keys.retain(|declared| declared.name != key);
self.tables.retain(|(name, _)| name != key);
self.arrays.retain(|(name, _)| name != key);
}
pub(crate) fn key(&self, name: &str) -> Option<&Key> {
self.keys.iter().find(|key| key.name == name)
}
pub(crate) fn inner(&self, name: &str) -> Option<&Shape> {
self.tables.iter().find(|(key, _)| key == name).map(|(_, shape)| shape)
}
pub(crate) fn entry(&self, name: &str) -> Option<&Shape> {
self.arrays.iter().find(|(key, _)| key == name).map(|(_, shape)| shape)
}
pub(crate) fn required_keys(&self) -> impl Iterator<Item = &str> {
self.keys.iter().filter(|key| key.required).map(|key| key.name.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kinds_describe_what_they_expect() {
assert_eq!(ValueKind::text().0.describe(), "a string");
assert_eq!(ValueKind::choice(["rw", "ro"]).0.describe(), "one of rw, ro");
assert_eq!(ValueKind::integer().0.describe(), "a whole number");
assert_eq!(ValueKind::flag().0.describe(), "a boolean");
}
#[test]
fn one_name_means_one_thing() {
let shape = Shape::new()
.required("profile", ValueKind::text())
.table("profile", Shape::new())
.entries("profile", Shape::new().required("name", ValueKind::text()));
assert!(shape.key("profile").is_none() && shape.inner("profile").is_none());
assert_eq!(shape.entry("profile").map(|entry| entry.required_keys().count()), Some(1));
let shape = shape.optional("profile", ValueKind::integer());
assert!(shape.entry("profile").is_none(), "the array is gone once the name is a key");
assert_eq!(shape.key("profile").map(|key| key.required), Some(false));
assert_eq!(shape.required_keys().count(), 0);
}
#[test]
fn declaring_a_key_again_replaces_it() {
let shape = Shape::new().optional("id", ValueKind::text()).required("id", ValueKind::text());
assert_eq!(shape.keys.len(), 1);
assert_eq!(shape.required_keys().collect::<Vec<_>>(), vec!["id"]);
assert_eq!(shape, Shape::new().required("id", ValueKind::text()));
}
}