1use std::{collections::BTreeMap, fmt, num::ParseIntError, str::FromStr};
2
3use iref::Uri;
4use serde::{Deserialize, Serialize};
5
6use crate::Value;
7
8pub type StructName = String;
9
10#[derive(Debug, thiserror::Error)]
12pub enum TypesFetchError {
13 #[error("remote EIP712 types are not supported")]
18 Unsupported,
19}
20
21pub trait TypesLoader {
26 #[allow(async_fn_in_trait)]
32 async fn fetch_types(&self, uri: &Uri) -> Result<Types, TypesFetchError>;
33}
34
35impl TypesLoader for () {
38 async fn fetch_types(&self, _uri: &Uri) -> Result<Types, TypesFetchError> {
39 Err(TypesFetchError::Unsupported)
40 }
41}
42
43impl<T: TypesLoader> TypesLoader for &T {
44 async fn fetch_types(&self, uri: &Uri) -> Result<Types, TypesFetchError> {
45 T::fetch_types(*self, uri).await
46 }
47}
48
49pub trait Eip712TypesLoaderProvider {
50 type Loader: TypesLoader;
51
52 fn eip712_types(&self) -> &Self::Loader;
53}
54
55impl<E: Eip712TypesLoaderProvider> Eip712TypesLoaderProvider for &E {
56 type Loader = E::Loader;
57
58 fn eip712_types(&self) -> &Self::Loader {
59 E::eip712_types(*self)
60 }
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
65#[serde(untagged)]
66#[serde(try_from = "String", into = "String")]
67pub enum TypeRef {
68 BytesN(usize),
69 UintN(usize),
70 IntN(usize),
71 Bool,
72 Address,
73 Bytes,
74 String,
75 Array(Box<TypeRef>),
76 ArrayN(Box<TypeRef>, usize),
77 Struct(StructName),
78}
79
80impl TypeRef {
81 pub fn as_struct_name(&self) -> Option<&StructName> {
83 match self {
84 Self::Struct(name) => Some(name),
85 Self::Array(type_box) | Self::ArrayN(type_box, _) => type_box.as_struct_name(),
86 _ => None,
87 }
88 }
89}
90
91#[derive(Debug, thiserror::Error)]
92pub enum TypeParseError {
93 #[error("Unmatched bracket")]
94 UnmatchedBracket,
95 #[error("Unable to parse data type size: {0}")]
96 SizeParse(#[from] ParseIntError),
97}
98
99impl FromStr for TypeRef {
100 type Err = TypeParseError;
101
102 fn from_str(string: &str) -> Result<Self, Self::Err> {
103 match string {
104 "bytes" => return Ok(TypeRef::Bytes),
105 "string" => return Ok(TypeRef::String),
106 "address" => return Ok(TypeRef::Address),
107 "bool" => return Ok(TypeRef::Bool),
108 _ => {}
109 }
110
111 if string.ends_with(']') {
112 let mut parts = string.rsplitn(2, '[');
113 let amount_str = parts.next().unwrap().split(']').next().unwrap();
114 let inner = parts.next().ok_or(TypeParseError::UnmatchedBracket)?;
115 let base = inner.parse()?;
116 if amount_str.is_empty() {
117 return Ok(TypeRef::Array(Box::new(base)));
118 } else {
119 return Ok(TypeRef::ArrayN(
120 Box::new(base),
121 usize::from_str(amount_str)?,
122 ));
123 }
124 } else if let Some(suffix) = string.strip_prefix("uint") {
125 return Ok(TypeRef::UintN(usize::from_str(suffix)?));
126 } else if let Some(suffix) = string.strip_prefix("int") {
127 return Ok(TypeRef::IntN(usize::from_str(suffix)?));
128 } else if let Some(suffix) = string.strip_prefix("bytes") {
129 return Ok(TypeRef::BytesN(usize::from_str(suffix)?));
130 }
131
132 Ok(TypeRef::Struct(string.to_owned()))
133 }
134}
135
136impl TryFrom<String> for TypeRef {
137 type Error = TypeParseError;
138
139 fn try_from(value: String) -> Result<Self, Self::Error> {
140 value.parse()
141 }
142}
143
144impl fmt::Display for TypeRef {
145 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146 match self {
147 TypeRef::Bytes => write!(f, "bytes"),
148 TypeRef::String => write!(f, "string"),
149 TypeRef::BytesN(n) => write!(f, "bytes{}", n),
150 TypeRef::UintN(n) => write!(f, "uint{}", n),
151 TypeRef::IntN(n) => write!(f, "int{}", n),
152 TypeRef::Bool => write!(f, "bool"),
153 TypeRef::Address => write!(f, "address"),
154 TypeRef::Array(type_) => {
155 write!(f, "{}[]", *type_)
156 }
157 TypeRef::ArrayN(type_, n) => {
158 write!(f, "{}[{}]", *type_, n)
159 }
160 TypeRef::Struct(name) => {
161 write!(f, "{}", name)
162 }
163 }
164 }
165}
166
167impl From<TypeRef> for String {
168 fn from(type_: TypeRef) -> String {
169 match type_ {
170 TypeRef::Struct(name) => name,
171 _ => {
172 format!("{}", &type_)
173 }
174 }
175 }
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
181pub struct TypeDefinition(Vec<MemberVariable>);
182
183impl TypeDefinition {
184 pub fn new(member_variables: Vec<MemberVariable>) -> Self {
185 Self(member_variables)
186 }
187
188 pub fn member_variables(&self) -> &[MemberVariable] {
189 &self.0
190 }
191
192 pub fn push(&mut self, m: MemberVariable) {
193 self.0.push(m)
194 }
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
198pub struct MemberVariable {
199 pub name: String,
200
201 #[serde(rename = "type")]
202 pub type_: TypeRef,
203}
204
205impl MemberVariable {
206 pub fn new(name: String, type_: TypeRef) -> Self {
207 Self { name, type_ }
208 }
209}
210
211#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
212pub struct Types {
213 #[serde(rename = "EIP712Domain")]
214 pub eip712_domain: TypeDefinition,
215
216 #[serde(flatten)]
217 pub types: BTreeMap<StructName, TypeDefinition>,
218}
219
220impl Types {
221 pub fn get(&self, struct_name: &str) -> Option<&TypeDefinition> {
222 if struct_name == "EIP712Domain" {
223 Some(&self.eip712_domain)
224 } else {
225 self.types.get(struct_name)
226 }
227 }
228
229 pub fn generate(
233 doc: &Value,
234 primary_type: StructName,
235 domain_type: TypeDefinition,
236 ) -> Result<Self, TypesGenerationError> {
237 Ok(Self {
238 eip712_domain: domain_type,
239 types: Self::generate_inner(doc, primary_type)?,
240 })
241 }
242
243 fn generate_inner(
247 doc: &Value,
248 primary_type: StructName,
249 ) -> Result<BTreeMap<StructName, TypeDefinition>, TypesGenerationError> {
250 let mut output = BTreeMap::default();
252 let mut types = TypeDefinition::default();
255 let doc_jcs = serde_jcs::to_string(doc).map_err(TypesGenerationError::JCS)?;
261 let doc: Value = serde_json::from_str(&doc_jcs).map_err(TypesGenerationError::JCS)?;
262 let object = doc
264 .as_struct()
265 .ok_or(TypesGenerationError::ExpectedObject)?;
266 let mut props: Vec<(&String, &Value)> = object.iter().collect();
267 props.sort_by_cached_key(|(name, _value)| name.encode_utf16().collect::<Vec<u16>>());
270 for (property_name, value) in props {
271 match value {
272 Value::Bool(_) => {
274 types.push(MemberVariable {
276 type_: TypeRef::Bool,
277 name: String::from(property_name),
278 });
279 }
280 Value::Integer(_) => {
281 types.push(MemberVariable {
283 type_: TypeRef::UintN(256),
284 name: String::from(property_name),
285 });
286 }
287 Value::String(_) => {
288 types.push(MemberVariable {
290 type_: TypeRef::String,
291 name: String::from(property_name),
292 });
293 }
294 Value::Array(array) => {
296 let mut values = array.iter();
298 let first_value = values
299 .next()
300 .ok_or_else(|| TypesGenerationError::EmptyArray(property_name.clone()))?;
301 match first_value {
302 Value::Bool(_) => {
303 for value in values {
305 if !matches!(value, Value::Bool(_)) {
306 return Err(TypesGenerationError::ArrayInconsistency(
307 "boolean",
308 property_name.clone(),
309 ));
310 }
311 }
312 types.push(MemberVariable {
313 type_: TypeRef::Array(Box::new(TypeRef::Bool)),
314 name: String::from(property_name),
315 });
316 }
317 Value::Integer(_) => {
318 for value in values {
320 if !matches!(value, Value::Integer(_)) {
321 return Err(TypesGenerationError::ArrayInconsistency(
322 "number",
323 property_name.clone(),
324 ));
325 }
326 }
327 types.push(MemberVariable {
328 type_: TypeRef::Array(Box::new(TypeRef::UintN(256))),
329 name: String::from(property_name),
330 });
331 }
332 Value::String(_) => {
333 for value in values {
335 if !matches!(value, Value::String(_)) {
336 return Err(TypesGenerationError::ArrayInconsistency(
337 "string",
338 property_name.clone(),
339 ));
340 }
341 }
342 types.push(MemberVariable {
343 type_: TypeRef::Array(Box::new(TypeRef::String)),
344 name: String::from(property_name),
345 });
346 }
347 _ => {
348 return Err(TypesGenerationError::ComplexArrayValue(
349 property_name.clone(),
350 ));
351 }
352 }
353 }
354 Value::Struct(object) => {
355 let mut recursive_output =
357 Self::generate_inner(&Value::Struct(object.clone()), primary_type.clone())?;
358 let recursive_types =
360 recursive_output.remove(&primary_type).ok_or_else(|| {
361 TypesGenerationError::MissingPrimaryTypeInRecursiveOutput(
362 primary_type.clone(),
363 )
364 })?;
365 let property_type = property_to_struct_name(property_name);
367 types.push(MemberVariable {
368 name: String::from(property_name),
369 type_: TypeRef::Struct(property_type.clone()),
370 });
371 output.insert(property_type, recursive_types);
373 for (prop, type_) in recursive_output.into_iter() {
375 output.insert(prop, type_);
376 }
377 }
378 _ => {
379 return Err(TypesGenerationError::ComplexValue(property_name.clone()));
380 }
381 }
382 }
383 output.insert(primary_type, types);
385 Ok(output)
386 }
387}
388
389#[derive(Debug, thiserror::Error)]
390pub enum TypesGenerationError {
391 #[error("Expected object")]
392 ExpectedObject,
393 #[error("Found empty array under property: {0}")]
394 EmptyArray(String),
395 #[error("Array inconsistency: expected type {0} under property: {1}")]
396 ArrayInconsistency(&'static str, String),
397 #[error("Array value must be boolean, number or string. Property: {0}")]
398 ComplexArrayValue(String),
399 #[error("Value must be boolean, number, string, array or struct. Property: {0}")]
400 ComplexValue(String),
401 #[error("Missing primaryType in recursive output. primaryType: {0}")]
402 MissingPrimaryTypeInRecursiveOutput(String),
403 #[error("JCS: {0}")]
404 JCS(serde_json::Error),
405 #[error("Proof type already exists")]
406 ProofAlreadyExists,
407}
408
409fn property_to_struct_name(property_name: &str) -> StructName {
410 let mut chars = property_name.chars();
412 let first_char = chars.next().unwrap_or_default();
413 first_char.to_uppercase().chain(chars).collect()
414}
415
416#[cfg(test)]
417lazy_static::lazy_static! {
418 pub static ref EXAMPLE_TYPES: serde_json::Value = {
421 serde_json::json!({
422 "Data": [
423 {
424 "name": "job",
425 "type": "Job"
426 },
427 {
428 "name": "name",
429 "type": "Name"
430 }
431 ],
432 "Job": [
433 {
434 "name": "employer",
435 "type": "string"
436 },
437 {
438 "name": "jobTitle",
439 "type": "string"
440 }
441 ],
442 "Name": [
443 {
444 "name": "firstName",
445 "type": "string"
446 },
447 {
448 "name": "lastName",
449 "type": "string"
450 }
451 ],
452 "Document": [
453 {
454 "name": "@context",
455 "type": "string[]"
456 },
457 {
458 "name": "@type",
459 "type": "string"
460 },
461 {
462 "name": "data",
463 "type": "Data"
464 },
465 {
466 "name": "proof",
467 "type": "Proof"
468 },
469 {
470 "name": "telephone",
471 "type": "string"
472 }
473 ],
474 "Proof": [
475 {
476 "name": "created",
477 "type": "string"
478 },
479 {
480 "name": "proofPurpose",
481 "type": "string"
482 },
483 {
484 "name": "type",
485 "type": "string"
486 },
487 {
488 "name": "verificationMethod",
489 "type": "string"
490 }
491 ]
492 })
493 };
494}