1use crate::{
6 Error,
7 eip712::resolver::{PropertyDef, TypeDef},
8};
9use alloc::{
10 string::{String, ToString},
11 vec::Vec,
12};
13use parser::{Error as TypeParserError, TypeSpecifier};
14
15use super::Resolver;
16
17#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct PropDef<'a> {
21 pub ty: TypeSpecifier<'a>,
23 pub name: &'a str,
25}
26
27impl PropDef<'_> {
28 pub fn to_owned(&self) -> PropertyDef {
30 PropertyDef::new(self.ty.span, self.name).unwrap()
31 }
32}
33
34impl<'a> TryFrom<&'a str> for PropDef<'a> {
35 type Error = Error;
36
37 #[inline]
38 fn try_from(input: &'a str) -> Result<Self, Self::Error> {
39 Self::parse(input)
40 }
41}
42
43impl<'a> PropDef<'a> {
44 pub fn parse(input: &'a str) -> Result<Self, Error> {
59 let (ty, name) =
60 input.rsplit_once(' ').ok_or_else(|| Error::invalid_property_def(input))?;
61 Ok(PropDef { ty: TypeSpecifier::parse_eip712(ty.trim())?, name: name.trim() })
62 }
63}
64
65#[derive(Clone, Debug, PartialEq, Eq)]
69pub struct ComponentType<'a> {
70 pub span: &'a str,
72 pub type_name: &'a str,
74 pub props: Vec<PropDef<'a>>,
76}
77
78impl<'a> TryFrom<&'a str> for ComponentType<'a> {
79 type Error = Error;
80
81 #[inline]
82 fn try_from(input: &'a str) -> Result<Self, Self::Error> {
83 Self::parse(input)
84 }
85}
86
87impl<'a> ComponentType<'a> {
88 pub fn parse(input: &'a str) -> Result<Self, Error> {
90 let (name, props_str) = input
91 .split_once('(')
92 .ok_or_else(|| Error::TypeParser(TypeParserError::invalid_type_string(input)))?;
93
94 let mut props = vec![];
95 let mut depth = 1; let mut last = 0;
97
98 for (i, c) in props_str.char_indices() {
99 match c {
100 '(' => depth += 1,
101 ')' => {
102 depth -= 1;
103 if depth == 0 {
104 let candidate = &props_str[last..i];
105 if !candidate.is_empty() {
106 props.push(candidate.try_into()?);
107 }
108 last = i + c.len_utf8();
109 break;
110 }
111 }
112 ',' => {
113 if depth == 1 {
114 props.push(props_str[last..i].try_into()?);
115 last = i + c.len_utf8();
116 }
117 }
118 _ => {}
119 }
120 }
121
122 Ok(Self { span: &input[..last + name.len() + 1], type_name: name.trim(), props })
123 }
124
125 pub fn to_owned(&self) -> TypeDef {
127 TypeDef::new(self.type_name, self.props.iter().map(|p| p.to_owned()).collect()).unwrap()
128 }
129}
130
131#[derive(Debug, PartialEq, Eq)]
133pub struct EncodeType<'a> {
134 pub types: Vec<ComponentType<'a>>,
136}
137
138impl<'a> TryFrom<&'a str> for EncodeType<'a> {
139 type Error = Error;
140
141 #[inline]
142 fn try_from(input: &'a str) -> Result<Self, Self::Error> {
143 Self::parse(input)
144 }
145}
146
147impl<'a> EncodeType<'a> {
148 pub fn parse(input: &'a str) -> Result<Self, Error> {
150 let mut types = vec![];
151 let mut remaining = input;
152
153 while let Ok(t) = ComponentType::parse(remaining) {
154 remaining = &remaining[t.span.len()..];
155 types.push(t);
156 }
157
158 Ok(Self { types })
159 }
160
161 pub fn canonicalize(&self) -> Result<String, Error> {
166 let mut resolver = Resolver::default();
168 for component_type in &self.types {
169 resolver.ingest(component_type.to_owned());
170 }
171
172 let mut non_dependent = resolver.non_dependent_types();
174
175 let first = non_dependent
176 .next()
177 .ok_or_else(|| Error::MissingType("primary component".to_string()))?;
178 if let Some(second) = non_dependent.next() {
179 let all_types = vec![first.type_name(), second.type_name()]
180 .into_iter()
181 .chain(non_dependent.map(|t| t.type_name()))
182 .collect::<Vec<_>>()
183 .join(", ");
184
185 return Err(Error::MissingType(format!("primary component: {all_types}")));
186 };
187
188 let primary = first.type_name();
189 _ = resolver.resolve(primary)?;
190
191 resolver.encode_type(primary)
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 const CANONICAL: &str = "Transaction(Person from,Person to,Asset tx)Asset(address token,uint256 amount)Person(address wallet,string name)";
201 const MISSING_COMPONENT: &str =
202 r#"Transaction(Person from, Person to, Asset tx) Person(address wallet, string name)"#;
203 const MISSING_PRIMARY: &str =
204 r#"Person(address wallet, string name) Asset(address token, uint256 amount)"#;
205 const CIRCULAR: &str = r#"
206 Transaction(Person from, Person to, Asset tx)
207 Asset(Person token, uint256 amount)
208 Person(Asset wallet, string name)
209 "#;
210 const MESSY: &str = r#"
211 Person(address wallet, string name)
212 Asset(address token, uint256 amount)
213 Transaction(Person from, Person to, Asset tx)
214 "#;
215
216 #[test]
217 fn empty_type() {
218 let empty_domain_type =
219 ComponentType { span: "EIP712Domain()", type_name: "EIP712Domain", props: vec![] };
220 assert_eq!(ComponentType::parse("EIP712Domain()"), Ok(empty_domain_type.clone()));
221
222 assert_eq!(
223 EncodeType::try_from("EIP712Domain()"),
224 Ok(EncodeType { types: vec![empty_domain_type] })
225 );
226 }
227
228 #[test]
229 fn test_component_type() {
230 assert_eq!(
231 ComponentType::parse("Transaction(Person from,Person to,Asset tx)"),
232 Ok(ComponentType {
233 span: "Transaction(Person from,Person to,Asset tx)",
234 type_name: "Transaction",
235 props: vec![
236 "Person from".try_into().unwrap(),
237 "Person to".try_into().unwrap(),
238 "Asset tx".try_into().unwrap(),
239 ],
240 })
241 );
242 }
243
244 #[test]
245 fn test_encode_type() {
246 assert_eq!(
247 EncodeType::parse(CANONICAL),
248 Ok(EncodeType {
249 types: vec![
250 "Transaction(Person from,Person to,Asset tx)".try_into().unwrap(),
251 "Asset(address token,uint256 amount)".try_into().unwrap(),
252 "Person(address wallet,string name)".try_into().unwrap(),
253 ]
254 })
255 );
256 assert_eq!(EncodeType::parse(CANONICAL).unwrap().canonicalize(), Ok(CANONICAL.to_string()));
257 }
258
259 #[test]
260 fn test_encode_type_messy() {
261 assert_eq!(EncodeType::parse(MESSY).unwrap().canonicalize(), Ok(CANONICAL.to_string()));
262 }
263
264 #[test]
265 fn test_fails_encode_type_missing_type() {
266 assert_eq!(
267 EncodeType::parse(MISSING_COMPONENT).unwrap().canonicalize(),
268 Err(Error::MissingType("Asset".into()))
269 );
270 }
271
272 #[test]
273 fn test_fails_encode_type_multi_primary() {
274 assert_eq!(
275 EncodeType::parse(MISSING_PRIMARY).unwrap().canonicalize(),
276 Err(Error::MissingType("primary component: Asset, Person".into()))
277 );
278 }
279
280 #[test]
281 fn test_fails_encode_type_circular() {
282 assert_eq!(
283 EncodeType::parse(CIRCULAR).unwrap().canonicalize(),
284 Err(Error::CircularDependency("Transaction".into()))
285 );
286 }
287}