#[cfg(test)]
mod test;
#[cfg(test)]
mod test_from_schema;
#[cfg(test)]
mod test_reasonable_str;
use std::{fmt, ops::Deref};
use crate::{
json, schema,
warning::{self, IntoCaveat as _},
FromSchema, Verdict,
};
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum Warning {
ContainsEscapeCodes,
ContainsNonPrintableASCII,
InvalidType { type_found: json::ValueKind },
InvalidLengthMax { length: usize },
InvalidLengthExact { length: usize },
PreferUppercase,
}
impl Warning {
fn invalid_type(elem: &json::Element<'_>) -> Self {
Self::InvalidType {
type_found: elem.value().kind(),
}
}
}
impl fmt::Display for Warning {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ContainsEscapeCodes => f.write_str("The string contains escape codes."),
Self::ContainsNonPrintableASCII => {
f.write_str("The string contains non-printable bytes.")
}
Self::InvalidType { type_found } => {
write!(f, "The value should be a string but is `{type_found}`")
}
Self::InvalidLengthMax { length } => {
write!(
f,
"The string is longer than the max length `{length}` defined in the spec.",
)
}
Self::InvalidLengthExact { length } => {
write!(f, "The string should be length `{length}`.")
}
Self::PreferUppercase => {
write!(f, "Upper case is preferred")
}
}
}
}
impl crate::Warning for Warning {
fn id(&self) -> warning::Id {
match self {
Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
Self::ContainsNonPrintableASCII => {
warning::Id::from_static("contains_non_printable_ascii")
}
Self::InvalidType { type_found } => {
warning::Id::from_string(format!("invalid_type({type_found})"))
}
Self::InvalidLengthMax { .. } => warning::Id::from_static("invalid_length_max"),
Self::InvalidLengthExact { .. } => warning::Id::from_static("invalid_length_exact"),
Self::PreferUppercase => warning::Id::from_static("prefer_upper_case"),
}
}
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct CiMaxLen<'buf, const MAX_LEN: usize>(&'buf str);
impl<const MAX_LEN: usize> Deref for CiMaxLen<'_, MAX_LEN> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl<const MAX_LEN: usize> fmt::Display for CiMaxLen<'_, MAX_LEN> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<'buf, const MAX_LEN: usize> json::FromJson<'buf> for CiMaxLen<'buf, MAX_LEN> {
type Warning = Warning;
fn from_json(elem: &json::Element<'buf>) -> Verdict<Self, Self::Warning> {
let (s, mut warnings) = Base::from_json(elem)?.into_parts();
if s.len() > MAX_LEN {
warnings.insert(elem, Warning::InvalidLengthMax { length: MAX_LEN });
}
Ok(Self(s.0).into_caveat(warnings))
}
}
impl<'buf, const MAX_LEN: usize> FromSchema<'buf, schema::Str<'buf>> for CiMaxLen<'buf, MAX_LEN> {
type Warning = Warning;
fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
let (s, mut warnings) = Base::from_schema(source)?.into_parts();
if s.len() > MAX_LEN {
warnings.insert(
source.element(),
Warning::InvalidLengthMax { length: MAX_LEN },
);
}
Ok(Self(s.0).into_caveat(warnings))
}
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct CiExactLen<'buf, const LEN: usize>(&'buf str);
impl<const LEN: usize> Deref for CiExactLen<'_, LEN> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl<const LEN: usize> fmt::Display for CiExactLen<'_, LEN> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<'buf, const LEN: usize> json::FromJson<'buf> for CiExactLen<'buf, LEN> {
type Warning = Warning;
fn from_json(elem: &json::Element<'buf>) -> Verdict<Self, Self::Warning> {
let (s, mut warnings) = Base::from_json(elem)?.into_parts();
if s.len() != LEN {
warnings.insert(elem, Warning::InvalidLengthExact { length: LEN });
}
Ok(Self(s.0).into_caveat(warnings))
}
}
impl<'buf, const LEN: usize> FromSchema<'buf, schema::Str<'buf>> for CiExactLen<'buf, LEN> {
type Warning = Warning;
fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
let (s, mut warnings) = Base::from_schema(source)?.into_parts();
if s.len() != LEN {
warnings.insert(
source.element(),
Warning::InvalidLengthExact { length: LEN },
);
}
Ok(Self(s.0).into_caveat(warnings))
}
}
#[derive(Copy, Clone, Debug)]
struct Base<'buf>(&'buf str);
impl Deref for Base<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl fmt::Display for Base<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<'buf> json::FromJson<'buf> for Base<'buf> {
type Warning = Warning;
fn from_json(elem: &json::Element<'buf>) -> Verdict<Self, Self::Warning> {
let mut warnings = warning::Set::new();
let Some(raw) = elem.to_raw_str() else {
return warnings.bail(elem, Warning::invalid_type(elem));
};
let issues = raw.lexical_issues();
if issues.escapes {
warnings.insert(elem, Warning::ContainsEscapeCodes);
}
if issues.non_printable_ascii {
warnings.insert(elem, Warning::ContainsNonPrintableASCII);
}
Ok(Self(raw.as_unescaped_str()).into_caveat(warnings))
}
}
impl<'buf> FromSchema<'buf, schema::Str<'buf>> for Base<'buf> {
type Warning = Warning;
fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
let mut warnings = warning::Set::new();
let elem = source.element();
let raw = source.value();
let issues = raw.lexical_issues();
if issues.escapes {
warnings.insert(elem, Warning::ContainsEscapeCodes);
}
if issues.non_printable_ascii {
warnings.insert(elem, Warning::ContainsNonPrintableASCII);
}
Ok(Self(raw.as_unescaped_str()).into_caveat(warnings))
}
}
pub(crate) struct SizeExceedsMax(());
impl std::error::Error for SizeExceedsMax {}
impl fmt::Debug for SizeExceedsMax {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("SizeExceedsMax").finish()
}
}
impl fmt::Display for SizeExceedsMax {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"The size of the input string exceeds the maximum length of {} megabytes",
ReasonableLen::FACTOR
)
}
}
#[derive(Copy, Clone)]
pub(crate) struct ReasonableLen<'buf>(&'buf str);
impl<'buf> ReasonableLen<'buf> {
const MEGA: usize = 1_000_000;
pub(crate) const FACTOR: usize = 5;
pub(crate) const MAX_STR_INPUT_LEN: usize = Self::FACTOR * Self::MEGA;
pub(crate) fn new(s: &'buf str) -> Result<ReasonableLen<'buf>, SizeExceedsMax> {
if s.len() >= Self::MAX_STR_INPUT_LEN {
return Err(SizeExceedsMax(()));
}
Ok(Self(s))
}
pub(crate) fn into_inner(self) -> &'buf str {
self.0
}
}