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
use indexmap::IndexMap;
use indexmap::IndexSet;

use super::child_shape::NamedShapePathKey;
use super::helpers::OffsetRange;
use super::Shape;

/// 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, 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 {
        fields: IndexMap<String, Shape>,
        rest: Shape,
    },

    /// A union of shapes, satisfied by values that satisfy any of the shapes in
    /// the set.
    One(IndexSet<Shape>),

    /// 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.
    All(IndexSet<Shape>),

    /// [`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(String, Vec<NamedShapePathKey>),

    /// 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 {
        message: String,
        range: OffsetRange,
        /// This `partial` shape can be another `ShapeCase::Error` shape, which
        /// allows for chaining of multiple errors.
        partial: Option<Shape>,
    },
}

impl ShapeCase {
    pub fn is_none(&self) -> bool {
        matches!(self, Self::None)
    }

    pub fn is_null(&self) -> bool {
        matches!(self, Self::Null)
    }

    pub fn error(message: &str) -> Self {
        Self::Error {
            message: message.to_string(),
            range: None,
            partial: None,
        }
    }

    pub fn error_with_range(message: &str, range: OffsetRange) -> Self {
        Self::Error {
            message: message.to_string(),
            range,
            partial: None,
        }
    }

    pub fn error_with_partial(message: &str, partial: Shape) -> Self {
        Self::Error {
            message: message.to_string(),
            range: None,
            partial: Some(partial),
        }
    }

    pub fn error_with_range_and_partial(message: &str, range: OffsetRange, partial: Shape) -> Self {
        Self::Error {
            message: message.to_string(),
            range,
            partial: Some(partial),
        }
    }
}

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))
    }
}