fidl_next_codec/wire/string/
optional.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use core::fmt;
6use core::str::from_utf8;
7
8use munge::munge;
9
10use crate::{
11    Decode, DecodeError, Decoder, EncodableOption, EncodeError, EncodeOption, Encoder, Slot,
12    TakeFrom, WireOptionalVector, WireString, WireVector, ZeroPadding,
13};
14
15/// An optional FIDL string
16#[repr(transparent)]
17pub struct WireOptionalString {
18    vec: WireOptionalVector<u8>,
19}
20
21unsafe impl ZeroPadding for WireOptionalString {
22    #[inline]
23    unsafe fn zero_padding(ptr: *mut Self) {
24        unsafe {
25            WireOptionalVector::<u8>::zero_padding(ptr.cast());
26        }
27    }
28}
29
30impl WireOptionalString {
31    /// Encodes that a string is present in a slot.
32    #[inline]
33    pub fn encode_present(slot: Slot<'_, Self>, len: u64) {
34        munge!(let Self { vec } = slot);
35        WireOptionalVector::encode_present(vec, len);
36    }
37
38    /// Encodes that a string is absent in a slot.
39    #[inline]
40    pub fn encode_absent(slot: Slot<'_, Self>) {
41        munge!(let Self { vec } = slot);
42        WireOptionalVector::encode_absent(vec);
43    }
44
45    /// Returns whether the optional string is present.
46    #[inline]
47    pub fn is_some(&self) -> bool {
48        self.vec.is_some()
49    }
50
51    /// Returns whether the optional string is absent.
52    #[inline]
53    pub fn is_none(&self) -> bool {
54        self.vec.is_none()
55    }
56
57    /// Returns a reference to the underlying string, if any.
58    #[inline]
59    pub fn as_ref(&self) -> Option<&WireString> {
60        self.vec.as_ref().map(|vec| unsafe { &*(vec as *const WireVector<u8>).cast() })
61    }
62}
63
64impl fmt::Debug for WireOptionalString {
65    #[inline]
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        self.as_ref().fmt(f)
68    }
69}
70
71unsafe impl<D: Decoder + ?Sized> Decode<D> for WireOptionalString {
72    #[inline]
73    fn decode(slot: Slot<'_, Self>, decoder: &mut D) -> Result<(), DecodeError> {
74        munge!(let Self { mut vec } = slot);
75
76        unsafe {
77            WireOptionalVector::decode_raw(vec.as_mut(), decoder)?;
78        }
79        let vec = unsafe { vec.deref_unchecked() };
80        if let Some(bytes) = vec.as_ref() {
81            // Check if the string is valid ASCII (fast path)
82            if !bytes.as_slice().is_ascii() {
83                // Fall back to checking if the string is valid UTF-8 (slow path)
84                // We're using `from_utf8` more like an `is_utf8` here.
85                let _ = from_utf8(bytes)?;
86            }
87        }
88
89        Ok(())
90    }
91}
92
93impl EncodableOption for String {
94    type EncodedOption = WireOptionalString;
95}
96
97impl<E: Encoder + ?Sized> EncodeOption<E> for String {
98    #[inline]
99    fn encode_option(
100        this: Option<&mut Self>,
101        encoder: &mut E,
102        slot: Slot<'_, Self::EncodedOption>,
103    ) -> Result<(), EncodeError> {
104        if let Some(string) = this {
105            encoder.write(string.as_bytes());
106            WireOptionalString::encode_present(slot, string.len() as u64);
107        } else {
108            WireOptionalString::encode_absent(slot);
109        }
110
111        Ok(())
112    }
113}
114
115impl TakeFrom<WireOptionalString> for Option<String> {
116    #[inline]
117    fn take_from(from: &WireOptionalString) -> Self {
118        from.as_ref().map(String::take_from)
119    }
120}