1use std::collections::HashMap;
8
9use proc_macro2::{Literal, TokenStream};
10use quote::{format_ident, quote};
11
12use crate::{
13 emit::common::format_int_literal,
14 ir::{FieldSpec, FlexibleVersions, MessageSpec, MessageType, VersionRange},
15 name_conv,
16 resolve::{self, Resolution},
17};
18
19#[derive(Debug, thiserror::Error)]
20pub enum EmitError {
21 #[error("unsupported (in 1a): {0}")]
22 Unsupported(String),
23 #[error("resolve error: {0}")]
24 Resolve(#[from] resolve::ResolveError),
25 #[error("an emitter produced the unlexable fragment `{fragment}`: {message}")]
26 Lex { fragment: String, message: String },
27 #[error("the generated {what} is not valid Rust: {source}")]
28 Syntax {
29 what: &'static str,
30 #[source]
31 source: syn::Error,
32 },
33 #[error("{0} must carry an apiKey in its schema")]
34 MissingApiKey(String),
35 #[error("tagged field `{0}` has no tag")]
36 MissingTag(String),
37 #[error("struct field `{0}` does not resolve to a type")]
38 Unresolved(String),
39}
40
41pub(crate) fn emit_common_imports(fields: &[FieldSpec], flex_min_val: i16) -> TokenStream {
48 let types = used_field_types_recursive(fields);
49 let has_flex = flex_min_val < i16::MAX;
50 let tagged = fields.iter().any(|f| f.tag.is_some());
51 let use_string = uses_string(&types);
52 let use_bytes = uses_bytes(&types);
53 let use_nullable_struct = uses_nullable_struct_recursive(fields);
54
55 let mut out = quote!(
56 use bytes::{Buf, BufMut};
57 );
58
59 {
60 let mut gets: Vec<&str> = Vec::new();
61 let mut puts: Vec<&str> = Vec::new();
62 for (t, g, p) in &[
63 ("int8", "get_i8", "put_i8"),
64 ("int16", "get_i16", "put_i16"),
65 ("uint16", "get_u16", "put_u16"),
66 ("int32", "get_i32", "put_i32"),
67 ("int64", "get_i64", "put_i64"),
68 ("bool", "get_bool", "put_bool"),
69 ("float64", "get_f64", "put_f64"),
70 ] {
71 if uses_fixed_type(&types, t) {
72 gets.push(g);
73 puts.push(p);
74 }
75 }
76 if use_nullable_struct && !gets.contains(&"get_i8") {
77 gets.push("get_i8");
79 }
80 if !gets.is_empty() {
81 let mut combined: Vec<&str> = gets.into_iter().chain(puts).collect();
82 combined.sort_unstable();
83 combined.dedup();
84 let items: Vec<_> = combined.iter().map(|n| format_ident!("{n}")).collect();
85 out.extend(quote!(use crate::primitives::fixed::{ #(#items),* };));
86 }
87 }
88
89 if use_string {
90 out.extend(string_import(uses_nullable_string_recursive(fields)));
91 }
92
93 if use_bytes {
94 out.extend(bytes_import(
95 uses_non_nullable_bytes_recursive(fields),
96 uses_nullable_bytes_recursive(fields),
97 ));
98 }
99
100 out.extend(tagged_import(has_flex, tagged));
101
102 out.extend(quote!(
103 use crate::{Decode, Encode, ProtocolError, UnknownTaggedFields};
104 ));
105 out
106}
107
108fn string_import(use_nullable_string: bool) -> TokenStream {
111 let names: &[&str] = if use_nullable_string {
112 &[
113 "compact_nullable_string_len",
114 "compact_string_len",
115 "get_compact_nullable_string_owned",
116 "get_compact_string_owned",
117 "get_nullable_string_owned",
118 "get_string_owned",
119 "nullable_string_len",
120 "put_compact_nullable_string",
121 "put_compact_string",
122 "put_nullable_string",
123 "put_string",
124 "string_len",
125 ]
126 } else {
127 &[
128 "compact_string_len",
129 "get_compact_string_owned",
130 "get_string_owned",
131 "put_compact_string",
132 "put_string",
133 "string_len",
134 ]
135 };
136 let items: Vec<_> = names.iter().map(|n| format_ident!("{n}")).collect();
137 quote!(use crate::primitives::string_bytes::{ #(#items),* };)
138}
139
140fn bytes_import(use_non_nullable_bytes: bool, use_nullable_bytes: bool) -> TokenStream {
142 let mut items: Vec<&str> = Vec::new();
143 if use_non_nullable_bytes {
144 items.extend([
145 "bytes_len",
146 "compact_bytes_len",
147 "get_bytes_owned",
148 "get_compact_bytes_owned",
149 "put_bytes",
150 "put_compact_bytes",
151 ]);
152 }
153 if use_nullable_bytes {
154 items.extend([
155 "compact_nullable_bytes_len",
156 "get_compact_nullable_bytes_owned",
157 "get_nullable_bytes_owned",
158 "nullable_bytes_len",
159 "put_compact_nullable_bytes",
160 "put_nullable_bytes",
161 ]);
162 }
163 items.sort_unstable();
164 let idents: Vec<_> = items.iter().map(|n| format_ident!("{n}")).collect();
165 quote!(use crate::primitives::string_bytes::{ #(#idents),* };)
166}
167
168fn tagged_import(flex: bool, tagged: bool) -> TokenStream {
171 if flex && tagged {
172 quote!(
173 use crate::tagged_fields::{
174 encode_to_bytes, read_tagged_fields, tagged_fields_len, WriteTaggedFields,
175 };
176 )
177 } else if flex {
178 quote!(
179 use crate::tagged_fields::{read_tagged_fields, tagged_fields_len, WriteTaggedFields};
180 )
181 } else {
182 quote!()
183 }
184}
185
186pub(crate) fn flex_min(spec: &MessageSpec) -> i16 {
187 match spec.flexible_versions {
188 FlexibleVersions::Range(r) => r.min,
189 FlexibleVersions::None => i16::MAX,
190 }
191}
192
193pub(crate) fn base_type(t: &str) -> &str {
194 t.strip_prefix("[]").unwrap_or(t)
195}
196
197fn used_field_types_recursive(fields: &[FieldSpec]) -> Vec<String> {
200 let mut types: Vec<String> = Vec::new();
201 for f in fields {
202 let base = base_type(&f.field_type).to_owned();
203 if !types.contains(&base) {
204 types.push(base);
205 }
206 if !f.fields.is_empty() {
207 for t in used_field_types_recursive(&f.fields) {
208 if !types.contains(&t) {
209 types.push(t);
210 }
211 }
212 }
213 }
214 types
215}
216
217fn has_tagged_fields_recursive(fields: &[FieldSpec]) -> bool {
218 fields
219 .iter()
220 .any(|f| f.tag.is_some() || has_tagged_fields_recursive(&f.fields))
221}
222
223pub(crate) fn has_float64_recursive(fields: &[FieldSpec]) -> bool {
227 fields.iter().any(|f| {
228 let base = base_type(&f.field_type);
229 base == "float64" || has_float64_recursive(&f.fields)
230 })
231}
232
233fn uses_fixed_type(types: &[String], t: &str) -> bool {
234 types.iter().any(|s| s == t)
235}
236
237fn uses_string(types: &[String]) -> bool {
238 types.iter().any(|t| t == "string")
239}
240
241fn uses_bytes(types: &[String]) -> bool {
242 types.iter().any(|t| t.as_str() == "bytes")
243}
244
245fn uses_nullable_bytes_recursive(fields: &[FieldSpec]) -> bool {
246 fields.iter().any(|f| {
247 let base = base_type(&f.field_type);
248 let here = base == "bytes" && f.nullable_versions.is_some();
249 here || uses_nullable_bytes_recursive(&f.fields)
250 })
251}
252
253fn uses_non_nullable_bytes_recursive(fields: &[FieldSpec]) -> bool {
254 fields.iter().any(|f| {
255 let base = base_type(&f.field_type);
256 let here = base == "bytes" && needs_non_nullable_codec(f);
257 here || uses_non_nullable_bytes_recursive(&f.fields)
258 })
259}
260
261fn needs_non_nullable_codec(f: &FieldSpec) -> bool {
266 f.nullable_versions.is_none() || nullable_split_cond(f).is_some()
267}
268
269fn uses_nullable_records_recursive(fields: &[FieldSpec]) -> bool {
270 fields.iter().any(|f| {
271 let base = base_type(&f.field_type);
272 let here = base == "records" && f.nullable_versions.is_some();
273 here || uses_nullable_records_recursive(&f.fields)
274 })
275}
276
277fn uses_non_nullable_records_recursive(fields: &[FieldSpec]) -> bool {
278 fields.iter().any(|f| {
279 let base = base_type(&f.field_type);
280 let here = base == "records" && needs_non_nullable_codec(f);
281 here || uses_non_nullable_records_recursive(&f.fields)
282 })
283}
284
285fn uses_nullable_string_recursive(fields: &[FieldSpec]) -> bool {
288 fields.iter().any(|f| {
289 let base = base_type(&f.field_type);
290 let here = base == "string" && (f.nullable_versions.is_some() || f.tag.is_some());
291 here || uses_nullable_string_recursive(&f.fields)
292 })
293}
294
295fn uses_nullable_struct_recursive(fields: &[FieldSpec]) -> bool {
298 fields.iter().any(|f| {
299 let t = &f.field_type;
300 let base = base_type(t);
301 let here = is_struct_type(base) && !t.starts_with("[]") && f.nullable_versions.is_some();
302 here || uses_nullable_struct_recursive(&f.fields)
303 })
304}
305
306pub(crate) fn has_any_flex(spec: &MessageSpec) -> bool {
307 matches!(spec.flexible_versions, FlexibleVersions::Range(_))
308}
309
310fn has_any_tagged_in_spec(spec: &MessageSpec) -> bool {
311 has_tagged_fields_recursive(&spec.fields)
312}
313
314pub(crate) fn emit_imports(spec: &MessageSpec) -> TokenStream {
315 let types = used_field_types_recursive(&spec.fields);
316 let tagged = has_any_tagged_in_spec(spec);
317 let flex = has_any_flex(spec);
318 let use_string = uses_string(&types);
319 let use_bytes = uses_bytes(&types);
320 let use_nullable_string = uses_nullable_string_recursive(&spec.fields);
321 let use_nullable_bytes = uses_nullable_bytes_recursive(&spec.fields);
322 let use_non_nullable_bytes = uses_non_nullable_bytes_recursive(&spec.fields);
323
324 let mut out = quote!(
325 use bytes::{Buf, BufMut};
326 );
327
328 let use_nullable_struct = uses_nullable_struct_recursive(&spec.fields);
329
330 {
332 let mut gets: Vec<&str> = Vec::new();
333 let mut puts: Vec<&str> = Vec::new();
334 if uses_fixed_type(&types, "int8") {
335 gets.push("get_i8");
336 puts.push("put_i8");
337 } else if use_nullable_struct {
338 gets.push("get_i8");
340 }
341 if uses_fixed_type(&types, "int16") {
342 gets.push("get_i16");
343 puts.push("put_i16");
344 }
345 if uses_fixed_type(&types, "uint16") {
346 gets.push("get_u16");
347 puts.push("put_u16");
348 }
349 if uses_fixed_type(&types, "int32") {
350 gets.push("get_i32");
351 puts.push("put_i32");
352 }
353 if uses_fixed_type(&types, "int64") {
354 gets.push("get_i64");
355 puts.push("put_i64");
356 }
357 if uses_fixed_type(&types, "bool") {
358 gets.push("get_bool");
359 puts.push("put_bool");
360 }
361 if uses_fixed_type(&types, "float64") {
362 gets.push("get_f64");
363 puts.push("put_f64");
364 }
365 if !gets.is_empty() {
366 let combined: Vec<&str> = gets.into_iter().chain(puts).collect();
367 let sorted = {
368 let mut v = combined;
369 v.sort_unstable();
370 v
371 };
372 let items: Vec<_> = sorted.iter().map(|n| format_ident!("{n}")).collect();
373 out.extend(quote!(use crate::primitives::fixed::{ #(#items),* };));
374 }
375 }
376
377 if use_string {
378 out.extend(string_import(use_nullable_string));
381 }
382
383 if use_bytes {
384 out.extend(bytes_import(use_non_nullable_bytes, use_nullable_bytes));
385 }
386
387 {
389 let use_nullable_records = uses_nullable_records_recursive(&spec.fields);
390 let use_non_nullable_records = uses_non_nullable_records_recursive(&spec.fields);
391 if use_nullable_records || use_non_nullable_records {
392 let mut items: Vec<&str> = Vec::new();
394 if use_non_nullable_records {
395 items.extend([
396 "get_bytes_owned",
397 "get_compact_bytes_owned",
398 "put_bytes",
399 "put_compact_bytes",
400 ]);
401 }
402 if use_nullable_records {
403 items.extend([
404 "get_compact_nullable_bytes_owned",
405 "get_nullable_bytes_owned",
406 "put_compact_bytes",
407 "put_compact_nullable_bytes",
408 "put_bytes",
409 "put_nullable_bytes",
410 ]);
411 }
412 items.sort_unstable();
413 items.dedup();
414 let idents: Vec<_> = items.iter().map(|n| format_ident!("{n}")).collect();
415 out.extend(quote!(use crate::primitives::string_bytes::{ #(#idents),* };));
416 }
417 }
418
419 out.extend(tagged_import(flex, tagged));
421
422 out.extend(quote!(
423 use crate::{Decode, Encode, ProtocolError, UnknownTaggedFields};
424 ));
425 out
426}
427
428pub(crate) fn emit_constants(spec: &MessageSpec) -> Result<TokenStream, EmitError> {
429 let min_version = Literal::i16_unsuffixed(spec.valid_versions.min);
430 let max_version = Literal::i16_unsuffixed(spec.valid_versions.max);
431 let flex_minimum = flex_min(spec);
432 let flex = Literal::i16_unsuffixed(flex_minimum);
433 let flex_check = match flex_minimum {
434 i16::MIN => quote!(true),
435 i16::MAX => quote!(version == FLEXIBLE_MIN),
436 _ => quote!(version >= FLEXIBLE_MIN),
437 };
438 let api_key_const = match spec.message_type {
440 MessageType::Request | MessageType::Response => {
441 let api_key = spec
442 .api_key
443 .map(Literal::i16_unsuffixed)
444 .ok_or_else(|| EmitError::MissingApiKey(spec.name.clone()))?;
445 quote!(pub const API_KEY: i16 = #api_key;)
446 }
447 MessageType::Header | MessageType::Data => quote!(),
449 };
450 let latest_stable_const = match spec.message_type {
453 MessageType::Request => {
454 let latest_stable = Literal::i16_unsuffixed(spec.latest_stable_version());
455 quote!(pub const LATEST_STABLE_VERSION: i16 = #latest_stable;)
456 }
457 MessageType::Response | MessageType::Header | MessageType::Data => quote!(),
458 };
459 Ok(quote! {
460 #api_key_const
461 pub const MIN_VERSION: i16 = #min_version;
462 pub const MAX_VERSION: i16 = #max_version;
463 #latest_stable_const
464 pub const FLEXIBLE_MIN: i16 = #flex;
465
466 #[inline]
467 #[must_use]
468 pub fn is_flexible(version: i16) -> bool { #flex_check }
469 })
470}
471
472pub(crate) fn owned_default_expr(f: &FieldSpec, res_map: &HashMap<String, Resolution>) -> String {
482 let base = base_type(&f.field_type);
483 let is_array = f.field_type.starts_with("[]");
484 let nullable = is_nullable(f) || matches!(&f.default, Some(serde_json::Value::Null));
485 let default_is_null = matches!(&f.default, Some(serde_json::Value::Null))
487 || matches!(&f.default, Some(serde_json::Value::String(s)) if s == "null");
488
489 if base == "records" && nullable {
500 return "None".into();
501 }
502
503 if nullable && default_is_null {
504 return "None".into();
505 }
506
507 let inner = if is_array {
508 "Vec::new()".into()
509 } else if f.default.is_none()
510 && let Some(resolution) = res_map.get(base)
511 {
512 format!("{}::default()", resolution.rust_path)
513 } else {
514 f.default
515 .as_ref()
516 .map_or_else(|| owned_zero(base), |v| scalar_owned_default(base, v))
517 };
518
519 if nullable {
520 format!("Some({inner})")
521 } else {
522 inner
523 }
524}
525
526fn scalar_owned_default(base_type: &str, val: &serde_json::Value) -> String {
527 match (base_type, val) {
530 (_, serde_json::Value::String(s)) if s == "null" => "None".into(),
532 ("string", serde_json::Value::String(s)) if s.is_empty() => "String::new()".into(),
533 ("string", serde_json::Value::String(s)) => format!("{s:?}.to_owned()"),
534 ("bool", serde_json::Value::String(s)) if s == "true" => "true".into(),
535 ("bool", serde_json::Value::String(_)) => "false".into(),
536 ("bool", serde_json::Value::Bool(b)) => b.to_string(),
537 ("int8", serde_json::Value::String(s)) => format!("{}i8", s.trim()),
538 ("int16", serde_json::Value::String(s)) => format!("{}i16", s.trim()),
539 ("int32", serde_json::Value::String(s)) => format_int_literal(s.trim(), "i32"),
541 ("int64", serde_json::Value::String(s)) => format_int_literal(s.trim(), "i64"),
542 ("int8", serde_json::Value::Number(n)) => format!("{n}i8"),
543 ("int16", serde_json::Value::Number(n)) => format!("{n}i16"),
544 ("int32", serde_json::Value::Number(n)) => format_int_literal(&n.to_string(), "i32"),
545 ("int64", serde_json::Value::Number(n)) => format_int_literal(&n.to_string(), "i64"),
546 _ => owned_zero(base_type),
547 }
548}
549
550fn owned_zero(base: &str) -> String {
551 match base {
552 "string" => "String::new()".into(),
553 "bytes" => "bytes::Bytes::new()".into(),
554 "bool" => "false".into(),
555 "int8" => "0i8".into(),
556 "int16" => "0i16".into(),
557 "int32" => "0i32".into(),
558 "int64" => "0i64".into(),
559 "uint16" => "0u16".into(),
560 "uint32" => "0u32".into(),
561 "float64" => "0.0f64".into(),
562 "uuid" => "crate::primitives::uuid::Uuid::default()".into(),
563 "records" => "crate::records::RecordsPayload::default()".into(),
564 _ => "Default::default()".into(),
565 }
566}
567
568fn parse_string_default_as_i64(s: &str) -> Option<i64> {
570 s.trim().parse::<i64>().ok()
571}
572
573pub(crate) fn needs_manual_default(fields: &[FieldSpec]) -> bool {
581 fields.iter().any(|f| {
582 let nullable = is_nullable(f) || matches!(&f.default, Some(serde_json::Value::Null));
583 let default_is_null = matches!(&f.default, Some(serde_json::Value::Null))
584 || matches!(&f.default, Some(serde_json::Value::String(s)) if s == "null");
585 if nullable {
586 return !default_is_null;
587 }
588 match &f.default {
589 None | Some(serde_json::Value::Null | serde_json::Value::Bool(false)) => false,
592 Some(serde_json::Value::String(s)) if s == "null" => false,
593 Some(serde_json::Value::String(s)) if s == "false" => false,
594 Some(serde_json::Value::String(s)) if s.is_empty() => false,
595 Some(serde_json::Value::Number(n)) if n.as_i64() == Some(0) => false,
596 Some(serde_json::Value::String(s)) if parse_string_default_as_i64(s) == Some(0) => {
597 false
598 }
599 Some(_) => true,
600 }
601 })
602}
603
604pub(crate) fn struct_path_for(
606 f: &FieldSpec,
607 res_map: &HashMap<String, Resolution>,
608) -> Option<String> {
609 let base = base_type(&f.field_type);
610 if is_struct_type(base) {
611 res_map.get(base).map(|r| r.rust_path.clone())
612 } else {
613 None
614 }
615}
616
617pub(crate) fn is_struct_type(t: &str) -> bool {
618 t.chars().next().is_some_and(char::is_uppercase)
619}
620
621pub(crate) fn owned_populated_value(
625 f: &FieldSpec,
626 res_map: &HashMap<String, Resolution>,
627 option: bool,
628) -> Result<String, EmitError> {
629 let base = base_type(&f.field_type);
630 let is_array = f.field_type.starts_with("[]");
631 if base == "records" {
634 return Ok(owned_default_expr(f, res_map));
635 }
636 let elem = owned_populated_scalar(base, f, res_map)?;
637 let inner = if is_array {
638 format!("vec![{elem}]")
639 } else {
640 elem
641 };
642 Ok(if option {
643 format!("Some({inner})")
644 } else {
645 inner
646 })
647}
648
649fn owned_populated_scalar(
650 base: &str,
651 f: &FieldSpec,
652 res_map: &HashMap<String, Resolution>,
653) -> Result<String, EmitError> {
654 Ok(match base {
655 "bool" => "true".to_owned(),
656 "int8" => "1i8".to_owned(),
657 "int16" => "1i16".to_owned(),
658 "int32" => "1i32".to_owned(),
659 "int64" => "1i64".to_owned(),
660 "uint16" => "1u16".to_owned(),
661 "uint32" => "1u32".to_owned(),
662 "float64" => "1.0f64".to_owned(),
663 "string" => "\"x\".to_owned()".to_owned(),
664 "bytes" => "::bytes::Bytes::from_static(b\"x\")".to_owned(),
665 "uuid" => "crate::primitives::uuid::Uuid([1u8; 16])".to_owned(),
666 _ => {
667 let path =
668 struct_path_for(f, res_map).ok_or_else(|| EmitError::Unresolved(f.name.clone()))?;
669 format!("{path}::populated(version)")
670 }
671 })
672}
673
674pub(crate) fn field_forces_non_flex(f: &FieldSpec) -> bool {
680 matches!(f.flexible_versions, Some(FlexibleVersions::None))
681}
682
683pub(crate) fn wrap_non_nullable_for_option(
687 _schema_type: &str,
688 non_nullable_call: &str,
689 _res_map: &HashMap<String, Resolution>,
690) -> String {
691 format!("Some({non_nullable_call})")
694}
695
696pub(crate) fn tagged_is_default_cond(f: &FieldSpec) -> String {
701 let field = name_conv::field_name(&f.name);
702 let base = base_type(&f.field_type);
703 let is_array = f.field_type.starts_with("[]");
704 let nullable = is_nullable(f) || matches!(&f.default, Some(serde_json::Value::Null));
705 let default_is_null = matches!(&f.default, Some(serde_json::Value::Null))
706 || matches!(&f.default, Some(serde_json::Value::String(s)) if s == "null");
707
708 if base == "records" && nullable {
711 return format!("self.{field}.is_none()");
712 }
713 if nullable && default_is_null {
714 return format!("self.{field}.is_none()");
716 }
717 if nullable && f.default.is_none() {
718 let inner = if is_array {
724 "Vec::new()".to_owned()
725 } else {
726 owned_zero(base)
727 };
728 return format!("self.{field} == Some({inner})");
729 }
730 if let Some(v) = &f.default {
731 let cmp_val = scalar_owned_default(base, v);
733 if cmp_val == "None" {
734 return format!("self.{field}.is_none()");
735 }
736 if nullable {
738 return format!("self.{field} == Some({cmp_val})");
739 }
740 if f.field_type.starts_with("[]") {
742 return format!("self.{field}.is_empty()");
743 }
744 return format!("self.{field} == {cmp_val}");
745 }
746 format!("crate::codegen_helpers::is_default(&self.{field})")
748}
749
750pub(crate) fn encode_call_option_as_non_nullable(schema_type: &str, expr: &str) -> String {
757 if let Some(elem) = schema_type.strip_prefix("[]") {
758 let elem_base = base_type(elem);
759 if is_struct_type(elem_base) {
760 return format!(
762 "{{ let v = ({expr}).as_deref().unwrap_or(&[]); \
763 crate::primitives::array::put_array_len(buf, v.len(), flex)?; \
764 for it in v {{ it.encode(buf, version)?; }} }}",
765 );
766 }
767 return format!(
769 "{{ let v = ({expr}).as_deref().unwrap_or(&[]); \
770 crate::primitives::array::put_array_len(buf, v.len(), flex)?; \
771 for it in v {{ {inner}; }} }}",
772 inner = encode_call(elem, "it", false),
773 );
774 }
775 match schema_type {
777 "string" => format!(
778 "if flex {{ put_compact_string(buf, ({expr}).as_deref().unwrap_or(\"\"))?; }} \
779 else {{ put_string(buf, ({expr}).as_deref().unwrap_or(\"\"))?; }}"
780 ),
781 "uuid" => format!("crate::primitives::uuid::put_uuid(buf, ({expr}).unwrap_or_default())"),
782 "records" => format!(
785 "match &{expr} {{ \
786 None => {{ let __rb_buf = bytes::BytesMut::new(); if flex {{ put_compact_bytes(buf, &__rb_buf)?; }} else {{ put_bytes(buf, &__rb_buf)?; }} }}, \
787 Some(__rb) => __rb.encode_length_prefixed(buf, version, flex)?, \
788 }}"
789 ),
790 _ => encode_call(schema_type, &format!("({expr}).unwrap_or_default()"), false),
791 }
792}
793
794pub(crate) fn encoded_len_expr_option_as_non_nullable(schema_type: &str, expr: &str) -> String {
797 if let Some(elem) = schema_type.strip_prefix("[]") {
798 let elem_base = base_type(elem);
799 if is_struct_type(elem_base) {
800 return format!(
801 "{{ let v = ({expr}).as_deref().unwrap_or(&[]); \
802 let prefix = crate::primitives::array::array_len_prefix_len(v.len(), flex); \
803 let body: usize = v.iter().map(|it| it.encoded_len(version)).sum(); \
804 prefix + body }}",
805 );
806 }
807 return format!(
808 "{{ let v = ({expr}).as_deref().unwrap_or(&[]); \
809 let prefix = crate::primitives::array::array_len_prefix_len(v.len(), flex); \
810 let body: usize = v.iter().map(|it| {inner}).sum(); \
811 prefix + body }}",
812 inner = encoded_len_expr(elem, "*it", false).replace("&*it", "it"),
813 );
814 }
815 match schema_type {
816 "string" => format!(
817 "if flex {{ compact_string_len(({expr}).as_deref().unwrap_or(\"\")) }} \
818 else {{ string_len(({expr}).as_deref().unwrap_or(\"\")) }}"
819 ),
820 "uuid" => "16".into(),
821 "records" => format!(
822 "({expr}).as_ref().map_or_else( \
823 || if flex {{ crate::primitives::string_bytes::compact_bytes_len_from_size(0) }} else {{ 4 }}, \
824 |__rb| {{ let __rb_len = <crate::records::RecordsPayload as crate::Encode>::encoded_len(__rb, version); if flex {{ crate::primitives::string_bytes::compact_bytes_len_from_size(__rb_len) }} else {{ 4 + __rb_len }} }})"
825 ),
826 _ => encoded_len_expr(schema_type, &format!("({expr}).unwrap_or_default()"), false),
827 }
828}
829
830pub(crate) fn encode_call_with_buf(
834 schema_type: &str,
835 expr: &str,
836 nullable: bool,
837 buf_var: &str,
838) -> String {
839 let base = encode_call(schema_type, expr, nullable);
841 base.replace("buf", buf_var)
843}
844
845pub(crate) fn decode_call_with_buf(
847 schema_type: &str,
848 nullable: bool,
849 res_map: &HashMap<String, Resolution>,
850 buf_var: &str,
851 lenient_records: bool,
852) -> String {
853 let base = decode_call(schema_type, nullable, res_map, lenient_records);
854 base.replace("buf", buf_var)
855}
856
857fn array_element_encode(elem: &str) -> String {
861 let call = encode_call(elem, "*it", false).replace("&*it", "it");
862 if call.starts_with("if ") {
863 call
864 } else {
865 format!("{call};")
866 }
867}
868
869pub(crate) fn encode_call(schema_type: &str, expr: &str, nullable: bool) -> String {
872 if let Some(elem) = schema_type.strip_prefix("[]") {
873 let elem_base = base_type(elem); if is_struct_type(elem_base) {
875 if nullable {
877 return format!(
878 "{{ let len = ({expr}).as_ref().map(Vec::len); \
879 crate::primitives::array::put_nullable_array_len(buf, len, flex)?; \
880 if let Some(v) = &{expr} {{ for it in v {{ it.encode(buf, version)?; }} }} }}",
881 );
882 }
883 return format!(
884 "{{ crate::primitives::array::put_array_len(buf, ({expr}).len(), flex)?; \
885 for it in &{expr} {{ it.encode(buf, version)?; }} }}",
886 );
887 }
888 if nullable {
889 return format!(
890 "{{ let len = ({expr}).as_ref().map(Vec::len); \
891 crate::primitives::array::put_nullable_array_len(buf, len, flex)?; \
892 if let Some(v) = &{expr} {{ for it in v {{ {inner} }} }} }}",
893 inner = array_element_encode(elem),
894 );
895 }
896 return format!(
897 "{{ crate::primitives::array::put_array_len(buf, ({expr}).len(), flex)?; \
898 for it in &{expr} {{ {inner} }} }}",
899 inner = array_element_encode(elem),
900 );
901 }
902
903 if is_struct_type(schema_type) {
904 if nullable {
907 return format!(
908 "match &{expr} {{ \
909 None => {{ buf.put_i8(-1); }}, \
910 Some(v) => {{ buf.put_i8(1); v.encode(buf, version)?; }} \
911 }}"
912 );
913 }
914 return format!("{expr}.encode(buf, version)?");
915 }
916
917 match (schema_type, nullable) {
918 ("int8", _) => format!("put_i8(buf, {expr})"),
919 ("int16", _) => format!("put_i16(buf, {expr})"),
920 ("uint16", _) => format!("put_u16(buf, {expr})"),
921 ("int32", _) => format!("put_i32(buf, {expr})"),
922 ("int64", _) => format!("put_i64(buf, {expr})"),
923 ("bool", _) => format!("put_bool(buf, {expr})"),
924 ("float64", _) => format!("put_f64(buf, {expr})"),
925 ("uuid", _) => format!("crate::primitives::uuid::put_uuid(buf, {expr})"),
926 ("string", false) => format!(
927 "if flex {{ put_compact_string(buf, &{expr})?; }} else {{ put_string(buf, &{expr})?; }}"
928 ),
929 ("string", true) => format!(
930 "if flex {{ put_compact_nullable_string(buf, {expr}.as_deref())?; }} else {{ put_nullable_string(buf, {expr}.as_deref())?; }}"
931 ),
932 ("bytes", false) => format!(
933 "if flex {{ put_compact_bytes(buf, &{expr})?; }} else {{ put_bytes(buf, &{expr})?; }}"
934 ),
935 ("bytes", true) => format!(
936 "if flex {{ put_compact_nullable_bytes(buf, {expr}.as_deref())?; }} else {{ put_nullable_bytes(buf, {expr}.as_deref())?; }}"
937 ),
938 ("records", false) => format!("{expr}.encode_length_prefixed(buf, version, flex)?"),
939 ("records", true) => format!(
940 "match &{expr} {{ \
941 None => if flex {{ put_compact_nullable_bytes(buf, None)?; }} else {{ put_nullable_bytes(buf, None)?; }}, \
942 Some(__rb) => __rb.encode_length_prefixed(buf, version, flex)?, \
943 }}"
944 ),
945 (t, _) => format!("compile_error!(\"unhandled type in encode_call: {t}\")"),
946 }
947}
948
949pub(crate) fn encoded_len_expr(schema_type: &str, expr: &str, nullable: bool) -> String {
950 if let Some(elem) = schema_type.strip_prefix("[]") {
951 let elem_base = base_type(elem);
952 if is_struct_type(elem_base) {
953 if nullable {
955 return format!(
956 "{{ let opt: Option<&Vec<_>> = ({expr}).as_ref(); \
957 let prefix = crate::primitives::array::nullable_array_len_prefix_len(opt.map(std::vec::Vec::len), flex); \
958 let body: usize = opt.map_or(0, |v| v.iter().map(|it| it.encoded_len(version)).sum()); \
959 prefix + body }}",
960 );
961 }
962 return format!(
963 "{{ let prefix = crate::primitives::array::array_len_prefix_len(({expr}).len(), flex); \
964 let body: usize = ({expr}).iter().map(|it| it.encoded_len(version)).sum(); \
965 prefix + body }}",
966 );
967 }
968 {
969 let inner = encoded_len_expr(elem, "*it", false);
970 let closure_arg = if inner.contains("*it") { "it" } else { "_" };
973 let inner = inner.replace("&*it", "it");
975 if nullable {
976 return format!(
977 "{{ let opt: Option<&Vec<_>> = ({expr}).as_ref(); \
978 let prefix = crate::primitives::array::nullable_array_len_prefix_len(opt.map(std::vec::Vec::len), flex); \
979 let body: usize = opt.map_or(0, |v| v.iter().map(|{closure_arg}| {inner}).sum()); \
980 prefix + body }}",
981 );
982 }
983 return format!(
984 "{{ let prefix = crate::primitives::array::array_len_prefix_len(({expr}).len(), flex); \
985 let body: usize = ({expr}).iter().map(|{closure_arg}| {inner}).sum(); \
986 prefix + body }}",
987 );
988 }
989 }
990
991 if is_struct_type(schema_type) {
992 if nullable {
993 return format!("1 + {expr}.as_ref().map_or(0, |v| v.encoded_len(version))");
995 }
996 return format!("{expr}.encoded_len(version)");
997 }
998
999 match (schema_type, nullable) {
1000 ("int8" | "bool", _) => "1".into(),
1001 ("int16" | "uint16", _) => "2".into(),
1002 ("int32", _) => "4".into(),
1003 ("int64" | "float64", _) => "8".into(),
1004 ("uuid", _) => "16".into(),
1005 ("string", false) => {
1006 format!("if flex {{ compact_string_len(&{expr}) }} else {{ string_len(&{expr}) }}")
1007 }
1008 ("string", true) => format!(
1009 "if flex {{ compact_nullable_string_len({expr}.as_deref()) }} else {{ nullable_string_len({expr}.as_deref()) }}"
1010 ),
1011 ("bytes", false) => {
1012 format!("if flex {{ compact_bytes_len(&{expr}) }} else {{ bytes_len(&{expr}) }}")
1013 }
1014 ("bytes", true) => format!(
1015 "if flex {{ compact_nullable_bytes_len({expr}.as_deref()) }} else {{ nullable_bytes_len({expr}.as_deref()) }}"
1016 ),
1017 ("records", false) => format!(
1018 "{{ let __rb_len = <crate::records::RecordsPayload as crate::Encode>::encoded_len(&{expr}, version); \
1019 if flex {{ crate::primitives::string_bytes::compact_bytes_len_from_size(__rb_len) }} \
1020 else {{ 4 + __rb_len }} }}"
1021 ),
1022 ("records", true) => format!(
1023 "({expr}).as_ref().map_or_else( \
1024 || if flex {{ crate::primitives::varint::uvarint_len(0) }} else {{ 4 }}, \
1025 |__rb| {{ let __rb_len = <crate::records::RecordsPayload as crate::Encode>::encoded_len(__rb, version); \
1026 if flex {{ crate::primitives::string_bytes::compact_bytes_len_from_size(__rb_len) }} \
1027 else {{ 4 + __rb_len }} }})"
1028 ),
1029 (t, _) => format!("compile_error!(\"unhandled type in encoded_len_expr: {t}\")"),
1030 }
1031}
1032
1033pub(crate) fn decode_call(
1034 schema_type: &str,
1035 nullable: bool,
1036 res_map: &HashMap<String, Resolution>,
1037 lenient_records: bool,
1038) -> String {
1039 if let Some(elem) = schema_type.strip_prefix("[]") {
1040 let elem_base = base_type(elem);
1041 if is_struct_type(elem_base) {
1042 let type_path = res_map
1044 .get(elem_base)
1045 .map_or(elem_base, |r| r.rust_path.as_str());
1046 if nullable {
1047 return format!(
1048 "{{ let opt = crate::primitives::array::get_nullable_array_len(buf, flex)?; \
1049 match opt {{ None => None, Some(n) => {{ let mut v = Vec::with_capacity(n); \
1050 for _ in 0..n {{ v.push({type_path}::decode(buf, version)?); }} Some(v) }} }} }}",
1051 );
1052 }
1053 return format!(
1054 "{{ let n = crate::primitives::array::get_array_len(buf, flex)?; \
1055 let mut v = Vec::with_capacity(n); \
1056 for _ in 0..n {{ v.push({type_path}::decode(buf, version)?); }} v }}",
1057 );
1058 }
1059 if nullable {
1060 return format!(
1061 "{{ let opt = crate::primitives::array::get_nullable_array_len(buf, flex)?; \
1062 match opt {{ None => None, Some(n) => {{ let mut v = Vec::with_capacity(n); \
1063 for _ in 0..n {{ v.push({inner}); }} Some(v) }} }} }}",
1064 inner = decode_call(elem, false, res_map, lenient_records),
1065 );
1066 }
1067 return format!(
1068 "{{ let n = crate::primitives::array::get_array_len(buf, flex)?; \
1069 let mut v = Vec::with_capacity(n); for _ in 0..n {{ v.push({inner}); }} v }}",
1070 inner = decode_call(elem, false, res_map, lenient_records),
1071 );
1072 }
1073
1074 if is_struct_type(schema_type) {
1075 let type_path = res_map
1076 .get(schema_type)
1077 .map_or(schema_type, |r| r.rust_path.as_str());
1078 if nullable {
1079 return format!(
1082 "if get_i8(buf)? < 0 {{ None }} else {{ Some({type_path}::decode(buf, version)?) }}"
1083 );
1084 }
1085 return format!("{type_path}::decode(buf, version)?");
1086 }
1087
1088 match (schema_type, nullable) {
1089 ("int8", _) => "get_i8(buf)?".into(),
1090 ("int16", _) => "get_i16(buf)?".into(),
1091 ("uint16", _) => "get_u16(buf)?".into(),
1092 ("int32", _) => "get_i32(buf)?".into(),
1093 ("int64", _) => "get_i64(buf)?".into(),
1094 ("bool", _) => "get_bool(buf)?".into(),
1095 ("float64",_) => "get_f64(buf)?".into(),
1096 ("uuid", _) => "crate::primitives::uuid::get_uuid(buf)?".into(),
1097 ("string", false) => "if flex { get_compact_string_owned(buf)? } else { get_string_owned(buf)? }".into(),
1098 ("string", true) => "if flex { get_compact_nullable_string_owned(buf)? } else { get_nullable_string_owned(buf)? }".into(),
1099 ("bytes", false) => "if flex { get_compact_bytes_owned(buf)? } else { get_bytes_owned(buf)? }".into(),
1100 ("bytes", true) => "if flex { get_compact_nullable_bytes_owned(buf)? } else { get_nullable_bytes_owned(buf)? }".into(),
1101 ("records", false) => if lenient_records {
1102 "{ \
1103 let __rb_bytes = if flex { get_compact_bytes_owned(buf)? } else { get_bytes_owned(buf)? }; \
1104 crate::records::RecordsPayload::from_fetch_bytes(__rb_bytes)? \
1105 }".into()
1106 } else {
1107 "{ \
1108 let __rb_bytes = if flex { get_compact_bytes_owned(buf)? } else { get_bytes_owned(buf)? }; \
1109 crate::records::RecordsPayload::from_bytes(__rb_bytes)? \
1110 }".into()
1111 },
1112 ("records", true) => if lenient_records {
1113 "{ \
1114 let __rb_opt = if flex { get_compact_nullable_bytes_owned(buf)? } else { get_nullable_bytes_owned(buf)? }; \
1115 match __rb_opt { \
1116 None => None, \
1117 Some(__rb_bytes) => { \
1118 Some(crate::records::RecordsPayload::from_fetch_bytes(__rb_bytes)?) \
1119 } \
1120 } \
1121 }".into()
1122 } else {
1123 "{ \
1124 let __rb_opt = if flex { get_compact_nullable_bytes_owned(buf)? } else { get_nullable_bytes_owned(buf)? }; \
1125 match __rb_opt { \
1126 None => None, \
1127 Some(__rb_bytes) => { \
1128 Some(crate::records::RecordsPayload::from_bytes(__rb_bytes)?) \
1129 } \
1130 } \
1131 }".into()
1132 },
1133 (t, _) => format!("compile_error!(\"unhandled type in decode_call: {t}\")"),
1134 }
1135}
1136
1137pub(crate) fn is_tagged(f: &FieldSpec) -> bool {
1140 f.tag.is_some()
1141}
1142pub(crate) fn is_nullable(f: &FieldSpec) -> bool {
1143 f.nullable_versions.is_some()
1144}
1145
1146pub(crate) fn nullable_split_cond(f: &FieldSpec) -> Option<String> {
1155 let r = f.nullable_versions?;
1156 let need_lower = r.min > f.versions.min;
1157 let need_upper = r.max < f.versions.max;
1158 if !need_lower && !need_upper {
1159 return None;
1160 }
1161 let mut parts = Vec::new();
1162 if need_lower {
1163 parts.push(if r.min == i16::MAX {
1164 "version == i16::MAX".to_owned()
1165 } else {
1166 format!("version >= {}", r.min)
1167 });
1168 }
1169 if need_upper {
1170 parts.push(if r.max == i16::MIN {
1171 "version == i16::MIN".to_owned()
1172 } else {
1173 format!("version <= {}", r.max)
1174 });
1175 }
1176 Some(parts.join(" && "))
1177}
1178
1179pub(crate) fn tagged_version_cond(f: &FieldSpec, flex_minimum: i16) -> Option<String> {
1189 let r = f.tagged_versions.unwrap_or(f.versions);
1190 if r.min <= flex_minimum && r.max == i16::MAX {
1191 return None;
1192 }
1193 Some(version_cond(r, "version"))
1194}
1195
1196pub(crate) fn version_cond(r: VersionRange, version_var: &str) -> String {
1197 if r.min == i16::MIN && r.max == i16::MAX {
1198 "true".to_owned()
1199 } else if r.min == r.max {
1200 format!("{version_var} == {}", r.min)
1201 } else if r.min == i16::MIN {
1202 format!("{version_var} <= {}", r.max)
1203 } else if r.max == i16::MAX {
1204 format!("{version_var} >= {}", r.min)
1205 } else {
1206 format!("({}..={}).contains(&{version_var})", r.min, r.max)
1207 }
1208}
1209
1210#[cfg(test)]
1211mod tests {
1212 use assert2::assert;
1213
1214 use super::*;
1215
1216 fn tagged_nullable_no_default_array_field() -> FieldSpec {
1223 serde_json::from_value(serde_json::json!({
1224 "name": "Owners", "type": "[]string", "versions": "1+",
1225 "nullableVersions": "1+", "taggedVersions": "1+", "tag": 5
1226 }))
1227 .unwrap()
1228 }
1229
1230 #[test]
1242 fn tagged_nullable_no_default_field_defaults_to_some_empty_not_none() {
1243 let field = tagged_nullable_no_default_array_field();
1244 let res_map: HashMap<String, Resolution> = HashMap::new();
1245
1246 assert!(owned_default_expr(&field, &res_map) == "Some(Vec::new())");
1247 assert!(tagged_is_default_cond(&field) == "self.owners == Some(Vec::new())");
1248 }
1249
1250 fn tagged_nullable_no_default_scalar_field() -> FieldSpec {
1256 serde_json::from_value(serde_json::json!({
1257 "name": "Owner", "type": "string", "versions": "1+",
1258 "nullableVersions": "1+", "taggedVersions": "1+", "tag": 6
1259 }))
1260 .unwrap()
1261 }
1262
1263 #[test]
1264 fn tagged_nullable_no_default_scalar_field_defaults_to_some_empty_not_none() {
1265 let field = tagged_nullable_no_default_scalar_field();
1266 let res_map: HashMap<String, Resolution> = HashMap::new();
1267
1268 assert!(owned_default_expr(&field, &res_map) == "Some(String::new())");
1269 assert!(tagged_is_default_cond(&field) == "self.owner == Some(String::new())");
1270 }
1271
1272 fn nullable_records_field_with_no_default() -> FieldSpec {
1280 serde_json::from_value(serde_json::json!({
1281 "name": "Records", "type": "records", "versions": "0+",
1282 "nullableVersions": "0+"
1283 }))
1284 .unwrap()
1285 }
1286
1287 #[test]
1288 fn nullable_records_field_always_defaults_to_none() {
1289 let field = nullable_records_field_with_no_default();
1290 let res_map: HashMap<String, Resolution> = HashMap::new();
1291
1292 assert!(owned_default_expr(&field, &res_map) == "None");
1293 assert!(tagged_is_default_cond(&field) == "self.records.is_none()");
1294 }
1295
1296 fn non_nullable_records_field() -> FieldSpec {
1300 serde_json::from_value(serde_json::json!({
1301 "name": "UnalignedRecords", "type": "records", "versions": "0+"
1302 }))
1303 .unwrap()
1304 }
1305
1306 #[test]
1307 fn non_nullable_records_field_keeps_empty_default() {
1308 let field = non_nullable_records_field();
1309 let res_map: HashMap<String, Resolution> = HashMap::new();
1310
1311 assert!(
1312 owned_default_expr(&field, &res_map) == "crate::records::RecordsPayload::default()"
1313 );
1314 }
1315}