shape/case_enum.rs
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 204 205 206
pub(crate) mod all;
mod deduplicate_shape;
pub(crate) mod one;
use std::iter::{empty, once};
use self::all::All;
use self::one::One;
use super::child_shape::NamedShapePathKey;
use super::Shape;
use crate::location::Located;
use indexmap::IndexMap;
/// The [`ShapeCase`] enum attempts to capture all the common shapes of JSON
/// values, not just the JSON types themselves, but also patterns of usage (such
/// as using JSON arrays as either static tuples or dynamic lists).
///
/// A [`ShapeCase`] enum variant like [`ShapeCase::One`] may temporarily
/// represent a structure that has not been fully simplified, but simplication
/// is required to turn the [`ShapeCase`] into a `Shape`, using the
/// `ShapeCase::simplify(&self) -> Shape` method.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ShapeCase {
/// The `Bool`, `String`, and `Int` variants can either represent a general
/// shape using `None` for the `Option`, or a specific shape using `Some`.
/// For example, `ShapeCase::Bool(None)` is a shape satisfied by either true
/// or false, like the Rust bool type, while `ShapeCase::Bool(Some(true))`
/// is a shape satisfied only by `true` (and likewise for `false`).
Bool(Option<bool>),
/// Similar to [`ShapeCase::Bool`], [`ShapeCase::String`] represents a shape
/// matching either any string (in the `None` case) or some specific string
/// (in the `Some` case).
String(Option<String>),
/// Similar to [`ShapeCase::Bool`] and [`ShapeCase::String`],
/// [`ShapeCase::Int`] can represent either any integer or some specific
/// integer.
Int(Option<i64>),
/// [`ShapeCase::Float`] is a shape that captures the set of all floating
/// point numbers. We do not allow singleton floating point value shapes,
/// since floating point equality is unreliable.
Float,
/// [`ShapeCase::Null`] is a singleton shape whose only possible value is
/// `null`. Note that [`ShapeCase::Null`] is different from
/// [`ShapeCase::None`], which represents the absence of a value.
Null,
/// [`ShapeCase::Array`] represents a `prefix` of statically known shapes
/// (like a tuple type), followed by an optional `tail` shape for all other
/// (dynamic) elements. When only the `prefix` elements are defined, the
/// `tail` shape is [`ShapeCase::None`]. `ShapeCase::Array(vec![],
/// ShapeCase::None)` is the shape of an empty array.
Array { prefix: Vec<Shape>, tail: Shape },
/// [`ShapeCase::Object`] is a map of statically known field names to field
/// shapes, together with an optional type for all other string keys. When
/// dynamic string indexing is disabled, the rest shape will be
/// [`ShapeCase::None`]. Note that accessing the dynamic map always returns
/// `ShapeCase::One([rest_shape, ShapeCase::None])`, to reflect the
/// uncertainty of the shape of dynamic keys not present in the static
/// fields.
Object {
// TODO: track location of keys
fields: IndexMap<String, Shape>,
rest: Shape,
},
/// A union of shapes, satisfied by values that satisfy any of the shapes in
/// the set.
///
/// Create using [`Shape::one`].
One(One),
/// An intersection of shapes, satisfied by values that satisfy all of the
/// shapes in the set. When applied to multiple [`ShapeCase::Object`]
/// shapes, [`ShapeCase::All`] represents merging the fields of the objects,
/// as reflected by the simplification logic.
///
/// Crete using [`Shape::all`].
All(All),
/// [`ShapeCase::Name`] refers to a shape declared with the given name,
/// possibly in the future. When shape processing needs to refer to the
/// shape of some subproperty of a named shape, it uses the
/// `Vec<NamedShapePathKey>` subpath to represent the nested shape. When the
/// named shape is eventually declared, the subpath can be used to resolve
/// the actual shape of the nested property path.
//
/// As of now, the name `String` is expected to be either an identifier or a
/// variable name like `$root` or `$this` or `$args`, allowing
/// [`ShapeCase::Name`] to represent types of subproperties of variables as
/// well as named types from some schema.
Name(Located<String>, Vec<Located<NamedShapePathKey>>),
/// [`ShapeCase::Unknown`] is a shape that represents any possible JSON
/// value (including `ShapeCase::None`).
Unknown,
/// Represents the absence of a value, or the shape of a property not
/// present in an object. Used by the rest parameters of both
/// [`ShapeCase::Array`] and [`ShapeCase::Object`] to indicate no additional
/// dynamic elements are allowed, and with [`ShapeCase::One`] to represent
/// optionality of values, e.g. `One<Bool, None>`.
None,
/// Represents a local failure of shape processing.
Error(Error),
}
impl ShapeCase {
#[must_use]
pub fn is_none(&self) -> bool {
matches!(self, Self::None)
}
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
#[must_use]
pub fn error(message: String) -> Self {
Self::Error(Error {
message,
partial: None,
})
}
#[must_use]
pub fn error_with_partial(message: String, partial: Shape) -> Self {
Self::Error(Error {
message,
partial: Some(partial),
})
}
/// Iterate over all errors within this shape, recursively
pub fn errors(&self) -> Box<dyn Iterator<Item = &Error> + '_> {
match self {
Self::Error(error) => Box::new(once(error)),
Self::Array { prefix, tail } => {
Box::new(prefix.iter().flat_map(Shape::errors).chain(tail.errors()))
}
Self::Object { fields, rest } => {
Box::new(fields.values().flat_map(Shape::errors).chain(rest.errors()))
}
Self::One(shapes) => Box::new(shapes.iter().flat_map(Shape::errors)),
Self::All(shapes) => Box::new(shapes.iter().flat_map(Shape::errors)),
_ => Box::new(empty()),
}
}
}
impl From<bool> for ShapeCase {
fn from(value: bool) -> Self {
ShapeCase::Bool(Some(value))
}
}
impl From<String> for ShapeCase {
fn from(value: String) -> Self {
ShapeCase::String(Some(value))
}
}
impl From<&str> for ShapeCase {
fn from(value: &str) -> Self {
ShapeCase::String(Some(value.to_string()))
}
}
impl From<i64> for ShapeCase {
fn from(value: i64) -> Self {
ShapeCase::Int(Some(value))
}
}
impl PartialEq<&ShapeCase> for ShapeCase {
fn eq(&self, other: &&ShapeCase) -> bool {
self == *other
}
}
impl PartialEq<ShapeCase> for &ShapeCase {
fn eq(&self, other: &ShapeCase) -> bool {
*self == other
}
}
/// Represents a local failure of shape processing.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Error {
pub message: String,
/// This `partial` shape can be another `ShapeCase::Error` shape, which
/// allows for chaining of multiple errors.
pub partial: Option<Shape>,
}
impl From<Error> for ShapeCase {
fn from(error: Error) -> Self {
ShapeCase::Error(error)
}
}