iota_graphql_rpc/types/
display.rs1use async_graphql::*;
6use diesel::{ExpressionMethods, OptionalExtension, QueryDsl};
7use iota_indexer::{models::display::StoredDisplay, schema::display};
8use iota_json_rpc_types::IotaMoveValue;
9use iota_types::TypeTag;
10use move_core_types::annotated_value::{MoveStruct, MoveValue};
11
12use crate::{
13 data::{Db, DbConnection, QueryExecutor},
14 error::Error,
15};
16
17pub(crate) struct Display {
18 pub stored: StoredDisplay,
19}
20
21#[derive(Debug, SimpleObject)]
25pub(crate) struct DisplayEntry {
26 pub key: String,
28 pub value: Option<String>,
30 pub error: Option<String>,
32}
33
34#[derive(thiserror::Error, Debug)]
35pub(crate) enum DisplayRenderError {
36 #[error("Display template value cannot be empty")]
37 TemplateValueEmpty,
38 #[error("Display template value of {0} exceeds maximum depth of {1}")]
39 ExceedsLookupDepth(usize, u64),
40 #[error("Vector of name {0} is not supported as a Display value")]
41 Vector(String),
42 #[error("Field '{0}' not found")]
43 FieldNotFound(String),
44 #[error("Unexpected MoveValue")]
45 UnexpectedMoveValue,
46}
47
48impl Display {
49 pub(crate) async fn query(db: &Db, type_: TypeTag) -> Result<Option<Display>, Error> {
51 let stored: Option<StoredDisplay> = db
52 .execute(move |conn| {
53 conn.first(move || {
54 use display::dsl;
55 dsl::display.filter(
56 dsl::object_type.eq(type_.to_canonical_string(true)),
57 )
58 })
59 .optional()
60 })
61 .await?;
62
63 Ok(stored.map(|stored| Display { stored }))
64 }
65
66 pub(crate) fn render(&self, struct_: &MoveStruct) -> Result<Vec<DisplayEntry>, Error> {
69 let event = self
70 .stored
71 .to_display_update_event()
72 .map_err(|e| Error::Internal(e.to_string()))?;
73
74 let mut rendered = vec![];
75 for entry in event.fields.contents {
76 rendered.push(match parse_template(&entry.value, struct_) {
77 Ok(v) => DisplayEntry::create_value(entry.key, v),
78 Err(e) => DisplayEntry::create_error(entry.key, e.to_string()),
79 });
80 }
81
82 Ok(rendered)
83 }
84}
85
86impl DisplayEntry {
87 pub(crate) fn create_value(key: String, value: String) -> Self {
88 Self {
89 key,
90 value: Some(value),
91 error: None,
92 }
93 }
94
95 pub(crate) fn create_error(key: String, error: String) -> Self {
96 Self {
97 key,
98 value: None,
99 error: Some(error),
100 }
101 }
102}
103
104fn parse_template(template: &str, move_struct: &MoveStruct) -> Result<String, DisplayRenderError> {
111 let mut output = template.to_string();
112 let mut var_name = String::new();
113 let mut in_braces = false;
114 let mut escaped = false;
115
116 for ch in template.chars() {
117 match ch {
118 '\\' => {
119 escaped = true;
120 continue;
121 }
122 '{' if !escaped => {
123 in_braces = true;
124 var_name.clear();
125 }
126 '}' if !escaped => {
127 in_braces = false;
128 let value = get_value_from_move_struct(move_struct, &var_name)?;
129 output = output.replace(&format!("{{{}}}", var_name), &value.to_string());
130 }
131 _ if !escaped => {
132 if in_braces {
133 var_name.push(ch);
134 }
135 }
136 _ => {}
137 }
138 escaped = false;
139 }
140
141 Ok(output.replace('\\', ""))
142}
143
144pub(crate) fn get_value_from_move_struct(
148 move_struct: &MoveStruct,
149 var_name: &str,
150) -> Result<String, DisplayRenderError> {
151 let parts: Vec<&str> = var_name.split('.').collect();
152 if parts.is_empty() {
153 return Err(DisplayRenderError::TemplateValueEmpty);
154 }
155 if parts.len() > 10 {
158 return Err(DisplayRenderError::ExceedsLookupDepth(parts.len(), 10));
159 }
160
161 let start_value = &MoveValue::Struct(move_struct.clone());
163
164 let result = parts
165 .iter()
166 .try_fold(start_value, |current_value, part| match current_value {
167 MoveValue::Struct(s) => s
168 .fields
169 .iter()
170 .find_map(|(id, value)| {
171 if id.as_str() == *part {
172 Some(value)
173 } else {
174 None
175 }
176 })
177 .ok_or_else(|| DisplayRenderError::FieldNotFound(part.to_string())),
178 _ => Err(DisplayRenderError::UnexpectedMoveValue),
179 })?;
180
181 let iota_move_value: IotaMoveValue = result.clone().into();
183
184 match iota_move_value {
185 IotaMoveValue::Option(move_option) => match move_option.as_ref() {
186 Some(move_value) => Ok(move_value.to_string()),
187 None => Ok("".to_string()),
188 },
189 IotaMoveValue::Vector(_) => Err(DisplayRenderError::Vector(var_name.to_string())),
190 _ => Ok(iota_move_value.to_string()),
191 }
192}