1use std::{
4 fs,
5 io::{self, Write},
6 path::Path,
7 process::{self, Stdio},
8};
9
10use crate::converter::convert_spec;
11use crate::model::*;
12
13use convert_case::{Case, Casing};
14
15mod converter;
16mod model;
17
18use quickfix_spec_parser::{FieldSpec, FieldType};
19
20trait FieldAccessorGenerator {
21 fn getter_prefix_text(&self) -> &'static str;
22 fn setter_prefix_text(&self) -> &'static str;
23 fn caller_suffix_text(&self) -> &'static str;
24}
25
26pub fn generate<S: AsRef<Path>, D: AsRef<Path>>(
28 src: S,
29 dst: D,
30 begin_string: &str,
31) -> io::Result<()> {
32 let spec_data = fs::read(src)?;
33 let spec = quickfix_spec_parser::parse_spec(&spec_data).expect("Cannot parse FIX spec");
34 let spec = convert_spec(spec);
35
36 println!("Generating code ...");
38 let mut output = String::with_capacity(5 << 20); generate_root(&mut output, begin_string);
40 generate_field_ids(&mut output, &spec.field_specs);
41 generate_field_types(&mut output, &spec.field_specs);
42 generate_headers(&mut output, &spec.headers);
43 generate_trailers(&mut output, &spec.trailers);
44 generate_messages(&mut output, &spec.messages);
45 generate_message_cracker(&mut output, &spec.messages);
46
47 let mut rustfmt = process::Command::new("rustfmt")
49 .stdin(Stdio::piped())
50 .stdout(Stdio::piped())
51 .spawn()?;
52
53 println!("Formatting code ...");
55 let mut rustfmt_in = rustfmt.stdin.take().expect("Fail to take rustfmt stdin");
56 rustfmt_in.write_all(output.as_bytes())?;
57 rustfmt_in.flush()?;
58 drop(rustfmt_in); let rustfmt_out = rustfmt.wait_with_output()?;
62 if !rustfmt_out.status.success() {
63 println!("rustfmt stdout =======================");
64 println!("{}", String::from_utf8_lossy(&rustfmt_out.stdout));
65 println!("rustfmt stderr =======================");
66 println!("{}", String::from_utf8_lossy(&rustfmt_out.stderr));
67
68 panic!("Fail to run rustfmt");
69 }
70
71 println!("Writing code to disk ...");
73 fs::write(dst, rustfmt_out.stdout)?;
74 Ok(())
75}
76
77fn generate_root(output: &mut String, begin_string: &str) {
78 output.push_str(&format!(
79 r#" #[allow(unused_imports)]
80 use quickfix::*;
81
82 pub const FIX_BEGIN_STRING: &str = "{begin_string}";
83
84 #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
85 pub struct FixParseError;
86
87 impl std::fmt::Display for FixParseError {{
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{
89 writeln!(f, "FIX parse error")
90 }}
91 }}
92
93 impl std::error::Error for FixParseError {{}}
94
95 pub struct GroupIterator<'a, T, I> {{
96 parent: &'a T,
97 clone_group_func: fn(&'a T, usize) -> Option<I>,
98 current_index: usize,
99 }}
100
101 impl<T, I> Iterator for GroupIterator<'_, T, I> {{
102 type Item = I;
103
104 fn next(&mut self) -> Option<Self::Item> {{
105 self.current_index += 1;
106 (self.clone_group_func)(self.parent, self.current_index)
107 }}
108 }}
109
110 "#
111 ))
112}
113
114fn generate_field_ids(output: &mut String, field_specs: &[FieldSpec]) {
115 output.push_str("pub mod field_id {\n");
116
117 for field_spec in field_specs {
118 output.push_str(&format!(
119 "pub const {}: i32 = {};\n",
120 field_spec.name.to_case(Case::Constant),
121 field_spec.number
122 ));
123 }
124
125 output.push_str("} // field_id\n\n");
126}
127
128fn generate_field_types(output: &mut String, field_specs: &[FieldSpec]) {
129 output.push_str("pub mod field_types {\n");
130
131 for field_spec in field_specs {
132 if !field_spec.values.is_empty() {
133 match &field_spec.r#type {
134 FieldType::Int | FieldType::Long => {
135 generate_field_type_int_values(output, field_spec);
136 }
137 _ => {
138 generate_field_type_char_values(output, field_spec);
139 }
140 }
141 } else {
142 generate_field_type_alias(output, field_spec);
143 }
144 }
145
146 output.push_str("} // field_types\n\n");
147}
148
149fn generate_field_type_int_values(output: &mut String, field_spec: &FieldSpec) {
150 assert!(!field_spec.values.is_empty());
151 assert!(matches!(
152 field_spec.r#type,
153 FieldType::Int | FieldType::Long
154 ));
155
156 let enum_name = field_spec.name.as_str();
157
158 output.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n");
160 output.push_str(&format!("pub enum {enum_name} {{\n"));
161 for value in &field_spec.values {
162 output.push_str(&format!(
163 "{} = {},\n",
164 value.description.to_case(Case::UpperCamel),
165 value.value
166 ));
167 }
168 output.push_str("}\n\n");
169
170 generate_field_type_values(output, field_spec);
171}
172
173fn generate_field_type_char_values(output: &mut String, field_spec: &FieldSpec) {
174 assert!(!field_spec.values.is_empty());
175
176 let enum_name = field_spec.name.as_str();
177
178 output.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n");
180 output.push_str(&format!("pub enum {enum_name} {{\n"));
181 for value in &field_spec.values {
182 output.push_str(&format!(
183 "{},\n",
184 value.description.to_case(Case::UpperCamel)
185 ));
186 }
187 output.push_str("}\n\n");
188
189 generate_field_type_values(output, field_spec);
190}
191
192fn generate_field_type_values(output: &mut String, field_spec: &FieldSpec) {
193 assert!(!field_spec.values.is_empty());
194
195 let type_name = field_spec.name.as_str();
196
197 output.push_str(&format!(
199 r#" impl {type_name} {{
200 #[inline(always)]
201 pub const fn from_const_bytes(s: &[u8]) -> Result<Self, crate::FixParseError> {{
202 match s {{
203 "#
204 ));
205 for value in &field_spec.values {
206 output.push_str(&format!(
207 " b\"{}\" => Ok(Self::{}),\n",
208 value.value,
209 value.description.to_case(Case::UpperCamel),
210 ));
211 }
212 output.push_str(
213 r#" _ => Err(crate::FixParseError),
214 }
215 }
216 }
217
218 "#,
219 );
220
221 output.push_str(&format!(
223 r#" impl std::str::FromStr for {type_name} {{
224 type Err = crate::FixParseError;
225 fn from_str(s: &str) -> Result<Self, Self::Err> {{
226 Self::from_const_bytes(s.as_bytes())
227 }}
228 }}
229
230 "#
231 ));
232
233 output.push_str(&format!(
235 r#" impl quickfix::IntoFixValue for {type_name} {{
236 fn into_fix_value(self) -> Result<std::ffi::CString, std::ffi::NulError> {{
237 std::ffi::CString::new(match self {{
238 "#
239 ));
240 for value in &field_spec.values {
241 output.push_str(&format!(
242 " Self::{} => \"{}\",\n",
243 value.description.to_case(Case::UpperCamel),
244 value.value,
245 ));
246 }
247 output.push_str(
248 r#" })
249 }
250 }
251
252 "#,
253 );
254}
255
256fn generate_field_type_alias(output: &mut String, field_spec: &FieldSpec) {
257 assert!(field_spec.values.is_empty());
258
259 let type_name = field_spec.name.as_str();
260 let rust_type = match &field_spec.r#type {
261 FieldType::Int => "i64",
262 FieldType::Long => "i128",
263 FieldType::Length => "u32",
264 FieldType::SequenceNumber => "u32",
265 FieldType::NumberInGroup => "i32",
266 FieldType::Boolean => "bool",
267
268 FieldType::Float
269 | FieldType::Price
270 | FieldType::Amount
271 | FieldType::Quantity
272 | FieldType::PriceOffset
273 | FieldType::Percentage => "f64",
274
275 FieldType::Char
276 | FieldType::Data
277 | FieldType::Time
278 | FieldType::Date
279 | FieldType::MonthYear
280 | FieldType::DayOfMonth
281 | FieldType::String
282 | FieldType::Currency
283 | FieldType::MultipleValueString
284 | FieldType::Exchange
285 | FieldType::LocalMarketDate
286 | FieldType::UtcTimeStamp
287 | FieldType::UtcDate
288 | FieldType::UtcTimeOnly
289 | FieldType::Country
290 | FieldType::UtcDateOnly
291 | FieldType::MultipleCharValue
292 | FieldType::MultipleStringValue
293 | FieldType::TzTimeOnly
294 | FieldType::TzTimestamp
295 | FieldType::XmlData
296 | FieldType::Language
297 | FieldType::TagNumber
298 | FieldType::XidRef
299 | FieldType::Xid
300 | FieldType::LocalMarketTime => "String",
301 x => unimplemented!("Unsupported FieldType: {x:?}"),
302 };
303
304 output.push_str(&format!("pub type {type_name} = {rust_type};\n\n"));
305}
306
307fn generate_headers(output: &mut String, components: &[SubComponent]) {
308 struct Accessor;
309
310 impl FieldAccessorGenerator for Accessor {
311 fn getter_prefix_text(&self) -> &'static str {
312 "inner.with_header(|x| x."
313 }
314 fn setter_prefix_text(&self) -> &'static str {
315 "inner.with_header_mut(|x| x."
316 }
317 fn caller_suffix_text(&self) -> &'static str {
318 ")"
319 }
320 }
321
322 generate_message_wrapper(output, "Header", components, &Accessor);
323}
324
325fn generate_trailers(output: &mut String, components: &[SubComponent]) {
326 struct Accessor;
327
328 impl FieldAccessorGenerator for Accessor {
329 fn getter_prefix_text(&self) -> &'static str {
330 "inner.with_trailer(|x| x."
331 }
332 fn setter_prefix_text(&self) -> &'static str {
333 "inner.with_trailer_mut(|x| x."
334 }
335 fn caller_suffix_text(&self) -> &'static str {
336 ")"
337 }
338 }
339
340 generate_message_wrapper(output, "Trailer", components, &Accessor);
341}
342
343fn generate_message_wrapper(
344 output: &mut String,
345 struct_name: &str,
346 components: &[SubComponent],
347 accessor: &impl FieldAccessorGenerator,
348) {
349 output.push_str(&format!(
350 r#" #[derive(Debug)]
351 pub struct {struct_name}<'a> {{ inner: &'a quickfix::Message }}
352
353 "#
354 ));
355
356 generate_sub_components(output, &struct_name.to_case(Case::Snake), components);
357
358 output.push_str(&format!("impl {struct_name}<'_> {{\n"));
359 generate_components_getters(output, struct_name, components, accessor);
360 output.push_str("}\n\n");
361
362 output.push_str(&format!(
363 r#" #[derive(Debug)]
364 pub struct {struct_name}Mut<'a> {{ inner: &'a mut quickfix::Message }}
365
366 "#
367 ));
368
369 output.push_str(&format!("impl {struct_name}Mut<'_> {{\n"));
370 generate_components_setters(output, struct_name, components, accessor);
371 output.push_str("}\n\n");
372}
373
374fn generate_messages(output: &mut String, messages: &[MessageSpec]) {
375 for message in messages {
376 generate_message(output, message);
377 }
378}
379
380fn generate_message(output: &mut String, message: &MessageSpec) {
381 let struct_name = message.name.as_str();
382 let msg_type = message.msg_type.as_str();
383
384 output.push_str(&format!(
386 r#" #[derive(Debug, Clone)]
387 pub struct {struct_name} {{
388 inner: quickfix::Message,
389 }}
390
391 impl {struct_name} {{
392 pub const MSG_TYPE_BYTES: &'static str = "{msg_type}";
393 pub const MSG_TYPE: crate::field_types::MsgType =
394 match crate::field_types::MsgType::from_const_bytes(Self::MSG_TYPE_BYTES.as_bytes()) {{
395 Ok(value) => value,
396 Err(_) => panic!("Invalid message type for {struct_name}"),
397 }};
398
399 #[inline(always)]
400 pub fn header(&mut self) -> Header<'_> {{
401 Header {{ inner: &self.inner }}
402 }}
403
404 #[inline(always)]
405 pub fn header_mut(&mut self) -> HeaderMut<'_> {{
406 HeaderMut {{ inner: &mut self.inner }}
407 }}
408
409 #[inline(always)]
410 pub fn trailer(&mut self) -> Trailer<'_> {{
411 Trailer {{ inner: &self.inner }}
412 }}
413
414 #[inline(always)]
415 pub fn trailer_mut(&mut self) -> TrailerMut<'_> {{
416 TrailerMut {{ inner: &mut self.inner }}
417 }}
418
419 /// Convert inner message as FIX text.
420 ///
421 /// This method is only here for debug / tests purposes. Do not use this
422 /// in real production code.
423 #[inline(never)]
424 pub fn to_fix_string(&self) -> String {{
425 self.inner
426 .to_fix_string()
427 .expect("Fail to format {struct_name} message as FIX string")
428 }}
429 }}
430
431 impl From<{struct_name}> for quickfix::Message {{
432 fn from(input: {struct_name}) -> Self {{
433 input.inner
434 }}
435 }}
436
437 impl From<quickfix::Message> for {struct_name} {{
438 fn from(input: quickfix::Message) -> Self {{
439 assert_eq!(
440 input
441 .with_header(|h| h.get_field(field_id::MSG_TYPE))
442 .and_then(|x| crate::field_types::MsgType::from_const_bytes(x.as_bytes()).ok()),
443 Some(Self::MSG_TYPE),
444 );
445 Self {{ inner: input }}
446 }}
447 }}
448
449 "#
450 ));
451
452 let required_params = format_required_params(&message.components);
454 let new_setters = format_new_setters(&message.components);
455
456 output.push_str(&format!(
457 r#" impl {struct_name} {{
458 #[allow(clippy::too_many_arguments)]
459 pub fn try_new({required_params}) -> Result<Self, quickfix::QuickFixError> {{
460 let mut inner = quickfix::Message::new();
461
462 // Set headers (most of them will be set by quickfix library).
463 inner.with_header_mut(|h| {{
464 h.set_field(crate::field_id::BEGIN_STRING, crate::FIX_BEGIN_STRING)
465 }})?;
466 inner.with_header_mut(|h| {{
467 h.set_field(crate::field_id::MSG_TYPE, Self::MSG_TYPE)
468 }})?;
469
470 // Set required attributes.
471 {new_setters}
472
473 Ok(Self {{ inner }})
474 }}
475 }}
476
477 "#
478 ));
479
480 struct Accessor;
482
483 impl FieldAccessorGenerator for Accessor {
484 fn getter_prefix_text(&self) -> &'static str {
485 "inner."
486 }
487 fn setter_prefix_text(&self) -> &'static str {
488 "inner."
489 }
490 fn caller_suffix_text(&self) -> &'static str {
491 ""
492 }
493 }
494
495 generate_sub_components(
496 output,
497 &message.name.to_case(Case::Snake),
498 &message.components,
499 );
500
501 output.push_str(&format!("impl {struct_name} {{\n\n"));
502 generate_components_getters(output, struct_name, &message.components, &Accessor);
503 generate_components_setters(output, struct_name, &message.components, &Accessor);
504 output.push_str("}\n\n");
505}
506
507fn generate_group(output: &mut String, group: &MessageGroup) {
508 let struct_name = group.name.as_str();
509 let group_id = format_field_id(&group.name);
510 let group_delim = format_field_id(
511 group
512 .components
513 .first()
514 .expect("Group cannot be empty")
515 .name(),
516 );
517 let group_value_ids = group
518 .components
519 .iter()
520 .map(|x| format_field_id(x.name()))
521 .collect::<Vec<_>>()
522 .join(",");
523
524 let required_params = format_required_params(&group.components);
526 let new_setters = format_new_setters(&group.components);
527
528 output.push_str(&format!(
529 r#" #[derive(Debug, Clone)]
530 pub struct {struct_name} {{
531 pub(crate) inner: quickfix::Group,
532 }}
533
534 impl {struct_name} {{
535 pub const FIELD_ID: i32 = {group_id};
536 pub const DELIMITER: i32 = {group_delim};
537
538 #[allow(clippy::too_many_arguments)]
539 pub fn try_new({required_params}) -> Result<Self, quickfix::QuickFixError> {{
540 #[allow(unused_mut)]
541 let mut inner = quickfix::Group::try_with_orders(
542 Self::FIELD_ID,
543 Self::DELIMITER,
544 &[{group_value_ids}],
545 ).expect("Fail to build group {struct_name}");
546
547 {new_setters}
548
549 Ok(Self {{ inner }})
550 }}
551 }}
552
553 "#
554 ));
555
556 struct Accessor;
558
559 impl FieldAccessorGenerator for Accessor {
560 fn getter_prefix_text(&self) -> &'static str {
561 "inner."
562 }
563 fn setter_prefix_text(&self) -> &'static str {
564 "inner."
565 }
566 fn caller_suffix_text(&self) -> &'static str {
567 ""
568 }
569 }
570
571 generate_sub_components(output, &group.name.to_case(Case::Snake), &group.components);
572
573 output.push_str(&format!("impl {struct_name} {{\n\n"));
574 generate_components_getters(output, struct_name, &group.components, &Accessor);
575 generate_components_setters(output, struct_name, &group.components, &Accessor);
576 output.push_str("}\n\n");
577}
578
579fn generate_sub_components(output: &mut String, module_name: &str, components: &[SubComponent]) {
580 if components
582 .iter()
583 .any(|x| matches!(x, SubComponent::Group(_)))
584 {
585 output.push_str(&format!(
587 r#" pub mod {module_name} {{
588 use super::*;
589
590 "#
591 ));
592
593 for value in components {
594 match value {
595 SubComponent::Field(_) => {} SubComponent::Group(x) => {
597 generate_group(output, x);
598 }
599 }
600 }
601 output.push_str("}\n\n");
602 }
603}
604
605fn generate_components_getters(
606 output: &mut String,
607 struct_name: &str,
608 components: &[SubComponent],
609 accessor: &impl FieldAccessorGenerator,
610) {
611 for component in components {
612 match component {
613 SubComponent::Field(x) => {
614 generate_field_getter(output, &x.name, x.required, accessor);
615 }
616 SubComponent::Group(x) => {
617 generate_group_reader(output, struct_name, x);
618 }
619 }
620 }
621}
622
623fn generate_components_setters(
624 output: &mut String,
625 struct_name: &str,
626 components: &[SubComponent],
627 accessor: &impl FieldAccessorGenerator,
628) {
629 for component in components {
630 match component {
631 SubComponent::Field(x) => {
632 generate_field_setters(output, x, accessor);
633 }
634 SubComponent::Group(x) => {
635 generate_fn_add_group(output, struct_name, x);
636 }
637 }
638 }
639}
640
641fn generate_field_getter(
642 output: &mut String,
643 field_name: &str,
644 field_required: bool,
645 accessor: &impl FieldAccessorGenerator,
646) {
647 let call_get_prefix = accessor.getter_prefix_text();
649 let call_suffix = accessor.caller_suffix_text();
650
651 let fun_name = format!("get_{}", field_name.to_case(Case::Snake));
652 let field_type = format!("crate::field_types::{field_name}");
653 let field_id = format_field_id(field_name);
654
655 if field_required {
657 output.push_str(&format!(
659 r#" #[inline(always)]
660 pub fn {fun_name}(&self) -> {field_type} {{
661 self.{call_get_prefix}get_field({field_id}){call_suffix}
662 .and_then(|x| x.parse().ok())
663 .expect("{field_id} is required but it is missing")
664 }}
665
666 "#
667 ));
668 } else {
669 output.push_str(&format!(
671 r#" #[inline(always)]
672 pub fn {fun_name}(&self) -> Option<{field_type}> {{
673 self.{call_get_prefix}get_field({field_id}){call_suffix}
674 .and_then(|x| x.parse().ok())
675 }}
676
677 "#
678 ));
679 }
680}
681
682fn generate_field_setters(
683 output: &mut String,
684 field: &MessageField,
685 accessor: &impl FieldAccessorGenerator,
686) {
687 let call_set_prefix = accessor.setter_prefix_text();
689 let call_suffix = accessor.caller_suffix_text();
690
691 let field_name = field.name.to_case(Case::Snake);
692 let field_type = format!("crate::field_types::{}", field.name);
693 let field_id = format_field_id(&field.name);
694
695 output.push_str(&format!(
697 r#" #[inline(always)]
698 pub fn set_{field_name}(&mut self, value: {field_type}) -> Result<&Self, quickfix::QuickFixError> {{
699 self.{call_set_prefix}set_field({field_id}, value){call_suffix}?;
700 Ok(self)
701 }}
702
703 "#
704 ));
705
706 if !field.required {
708 output.push_str(&format!(
709 r#" #[inline(always)]
710 pub fn remove_{field_name}(&mut self) -> Result<&Self, quickfix::QuickFixError> {{
711 self.{call_set_prefix}remove_field({field_id}){call_suffix}?;
712 Ok(self)
713 }}
714
715 "#
716 ));
717 }
718}
719
720fn generate_group_reader(output: &mut String, struct_name: &str, group: &MessageGroup) {
721 let group_name = group.name.to_case(Case::Snake);
723 let group_type = format!("self::{}::{}", struct_name.to_case(Case::Snake), group.name);
724
725 output.push_str(&format!(
727 r#" #[inline(always)]
728 pub fn {group_name}_len(&self) -> usize {{
729 self.inner
730 .get_field({group_type}::FIELD_ID)
731 .and_then(|x| x.parse().ok())
732 .unwrap_or_default()
733 }}
734
735 #[inline(always)]
736 pub fn clone_group_{group_name}(&self, index: usize) -> Option<{group_type}> {{
737 self.inner
738 .clone_group(index as i32, {group_type}::FIELD_ID)
739 .map(|raw_group| {group_type} {{ inner: raw_group }})
740 }}
741
742 #[inline(always)]
743 pub fn iter_{group_name}(&self) -> GroupIterator<'_, Self, {group_type}> {{
744 GroupIterator {{
745 parent: self,
746 clone_group_func: |parent, idx| parent.clone_group_{group_name}(idx),
747 current_index: 0,
748 }}
749 }}
750
751 "#
752 ));
753}
754
755fn generate_fn_add_group(output: &mut String, struct_name: &str, group: &MessageGroup) {
756 let group_name = group.name.to_case(Case::Snake);
758 let group_type = format!("self::{}::{}", struct_name.to_case(Case::Snake), group.name);
759
760 output.push_str(&format!(
762 r#" #[inline(always)]
763 pub fn add_{group_name}(&mut self, value: {group_type}) -> Result<&Self, quickfix::QuickFixError> {{
764 self.inner.add_group(&value.inner)?;
765 Ok(self)
766 }}
767
768 "#
769 ));
770}
771
772fn generate_message_cracker(output: &mut String, messages: &[MessageSpec]) {
773 output.push_str(
775 r#" #[derive(Debug, Clone)]
776 pub enum Messages {
777 "#,
778 );
779 for message in messages {
780 let struct_name = &message.name;
781
782 output.push_str(&format!(" {struct_name}({struct_name}),\n"));
783 }
784 output.push_str(
785 r#" }
786 "#,
787 );
788
789 output.push_str(
791 r#" impl Messages {
792 /// Try decoding input message or return the message if it does not match any known message type.
793 pub fn decode(input: quickfix::Message) -> Result<Self, quickfix::Message> {
794 match input
795 .with_header(|h| h.get_field(crate::field_id::MSG_TYPE))
796 .as_deref()
797 {
798 "#,
799 );
800 for message in messages {
801 let struct_name = &message.name;
802 let message_type = &message.msg_type;
803
804 output.push_str(&format!(
805 " Some(\"{message_type}\") => Ok(Self::{struct_name}(input.into())),\n"
806 ));
807 }
808 output.push_str(
809 r#" _ => Err(input),
810 }
811 }
812 }
813 "#,
814 );
815}
816
817fn format_field_id(input: &str) -> String {
818 format!("crate::field_id::{}", input.to_case(Case::Constant))
819}
820
821fn format_required_params(components: &[SubComponent]) -> String {
822 components
823 .iter()
824 .filter(|x| x.is_required())
825 .map(|x| {
826 let name = x.name();
827 let param_name = name.to_case(Case::Snake);
828 format!("{param_name}: crate::field_types::{name}")
829 })
830 .collect::<Vec<_>>()
831 .join(", ")
832}
833
834fn format_new_setters(components: &[SubComponent]) -> String {
835 components
836 .iter()
837 .filter(|x| x.is_required())
838 .map(|x| {
839 let name = x.name();
840 let field_id = format_field_id(name);
841 let param_name = name.to_case(Case::Snake);
842 format!("inner.set_field({field_id}, {param_name})?;")
843 })
844 .collect::<Vec<_>>()
845 .join("\n")
846}