use alloc::{borrow::Cow, fmt, string::String};
use core::{
error::Error,
str::{FromStr, Utf8Error},
};
#[cfg(feature = "std")]
use std::{ffi::OsStr, path::Path};
use crate::inline::{InlineFlexStr, TooLongForInlining, inline_partial_eq_impl};
use flexstr_support::StringToFromBytes;
pub type InlineStr = InlineFlexStr<str>;
#[derive(Debug)]
pub enum TooLongOrUtf8Error {
TooLong(TooLongForInlining),
Utf8Error(Utf8Error),
}
impl fmt::Display for TooLongOrUtf8Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TooLongOrUtf8Error::TooLong(e) => e.fmt(f),
TooLongOrUtf8Error::Utf8Error(e) => e.fmt(f),
}
}
}
impl Error for TooLongOrUtf8Error {}
impl<'s> TryFrom<&'s str> for InlineFlexStr<str> {
type Error = TooLongForInlining;
#[inline]
fn try_from(s: &'s str) -> Result<Self, Self::Error> {
InlineFlexStr::try_from_type(s)
}
}
impl<'s> TryFrom<&'s [u8]> for InlineFlexStr<str> {
type Error = TooLongOrUtf8Error;
#[inline]
fn try_from(s: &'s [u8]) -> Result<Self, Self::Error> {
match str::from_utf8(s) {
Ok(s) => InlineFlexStr::try_from_type(s).map_err(TooLongOrUtf8Error::TooLong),
Err(e) => Err(TooLongOrUtf8Error::Utf8Error(e)),
}
}
}
#[cfg(feature = "std")]
impl<'s> TryFrom<&'s OsStr> for InlineFlexStr<str> {
type Error = TooLongOrUtf8Error;
#[inline]
fn try_from(s: &'s OsStr) -> Result<Self, Self::Error> {
match s.try_into() {
Ok(s) => InlineFlexStr::try_from_type(s).map_err(TooLongOrUtf8Error::TooLong),
Err(e) => Err(TooLongOrUtf8Error::Utf8Error(e)),
}
}
}
#[cfg(feature = "std")]
impl<'s> TryFrom<&'s Path> for InlineFlexStr<str> {
type Error = TooLongOrUtf8Error;
#[inline]
fn try_from(s: &'s Path) -> Result<Self, Self::Error> {
match s.as_os_str().try_into() {
Ok(s) => InlineFlexStr::try_from_type(s).map_err(TooLongOrUtf8Error::TooLong),
Err(e) => Err(TooLongOrUtf8Error::Utf8Error(e)),
}
}
}
inline_partial_eq_impl!(str, str);
inline_partial_eq_impl!(&str, str);
inline_partial_eq_impl!(String, str);
inline_partial_eq_impl!(Cow<'_, str>, str);
impl<S: ?Sized + StringToFromBytes> AsRef<str> for InlineFlexStr<S>
where
S: AsRef<str>,
{
fn as_ref(&self) -> &str {
self.as_ref_type().as_ref()
}
}
impl FromStr for InlineFlexStr<str> {
type Err = TooLongForInlining;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
InlineFlexStr::try_from_type(s)
}
}
#[cfg(feature = "prost")]
#[allow(deprecated)] impl prost::Message for InlineFlexStr<str> {
fn encode_raw(&self, buf: &mut impl prost::bytes::BufMut)
where
Self: Sized,
{
buf.put_slice(self.as_ref_type().as_bytes());
}
fn merge_field(
&mut self,
tag: u32,
wire_type: prost::encoding::WireType,
buf: &mut impl prost::bytes::Buf,
ctx: prost::encoding::DecodeContext,
) -> Result<(), prost::DecodeError>
where
Self: Sized,
{
prost::encoding::skip_field(wire_type, tag, buf, ctx)
}
fn encoded_len(&self) -> usize {
self.as_ref_type().len()
}
fn clear(&mut self) {
*self = Default::default();
}
fn merge(&mut self, mut buf: impl prost::bytes::Buf) -> Result<(), prost::DecodeError>
where
Self: Sized,
{
let bytes = buf.copy_to_bytes(buf.remaining());
let s = core::str::from_utf8(&bytes)
.map_err(|_| prost::DecodeError::new("invalid UTF-8 in string field"))?;
*self = InlineFlexStr::try_from_type(s)
.map_err(|_| prost::DecodeError::new("string too long for inline storage"))?;
Ok(())
}
fn merge_length_delimited(
&mut self,
mut buf: impl prost::bytes::Buf,
) -> Result<(), prost::DecodeError>
where
Self: Sized,
{
let len = prost::encoding::decode_varint(&mut buf)? as usize;
if buf.remaining() < len {
return Err(prost::DecodeError::new("buffer underflow"));
}
self.merge(buf.take(len))
}
}
#[cfg(feature = "sqlx")]
impl<'r, DB: sqlx::Database> sqlx::Decode<'r, DB> for InlineFlexStr<str>
where
&'r str: sqlx::Decode<'r, DB>,
{
fn decode(
value: <DB as sqlx::Database>::ValueRef<'r>,
) -> Result<Self, sqlx::error::BoxDynError> {
let value = <&str as sqlx::Decode<DB>>::decode(value)?;
Ok(value.try_into()?)
}
}
#[cfg(feature = "sqlx")]
impl<'r, DB: sqlx::Database> sqlx::Encode<'r, DB> for InlineFlexStr<str>
where
String: sqlx::Encode<'r, DB>,
{
fn encode_by_ref(
&self,
buf: &mut <DB as sqlx::Database>::ArgumentBuffer<'r>,
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
<String as sqlx::Encode<'r, DB>>::encode(self.to_string(), buf)
}
fn size_hint(&self) -> usize {
self.len()
}
}
#[cfg(feature = "sqlx")]
impl<DB: sqlx::Database> sqlx::Type<DB> for InlineFlexStr<str>
where
str: sqlx::Type<DB>,
{
fn type_info() -> <DB as sqlx::Database>::TypeInfo {
<str as sqlx::Type<DB>>::type_info()
}
fn compatible(ty: &<DB as sqlx::Database>::TypeInfo) -> bool {
<str as sqlx::Type<DB>>::compatible(ty)
}
}
#[cfg(all(feature = "sqlx", feature = "sqlx_pg_arrays"))]
impl sqlx::postgres::PgHasArrayType for InlineFlexStr<str>
where
for<'a> &'a str: sqlx::postgres::PgHasArrayType,
{
fn array_type_info() -> sqlx::postgres::PgTypeInfo {
<&str as sqlx::postgres::PgHasArrayType>::array_type_info()
}
fn array_compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
<&str as sqlx::postgres::PgHasArrayType>::array_compatible(ty)
}
}
#[cfg(feature = "utoipa")]
impl utoipa::PartialSchema for InlineFlexStr<str> {
fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
utoipa::openapi::schema::ObjectBuilder::new()
.schema_type(utoipa::openapi::schema::SchemaType::new(
utoipa::openapi::schema::Type::String,
))
.into()
}
}
#[cfg(feature = "utoipa")]
impl utoipa::ToSchema for InlineFlexStr<str> {
fn name() -> Cow<'static, str> {
Cow::Borrowed("String")
}
}