1use crc_any::CRCu16;
2use std::cmp::Ordering;
3use std::collections::btree_map::Entry;
4use std::collections::{BTreeMap, HashSet};
5use std::default::Default;
6use std::fmt::Display;
7use std::io::Write;
8use std::path::{Path, PathBuf};
9use std::str::FromStr;
10use std::sync::LazyLock;
11
12use regex::Regex;
13
14use quick_xml::{events::Event, Reader};
15
16use proc_macro2::{Ident, TokenStream};
17use quote::{format_ident, quote};
18
19#[cfg(feature = "serde")]
20use serde::{Deserialize, Serialize};
21
22use crate::error::BindGenError;
23use crate::util;
24
25static URL_REGEX: LazyLock<Regex> = LazyLock::new(|| {
26 Regex::new(concat!(
27 r"(https?://", r"([-a-zA-Z0-9@:%._\+~#=]{2,256}\.)+", r"[a-zA-Z]{2,63}", r"\b([-a-zA-Z0-9@:%_\+.~#?&/=]*[-a-zA-Z0-9@:%_\+~#?&/=])?)", ))
32 .expect("failed to build regex")
33});
34
35#[derive(Debug, PartialEq, Clone, Default)]
36#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
37pub struct MavProfile {
38 pub messages: BTreeMap<String, MavMessage>,
39 pub enums: BTreeMap<String, MavEnum>,
40 pub version: Option<u8>,
41 pub dialect: Option<u8>,
42}
43
44impl MavProfile {
45 fn add_message(&mut self, message: &MavMessage) {
46 match self.messages.entry(message.name.clone()) {
47 Entry::Occupied(entry) => {
48 assert!(
49 entry.get() == message,
50 "Message '{}' defined twice but definitions are different",
51 message.name
52 );
53 }
54 Entry::Vacant(entry) => {
55 entry.insert(message.clone());
56 }
57 }
58 }
59
60 fn add_enum(&mut self, enm: &MavEnum) {
61 match self.enums.entry(enm.name.clone()) {
62 Entry::Occupied(entry) => {
63 entry.into_mut().try_combine(enm);
64 }
65 Entry::Vacant(entry) => {
66 entry.insert(enm.clone());
67 }
68 }
69 }
70
71 fn update_enums(mut self) -> Self {
74 for msg in self.messages.values_mut() {
75 for field in &mut msg.fields {
76 if let Some(enum_name) = &field.enumtype {
77 if let Some(enm) = self.enums.get_mut(enum_name) {
79 if field.display == Some("bitmask".to_string()) {
81 enm.bitmask = true;
82 }
83
84 if enm.bitmask {
86 enm.primitive = Some(field.mavtype.rust_primitive_type());
87
88 for entry in &enm.entries {
90 assert!(
91 entry.value.unwrap_or_default() <= field.mavtype.max_int_value(),
92 "bitflag enum field {} of {} must be able to fit all possible values for {}",
93 field.name,
94 msg.name,
95 enum_name,
96 );
97 }
98
99 if field.display.is_none() {
101 field.display = Some("bitmask".to_string());
102 }
103 }
104 }
105 }
106 }
107 }
108 self
109 }
110
111 #[inline(always)]
123 fn emit_comments(&self, dialect_name: &str) -> TokenStream {
124 let message = format!("MAVLink {dialect_name} dialect.");
125 quote!(
126 #![doc = #message]
127 #![doc = ""]
128 #![doc = "This file was automatically generated, do not edit."]
129 )
130 }
131
132 #[inline(always)]
134 fn emit_msgs(&self) -> Vec<TokenStream> {
135 self.messages
136 .values()
137 .map(|d| d.emit_rust(self.version.is_some()))
138 .collect()
139 }
140
141 #[inline(always)]
143 fn emit_enums(&self) -> Vec<TokenStream> {
144 self.enums.values().map(|d| d.emit_rust()).collect()
145 }
146
147 #[inline(always)]
148 fn emit_deprecations(&self) -> Vec<TokenStream> {
149 self.messages
150 .values()
151 .map(|msg| {
152 msg.deprecated
153 .as_ref()
154 .map(|d| d.emit_tokens())
155 .unwrap_or_default()
156 })
157 .collect()
158 }
159
160 #[inline(always)]
162 fn emit_enum_names(&self) -> Vec<TokenStream> {
163 self.messages
164 .values()
165 .map(|msg| {
166 let name = format_ident!("{}", msg.name);
167 quote!(#name)
168 })
169 .collect()
170 }
171
172 #[inline(always)]
174 fn emit_struct_names(&self) -> Vec<TokenStream> {
175 self.messages
176 .values()
177 .map(|msg| msg.emit_struct_name())
178 .collect()
179 }
180
181 fn emit_rust(&self, dialect_name: &str) -> TokenStream {
182 let id_width = format_ident!("u32");
184
185 let comment = self.emit_comments(dialect_name);
186 let mav_minor_version = self.emit_minor_version();
187 let mav_dialect_number = self.emit_dialect_number();
188 let msgs = self.emit_msgs();
189 let deprecations = self.emit_deprecations();
190 let enum_names = self.emit_enum_names();
191 let struct_names = self.emit_struct_names();
192 let enums = self.emit_enums();
193
194 let variant_docs = self.emit_variant_description();
195
196 let mav_message =
197 self.emit_mav_message(&variant_docs, &deprecations, &enum_names, &struct_names);
198 let mav_message_all_ids = self.emit_mav_message_all_ids();
199 let mav_message_all_messages = self.emit_mav_message_all_messages();
200 let mav_message_parse = self.emit_mav_message_parse(&enum_names, &struct_names);
201 let mav_message_crc = self.emit_mav_message_crc(&id_width, &struct_names);
202 let mav_message_name = self.emit_mav_message_name(&enum_names, &struct_names);
203 let mav_message_id = self.emit_mav_message_id(&enum_names, &struct_names);
204 let mav_message_id_from_name = self.emit_mav_message_id_from_name(&struct_names);
205 let mav_message_default_from_id =
206 self.emit_mav_message_default_from_id(&enum_names, &struct_names);
207 let mav_message_random_from_id =
208 self.emit_mav_message_random_from_id(&enum_names, &struct_names);
209 let mav_message_serialize = self.emit_mav_message_serialize(&enum_names);
210 let mav_message_target_system_id = self.emit_mav_message_target_system_id();
211 let mav_message_target_component_id = self.emit_mav_message_target_component_id();
212
213 quote! {
214 #comment
215 #![allow(deprecated)]
216 #![allow(clippy::match_single_binding)]
217 #[allow(unused_imports)]
218 use num_derive::{FromPrimitive, ToPrimitive};
219 #[allow(unused_imports)]
220 use num_traits::{FromPrimitive, ToPrimitive};
221 #[allow(unused_imports)]
222 use bitflags::{bitflags, Flags};
223 #[allow(unused_imports)]
224 use mavlink_core::{MavlinkVersion, Message, MessageData, bytes::Bytes, bytes_mut::BytesMut, types::CharArray};
225
226 #[cfg(feature = "serde")]
227 use serde::{Serialize, Deserialize};
228
229 #[cfg(feature = "arbitrary")]
230 use arbitrary::Arbitrary;
231
232 #[cfg(feature = "ts-rs")]
233 use ts_rs::TS;
234
235 #mav_minor_version
236 #mav_dialect_number
237
238 #(#enums)*
239
240 #(#msgs)*
241
242 #[derive(Clone, PartialEq, Debug)]
243 #mav_message
244
245 impl MavMessage {
246 #mav_message_all_ids
247 #mav_message_all_messages
248 }
249
250 impl Message for MavMessage {
251 #mav_message_parse
252 #mav_message_name
253 #mav_message_id
254 #mav_message_id_from_name
255 #mav_message_default_from_id
256 #mav_message_random_from_id
257 #mav_message_serialize
258 #mav_message_crc
259 #mav_message_target_system_id
260 #mav_message_target_component_id
261 }
262 }
263 }
264
265 #[inline(always)]
266 fn emit_mav_message(
267 &self,
268 docs: &[TokenStream],
269 deprecations: &[TokenStream],
270 enums: &[TokenStream],
271 structs: &[TokenStream],
272 ) -> TokenStream {
273 quote! {
274 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
275 #[cfg_attr(feature = "serde", serde(tag = "type"))]
276 #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
277 #[cfg_attr(feature = "ts-rs", derive(TS))]
278 #[cfg_attr(feature = "ts-rs", ts(export))]
279 #[repr(u32)]
280 pub enum MavMessage {
281 #(#docs #deprecations #enums(#structs),)*
282 }
283 }
284 }
285
286 fn emit_variant_description(&self) -> Vec<TokenStream> {
287 self.messages
288 .values()
289 .map(|msg| {
290 let mut ts = TokenStream::new();
291
292 if let Some(doc) = msg.description.as_ref() {
293 let doc = format!("{doc}{}", if doc.ends_with('.') { "" } else { "." });
294 let doc = URL_REGEX.replace_all(&doc, "<$1>");
295 ts.extend(quote!(#[doc = #doc]));
296
297 ts.extend(quote!(#[doc = ""]));
299 }
300
301 let id = format!("ID: {}", msg.id);
302 ts.extend(quote!(#[doc = #id]));
303
304 ts
305 })
306 .collect()
307 }
308
309 #[inline(always)]
310 fn emit_mav_message_all_ids(&self) -> TokenStream {
311 let mut message_ids = self.messages.values().map(|m| m.id).collect::<Vec<u32>>();
312 message_ids.sort();
313
314 quote!(
315 pub const fn all_ids() -> &'static [u32] {
316 &[#(#message_ids),*]
317 }
318 )
319 }
320
321 #[inline(always)]
322 fn emit_minor_version(&self) -> TokenStream {
323 if let Some(version) = self.version {
324 quote! (pub const MINOR_MAVLINK_VERSION: u8 = #version;)
325 } else {
326 TokenStream::default()
327 }
328 }
329
330 #[inline(always)]
331 fn emit_dialect_number(&self) -> TokenStream {
332 if let Some(dialect) = self.dialect {
333 quote! (pub const DIALECT_NUMBER: u8 = #dialect;)
334 } else {
335 TokenStream::default()
336 }
337 }
338
339 #[inline(always)]
340 fn emit_mav_message_parse(
341 &self,
342 enums: &[TokenStream],
343 structs: &[TokenStream],
344 ) -> TokenStream {
345 let id_width = format_ident!("u32");
346
347 quote! {
348 fn parse(version: MavlinkVersion, id: #id_width, payload: &[u8]) -> Result<Self, ::mavlink_core::error::ParserError> {
349 match id {
350 #(#structs::ID => #structs::deser(version, payload).map(Self::#enums),)*
351 _ => {
352 Err(::mavlink_core::error::ParserError::UnknownMessage { id })
353 },
354 }
355 }
356 }
357 }
358
359 #[inline(always)]
360 fn emit_mav_message_crc(&self, id_width: &Ident, structs: &[TokenStream]) -> TokenStream {
361 quote! {
362 fn extra_crc(id: #id_width) -> u8 {
363 match id {
364 #(#structs::ID => #structs::EXTRA_CRC,)*
365 _ => {
366 0
367 },
368 }
369 }
370 }
371 }
372
373 #[inline(always)]
374 fn emit_mav_message_name(&self, enums: &[TokenStream], structs: &[TokenStream]) -> TokenStream {
375 quote! {
376 fn message_name(&self) -> &'static str {
377 match self {
378 #(Self::#enums(..) => #structs::NAME,)*
379 }
380 }
381 }
382 }
383
384 #[inline(always)]
385 fn emit_mav_message_id(&self, enums: &[TokenStream], structs: &[TokenStream]) -> TokenStream {
386 let id_width = format_ident!("u32");
387 quote! {
388 fn message_id(&self) -> #id_width {
389 match self {
390 #(Self::#enums(..) => #structs::ID,)*
391 }
392 }
393 }
394 }
395
396 #[inline(always)]
397 fn emit_mav_message_id_from_name(&self, structs: &[TokenStream]) -> TokenStream {
398 quote! {
399 fn message_id_from_name(name: &str) -> Option<u32> {
400 match name {
401 #(#structs::NAME => Some(#structs::ID),)*
402 _ => {
403 None
404 }
405 }
406 }
407 }
408 }
409
410 #[inline(always)]
411 fn emit_mav_message_default_from_id(
412 &self,
413 enums: &[TokenStream],
414 structs: &[TokenStream],
415 ) -> TokenStream {
416 quote! {
417 fn default_message_from_id(id: u32) -> Option<Self> {
418 match id {
419 #(#structs::ID => Some(Self::#enums(#structs::default())),)*
420 _ => {
421 None
422 }
423 }
424 }
425 }
426 }
427
428 #[inline(always)]
429 fn emit_mav_message_random_from_id(
430 &self,
431 enums: &[TokenStream],
432 structs: &[TokenStream],
433 ) -> TokenStream {
434 quote! {
435 #[cfg(feature = "arbitrary")]
436 fn random_message_from_id<R: rand::RngCore>(id: u32, rng: &mut R) -> Option<Self> {
437 match id {
438 #(#structs::ID => Some(Self::#enums(#structs::random(rng))),)*
439 _ => None,
440 }
441 }
442 }
443 }
444
445 #[inline(always)]
446 fn emit_mav_message_serialize(&self, enums: &Vec<TokenStream>) -> TokenStream {
447 quote! {
448 fn ser(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize {
449 match self {
450 #(Self::#enums(body) => body.ser(version, bytes),)*
451 }
452 }
453 }
454 }
455
456 #[inline(always)]
457 fn emit_mav_message_target_system_id(&self) -> TokenStream {
458 let arms: Vec<TokenStream> = self
459 .messages
460 .values()
461 .filter(|msg| msg.fields.iter().any(|f| f.name == "target_system"))
462 .map(|msg| {
463 let variant = format_ident!("{}", msg.name);
464 quote!(Self::#variant(inner) => Some(inner.target_system),)
465 })
466 .collect();
467
468 quote! {
469 fn target_system_id(&self) -> Option<u8> {
470 match self {
471 #(#arms)*
472 _ => None,
473 }
474 }
475 }
476 }
477
478 #[inline(always)]
479 fn emit_mav_message_target_component_id(&self) -> TokenStream {
480 let arms: Vec<TokenStream> = self
481 .messages
482 .values()
483 .filter(|msg| msg.fields.iter().any(|f| f.name == "target_component"))
484 .map(|msg| {
485 let variant = format_ident!("{}", msg.name);
486 quote!(Self::#variant(inner) => Some(inner.target_component),)
487 })
488 .collect();
489
490 quote! {
491 fn target_component_id(&self) -> Option<u8> {
492 match self {
493 #(#arms)*
494 _ => None,
495 }
496 }
497 }
498 }
499
500 #[inline(always)]
501 fn emit_mav_message_all_messages(&self) -> TokenStream {
502 let mut entries = self
503 .messages
504 .values()
505 .map(|msg| (msg.id, msg.emit_struct_name()))
506 .collect::<Vec<_>>();
507
508 entries.sort_by_key(|(id, _)| *id);
509
510 let pairs = entries
511 .into_iter()
512 .map(|(_, struct_name)| quote!((#struct_name::NAME, #struct_name::ID)))
513 .collect::<Vec<_>>();
514
515 quote! {
516 pub const fn all_messages() -> &'static [(&'static str, u32)] {
517 &[#(#pairs),*]
518 }
519 }
520 }
521}
522
523#[derive(Debug, PartialEq, Clone, Default)]
524#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
525pub struct MavEnum {
526 pub name: String,
527 pub description: Option<String>,
528 pub entries: Vec<MavEnumEntry>,
529 pub primitive: Option<String>,
533 pub bitmask: bool,
534 pub deprecated: Option<MavDeprecation>,
535}
536
537impl MavEnum {
538 fn is_generated_as_bitflags(&self) -> bool {
540 self.primitive.is_some()
541 }
542
543 fn try_combine(&mut self, enm: &Self) {
544 if self.name == enm.name {
545 for enum_entry in &enm.entries {
546 let found_entry = self.entries.iter().find(|elem| {
547 elem.name == enum_entry.name && elem.value.unwrap() == enum_entry.value.unwrap()
548 });
549 match found_entry {
550 Some(entry) => panic!("Enum entry {} already exists", entry.name),
551 None => self.entries.push(enum_entry.clone()),
552 }
553 }
554 }
555 }
556
557 fn emit_defs(&self) -> Vec<TokenStream> {
558 let mut cnt = 0u64;
559 self.entries
560 .iter()
561 .map(|enum_entry| {
562 let name = format_ident!("{}", enum_entry.name.clone());
563 let value;
564
565 let deprecation = enum_entry.emit_deprecation();
566
567 let description = if let Some(description) = enum_entry.description.as_ref() {
568 let description = URL_REGEX.replace_all(description, "<$1>");
569 quote!(#[doc = #description])
570 } else {
571 quote!()
572 };
573
574 let params_doc = enum_entry.emit_params();
575
576 if let Some(tmp_value) = enum_entry.value {
577 cnt = cnt.max(tmp_value);
578 let tmp = TokenStream::from_str(&tmp_value.to_string()).unwrap();
579 value = quote!(#tmp);
580 } else {
581 cnt += 1;
582 value = quote!(#cnt);
583 }
584
585 if self.is_generated_as_bitflags() {
586 quote! {
587 #deprecation
588 #description
589 #params_doc
590 const #name = #value;
591 }
592 } else {
593 quote! {
594 #deprecation
595 #description
596 #params_doc
597 #name = #value,
598 }
599 }
600 })
601 .collect()
602 }
603
604 #[inline(always)]
605 fn emit_name(&self) -> TokenStream {
606 let name = format_ident!("{}", self.name);
607 quote!(#name)
608 }
609
610 #[inline(always)]
611 fn emit_const_default(&self) -> TokenStream {
612 let default = format_ident!("{}", self.entries[0].name);
613 quote!(pub const DEFAULT: Self = Self::#default;)
614 }
615
616 #[inline(always)]
617 fn emit_deprecation(&self) -> TokenStream {
618 self.deprecated
619 .as_ref()
620 .map(|d| d.emit_tokens())
621 .unwrap_or_default()
622 }
623
624 fn emit_rust(&self) -> TokenStream {
625 let defs = self.emit_defs();
626 let enum_name = self.emit_name();
627 let const_default = self.emit_const_default();
628
629 let deprecated = self.emit_deprecation();
630
631 let description = if let Some(description) = self.description.as_ref() {
632 let desc = URL_REGEX.replace_all(description, "<$1>");
633 quote!(#[doc = #desc])
634 } else {
635 quote!()
636 };
637
638 let mav_bool_impl = if self.name == "MavBool"
639 && self
640 .entries
641 .iter()
642 .any(|entry| entry.name == "MAV_BOOL_TRUE")
643 {
644 if self.is_generated_as_bitflags() {
645 quote!(
646 pub fn as_bool(&self) -> bool {
647 self.contains(Self::MAV_BOOL_TRUE)
648 }
649 )
650 } else {
651 quote!(
652 pub fn as_bool(&self) -> bool {
653 *self == Self::MAV_BOOL_TRUE
654 }
655 )
656 }
657 } else {
658 quote!()
659 };
660
661 let enum_def;
662 if let Some(primitive) = self.primitive.clone() {
663 let primitive = format_ident!("{}", primitive);
664 enum_def = quote! {
665 bitflags!{
666 #[cfg_attr(feature = "ts-rs", derive(TS))]
667 #[cfg_attr(feature = "ts-rs", ts(export, type = "number"))]
668 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
669 #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
670 #[derive(Debug, Copy, Clone, PartialEq)]
671 #deprecated
672 #description
673 pub struct #enum_name: #primitive {
674 #(#defs)*
675 }
676 }
677 };
678 } else {
679 enum_def = quote! {
680 #[cfg_attr(feature = "ts-rs", derive(TS))]
681 #[cfg_attr(feature = "ts-rs", ts(export))]
682 #[derive(Debug, Copy, Clone, PartialEq, FromPrimitive, ToPrimitive)]
683 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
684 #[cfg_attr(feature = "serde", serde(tag = "type"))]
685 #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
686 #[repr(u32)]
687 #deprecated
688 #description
689 pub enum #enum_name {
690 #(#defs)*
691 }
692 };
693 }
694
695 quote! {
696 #enum_def
697
698 impl #enum_name {
699 #const_default
700 #mav_bool_impl
701 }
702
703 impl Default for #enum_name {
704 fn default() -> Self {
705 Self::DEFAULT
706 }
707 }
708 }
709 }
710}
711
712#[derive(Debug, PartialEq, Clone, Default)]
713#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
714pub struct MavEnumEntry {
715 pub value: Option<u64>,
716 pub name: String,
717 pub description: Option<String>,
718 pub params: Option<Vec<MavParam>>,
719 pub deprecated: Option<MavDeprecation>,
720}
721
722impl MavEnumEntry {
723 #[inline(always)]
724 fn emit_deprecation(&self) -> TokenStream {
725 self.deprecated
726 .as_ref()
727 .map(|d| d.emit_tokens())
728 .unwrap_or_default()
729 }
730
731 #[inline(always)]
732 fn emit_params(&self) -> TokenStream {
733 let Some(params) = &self.params else {
734 return quote!();
735 };
736 let any_value_range = params.iter().any(|p| {
737 p.min_value.is_some()
738 || p.max_value.is_some()
739 || p.increment.is_some()
740 || p.enum_used.is_some()
741 || (p.reserved && p.default.is_some())
742 });
743 let any_units = params.iter().any(|p| p.units.is_some());
744 let lines = params
745 .iter()
746 .map(|param| param.emit_doc_row(any_value_range, any_units));
747 let mut table_header = "| Parameter | Description |".to_string();
748 let mut table_hl = "| --------- | ----------- |".to_string();
749 if any_value_range {
750 table_header += " Values |";
751 table_hl += " ------ |";
752 }
753 if any_units {
754 table_header += " Units |";
755 table_hl += " ----- |";
756 }
757 quote! {
758 #[doc = ""]
759 #[doc = "# Parameters"]
760 #[doc = ""]
761 #[doc = #table_header]
762 #[doc = #table_hl]
763 #(#lines)*
764 }
765 }
766}
767
768#[derive(Debug, PartialEq, Clone, Default)]
769#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
770pub struct MavParam {
771 pub index: usize,
772 pub description: Option<String>,
773 pub label: Option<String>,
774 pub units: Option<String>,
775 pub enum_used: Option<String>,
776 pub increment: Option<f32>,
777 pub min_value: Option<f32>,
778 pub max_value: Option<f32>,
779 pub reserved: bool,
780 pub default: Option<f32>,
781}
782
783fn format_number_range(min: Option<f32>, max: Option<f32>, inc: Option<f32>) -> String {
784 match (min, max, inc) {
785 (Some(min), Some(max), Some(inc)) => {
786 if min + inc == max {
787 format!("{min}, {max}")
788 } else if min + 2. * inc == max {
789 format!("{}, {}, {}", min, min + inc, max)
790 } else {
791 format!("{}, {}, .. , {}", min, min + inc, max)
792 }
793 }
794 (Some(min), Some(max), None) => format!("{min} .. {max}"),
795 (Some(min), None, Some(inc)) => format!("{}, {}, ..", min, min + inc),
796 (None, Some(max), Some(inc)) => format!(".., {}, {}", max - inc, max),
797 (Some(min), None, None) => format!("≥ {min}"),
798 (None, Some(max), None) => format!("≤ {max}"),
799 (None, None, Some(inc)) => format!("Multiples of {inc}"),
800 (None, None, None) => String::new(),
801 }
802}
803
804impl MavParam {
805 fn format_valid_values(&self) -> String {
806 if let (true, Some(default)) = (self.reserved, self.default) {
807 format!("Reserved (use {default})")
808 } else if let Some(enum_used) = &self.enum_used {
809 format!("[`{enum_used}`]")
810 } else {
811 format_number_range(self.min_value, self.max_value, self.increment)
812 }
813 }
814
815 fn emit_doc_row(&self, value_range_col: bool, units_col: bool) -> TokenStream {
816 let label = if let Some(label) = &self.label {
817 format!("{} ({})", self.index, label)
818 } else {
819 format!("{}", self.index)
820 };
821 let mut line = format!(
822 "| {label:10}| {:12}|",
823 self.description.as_deref().unwrap_or_default()
824 );
825 if value_range_col {
826 let range = self.format_valid_values();
827 line += &format!(" {range} |");
828 }
829 if units_col {
830 let units = self.units.clone().unwrap_or_default();
831 line += &format!(" {units} |");
832 }
833 quote! {#[doc = #line]}
834 }
835}
836
837#[derive(Debug, PartialEq, Clone, Default)]
838#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
839pub struct MavMessage {
840 pub id: u32,
841 pub name: String,
842 pub description: Option<String>,
843 pub fields: Vec<MavField>,
844 pub deprecated: Option<MavDeprecation>,
845}
846
847impl MavMessage {
848 fn emit_struct_name(&self) -> TokenStream {
851 let name = format_ident!("{}", format!("{}_DATA", self.name));
852 quote!(#name)
853 }
854
855 #[inline(always)]
856 fn emit_name_types(&self) -> (Vec<TokenStream>, usize) {
857 let mut encoded_payload_len: usize = 0;
858 let field_toks = self
859 .fields
860 .iter()
861 .map(|field| {
862 let nametype = field.emit_name_type();
863 encoded_payload_len += field.mavtype.len();
864
865 let description = field.emit_description();
866
867 let serde_default = if field.is_extension {
871 if field.enumtype.is_some() {
872 quote!(#[cfg_attr(feature = "serde", serde(default))])
873 } else {
874 quote!(#[cfg_attr(feature = "serde", serde(default = "crate::utils::RustDefault::rust_default"))])
875 }
876 } else {
877 quote!()
878 };
879
880 let serde_with_attr = if matches!(field.mavtype, MavType::Array(_, _)) {
881 quote!(
882 #[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
883 #[cfg_attr(feature = "ts-rs", ts(type = "Array<number>"))]
884 )
885 } else if matches!(field.mavtype, MavType::CharArray(_)) {
886 quote!(
887 #[cfg_attr(feature = "ts-rs", ts(type = "string"))]
888 )
889 } else {
890 quote!()
891 };
892
893 quote! {
894 #description
895 #serde_default
896 #serde_with_attr
897 #nametype
898 }
899 })
900 .collect::<Vec<TokenStream>>();
901 (field_toks, encoded_payload_len)
902 }
903
904 #[inline(always)]
906 fn emit_description(&self) -> TokenStream {
907 let mut ts = TokenStream::new();
908 if let Some(doc) = self.description.as_ref() {
909 let doc = format!("{doc}{}", if doc.ends_with('.') { "" } else { "." });
910 let doc = URL_REGEX.replace_all(&doc, "<$1>");
912 ts.extend(quote!(#[doc = #doc]));
913 ts.extend(quote!(#[doc = ""]));
915 }
916 let id = format!("ID: {}", self.id);
917 ts.extend(quote!(#[doc = #id]));
918 ts
919 }
920
921 #[inline(always)]
922 fn emit_serialize_vars(&self) -> TokenStream {
923 let (base_fields, ext_fields): (Vec<_>, Vec<_>) =
924 self.fields.iter().partition(|f| !f.is_extension);
925 let ser_vars = base_fields.iter().map(|f| f.rust_writer());
926 let ser_ext_vars = ext_fields.iter().map(|f| f.rust_writer());
927 quote! {
928 let mut __tmp = BytesMut::new(bytes);
929
930 #[allow(clippy::absurd_extreme_comparisons)]
938 #[allow(unused_comparisons)]
939 if __tmp.remaining() < Self::ENCODED_LEN {
940 panic!(
941 "buffer is too small (need {} bytes, but got {})",
942 Self::ENCODED_LEN,
943 __tmp.remaining(),
944 )
945 }
946
947 #(#ser_vars)*
948 if matches!(version, MavlinkVersion::V2) {
949 #(#ser_ext_vars)*
950 let len = __tmp.len();
951 ::mavlink_core::utils::remove_trailing_zeroes(&bytes[..len])
952 } else {
953 __tmp.len()
954 }
955 }
956 }
957
958 #[inline(always)]
959 fn emit_deserialize_vars(&self) -> TokenStream {
960 let deser_vars = self
961 .fields
962 .iter()
963 .map(|f| f.rust_reader())
964 .collect::<Vec<TokenStream>>();
965
966 if deser_vars.is_empty() {
967 quote! {
969 Ok(Self::default())
970 }
971 } else {
972 quote! {
973 let avail_len = __input.len();
974
975 let mut payload_buf = [0; Self::ENCODED_LEN];
976 let mut buf = if avail_len < Self::ENCODED_LEN {
977 payload_buf[0..avail_len].copy_from_slice(__input);
979 Bytes::new(&payload_buf)
980 } else {
981 Bytes::new(__input)
983 };
984
985 let mut __struct = Self::default();
986 #(#deser_vars)*
987 Ok(__struct)
988 }
989 }
990 }
991
992 #[inline(always)]
993 fn emit_default_impl(&self) -> TokenStream {
994 let msg_name = self.emit_struct_name();
995 quote! {
996 impl Default for #msg_name {
997 fn default() -> Self {
998 Self::DEFAULT.clone()
999 }
1000 }
1001 }
1002 }
1003
1004 #[inline(always)]
1005 fn emit_deprecation(&self) -> TokenStream {
1006 self.deprecated
1007 .as_ref()
1008 .map(|d| d.emit_tokens())
1009 .unwrap_or_default()
1010 }
1011
1012 #[inline(always)]
1013 fn emit_const_default(&self, dialect_has_version: bool) -> TokenStream {
1014 let initializers = self
1015 .fields
1016 .iter()
1017 .map(|field| field.emit_default_initializer(dialect_has_version));
1018 quote!(pub const DEFAULT: Self = Self { #(#initializers)* };)
1019 }
1020
1021 fn emit_rust(&self, dialect_has_version: bool) -> TokenStream {
1022 let msg_name = self.emit_struct_name();
1023 let id = self.id;
1024 let name = self.name.clone();
1025 let extra_crc = extra_crc(self);
1026 let (name_types, payload_encoded_len) = self.emit_name_types();
1027 assert!(
1028 payload_encoded_len <= 255,
1029 "maximum payload length is 255 bytes"
1030 );
1031
1032 let deser_vars = self.emit_deserialize_vars();
1033 let serialize_vars = self.emit_serialize_vars();
1034 let const_default = self.emit_const_default(dialect_has_version);
1035 let default_impl = self.emit_default_impl();
1036
1037 let deprecation = self.emit_deprecation();
1038
1039 let description = self.emit_description();
1040
1041 quote! {
1042 #deprecation
1043 #description
1044 #[derive(Debug, Clone, PartialEq)]
1045 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1046 #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
1047 #[cfg_attr(feature = "ts-rs", derive(TS))]
1048 #[cfg_attr(feature = "ts-rs", ts(export))]
1049 pub struct #msg_name {
1050 #(#name_types)*
1051 }
1052
1053 impl #msg_name {
1054 pub const ENCODED_LEN: usize = #payload_encoded_len;
1055 #const_default
1056
1057 #[cfg(feature = "arbitrary")]
1058 pub fn random<R: rand::RngCore>(rng: &mut R) -> Self {
1059 use arbitrary::{Unstructured, Arbitrary};
1060 let mut buf = [0u8; 1024];
1061 rng.fill_bytes(&mut buf);
1062 let mut unstructured = Unstructured::new(&buf);
1063 Self::arbitrary(&mut unstructured).unwrap_or_default()
1064 }
1065 }
1066
1067 #default_impl
1068
1069 impl MessageData for #msg_name {
1070 type Message = MavMessage;
1071
1072 const ID: u32 = #id;
1073 const NAME: &'static str = #name;
1074 const EXTRA_CRC: u8 = #extra_crc;
1075 const ENCODED_LEN: usize = #payload_encoded_len;
1076
1077 fn deser(_version: MavlinkVersion, __input: &[u8]) -> Result<Self, ::mavlink_core::error::ParserError> {
1078 #deser_vars
1079 }
1080
1081 fn ser(&self, version: MavlinkVersion, bytes: &mut [u8]) -> usize {
1082 #serialize_vars
1083 }
1084 }
1085 }
1086 }
1087
1088 fn validate_unique_fields(&self) {
1092 let mut seen: HashSet<&str> = HashSet::new();
1093 for f in &self.fields {
1094 let name: &str = &f.name;
1095 assert!(
1096 seen.insert(name),
1097 "Duplicate field '{}' found in message '{}' while generating bindings",
1098 name,
1099 self.name
1100 );
1101 }
1102 }
1103
1104 fn validate_field_count(&self) {
1106 assert!(
1107 !self.fields.is_empty(),
1108 "Message '{}' does not any fields",
1109 self.name
1110 );
1111 assert!(
1112 self.fields.len() <= 64,
1113 "Message '{}' has more then 64 fields",
1114 self.name
1115 );
1116 }
1117}
1118
1119#[derive(Debug, PartialEq, Clone, Default)]
1120#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1121pub struct MavField {
1122 pub mavtype: MavType,
1123 pub name: String,
1124 pub description: Option<String>,
1125 pub enumtype: Option<String>,
1126 pub display: Option<String>,
1127 pub is_extension: bool,
1128}
1129
1130impl MavField {
1131 #[inline(always)]
1133 fn emit_name(&self) -> TokenStream {
1134 let name = format_ident!("{}", self.name);
1135 quote!(#name)
1136 }
1137
1138 #[inline(always)]
1140 fn emit_type(&self) -> TokenStream {
1141 let mavtype;
1142 if matches!(self.mavtype, MavType::Array(_, _)) {
1143 let rt = TokenStream::from_str(&self.mavtype.rust_type()).unwrap();
1144 mavtype = quote!(#rt);
1145 } else if let Some(enumname) = &self.enumtype {
1146 let en = TokenStream::from_str(enumname).unwrap();
1147 mavtype = quote!(#en);
1148 } else {
1149 let rt = TokenStream::from_str(&self.mavtype.rust_type()).unwrap();
1150 mavtype = quote!(#rt);
1151 }
1152 mavtype
1153 }
1154
1155 #[inline(always)]
1157 fn emit_description(&self) -> TokenStream {
1158 let mut ts = TokenStream::new();
1159 if let Some(val) = self.description.as_ref() {
1160 let desc = URL_REGEX.replace_all(val, "<$1>");
1161 ts.extend(quote!(#[doc = #desc]));
1162 }
1163 ts
1164 }
1165
1166 #[inline(always)]
1168 fn emit_name_type(&self) -> TokenStream {
1169 let name = self.emit_name();
1170 let fieldtype = self.emit_type();
1171 quote!(pub #name: #fieldtype,)
1172 }
1173
1174 fn rust_writer(&self) -> TokenStream {
1176 let mut name = "self.".to_string() + &self.name.clone();
1177 if self.enumtype.is_some() {
1178 if !matches!(self.mavtype, MavType::Array(_, _)) {
1181 if let Some(dsp) = &self.display {
1182 if dsp == "bitmask" {
1184 name += ".bits() as ";
1186 name += &self.mavtype.rust_type();
1187 } else {
1188 panic!("Display option not implemented");
1189 }
1190 } else {
1191 name += " as ";
1193 name += &self.mavtype.rust_type();
1194 }
1195 }
1196 }
1197 let ts = TokenStream::from_str(&name).unwrap();
1198 let name = quote!(#ts);
1199 let buf = format_ident!("__tmp");
1200 self.mavtype.rust_writer(&name, buf)
1201 }
1202
1203 fn rust_reader(&self) -> TokenStream {
1205 let _name = TokenStream::from_str(&self.name).unwrap();
1206
1207 let name = quote!(__struct.#_name);
1208 let buf = format_ident!("buf");
1209 if let Some(enum_name) = &self.enumtype {
1210 if let MavType::Array(_t, _size) = &self.mavtype {
1213 return self.mavtype.rust_reader(&name, buf);
1214 }
1215 if let Some(dsp) = &self.display {
1216 if dsp == "bitmask" {
1217 let tmp = self.mavtype.rust_reader("e!(let tmp), buf);
1219 let enum_name_ident = format_ident!("{}", enum_name);
1220 quote! {
1221 #tmp
1222 #name = #enum_name_ident::from_bits(tmp as <#enum_name_ident as Flags>::Bits)
1223 .ok_or(::mavlink_core::error::ParserError::InvalidFlag { flag_type: #enum_name, value: tmp as u64 })?;
1224 }
1225 } else {
1226 panic!("Display option not implemented");
1227 }
1228 } else {
1229 let tmp = self.mavtype.rust_reader("e!(let tmp), buf);
1231 let val = format_ident!("from_{}", &self.mavtype.rust_type());
1232 quote!(
1233 #tmp
1234 #name = FromPrimitive::#val(tmp)
1235 .ok_or(::mavlink_core::error::ParserError::InvalidEnum { enum_type: #enum_name, value: tmp as u64 })?;
1236 )
1237 }
1238 } else {
1239 self.mavtype.rust_reader(&name, buf)
1240 }
1241 }
1242
1243 #[inline(always)]
1244 fn emit_default_initializer(&self, dialect_has_version: bool) -> TokenStream {
1245 let field = self.emit_name();
1246 if matches!(self.mavtype, MavType::Array(_, _)) {
1248 let default_value = self.mavtype.emit_default_value(dialect_has_version);
1249 quote!(#field: #default_value,)
1250 } else if let Some(enumname) = &self.enumtype {
1251 let ty = TokenStream::from_str(enumname).unwrap();
1252 quote!(#field: #ty::DEFAULT,)
1253 } else {
1254 let default_value = self.mavtype.emit_default_value(dialect_has_version);
1255 quote!(#field: #default_value,)
1256 }
1257 }
1258}
1259
1260#[derive(Debug, PartialEq, Clone, Default)]
1261#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1262pub enum MavType {
1263 UInt8MavlinkVersion,
1264 #[default]
1265 UInt8,
1266 UInt16,
1267 UInt32,
1268 UInt64,
1269 Int8,
1270 Int16,
1271 Int32,
1272 Int64,
1273 Char,
1274 Float,
1275 Double,
1276 CharArray(usize),
1277 Array(Box<Self>, usize),
1278}
1279
1280impl MavType {
1281 fn parse_type(s: &str) -> Option<Self> {
1282 use self::MavType::*;
1283 match s {
1284 "uint8_t_mavlink_version" => Some(UInt8MavlinkVersion),
1285 "uint8_t" => Some(UInt8),
1286 "uint16_t" => Some(UInt16),
1287 "uint32_t" => Some(UInt32),
1288 "uint64_t" => Some(UInt64),
1289 "int8_t" => Some(Int8),
1290 "int16_t" => Some(Int16),
1291 "int32_t" => Some(Int32),
1292 "int64_t" => Some(Int64),
1293 "char" => Some(Char),
1294 "float" => Some(Float),
1295 "Double" => Some(Double),
1296 "double" => Some(Double),
1297 _ if s.starts_with("char[") => {
1298 let start = 4;
1299 let size = s[start + 1..(s.len() - 1)].parse::<usize>().ok()?;
1300 Some(CharArray(size))
1301 }
1302 _ if s.ends_with(']') => {
1303 let start = s.find('[')?;
1304 let size = s[start + 1..(s.len() - 1)].parse::<usize>().ok()?;
1305 let mtype = Self::parse_type(&s[0..start])?;
1306 Some(Array(Box::new(mtype), size))
1307 }
1308 _ => None,
1309 }
1310 }
1311
1312 pub fn rust_reader(&self, val: &TokenStream, buf: Ident) -> TokenStream {
1314 use self::MavType::*;
1315 match self {
1316 Char => quote! {#val = #buf.get_u8()?;},
1317 UInt8 => quote! {#val = #buf.get_u8()?;},
1318 UInt16 => quote! {#val = #buf.get_u16_le()?;},
1319 UInt32 => quote! {#val = #buf.get_u32_le()?;},
1320 UInt64 => quote! {#val = #buf.get_u64_le()?;},
1321 UInt8MavlinkVersion => quote! {#val = #buf.get_u8()?;},
1322 Int8 => quote! {#val = #buf.get_i8()?;},
1323 Int16 => quote! {#val = #buf.get_i16_le()?;},
1324 Int32 => quote! {#val = #buf.get_i32_le()?;},
1325 Int64 => quote! {#val = #buf.get_i64_le()?;},
1326 Float => quote! {#val = #buf.get_f32_le()?;},
1327 Double => quote! {#val = #buf.get_f64_le()?;},
1328 CharArray(size) => {
1329 quote! {
1330 let mut tmp = [0_u8; #size];
1331 for v in &mut tmp {
1332 *v = #buf.get_u8()?;
1333 }
1334 #val = CharArray::new(tmp);
1335 }
1336 }
1337 Array(t, _) => {
1338 let r = t.rust_reader("e!(let val), buf);
1339 quote! {
1340 for v in &mut #val {
1341 #r
1342 *v = val;
1343 }
1344 }
1345 }
1346 }
1347 }
1348
1349 pub fn rust_writer(&self, val: &TokenStream, buf: Ident) -> TokenStream {
1351 use self::MavType::*;
1352 match self {
1353 UInt8MavlinkVersion => quote! {#buf.put_u8(#val);},
1354 UInt8 => quote! {#buf.put_u8(#val);},
1355 Char => quote! {#buf.put_u8(#val);},
1356 UInt16 => quote! {#buf.put_u16_le(#val);},
1357 UInt32 => quote! {#buf.put_u32_le(#val);},
1358 Int8 => quote! {#buf.put_i8(#val);},
1359 Int16 => quote! {#buf.put_i16_le(#val);},
1360 Int32 => quote! {#buf.put_i32_le(#val);},
1361 Float => quote! {#buf.put_f32_le(#val);},
1362 UInt64 => quote! {#buf.put_u64_le(#val);},
1363 Int64 => quote! {#buf.put_i64_le(#val);},
1364 Double => quote! {#buf.put_f64_le(#val);},
1365 CharArray(_) => {
1366 let w = Char.rust_writer("e!(*val), buf);
1367 quote! {
1368 for val in &#val {
1369 #w
1370 }
1371 }
1372 }
1373 Array(t, _size) => {
1374 let w = t.rust_writer("e!(*val), buf);
1375 quote! {
1376 for val in &#val {
1377 #w
1378 }
1379 }
1380 }
1381 }
1382 }
1383
1384 fn len(&self) -> usize {
1386 use self::MavType::*;
1387 match self {
1388 UInt8MavlinkVersion | UInt8 | Int8 | Char => 1,
1389 UInt16 | Int16 => 2,
1390 UInt32 | Int32 | Float => 4,
1391 UInt64 | Int64 | Double => 8,
1392 CharArray(size) => *size,
1393 Array(t, size) => t.len() * size,
1394 }
1395 }
1396
1397 fn max_int_value(&self) -> u64 {
1398 match self {
1399 Self::UInt8MavlinkVersion | Self::UInt8 => u8::MAX as u64,
1400 Self::UInt16 => u16::MAX as u64,
1401 Self::UInt32 => u32::MAX as u64,
1402 Self::UInt64 => u64::MAX,
1403 Self::Int8 | Self::Char | Self::CharArray(_) => i8::MAX as u64,
1404 Self::Int16 => i16::MAX as u64,
1405 Self::Int32 => i32::MAX as u64,
1406 Self::Int64 => i64::MAX as u64,
1407 Self::Float => (1 << f32::MANTISSA_DIGITS) - 1,
1409 Self::Double => (1 << f64::MANTISSA_DIGITS) - 1,
1410 Self::Array(mav_type, _) => mav_type.max_int_value(),
1411 }
1412 }
1413
1414 fn order_len(&self) -> usize {
1416 use self::MavType::*;
1417 match self {
1418 UInt8MavlinkVersion | UInt8 | Int8 | Char | CharArray(_) => 1,
1419 UInt16 | Int16 => 2,
1420 UInt32 | Int32 | Float => 4,
1421 UInt64 | Int64 | Double => 8,
1422 Array(t, _) => t.len(),
1423 }
1424 }
1425
1426 pub fn primitive_type(&self) -> String {
1428 use self::MavType::*;
1429 match self {
1430 UInt8MavlinkVersion => "uint8_t".into(),
1431 UInt8 => "uint8_t".into(),
1432 Int8 => "int8_t".into(),
1433 Char => "char".into(),
1434 UInt16 => "uint16_t".into(),
1435 Int16 => "int16_t".into(),
1436 UInt32 => "uint32_t".into(),
1437 Int32 => "int32_t".into(),
1438 Float => "float".into(),
1439 UInt64 => "uint64_t".into(),
1440 Int64 => "int64_t".into(),
1441 Double => "double".into(),
1442 CharArray(_) => "char".into(),
1443 Array(t, _) => t.primitive_type(),
1444 }
1445 }
1446
1447 pub fn rust_type(&self) -> String {
1450 use self::MavType::*;
1451 match self {
1452 UInt8 | UInt8MavlinkVersion => "u8".into(),
1453 Int8 => "i8".into(),
1454 Char => "u8".into(),
1455 UInt16 => "u16".into(),
1456 Int16 => "i16".into(),
1457 UInt32 => "u32".into(),
1458 Int32 => "i32".into(),
1459 Float => "f32".into(),
1460 UInt64 => "u64".into(),
1461 Int64 => "i64".into(),
1462 Double => "f64".into(),
1463 CharArray(size) => format!("CharArray<{size}>"),
1464 Array(t, size) => format!("[{};{}]", t.rust_type(), size),
1465 }
1466 }
1467
1468 pub fn emit_default_value(&self, dialect_has_version: bool) -> TokenStream {
1469 use self::MavType::*;
1470 match self {
1471 UInt8 => quote!(0_u8),
1472 UInt8MavlinkVersion => {
1473 if dialect_has_version {
1474 quote!(MINOR_MAVLINK_VERSION)
1475 } else {
1476 quote!(0_u8)
1477 }
1478 }
1479 Int8 => quote!(0_i8),
1480 Char => quote!(0_u8),
1481 UInt16 => quote!(0_u16),
1482 Int16 => quote!(0_i16),
1483 UInt32 => quote!(0_u32),
1484 Int32 => quote!(0_i32),
1485 Float => quote!(0.0_f32),
1486 UInt64 => quote!(0_u64),
1487 Int64 => quote!(0_i64),
1488 Double => quote!(0.0_f64),
1489 CharArray(size) => quote!(CharArray::new([0_u8; #size])),
1490 Array(ty, size) => {
1491 let default_value = ty.emit_default_value(dialect_has_version);
1492 quote!([#default_value; #size])
1493 }
1494 }
1495 }
1496
1497 pub fn rust_primitive_type(&self) -> String {
1501 use self::MavType::*;
1502 match self {
1503 Array(t, _) => t.rust_primitive_type(),
1504 _ => self.rust_type(),
1505 }
1506 }
1507
1508 pub fn compare(&self, other: &Self) -> Ordering {
1510 let len = self.order_len();
1511 (-(len as isize)).cmp(&(-(other.order_len() as isize)))
1512 }
1513}
1514
1515#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1516#[derive(Debug, PartialEq, Eq, Clone, Default)]
1517pub enum MavDeprecationType {
1518 #[default]
1519 Deprecated,
1520 Superseded,
1521}
1522
1523impl Display for MavDeprecationType {
1524 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1525 match self {
1526 Self::Deprecated => f.write_str("Deprecated"),
1527 Self::Superseded => f.write_str("Superseded"),
1528 }
1529 }
1530}
1531
1532#[derive(Debug, PartialEq, Eq, Clone, Default)]
1533#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1534pub struct MavDeprecation {
1535 pub since: String,
1537 pub replaced_by: Option<String>,
1538 pub deprecation_type: MavDeprecationType,
1539 pub note: Option<String>,
1540}
1541
1542impl MavDeprecation {
1543 pub fn emit_tokens(&self) -> TokenStream {
1544 let since = &self.since;
1545 let note = match &self.note {
1546 Some(str) if str.is_empty() || str.ends_with(".") => str.clone(),
1547 Some(str) => format!("{str}."),
1548 None => String::new(),
1549 };
1550 let replaced_by = match &self.replaced_by {
1551 Some(str) if str.starts_with('`') => format!("See {str}"),
1552 Some(str) => format!("See `{str}`"),
1553 None => String::new(),
1554 };
1555 let message = format!(
1556 "{note} {replaced_by} ({} since {since})",
1557 self.deprecation_type
1558 );
1559 quote!(#[deprecated = #message])
1560 }
1561}
1562
1563#[derive(Debug, PartialEq, Eq, Clone, Default)]
1564#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1565pub struct MavSuperseded {
1566 pub since: String,
1568 pub replaced_by: String,
1570 pub note: Option<String>,
1571}
1572
1573impl MavSuperseded {
1574 pub fn emit_tokens(&self) -> TokenStream {
1575 let since = &self.since;
1576 let note = match &self.note {
1577 Some(str) if str.is_empty() || str.ends_with(".") => str.clone(),
1578 Some(str) => format!("{str}."),
1579 None => String::new(),
1580 };
1581 let replaced_by = if self.replaced_by.starts_with("`") {
1582 format!("See {}", self.replaced_by)
1583 } else if self.replaced_by.is_empty() {
1584 String::new()
1585 } else {
1586 format!("See `{}`", self.replaced_by)
1587 };
1588 let message = format!("{note} {replaced_by} (Superseded since {since})");
1589 quote!(#[superseded = #message])
1590 }
1591}
1592
1593#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1594#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1595#[cfg_attr(feature = "serde", serde(tag = "type"))]
1596pub enum MavXmlElement {
1597 Version,
1598 Mavlink,
1599 Dialect,
1600 Include,
1601 Enums,
1602 Enum,
1603 Entry,
1604 Description,
1605 Param,
1606 Messages,
1607 Message,
1608 Field,
1609 Deprecated,
1610 Wip,
1611 Extensions,
1612 Superseded,
1613}
1614
1615const fn identify_element(s: &[u8]) -> Option<MavXmlElement> {
1616 use self::MavXmlElement::*;
1617 match s {
1618 b"version" => Some(Version),
1619 b"mavlink" => Some(Mavlink),
1620 b"dialect" => Some(Dialect),
1621 b"include" => Some(Include),
1622 b"enums" => Some(Enums),
1623 b"enum" => Some(Enum),
1624 b"entry" => Some(Entry),
1625 b"description" => Some(Description),
1626 b"param" => Some(Param),
1627 b"messages" => Some(Messages),
1628 b"message" => Some(Message),
1629 b"field" => Some(Field),
1630 b"deprecated" => Some(Deprecated),
1631 b"wip" => Some(Wip),
1632 b"extensions" => Some(Extensions),
1633 b"superseded" => Some(Superseded),
1634 _ => None,
1635 }
1636}
1637
1638fn is_valid_parent(p: Option<MavXmlElement>, s: MavXmlElement) -> bool {
1639 use self::MavXmlElement::*;
1640 match s {
1641 Version => p == Some(Mavlink),
1642 Mavlink => p.is_none(),
1643 Dialect => p == Some(Mavlink),
1644 Include => p == Some(Mavlink),
1645 Enums => p == Some(Mavlink),
1646 Enum => p == Some(Enums),
1647 Entry => p == Some(Enum),
1648 Description => p == Some(Entry) || p == Some(Message) || p == Some(Enum),
1649 Param => p == Some(Entry),
1650 Messages => p == Some(Mavlink),
1651 Message => p == Some(Messages),
1652 Field => p == Some(Message),
1653 Deprecated => p == Some(Entry) || p == Some(Message) || p == Some(Enum),
1654 Wip => p == Some(Entry) || p == Some(Message) || p == Some(Enum),
1655 Extensions => p == Some(Message),
1656 Superseded => p == Some(Entry) || p == Some(Message) || p == Some(Enum),
1657 }
1658}
1659
1660pub fn parse_profile(
1661 definitions_dir: &Path,
1662 definition_file: &Path,
1663 parsed_files: &mut HashSet<PathBuf>,
1664) -> Result<MavProfile, BindGenError> {
1665 let in_path = Path::new(&definitions_dir).join(definition_file);
1666 parsed_files.insert(in_path.clone()); let mut stack: Vec<MavXmlElement> = vec![];
1669
1670 let mut text = None;
1671
1672 let mut profile = MavProfile::default();
1673 let mut field = MavField::default();
1674 let mut message = MavMessage::default();
1675 let mut mavenum = MavEnum::default();
1676 let mut entry = MavEnumEntry::default();
1677 let mut param_index: Option<usize> = None;
1678 let mut param_label: Option<String> = None;
1679 let mut param_units: Option<String> = None;
1680 let mut param_enum: Option<String> = None;
1681 let mut param_increment: Option<f32> = None;
1682 let mut param_min_value: Option<f32> = None;
1683 let mut param_max_value: Option<f32> = None;
1684 let mut param_reserved = false;
1685 let mut param_default: Option<f32> = None;
1686 let mut deprecated: Option<MavDeprecation> = None;
1687
1688 let mut xml_filter = MavXmlFilter::default();
1689 let mut events: Vec<Result<Event, quick_xml::Error>> = Vec::new();
1690 let xml = std::fs::read_to_string(&in_path).map_err(|e| {
1691 BindGenError::CouldNotReadDefinitionFile {
1692 source: e,
1693 path: in_path.clone(),
1694 }
1695 })?;
1696 let mut reader = Reader::from_str(&xml);
1697 reader.config_mut().trim_text(true);
1698 reader.config_mut().expand_empty_elements = true;
1699
1700 loop {
1701 match reader.read_event() {
1702 Ok(Event::Eof) => {
1703 events.push(Ok(Event::Eof));
1704 break;
1705 }
1706 Ok(event) => events.push(Ok(event.into_owned())),
1707 Err(why) => events.push(Err(why)),
1708 }
1709 }
1710 xml_filter.filter(&mut events);
1711 let mut is_in_extension = false;
1712 for e in events {
1713 match e {
1714 Ok(Event::Start(bytes)) => {
1715 let Some(id) = identify_element(bytes.name().into_inner()) else {
1716 panic!(
1717 "unexpected element {:?}",
1718 String::from_utf8_lossy(bytes.name().into_inner())
1719 );
1720 };
1721
1722 assert!(
1723 is_valid_parent(stack.last().copied(), id),
1724 "not valid parent {:?} of {id:?}",
1725 stack.last(),
1726 );
1727
1728 match id {
1729 MavXmlElement::Extensions => {
1730 is_in_extension = true;
1731 }
1732 MavXmlElement::Message => {
1733 message = MavMessage::default();
1734 }
1735 MavXmlElement::Field => {
1736 field = MavField {
1737 is_extension: is_in_extension,
1738 ..Default::default()
1739 };
1740 }
1741 MavXmlElement::Enum => {
1742 mavenum = MavEnum::default();
1743 }
1744 MavXmlElement::Entry => {
1745 if mavenum.entries.is_empty() {
1746 mavenum.deprecated = deprecated;
1747 }
1748 deprecated = None;
1749 entry = MavEnumEntry::default();
1750 }
1751 MavXmlElement::Param => {
1752 param_index = None;
1753 param_increment = None;
1754 param_min_value = None;
1755 param_max_value = None;
1756 param_reserved = false;
1757 param_default = None;
1758 }
1759 MavXmlElement::Deprecated => {
1760 deprecated = Some(MavDeprecation {
1761 replaced_by: None,
1762 since: String::new(),
1763 deprecation_type: MavDeprecationType::Deprecated,
1764 note: None,
1765 });
1766 }
1767 MavXmlElement::Superseded => {
1768 deprecated = Some(MavDeprecation {
1769 replaced_by: Some(String::new()),
1770 since: String::new(),
1771 deprecation_type: MavDeprecationType::Superseded,
1772 note: None,
1773 });
1774 }
1775 _ => (),
1776 }
1777 stack.push(id);
1778
1779 for attr in bytes.attributes() {
1780 let attr = attr.unwrap();
1781 match stack.last() {
1782 Some(&MavXmlElement::Enum) => {
1783 if attr.key.into_inner() == b"name" {
1784 mavenum.name = to_pascal_case(attr.value);
1785 } else if attr.key.into_inner() == b"bitmask" {
1787 mavenum.bitmask = true;
1788 }
1789 }
1790 Some(&MavXmlElement::Entry) => {
1791 match attr.key.into_inner() {
1792 b"name" => {
1793 entry.name = String::from_utf8_lossy(&attr.value).to_string();
1794 }
1795 b"value" => {
1796 let value = String::from_utf8_lossy(&attr.value);
1797 let (src, radix) = value
1799 .strip_prefix("0x")
1800 .map(|value| (value, 16))
1801 .unwrap_or((value.as_ref(), 10));
1802 entry.value = u64::from_str_radix(src, radix).ok();
1803 }
1804 _ => (),
1805 }
1806 }
1807 Some(&MavXmlElement::Message) => {
1808 match attr.key.into_inner() {
1809 b"name" => {
1810 message.name = String::from_utf8_lossy(&attr.value).to_string();
1824 }
1825 b"id" => {
1826 message.id =
1827 String::from_utf8_lossy(&attr.value).parse().unwrap();
1828 }
1829 _ => (),
1830 }
1831 }
1832 Some(&MavXmlElement::Field) => {
1833 match attr.key.into_inner() {
1834 b"name" => {
1835 let name = String::from_utf8_lossy(&attr.value);
1836 field.name = if name == "type" {
1837 "mavtype".to_string()
1838 } else {
1839 name.to_string()
1840 };
1841 }
1842 b"type" => {
1843 let r#type = String::from_utf8_lossy(&attr.value);
1844 field.mavtype = MavType::parse_type(&r#type).unwrap();
1845 }
1846 b"enum" => {
1847 field.enumtype = Some(to_pascal_case(&attr.value));
1848
1849 if let Some(e) =
1851 profile.enums.get(field.enumtype.as_ref().unwrap())
1852 {
1853 if e.bitmask {
1854 field.display = Some("bitmask".to_string());
1855 }
1856 }
1857 }
1858 b"display" => {
1859 field.display =
1860 Some(String::from_utf8_lossy(&attr.value).to_string());
1861 }
1862 _ => (),
1863 }
1864 }
1865 Some(&MavXmlElement::Param) => {
1866 if entry.params.is_none() {
1867 entry.params = Some(vec![]);
1868 }
1869 match attr.key.into_inner() {
1870 b"index" => {
1871 let value = String::from_utf8_lossy(&attr.value)
1872 .parse()
1873 .expect("failed to parse param index");
1874 assert!(
1875 (1..=7).contains(&value),
1876 "param index must be between 1 and 7"
1877 );
1878 param_index = Some(value);
1879 }
1880 b"label" => {
1881 param_label =
1882 std::str::from_utf8(&attr.value).ok().map(str::to_owned);
1883 }
1884 b"increment" => {
1885 param_increment = Some(
1886 String::from_utf8_lossy(&attr.value)
1887 .parse()
1888 .expect("failed to parse param increment"),
1889 );
1890 }
1891 b"minValue" => {
1892 param_min_value = Some(
1893 String::from_utf8_lossy(&attr.value)
1894 .parse()
1895 .expect("failed to parse param minValue"),
1896 );
1897 }
1898 b"maxValue" => {
1899 param_max_value = Some(
1900 String::from_utf8_lossy(&attr.value)
1901 .parse()
1902 .expect("failed to parse param maxValue"),
1903 );
1904 }
1905 b"units" => {
1906 param_units =
1907 std::str::from_utf8(&attr.value).ok().map(str::to_owned);
1908 }
1909 b"enum" => {
1910 param_enum =
1911 std::str::from_utf8(&attr.value).ok().map(to_pascal_case);
1912 }
1913 b"reserved" => {
1914 param_reserved = attr.value.as_ref() == b"true";
1915 }
1916 b"default" => {
1917 param_default = Some(
1918 String::from_utf8_lossy(&attr.value)
1919 .parse()
1920 .expect("failed to parse param maxValue"),
1921 );
1922 }
1923 _ => (),
1924 }
1925 }
1926 Some(&MavXmlElement::Deprecated) => match attr.key.into_inner() {
1927 b"since" => {
1928 deprecated.as_mut().unwrap().since =
1929 String::from_utf8_lossy(&attr.value).to_string();
1930 }
1931 b"replaced_by" => {
1932 let value = String::from_utf8_lossy(&attr.value);
1933 deprecated.as_mut().unwrap().replaced_by = if value.is_empty() {
1934 None
1935 } else {
1936 Some(value.to_string())
1937 };
1938 }
1939 _ => (),
1940 },
1941 Some(&MavXmlElement::Superseded) => match attr.key.into_inner() {
1942 b"since" => {
1943 deprecated.as_mut().unwrap().since =
1944 String::from_utf8_lossy(&attr.value).to_string();
1945 }
1946 b"replaced_by" => {
1947 deprecated.as_mut().unwrap().replaced_by =
1948 Some(String::from_utf8_lossy(&attr.value).to_string());
1949 }
1950 _ => (),
1951 },
1952 _ => (),
1953 }
1954 }
1955 }
1956 Ok(Event::Text(bytes)) => {
1957 let s = String::from_utf8_lossy(&bytes);
1958
1959 use self::MavXmlElement::*;
1960 match (stack.last(), stack.get(stack.len() - 2)) {
1961 (Some(&Description), Some(&Message))
1962 | (Some(&Field), Some(&Message))
1963 | (Some(&Description), Some(&Enum))
1964 | (Some(&Description), Some(&Entry))
1965 | (Some(&Include), Some(&Mavlink))
1966 | (Some(&Version), Some(&Mavlink))
1967 | (Some(&Dialect), Some(&Mavlink))
1968 | (Some(&Param), Some(&Entry))
1969 | (Some(Deprecated), _)
1970 | (Some(Superseded), _) => {
1971 text = Some(text.map(|t| t + s.as_ref()).unwrap_or(s.to_string()));
1972 }
1973 data => {
1974 panic!("unexpected text data {data:?} reading {s:?}");
1975 }
1976 }
1977 }
1978 Ok(Event::GeneralRef(bytes)) => {
1979 let entity = String::from_utf8_lossy(&bytes);
1980 text = Some(
1981 text.map(|t| format!("{t}&{entity};"))
1982 .unwrap_or(format!("&{entity};")),
1983 );
1984 }
1985 Ok(Event::End(_)) => {
1986 match stack.last() {
1987 Some(&MavXmlElement::Field) => {
1988 field.description = text.map(|t| t.replace('\n', " "));
1989 message.fields.push(field.clone());
1990 }
1991 Some(&MavXmlElement::Entry) => {
1992 entry.deprecated = deprecated;
1993 deprecated = None;
1994 mavenum.entries.push(entry.clone());
1995 }
1996 Some(&MavXmlElement::Message) => {
1997 message.deprecated = deprecated;
1998
1999 deprecated = None;
2000 is_in_extension = false;
2001 let mut not_extension_fields = message.fields.clone();
2003 let mut extension_fields = message.fields.clone();
2004
2005 not_extension_fields.retain(|field| !field.is_extension);
2006 extension_fields.retain(|field| field.is_extension);
2007
2008 not_extension_fields.sort_by(|a, b| a.mavtype.compare(&b.mavtype));
2010
2011 let mut msg = message.clone();
2013 msg.fields.clear();
2014 msg.fields.extend(not_extension_fields);
2015 msg.fields.extend(extension_fields);
2016
2017 msg.validate_unique_fields();
2019 msg.validate_field_count();
2021
2022 profile.add_message(&msg);
2023 }
2024 Some(&MavXmlElement::Enum) => {
2025 profile.add_enum(&mavenum);
2026 }
2027 Some(&MavXmlElement::Include) => {
2028 let include =
2029 PathBuf::from(text.map(|t| t.replace('\n', "")).unwrap_or_default());
2030 let include_file = Path::new(&definitions_dir).join(include.clone());
2031 if !parsed_files.contains(&include_file) {
2032 let included_profile =
2033 parse_profile(definitions_dir, &include, parsed_files)?;
2034 for message in included_profile.messages.values() {
2035 profile.add_message(message);
2036 }
2037 for enm in included_profile.enums.values() {
2038 profile.add_enum(enm);
2039 }
2040 if profile.version.is_none() {
2041 profile.version = included_profile.version;
2042 }
2043 }
2044 }
2045 Some(&MavXmlElement::Description) => match stack.get(stack.len() - 2) {
2046 Some(&MavXmlElement::Message) => {
2047 message.description = text.map(|t| t.replace('\n', " "));
2048 }
2049 Some(&MavXmlElement::Enum) => {
2050 mavenum.description = text.map(|t| t.replace('\n', " "));
2051 }
2052 Some(&MavXmlElement::Entry) => {
2053 entry.description = text.map(|t| t.replace('\n', " "));
2054 }
2055 _ => (),
2056 },
2057 Some(&MavXmlElement::Version) => {
2058 if let Some(t) = text {
2059 profile.version =
2060 Some(t.parse().expect("Invalid minor version number format"));
2061 }
2062 }
2063 Some(&MavXmlElement::Dialect) => {
2064 if let Some(t) = text {
2065 profile.dialect =
2066 Some(t.parse().expect("Invalid dialect number format"));
2067 }
2068 }
2069 Some(&MavXmlElement::Deprecated) => {
2070 if let Some(t) = text {
2071 deprecated.as_mut().unwrap().note = Some(t);
2072 }
2073 }
2074 Some(&MavXmlElement::Param) => {
2075 if let Some(params) = entry.params.as_mut() {
2076 let param_index = param_index.expect("entry params must have an index");
2078 while params.len() < param_index {
2079 params.push(MavParam {
2080 index: params.len() + 1,
2081 description: None,
2082 ..Default::default()
2083 });
2084 }
2085 if let Some((min, max)) = param_min_value.zip(param_max_value) {
2086 assert!(
2087 min <= max,
2088 "param minValue must not be greater than maxValue"
2089 );
2090 }
2091 params[param_index - 1] = MavParam {
2092 index: param_index,
2093 description: text.map(|t| t.replace('\n', " ")),
2094 label: param_label,
2095 units: param_units,
2096 enum_used: param_enum,
2097 increment: param_increment,
2098 max_value: param_max_value,
2099 min_value: param_min_value,
2100 reserved: param_reserved,
2101 default: param_default,
2102 };
2103 param_label = None;
2104 param_units = None;
2105 param_enum = None;
2106 }
2107 }
2108 _ => (),
2109 }
2110 text = None;
2111 stack.pop();
2112 }
2114 Err(e) => {
2115 eprintln!("Error: {e}");
2116 break;
2117 }
2118 _ => {}
2119 }
2120 }
2121
2122 Ok(profile.update_enums())
2124}
2125
2126pub fn generate<W: Write>(
2129 definitions_dir: &Path,
2130 definition_file: &Path,
2131 output_rust: &mut W,
2132) -> Result<(), BindGenError> {
2133 let mut parsed_files: HashSet<PathBuf> = HashSet::new();
2134 let profile = parse_profile(definitions_dir, definition_file, &mut parsed_files)?;
2135
2136 let dialect_name = util::to_dialect_name(definition_file);
2137
2138 let rust_tokens = profile.emit_rust(&dialect_name);
2140 writeln!(output_rust, "{rust_tokens}").unwrap();
2141
2142 Ok(())
2143}
2144
2145pub fn extra_crc(msg: &MavMessage) -> u8 {
2151 let mut crc = CRCu16::crc16mcrf4cc();
2154
2155 crc.digest(msg.name.as_bytes());
2156 crc.digest(b" ");
2157
2158 let mut f = msg.fields.clone();
2159 f.retain(|f| !f.is_extension);
2161 f.sort_by(|a, b| a.mavtype.compare(&b.mavtype));
2162 for field in &f {
2163 crc.digest(field.mavtype.primitive_type().as_bytes());
2164 crc.digest(b" ");
2165 if field.name == "mavtype" {
2166 crc.digest(b"type");
2167 } else {
2168 crc.digest(field.name.as_bytes());
2169 }
2170 crc.digest(b" ");
2171 if let MavType::Array(_, size) | MavType::CharArray(size) = field.mavtype {
2172 crc.digest(&[size as u8]);
2173 }
2174 }
2175
2176 let crcval = crc.get_crc();
2177 ((crcval & 0xFF) ^ (crcval >> 8)) as u8
2178}
2179
2180#[cfg(not(feature = "mav2-message-extensions"))]
2181struct ExtensionFilter {
2182 pub is_in: bool,
2183}
2184
2185struct MessageFilter {
2186 pub is_in: bool,
2187 pub messages: Vec<String>,
2188}
2189
2190impl MessageFilter {
2191 pub fn new() -> Self {
2192 Self {
2193 is_in: false,
2194 messages: vec![
2195 "STORM32_GIMBAL_MANAGER_INFORMATION".to_string(),
2197 ],
2198 }
2199 }
2200}
2201
2202struct MavXmlFilter {
2203 #[cfg(not(feature = "mav2-message-extensions"))]
2204 extension_filter: ExtensionFilter,
2205 message_filter: MessageFilter,
2206}
2207
2208impl Default for MavXmlFilter {
2209 fn default() -> Self {
2210 Self {
2211 #[cfg(not(feature = "mav2-message-extensions"))]
2212 extension_filter: ExtensionFilter { is_in: false },
2213 message_filter: MessageFilter::new(),
2214 }
2215 }
2216}
2217
2218impl MavXmlFilter {
2219 pub fn filter(&mut self, elements: &mut Vec<Result<Event, quick_xml::Error>>) {
2220 elements.retain(|x| self.filter_extension(x) && self.filter_messages(x));
2221 }
2222
2223 #[cfg(feature = "mav2-message-extensions")]
2224 pub fn filter_extension(&mut self, _element: &Result<Event, quick_xml::Error>) -> bool {
2225 true
2226 }
2227
2228 #[cfg(not(feature = "mav2-message-extensions"))]
2230 pub fn filter_extension(&mut self, element: &Result<Event, quick_xml::Error>) -> bool {
2231 match element {
2232 Ok(content) => {
2233 match content {
2234 Event::Start(bytes) | Event::Empty(bytes) => {
2235 let Some(id) = identify_element(bytes.name().into_inner()) else {
2236 panic!(
2237 "unexpected element {:?}",
2238 String::from_utf8_lossy(bytes.name().into_inner())
2239 );
2240 };
2241 if id == MavXmlElement::Extensions {
2242 self.extension_filter.is_in = true;
2243 }
2244 }
2245 Event::End(bytes) => {
2246 let Some(id) = identify_element(bytes.name().into_inner()) else {
2247 panic!(
2248 "unexpected element {:?}",
2249 String::from_utf8_lossy(bytes.name().into_inner())
2250 );
2251 };
2252
2253 if id == MavXmlElement::Message {
2254 self.extension_filter.is_in = false;
2255 }
2256 }
2257 _ => {}
2258 }
2259 !self.extension_filter.is_in
2260 }
2261 Err(error) => panic!("Failed to filter XML: {error}"),
2262 }
2263 }
2264
2265 pub fn filter_messages(&mut self, element: &Result<Event, quick_xml::Error>) -> bool {
2267 match element {
2268 Ok(content) => {
2269 match content {
2270 Event::Start(bytes) | Event::Empty(bytes) => {
2271 let Some(id) = identify_element(bytes.name().into_inner()) else {
2272 panic!(
2273 "unexpected element {:?}",
2274 String::from_utf8_lossy(bytes.name().into_inner())
2275 );
2276 };
2277 if id == MavXmlElement::Message {
2278 for attr in bytes.attributes() {
2279 let attr = attr.unwrap();
2280 if attr.key.into_inner() == b"name" {
2281 let value = String::from_utf8_lossy(&attr.value).into_owned();
2282 if self.message_filter.messages.contains(&value) {
2283 self.message_filter.is_in = true;
2284 return false;
2285 }
2286 }
2287 }
2288 }
2289 }
2290 Event::End(bytes) => {
2291 let Some(id) = identify_element(bytes.name().into_inner()) else {
2292 panic!(
2293 "unexpected element {:?}",
2294 String::from_utf8_lossy(bytes.name().into_inner())
2295 );
2296 };
2297
2298 if id == MavXmlElement::Message && self.message_filter.is_in {
2299 self.message_filter.is_in = false;
2300 return false;
2301 }
2302 }
2303 _ => {}
2304 }
2305 !self.message_filter.is_in
2306 }
2307 Err(error) => panic!("Failed to filter XML: {error}"),
2308 }
2309 }
2310}
2311
2312#[inline(always)]
2313fn to_pascal_case(text: impl AsRef<[u8]>) -> String {
2314 let input = text.as_ref();
2315 let mut result = String::with_capacity(input.len());
2316 let mut capitalize = true;
2317
2318 for &b in input {
2319 if b == b'_' {
2320 capitalize = true;
2321 continue;
2322 }
2323
2324 if capitalize {
2325 result.push((b as char).to_ascii_uppercase());
2326 capitalize = false;
2327 } else {
2328 result.push((b as char).to_ascii_lowercase());
2329 }
2330 }
2331
2332 result
2333}
2334
2335#[cfg(test)]
2336mod tests {
2337 use super::*;
2338
2339 #[test]
2340 fn emits_target_id_match_arms() {
2341 let mut profile = MavProfile::default();
2343
2344 let msg_with_targets = MavMessage {
2345 id: 300,
2346 name: "COMMAND_INT".to_string(),
2347 description: None,
2348 fields: vec![
2349 MavField {
2350 mavtype: MavType::UInt8,
2351 name: "target_system".to_string(),
2352 description: None,
2353 enumtype: None,
2354 display: None,
2355 is_extension: false,
2356 },
2357 MavField {
2358 mavtype: MavType::UInt8,
2359 name: "target_component".to_string(),
2360 description: None,
2361 enumtype: None,
2362 display: None,
2363 is_extension: false,
2364 },
2365 ],
2366 deprecated: None,
2367 };
2368
2369 let msg_without_targets = MavMessage {
2370 id: 0,
2371 name: "HEARTBEAT".to_string(),
2372 description: None,
2373 fields: vec![MavField {
2374 mavtype: MavType::UInt32,
2375 name: "custom_mode".to_string(),
2376 description: None,
2377 enumtype: None,
2378 display: None,
2379 is_extension: false,
2380 }],
2381 deprecated: None,
2382 };
2383
2384 profile.add_message(&msg_with_targets);
2385 profile.add_message(&msg_without_targets);
2386
2387 let tokens = profile.emit_rust("common");
2388 let mut code = tokens.to_string();
2389 code.retain(|c| !c.is_whitespace());
2390
2391 assert!(code.contains("fntarget_system_id(&self)->Option<u8>"));
2393 assert!(code.contains("fntarget_component_id(&self)->Option<u8>"));
2394
2395 assert!(code.contains("Self::COMMAND_INT(inner)=>Some(inner.target_system)"));
2397 assert!(code.contains("Self::COMMAND_INT(inner)=>Some(inner.target_component)"));
2398
2399 assert!(!code.contains("Self::HEARTBEAT(inner)=>Some(inner.target_system)"));
2401 assert!(!code.contains("Self::HEARTBEAT(inner)=>Some(inner.target_component)"));
2402 }
2403
2404 #[test]
2405 fn validate_unique_fields_allows_unique() {
2406 let msg = MavMessage {
2407 id: 1,
2408 name: "FOO".to_string(),
2409 description: None,
2410 fields: vec![
2411 MavField {
2412 mavtype: MavType::UInt8,
2413 name: "a".to_string(),
2414 description: None,
2415 enumtype: None,
2416 display: None,
2417 is_extension: false,
2418 },
2419 MavField {
2420 mavtype: MavType::UInt16,
2421 name: "b".to_string(),
2422 description: None,
2423 enumtype: None,
2424 display: None,
2425 is_extension: false,
2426 },
2427 ],
2428 deprecated: None,
2429 };
2430 msg.validate_unique_fields();
2432 }
2433
2434 #[test]
2435 #[should_panic(expected = "Duplicate field")]
2436 fn validate_unique_fields_panics_on_duplicate() {
2437 let msg = MavMessage {
2438 id: 2,
2439 name: "BAR".to_string(),
2440 description: None,
2441 fields: vec![
2442 MavField {
2443 mavtype: MavType::UInt8,
2444 name: "target_system".to_string(),
2445 description: None,
2446 enumtype: None,
2447 display: None,
2448 is_extension: false,
2449 },
2450 MavField {
2451 mavtype: MavType::UInt8,
2452 name: "target_system".to_string(),
2453 description: None,
2454 enumtype: None,
2455 display: None,
2456 is_extension: false,
2457 },
2458 ],
2459 deprecated: None,
2460 };
2461 msg.validate_unique_fields();
2463 }
2464
2465 #[test]
2466 fn validate_field_count_ok() {
2467 let msg = MavMessage {
2468 id: 2,
2469 name: "FOO".to_string(),
2470 description: None,
2471 fields: vec![
2472 MavField {
2473 mavtype: MavType::UInt8,
2474 name: "a".to_string(),
2475 description: None,
2476 enumtype: None,
2477 display: None,
2478 is_extension: false,
2479 },
2480 MavField {
2481 mavtype: MavType::UInt8,
2482 name: "b".to_string(),
2483 description: None,
2484 enumtype: None,
2485 display: None,
2486 is_extension: false,
2487 },
2488 ],
2489 deprecated: None,
2490 };
2491 msg.validate_field_count();
2493 }
2494
2495 #[test]
2496 #[should_panic]
2497 fn validate_field_count_too_many() {
2498 let mut fields = vec![];
2499 for i in 0..65 {
2500 let field = MavField {
2501 mavtype: MavType::UInt8,
2502 name: format!("field_{i}"),
2503 description: None,
2504 enumtype: None,
2505 display: None,
2506 is_extension: false,
2507 };
2508 fields.push(field);
2509 }
2510 let msg = MavMessage {
2511 id: 2,
2512 name: "BAZ".to_string(),
2513 description: None,
2514 fields,
2515 deprecated: None,
2516 };
2517 msg.validate_field_count();
2519 }
2520
2521 #[test]
2522 #[should_panic]
2523 fn validate_field_count_empty() {
2524 let msg = MavMessage {
2525 id: 2,
2526 name: "BAM".to_string(),
2527 description: None,
2528 fields: vec![],
2529 deprecated: None,
2530 };
2531 msg.validate_field_count();
2533 }
2534
2535 #[test]
2536 fn test_fmt_mav_param_values() {
2537 let enum_param = MavParam {
2538 enum_used: Some("ENUM_NAME".to_string()),
2539 ..Default::default()
2540 };
2541 assert_eq!(enum_param.format_valid_values(), "[`ENUM_NAME`]");
2542
2543 let reserved_param = MavParam {
2544 reserved: true,
2545 default: Some(f32::NAN),
2546 ..Default::default()
2547 };
2548 assert_eq!(reserved_param.format_valid_values(), "Reserved (use NaN)");
2549
2550 let unrestricted_param = MavParam::default();
2551 assert_eq!(unrestricted_param.format_valid_values(), "");
2552
2553 let int_param = MavParam {
2554 increment: Some(1.0),
2555 ..Default::default()
2556 };
2557 assert_eq!(int_param.format_valid_values(), "Multiples of 1");
2558
2559 let pos_param = MavParam {
2560 min_value: Some(0.0),
2561 ..Default::default()
2562 };
2563 assert_eq!(pos_param.format_valid_values(), "≥ 0");
2564
2565 let max_param = MavParam {
2566 max_value: Some(5.5),
2567 ..Default::default()
2568 };
2569 assert_eq!(max_param.format_valid_values(), "≤ 5.5");
2570
2571 let pos_int_param = MavParam {
2572 min_value: Some(0.0),
2573 increment: Some(1.0),
2574 ..Default::default()
2575 };
2576 assert_eq!(pos_int_param.format_valid_values(), "0, 1, ..");
2577
2578 let max_inc_param = MavParam {
2579 increment: Some(1.0),
2580 max_value: Some(360.0),
2581 ..Default::default()
2582 };
2583 assert_eq!(max_inc_param.format_valid_values(), ".., 359, 360");
2584
2585 let range_param = MavParam {
2586 min_value: Some(0.0),
2587 max_value: Some(10.0),
2588 ..Default::default()
2589 };
2590 assert_eq!(range_param.format_valid_values(), "0 .. 10");
2591
2592 let int_range_param = MavParam {
2593 min_value: Some(0.0),
2594 max_value: Some(10.0),
2595 increment: Some(1.0),
2596 ..Default::default()
2597 };
2598 assert_eq!(int_range_param.format_valid_values(), "0, 1, .. , 10");
2599
2600 let close_inc_range_param = MavParam {
2601 min_value: Some(-2.0),
2602 max_value: Some(2.0),
2603 increment: Some(2.0),
2604 ..Default::default()
2605 };
2606 assert_eq!(close_inc_range_param.format_valid_values(), "-2, 0, 2");
2607
2608 let bin_range_param = MavParam {
2609 min_value: Some(0.0),
2610 max_value: Some(1.0),
2611 increment: Some(1.0),
2612 ..Default::default()
2613 };
2614 assert_eq!(bin_range_param.format_valid_values(), "0, 1");
2615 }
2616
2617 #[test]
2618 fn test_emit_doc_row() {
2619 let param = MavParam {
2620 index: 3,
2621 label: Some("test param".to_string()),
2622 min_value: Some(0.0),
2623 units: Some("m/s".to_string()),
2624 ..Default::default()
2625 };
2626 assert_eq!(
2628 param.emit_doc_row(false, false).to_string(),
2629 quote! {#[doc = "| 3 (test param)| |"]}.to_string()
2630 );
2631 assert_eq!(
2632 param.emit_doc_row(false, true).to_string(),
2633 quote! {#[doc = "| 3 (test param)| | m/s |"]}.to_string()
2634 );
2635 assert_eq!(
2636 param.emit_doc_row(true, false).to_string(),
2637 quote! {#[doc = "| 3 (test param)| | ≥ 0 |"]}.to_string()
2638 );
2639 assert_eq!(
2640 param.emit_doc_row(true, true).to_string(),
2641 quote! {#[doc = "| 3 (test param)| | ≥ 0 | m/s |"]}.to_string()
2642 );
2643
2644 let unlabeled_param = MavParam {
2645 index: 2,
2646 ..Default::default()
2647 };
2648 assert_eq!(
2649 unlabeled_param.emit_doc_row(false, false).to_string(),
2650 quote! {#[doc = "| 2 | |"]}.to_string()
2651 );
2652 }
2653}