shape/child_shape.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 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
use indexmap::IndexSet;
use crate::helpers::quote_non_identifier;
use super::Shape;
use super::ShapeCase;
/// `NamedShapePathKey` represents a single step in a subpath associated with a
/// [`ShapeCase::Name`] shape reference. When pretty-printed, these subpaths are
/// delimited by `.` characters (with `"..."`-quoting as necessary for
/// non-identifier field names), and can be either `::Field` names or array
/// `::Index` values.
///
/// As a special form of catch-all `::Index` value, the step may also be the
/// wildcard `::AnyIndex`, which denotes a union of all the element shapes of an
/// array, or just the given shape if not an array, which is useful to support
/// GraphQL-like array mapping. When pretty-printed, these wildcard keys look
/// like `.*`, and if multiple wildcards are used in a row, they will be
/// coalesced/simplified down to just one logical `.*`.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum NamedShapePathKey {
// The value shape of an object field with a specific name.
Field(String),
// The .** wildcard denoting a union of all object field value shapes,
// automatically mapped over arrays, and returning the shape itself for
// non-object, non-array shapes.
AnyField,
// The shape of an array element at a specific index.
Index(usize),
// The .* wildcard, which represents a union of all array element shapes
// (ignoring None) or just the shape itself for non-array shapes.
AnyIndex,
}
impl From<String> for NamedShapePathKey {
fn from(value: String) -> Self {
NamedShapePathKey::Field(value)
}
}
impl From<&str> for NamedShapePathKey {
fn from(value: &str) -> Self {
NamedShapePathKey::Field(value.to_string())
}
}
impl From<usize> for NamedShapePathKey {
fn from(value: usize) -> Self {
NamedShapePathKey::Index(value)
}
}
impl NamedShapePathKey {
pub fn path_to_string(path: &[NamedShapePathKey]) -> String {
let mut dotted = String::new();
for key in path {
dotted.push('.');
match key {
NamedShapePathKey::Field(name) => {
dotted.push_str(quote_non_identifier(name.as_str()).as_str());
}
NamedShapePathKey::AnyField => {
dotted.push_str("**");
}
NamedShapePathKey::Index(index) => {
dotted.push_str(index.to_string().as_str());
}
NamedShapePathKey::AnyIndex => {
dotted.push_str("*");
}
}
}
dotted
}
}
impl Shape {
/// Returns a new [`Shape`] representing the shape of a given subproperty
/// (field name) of the `self` shape.
pub fn field(&self, field_name: &str) -> Shape {
self.child(&NamedShapePathKey::from(field_name))
}
/// Returns a new [`Shape`] representing the union of all field shapes of
/// object shapes, or just the shape itself for non-object shapes.
pub fn any_field(&self) -> Shape {
self.child(&NamedShapePathKey::AnyField)
}
/// Returns a new [`Shape`] representing the shape of a given element of an
/// array shape.
pub fn item(&self, index: usize) -> Shape {
self.child(&NamedShapePathKey::from(index))
}
/// Returns a new [`Shape`] representing the union of all element shapes of
/// array shapes, or just the shape itself for non-array shapes.
pub fn any_item(&self) -> Shape {
self.child(&NamedShapePathKey::AnyIndex)
}
fn child(&self, key: &NamedShapePathKey) -> Shape {
match self.case() {
ShapeCase::Object { fields, rest, .. } => match key {
NamedShapePathKey::Field(field_name) => {
if let Some(shape) = fields.get(field_name) {
shape.clone()
} else {
// The rest shape might be ShapeCase::None, so the
// ShapeCase::One will simplify to just ShapeCase::None.
Shape::one(&[rest.clone(), Shape::none()])
}
}
NamedShapePathKey::AnyField => {
let mut subshapes = IndexSet::new();
for shape in fields.values() {
subshapes.insert(shape.clone());
}
if !rest.is_none() {
subshapes.insert(rest.clone());
}
ShapeCase::One(subshapes).simplify()
}
// TODO Support .** wildcard for union of all field value
// shapes, a la .* for array element shapes?
NamedShapePathKey::AnyIndex => self.clone(),
// Object shapes have no specific indexes.
NamedShapePathKey::Index(_) => Shape::none(),
},
ShapeCase::Array { prefix, tail } => match key {
NamedShapePathKey::Index(index) => {
if let Some(shape) = prefix.get(*index) {
shape.clone()
} else {
// The rest shape might be ShapeCase::None, so the
// ShapeCase::One will simplify to just ShapeCase::None.
Shape::one(&[tail.clone(), Shape::none()])
}
}
NamedShapePathKey::AnyIndex => {
let mut subshapes = IndexSet::new();
for shape in prefix {
subshapes.insert(shape.clone());
}
if !tail.is_none() {
subshapes.insert(tail.clone());
}
ShapeCase::One(subshapes).simplify()
}
NamedShapePathKey::AnyField | NamedShapePathKey::Field(_) => {
// Following GraphQL logic, map key over the array and make
// a new ShapeCase::Array with the resulting shapes.
let new_items = prefix
.iter()
.map(|shape| shape.child(key))
.collect::<Vec<_>>();
let new_rest = tail.child(key);
// If we tried mapping a field name over an array, and all
// we got back was an empty array, then we can simplify to
// ShapeCase::None.
if new_rest.is_none() && new_items.iter().all(|shape| shape.is_none()) {
Shape::none()
} else {
Shape::array(&new_items, new_rest)
}
}
},
ShapeCase::String(value) => match key {
NamedShapePathKey::Index(index) => {
if let Some(singleton) = value {
if let Some(ch) = singleton.chars().nth(*index) {
Shape::string_value(ch.to_string().as_str())
} else {
Shape::none()
}
} else {
Shape::one(&[Shape::string(), Shape::none()])
}
}
NamedShapePathKey::AnyIndex => {
if let Some(singleton) = value {
let mut subshapes = IndexSet::new();
for ch in singleton.chars() {
subshapes.insert(Shape::string_value(ch.to_string().as_str()));
}
ShapeCase::One(subshapes).simplify()
} else {
Shape::string()
}
}
// String shapes have no named fields.
NamedShapePathKey::Field(_) => Shape::none(),
NamedShapePathKey::AnyField => self.clone(),
},
ShapeCase::One(shapes) => {
let mut subshapes = IndexSet::new();
for shape in shapes {
subshapes.insert(shape.child(key));
}
if subshapes.is_empty() {
Shape::none()
} else {
ShapeCase::One(subshapes).simplify()
}
}
ShapeCase::All(shapes) => {
let mut subshapes = IndexSet::new();
for shape in shapes {
let subshape = shape.child(key);
if matches!(subshape.case.as_ref(), ShapeCase::None) {
continue;
}
subshapes.insert(subshape);
}
if subshapes.is_empty() {
Shape::none()
} else {
ShapeCase::All(subshapes).simplify()
}
}
// For ShapeCase::Name, the subproperties accumulate in the subpath
// vector, to be evaluated once the named shape has been declared.
// For all other ShapeCase variants, the child shape can be
// evaluated immediately.
ShapeCase::Name(name, subpath) => match key {
NamedShapePathKey::Field(_) | NamedShapePathKey::Index(_) => {
let mut new_subpath = subpath.clone();
new_subpath.push(key.clone());
ShapeCase::Name(name.clone(), new_subpath).simplify()
}
NamedShapePathKey::AnyIndex => {
// Disallow multiple consecutive .*.*... wildcards.
match subpath.last() {
Some(NamedShapePathKey::AnyIndex) => {
ShapeCase::Name(name.clone(), subpath.clone()).simplify()
}
_ => {
let mut new_subpath = subpath.clone();
new_subpath.push(key.clone());
ShapeCase::Name(name.clone(), new_subpath).simplify()
}
}
}
NamedShapePathKey::AnyField => {
// Unlike with .*, multiple consecutive .** wildcards do not
// collapse to one.
let mut new_subpath = subpath.clone();
new_subpath.push(key.clone());
ShapeCase::Name(name.clone(), new_subpath).simplify()
}
},
// ShapeCase::Error allows access to subproperty shapes of its
// partial field.
ShapeCase::Error { partial, .. } => partial
.as_ref()
.map(|shape| shape.child(key))
.unwrap_or(Shape::none()),
// ShapeCase::None has no subproperties, as it represents the
// absence of a value, or (to put it another way) the shape of any
// subproperty of ShapeCase::None is also ShapeCase::None.
ShapeCase::None => Shape::none(),
// Non-String primitive shapes like ::Bool and ::Int and ::Float do
// not have subproperties, but a wildcard .* subproperty produces
// the same shape (as if it was the only element of an array).
_ => match key {
NamedShapePathKey::AnyIndex => self.clone(),
NamedShapePathKey::AnyField => self.clone(),
_ => Shape::none(),
},
}
}
}