JMAP protocol layer refactoring (part 1)
This commit is contained in:
@@ -34,9 +34,7 @@ http-body-util = "0.1.0"
|
||||
form_urlencoded = "1.1.0"
|
||||
psl = "2"
|
||||
quick_cache = "0.6.9"
|
||||
downcast-rs = "2.0.1"
|
||||
fast-float = "0.2.0"
|
||||
erased-serde = "0.4.5"
|
||||
rkyv = { version = "0.8.10", features = ["little_endian"] }
|
||||
compact_str = "0.9.0"
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod parser;
|
||||
pub mod pointer;
|
||||
|
||||
use downcast_rs::{Downcast, impl_downcast};
|
||||
use std::{fmt::Debug, slice::Iter};
|
||||
|
||||
pub trait JsonQueryable: Downcast + Debug + 'static {
|
||||
fn eval_pointer<'x>(
|
||||
&'x self,
|
||||
pointer: Iter<JsonPointerItem>,
|
||||
results: &mut Vec<&'x dyn JsonQueryable>,
|
||||
);
|
||||
}
|
||||
|
||||
impl_downcast!(JsonQueryable);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize)]
|
||||
pub struct JsonPointer(pub Vec<JsonPointerItem>);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize)]
|
||||
pub enum JsonPointerItem {
|
||||
Root,
|
||||
Wildcard,
|
||||
String(String),
|
||||
Number(u64),
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::codec::{base32_custom::BASE32_INVERSE, leb128::Leb128Iterator};
|
||||
|
||||
use super::json::Parser;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct JsonBase32Reader<'x, 'y> {
|
||||
bytes: &'y mut Parser<'x>,
|
||||
last_byte: u8,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'x, 'y> JsonBase32Reader<'x, 'y> {
|
||||
pub fn new(bytes: &'y mut Parser<'x>) -> Self {
|
||||
JsonBase32Reader {
|
||||
bytes,
|
||||
pos: 0,
|
||||
last_byte: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn map_byte(&mut self) -> Option<u8> {
|
||||
match self.bytes.next_unescaped() {
|
||||
Ok(Some(byte)) => match BASE32_INVERSE[byte as usize] {
|
||||
decoded_byte if decoded_byte != u8::MAX => {
|
||||
self.last_byte = decoded_byte;
|
||||
Some(decoded_byte)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error(&mut self) -> trc::Error {
|
||||
self.bytes.error_value()
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for JsonBase32Reader<'_, '_> {
|
||||
type Item = u8;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let pos = self.pos % 5;
|
||||
let last_byte = self.last_byte;
|
||||
let byte = self.map_byte()?;
|
||||
self.pos += 1;
|
||||
|
||||
match pos {
|
||||
0 => ((byte << 3) | (self.map_byte().unwrap_or(0) >> 2)).into(),
|
||||
1 => ((last_byte << 6) | (byte << 1) | (self.map_byte().unwrap_or(0) >> 4)).into(),
|
||||
2 => ((last_byte << 4) | (byte >> 1)).into(),
|
||||
3 => ((last_byte << 7) | (byte << 2) | (self.map_byte().unwrap_or(0) >> 3)).into(),
|
||||
4 => ((last_byte << 5) | byte).into(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Leb128Iterator<u8> for JsonBase32Reader<'_, '_> {}
|
||||
@@ -1,308 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::map::{
|
||||
bitmap::{Bitmap, BitmapItem},
|
||||
vec_map::VecMap,
|
||||
};
|
||||
|
||||
use super::{Ignore, JsonObjectParser, Token, json::Parser};
|
||||
|
||||
impl JsonObjectParser for u64 {
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut hash = 0;
|
||||
let mut shift = 0;
|
||||
|
||||
while let Some(ch) = parser.next_unescaped()? {
|
||||
if shift < 64 {
|
||||
hash |= (ch as u64) << shift;
|
||||
shift += 8;
|
||||
} else {
|
||||
hash = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonObjectParser for u128 {
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut hash = 0;
|
||||
let mut shift = 0;
|
||||
|
||||
while let Some(ch) = parser.next_unescaped()? {
|
||||
if shift < 128 {
|
||||
hash |= (ch as u128) << shift;
|
||||
shift += 8;
|
||||
} else {
|
||||
hash = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonObjectParser for String {
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let start_pos = parser.pos;
|
||||
|
||||
while let Some(ch) = parser.next_char() {
|
||||
match ch {
|
||||
b'\\' => {
|
||||
let mut is_escaped = true;
|
||||
let mut buf = Vec::with_capacity((parser.pos - start_pos) + 16);
|
||||
buf.extend_from_slice(&parser.bytes[start_pos..parser.pos - 1]);
|
||||
|
||||
while let Some(ch) = parser.next_char() {
|
||||
match ch {
|
||||
b'\\' if !is_escaped => {
|
||||
is_escaped = true;
|
||||
}
|
||||
b'"' if !is_escaped => {
|
||||
parser.is_eof = true;
|
||||
return String::from_utf8(buf).map_err(|_| parser.error_utf8());
|
||||
}
|
||||
_ => {
|
||||
if !is_escaped {
|
||||
buf.push(ch);
|
||||
} else {
|
||||
match ch {
|
||||
b'"' => {
|
||||
buf.push(b'"');
|
||||
}
|
||||
b'\\' => {
|
||||
buf.push(b'\\');
|
||||
}
|
||||
b'n' => {
|
||||
buf.push(b'\n');
|
||||
}
|
||||
b't' => {
|
||||
buf.push(b'\t');
|
||||
}
|
||||
b'r' => {
|
||||
buf.push(b'\r');
|
||||
}
|
||||
b'b' => {
|
||||
buf.push(0x08);
|
||||
}
|
||||
b'f' => {
|
||||
buf.push(0x0c);
|
||||
}
|
||||
b'/' => {
|
||||
buf.push(b'/');
|
||||
}
|
||||
b'u' => {
|
||||
let mut code = [
|
||||
*parser.iter.next().ok_or_else(|| {
|
||||
parser.error("Incomplete unicode sequence")
|
||||
})?,
|
||||
*parser.iter.next().ok_or_else(|| {
|
||||
parser.error("Incomplete unicode sequence")
|
||||
})?,
|
||||
*parser.iter.next().ok_or_else(|| {
|
||||
parser.error("Incomplete unicode sequence")
|
||||
})?,
|
||||
*parser.iter.next().ok_or_else(|| {
|
||||
parser.error("Incomplete unicode sequence")
|
||||
})?,
|
||||
];
|
||||
parser.pos += 4;
|
||||
let code_str = std::str::from_utf8(&code)
|
||||
.map_err(|_| parser.error_utf8())?;
|
||||
let code_str = char::from_u32(
|
||||
u32::from_str_radix(code_str, 16).map_err(
|
||||
|_| {
|
||||
parser.error(&format!(
|
||||
"Invalid unicode sequence {code_str}"
|
||||
))
|
||||
},
|
||||
)?,
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
parser.error(&format!(
|
||||
"Invalid unicode sequence {code_str}"
|
||||
))
|
||||
})?
|
||||
.encode_utf8(&mut code);
|
||||
buf.extend_from_slice(code_str.as_bytes());
|
||||
}
|
||||
_ => {
|
||||
buf.push(ch);
|
||||
}
|
||||
}
|
||||
is_escaped = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
b'"' => {
|
||||
parser.is_eof = true;
|
||||
return std::str::from_utf8(
|
||||
parser
|
||||
.bytes
|
||||
.get(start_pos..parser.pos - 1)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.map(Into::into)
|
||||
.map_err(|_| parser.error_utf8());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Err(parser.error_unterminated())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: JsonObjectParser + Eq> JsonObjectParser for Vec<T> {
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut vec = Vec::new();
|
||||
|
||||
parser.next_token::<Ignore>()?.assert(Token::ArrayStart)?;
|
||||
loop {
|
||||
match parser.next_token::<T>()? {
|
||||
Token::String(item) => vec.push(item),
|
||||
Token::Comma => (),
|
||||
Token::ArrayEnd => break,
|
||||
token => return Err(token.error("", "[ or string")),
|
||||
}
|
||||
}
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: JsonObjectParser + Eq> JsonObjectParser for Option<Vec<T>> {
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match parser.next_token::<Ignore>()? {
|
||||
Token::ArrayStart => {
|
||||
let mut vec = Vec::new();
|
||||
loop {
|
||||
match parser.next_token::<T>()? {
|
||||
Token::String(item) => vec.push(item),
|
||||
Token::Comma => (),
|
||||
Token::ArrayEnd => break,
|
||||
token => return Err(token.error("", "string")),
|
||||
}
|
||||
}
|
||||
Ok(Some(vec))
|
||||
}
|
||||
Token::Null => Ok(None),
|
||||
token => Err(token.error("", "array or null")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: JsonObjectParser + Eq + BitmapItem> JsonObjectParser for Bitmap<T> {
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut bm = Bitmap::new();
|
||||
match parser.next_token::<Ignore>()? {
|
||||
Token::ArrayStart => {
|
||||
loop {
|
||||
match parser.next_token::<T>()? {
|
||||
Token::String(item) => bm.insert(item),
|
||||
Token::Comma => (),
|
||||
Token::ArrayEnd => break,
|
||||
token => return Err(token.error("", "string")),
|
||||
}
|
||||
}
|
||||
Ok(bm)
|
||||
}
|
||||
Token::Null => Ok(bm),
|
||||
token => Err(token.error("", "array or null")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: JsonObjectParser + Eq + Display, V: JsonObjectParser> JsonObjectParser for VecMap<K, V> {
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut map = VecMap::new();
|
||||
|
||||
parser.next_token::<Ignore>()?.assert(Token::DictStart)?;
|
||||
while let Some(key) = parser.next_dict_key()? {
|
||||
map.append(key, V::parse(parser)?);
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: JsonObjectParser + Eq + Display, V: JsonObjectParser> JsonObjectParser
|
||||
for Option<VecMap<K, V>>
|
||||
{
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match parser.next_token::<Ignore>()? {
|
||||
Token::DictStart => {
|
||||
let mut map = VecMap::new();
|
||||
|
||||
while let Some(key) = parser.next_dict_key()? {
|
||||
map.append(key, V::parse(parser)?);
|
||||
}
|
||||
|
||||
Ok(Some(map))
|
||||
}
|
||||
Token::Null => Ok(None),
|
||||
token => Err(token.error("", &token.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonObjectParser for bool {
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match parser.next_token::<Ignore>()? {
|
||||
Token::Boolean(value) => Ok(value),
|
||||
Token::Null => Ok(false),
|
||||
token => Err(token.error("", &token.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonObjectParser for Ignore {
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if parser.skip_string() {
|
||||
Ok(Ignore {})
|
||||
} else {
|
||||
Err(parser.error_unterminated())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,390 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::{fmt::Display, iter::Peekable, slice::Iter};
|
||||
|
||||
use compact_str::format_compact;
|
||||
|
||||
use super::{Ignore, JsonObjectParser, Token};
|
||||
|
||||
const MAX_NESTED_LEVELS: u32 = 16;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Parser<'x> {
|
||||
pub bytes: &'x [u8],
|
||||
pub iter: Peekable<Iter<'x, u8>>,
|
||||
pub next_ch: Option<u8>,
|
||||
pub pos: usize,
|
||||
pub pos_marker: usize,
|
||||
pub depth_array: u32,
|
||||
pub depth_dict: u32,
|
||||
pub is_eof: bool,
|
||||
}
|
||||
|
||||
impl<'x> Parser<'x> {
|
||||
pub fn new(bytes: &'x [u8]) -> Self {
|
||||
Self {
|
||||
bytes,
|
||||
iter: bytes.iter().peekable(),
|
||||
next_ch: None,
|
||||
pos: 0,
|
||||
pos_marker: 0,
|
||||
is_eof: false,
|
||||
depth_array: 0,
|
||||
depth_dict: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error(&self, message: &str) -> trc::Error {
|
||||
trc::JmapEvent::NotJson
|
||||
.into_err()
|
||||
.details(format_compact!("{message} at position {}.", self.pos))
|
||||
}
|
||||
|
||||
pub fn error_unterminated(&self) -> trc::Error {
|
||||
trc::JmapEvent::NotJson.into_err().details(format_compact!(
|
||||
"Unterminated string at position {pos}.",
|
||||
pos = self.pos
|
||||
))
|
||||
}
|
||||
|
||||
pub fn error_utf8(&self) -> trc::Error {
|
||||
trc::JmapEvent::NotJson.into_err().details(format_compact!(
|
||||
"Invalid UTF-8 sequence at position {pos}.",
|
||||
pos = self.pos
|
||||
))
|
||||
}
|
||||
|
||||
pub fn error_value(&mut self) -> trc::Error {
|
||||
if self.is_eof || self.skip_string() {
|
||||
trc::JmapEvent::InvalidArguments
|
||||
.into_err()
|
||||
.details(format_compact!(
|
||||
"Invalid value {:?} at position {}.",
|
||||
String::from_utf8_lossy(self.bytes[self.pos_marker..self.pos - 1].as_ref()),
|
||||
self.pos
|
||||
))
|
||||
} else {
|
||||
self.error_unterminated()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn peek_char(&mut self) -> Option<u8> {
|
||||
self.iter.peek().map(|&&ch| ch)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn next_char(&mut self) -> Option<u8> {
|
||||
self.pos += 1;
|
||||
self.iter.next().copied()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn next_unescaped(&mut self) -> trc::Result<Option<u8>> {
|
||||
match self.next_char() {
|
||||
Some(b'"') => {
|
||||
self.is_eof = true;
|
||||
Ok(None)
|
||||
}
|
||||
Some(b'\\') => self
|
||||
.next_char()
|
||||
.ok_or_else(|| self.error_unterminated())
|
||||
.map(Some),
|
||||
Some(ch) => Ok(Some(ch)),
|
||||
None => {
|
||||
if self.is_eof {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(self.error_unterminated())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn skip_string(&mut self) -> bool {
|
||||
let mut last_ch = 0;
|
||||
|
||||
while let Some(ch) = self.next_char() {
|
||||
if ch == b'"' && last_ch != b'\\' {
|
||||
self.is_eof = true;
|
||||
return true;
|
||||
} else {
|
||||
last_ch = ch;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn next_token<T: JsonObjectParser>(&mut self) -> trc::Result<Token<T>> {
|
||||
let mut next_ch = self.next_ch.take().or_else(|| self.next_char());
|
||||
|
||||
while let Some(mut ch) = next_ch {
|
||||
match ch {
|
||||
b'"' => {
|
||||
self.pos_marker = self.pos;
|
||||
self.is_eof = false;
|
||||
let value = T::parse(self)?;
|
||||
return if self.is_eof || self.skip_string() {
|
||||
Ok(Token::String(value))
|
||||
} else {
|
||||
Err(self.error_unterminated())
|
||||
};
|
||||
}
|
||||
b',' => {
|
||||
return Ok(Token::Comma);
|
||||
}
|
||||
b':' => {
|
||||
return Ok(Token::Colon);
|
||||
}
|
||||
b'[' => {
|
||||
if self.depth_array + self.depth_dict < MAX_NESTED_LEVELS {
|
||||
self.depth_array += 1;
|
||||
return Ok(Token::ArrayStart);
|
||||
} else {
|
||||
return Err(self.error("Too many nested objects"));
|
||||
}
|
||||
}
|
||||
b']' => {
|
||||
return if self.depth_array != 0 {
|
||||
self.depth_array -= 1;
|
||||
Ok(Token::ArrayEnd)
|
||||
} else {
|
||||
Err(self.error("Unexpected array end"))
|
||||
};
|
||||
}
|
||||
b'{' => {
|
||||
if self.depth_array + self.depth_dict < MAX_NESTED_LEVELS {
|
||||
self.depth_dict += 1;
|
||||
return Ok(Token::DictStart);
|
||||
} else {
|
||||
return Err(self.error("Too many nested objects"));
|
||||
}
|
||||
}
|
||||
b'}' => {
|
||||
return if self.depth_dict != 0 {
|
||||
self.depth_dict -= 1;
|
||||
Ok(Token::DictEnd)
|
||||
} else {
|
||||
Err(self.error("Unexpected dictionary end"))
|
||||
};
|
||||
}
|
||||
b'0'..=b'9' | b'-' | b'+' => {
|
||||
let mut num: i64 = 0;
|
||||
let mut is_float = false;
|
||||
let mut is_negative = false;
|
||||
let num_start = self.pos - 1;
|
||||
|
||||
loop {
|
||||
match ch {
|
||||
b'-' => {
|
||||
is_negative = true;
|
||||
}
|
||||
b'0'..=b'9' => {
|
||||
if !is_float {
|
||||
num = num.saturating_mul(10).saturating_add((ch - b'0') as i64);
|
||||
}
|
||||
}
|
||||
b',' | b']' | b'}' => {
|
||||
self.next_ch = ch.into();
|
||||
break;
|
||||
}
|
||||
b'+' => (),
|
||||
b'.' | b'e' | b'E' => {
|
||||
is_float = true;
|
||||
}
|
||||
b' ' | b'\r' | b'\t' | b'\n' => {
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
return Err(self
|
||||
.error(&format!("Unexpected character {:?}", char::from(ch))));
|
||||
}
|
||||
}
|
||||
|
||||
ch = self.next_char().ok_or_else(|| self.error_unterminated())?;
|
||||
}
|
||||
|
||||
return if !is_float {
|
||||
Ok(Token::Integer(if !is_negative { num } else { -num }))
|
||||
} else {
|
||||
fast_float::parse(
|
||||
self.bytes.get(num_start..self.pos - 1).unwrap_or_default(),
|
||||
)
|
||||
.map(Token::Float)
|
||||
.map_err(|_| {
|
||||
self.error(&format!(
|
||||
"Failed to parse number {:?}",
|
||||
String::from_utf8_lossy(
|
||||
self.bytes.get(num_start..self.pos - 1).unwrap_or_default()
|
||||
)
|
||||
))
|
||||
})
|
||||
};
|
||||
}
|
||||
b't' => {
|
||||
return if let (Some(b'r'), Some(b'u'), Some(b'e')) =
|
||||
(self.iter.next(), self.iter.next(), self.iter.next())
|
||||
{
|
||||
self.pos += 3;
|
||||
Ok(Token::Boolean(true))
|
||||
} else {
|
||||
Err(self.error("Invalid JSON token"))
|
||||
};
|
||||
}
|
||||
b'f' => {
|
||||
return if let (Some(b'a'), Some(b'l'), Some(b's'), Some(b'e')) = (
|
||||
self.iter.next(),
|
||||
self.iter.next(),
|
||||
self.iter.next(),
|
||||
self.iter.next(),
|
||||
) {
|
||||
self.pos += 4;
|
||||
Ok(Token::Boolean(false))
|
||||
} else {
|
||||
Err(self.error("Invalid JSON token"))
|
||||
};
|
||||
}
|
||||
b'n' => {
|
||||
return if let (Some(b'u'), Some(b'l'), Some(b'l')) =
|
||||
(self.iter.next(), self.iter.next(), self.iter.next())
|
||||
{
|
||||
self.pos += 3;
|
||||
Ok(Token::Null)
|
||||
} else {
|
||||
Err(self.error("Invalid JSON token"))
|
||||
};
|
||||
}
|
||||
b' ' | b'\t' | b'\r' | b'\n' => (),
|
||||
_ => {
|
||||
return Err(self.error(&format!("Unexpected character {:?}", char::from(ch))));
|
||||
}
|
||||
}
|
||||
|
||||
next_ch = self.next_char();
|
||||
}
|
||||
|
||||
Err(self.error("Unexpected EOF"))
|
||||
}
|
||||
|
||||
pub fn next_dict_key<T: JsonObjectParser + Display + Eq>(&mut self) -> trc::Result<Option<T>> {
|
||||
loop {
|
||||
match self.next_token::<T>()? {
|
||||
Token::String(k) => {
|
||||
self.next_token::<T>()?.assert(Token::Colon)?;
|
||||
return Ok(Some(k));
|
||||
}
|
||||
Token::Comma => (),
|
||||
Token::DictEnd => return Ok(None),
|
||||
token => {
|
||||
return Err(self.error(&format!("Expected object property, found {}", token)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn skip_token(&mut self, start_depth_array: u32, start_depth_dict: u32) -> trc::Result<()> {
|
||||
while {
|
||||
self.next_token::<Ignore>()?;
|
||||
start_depth_array != self.depth_array || start_depth_dict != self.depth_dict
|
||||
} {}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::json::parser::Token;
|
||||
|
||||
use super::Parser;
|
||||
|
||||
#[test]
|
||||
fn parse_json() {
|
||||
for (input, expected_result) in [
|
||||
(
|
||||
&b"[true, false, 123, 456 , -123, 0.123, -0.456, 3.7e-5, 6.02e+23, null]"[..],
|
||||
vec![
|
||||
Token::ArrayStart,
|
||||
Token::Boolean(true),
|
||||
Token::Comma,
|
||||
Token::Boolean(false),
|
||||
Token::Comma,
|
||||
Token::Integer(123),
|
||||
Token::Comma,
|
||||
Token::Integer(456),
|
||||
Token::Comma,
|
||||
Token::Integer(-123),
|
||||
Token::Comma,
|
||||
Token::Float(0.123),
|
||||
Token::Comma,
|
||||
Token::Float(-0.456),
|
||||
Token::Comma,
|
||||
Token::Float(3.7e-5),
|
||||
Token::Comma,
|
||||
Token::Float(6.02e23),
|
||||
Token::Comma,
|
||||
Token::Null,
|
||||
Token::ArrayEnd,
|
||||
],
|
||||
),
|
||||
(
|
||||
&b"{\"\": true, \"\": false , \"\": {\"\": 123}, \"\": [ ]}"[..],
|
||||
vec![
|
||||
Token::DictStart,
|
||||
Token::String("".to_string()),
|
||||
Token::Colon,
|
||||
Token::Boolean(true),
|
||||
Token::Comma,
|
||||
Token::String("".to_string()),
|
||||
Token::Colon,
|
||||
Token::Boolean(false),
|
||||
Token::Comma,
|
||||
Token::String("".to_string()),
|
||||
Token::Colon,
|
||||
Token::DictStart,
|
||||
Token::String("".to_string()),
|
||||
Token::Colon,
|
||||
Token::Integer(123),
|
||||
Token::DictEnd,
|
||||
Token::Comma,
|
||||
Token::String("".to_string()),
|
||||
Token::Colon,
|
||||
Token::ArrayStart,
|
||||
Token::ArrayEnd,
|
||||
Token::DictEnd,
|
||||
],
|
||||
),
|
||||
] {
|
||||
let mut p = Parser::new(input);
|
||||
let mut result = Vec::new();
|
||||
while let Ok(token) = p.next_token() {
|
||||
result.push(token);
|
||||
}
|
||||
|
||||
assert_eq!(result, expected_result);
|
||||
}
|
||||
|
||||
for (input, expected_result) in [
|
||||
("hello\t\nworld", "hello\t\nworld"),
|
||||
("hello\t\n\\\"world\\\"\\n", "hello\t\n\"world\"\n"),
|
||||
("\\\"hello\\\tworld\\\"", "\"hello\tworld\""),
|
||||
("\\u0009\\u0020\\u263A", "\t ☺"),
|
||||
("", ""),
|
||||
] {
|
||||
assert_eq!(
|
||||
Parser::new(format!("\"{input}\"").as_bytes())
|
||||
.next_token::<String>()
|
||||
.unwrap()
|
||||
.unwrap_string("")
|
||||
.unwrap(),
|
||||
expected_result
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use compact_str::format_compact;
|
||||
|
||||
use self::json::Parser;
|
||||
|
||||
pub mod base32;
|
||||
pub mod impls;
|
||||
pub mod json;
|
||||
pub mod pointer;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Token<T> {
|
||||
Colon,
|
||||
Comma,
|
||||
DictStart,
|
||||
DictEnd,
|
||||
ArrayStart,
|
||||
ArrayEnd,
|
||||
Integer(i64),
|
||||
Float(f64),
|
||||
Boolean(bool),
|
||||
String(T),
|
||||
Null,
|
||||
}
|
||||
|
||||
impl<T: PartialEq> Eq for Token<T> {}
|
||||
|
||||
pub trait JsonObjectParser {
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Ignore {}
|
||||
|
||||
impl<T: Eq> Token<T> {
|
||||
pub fn unwrap_string(self, property: &str) -> trc::Result<T> {
|
||||
match self {
|
||||
Token::String(s) => Ok(s),
|
||||
token => Err(token.error(property, "string")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_string_or_null(self, property: &str) -> trc::Result<Option<T>> {
|
||||
match self {
|
||||
Token::String(s) => Ok(Some(s)),
|
||||
Token::Null => Ok(None),
|
||||
token => Err(token.error(property, "string")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_bool(self, property: &str) -> trc::Result<bool> {
|
||||
match self {
|
||||
Token::Boolean(v) => Ok(v),
|
||||
token => Err(token.error(property, "boolean")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_bool_or_null(self, property: &str) -> trc::Result<Option<bool>> {
|
||||
match self {
|
||||
Token::Boolean(v) => Ok(Some(v)),
|
||||
Token::Null => Ok(None),
|
||||
token => Err(token.error(property, "boolean")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_usize_or_null(self, property: &str) -> trc::Result<Option<usize>> {
|
||||
match self {
|
||||
Token::Integer(v) if v >= 0 => Ok(Some(v as usize)),
|
||||
Token::Float(v) if v >= 0.0 => Ok(Some(v as usize)),
|
||||
Token::Null => Ok(None),
|
||||
token => Err(token.error(property, "unsigned integer")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_uint_or_null(self, property: &str) -> trc::Result<Option<u64>> {
|
||||
match self {
|
||||
Token::Integer(v) if v >= 0 => Ok(Some(v as u64)),
|
||||
Token::Float(v) if v >= 0.0 => Ok(Some(v as u64)),
|
||||
Token::Null => Ok(None),
|
||||
token => Err(token.error(property, "unsigned integer")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_int_or_null(self, property: &str) -> trc::Result<Option<i64>> {
|
||||
match self {
|
||||
Token::Integer(v) => Ok(Some(v)),
|
||||
Token::Float(v) => Ok(Some(v as i64)),
|
||||
Token::Null => Ok(None),
|
||||
token => Err(token.error(property, "unsigned integer")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_ints_or_null(self, property: &str) -> trc::Result<Option<i32>> {
|
||||
match self {
|
||||
Token::Integer(v) => Ok(Some(v as i32)),
|
||||
Token::Float(v) => Ok(Some(v as i32)),
|
||||
Token::Null => Ok(None),
|
||||
token => Err(token.error(property, "unsigned integer")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert(self, token: Token<T>) -> trc::Result<()> {
|
||||
if self == token {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self.error("", &token.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert_jmap(self, token: Token<T>) -> trc::Result<()> {
|
||||
if self == token {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(trc::JmapEvent::NotRequest
|
||||
.into_err()
|
||||
.details(format_compact!(
|
||||
"Invalid JMAP request: expected '{token}', got '{self}'."
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error(&self, property: &str, expected: &str) -> trc::Error {
|
||||
trc::JmapEvent::InvalidArguments
|
||||
.into_err()
|
||||
.details(if !property.is_empty() {
|
||||
format_compact!(
|
||||
"Invalid argument for '{property:?}': expected '{expected}', got '{self}'.",
|
||||
)
|
||||
} else {
|
||||
format_compact!("Invalid argument: expected '{expected}', got '{self}'.")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ignore {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "string")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Display for Token<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Token::Colon => write!(f, ":"),
|
||||
Token::Comma => write!(f, ","),
|
||||
Token::DictStart => write!(f, "{{"),
|
||||
Token::DictEnd => write!(f, "}}"),
|
||||
Token::ArrayStart => write!(f, "["),
|
||||
Token::ArrayEnd => write!(f, "]"),
|
||||
Token::Integer(i) => write!(f, "{}", i),
|
||||
Token::Float(v) => write!(f, "{}", v),
|
||||
Token::Boolean(b) => write!(f, "{}", b),
|
||||
Token::Null => write!(f, "null"),
|
||||
Token::String(_) => write!(f, "string"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::json::{JsonPointer, JsonPointerItem};
|
||||
|
||||
use super::{JsonObjectParser, json::Parser};
|
||||
|
||||
enum TokenType {
|
||||
Unknown,
|
||||
Number,
|
||||
String,
|
||||
Wildcard,
|
||||
Escaped,
|
||||
}
|
||||
|
||||
impl JsonObjectParser for JsonPointer {
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut path = Vec::new();
|
||||
let mut num = 0u64;
|
||||
let mut buf = Vec::new();
|
||||
let mut token = TokenType::Unknown;
|
||||
let mut start_pos = parser.pos;
|
||||
|
||||
while let Some(ch) = parser.next_char() {
|
||||
match (ch, &token) {
|
||||
(b'0'..=b'9', TokenType::Unknown | TokenType::Number) => {
|
||||
num = num.saturating_mul(10).saturating_add((ch - b'0') as u64);
|
||||
token = TokenType::Number;
|
||||
}
|
||||
(b'*', TokenType::Unknown) => {
|
||||
token = TokenType::Wildcard;
|
||||
}
|
||||
(b'0', TokenType::Escaped) => {
|
||||
buf.push(b'~');
|
||||
token = TokenType::String;
|
||||
}
|
||||
(b'1', TokenType::Escaped) => {
|
||||
buf.push(b'/');
|
||||
token = TokenType::String;
|
||||
}
|
||||
(b'/' | b'"', _) => {
|
||||
match token {
|
||||
TokenType::String => {
|
||||
path.push(JsonPointerItem::String(
|
||||
String::from_utf8(buf).map_err(|_| parser.error_utf8())?,
|
||||
));
|
||||
buf = Vec::new();
|
||||
}
|
||||
TokenType::Number => {
|
||||
path.push(JsonPointerItem::Number(num));
|
||||
num = 0;
|
||||
}
|
||||
TokenType::Wildcard => {
|
||||
path.push(JsonPointerItem::Wildcard);
|
||||
}
|
||||
TokenType::Unknown if parser.pos_marker != start_pos => {
|
||||
path.push(JsonPointerItem::String(String::new()));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if ch == b'/' {
|
||||
token = TokenType::Unknown;
|
||||
start_pos = parser.pos;
|
||||
} else {
|
||||
parser.is_eof = true;
|
||||
|
||||
if path.is_empty() {
|
||||
path.push(JsonPointerItem::Root);
|
||||
}
|
||||
|
||||
return Ok(JsonPointer(path));
|
||||
}
|
||||
}
|
||||
(_, _) => {
|
||||
if matches!(&token, TokenType::Number | TokenType::Wildcard)
|
||||
&& parser.pos - 1 > start_pos
|
||||
{
|
||||
buf.extend_from_slice(
|
||||
parser
|
||||
.bytes
|
||||
.get(start_pos..parser.pos - 1)
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
|
||||
token = match ch {
|
||||
b'~' if !matches!(&token, TokenType::Escaped) => TokenType::Escaped,
|
||||
b'\\' => {
|
||||
buf.push(parser.next_char().unwrap_or(b'\\'));
|
||||
TokenType::String
|
||||
}
|
||||
_ => {
|
||||
buf.push(ch);
|
||||
TokenType::String
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(parser.error_unterminated())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for JsonPointer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for (i, ptr) in self.0.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, "/")?;
|
||||
}
|
||||
write!(f, "{}", ptr)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for JsonPointerItem {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
JsonPointerItem::Root => write!(f, "/"),
|
||||
JsonPointerItem::Wildcard => write!(f, "*"),
|
||||
JsonPointerItem::String(s) => write!(f, "{}", s),
|
||||
JsonPointerItem::Number(n) => write!(f, "{}", n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::json::parser::json::Parser;
|
||||
|
||||
use super::{JsonPointer, JsonPointerItem};
|
||||
|
||||
#[test]
|
||||
fn json_pointer_parse() {
|
||||
for (input, output) in vec![
|
||||
("hello", vec![JsonPointerItem::String("hello".to_string())]),
|
||||
("9a", vec![JsonPointerItem::String("9a".to_string())]),
|
||||
("a9", vec![JsonPointerItem::String("a9".to_string())]),
|
||||
("*a", vec![JsonPointerItem::String("*a".to_string())]),
|
||||
(
|
||||
"/hello/world",
|
||||
vec![
|
||||
JsonPointerItem::String("hello".to_string()),
|
||||
JsonPointerItem::String("world".to_string()),
|
||||
],
|
||||
),
|
||||
("*", vec![JsonPointerItem::Wildcard]),
|
||||
(
|
||||
"/hello/*",
|
||||
vec![
|
||||
JsonPointerItem::String("hello".to_string()),
|
||||
JsonPointerItem::Wildcard,
|
||||
],
|
||||
),
|
||||
("1234", vec![JsonPointerItem::Number(1234)]),
|
||||
(
|
||||
"/hello/1234",
|
||||
vec![
|
||||
JsonPointerItem::String("hello".to_string()),
|
||||
JsonPointerItem::Number(1234),
|
||||
],
|
||||
),
|
||||
("~0~1", vec![JsonPointerItem::String("~/".to_string())]),
|
||||
(
|
||||
"/hello/~0~1",
|
||||
vec![
|
||||
JsonPointerItem::String("hello".to_string()),
|
||||
JsonPointerItem::String("~/".to_string()),
|
||||
],
|
||||
),
|
||||
(
|
||||
"/hello/1~0~1/*~1~0",
|
||||
vec![
|
||||
JsonPointerItem::String("hello".to_string()),
|
||||
JsonPointerItem::String("1~/".to_string()),
|
||||
JsonPointerItem::String("*/~".to_string()),
|
||||
],
|
||||
),
|
||||
(
|
||||
"/hello/world/*/99",
|
||||
vec![
|
||||
JsonPointerItem::String("hello".to_string()),
|
||||
JsonPointerItem::String("world".to_string()),
|
||||
JsonPointerItem::Wildcard,
|
||||
JsonPointerItem::Number(99),
|
||||
],
|
||||
),
|
||||
("/", vec![JsonPointerItem::String("".to_string())]),
|
||||
(
|
||||
"///",
|
||||
vec![
|
||||
JsonPointerItem::String("".to_string()),
|
||||
JsonPointerItem::String("".to_string()),
|
||||
JsonPointerItem::String("".to_string()),
|
||||
],
|
||||
),
|
||||
("", vec![JsonPointerItem::Root]),
|
||||
] {
|
||||
assert_eq!(
|
||||
Parser::new(format!("\"{input}\"").as_bytes())
|
||||
.next_token::<JsonPointer>()
|
||||
.unwrap()
|
||||
.unwrap_string("")
|
||||
.unwrap()
|
||||
.0,
|
||||
output,
|
||||
"{input}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{JsonPointerItem, JsonQueryable};
|
||||
use std::hash::BuildHasher;
|
||||
use std::{collections::HashMap, slice::Iter};
|
||||
|
||||
impl<T: JsonQueryable> JsonQueryable for Vec<T> {
|
||||
fn eval_pointer<'x>(
|
||||
&'x self,
|
||||
mut pointer: Iter<JsonPointerItem>,
|
||||
results: &mut Vec<&'x dyn JsonQueryable>,
|
||||
) {
|
||||
match pointer.next() {
|
||||
Some(JsonPointerItem::Number(n)) => {
|
||||
if let Some(v) = self.get(*n as usize) {
|
||||
v.eval_pointer(pointer, results);
|
||||
}
|
||||
}
|
||||
Some(JsonPointerItem::Wildcard) => {
|
||||
for v in self {
|
||||
v.eval_pointer(pointer.clone(), results);
|
||||
}
|
||||
}
|
||||
Some(JsonPointerItem::Root) | None => {
|
||||
results.push(self);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: JsonQueryable, S: BuildHasher + Default + 'static> JsonQueryable for HashMap<String, V, S> {
|
||||
fn eval_pointer<'x>(
|
||||
&'x self,
|
||||
mut pointer: Iter<JsonPointerItem>,
|
||||
results: &mut Vec<&'x dyn JsonQueryable>,
|
||||
) {
|
||||
match pointer.next() {
|
||||
Some(JsonPointerItem::String(n)) => {
|
||||
if let Some(v) = self.get(n) {
|
||||
v.eval_pointer(pointer, results);
|
||||
}
|
||||
}
|
||||
Some(JsonPointerItem::Number(n)) => {
|
||||
let n = n.to_string();
|
||||
if let Some(v) = self.get(&n) {
|
||||
v.eval_pointer(pointer, results);
|
||||
}
|
||||
}
|
||||
Some(JsonPointerItem::Wildcard) => {
|
||||
for v in self.values() {
|
||||
v.eval_pointer(pointer.clone(), results);
|
||||
}
|
||||
}
|
||||
Some(JsonPointerItem::Root) | None => {
|
||||
results.push(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonQueryable for serde_json::Value {
|
||||
fn eval_pointer<'x>(
|
||||
&'x self,
|
||||
mut pointer: Iter<JsonPointerItem>,
|
||||
results: &mut Vec<&'x dyn JsonQueryable>,
|
||||
) {
|
||||
match pointer.next() {
|
||||
Some(JsonPointerItem::String(n)) => {
|
||||
if let serde_json::Value::Object(map) = self
|
||||
&& let Some(v) = map.get(n)
|
||||
{
|
||||
v.eval_pointer(pointer, results);
|
||||
}
|
||||
}
|
||||
Some(JsonPointerItem::Number(n)) => match self {
|
||||
serde_json::Value::Array(values) => {
|
||||
if let Some(v) = values.get(*n as usize) {
|
||||
v.eval_pointer(pointer, results);
|
||||
}
|
||||
}
|
||||
serde_json::Value::Object(map) => {
|
||||
let n = n.to_string();
|
||||
if let Some(v) = map.get(&n) {
|
||||
v.eval_pointer(pointer, results);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(JsonPointerItem::Wildcard) => match self {
|
||||
serde_json::Value::Array(values) => {
|
||||
for v in values {
|
||||
v.eval_pointer(pointer.clone(), results);
|
||||
}
|
||||
}
|
||||
serde_json::Value::Object(map) => {
|
||||
for v in map.values() {
|
||||
v.eval_pointer(pointer.clone(), results);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(JsonPointerItem::Root) | None => {
|
||||
results.push(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ pub mod cache;
|
||||
pub mod codec;
|
||||
pub mod config;
|
||||
pub mod glob;
|
||||
pub mod json;
|
||||
pub mod map;
|
||||
pub mod snowflake;
|
||||
pub mod template;
|
||||
@@ -26,9 +25,6 @@ use rustls::{
|
||||
use rustls_pki_types::TrustAnchor;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use downcast_rs;
|
||||
pub use erased_serde;
|
||||
|
||||
pub trait HttpLimitResponse: Sync + Send {
|
||||
fn bytes_with_limit(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user