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    Decode, DecodeError, Decoder, EncodableOption, EncodeError, EncodeOption, EncodeOptionRef,
13    Encoder, FromWireOption, FromWireOptionRef, Slot, Wire, WireOptionalVector, WireString,
14    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
67impl fmt::Debug for WireOptionalString<'_> {
68    #[inline]
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        self.as_ref().fmt(f)
71    }
72}
73
74unsafe impl<D: Decoder + ?Sized> Decode<D> for WireOptionalString<'static> {
75    #[inline]
76    fn decode(slot: Slot<'_, Self>, decoder: &mut D) -> Result<(), DecodeError> {
77        munge!(let Self { mut vec } = slot);
78
79        unsafe {
80            WireOptionalVector::decode_raw(vec.as_mut(), decoder)?;
81        }
82        let vec = unsafe { vec.deref_unchecked() };
83        if let Some(bytes) = vec.as_ref() {
84            // Check if the string is valid ASCII (fast path)
85            if !bytes.as_slice().is_ascii() {
86                // Fall back to checking if the string is valid UTF-8 (slow path)
87                // We're using `from_utf8` more like an `is_utf8` here.
88                let _ = from_utf8(bytes)?;
89            }
90        }
91
92        Ok(())
93    }
94}
95
96impl EncodableOption for String {
97    type EncodedOption = WireOptionalString<'static>;
98}
99
100unsafe impl<E: Encoder + ?Sized> EncodeOption<E> for String {
101    #[inline]
102    fn encode_option(
103        this: Option<Self>,
104        encoder: &mut E,
105        out: &mut MaybeUninit<Self::EncodedOption>,
106    ) -> Result<(), EncodeError> {
107        <&str>::encode_option(this.as_deref(), encoder, out)
108    }
109}
110
111unsafe impl<E: Encoder + ?Sized> EncodeOptionRef<E> for String {
112    #[inline]
113    fn encode_option_ref(
114        this: Option<&Self>,
115        encoder: &mut E,
116        out: &mut MaybeUninit<Self::EncodedOption>,
117    ) -> Result<(), EncodeError> {
118        <&str>::encode_option(this.map(String::as_str), encoder, out)
119    }
120}
121
122impl EncodableOption for &str {
123    type EncodedOption = WireOptionalString<'static>;
124}
125
126unsafe impl<E: Encoder + ?Sized> EncodeOption<E> for &str {
127    #[inline]
128    fn encode_option(
129        this: Option<Self>,
130        encoder: &mut E,
131        out: &mut MaybeUninit<Self::EncodedOption>,
132    ) -> Result<(), EncodeError> {
133        if let Some(string) = this {
134            encoder.write(string.as_bytes());
135            WireOptionalString::encode_present(out, string.len() as u64);
136        } else {
137            WireOptionalString::encode_absent(out);
138        }
139        Ok(())
140    }
141}
142
143impl FromWireOption<WireOptionalString<'_>> for String {
144    #[inline]
145    fn from_wire_option(wire: WireOptionalString<'_>) -> Option<Self> {
146        Vec::from_wire_option(wire.vec).map(|vec| unsafe { String::from_utf8_unchecked(vec) })
147    }
148}
149
150impl FromWireOptionRef<WireOptionalString<'_>> for String {
151    #[inline]
152    fn from_wire_option_ref(wire: &WireOptionalString<'_>) -> Option<Self> {
153        Vec::from_wire_option_ref(&wire.vec).map(|vec| unsafe { String::from_utf8_unchecked(vec) })
154    }
155}