Skip to main content

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::mem::MaybeUninit;
7use core::str::from_utf8;
8
9use munge::munge;
10
11use crate::{
12    Constrained, Decode, DecodeError, Decoder, EncodeError, EncodeOption, Encoder, FromWireOption,
13    FromWireOptionRef, IntoNatural, Slot, ValidationError, Wire, wire,
14};
15
16/// An optional FIDL string
17#[repr(transparent)]
18pub struct OptionalString<'de> {
19    vec: wire::OptionalVector<'de, u8>,
20}
21
22unsafe impl Wire for OptionalString<'static> {
23    type Narrowed<'de> = OptionalString<'de>;
24
25    #[inline]
26    fn zero_padding(out: &mut MaybeUninit<Self>) {
27        munge!(let Self { vec } = out);
28        wire::OptionalVector::<u8>::zero_padding(vec);
29    }
30}
31
32impl OptionalString<'_> {
33    /// Encodes that a string is present in a slot.
34    #[inline]
35    pub fn encode_present(out: &mut MaybeUninit<Self>, len: u64) {
36        munge!(let Self { vec } = out);
37        wire::OptionalVector::encode_present(vec, len);
38    }
39
40    /// Encodes that a string is absent in a slot.
41    #[inline]
42    pub fn encode_absent(out: &mut MaybeUninit<Self>) {
43        munge!(let Self { vec } = out);
44        wire::OptionalVector::encode_absent(vec);
45    }
46
47    /// Returns whether the optional string is present.
48    #[inline]
49    pub fn is_some(&self) -> bool {
50        self.vec.is_some()
51    }
52
53    /// Returns whether the optional string is absent.
54    #[inline]
55    pub fn is_none(&self) -> bool {
56        self.vec.is_none()
57    }
58
59    /// Returns a reference to the underlying string, if any.
60    #[inline]
61    pub fn as_ref(&self) -> Option<&wire::String<'_>> {
62        self.vec.as_ref().map(|vec| unsafe { &*(vec as *const wire::Vector<'_, u8>).cast() })
63    }
64
65    /// Validate that this string's length falls within the limit.
66    fn validate_max_len(slot: Slot<'_, Self>, limit: u64) -> Result<(), ValidationError> {
67        munge!(let Self { vec } = slot);
68        match wire::OptionalVector::validate_max_len(vec, limit) {
69            Ok(()) => Ok(()),
70            Err(ValidationError::VectorTooLong { count, limit }) => {
71                Err(ValidationError::StringTooLong { count, limit })
72            }
73            Err(e) => Err(e),
74        }
75    }
76}
77
78impl Constrained for OptionalString<'_> {
79    type Constraint = u64;
80
81    fn validate(slot: Slot<'_, Self>, constraint: Self::Constraint) -> Result<(), ValidationError> {
82        Self::validate_max_len(slot, constraint)
83    }
84}
85
86impl fmt::Debug for OptionalString<'_> {
87    #[inline]
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        self.as_ref().fmt(f)
90    }
91}
92
93impl<T> PartialEq<Option<T>> for OptionalString<'_>
94where
95    for<'de> wire::String<'de>: PartialEq<T>,
96{
97    fn eq(&self, other: &Option<T>) -> bool {
98        match (self.as_ref(), other.as_ref()) {
99            (Some(lhs), Some(rhs)) => lhs == rhs,
100            (None, None) => true,
101            _ => false,
102        }
103    }
104}
105
106unsafe impl<'de, D: Decoder<'de> + ?Sized> Decode<D> for OptionalString<'de> {
107    #[inline]
108    fn decode(slot: Slot<'_, Self>, decoder: &mut D, constraint: u64) -> Result<(), DecodeError> {
109        munge!(let Self { mut vec } = slot);
110
111        let result = unsafe { wire::OptionalVector::decode_raw(vec.as_mut(), decoder, constraint) };
112        match result {
113            Ok(()) => (),
114            Err(DecodeError::Validation(ValidationError::VectorTooLong { count, limit })) => {
115                return Err(DecodeError::Validation(ValidationError::StringTooLong {
116                    count,
117                    limit,
118                }));
119            }
120            Err(e) => return Err(e),
121        }
122        let vec = unsafe { vec.deref_unchecked() };
123        if let Some(bytes) = vec.as_ref() {
124            // Check if the string is valid ASCII (fast path)
125            if !bytes.as_slice().is_ascii() {
126                // Fall back to checking if the string is valid UTF-8 (slow path)
127                // We're using `from_utf8` more like an `is_utf8` here.
128                let _ = from_utf8(bytes)?;
129            }
130        }
131
132        Ok(())
133    }
134}
135
136unsafe impl<E: Encoder + ?Sized> EncodeOption<OptionalString<'static>, E> for String {
137    #[inline]
138    fn encode_option(
139        this: Option<Self>,
140        encoder: &mut E,
141        out: &mut MaybeUninit<OptionalString<'static>>,
142        constraint: u64,
143    ) -> Result<(), EncodeError> {
144        <&str>::encode_option(this.as_deref(), encoder, out, constraint)
145    }
146}
147
148unsafe impl<E: Encoder + ?Sized> EncodeOption<OptionalString<'static>, E> for &String {
149    #[inline]
150    fn encode_option(
151        this: Option<Self>,
152        encoder: &mut E,
153        out: &mut MaybeUninit<OptionalString<'static>>,
154        constraint: u64,
155    ) -> Result<(), EncodeError> {
156        <&str>::encode_option(this.map(String::as_str), encoder, out, constraint)
157    }
158}
159
160unsafe impl<E: Encoder + ?Sized> EncodeOption<OptionalString<'static>, E> for &str {
161    #[inline]
162    fn encode_option(
163        this: Option<Self>,
164        encoder: &mut E,
165        out: &mut MaybeUninit<OptionalString<'static>>,
166        _constraint: u64,
167    ) -> Result<(), EncodeError> {
168        if let Some(string) = this {
169            encoder.write(string.as_bytes());
170            OptionalString::encode_present(out, string.len() as u64);
171        } else {
172            OptionalString::encode_absent(out);
173        }
174        Ok(())
175    }
176}
177
178impl FromWireOption<OptionalString<'_>> for String {
179    #[inline]
180    fn from_wire_option(wire: OptionalString<'_>) -> Option<Self> {
181        Vec::from_wire_option(wire.vec).map(|vec| unsafe { String::from_utf8_unchecked(vec) })
182    }
183}
184
185impl IntoNatural for OptionalString<'_> {
186    type Natural = Option<String>;
187}
188
189impl FromWireOptionRef<OptionalString<'_>> for String {
190    #[inline]
191    fn from_wire_option_ref(wire: &OptionalString<'_>) -> Option<Self> {
192        Vec::from_wire_option_ref(&wire.vec).map(|vec| unsafe { String::from_utf8_unchecked(vec) })
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use crate::{DecoderExt as _, EncoderExt as _, chunks, wire};
199
200    #[test]
201    fn decode_optional_string() {
202        assert_eq!(
203            chunks![
204                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
205                0x00, 0x00,
206            ]
207            .as_mut_slice()
208            .decode_with_constraint::<wire::OptionalString<'_>>(1000)
209            .unwrap(),
210            None::<&str>,
211        );
212    }
213
214    #[test]
215    fn encode_optional_string() {
216        assert_eq!(
217            Vec::encode_with_constraint(None::<String>, 1000).unwrap(),
218            chunks![
219                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
220                0x00, 0x00,
221            ],
222        );
223    }
224}