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