tsproto_structs/
lib.rs

1//! `tsproto-structs` contains machine readable data for several TeamSpeak related topics.
2//!
3//! The underlying data files can be found in the
4//! [tsdeclarations](https://github.com/ReSpeak/tsdeclarations) repository.
5
6use std::fmt;
7use std::str::FromStr;
8
9use heck::*;
10use serde::Deserialize;
11
12type Result<T> = std::result::Result<T, fmt::Error>;
13
14pub mod book;
15pub mod book_to_messages;
16pub mod enums;
17pub mod errors;
18pub mod messages;
19pub mod messages_to_book;
20pub mod permissions;
21pub mod versions;
22
23#[derive(Debug, Deserialize)]
24pub struct EnumValue {
25	pub name: String,
26	pub doc: String,
27	pub num: String,
28}
29
30#[derive(Clone, Debug)]
31pub enum InnerRustType {
32	Primitive(String),
33	Struct(String),
34	Ref(Box<InnerRustType>),
35	Option(Box<InnerRustType>),
36	/// Key, value
37	Map(Box<InnerRustType>, Box<InnerRustType>),
38	Set(Box<InnerRustType>),
39	Vec(Box<InnerRustType>),
40	Cow(Box<InnerRustType>),
41}
42
43#[derive(Clone, Debug)]
44pub struct RustType {
45	pub inner: InnerRustType,
46	/// Include a lifetime specifier 'a
47	pub lifetime: bool,
48}
49
50impl InnerRustType {
51	/// `lifetime`: Include 'a for references
52	/// `is_ref`: If this is a refercene, used to emit either `str` or `String` or slices
53	pub fn fmt(&self, f: &mut fmt::Formatter, lifetime: bool, is_ref: bool) -> fmt::Result {
54		let lifetime_str = if lifetime { "'a " } else { "" };
55		match self {
56			Self::Struct(s) if s == "str" => {
57				if is_ref {
58					write!(f, "str")?;
59				} else {
60					write!(f, "String")?;
61				}
62			}
63			Self::Struct(s) if s == "Uid" => {
64				write!(f, "{}", s)?;
65			}
66			Self::Primitive(s) | Self::Struct(s) => write!(f, "{}", s)?,
67			Self::Ref(i) => {
68				write!(f, "&{}", lifetime_str)?;
69				i.fmt(f, lifetime, true)?;
70			}
71			Self::Option(i) => {
72				write!(f, "Option<")?;
73				i.fmt(f, lifetime, false)?;
74				write!(f, ">")?;
75			}
76			Self::Map(k, v) => {
77				write!(f, "HashMap<")?;
78				k.fmt(f, lifetime, false)?;
79				write!(f, ", ")?;
80				v.fmt(f, lifetime, false)?;
81				write!(f, ">")?;
82			}
83			Self::Set(i) => {
84				write!(f, "HashSet<")?;
85				i.fmt(f, lifetime, false)?;
86				write!(f, ">")?;
87			}
88			Self::Vec(i) => {
89				if is_ref {
90					write!(f, "[")?;
91					i.fmt(f, lifetime, false)?;
92					write!(f, "]")?;
93				} else {
94					write!(f, "Vec<")?;
95					i.fmt(f, lifetime, false)?;
96					write!(f, ">")?;
97				}
98			}
99			Self::Cow(i) => {
100				write!(f, "Cow<{}, ", lifetime_str.trim())?;
101				i.fmt(f, false, true)?;
102				write!(f, ">")?;
103			}
104		}
105		Ok(())
106	}
107
108	/// Returns a converted type.
109	pub fn to_ref(&self) -> Self {
110		match self {
111			Self::Struct(s) if s == "UidBuf" => Self::Ref(Box::new(Self::Struct("Uid".into()))),
112			Self::Struct(s) if s == "String" => Self::Ref(Box::new(Self::Struct("str".into()))),
113			Self::Struct(_) | Self::Map(_, _) | Self::Set(_) | Self::Vec(_) => {
114				Self::Ref(Box::new(self.clone()))
115			}
116			Self::Primitive(_) | Self::Ref(_) | Self::Cow(_) => self.clone(),
117			Self::Option(i) => Self::Option(Box::new(i.to_ref())),
118		}
119	}
120
121	/// Returns a converted type.
122	pub fn to_cow(&self) -> Self {
123		match self {
124			Self::Struct(s) if s == "UidBuf" => Self::Cow(Box::new(Self::Struct("Uid".into()))),
125			Self::Struct(s) if s == "String" => Self::Cow(Box::new(Self::Struct("str".into()))),
126			Self::Struct(_) | Self::Map(_, _) | Self::Set(_) | Self::Vec(_) => {
127				Self::Cow(Box::new(self.clone()))
128			}
129			Self::Primitive(_) | Self::Ref(_) | Self::Cow(_) => self.clone(),
130			Self::Option(i) => Self::Option(Box::new(i.to_cow())),
131		}
132	}
133
134	/// Get code snippet for `as_ref`.
135	pub fn code_as_ref(&self, name: &str) -> String {
136		match self {
137			Self::Struct(s) if s == "UidBuf" || s == "str" => format!("{}.as_ref()", name),
138			Self::Struct(s) if s == "String" => format!("{}.as_str()", name),
139			Self::Struct(s) if s == "str" => name.into(),
140			Self::Struct(_) => format!("&{}", name),
141			Self::Map(_, _) | Self::Set(_) | Self::Vec(_) | Self::Cow(_) => {
142				format!("{}.as_ref()", name)
143			}
144			Self::Primitive(_) => name.into(),
145			Self::Ref(i) => {
146				let inner = i.code_as_ref(name);
147				if inner == name {
148					format!("*{}", name)
149				} else if inner.starts_with('&') && &inner[1..] == name {
150					name.into()
151				} else {
152					inner
153				}
154			}
155			Self::Option(i) => {
156				match &**i {
157					// Shortcut
158					Self::Struct(s) if s == "String" => format!("{}.as_deref()", name),
159					_ => {
160						let inner = i.code_as_ref(name);
161						if inner == name {
162							inner
163						} else if inner.starts_with('&') && &inner[1..] == name {
164							format!("{0}.as_ref()", name)
165						} else {
166							format!("{0}.as_ref().map(|{0}| {1})", name, inner)
167						}
168					}
169				}
170			}
171		}
172	}
173
174	pub fn uses_lifetime(&self) -> bool {
175		match self {
176			Self::Struct(s) if s == "Uid" => true,
177			Self::Ref(_) | Self::Cow(_) => true,
178			Self::Map(_, i) | Self::Set(i) | Self::Vec(i) | Self::Option(i) => i.uses_lifetime(),
179			_ => false,
180		}
181	}
182}
183
184impl FromStr for InnerRustType {
185	type Err = fmt::Error;
186	fn from_str(s: &str) -> Result<Self> {
187		if s == "DateTime" {
188			Ok(Self::Primitive("OffsetDateTime".into()))
189		} else if s.starts_with("Duration") {
190			Ok(Self::Primitive("Duration".into()))
191		} else if s == "PermissionId" {
192			Ok(Self::Primitive("Permission".into()))
193		} else if s == "Ts3ErrorCode" {
194			Ok(Self::Primitive("Error".into()))
195		} else if s == "bool"
196			|| s.starts_with('i')
197			|| s.starts_with('u')
198			|| s.starts_with('f')
199			|| s.ends_with("Id")
200			|| s.ends_with("Type")
201			|| s.ends_with("Mode")
202			|| s == "ChannelPermissionHint"
203			|| s == "ClientPermissionHint"
204			|| s == "Codec"
205			|| s == "LogLevel"
206			|| s == "MaxClients"
207			|| s == "IpAddr"
208			|| s == "Reason"
209			|| s == "SocketAddr"
210		{
211			Ok(Self::Primitive(s.into()))
212		} else if s == "Uid" {
213			Ok(Self::Struct("UidBuf".into()))
214		} else if s == "str" || s == "String" {
215			Ok(Self::Struct("String".into()))
216		} else if let Some(rest) = s.strip_prefix('&') {
217			let rest = if rest.starts_with('\'') {
218				let i = rest.find(' ').ok_or_else(|| {
219					eprintln!("Reference type with lifetime has no inner type: {:?}", s);
220					fmt::Error
221				})?;
222				&rest[i + 1..]
223			} else {
224				rest
225			};
226			Ok(Self::Ref(Box::new(rest.parse()?)))
227		} else if let Some(rest) = s.strip_suffix('?') {
228			Ok(Self::Option(Box::new(rest.parse()?)))
229		} else if s.starts_with("Option<") {
230			let rest = &s[7..s.len() - 1];
231			Ok(Self::Option(Box::new(rest.parse()?)))
232		} else if let Some(rest) = s.strip_suffix("[]") {
233			Ok(Self::Vec(Box::new(rest.parse()?)))
234		} else if s.starts_with("HashMap<") {
235			let rest = &s[8..s.len() - 1];
236			let i = rest.find(',').ok_or_else(|| {
237				eprintln!("HashMap without key: {:?}", s);
238				fmt::Error
239			})?;
240			Ok(Self::Map(Box::new((&rest[..i]).parse()?), Box::new(rest[i..].trim().parse()?)))
241		} else if s.starts_with("HashSet<") {
242			let rest = &s[8..s.len() - 1];
243			Ok(Self::Set(Box::new(rest.parse()?)))
244		} else if s.starts_with("Vec<") {
245			let rest = &s[4..s.len() - 1];
246			Ok(Self::Vec(Box::new(rest.parse()?)))
247		} else if s.starts_with('[') {
248			let rest = &s[1..s.len() - 1];
249			if rest.contains(';') {
250				// Slice with explicit length, take as struct
251				Ok(Self::Struct(s.into()))
252			} else {
253				Ok(Self::Vec(Box::new(rest.parse()?)))
254			}
255		} else if let Some(rest) = s.strip_suffix('T') {
256			rest.parse()
257		} else {
258			Ok(Self::Struct(s.into()))
259		}
260	}
261}
262
263impl FromStr for RustType {
264	type Err = fmt::Error;
265	fn from_str(s: &str) -> Result<Self> {
266		Ok(Self { inner: s.parse()?, lifetime: s.contains("&'") })
267	}
268}
269
270impl RustType {
271	pub fn with_opt(s: &str, opt: bool) -> Result<Self> {
272		let inner = s.parse()?;
273		let inner = if opt { InnerRustType::Option(Box::new(inner)) } else { inner };
274		Ok(Self { inner, lifetime: s.contains("&'") })
275	}
276
277	/// `map` has a key
278	pub fn with(s: &str, opt: bool, map: Option<&str>, set: bool, vec: bool) -> Result<Self> {
279		assert!(
280			[map.is_some(), set, vec].iter().filter(|b| **b).count() <= 1,
281			"Too many modifiers active (map: {:?}, set: {:?}, vec: {:?})",
282			map,
283			set,
284			vec
285		);
286		let mut inner = s.parse()?;
287		if let Some(key) = map {
288			inner = InnerRustType::Map(Box::new(key.parse()?), Box::new(inner));
289		}
290		if set {
291			inner = InnerRustType::Set(Box::new(inner));
292		}
293		if vec {
294			inner = InnerRustType::Vec(Box::new(inner));
295		}
296
297		inner = if opt { InnerRustType::Option(Box::new(inner)) } else { inner };
298		Ok(Self { inner, lifetime: s.contains("&'") })
299	}
300
301	pub fn to_opt(&self, opt: bool) -> Self {
302		if opt {
303			Self {
304				inner: InnerRustType::Option(Box::new(self.inner.clone())),
305				lifetime: self.lifetime,
306			}
307		} else {
308			self.clone()
309		}
310	}
311
312	pub fn to_ref(&self, as_ref: bool) -> Self {
313		if as_ref {
314			Self { inner: self.inner.to_ref(), lifetime: self.lifetime }
315		} else {
316			self.clone()
317		}
318	}
319
320	pub fn to_cow(&self) -> Self { Self { inner: self.inner.to_cow(), lifetime: self.lifetime } }
321
322	pub fn lifetime(&self, lifetime: bool) -> Self {
323		let mut r = self.clone();
324		r.lifetime = lifetime;
325		r
326	}
327
328	pub fn wrap_ref(&self) -> Self {
329		Self { inner: InnerRustType::Ref(Box::new(self.inner.clone())), lifetime: self.lifetime }
330	}
331
332	pub fn wrap_opt(&self) -> Self {
333		Self { inner: InnerRustType::Option(Box::new(self.inner.clone())), lifetime: self.lifetime }
334	}
335
336	pub fn is_opt(&self) -> bool { matches!(self.inner, InnerRustType::Option(_)) }
337
338	pub fn is_primitive(&self) -> bool { matches!(self.inner, InnerRustType::Primitive(_)) }
339
340	pub fn is_vec(&self) -> bool { matches!(self.inner, InnerRustType::Vec(_)) }
341
342	pub fn is_cow(&self) -> bool {
343		let inner = if let InnerRustType::Option(i) = &self.inner { &*i } else { &self.inner };
344		matches!(inner, InnerRustType::Cow(_))
345	}
346
347	pub fn uses_lifetime(&self) -> bool { self.inner.uses_lifetime() }
348
349	/// Returns an identifier from this type in camelCase.
350	pub fn to_name(&self) -> String {
351		self.to_string().replace('<', "_").replace('>', "").to_camel_case()
352	}
353
354	/// Get code snippet for `as_ref`.
355	pub fn code_as_ref(&self, name: &str) -> String { self.inner.code_as_ref(name) }
356}
357
358impl From<InnerRustType> for RustType {
359	fn from(inner: InnerRustType) -> Self { Self { inner, lifetime: false } }
360}
361
362impl fmt::Display for RustType {
363	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner.fmt(f, self.lifetime, false) }
364}
365
366fn get_false() -> bool { false }
367
368/// Prepend `/// ` to each line of a string.
369pub fn doc_comment(s: &str) -> String { s.lines().map(|l| format!("/// {}\n", l)).collect() }
370
371/// Indent a string by a given count using tabs.
372pub fn indent<S: AsRef<str>>(s: S, count: usize) -> String {
373	let sref = s.as_ref();
374	let line_count = sref.lines().count();
375	let mut result = String::with_capacity(sref.len() + line_count * count * 4);
376	for l in sref.lines() {
377		if !l.is_empty() {
378			result.push_str(std::iter::repeat("\t").take(count).collect::<String>().as_str());
379		}
380		result.push_str(l);
381		result.push('\n');
382	}
383	result
384}
385
386/// Unindent a string by a given count of tabs.
387pub fn unindent(mut s: &mut String) {
388	std::mem::swap(&mut s.replace("\n\t", "\n"), &mut s);
389	if s.get(0..1) == Some("\t") {
390		s.remove(0);
391	}
392}
393
394/// Returns an empty string if `s` is empty, otherwise `s` with braces.
395pub fn embrace(s: &str) -> String { if s.is_empty() { String::new() } else { format!("({})", s) } }