sapio_base/simp/
mod.rs

1// Copyright Judica, Inc 2021
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4//  License, v. 2.0. If a copy of the MPL was not distributed with this
5//  file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
7//! Utilities for working with SIMPs (Sapio Interactive Metadata Protocols)
8
9use std::{
10    collections::BTreeMap,
11    marker::PhantomData,
12    ops::{ShlAssign, Shr},
13};
14
15use serde_json::Value;
16
17/// Errors that may come up when working with SIMPs
18#[derive(Debug)]
19pub enum SIMPError {
20    /// If this SIMP is already present.
21    /// Implementors may wish to handle or ignore this error if it is not an
22    /// issue, but usually it is a bug.
23    /// todo: Mergeable SIMPs may merge one another
24    AlreadyDefined(serde_json::Value),
25    /// If the error was because a SIMP could not be serialized.
26    ///
27    /// If this error ever happens, your SIMP is poorly designed most likely!
28    SerializationError(serde_json::Error),
29}
30impl std::fmt::Display for SIMPError {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
32        write!(f, "{:?}", self)
33    }
34}
35
36impl std::error::Error for SIMPError {}
37impl From<serde_json::Error> for SIMPError {
38    fn from(v: serde_json::Error) -> Self {
39        SIMPError::SerializationError(v)
40    }
41}
42
43/// Trait for Sapio Interactive Metadata Protocol Implementors
44pub trait SIMP {
45    /// Get a protocol number, which should be one that is assigned through the
46    /// SIMP repo. Proprietary SIMPs can safely use negative numbers.
47    fn static_get_protocol_number() -> i64
48    where
49        Self: Sized;
50    /// Get a protocol number, which should be one that is assigned through the
51    /// SIMP repo. Proprietary SIMPs can safely use negative numbers.
52    ///
53    /// Should be implementd as a pass throught to
54    /// [`Self::static_get_protocol_number`], but the  trait system can't
55    /// express that
56    fn get_protocol_number(&self) -> i64;
57    /// Conver a SIMP to a JSON. Concretely typed so that SIMP can be a trait object.
58    fn to_json(&self) -> Result<Value, serde_json::Error>;
59    /// Conver a SIMP from a JSON. Sized bound so that SIMP can be a trait object.
60    fn from_json(value: Value) -> Result<Self, serde_json::Error>
61    where
62        Self: Sized;
63}
64
65/// Tag for where a SIMP may be validly injected
66pub trait LocationTag {}
67
68macro_rules! gen_location {
69    ($x:ident) => {
70        /// Type Tag for a SIMP Location
71        pub struct $x;
72        impl LocationTag for $x {}
73    };
74}
75
76gen_location!(ContinuationPointLT);
77gen_location!(CompiledObjectLT);
78gen_location!(TemplateLT);
79gen_location!(TemplateOutputLT);
80gen_location!(GuardLT);
81gen_location!(TemplateInputLT);
82
83/// a trait a SIMP can implement to indicate where it should be able to be
84/// placed
85pub trait SIMPAttachableAt<T: LocationTag>
86where
87    Self: SIMP,
88{
89}
90
91/// Trait Type Wrapper for Indexing with a SIMP.
92///
93/// Given a BTreeMap of SIMPs b, you can index it with the following syntax:
94///
95/// ```
96/// use sapio_base::simp::SIMP;
97/// use serde_json::Value;
98/// use sapio_base::simp::by_simp;
99/// use sapio_base::simp::simp_value;
100/// use std::collections::BTreeMap;
101/// struct MySimp(Value);
102/// impl SIMP for MySimp {
103/// fn static_get_protocol_number() -> i64
104/// where
105///     Self: Sized,
106/// {
107///     1234
108/// }
109/// fn get_protocol_number(&self) -> i64 {
110///     Self::static_get_protocol_number()
111/// }
112/// fn to_json(&self) -> Result<Value, serde_json::Error> {
113///     Ok(self.0.clone())
114/// }
115/// fn from_json(value: Value) -> Result<Self, serde_json::Error>
116/// where
117///     Self: Sized,
118/// {
119///     Ok(Self(value))
120/// }
121/// }
122/// let mut b : BTreeMap<i64, Box<dyn SIMP>> = BTreeMap::new();
123/// b <<= Box::new(MySimp("Howdy".into()));
124/// if let Some(simp) = &b >> by_simp::<MySimp>() {
125/// } else {
126///     panic!();
127/// }
128/// let mut c : BTreeMap<i64, Value> = BTreeMap::new();
129/// c <<= simp_value(MySimp("Howdy 2".into()));
130/// if let Some(simp) = &c >> by_simp::<MySimp>() {
131/// } else {
132///     panic!();
133/// }
134/// ```
135pub struct BySIMP<T>(pub PhantomData<T>);
136/// Helper for indexing BySimp
137pub fn by_simp<T>() -> BySIMP<T> {
138    BySIMP(Default::default())
139}
140
141/// Wrapper to write to simp
142pub struct SimpValue<T>(pub T);
143/// Wrapper helper to write to simp
144pub fn simp_value<T: SIMP>(v: T) -> SimpValue<T> {
145    SimpValue(v)
146}
147
148impl<T: SIMP> ShlAssign<SimpValue<T>> for BTreeMap<i64, Value> {
149    fn shl_assign(&mut self, rhs: SimpValue<T>) {
150        if let Ok(js) = rhs.0.to_json() {
151            self.insert(T::static_get_protocol_number(), js);
152        }
153    }
154}
155
156impl ShlAssign<Box<dyn SIMP>> for BTreeMap<i64, Box<dyn SIMP>> {
157    fn shl_assign(&mut self, rhs: Box<dyn SIMP>) {
158        self.insert(rhs.get_protocol_number(), rhs);
159    }
160}
161
162impl<'a, T: SIMP, V> Shr<BySIMP<T>> for &'a BTreeMap<i64, V> {
163    type Output = Option<&'a V>;
164    fn shr(self, _rhs: BySIMP<T>) -> Self::Output {
165        self.get(&T::static_get_protocol_number())
166    }
167}
168
169impl<'a, T: SIMP, V> Shr<BySIMP<T>> for &'a mut BTreeMap<i64, V> {
170    type Output = Option<&'a mut V>;
171    fn shr(self, _rhs: BySIMP<T>) -> Self::Output {
172        self.get_mut(&T::static_get_protocol_number())
173    }
174}