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}