Skip to main content

krabka_protocol_codegen/emit/
borrowed_quote.rs

1//! A `quote!`-based reimplementation of the `borrowed` emitter.
2//!
3//! This is the zero-copy, lifetime-carrying flavor with `&'a str`, `&'a [u8]`,
4//! and `RecordsPayloadBorrowed<'a>`, plus a `to_owned()` bridge to the owned
5//! types and a `DecodeBorrow<'de>` impl.
6//!
7//! The approach is the same as [`super::owned_quote`]. This module builds the
8//! structural scaffolding as tokens: the struct, `Default`, `to_owned`,
9//! `Encode`, `DecodeBorrow`, nested structs, and `populated`. It reuses the
10//! leaf codec expressions, the import and constant blocks, and the
11//! common-struct files verbatim from `super::borrowed`, whose wire bytes are
12//! already test-covered. The `borrowed_quote_parity` test asserts
13//! token-stream equality with the string emitter for every schema.
14
15use std::collections::{HashMap, HashSet};
16
17use proc_macro2::{Literal, TokenStream};
18use quote::{format_ident, quote};
19
20use crate::{
21    emit::{
22        EmittedMessage,
23        borrowed::{
24            self, base_type, borrowed_default_expr, borrowed_populated_value, decode_borrow_call,
25            decode_owned_call, encode_call, encode_call_option_as_non_nullable, encoded_len_expr,
26            encoded_len_expr_option_as_non_nullable, field_forces_non_flex, flex_min, has_any_flex,
27            has_float64_recursive, is_nullable, is_struct_type, is_tagged, needs_lifetime,
28            nullable_split_cond, owned_struct_path_for, spec_needs_lifetime, struct_path_for,
29            tagged_field_needs_owned, tagged_is_default_cond, to_owned_field_expr, version_cond,
30        },
31        common::{self, banner, parse_tokens, statement, tag_literal},
32        owned::{EmitError, tagged_version_cond},
33        owned_quote::tagged_decode_arm,
34    },
35    ir::{FieldSpec, MessageSpec, MessageType},
36    name_conv,
37    resolve::{self, Resolution, StructKind},
38    type_map,
39};
40
41type ResMap = HashMap<String, Resolution>;
42
43fn tagged_should_encode(f: &FieldSpec) -> Result<TokenStream, EmitError> {
44    if base_type(&f.field_type) == "bool" {
45        let field = format_ident!("{}", name_conv::field_name(&f.name));
46        return Ok(match &f.default {
47            Some(serde_json::Value::Bool(true)) => {
48                quote!(!self.#field)
49            }
50            Some(serde_json::Value::String(value)) if value == "true" => {
51                quote!(!self.#field)
52            }
53            Some(serde_json::Value::Bool(false)) => {
54                quote!(self.#field)
55            }
56            Some(serde_json::Value::String(value)) if value == "false" => quote!(self.#field),
57            _ => tagged_should_encode_from_default(f)?,
58        });
59    }
60    tagged_should_encode_from_default(f)
61}
62
63/// `tagged_should_encode` with the field's tagged-version range in front of
64/// it. KIP-482 binds a tag to the versions its schema declares, so a message
65/// must not write the tag below that range even when the value is non-default.
66fn gated_tagged_should_encode(f: &FieldSpec, flex_minimum: i16) -> Result<TokenStream, EmitError> {
67    let value = tagged_should_encode(f)?;
68    Ok(match tagged_version_cond(f, flex_minimum) {
69        // Every leaf of `tagged_should_encode` and `version_cond` binds tighter
70        // than `&&`, so neither side needs parentheses.
71        Some(cond) => {
72            let cond = parse_tokens(&cond)?;
73            quote!(#cond && #value)
74        }
75        None => value,
76    })
77}
78
79fn tagged_should_encode_from_default(f: &FieldSpec) -> Result<TokenStream, EmitError> {
80    let default = tagged_is_default_cond(f);
81    if let Some(option) = default.strip_suffix(".is_none()") {
82        return parse_tokens(&format!("{option}.is_some()"));
83    }
84    if let Some((value, expected)) = default.split_once(" == ") {
85        return parse_tokens(&format!("{value} != {expected}"));
86    }
87    if let Some(positive) = default.strip_prefix('!') {
88        return parse_tokens(positive);
89    }
90    let default = parse_tokens(&default)?;
91    Ok(quote!(!(#default)))
92}
93
94/// `crate::owned`, or `crate::<ns>::owned` for a namespaced run.
95fn owned_root(namespace: Option<&str>) -> String {
96    namespace.map_or_else(
97        || "crate::owned".to_owned(),
98        |ns| format!("crate::{ns}::owned"),
99    )
100}
101
102/// Shared context threaded through every struct block in one message file.
103struct Ctx<'a> {
104    res_map: &'a ResMap,
105    /// The owned module the `to_owned()` targets live in (the message's module).
106    parent_module: &'a str,
107    /// `crate::owned` or `crate::<ns>::owned`.
108    owned_root: &'a str,
109    /// The struct types whose `to_owned` can fail, because a `records` field
110    /// sits somewhere below them.
111    fallible: &'a HashSet<String>,
112}
113
114/// The struct types of `spec` whose `to_owned` can fail: the ones that hold a
115/// `records` field, directly or through a nested or common struct.
116pub(crate) fn fallible_structs(spec: &MessageSpec) -> HashSet<String> {
117    let mut structs: Vec<(String, &[FieldSpec])> =
118        vec![(spec.name.clone(), spec.fields.as_slice())];
119    let mut stack: Vec<&[FieldSpec]> = vec![spec.fields.as_slice()];
120    stack.extend(spec.common_structs.iter().map(|cs| cs.fields.as_slice()));
121    structs.extend(
122        spec.common_structs
123            .iter()
124            .map(|cs| (cs.name.clone(), cs.fields.as_slice())),
125    );
126    while let Some(fields) = stack.pop() {
127        for f in fields.iter().filter(|f| !f.fields.is_empty()) {
128            structs.push((base_type(&f.field_type).to_owned(), f.fields.as_slice()));
129            stack.push(&f.fields);
130        }
131    }
132    let mut fallible = HashSet::new();
133    let mut changed = true;
134    while changed {
135        changed = false;
136        for (name, fields) in &structs {
137            if fallible.contains(name) {
138                continue;
139            }
140            let holds_records = fields.iter().any(|f| {
141                let base = base_type(&f.field_type);
142                base == "records" || fallible.contains(base)
143            });
144            if holds_records {
145                fallible.insert(name.clone());
146                changed = true;
147            }
148        }
149    }
150    fallible
151}
152
153enum FlexSource {
154    TopLevel(i16),
155    Nested(i16),
156}
157
158impl FlexSource {
159    /// The flexible threshold of the struct. Tagged fields exist only at or
160    /// above it, so a tag whose range starts there needs no version test.
161    fn minimum(&self) -> i16 {
162        match self {
163            Self::TopLevel(fm) | Self::Nested(fm) => *fm,
164        }
165    }
166
167    fn tokens(&self) -> TokenStream {
168        match self {
169            Self::TopLevel(_) => quote!(is_flexible(version)),
170            Self::Nested(i16::MIN) => quote!(true),
171            Self::Nested(i16::MAX) => quote!(version == i16::MAX),
172            Self::Nested(fm) => {
173                let n = Literal::i16_unsuffixed(*fm);
174                quote!(version >= #n)
175            }
176        }
177    }
178}
179
180/// # Errors
181/// Returns an error when the schema model is invalid or generated Rust cannot be formatted or written.
182pub fn emit(
183    spec: &MessageSpec,
184    schemas_version: &str,
185    namespace: Option<&str>,
186) -> Result<EmittedMessage, EmitError> {
187    // Resolve struct references — also validates that everything resolves.
188    let res_map = resolve::resolve_message(spec)?;
189    let parent_module = name_conv::module_name(&spec.name);
190    let owned_root = owned_root(namespace);
191    let fallible = fallible_structs(spec);
192    let ctx = Ctx {
193        res_map: &res_map,
194        parent_module: &parent_module,
195        owned_root: &owned_root,
196        fallible: &fallible,
197    };
198
199    let mut structural = struct_block(
200        &ctx,
201        &name_conv::type_name(&spec.name),
202        &spec.fields,
203        spec_needs_lifetime(spec, &res_map),
204        &FlexSource::TopLevel(flex_min(spec)),
205        Some(version_err(spec)),
206        (true, has_any_flex(spec)),
207    )?;
208    structural.extend(nested_structs(&ctx, &spec.fields, flex_min(spec))?);
209
210    let imports = borrowed::emit_imports(spec, &res_map).to_string();
211    let constants = borrowed::emit_constants(spec)?.to_string();
212    let banner = common::banner(schemas_version);
213
214    let primary = format!("{banner}{imports}{constants}\n{structural}\n");
215    syn::parse_str::<syn::File>(&primary).map_err(|e| {
216        EmitError::Unsupported(format!(
217            "borrowed_quote produced invalid Rust for {}: {e}",
218            spec.name
219        ))
220    })?;
221
222    let commons = emit_commons(spec, &res_map, schemas_version, namespace, &fallible)?;
223
224    Ok(EmittedMessage { primary, commons })
225}
226
227/// Emit the standalone `.rs` file bodies for the message's `commonStructs`.
228///
229/// `commonStructs` are message-local. The emitter writes each one under a
230/// per-message nested module `common/<message_snake>/<struct_snake>`. The
231/// `commons` key is the relative path stem `<message_snake>/<struct_snake>`.
232/// The caller turns that stem into the on-disk body path and the wrapper
233/// module nesting.
234fn emit_commons(
235    spec: &MessageSpec,
236    res_map: &ResMap,
237    schemas_version: &str,
238    namespace: Option<&str>,
239    fallible: &HashSet<String>,
240) -> Result<Vec<(String, String)>, EmitError> {
241    let message_snake = name_conv::module_name(&spec.name);
242    let fm = flex_min(spec);
243    let owned_root = owned_root(namespace);
244    let mut commons: Vec<(String, String)> = Vec::new();
245    for cs in &spec.common_structs {
246        // Build a modified res_map for the common-struct context:
247        // - Common-struct references use sibling paths `super::<struct_snake>::TypeName`
248        //   (the body lands under `src/{flavor}/common/<message_snake>/<struct_snake>.rs`,
249        //   and sibling common structs of the same message share that parent module).
250        // - Nested struct references remain unchanged (bare type name, same file).
251        let common_res_map: ResMap = res_map
252            .iter()
253            .map(|(k, v)| {
254                let new_path = if v.kind == StructKind::Common {
255                    let snake = name_conv::module_name(k);
256                    format!("super::{snake}::{k}")
257                } else {
258                    v.rust_path.clone()
259                };
260                (
261                    k.clone(),
262                    Resolution {
263                        kind: v.kind.clone(),
264                        rust_path: new_path,
265                        needs_lifetime: v.needs_lifetime,
266                    },
267                )
268            })
269            .collect();
270
271        let cs_snake = name_conv::module_name(&cs.name);
272        // Use `common::<message_snake>::<struct_snake>` as the parent_module so
273        // `to_owned()` targets `crate::owned::common::<message_snake>::<struct_snake>::TypeName`.
274        let cs_parent_module = format!("common::{message_snake}::{cs_snake}");
275        let ctx = Ctx {
276            res_map: &common_res_map,
277            parent_module: &cs_parent_module,
278            owned_root: &owned_root,
279            fallible,
280        };
281
282        let name = name_conv::type_name(&cs.name);
283        let has_lt = needs_lifetime(&cs.fields, &common_res_map);
284        let mut struct_tokens = struct_block(
285            &ctx,
286            &name,
287            &cs.fields,
288            has_lt,
289            &FlexSource::Nested(fm),
290            None,
291            (false, fm < i16::MAX),
292        )?;
293        struct_tokens.extend(nested_structs(&ctx, &cs.fields, fm)?);
294
295        let body = format!(
296            "{}{}{}",
297            banner(schemas_version),
298            borrowed::emit_common_imports(&cs.fields, fm),
299            struct_tokens,
300        );
301
302        commons.push((format!("{message_snake}/{cs_snake}"), body));
303    }
304    Ok(commons)
305}
306
307fn version_err(spec: &MessageSpec) -> TokenStream {
308    match spec.message_type {
309        MessageType::Request | MessageType::Response => {
310            quote!(return Err(ProtocolError::UnsupportedVersion { api_key: API_KEY, version });)
311        }
312        MessageType::Header | MessageType::Data => {
313            let msg = format!("{} version out of range", spec.name);
314            quote!(return Err(ProtocolError::SchemaMismatch(#msg));)
315        }
316    }
317}
318
319/// Struct + `Default` + `to_owned` + `Encode` + `DecodeBorrow` + `populated`.
320/// `top_level` toggles the tagged-field-owned typing that only the message
321/// type uses. Nested structs always store tagged fields in their borrowed
322/// form.
323fn struct_block(
324    ctx: &Ctx,
325    name: &str,
326    fields: &[FieldSpec],
327    has_lt: bool,
328    flex: &FlexSource,
329    guard: Option<TokenStream>,
330    options: (bool, bool),
331) -> Result<TokenStream, EmitError> {
332    let (top_level, has_flex) = options;
333    let ty = format_ident!("{name}");
334    // `<'a>` on the value type / impls, `<'de>` on the Self type in DecodeBorrow.
335    let gen_a = if has_lt { quote!(<'a>) } else { quote!() };
336    let gen_de = if has_lt { quote!(<'de>) } else { quote!() };
337    let impl_a = if has_lt {
338        quote!(impl<'a>)
339    } else {
340        quote!(impl)
341    };
342    let ty_a = quote!(#ty #gen_a);
343    let ty_de = quote!(#ty #gen_de);
344    // An impl that never names the lifetime elides it.
345    let ty_elided = if has_lt { quote!(#ty<'_>) } else { quote!(#ty) };
346
347    let eq = if has_float64_recursive(fields) {
348        quote!()
349    } else {
350        quote!(, Eq)
351    };
352
353    let (field_defs, default_assigns, owned_target, to_owned_assigns) =
354        struct_members(ctx, name, fields, top_level)?;
355    let owned_value = quote! {
356        #owned_target {
357            #to_owned_assigns
358            unknown_tagged_fields: self.unknown_tagged_fields.clone(),
359        }
360    };
361    let to_owned_fn = if ctx.fallible.contains(name) {
362        quote! {
363            /// # Errors
364            ///
365            /// Returns the error of a records field that holds an invalid
366            /// encoded record batch.
367            pub fn to_owned(&self) -> Result<#owned_target, crate::records::RecordsError> {
368                Ok(#owned_value)
369            }
370        }
371    } else {
372        quote! {
373            #[must_use]
374            pub fn to_owned(&self) -> #owned_target {
375                #owned_value
376            }
377        }
378    };
379
380    let guard =
381        guard.map(|err| quote!(if !(MIN_VERSION..=MAX_VERSION).contains(&version) { #err }));
382    let flex_src = flex.tokens();
383    let flex_minimum = flex.minimum();
384    let encode_body = encode_body(ctx, fields, has_flex, flex_minimum)?;
385    let len_body = len_body(ctx, fields, has_flex, flex_minimum)?;
386    let decode_body = decode_body(ctx, fields, has_flex, flex_minimum)?;
387    let (codec_helpers, encode_body, decode_body) = if fields.len() >= 8 {
388        let (encode_helpers, encode_calls) =
389            split_encode_body(ctx, fields, has_flex, flex_minimum)?;
390        let (decode_helpers, decode_calls) =
391            split_decode_body(ctx, fields, has_flex, flex_minimum, has_lt)?;
392        (
393            quote!(#encode_helpers #decode_helpers),
394            encode_calls,
395            decode_calls,
396        )
397    } else {
398        (quote!(), encode_body, decode_body)
399    };
400    let encode_flex = flex_binding(&encode_body, &flex_src);
401    let len_flex = flex_binding(&len_body, &flex_src);
402    let decode_flex = flex_binding(&decode_body, &flex_src);
403    // A field default such as `<Node<'a>>::default()` names the lifetime.
404    let (default_impl, default_ty) = if default_assigns.to_string().contains("'a") {
405        (impl_a.clone(), ty_a.clone())
406    } else {
407        (quote!(impl), ty_elided.clone())
408    };
409    // The split decode helpers take a `&mut &'a [u8]`, so only they keep the
410    // named lifetime on the inherent impl.
411    let inherent_impl = if fields.len() >= 8 {
412        quote!(#impl_a #ty_a)
413    } else {
414        quote!(impl #ty_elided)
415    };
416    let populated_target = if has_lt {
417        quote!(impl #ty<'_>)
418    } else {
419        quote!(impl #ty)
420    };
421    let populated = populated_impl(ctx, &populated_target, fields)?;
422
423    Ok(quote! {
424        #[derive(Debug, Clone, PartialEq #eq)]
425        pub struct #ty_a {
426            #field_defs
427            pub unknown_tagged_fields: UnknownTaggedFields,
428        }
429
430        #default_impl Default for #default_ty {
431            fn default() -> Self {
432                Self {
433                    #default_assigns
434                    unknown_tagged_fields: UnknownTaggedFields::default(),
435                }
436            }
437        }
438
439        #inherent_impl {
440            #to_owned_fn
441
442            #codec_helpers
443        }
444
445        impl Encode for #ty_elided {
446            fn encode<B: BufMut>(&self, buf: &mut B, version: i16) -> Result<(), ProtocolError> {
447                #guard
448                #encode_flex
449                #encode_body
450                Ok(())
451            }
452            fn encoded_len(&self, version: i16) -> usize {
453                #len_flex
454                let mut n: usize = 0;
455                #len_body
456                n
457            }
458        }
459
460        impl<'de> DecodeBorrow<'de> for #ty_de {
461            fn decode_borrow(buf: &mut &'de [u8], version: i16) -> Result<Self, ProtocolError> {
462                #guard
463                #decode_flex
464                let mut out = Self::default();
465                #decode_body
466                Ok(out)
467            }
468        }
469
470        #populated
471    })
472}
473
474/// The tokens a struct block splices in: the field definitions, the `Default`
475/// field assignments, the owned `to_owned` target type, and the `to_owned`
476/// field assignments.
477type Members = (TokenStream, TokenStream, TokenStream, TokenStream);
478
479fn struct_members(
480    ctx: &Ctx,
481    name: &str,
482    fields: &[FieldSpec],
483    top_level: bool,
484) -> Result<Members, EmitError> {
485    let field_defs = ordered(fields)
486        .map(|field| {
487            let name = format_ident!("{}", name_conv::field_name(&field.name));
488            let ty = parse_tokens(&field_type(ctx, field, top_level))?;
489            Ok(quote!(pub #name: #ty,))
490        })
491        .collect::<Result<Vec<_>, EmitError>>()?;
492    let default_assigns = ordered(fields)
493        .map(|field| {
494            let name = format_ident!("{}", name_conv::field_name(&field.name));
495            let expr = if is_struct_type(base_type(&field.field_type))
496                && !field.field_type.starts_with("[]")
497                && field.default.is_none()
498                && !is_nullable(field)
499            {
500                let ty = parse_tokens(&field_type(ctx, field, top_level))?;
501                quote!(<#ty>::default())
502            } else {
503                parse_tokens(&borrowed_default_expr(field, ctx.res_map))?
504            };
505            Ok(quote!(#name: #expr,))
506        })
507        .collect::<Result<Vec<_>, EmitError>>()?;
508    let owned_target = parse_tokens(&format!(
509        "{}::{}::{name}",
510        ctx.owned_root, ctx.parent_module
511    ))?;
512    let to_owned_assigns = ordered(fields)
513        .map(|field| {
514            let name = format_ident!("{}", name_conv::field_name(&field.name));
515            let expr = parse_tokens(&to_owned_expr(ctx, field, top_level))?;
516            Ok(quote!(#name: #expr,))
517        })
518        .collect::<Result<Vec<_>, EmitError>>()?;
519    Ok((
520        quote!(#(#field_defs)*),
521        quote!(#(#default_assigns)*),
522        owned_target,
523        quote!(#(#to_owned_assigns)*),
524    ))
525}
526
527fn flex_binding(body: &TokenStream, flex_source: &TokenStream) -> Option<TokenStream> {
528    body.to_string()
529        .contains("flex")
530        .then(|| quote!(let flex = #flex_source;))
531}
532
533fn ordered(fields: &[FieldSpec]) -> impl Iterator<Item = &FieldSpec> {
534    fields
535        .iter()
536        .filter(|f| !is_tagged(f))
537        .chain(fields.iter().filter(|f| is_tagged(f)))
538}
539
540fn default_is_null(f: &FieldSpec) -> bool {
541    matches!(&f.default, Some(serde_json::Value::Null))
542}
543
544/// Rust struct-field type. The emitter stores top-level tagged fields owned
545/// when they cannot borrow from the ephemeral tagged-payload buffer.
546fn field_type(ctx: &Ctx, f: &FieldSpec, top_level: bool) -> String {
547    if is_tagged(f) {
548        let nullable = is_nullable(f) || default_is_null(f);
549        if top_level && tagged_field_needs_owned(f, ctx.res_map) {
550            let owned_path = owned_struct_path_for(f, ctx.parent_module, ctx.res_map);
551            return type_map::owned_type(&f.field_type, nullable, owned_path.as_deref());
552        }
553        return type_map::borrowed_type(
554            &f.field_type,
555            nullable,
556            struct_path_for(f, ctx.res_map).as_deref(),
557        );
558    }
559    type_map::borrowed_type(
560        &f.field_type,
561        is_nullable(f),
562        struct_path_for(f, ctx.res_map).as_deref(),
563    )
564}
565
566fn to_owned_expr(ctx: &Ctx, f: &FieldSpec, top_level: bool) -> String {
567    let field = name_conv::field_name(&f.name);
568    let expr = format!("self.{field}");
569    // The borrowed struct a struct field holds, without its lifetime, so that
570    // `<path>::to_owned` names its inherent `to_owned`.
571    let struct_path = ctx
572        .res_map
573        .get(base_type(&f.field_type))
574        .map(|r| r.rust_path.as_str());
575    let fallible = ctx.fallible.contains(base_type(&f.field_type));
576    if is_tagged(f) {
577        let nullable = is_nullable(f) || default_is_null(f);
578        if top_level && tagged_field_needs_owned(f, ctx.res_map) {
579            return format!("self.{field}.clone()");
580        }
581        return to_owned_field_expr(&f.field_type, &expr, nullable, struct_path, fallible);
582    }
583    to_owned_field_expr(&f.field_type, &expr, is_nullable(f), struct_path, fallible)
584}
585
586// --- encode / encoded_len (identical shape to owned, borrowed leaf codecs) ---
587
588fn encode_body(
589    ctx: &Ctx,
590    fields: &[FieldSpec],
591    has_flex: bool,
592    flex_minimum: i16,
593) -> Result<TokenStream, EmitError> {
594    let stmts = fields
595        .iter()
596        .filter(|f| !is_tagged(f))
597        .map(encode_one)
598        .collect::<Result<Vec<_>, EmitError>>()?;
599    let trailer = if has_flex {
600        Some(encode_tagged_trailer(ctx, fields, flex_minimum)?)
601    } else {
602        None
603    };
604    Ok(quote!(#(#stmts)* #trailer))
605}
606
607/// The `if flex { .. }` block that writes the known and unknown tagged fields.
608fn encode_tagged_trailer(
609    ctx: &Ctx,
610    fields: &[FieldSpec],
611    flex_minimum: i16,
612) -> Result<TokenStream, EmitError> {
613    let mut_kw = if fields.iter().any(is_tagged) {
614        quote!(mut)
615    } else {
616        quote!()
617    };
618    let adds = fields
619        .iter()
620        .filter(|f| is_tagged(f))
621        .map(|f| encode_tagged(ctx, f, flex_minimum))
622        .collect::<Result<Vec<_>, EmitError>>()?;
623    Ok(quote! {
624        if flex {
625            let #mut_kw tagged = WriteTaggedFields::new();
626            #(#adds)*
627            tagged.write(buf, &self.unknown_tagged_fields)?;
628        }
629    })
630}
631
632fn split_encode_body(
633    ctx: &Ctx,
634    fields: &[FieldSpec],
635    has_flex: bool,
636    flex_minimum: i16,
637) -> Result<(TokenStream, TokenStream), EmitError> {
638    let mut helpers = Vec::new();
639    let mut calls = Vec::new();
640    for (index, field) in fields.iter().filter(|field| !is_tagged(field)).enumerate() {
641        let helper = format_ident!("encode_field_{index}");
642        let body = encode_one(field)?;
643        let flex = if body.to_string().contains("flex") {
644            quote!(flex)
645        } else {
646            quote!(_flex)
647        };
648        if body.to_string().contains('?') {
649            helpers.push(quote! {
650                fn #helper<B: BufMut>(
651                    &self,
652                    buf: &mut B,
653                    version: i16,
654                    #flex: bool,
655                ) -> Result<(), ProtocolError> {
656                    #body
657                    Ok(())
658                }
659            });
660            calls.push(quote!(self.#helper(buf, version, flex)?;));
661        } else {
662            helpers.push(quote! {
663                fn #helper<B: BufMut>(&self, buf: &mut B, version: i16, #flex: bool) {
664                    #body
665                }
666            });
667            calls.push(quote!(self.#helper(buf, version, flex);));
668        }
669    }
670    if has_flex {
671        let helper = format_ident!("encode_tagged_fields");
672        let body = encode_tagged_trailer(ctx, fields, flex_minimum)?;
673        let version = if body.to_string().contains("version") {
674            quote!(version)
675        } else {
676            quote!(_version)
677        };
678        helpers.push(quote! {
679            fn #helper<B: BufMut>(
680                &self,
681                buf: &mut B,
682                #version: i16,
683                flex: bool,
684            ) -> Result<(), ProtocolError> {
685                #body
686                Ok(())
687            }
688        });
689        calls.push(quote!(self.#helper(buf, version, flex)?;));
690    }
691    Ok((quote!(#(#helpers)*), quote!(#(#calls)*)))
692}
693
694fn non_flex_wrap(f: &FieldSpec, body: TokenStream) -> TokenStream {
695    if field_forces_non_flex(f) {
696        quote!({ let flex = false; #body })
697    } else {
698        body
699    }
700}
701
702fn encode_one(f: &FieldSpec) -> Result<TokenStream, EmitError> {
703    let field = name_conv::field_name(&f.name);
704    let cond = parse_tokens(&version_cond(f.versions, "version"))?;
705    let expr = format!("self.{field}");
706    let inner = if let Some(ncond) = nullable_split_cond(f) {
707        let nb = parse_tokens(&encode_call(&f.field_type, &expr, true))?;
708        let nnb = parse_tokens(&encode_call_option_as_non_nullable(&f.field_type, &expr))?;
709        let nc = parse_tokens(&ncond)?;
710        non_flex_wrap(f, quote!(if #nc { #nb } else { #nnb }))
711    } else {
712        non_flex_wrap(
713            f,
714            parse_tokens(&encode_call(&f.field_type, &expr, is_nullable(f)))?,
715        )
716    };
717    let inner = statement(&inner);
718    Ok(quote!(if #cond { #inner }))
719}
720
721/// Tagged string and bytes fields stored owned use the owned-flavor codec,
722/// which adds `.as_deref()`. Everything else uses the borrowed codec.
723fn tagged_owned_codec(ctx: &Ctx, f: &FieldSpec) -> bool {
724    tagged_field_needs_owned(f, ctx.res_map)
725        && matches!(base_type(&f.field_type), "string" | "bytes")
726}
727
728fn encode_tagged(ctx: &Ctx, f: &FieldSpec, flex_minimum: i16) -> Result<TokenStream, EmitError> {
729    let field = name_conv::field_name(&f.name);
730    let tag = tag_literal(f)?;
731    let nullable = is_nullable(f) || default_is_null(f);
732    let expr = format!("self.{field}");
733    let body_str = if tagged_owned_codec(ctx, f) {
734        borrowed::owned_encode_call(&f.field_type, &expr, nullable)
735    } else {
736        encode_call(&f.field_type, &expr, nullable)
737    };
738    let body = statement(&parse_tokens(&body_str.replace("buf", "b"))?);
739    let len = parse_tokens(&tagged_len_str(ctx, f, &expr, nullable))?;
740    let should_encode = gated_tagged_should_encode(f, flex_minimum)?;
741    Ok(quote! {
742        if #should_encode {
743            let payload = encode_to_bytes(#len, |b| { #body Ok(()) })?;
744            tagged.add(#tag, payload);
745        }
746    })
747}
748
749fn tagged_len_str(ctx: &Ctx, f: &FieldSpec, expr: &str, nullable: bool) -> String {
750    if tagged_owned_codec(ctx, f) {
751        borrowed::owned_encoded_len_expr(&f.field_type, expr, nullable)
752    } else {
753        encoded_len_expr(&f.field_type, expr, nullable)
754    }
755}
756
757fn len_body(
758    ctx: &Ctx,
759    fields: &[FieldSpec],
760    has_flex: bool,
761    flex_minimum: i16,
762) -> Result<TokenStream, EmitError> {
763    let stmts = fields
764        .iter()
765        .filter(|f| !is_tagged(f))
766        .map(len_one)
767        .collect::<Result<Vec<_>, EmitError>>()?;
768    let trailer = if has_flex {
769        let mut_kw = if fields.iter().any(is_tagged) {
770            quote!(mut)
771        } else {
772            quote!()
773        };
774        let pushes = fields
775            .iter()
776            .filter(|f| is_tagged(f))
777            .map(|f| len_tagged(ctx, f, flex_minimum))
778            .collect::<Result<Vec<_>, EmitError>>()?;
779        Some(quote! {
780            if flex {
781                let #mut_kw known_pairs: Vec<(u32, usize)> = Vec::new();
782                #(#pushes)*
783                n += tagged_fields_len(&known_pairs, &self.unknown_tagged_fields);
784            }
785        })
786    } else {
787        None
788    };
789    Ok(quote!(#(#stmts)* #trailer))
790}
791
792fn len_one(f: &FieldSpec) -> Result<TokenStream, EmitError> {
793    let field = name_conv::field_name(&f.name);
794    let cond = parse_tokens(&version_cond(f.versions, "version"))?;
795    let expr = format!("self.{field}");
796    let inner = if let Some(ncond) = nullable_split_cond(f) {
797        let nb = parse_tokens(&encoded_len_expr(&f.field_type, &expr, true))?;
798        let nnb = parse_tokens(&encoded_len_expr_option_as_non_nullable(
799            &f.field_type,
800            &expr,
801        ))?;
802        let nc = parse_tokens(&ncond)?;
803        non_flex_wrap(f, quote!(if #nc { #nb } else { #nnb }))
804    } else {
805        non_flex_wrap(
806            f,
807            parse_tokens(&encoded_len_expr(&f.field_type, &expr, is_nullable(f)))?,
808        )
809    };
810    Ok(quote!(if #cond { n += #inner; }))
811}
812
813fn len_tagged(ctx: &Ctx, f: &FieldSpec, flex_minimum: i16) -> Result<TokenStream, EmitError> {
814    let field = name_conv::field_name(&f.name);
815    let tag = tag_literal(f)?;
816    let nullable = is_nullable(f) || default_is_null(f);
817    let len = parse_tokens(&tagged_len_str(ctx, f, &format!("self.{field}"), nullable))?;
818    let should_encode = gated_tagged_should_encode(f, flex_minimum)?;
819    Ok(quote! {
820        if #should_encode {
821            known_pairs.push((#tag, #len));
822        }
823    })
824}
825
826// --- decode_borrow ----------------------------------------------------------
827
828fn decode_body(
829    ctx: &Ctx,
830    fields: &[FieldSpec],
831    has_flex: bool,
832    flex_minimum: i16,
833) -> Result<TokenStream, EmitError> {
834    let stmts = fields
835        .iter()
836        .filter(|f| !is_tagged(f))
837        .map(|f| decode_one(ctx, f))
838        .collect::<Result<Vec<_>, EmitError>>()?;
839    let trailer = if has_flex {
840        Some(decode_tagged_block(ctx, fields, flex_minimum)?)
841    } else {
842        None
843    };
844    Ok(quote!(#(#stmts)* #trailer))
845}
846
847fn split_decode_body(
848    ctx: &Ctx,
849    fields: &[FieldSpec],
850    has_flex: bool,
851    flex_minimum: i16,
852    has_lt: bool,
853) -> Result<(TokenStream, TokenStream), EmitError> {
854    let mut helpers = Vec::new();
855    let mut calls = Vec::new();
856    let helper_buf = if has_lt {
857        quote!(&mut &'a [u8])
858    } else {
859        quote!(&mut &[u8])
860    };
861    for (index, field) in fields.iter().filter(|field| !is_tagged(field)).enumerate() {
862        let helper = format_ident!("decode_field_{index}");
863        let body = decode_one(ctx, field)?;
864        let flex = if body.to_string().contains("flex") {
865            quote!(flex)
866        } else {
867            quote!(_flex)
868        };
869        helpers.push(quote! {
870            fn #helper(
871                out: &mut Self,
872                buf: #helper_buf,
873                version: i16,
874                #flex: bool,
875            ) -> Result<(), ProtocolError> {
876                #body
877                Ok(())
878            }
879        });
880        calls.push(quote!(Self::#helper(&mut out, buf, version, flex)?;));
881    }
882    if has_flex {
883        let helper = format_ident!("decode_tagged_fields");
884        let body = decode_tagged_block(ctx, fields, flex_minimum)?;
885        let version = if body.to_string().contains("version") {
886            quote!(version)
887        } else {
888            quote!(_version)
889        };
890        helpers.push(quote! {
891            fn #helper(
892                out: &mut Self,
893                buf: #helper_buf,
894                #version: i16,
895                flex: bool,
896            ) -> Result<(), ProtocolError> {
897                #body
898                Ok(())
899            }
900        });
901        calls.push(quote!(Self::#helper(&mut out, buf, version, flex)?;));
902    }
903    Ok((quote!(#(#helpers)*), quote!(#(#calls)*)))
904}
905
906fn decode_one(ctx: &Ctx, f: &FieldSpec) -> Result<TokenStream, EmitError> {
907    let field = format_ident!("{}", name_conv::field_name(&f.name));
908    let cond = parse_tokens(&version_cond(f.versions, "version"))?;
909    let inner = if let Some(ncond) = nullable_split_cond(f) {
910        let nb = parse_tokens(&decode_borrow_call(&f.field_type, true, ctx.res_map))?;
911        let nnb = parse_tokens(&decode_borrow_call(&f.field_type, false, ctx.res_map))?;
912        let nc = parse_tokens(&ncond)?;
913        non_flex_wrap(f, quote!(if #nc { #nb } else { Some(#nnb) }))
914    } else {
915        non_flex_wrap(
916            f,
917            parse_tokens(&decode_borrow_call(
918                &f.field_type,
919                is_nullable(f),
920                ctx.res_map,
921            ))?,
922        )
923    };
924    Ok(quote!(if #cond { out.#field = #inner; }))
925}
926
927fn decode_tagged_block(
928    ctx: &Ctx,
929    fields: &[FieldSpec],
930    flex_minimum: i16,
931) -> Result<TokenStream, EmitError> {
932    if !fields.iter().any(is_tagged) {
933        return Ok(quote! {
934            if flex {
935                out.unknown_tagged_fields = read_tagged_fields(buf, |_tag, _payload| { Ok(false) })?;
936            }
937        });
938    }
939    let slots = fields.iter().filter(|f| is_tagged(f)).map(|f| {
940        let slot = format_ident!("tag_{}", name_conv::field_name(&f.name));
941        quote!(let mut #slot = None;)
942    });
943    let arms = fields
944        .iter()
945        .filter(|f| is_tagged(f))
946        .map(|f| {
947            let slot = format_ident!("tag_{}", name_conv::field_name(&f.name));
948            let nullable = is_nullable(f) || default_is_null(f);
949            let call = if tagged_field_needs_owned(f, ctx.res_map) {
950                decode_owned_call(&f.field_type, nullable, ctx.parent_module, ctx.res_map)
951                    .replace("buf", "b")
952            } else {
953                decode_borrow_call(&f.field_type, nullable, ctx.res_map).replace("buf", "b")
954            };
955            let call = parse_tokens(&call)?;
956            let read = quote!(#slot = Some({ let b: &mut &[u8] = payload; #call }); Ok(true));
957            tagged_decode_arm(f, flex_minimum, &read)
958        })
959        .collect::<Result<Vec<_>, EmitError>>()?;
960    let writebacks = fields.iter().filter(|f| is_tagged(f)).map(|f| {
961        let field = format_ident!("{}", name_conv::field_name(&f.name));
962        let slot = format_ident!("tag_{}", name_conv::field_name(&f.name));
963        quote!(if let Some(v) = #slot { out.#field = v; })
964    });
965    Ok(quote! {
966        if flex {
967            #(#slots)*
968            out.unknown_tagged_fields = read_tagged_fields(buf, |tag, payload| {
969                match tag {
970                    #(#arms)*
971                    _ => Ok(false),
972                }
973            })?;
974            #(#writebacks)*
975        }
976    })
977}
978
979// --- populated + nested -----------------------------------------------------
980
981fn populated_impl(
982    ctx: &Ctx,
983    impl_target: &TokenStream,
984    fields: &[FieldSpec],
985) -> Result<TokenStream, EmitError> {
986    let assigns = ordered(fields)
987        .filter(|f| base_type(&f.field_type) != "records")
988        .map(|f| {
989            let field = format_ident!("{}", name_conv::field_name(&f.name));
990            let option = is_nullable(f) || (is_tagged(f) && default_is_null(f));
991            let value = parse_tokens(&borrowed_populated_value(
992                f,
993                ctx.res_map,
994                ctx.parent_module,
995                option,
996            )?)?;
997            let cond = parse_tokens(&version_cond(f.versions, "version"))?;
998            Ok(quote!(if #cond { m.#field = #value; }))
999        })
1000        .collect::<Result<Vec<_>, EmitError>>()?;
1001    if assigns.is_empty() {
1002        return Ok(quote! {
1003            #[cfg(test)]
1004            #impl_target {
1005                #[must_use]
1006                pub fn populated(_version: i16) -> Self {
1007                    Self::default()
1008                }
1009            }
1010        });
1011    }
1012    let mut_kw = quote!(mut);
1013    let version = if assigns
1014        .iter()
1015        .any(|assign| assign.to_string().contains("version"))
1016    {
1017        quote!(version)
1018    } else {
1019        quote!(_version)
1020    };
1021    Ok(quote! {
1022        #[cfg(test)]
1023        #impl_target {
1024            #[must_use]
1025            pub fn populated(#version: i16) -> Self {
1026                let #mut_kw m = Self::default();
1027                #(#assigns)*
1028                m
1029            }
1030        }
1031    })
1032}
1033
1034fn nested_structs(
1035    ctx: &Ctx,
1036    fields: &[FieldSpec],
1037    flex_min_val: i16,
1038) -> Result<TokenStream, EmitError> {
1039    let mut out = TokenStream::new();
1040    for f in fields.iter().filter(|f| !f.fields.is_empty()) {
1041        let name = base_type(&f.field_type);
1042        out.extend(struct_block(
1043            ctx,
1044            name,
1045            &f.fields,
1046            needs_lifetime(&f.fields, ctx.res_map),
1047            &FlexSource::Nested(flex_min_val),
1048            None,
1049            (false, flex_min_val < i16::MAX),
1050        )?);
1051        out.extend(nested_structs(ctx, &f.fields, flex_min_val)?);
1052    }
1053    Ok(out)
1054}
1055
1056#[cfg(test)]
1057mod tests {
1058    use std::slice;
1059
1060    use assert2::assert;
1061
1062    use super::*;
1063    use crate::resolve::StructKind;
1064
1065    /// `struct_members`' `default_assigns` has a special case for a
1066    /// no-default struct-typed field: it emits a bare `<Ty>::default()`
1067    /// instead of routing through `borrowed_default_expr`, because a
1068    /// non-nullable struct field's `Default::default()` already agrees with
1069    /// `borrowed_default_expr`'s own resolution-based answer. A *nullable*
1070    /// struct field must not take that shortcut, since its default is
1071    /// `Some(<Ty>::default())`, not a bare `<Ty>::default()` — so the
1072    /// special case is guarded with `!is_nullable(field)` and falls through
1073    /// to `borrowed_default_expr` here.
1074    #[test]
1075    fn nullable_struct_field_with_no_default_routes_through_borrowed_default_expr() {
1076        let field: FieldSpec = serde_json::from_value(serde_json::json!({
1077            "name": "Owner", "type": "OwnerStruct", "versions": "1+",
1078            "nullableVersions": "1+"
1079        }))
1080        .unwrap();
1081        let mut res_map = ResMap::new();
1082        res_map.insert(
1083            "OwnerStruct".to_owned(),
1084            Resolution {
1085                kind: StructKind::Nested,
1086                rust_path: "OwnerStruct".to_owned(),
1087                needs_lifetime: false,
1088            },
1089        );
1090        let fallible = HashSet::new();
1091        let ctx = Ctx {
1092            res_map: &res_map,
1093            parent_module: "test",
1094            owned_root: "crate::owned",
1095            fallible: &fallible,
1096        };
1097
1098        let (_, default_assigns, _, _) =
1099            struct_members(&ctx, "Test", slice::from_ref(&field), true).unwrap();
1100
1101        let expected = quote!(owner: Some(OwnerStruct::default()),);
1102        assert!(default_assigns.to_string() == expected.to_string());
1103    }
1104}