tsproto_structs/
book_to_messages.rs

1use std::str::FromStr;
2
3use heck::*;
4use once_cell::sync::Lazy;
5use serde::Deserialize;
6
7use crate::book::{BookDeclarations, Property, Struct};
8use crate::messages::{Field, Message, MessageDeclarations};
9use crate::*;
10
11pub const DATA_STR: &str =
12	include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/declarations/BookToMessages.toml"));
13
14pub static DATA: Lazy<BookToMessagesDeclarations<'static>> = Lazy::new(|| {
15	let rules: TomlStruct = toml::from_str(DATA_STR).unwrap();
16	let book = &book::DATA;
17	let messages = &messages::DATA;
18
19	let decls: Vec<_> = rules
20		.rule
21		.into_iter()
22		.map(|r| {
23			let msg = messages.get_message(&r.to);
24			let msg_fields =
25				msg.attributes.iter().map(|a| messages.get_field(a)).collect::<Vec<_>>();
26			let book_struct = book
27				.structs
28				.iter()
29				.find(|s| s.name == r.from)
30				.unwrap_or_else(|| panic!("Cannot find struct {}", r.from));
31
32			let find_prop =
33				|name: &str, book_struct: &'static Struct| -> Option<&'static Property> {
34					book_struct.properties.iter().find(|p| p.name == *name)
35				};
36
37			// Map RuleProperty to RuleKind
38			let to_rule_kind = |p: RuleProperty| {
39				p.assert_valid();
40
41				if p.function.is_some() {
42					if p.type_s.is_some() {
43						RuleKind::ArgumentFunction {
44							type_s: p.type_s.unwrap(),
45							from: p.from.unwrap(),
46							name: p.function.unwrap(),
47							to: p
48								.tolist
49								.unwrap()
50								.into_iter()
51								.map(|p| find_field(&p, &msg_fields))
52								.collect(),
53						}
54					} else {
55						RuleKind::Function {
56							from: p.from.as_ref().map(|p| {
57								find_prop(p, book_struct).unwrap_or_else(|| {
58									panic!("No such (nested) property {} found in struct", p)
59								})
60							}),
61							name: p.function.unwrap(),
62							to: p
63								.tolist
64								.unwrap()
65								.into_iter()
66								.map(|p| find_field(&p, &msg_fields))
67								.collect(),
68						}
69					}
70				} else if let Some(prop) = find_prop(p.from.as_ref().unwrap(), book_struct) {
71					RuleKind::Map { from: prop, to: find_field(&p.to.unwrap(), &msg_fields) }
72				} else {
73					RuleKind::ArgumentMap {
74						from: p.from.unwrap(),
75						to: find_field(&p.to.unwrap(), &msg_fields),
76					}
77				}
78			};
79
80			let mut ev = Event {
81				op: r.operation.parse().expect("Failed to parse operation"),
82				ids: r.ids.into_iter().map(to_rule_kind).collect(),
83				msg,
84				book_struct,
85				rules: r.properties.into_iter().map(to_rule_kind).collect(),
86			};
87
88			// Add ids, which are required fields in the message.
89			// The filter checks that the message is not optional.
90			for field in msg_fields.iter().filter(|f| msg.attributes.iter().any(|a| *a == f.map)) {
91				if !ev.ids.iter().any(|i| match i {
92					RuleKind::Map { to, .. } => to == field,
93					RuleKind::ArgumentMap { to, .. } => to == field,
94					RuleKind::Function { to, .. } | RuleKind::ArgumentFunction { to, .. } => {
95						to.contains(field)
96					}
97				}) {
98					// Try to find matching property
99					if let Some(prop) = book
100						.get_struct(&ev.book_struct.name)
101						.properties
102						.iter()
103						.find(|p| !p.opt && p.name == field.pretty)
104					{
105						ev.ids.push(RuleKind::Map { from: prop, to: field })
106					}
107					// The property may be in the properties
108				}
109			}
110
111			// Add properties
112			for field in msg_fields.iter().filter(|f| !msg.attributes.iter().any(|a| *a == f.map)) {
113				if !ev.ids.iter().chain(ev.rules.iter()).any(|i| match i {
114					RuleKind::Map { to, .. } => to == field,
115					RuleKind::ArgumentMap { to, .. } => to == field,
116					RuleKind::Function { to, .. } | RuleKind::ArgumentFunction { to, .. } => {
117						to.contains(field)
118					}
119				}) {
120					// We ignore that properties are set as option. In all current cases, it
121					// makes no sense to set them to `None`, so we handle them the same way as
122					// non-optional properties.
123					if let Some(prop) = book
124						.get_struct(&ev.book_struct.name)
125						.properties
126						.iter()
127						.find(|p| p.name == field.pretty)
128					{
129						if !ev.ids.iter().chain(ev.rules.iter()).any(|i| i.from_name() == prop.name)
130						{
131							ev.rules.push(RuleKind::Map { from: prop, to: field })
132						}
133					}
134				}
135			}
136
137			ev
138		})
139		.collect();
140
141	BookToMessagesDeclarations { book, messages, decls }
142});
143
144#[derive(Debug)]
145pub struct BookToMessagesDeclarations<'a> {
146	pub book: &'a BookDeclarations,
147	pub messages: &'a MessageDeclarations,
148	pub decls: Vec<Event<'a>>,
149}
150
151#[derive(Debug)]
152pub struct Event<'a> {
153	pub op: RuleOp,
154	pub msg: &'a Message,
155	pub book_struct: &'a Struct,
156	pub ids: Vec<RuleKind<'a>>,
157	pub rules: Vec<RuleKind<'a>>,
158}
159
160#[derive(Debug)]
161pub enum RuleKind<'a> {
162	Map { from: &'a Property, to: &'a Field },
163	ArgumentMap { from: String, to: &'a Field },
164	Function { from: Option<&'a Property>, name: String, to: Vec<&'a Field> },
165	ArgumentFunction { from: String, type_s: String, name: String, to: Vec<&'a Field> },
166}
167
168#[derive(Debug, PartialEq, Eq, Clone, Copy)]
169pub enum RuleOp {
170	Add,
171	Remove,
172	Update,
173}
174
175#[derive(Deserialize, Debug)]
176#[serde(deny_unknown_fields)]
177struct TomlStruct {
178	rule: Vec<Rule>,
179}
180
181#[derive(Clone, Deserialize, Debug)]
182#[serde(deny_unknown_fields)]
183pub struct Rule {
184	from: String,
185	to: String,
186	operation: String,
187	#[serde(default = "Vec::new")]
188	ids: Vec<RuleProperty>,
189	#[serde(default = "Vec::new")]
190	properties: Vec<RuleProperty>,
191}
192
193#[derive(Clone, Deserialize, Debug)]
194#[serde(deny_unknown_fields)]
195pub struct RuleProperty {
196	from: Option<String>,
197	to: Option<String>,
198
199	#[serde(rename = "type")]
200	type_s: Option<String>,
201	function: Option<String>,
202	tolist: Option<Vec<String>>,
203}
204
205impl RuleProperty {
206	fn assert_valid(&self) {
207		if let Some(to) = &self.to {
208			assert!(self.from.is_some(), "to-property '{}' is invalid. It needs a 'from'", to);
209			assert!(
210				self.function.is_none(),
211				"to-property '{}' is invalid. It must not have a 'function'",
212				to
213			);
214			assert!(
215				self.tolist.is_none(),
216				"to-property '{}' is invalid. It must not have a 'tolist'",
217				to
218			);
219			assert!(
220				self.type_s.is_none(),
221				"to-property '{}' is invalid. It must not have a 'type'",
222				to
223			);
224		} else if let Some(fun) = &self.function {
225			assert!(
226				self.tolist.is_some(),
227				"function-property '{}' is invalid. It needs 'tolist'",
228				fun
229			);
230			assert!(
231				self.type_s.is_none() || self.from.is_some(),
232				"function-property '{}' is invalid. If the type ({:?}) is set, from must be set \
233				 too",
234				fun,
235				self.type_s
236			);
237		} else {
238			panic!(
239				"Property is invalid. It needs either a 'to' or 'tolist'+'function'.Info: \
240				 tolist={:?} type={:?} from={:?}",
241				self.tolist, self.type_s, self.from
242			);
243		}
244	}
245}
246
247impl FromStr for RuleOp {
248	type Err = fmt::Error;
249	fn from_str(s: &str) -> Result<Self> {
250		if s == "add" {
251			Ok(RuleOp::Add)
252		} else if s == "remove" {
253			Ok(RuleOp::Remove)
254		} else if s == "update" {
255			Ok(RuleOp::Update)
256		} else {
257			eprintln!("Cannot parse operation, needs to be add, remove or update");
258			Err(fmt::Error)
259		}
260	}
261}
262
263// the in rust callable name (in PascalCase) from the field
264fn find_field<'a>(name: &str, msg_fields: &[&'a Field]) -> &'a Field {
265	*msg_fields
266		.iter()
267		.find(|f| f.pretty == name)
268		.unwrap_or_else(|| panic!("Cannot find field '{}'", name))
269}
270
271impl<'a> RuleKind<'a> {
272	pub fn from_name(&'a self) -> &'a str {
273		match self {
274			RuleKind::Map { from, .. } => &from.name,
275			RuleKind::ArgumentMap { from, .. } => &from,
276			RuleKind::Function { from, name, .. } => {
277				&from.unwrap_or_else(|| panic!("From not set for function {}", name)).name
278			}
279			RuleKind::ArgumentFunction { from, .. } => &from,
280		}
281	}
282
283	pub fn from_name_singular(&'a self) -> &'a str {
284		let name = self.from_name();
285		if let Some(s) = name.strip_suffix('s') { s } else { name }
286	}
287
288	pub fn from(&self) -> &'a Property {
289		match self {
290			RuleKind::Map { from, .. } => from,
291			RuleKind::Function { from, name, .. } => {
292				from.unwrap_or_else(|| panic!("From not set for function {}", name))
293			}
294			RuleKind::ArgumentMap { .. } | RuleKind::ArgumentFunction { .. } => {
295				panic!("From is not a property for argument functions")
296			}
297		}
298	}
299
300	pub fn is_function(&self) -> bool {
301		matches!(self, RuleKind::Function { .. } | RuleKind::ArgumentFunction { .. })
302	}
303
304	pub fn get_type(&self) -> RustType {
305		match self {
306			RuleKind::Map { .. } | RuleKind::Function { .. } => self.from().get_type().unwrap(),
307			RuleKind::ArgumentMap { to, .. } => to.get_type("").unwrap(),
308			RuleKind::ArgumentFunction { type_s, .. } => type_s.parse().unwrap(),
309		}
310	}
311
312	pub fn get_type_no_option(&self) -> RustType {
313		match self {
314			RuleKind::Map { .. } => {
315				let mut rust_type = self.from().clone();
316				rust_type.opt = false;
317				rust_type.get_type().unwrap()
318			}
319			_ => self.get_type(),
320		}
321	}
322
323	pub fn get_argument(&self) -> String {
324		format!("{}: {}", self.from_name().to_snake_case(), self.get_type().to_ref(true))
325	}
326
327	pub fn get_argument_no_option(&self) -> String {
328		format!("{}: {}", self.from_name().to_snake_case(), self.get_type_no_option().to_ref(true))
329	}
330}
331
332impl<'a> Event<'a> {
333	/// The name of the change, could be a keyword.
334	pub fn get_small_name(&self) -> String {
335		// Strip own struct name and 'Request'
336		self.msg.name.replace(&self.book_struct.name, "").replace("Request", "")
337	}
338
339	/// The small name, not a keyword
340	pub fn get_change_name(&self) -> String {
341		let small_change_name = self.get_small_name();
342		if small_change_name == "Move" { self.msg.name.clone() } else { small_change_name }
343	}
344}