Skip to main content

sark_json/bytes/
proofs.rs

1use std::{ops, str};
2
3use o3::{buffer::storage, mem::Rejected};
4use sark_http::request;
5
6use crate::{Bytes, Token, bytes::seals, input::Input as _};
7
8/// Immutable byte owners supported by JSON admission.
9#[doc(hidden)]
10pub trait Backing: seals::Owner + AsRef<[u8]> {}
11
12impl<T: seals::Owner + AsRef<[u8]>> Backing for T {}
13
14/// An immutable owner whose complete bytes are valid UTF-8.
15#[derive(Copy, Debug)]
16#[repr(transparent)]
17pub struct Text<T> {
18    owner: T,
19}
20
21/// Valid UTF-8 containing no JSON string escape characters.
22#[derive(Copy, Debug)]
23#[repr(transparent)]
24pub struct Plain<T> {
25    text: Text<T>,
26}
27
28/// One complete validated JSON value, including its UTF-8 proof.
29#[derive(Copy, Debug)]
30#[repr(transparent)]
31pub struct Raw<T> {
32    text: Text<T>,
33}
34
35impl<T: Copy> Clone for Text<T> {
36    fn clone(&self) -> Self {
37        *self
38    }
39}
40
41impl<T: Copy> Clone for Plain<T> {
42    fn clone(&self) -> Self {
43        *self
44    }
45}
46
47impl<T: Copy> Clone for Raw<T> {
48    fn clone(&self) -> Self {
49        *self
50    }
51}
52
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub enum PlainError {
55    Utf8(str::Utf8Error),
56    Escape { index: usize, byte: u8 },
57}
58
59impl<T: Backing> Text<T> {
60    pub fn try_new(owner: T) -> Result<Self, Rejected<T, str::Utf8Error>> {
61        if let Err(error) = str::from_utf8(owner.as_ref()) {
62            return Err(Rejected::new(owner, error));
63        }
64        Ok(Self::admitted(owner))
65    }
66}
67
68impl<T: Backing> Plain<T> {
69    pub fn try_new(owner: T) -> Result<Self, Rejected<T, PlainError>> {
70        if let Err(error) = str::from_utf8(owner.as_ref()) {
71            return Err(Rejected::new(owner, PlainError::Utf8(error)));
72        }
73        for (index, &byte) in owner.as_ref().iter().enumerate() {
74            if byte < 0x20 || matches!(byte, b'"' | b'\\') {
75                return Err(Rejected::new(owner, PlainError::Escape { index, byte }));
76            }
77        }
78        Ok(Self::admitted(owner))
79    }
80}
81
82impl<T: Backing> Raw<T> {
83    pub fn try_new(owner: T) -> Result<Self, Rejected<T, crate::Error>> {
84        let input = owner.as_ref();
85        let mut index = 0;
86        if let Err(error) = input
87            .skip_value(&mut index)
88            .and_then(|()| input.finish(&mut index))
89        {
90            return Err(Rejected::new(owner, error));
91        }
92        Ok(Self::admitted(owner))
93    }
94}
95
96impl<T: Backing> TryFrom<Text<T>> for Raw<T> {
97    type Error = Rejected<Text<T>, crate::Error>;
98
99    fn try_from(text: Text<T>) -> Result<Self, Self::Error> {
100        if let Err(error) = crate::input::try_raw(text.as_str()) {
101            return Err(Rejected::new(text, error));
102        }
103        Ok(Self { text })
104    }
105}
106
107impl<'a> From<&'a str> for Text<&'a [u8]> {
108    fn from(owner: &'a str) -> Self {
109        Self::admitted(owner.as_bytes())
110    }
111}
112
113impl From<String> for Text<String> {
114    fn from(owner: String) -> Self {
115        Self::admitted(owner)
116    }
117}
118
119impl From<Token<0>> for Text<Token<0>> {
120    fn from(owner: Token<0>) -> Self {
121        Self::admitted(owner)
122    }
123}
124
125impl<T> From<Plain<T>> for Text<T> {
126    fn from(value: Plain<T>) -> Self {
127        value.text
128    }
129}
130
131impl<T> From<Raw<T>> for Text<T> {
132    fn from(value: Raw<T>) -> Self {
133        value.text
134    }
135}
136
137/// String inputs whose UTF-8 proof survives until the complete write.
138pub trait TextValue: seals::Text {
139    fn as_bytes(&self) -> &[u8];
140}
141
142impl<T: seals::Text + AsRef<[u8]> + ?Sized> TextValue for T {
143    fn as_bytes(&self) -> &[u8] {
144        self.as_ref()
145    }
146}
147impl<T: Backing> seals::Text for Text<T> {}
148impl<T: Backing> seals::Text for Plain<T> {}
149
150impl<T> Text<T> {
151    pub(crate) const fn admitted(owner: T) -> Self {
152        Self { owner }
153    }
154    pub fn into_inner(self) -> T {
155        self.owner
156    }
157}
158impl<T> Plain<T> {
159    pub(crate) const fn admitted(owner: T) -> Self {
160        Self {
161            text: Text::admitted(owner),
162        }
163    }
164    pub fn into_inner(self) -> T {
165        self.text.owner
166    }
167}
168impl<T> Raw<T> {
169    pub(crate) const fn admitted(owner: T) -> Self {
170        Self {
171            text: Text::admitted(owner),
172        }
173    }
174    pub fn into_inner(self) -> T {
175        self.text.owner
176    }
177}
178
179impl<T: Backing> Text<T> {
180    pub fn as_slice(&self) -> &[u8] {
181        self.owner.as_ref()
182    }
183    pub fn as_bytes(&self) -> &[u8] {
184        self.owner.as_ref()
185    }
186
187    pub fn as_str(&self) -> &str {
188        // SAFETY: admission establishes UTF-8 on this complete immutable owner;
189        // no API exposes a mutable owner or changes its byte range under this proof.
190        unsafe { str::from_utf8_unchecked(self.owner.as_ref()) }
191    }
192}
193
194impl<T: Backing> AsRef<[u8]> for Text<T> {
195    fn as_ref(&self) -> &[u8] {
196        self.owner.as_ref()
197    }
198}
199impl<T: Backing> AsRef<[u8]> for Plain<T> {
200    fn as_ref(&self) -> &[u8] {
201        self.text.owner.as_ref()
202    }
203}
204impl<T: Backing> AsRef<[u8]> for Raw<T> {
205    fn as_ref(&self) -> &[u8] {
206        self.text.owner.as_ref()
207    }
208}
209impl<T: Backing> ops::Deref for Text<T> {
210    type Target = [u8];
211    fn deref(&self) -> &[u8] {
212        self.owner.as_ref()
213    }
214}
215impl<T> ops::Deref for Plain<T> {
216    type Target = Text<T>;
217    fn deref(&self) -> &Text<T> {
218        &self.text
219    }
220}
221impl<T> ops::Deref for Raw<T> {
222    type Target = Text<T>;
223    fn deref(&self) -> &Text<T> {
224        &self.text
225    }
226}
227impl<T: Backing, U: AsRef<[u8]>> PartialEq<U> for Text<T> {
228    fn eq(&self, other: &U) -> bool {
229        self.owner.as_ref() == other.as_ref()
230    }
231}
232impl<T: Backing, U: AsRef<[u8]>> PartialEq<U> for Plain<T> {
233    fn eq(&self, other: &U) -> bool {
234        self.text.owner.as_ref() == other.as_ref()
235    }
236}
237impl<T: Backing, U: AsRef<[u8]>> PartialEq<U> for Raw<T> {
238    fn eq(&self, other: &U) -> bool {
239        self.text.owner.as_ref() == other.as_ref()
240    }
241}
242
243impl Text<storage::Shared> {
244    pub fn try_copy(value: &str) -> Result<Self, storage::AllocError> {
245        storage::Shared::try_copy(value.as_bytes()).map(Self::admitted)
246    }
247
248    pub fn into_request<'d>(self, id: request::Id<'d>) -> Text<request::Bytes<'d>> {
249        Text::admitted(request::Bytes::from_shared(id, self.owner))
250    }
251}
252impl Plain<storage::Shared> {
253    pub fn into_request<'d>(self, id: request::Id<'d>) -> Plain<request::Bytes<'d>> {
254        Plain {
255            text: self.text.into_request(id),
256        }
257    }
258}
259impl Raw<storage::Shared> {
260    pub fn into_request<'d>(self, id: request::Id<'d>) -> Raw<request::Bytes<'d>> {
261        Raw {
262            text: self.text.into_request(id),
263        }
264    }
265}
266
267impl Text<Bytes<'_>> {
268    pub fn try_share(self) -> Result<Text<storage::Shared>, Rejected<Self, storage::AllocError>> {
269        self.owner
270            .try_share()
271            .map(Text::admitted)
272            .map_err(|rejected| rejected.map(Text::admitted))
273    }
274}