tsproto_structs/
messages_to_book.rs

1use std::collections::HashSet;
2use std::str::FromStr;
3
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/MessagesToBook.toml"));
13
14pub static DATA: Lazy<MessagesToBookDeclarations<'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 mut decls: Vec<_> = rules
20		.rule
21		.into_iter()
22		.map(|r| {
23			let msg = messages.get_message(&r.from);
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.to)
30				.unwrap_or_else(|| panic!("Cannot find struct {}", r.to));
31
32			let mut ev = Event {
33				op: r.operation.parse().expect("Failed to parse operation"),
34				id: r.id.iter().map(|s| find_field(s, &msg_fields)).collect(),
35				msg,
36				book_struct,
37				rules: r
38					.properties
39					.into_iter()
40					.map(|p| {
41						assert!(p.is_valid());
42
43						let find_prop = |name, book_struct: &'static Struct| -> &'static Property {
44							if let Some(prop) =
45								book_struct.properties.iter().find(|p| p.name == name)
46							{
47								return prop;
48							}
49							panic!(
50								"No such (nested) property {} found in struct {}",
51								name, book_struct.name,
52							);
53						};
54
55						if p.function.is_some() {
56							RuleKind::Function {
57								name: p.function.unwrap(),
58								to: p
59									.tolist
60									.unwrap()
61									.into_iter()
62									.map(|p| find_prop(p, book_struct))
63									.collect(),
64							}
65						} else {
66							RuleKind::Map {
67								from: find_field(&p.from.unwrap(), &msg_fields),
68								to: find_prop(p.to.unwrap(), book_struct),
69								op: p
70									.operation
71									.map(|s| s.parse().expect("Invalid operation for property"))
72									.unwrap_or(RuleOp::Update),
73							}
74						}
75					})
76					.collect(),
77			};
78
79			// Add attributes with the same name automatically (if they are not
80			// yet there).
81			let used_flds = ev
82				.rules
83				.iter()
84				.filter_map(|f| match *f {
85					RuleKind::Map { from, .. } => Some(from),
86					_ => None,
87				})
88				.collect::<HashSet<_>>();
89
90			let mut used_props = vec![];
91			for rule in &ev.rules {
92				if let RuleKind::Function { to, .. } = rule {
93					for p in to {
94						used_props.push(p.name.clone());
95					}
96				}
97			}
98
99			for fld in &msg_fields {
100				if used_flds.contains(fld) {
101					continue;
102				}
103				if let Some(prop) = book
104					.get_struct(&ev.book_struct.name)
105					.properties
106					.iter()
107					.find(|p| p.name == fld.pretty)
108				{
109					if used_props.contains(&prop.name) {
110						continue;
111					}
112
113					ev.rules.push(RuleKind::Map { from: fld, to: prop, op: RuleOp::Update });
114				}
115			}
116
117			ev
118		})
119		.collect();
120
121	// InitServer is done manually
122	decls.retain(|ev| ev.msg.name != "InitServer");
123
124	MessagesToBookDeclarations { book, messages, decls }
125});
126
127#[derive(Debug)]
128pub struct MessagesToBookDeclarations<'a> {
129	pub book: &'a BookDeclarations,
130	pub messages: &'a MessageDeclarations,
131	pub decls: Vec<Event<'a>>,
132}
133
134#[derive(Debug)]
135pub struct Event<'a> {
136	pub op: RuleOp,
137	/// Unique access tuple to get the property
138	pub id: Vec<&'a Field>,
139	pub msg: &'a Message,
140	pub book_struct: &'a Struct,
141	pub rules: Vec<RuleKind<'a>>,
142}
143
144#[derive(Debug)]
145pub enum RuleKind<'a> {
146	Map { from: &'a Field, to: &'a Property, op: RuleOp },
147	Function { name: String, to: Vec<&'a Property> },
148}
149
150#[derive(Debug, PartialEq, Eq, Clone, Copy)]
151pub enum RuleOp {
152	Add,
153	Remove,
154	Update,
155}
156
157#[derive(Clone, Deserialize, Debug)]
158#[serde(deny_unknown_fields)]
159struct TomlStruct {
160	rule: Vec<Rule>,
161}
162
163#[derive(Clone, Deserialize, Debug)]
164#[serde(deny_unknown_fields)]
165struct Rule {
166	id: Vec<String>,
167	from: String,
168	to: String,
169	operation: String,
170	#[serde(default = "Vec::new")]
171	properties: Vec<RuleProperty>,
172}
173
174#[derive(Clone, Deserialize, Debug)]
175#[serde(deny_unknown_fields)]
176struct RuleProperty {
177	from: Option<String>,
178	to: Option<String>,
179	operation: Option<String>,
180
181	function: Option<String>,
182	tolist: Option<Vec<String>>,
183}
184
185impl Event<'_> {
186	/// Fill the id of a `PropertyId`.
187	///
188	/// `msg` is the name of the message object.
189	pub fn get_id_args(&self, msg: &str) -> String {
190		let mut res = String::new();
191		for f in &self.id {
192			if !res.is_empty() {
193				res.push_str(", ");
194			}
195			if !f.get_type("").unwrap().is_primitive() {
196				res.push('&');
197			}
198			res.push_str(&format!("{}.{}", msg, f.get_rust_name()));
199		}
200		res
201	}
202
203	/// Create a `PropertyId` from a message struct.
204	///
205	/// `msg` is the name of the message object.
206	pub fn get_property_id(&self, p: &Property, from: &Field, msg: &str) -> String {
207		let mut ids = self.get_id_args(msg);
208		if let Some(m) = &p.modifier {
209			if !ids.is_empty() {
210				ids.push_str(", ");
211			}
212			if m == "map" || m == "array" || m == "set" {
213				ids.push_str(&format!("{}.{}", msg, from.get_rust_name()));
214			} else {
215				panic!("Unknown modifier {}", m);
216			}
217		}
218		format!("PropertyId::{}{}{}", self.book_struct.name, p.get_name(), embrace(&ids))
219	}
220}
221
222impl RuleProperty {
223	fn is_valid(&self) -> bool {
224		if self.from.is_some() {
225			self.to.is_some() && self.function.is_none() && self.tolist.is_none()
226		} else {
227			self.from.is_none()
228				&& self.to.is_none()
229				&& self.operation.is_none()
230				&& self.function.is_some()
231				&& self.tolist.is_some()
232		}
233	}
234}
235
236impl FromStr for RuleOp {
237	type Err = fmt::Error;
238	fn from_str(s: &str) -> Result<Self> {
239		if s == "add" {
240			Ok(RuleOp::Add)
241		} else if s == "remove" {
242			Ok(RuleOp::Remove)
243		} else if s == "update" {
244			Ok(RuleOp::Update)
245		} else {
246			eprintln!("Cannot parse operation, needs to be add, remove or update");
247			Err(fmt::Error)
248		}
249	}
250}
251
252// the in rust callable name (in PascalCase) from the field
253fn find_field<'a>(name: &str, msg_fields: &[&'a Field]) -> &'a Field {
254	*msg_fields
255		.iter()
256		.find(|f| f.pretty == name)
257		.unwrap_or_else(|| panic!("Cannot find field '{}'", name))
258}
259
260impl<'a> RuleKind<'a> {
261	pub fn is_function(&self) -> bool { matches!(self, RuleKind::Function { .. }) }
262}