Skip to main content

rs_matter/im/encoding/attr/
read_builder.rs

1/*
2 *
3 *    Copyright (c) 2026 Project CHIP Authors
4 *
5 *    Licensed under the Apache License, Version 2.0 (the "License");
6 *    you may not use this file except in compliance with the License.
7 *    You may obtain a copy of the License at
8 *
9 *        http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *    Unless required by applicable law or agreed to in writing, software
12 *    distributed under the License is distributed on an "AS IS" BASIS,
13 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *    See the License for the specific language governing permissions and
15 *    limitations under the License.
16 */
17
18//! Streaming TLV builders for `ReadRequestMessage` and its sub-structures.
19//!
20//! Compare with `WriteReqBuilder` in
21//! [`crate::im::encoding::attr::write_builder`]: same typestate-machine shape,
22//! same implicit-skip convention (optional fields are omitted by not
23//! calling their setter; later-field setters are available on
24//! earlier states so the user can write a minimal request in one
25//! straight chain). The Read variant carries no payload per entry —
26//! every entry is just an `AttrPath` list — so the path sub-builder
27//! is much simpler than its `AttrData` counterpart.
28//!
29//! # Layout
30//!
31//! Per Matter Core spec `ReadRequestMessage` is an
32//! anonymous-tagged struct with five fields:
33//!
34//! | Tag | Field             | Type             | Required |
35//! |-----|-------------------|------------------|----------|
36//! | 0   | AttributeRequests | array[AttrPath]? | no       |
37//! | 1   | EventRequests     | array[EventPath]?| no       |
38//! | 2   | EventFilters      | array[EventFilter]?| no     |
39//! | 3   | FabricFiltered    | bool             | **yes**  |
40//! | 4   | DataVersionFilters| array[DataVersionFilter]?| no |
41//!
42//! First-cut surface here covers the common case: attribute reads
43//! plus the mandatory `fabric_filtered` toggle. Event-side fields and
44//! dataver filters can be passed as pre-built slices via the
45//! `*_from(...)` helpers, or added as proper streaming sub-builders
46//! when a real use case appears.
47//!
48//! # Usage
49//!
50//! ```ignore
51//! exchange.send_with(|_, wb| {
52//!     let parent = TLVWriteParent::new("ReadRequest", wb);
53//!     ReadReqBuilder::new(parent)?
54//!         .attr_requests()?
55//!             .push()?.endpoint(1).cluster(0x0006).attr(0x0000).end()?
56//!             .push()?.endpoint(1).cluster(0x0008).attr(0x0000).end()?
57//!         .end()?
58//!         .fabric_filtered(true)?
59//!         .end()?;
60//!     Ok(Some(OpCode::ReadRequest.into()))
61//! }).await
62//! ```
63
64use core::marker::PhantomData;
65
66use crate::error::Error;
67use crate::im::encoding::{AttrId, ClusterId, EndptId};
68use crate::im::{
69    AttrPath, AttrPathTag, DataVersionFilter, EventFilter, EventPath, NodeId, ReadReqTag,
70    IM_REVISION,
71};
72use crate::tlv::{TLVBuilder, TLVBuilderParent, TLVTag, TLVWrite, ToTLV};
73
74/// Streaming builder for a `ReadRequestMessage`. Type-state-tagged
75/// so the compiler enforces in-order field writes; optional fields
76/// are implicitly skipped by not calling their setter.
77///
78/// Field-state values (state *after* each named field has been
79/// written or implicitly skipped):
80/// - `0`: nothing written yet
81/// - `1`: past `AttributeRequests`
82/// - `2`: past `EventRequests`
83/// - `3`: past `EventFilters`
84/// - `4`: past `FabricFiltered` (mandatory; no implicit-skip path
85///   from state 0/1/2/3 to here)
86/// - `5`: past `DataVersionFilters`
87/// - `6`: past `InteractionModelRevision` (auto-injected at default
88///   value [`IM_REVISION`] by `end()` if the optional setter wasn't
89///   called)
90pub struct ReadReqBuilder<P, const F: usize = 0> {
91    p: P,
92}
93
94impl<P> ReadReqBuilder<P, 0>
95where
96    P: TLVBuilderParent,
97{
98    /// Begin a new `ReadRequestMessage` — opens a struct at the given
99    /// tag on the parent's writer. For top-level use (the usual case)
100    /// pass `&TLVTag::Anonymous`.
101    pub fn new(mut p: P, tag: &TLVTag) -> Result<Self, Error> {
102        p.writer().start_struct(tag)?;
103        Ok(Self { p })
104    }
105}
106
107impl<P> TLVBuilder<P> for ReadReqBuilder<P, 0>
108where
109    P: TLVBuilderParent,
110{
111    fn new(parent: P, tag: &TLVTag) -> Result<Self, Error> {
112        Self::new(parent, tag)
113    }
114
115    fn unchecked_into_parent(self) -> P {
116        self.p
117    }
118}
119
120// ---------------------------------------------------------------------
121// `attr_requests` — openable from state 0; advances to state 1.
122// ---------------------------------------------------------------------
123impl<P> ReadReqBuilder<P, 0>
124where
125    P: TLVBuilderParent,
126{
127    /// Open the optional `AttributeRequests` array. Each `.push()`
128    /// yields an [`AttrPathBuilder`]; close with `.end()` to advance
129    /// to the next message field.
130    pub fn attr_requests(self) -> Result<AttrPathArrayBuilder<ReadReqBuilder<P, 1>>, Error> {
131        AttrPathArrayBuilder::new(
132            ReadReqBuilder { p: self.p },
133            &TLVTag::Context(ReadReqTag::AttrRequests as u8),
134        )
135    }
136
137    /// Write `AttributeRequests` from a pre-built slice. Convenience
138    /// for callers that already have an `&[AttrPath]` on hand.
139    pub fn attr_requests_from(mut self, paths: &[AttrPath]) -> Result<ReadReqBuilder<P, 1>, Error> {
140        let w = self.p.writer();
141        w.start_array(&TLVTag::Context(ReadReqTag::AttrRequests as u8))?;
142        for p in paths {
143            p.to_tlv(&TLVTag::Anonymous, &mut *w)?;
144        }
145        w.end_container()?;
146        Ok(ReadReqBuilder { p: self.p })
147    }
148}
149
150// ---------------------------------------------------------------------
151// `event_requests` — openable from state 0 or 1; advances to state 2.
152// ---------------------------------------------------------------------
153impl<P> ReadReqBuilder<P, 0>
154where
155    P: TLVBuilderParent,
156{
157    /// Write `EventRequests` from a pre-built slice, implicitly
158    /// skipping `AttributeRequests`.
159    pub fn event_requests_from(self, paths: &[EventPath]) -> Result<ReadReqBuilder<P, 2>, Error> {
160        ReadReqBuilder::<P, 1> { p: self.p }.event_requests_from(paths)
161    }
162}
163
164impl<P> ReadReqBuilder<P, 1>
165where
166    P: TLVBuilderParent,
167{
168    /// Write `EventRequests` from a pre-built slice. A streaming
169    /// sub-builder for `EventPath` is on the to-do list for when an
170    /// MCU client actually subscribes to events directly.
171    pub fn event_requests_from(
172        mut self,
173        paths: &[EventPath],
174    ) -> Result<ReadReqBuilder<P, 2>, Error> {
175        let w = self.p.writer();
176        w.start_array(&TLVTag::Context(ReadReqTag::EventRequests as u8))?;
177        for p in paths {
178            p.to_tlv(&TLVTag::Anonymous, &mut *w)?;
179        }
180        w.end_container()?;
181        Ok(ReadReqBuilder { p: self.p })
182    }
183}
184
185// ---------------------------------------------------------------------
186// `event_filters` — settable from state 0, 1, or 2; advances to 3.
187// ---------------------------------------------------------------------
188impl<P> ReadReqBuilder<P, 0>
189where
190    P: TLVBuilderParent,
191{
192    pub fn event_filters_from(
193        self,
194        filters: &[EventFilter],
195    ) -> Result<ReadReqBuilder<P, 3>, Error> {
196        ReadReqBuilder::<P, 2> { p: self.p }.event_filters_from(filters)
197    }
198}
199
200impl<P> ReadReqBuilder<P, 1>
201where
202    P: TLVBuilderParent,
203{
204    pub fn event_filters_from(
205        self,
206        filters: &[EventFilter],
207    ) -> Result<ReadReqBuilder<P, 3>, Error> {
208        ReadReqBuilder::<P, 2> { p: self.p }.event_filters_from(filters)
209    }
210}
211
212impl<P> ReadReqBuilder<P, 2>
213where
214    P: TLVBuilderParent,
215{
216    /// Write `EventFilters` from a pre-built slice.
217    pub fn event_filters_from(
218        mut self,
219        filters: &[EventFilter],
220    ) -> Result<ReadReqBuilder<P, 3>, Error> {
221        let w = self.p.writer();
222        w.start_array(&TLVTag::Context(ReadReqTag::EventFilters as u8))?;
223        for ef in filters {
224            ef.to_tlv(&TLVTag::Anonymous, &mut *w)?;
225        }
226        w.end_container()?;
227        Ok(ReadReqBuilder { p: self.p })
228    }
229}
230
231// ---------------------------------------------------------------------
232// `fabric_filtered` — *mandatory*; settable from state 0, 1, 2, or 3.
233// ---------------------------------------------------------------------
234impl<P> ReadReqBuilder<P, 0>
235where
236    P: TLVBuilderParent,
237{
238    /// Write the mandatory `FabricFiltered` field, implicitly
239    /// skipping `AttributeRequests`, `EventRequests`, and
240    /// `EventFilters`.
241    pub fn fabric_filtered(self, value: bool) -> Result<ReadReqBuilder<P, 4>, Error> {
242        ReadReqBuilder::<P, 3> { p: self.p }.fabric_filtered(value)
243    }
244}
245
246impl<P> ReadReqBuilder<P, 1>
247where
248    P: TLVBuilderParent,
249{
250    pub fn fabric_filtered(self, value: bool) -> Result<ReadReqBuilder<P, 4>, Error> {
251        ReadReqBuilder::<P, 3> { p: self.p }.fabric_filtered(value)
252    }
253}
254
255impl<P> ReadReqBuilder<P, 2>
256where
257    P: TLVBuilderParent,
258{
259    pub fn fabric_filtered(self, value: bool) -> Result<ReadReqBuilder<P, 4>, Error> {
260        ReadReqBuilder::<P, 3> { p: self.p }.fabric_filtered(value)
261    }
262}
263
264impl<P> ReadReqBuilder<P, 3>
265where
266    P: TLVBuilderParent,
267{
268    /// Write the mandatory `FabricFiltered` field. `true` constrains
269    /// reads of fabric-scoped attributes to the accessing fabric;
270    /// `false` returns entries for every fabric the accessor has
271    /// access to.
272    pub fn fabric_filtered(mut self, value: bool) -> Result<ReadReqBuilder<P, 4>, Error> {
273        self.p
274            .writer()
275            .bool(&TLVTag::Context(ReadReqTag::FabricFiltered as u8), value)?;
276        Ok(ReadReqBuilder { p: self.p })
277    }
278}
279
280// ---------------------------------------------------------------------
281// `dataver_filters` — settable from state 4; advances to state 5.
282// ---------------------------------------------------------------------
283impl<P> ReadReqBuilder<P, 4>
284where
285    P: TLVBuilderParent,
286{
287    /// Write `DataVersionFilters` from a pre-built slice. Used by
288    /// caching clients to avoid re-reading attributes that haven't
289    /// changed since the last data version they observed.
290    pub fn dataver_filters_from(
291        mut self,
292        filters: &[DataVersionFilter],
293    ) -> Result<ReadReqBuilder<P, 5>, Error> {
294        let w = self.p.writer();
295        w.start_array(&TLVTag::Context(ReadReqTag::DataVersionFilters as u8))?;
296        for f in filters {
297            f.to_tlv(&TLVTag::Anonymous, &mut *w)?;
298        }
299        w.end_container()?;
300        Ok(ReadReqBuilder { p: self.p })
301    }
302}
303
304// ---------------------------------------------------------------------
305// `end` — closable from state 4 or 5.
306// ---------------------------------------------------------------------
307impl<P> ReadReqBuilder<P, 4>
308where
309    P: TLVBuilderParent,
310{
311    /// Close the message struct, implicitly skipping
312    /// `DataVersionFilters`. Returns the parent.
313    pub fn end(self) -> Result<P, Error> {
314        ReadReqBuilder::<P, 5> { p: self.p }.end()
315    }
316}
317
318impl<P> ReadReqBuilder<P, 4>
319where
320    P: TLVBuilderParent,
321{
322    /// Write `InteractionModelRevision`, implicitly skipping
323    /// `DataVersionFilters`. This is a typestate skip-shim mirroring
324    /// the pattern PR #447 established for `SuppressResponse` /
325    /// `TimedRequest` on `InvReqBuilder`: callers who don't populate
326    /// the optional preceding field can advance straight to setting
327    /// (or auto-injecting) `InteractionModelRevision` without an
328    /// explicit no-op transition.
329    pub fn interaction_model_revision(self, value: u8) -> Result<ReadReqBuilder<P, 6>, Error> {
330        ReadReqBuilder::<P, 5> { p: self.p }.interaction_model_revision(value)
331    }
332}
333
334impl<P> ReadReqBuilder<P, 5>
335where
336    P: TLVBuilderParent,
337{
338    /// Write the mandatory-on-the-wire `InteractionModelRevision`
339    /// field (Matter Core: value is `13` since Matter
340    /// 1.3, unchanged in 1.4 and 1.5). Optional at the API level —
341    /// omit and `end()` injects [`IM_REVISION`] automatically. Set
342    /// explicitly only when speaking a non-default revision is
343    /// required (e.g. interop testing against a peer pinned to an
344    /// older revision).
345    pub fn interaction_model_revision(mut self, value: u8) -> Result<ReadReqBuilder<P, 6>, Error> {
346        self.p.writer().u8(
347            &TLVTag::Context(crate::im::encoding::IM_REVISION_TAG),
348            value,
349        )?;
350        Ok(ReadReqBuilder { p: self.p })
351    }
352
353    /// Close the message struct, auto-injecting
354    /// `InteractionModelRevision` at its default value
355    /// [`IM_REVISION`]. Returns the parent.
356    pub fn end(self) -> Result<P, Error> {
357        self.interaction_model_revision(IM_REVISION)?.end()
358    }
359}
360
361impl<P> ReadReqBuilder<P, 6>
362where
363    P: TLVBuilderParent,
364{
365    /// Close the message struct and return the parent.
366    pub fn end(mut self) -> Result<P, Error> {
367        self.p.writer().end_container()?;
368        Ok(self.p)
369    }
370}
371
372impl<P, const F: usize> TLVBuilderParent for ReadReqBuilder<P, F>
373where
374    P: TLVBuilderParent,
375{
376    type Write = P::Write;
377
378    fn writer(&mut self) -> &mut Self::Write {
379        self.p.writer()
380    }
381}
382
383impl<P, const F: usize> core::fmt::Debug for ReadReqBuilder<P, F>
384where
385    P: core::fmt::Debug,
386{
387    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
388        write!(f, "{:?}::ReadRequestMessage<{}>", self.p, F)
389    }
390}
391
392#[cfg(feature = "defmt")]
393impl<P, const F: usize> defmt::Format for ReadReqBuilder<P, F>
394where
395    P: defmt::Format,
396{
397    fn format(&self, fmt: defmt::Formatter<'_>) {
398        defmt::write!(fmt, "{:?}::ReadRequestMessage<{}>", self.p, F);
399    }
400}
401
402// =====================================================================
403// AttrPath array sub-builder
404// =====================================================================
405
406/// Array builder for the `AttributeRequests` field. Opened by
407/// [`ReadReqBuilder::attr_requests`]; close with `.end()`
408/// to return to the message builder.
409pub struct AttrPathArrayBuilder<P> {
410    p: P,
411}
412
413impl<P> AttrPathArrayBuilder<P>
414where
415    P: TLVBuilderParent,
416{
417    /// Begin a new `AttrPath` array — opens an array at the given
418    /// tag on the parent's writer.
419    pub fn new(mut p: P, tag: &TLVTag) -> Result<Self, Error> {
420        p.writer().start_array(tag)?;
421        Ok(Self { p })
422    }
423
424    /// Start a new `AttrPath` entry. The returned [`AttrPathBuilder`]
425    /// terminates with `.end()` which returns this array builder.
426    pub fn push(self) -> Result<AttrPathBuilder<Self, 0>, Error> {
427        AttrPathBuilder::new(self, &TLVTag::Anonymous)
428    }
429
430    /// Close the array and return the message builder.
431    pub fn end(mut self) -> Result<P, Error> {
432        self.p.writer().end_container()?;
433        Ok(self.p)
434    }
435}
436
437impl<P> TLVBuilder<P> for AttrPathArrayBuilder<P>
438where
439    P: TLVBuilderParent,
440{
441    fn new(parent: P, tag: &TLVTag) -> Result<Self, Error> {
442        Self::new(parent, tag)
443    }
444
445    fn unchecked_into_parent(self) -> P {
446        self.p
447    }
448}
449
450impl<P> TLVBuilderParent for AttrPathArrayBuilder<P>
451where
452    P: TLVBuilderParent,
453{
454    type Write = P::Write;
455
456    fn writer(&mut self) -> &mut Self::Write {
457        self.p.writer()
458    }
459}
460
461impl<P> core::fmt::Debug for AttrPathArrayBuilder<P>
462where
463    P: core::fmt::Debug,
464{
465    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
466        write!(f, "{:?}[]", self.p)
467    }
468}
469
470#[cfg(feature = "defmt")]
471impl<P> defmt::Format for AttrPathArrayBuilder<P>
472where
473    P: defmt::Format,
474{
475    fn format(&self, fmt: defmt::Formatter<'_>) {
476        defmt::write!(fmt, "{:?}[]", self.p);
477    }
478}
479
480// =====================================================================
481// AttrPath builder (one entry in the array)
482// =====================================================================
483
484/// Streaming builder for one `AttrPath` (`AttributePathIB`) entry.
485///
486/// Field-state values:
487/// - `0`: nothing written yet
488/// - `1`: past `Node`
489/// - `2`: past `Endpoint`
490/// - `3`: past `Cluster`
491/// - `4`: past `Attribute`
492/// - `5`: past `ListIndex`
493///
494/// Every field is optional — wildcards are common on the read side
495/// (e.g. "all attributes of cluster X on endpoint 1" omits
496/// `Attribute`; "every endpoint that has cluster X" omits both
497/// `Endpoint` and `Attribute`). Each setter advances directly to its
498/// own state; later-field setters on earlier states implicitly skip
499/// the ones in between.
500pub struct AttrPathBuilder<P, const F: usize = 0> {
501    p: P,
502    _f: PhantomData<[(); F]>,
503}
504
505impl<P> AttrPathBuilder<P, 0>
506where
507    P: TLVBuilderParent,
508{
509    /// Begin a new `AttrPath` entry — opens a TLV list at the given
510    /// tag. Use `&TLVTag::Anonymous` when pushed into an
511    /// `AttributeRequests` array (the typical case).
512    pub fn new(mut p: P, tag: &TLVTag) -> Result<Self, Error> {
513        p.writer().start_list(tag)?;
514        Ok(Self { p, _f: PhantomData })
515    }
516}
517
518impl<P> TLVBuilder<P> for AttrPathBuilder<P, 0>
519where
520    P: TLVBuilderParent,
521{
522    fn new(parent: P, tag: &TLVTag) -> Result<Self, Error> {
523        Self::new(parent, tag)
524    }
525
526    fn unchecked_into_parent(self) -> P {
527        self.p
528    }
529}
530
531// ---- node ------------------------------------------------------------
532impl<P> AttrPathBuilder<P, 0>
533where
534    P: TLVBuilderParent,
535{
536    /// Write the optional `Node` field. Rarely used on writes/reads
537    /// to "self" — exists for proxied reads against other nodes.
538    pub fn node(mut self, value: NodeId) -> Result<AttrPathBuilder<P, 1>, Error> {
539        self.p
540            .writer()
541            .u64(&TLVTag::Context(AttrPathTag::Node as u8), value)?;
542        Ok(AttrPathBuilder {
543            p: self.p,
544            _f: PhantomData,
545        })
546    }
547}
548
549// ---- endpoint --------------------------------------------------------
550impl<P> AttrPathBuilder<P, 0>
551where
552    P: TLVBuilderParent,
553{
554    /// Write the optional `Endpoint` field, implicitly skipping
555    /// `Node`. Omit (call `.cluster(...)` instead) for a
556    /// wildcard-endpoint read.
557    pub fn endpoint(self, value: EndptId) -> Result<AttrPathBuilder<P, 2>, Error> {
558        AttrPathBuilder::<P, 1> {
559            p: self.p,
560            _f: PhantomData,
561        }
562        .endpoint(value)
563    }
564}
565
566impl<P> AttrPathBuilder<P, 1>
567where
568    P: TLVBuilderParent,
569{
570    pub fn endpoint(mut self, value: EndptId) -> Result<AttrPathBuilder<P, 2>, Error> {
571        self.p
572            .writer()
573            .u16(&TLVTag::Context(AttrPathTag::Endpoint as u8), value)?;
574        Ok(AttrPathBuilder {
575            p: self.p,
576            _f: PhantomData,
577        })
578    }
579}
580
581// ---- cluster ---------------------------------------------------------
582impl<P> AttrPathBuilder<P, 0>
583where
584    P: TLVBuilderParent,
585{
586    pub fn cluster(self, value: ClusterId) -> Result<AttrPathBuilder<P, 3>, Error> {
587        AttrPathBuilder::<P, 2> {
588            p: self.p,
589            _f: PhantomData,
590        }
591        .cluster(value)
592    }
593}
594
595impl<P> AttrPathBuilder<P, 1>
596where
597    P: TLVBuilderParent,
598{
599    pub fn cluster(self, value: ClusterId) -> Result<AttrPathBuilder<P, 3>, Error> {
600        AttrPathBuilder::<P, 2> {
601            p: self.p,
602            _f: PhantomData,
603        }
604        .cluster(value)
605    }
606}
607
608impl<P> AttrPathBuilder<P, 2>
609where
610    P: TLVBuilderParent,
611{
612    pub fn cluster(mut self, value: ClusterId) -> Result<AttrPathBuilder<P, 3>, Error> {
613        self.p
614            .writer()
615            .u32(&TLVTag::Context(AttrPathTag::Cluster as u8), value)?;
616        Ok(AttrPathBuilder {
617            p: self.p,
618            _f: PhantomData,
619        })
620    }
621}
622
623// ---- attr ------------------------------------------------------------
624impl<P> AttrPathBuilder<P, 0>
625where
626    P: TLVBuilderParent,
627{
628    pub fn attr(self, value: AttrId) -> Result<AttrPathBuilder<P, 4>, Error> {
629        AttrPathBuilder::<P, 3> {
630            p: self.p,
631            _f: PhantomData,
632        }
633        .attr(value)
634    }
635}
636
637impl<P> AttrPathBuilder<P, 1>
638where
639    P: TLVBuilderParent,
640{
641    pub fn attr(self, value: AttrId) -> Result<AttrPathBuilder<P, 4>, Error> {
642        AttrPathBuilder::<P, 3> {
643            p: self.p,
644            _f: PhantomData,
645        }
646        .attr(value)
647    }
648}
649
650impl<P> AttrPathBuilder<P, 2>
651where
652    P: TLVBuilderParent,
653{
654    pub fn attr(self, value: AttrId) -> Result<AttrPathBuilder<P, 4>, Error> {
655        AttrPathBuilder::<P, 3> {
656            p: self.p,
657            _f: PhantomData,
658        }
659        .attr(value)
660    }
661}
662
663impl<P> AttrPathBuilder<P, 3>
664where
665    P: TLVBuilderParent,
666{
667    pub fn attr(mut self, value: AttrId) -> Result<AttrPathBuilder<P, 4>, Error> {
668        self.p
669            .writer()
670            .u32(&TLVTag::Context(AttrPathTag::Attribute as u8), value)?;
671        Ok(AttrPathBuilder {
672            p: self.p,
673            _f: PhantomData,
674        })
675    }
676}
677
678// ---- list_index ------------------------------------------------------
679impl<P> AttrPathBuilder<P, 4>
680where
681    P: TLVBuilderParent,
682{
683    /// Write the optional `ListIndex` field — used to read a specific
684    /// index within a list-typed attribute.
685    pub fn list_index(mut self, value: Option<u16>) -> Result<AttrPathBuilder<P, 5>, Error> {
686        // Nullable<u16> = Option<u16> with `None` encoded as TLV null.
687        // Encode via the regular `to_tlv` of `Nullable`.
688        let n: crate::tlv::Nullable<u16> = match value {
689            Some(v) => crate::tlv::Nullable::some(v),
690            None => crate::tlv::Nullable::none(),
691        };
692        n.to_tlv(
693            &TLVTag::Context(AttrPathTag::ListIndex as u8),
694            self.p.writer(),
695        )?;
696        Ok(AttrPathBuilder {
697            p: self.p,
698            _f: PhantomData,
699        })
700    }
701}
702
703// ---- end -------------------------------------------------------------
704// Allowed from any state past 0 (each implicit-skip via forwarders).
705impl<P> AttrPathBuilder<P, 0>
706where
707    P: TLVBuilderParent,
708{
709    pub fn end(self) -> Result<P, Error> {
710        AttrPathBuilder::<P, 5> {
711            p: self.p,
712            _f: PhantomData,
713        }
714        .end()
715    }
716}
717impl<P> AttrPathBuilder<P, 1>
718where
719    P: TLVBuilderParent,
720{
721    pub fn end(self) -> Result<P, Error> {
722        AttrPathBuilder::<P, 5> {
723            p: self.p,
724            _f: PhantomData,
725        }
726        .end()
727    }
728}
729impl<P> AttrPathBuilder<P, 2>
730where
731    P: TLVBuilderParent,
732{
733    pub fn end(self) -> Result<P, Error> {
734        AttrPathBuilder::<P, 5> {
735            p: self.p,
736            _f: PhantomData,
737        }
738        .end()
739    }
740}
741impl<P> AttrPathBuilder<P, 3>
742where
743    P: TLVBuilderParent,
744{
745    pub fn end(self) -> Result<P, Error> {
746        AttrPathBuilder::<P, 5> {
747            p: self.p,
748            _f: PhantomData,
749        }
750        .end()
751    }
752}
753impl<P> AttrPathBuilder<P, 4>
754where
755    P: TLVBuilderParent,
756{
757    pub fn end(self) -> Result<P, Error> {
758        AttrPathBuilder::<P, 5> {
759            p: self.p,
760            _f: PhantomData,
761        }
762        .end()
763    }
764}
765impl<P> AttrPathBuilder<P, 5>
766where
767    P: TLVBuilderParent,
768{
769    /// Close the `AttrPath` list and return the array builder so the
770    /// caller can `.push()` another entry or `.end()` the array.
771    pub fn end(mut self) -> Result<P, Error> {
772        self.p.writer().end_container()?;
773        Ok(self.p)
774    }
775}
776
777impl<P, const F: usize> TLVBuilderParent for AttrPathBuilder<P, F>
778where
779    P: TLVBuilderParent,
780{
781    type Write = P::Write;
782
783    fn writer(&mut self) -> &mut Self::Write {
784        self.p.writer()
785    }
786}
787
788impl<P, const F: usize> core::fmt::Debug for AttrPathBuilder<P, F>
789where
790    P: core::fmt::Debug,
791{
792    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
793        write!(f, "{:?}::AttrPath<{}>", self.p, F)
794    }
795}
796
797#[cfg(feature = "defmt")]
798impl<P, const F: usize> defmt::Format for AttrPathBuilder<P, F>
799where
800    P: defmt::Format,
801{
802    fn format(&self, fmt: defmt::Formatter<'_>) {
803        defmt::write!(fmt, "{:?}::AttrPath<{}>", self.p, F);
804    }
805}