shapely_core/
shape.rs

1use std::{alloc::Layout, any::TypeId, collections::HashSet, fmt::Formatter, ptr::NonNull};
2
3/// Schema for reflection of a type
4#[derive(Clone, Copy)]
5pub struct Shape {
6    /// A descriptive name for the type, e.g. `u64`, or `Person`
7    pub name: NameFn,
8
9    /// The typeid of the underlying type
10    pub typeid: TypeId,
11
12    /// Size & alignment
13    pub layout: Layout,
14
15    /// Details/contents of the value
16    pub innards: Innards,
17
18    /// Set the value at a given address to the default value for this type
19    pub set_to_default: Option<SetToDefaultFn>,
20
21    /// Drop the value at a given address
22    ///
23    /// # Safety
24    ///
25    /// This function should be called only for initialized values.
26    /// It's the caller's responsibility to ensure the address points to a valid value.
27    pub drop_in_place: Option<DropFn>,
28}
29
30pub type NameFn = fn(f: &mut std::fmt::Formatter) -> std::fmt::Result;
31
32// Helper struct to format the name for display
33impl std::fmt::Display for Shape {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        (self.name)(f)
36    }
37}
38
39/// A function that sets a value to its default at a specific memory address
40pub type SetToDefaultFn = unsafe fn(*mut u8);
41
42/// A function that drops a value at a specific memory address
43pub type DropFn = unsafe fn(*mut u8);
44
45impl PartialEq for Shape {
46    fn eq(&self, other: &Self) -> bool {
47        self.typeid == other.typeid
48    }
49}
50
51impl Eq for Shape {}
52
53impl std::hash::Hash for Shape {
54    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
55        self.typeid.hash(state);
56    }
57}
58
59impl Shape {
60    const INDENT: usize = 2;
61
62    /// Extract the scalar contents from a shape and a pointer
63    ///
64    /// # Safety
65    ///
66    /// This function is unsafe because it reads from raw memory.
67    /// The caller must ensure that:
68    /// 1. The pointer points to a valid, initialized value of the correct type
69    /// 2. The memory is properly aligned for the type
70    /// 3. The memory is not mutated while the returned ScalarContents is in use
71    pub unsafe fn get_scalar_contents<'a>(&self, ptr: *const u8) -> crate::ScalarContents<'a> {
72        match self.innards {
73            Innards::Scalar(scalar) => unsafe { scalar.get_contents(ptr) },
74            _ => panic!("Expected a scalar shape"),
75        }
76    }
77
78    /// Pretty-print this shape, recursively.
79    pub fn pretty_print_recursive(&self, f: &mut Formatter) -> std::fmt::Result {
80        self.pretty_print_recursive_internal(f, &mut HashSet::new(), 0)
81    }
82
83    fn pretty_print_recursive_internal(
84        &self,
85        f: &mut Formatter,
86        printed_schemas: &mut HashSet<Shape>,
87        indent: usize,
88    ) -> std::fmt::Result {
89        if !printed_schemas.insert(*self) {
90            write!(f, "{:indent$}\x1b[1;33m", "", indent = indent)?;
91            (self.name)(f)?;
92            writeln!(f, "\x1b[0m (\x1b[1;31malready printed\x1b[0m)")?;
93            return Ok(());
94        }
95
96        write!(f, "{:indent$}\x1b[1;33m", "", indent = indent)?;
97        (self.name)(f)?;
98        writeln!(
99            f,
100            "\x1b[0m (size: \x1b[1;34m{}\x1b[0m, align: \x1b[1;35m{}\x1b[0m)",
101            self.layout.size(),
102            self.layout.align()
103        )?;
104
105        match &self.innards {
106            Innards::Struct { fields } => {
107                for field in *fields {
108                    writeln!(
109                        f,
110                        "{:indent$}\x1b[1;32m{}\x1b[0m: ",
111                        "",
112                        field.name,
113                        indent = indent + Self::INDENT
114                    )?;
115                    if field.flags.is_sensitive() {
116                        writeln!(
117                            f,
118                            "{:indent$}[REDACTED]",
119                            "",
120                            indent = indent + Self::INDENT * 2
121                        )?;
122                    } else {
123                        field.shape.get().pretty_print_recursive_internal(
124                            f,
125                            printed_schemas,
126                            indent + Self::INDENT * 2,
127                        )?;
128                    }
129                }
130            }
131            Innards::HashMap { value_shape } => {
132                writeln!(
133                    f,
134                    "{:indent$}\x1b[1;36mHashMap with arbitrary keys and value shape:\x1b[0m",
135                    "",
136                    indent = indent + Self::INDENT
137                )?;
138                value_shape.get().pretty_print_recursive_internal(
139                    f,
140                    printed_schemas,
141                    indent + Self::INDENT * 2,
142                )?;
143            }
144            Innards::Array(elem_schema) => {
145                write!(
146                    f,
147                    "{:indent$}\x1b[1;36mArray of:\x1b[0m ",
148                    "",
149                    indent = indent + Self::INDENT
150                )?;
151                elem_schema.get().pretty_print_recursive_internal(
152                    f,
153                    printed_schemas,
154                    indent + Self::INDENT * 2,
155                )?;
156            }
157            Innards::Transparent(inner_schema) => {
158                write!(
159                    f,
160                    "{:indent$}\x1b[1;36mTransparent wrapper for:\x1b[0m ",
161                    "",
162                    indent = indent + Self::INDENT
163                )?;
164                inner_schema.get().pretty_print_recursive_internal(
165                    f,
166                    printed_schemas,
167                    indent + Self::INDENT * 2,
168                )?;
169            }
170            Innards::Scalar(scalar) => {
171                writeln!(
172                    f,
173                    "{:indent$}\x1b[1;36mScalar:\x1b[0m \x1b[1;33m{:?}\x1b[0m",
174                    "",
175                    scalar,
176                    indent = indent
177                )?;
178            }
179        }
180
181        Ok(())
182    }
183
184    /// Returns a slice of statically known fields. Fields that are not in there might still be inserted if it's a dynamic collection.
185    pub fn known_fields(&self) -> &'static [Field] {
186        match self.innards {
187            Innards::Struct { fields } => fields,
188            _ => &[],
189        }
190    }
191
192    /// Returns a reference to a field with the given name, if it exists
193    pub fn field_by_name(&self, name: &str) -> Option<&Field> {
194        self.known_fields().iter().find(|field| field.name == name)
195    }
196
197    /// Returns a reference to a field with the given index, if it exists
198    pub fn field_by_index(&self, index: usize) -> Result<&Field, FieldError> {
199        match self.innards {
200            Innards::Struct { fields } => fields.get(index).ok_or(FieldError::IndexOutOfBounds),
201            _ => Err(FieldError::NotAStruct),
202        }
203    }
204
205    /// Returns a dangling pointer for this shape.
206    ///
207    /// This is useful for zero-sized types (ZSTs) which don't need actual memory allocation,
208    /// but still need a properly aligned "some address".
209    ///
210    /// # Safety
211    ///
212    /// This function returns a dangling pointer. It should only be used in contexts where
213    /// a non-null pointer is required but no actual memory access will occur, such as for ZSTs.
214    pub fn dangling(&self) -> NonNull<u8> {
215        let dang = NonNull::dangling();
216        let offset = dang.align_offset(self.layout.align());
217        unsafe { dang.byte_add(offset) }
218    }
219}
220
221/// Errors encountered when calling `field_by_index` or `field_by_name`
222#[derive(Debug)]
223pub enum FieldError {
224    /// `field_by_index` was called on a dynamic collection, that has no
225    /// static fields. a HashMap doesn't have a "first field", it can only
226    /// associate by keys.
227    NoStaticFields,
228
229    /// `field_by_name` was called on a struct, and there is no static field
230    /// with the given key.
231    NoSuchStaticField,
232
233    /// `field_by_index` was called on a fixed-size collection (like a tuple,
234    /// a struct, or a fixed-size array) and the index was out of bounds.
235    IndexOutOfBounds,
236
237    /// `field_by_index` or `field_by_name` was called on a non-struct type.
238    NotAStruct,
239}
240
241impl std::error::Error for FieldError {}
242
243impl std::fmt::Display for FieldError {
244    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245        match self {
246            FieldError::NoStaticFields => write!(f, "No static fields available"),
247            FieldError::NoSuchStaticField => write!(f, "No such static field"),
248            FieldError::IndexOutOfBounds => write!(f, "Index out of bounds"),
249            FieldError::NotAStruct => write!(f, "Not a struct"),
250        }
251    }
252}
253
254impl std::fmt::Debug for Shape {
255    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256        self.pretty_print_recursive(f)
257    }
258}
259
260/// The shape of a schema: is it more map-shaped, array-shaped, scalar-shaped?
261#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
262pub enum Innards {
263    /// Struct with statically-known fields
264    Struct { fields: &'static [Field] },
265
266    /// HashMap — keys are dynamic, values are homogeneous
267    HashMap { value_shape: ShapeDesc },
268
269    /// Ordered list of heterogenous values, variable size
270    Array(ShapeDesc),
271
272    /// Transparent — forwards to another known schema
273    Transparent(ShapeDesc),
274
275    /// Scalar — known based type
276    Scalar(Scalar),
277}
278
279#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
280pub struct Field {
281    /// key for the map field
282    pub name: &'static str,
283
284    /// schema of the inner type
285    pub shape: ShapeDesc,
286
287    /// offset of the field in the map, if known.
288    ///
289    /// For example, when deserializing a self-descriptive format like JSON, we're going to get
290    /// some map fields with dynamically discovered field names, and they're not going to have
291    /// an offset.
292    ///
293    /// However, when deserializing formats that are non-self descriptive and working from an
294    /// existing shape, then their map fields are probably going to have offsets, especially if
295    /// they're using derived macros.
296    pub offset: usize,
297
298    /// Flags for the field (e.g. sensitive)
299    pub flags: FieldFlags,
300}
301
302/// The outcome of trying to set a field on a map
303#[derive(Debug, Clone, Copy)]
304pub enum SetFieldOutcome {
305    /// The field was successfully set
306    Accepted,
307
308    /// The field was rejected (unknown field set in a struct, for example)
309    Rejected,
310}
311
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
313#[non_exhaustive]
314pub enum Scalar {
315    // Valid utf-8
316    String,
317
318    // Not valid utf-8 🤷
319    Bytes,
320
321    I8,
322    I16,
323    I32,
324    I64,
325    I128,
326
327    U8,
328    U16,
329    U32,
330    U64,
331    U128,
332
333    F32,
334    F64,
335
336    Boolean,
337
338    /// An empty tuple, null, undefined, whatever you wish
339    Nothing,
340}
341
342/// A function that returns a shape. There should only be one of these per concrete type in a
343#[derive(Clone, Copy)]
344pub struct ShapeDesc(pub fn() -> Shape);
345
346impl From<fn() -> Shape> for ShapeDesc {
347    fn from(f: fn() -> Shape) -> Self {
348        Self(f)
349    }
350}
351
352impl ShapeDesc {
353    /// Build the inner shape
354    #[inline(always)]
355    pub fn get(&self) -> Shape {
356        (self.0)()
357    }
358}
359
360impl PartialEq for ShapeDesc {
361    fn eq(&self, other: &Self) -> bool {
362        if std::ptr::eq(self.0 as *const (), other.0 as *const ()) {
363            true
364        } else {
365            let self_shape = self.0();
366            let other_shape = other.0();
367            self_shape == other_shape
368        }
369    }
370}
371
372impl Eq for ShapeDesc {}
373
374impl std::hash::Hash for ShapeDesc {
375    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
376        // Hash the function pointer
377        (self.0 as *const ()).hash(state);
378    }
379}
380
381impl std::fmt::Debug for ShapeDesc {
382    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
383        self.get().fmt(f)
384    }
385}
386
387/// Flags that can be applied to fields to modify their behavior
388/// 
389/// # Examples
390/// 
391/// ```rust
392/// use shapely_core::FieldFlags;
393/// 
394/// // Create flags with the sensitive bit set
395/// let flags = FieldFlags::SENSITIVE;
396/// assert!(flags.is_sensitive());
397/// 
398/// // Combine multiple flags using bitwise OR
399/// let flags = FieldFlags::SENSITIVE | FieldFlags::EMPTY;
400/// ```
401#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
402pub struct FieldFlags(u64);
403
404impl FieldFlags {
405    /// An empty set of flags
406    pub const EMPTY: Self = Self(0);
407
408    /// Flag indicating this field contains sensitive data that should not be displayed
409    pub const SENSITIVE: Self = Self(1 << 0);
410
411    /// Returns true if the sensitive flag is set
412    #[inline]
413    pub fn is_sensitive(&self) -> bool {
414        self.0 & Self::SENSITIVE.0 != 0
415    }
416
417    /// Sets the sensitive flag and returns self for chaining
418    #[inline]
419    pub fn set_sensitive(&mut self) -> &mut Self {
420        self.0 |= Self::SENSITIVE.0;
421        self
422    }
423
424    /// Unsets the sensitive flag and returns self for chaining
425    #[inline]
426    pub fn unset_sensitive(&mut self) -> &mut Self {
427        self.0 &= !Self::SENSITIVE.0;
428        self
429    }
430
431    /// Creates a new FieldFlags with the sensitive flag set
432    #[inline]
433    pub const fn sensitive() -> Self {
434        Self::SENSITIVE
435    }
436}
437
438impl std::ops::BitOr for FieldFlags {
439    type Output = Self;
440
441    fn bitor(self, rhs: Self) -> Self {
442        Self(self.0 | rhs.0)
443    }
444}
445
446impl std::ops::BitOrAssign for FieldFlags {
447    fn bitor_assign(&mut self, rhs: Self) {
448        self.0 |= rhs.0;
449    }
450}
451
452impl Default for FieldFlags {
453    #[inline(always)]
454    fn default() -> Self {
455        Self::EMPTY
456    }
457}
458
459impl std::fmt::Display for FieldFlags {
460    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
461        if self.0 == 0 {
462            return write!(f, "none");
463        }
464
465        // Define a vector of flag entries: (flag bit, name)
466        let flags = [
467            (Self::SENSITIVE.0, "sensitive"),
468            // Future flags can be easily added here:
469            // (Self::SOME_FLAG.0, "some_flag"),
470            // (Self::ANOTHER_FLAG.0, "another_flag"),
471        ];
472
473        // Write all active flags with proper separators
474        let mut is_first = true;
475        for (bit, name) in flags {
476            if self.0 & bit != 0 {
477                if !is_first {
478                    write!(f, ", ")?;
479                }
480                is_first = false;
481                write!(f, "{}", name)?;
482            }
483        }
484
485        Ok(())
486    }
487}