krabka_protocol_codegen/emit/
mod_rs.rs1use quote::{format_ident, quote};
4
5use crate::{
6 emit::{
7 EmitError,
8 common::{banner, validate},
9 wrappers::Flavor,
10 },
11 ir::MessageSpec,
12 name_conv,
13};
14
15pub fn emit(
26 specs: &[&MessageSpec],
27 flavor: Flavor,
28 schemas_version: &str,
29 has_common: bool,
30) -> Result<String, EmitError> {
31 let flavor_comment = match flavor {
32 Flavor::Owned => "Owned (heap-allocated) message types.",
33 Flavor::Borrowed => "Borrowed-flavor generated message types.",
34 };
35 let banner = banner(schemas_version);
36
37 let mut entries: Vec<String> = specs
39 .iter()
40 .filter(|s| !s.valid_versions.is_empty())
41 .map(|s| name_conv::module_name(&s.name))
42 .collect();
43 if has_common {
45 entries.push("common".to_owned());
46 }
47 entries.sort();
48 entries.dedup();
49
50 let doc = format!(" {flavor_comment}");
51 let mods = entries.iter().map(|s| {
52 let id = format_ident!("{s}");
53 quote!(pub mod #id;)
54 });
55 let tokens = quote! {
56 #![doc = #doc]
57 #(#mods)*
58 };
59
60 validate::<syn::File>(&tokens, "mod.rs")?;
62
63 Ok(format!("{banner}{tokens}"))
64}
65
66#[cfg(test)]
67mod tests {
68 use assert2::assert;
69
70 use super::*;
71 use crate::ir::{FlexibleVersions, MessageSpec, MessageType, VersionRange};
72
73 fn make_spec(name: &str, min: i16, max: i16, ty: MessageType) -> MessageSpec {
74 MessageSpec {
75 name: name.to_owned(),
76 message_type: ty,
77 api_key: None,
78 valid_versions: VersionRange { min, max },
79 latest_version_unstable: false,
80 flexible_versions: FlexibleVersions::None,
81 fields: vec![],
82 common_structs: vec![],
83 internal: false,
84 }
85 }
86
87 #[test]
88 fn owned_mod_rs_sorted_alphabetically() {
89 let specs = [
90 make_spec("ProduceRequest", 0, 10, MessageType::Request),
91 make_spec("ApiVersionsRequest", 0, 3, MessageType::Request),
92 make_spec("MetadataRequest", 0, 13, MessageType::Request),
93 ];
94 let refs: Vec<&MessageSpec> = specs.iter().collect();
95 let out = emit(&refs, Flavor::Owned, "test-sha", false).unwrap();
96 let api_pos = out.find("pub mod api_versions_request").unwrap();
100 let meta_pos = out.find("pub mod metadata_request").unwrap();
101 let prod_pos = out.find("pub mod produce_request").unwrap();
102 assert!(api_pos < meta_pos, "api before metadata");
103 assert!(meta_pos < prod_pos, "metadata before produce");
104 }
105
106 #[test]
107 fn skips_specs_with_empty_valid_versions() {
108 let specs = [
109 make_spec("ActiveRequest", 0, 5, MessageType::Request),
110 make_spec("RemovedRequest", i16::MAX, i16::MIN, MessageType::Request),
111 ];
112 let refs: Vec<&MessageSpec> = specs.iter().collect();
113 let out = emit(&refs, Flavor::Owned, "test-sha", false).unwrap();
114 assert!(out.contains("pub mod active_request"));
115 assert!(!out.contains("pub mod removed_request"));
116 }
117
118 #[test]
119 fn borrowed_flavor_has_correct_doc_comment() {
120 let refs: Vec<&MessageSpec> = vec![];
121 let out = emit(&refs, Flavor::Borrowed, "sha123", false).unwrap();
122 assert!(out.contains("Borrowed-flavor generated message types."));
125 }
126}