add Cowy util trait

This commit is contained in:
panicbit 2020-11-14 22:46:29 +01:00
parent 2fc015fb89
commit 0425bf2cf3
5 changed files with 40 additions and 19 deletions

View File

@ -3,6 +3,7 @@ use std::fmt;
use itertools::Itertools; use itertools::Itertools;
use crate::types::URIReference; use crate::types::URIReference;
use crate::util::Cowy;
#[derive(Default)] #[derive(Default)]
pub struct Document { pub struct Document {
@ -42,7 +43,7 @@ impl Document {
self self
} }
pub fn add_link<'a, U>(&mut self, uri: U, label: impl AsRef<str> + Into<String>) -> &mut Self pub fn add_link<'a, U>(&mut self, uri: U, label: impl Cowy<str>) -> &mut Self
where where
U: TryInto<URIReference<'a>>, U: TryInto<URIReference<'a>>,
{ {
@ -92,7 +93,7 @@ impl Document {
self self
} }
pub fn add_heading(&mut self, level: HeadingLevel, text: impl AsRef<str> + Into<String>) -> &mut Self { pub fn add_heading(&mut self, level: HeadingLevel, text: impl Cowy<str>) -> &mut Self {
let text = HeadingText::new_lossy(text); let text = HeadingText::new_lossy(text);
let heading = Heading { let heading = Heading {
level, level,
@ -182,7 +183,7 @@ impl Text {
Self::default() Self::default()
} }
pub fn new_lossy(line: impl AsRef<str> + Into<String>) -> Self { pub fn new_lossy(line: impl Cowy<str>) -> Self {
Self(lossy_escaped_line(line, SPECIAL_STARTS)) Self(lossy_escaped_line(line, SPECIAL_STARTS))
} }
} }
@ -195,7 +196,7 @@ pub struct Link {
pub struct LinkLabel(String); pub struct LinkLabel(String);
impl LinkLabel { impl LinkLabel {
pub fn from_lossy(line: impl AsRef<str> + Into<String>) -> Self { pub fn from_lossy(line: impl Cowy<str>) -> Self {
let line = strip_newlines(line); let line = strip_newlines(line);
LinkLabel(line) LinkLabel(line)
@ -210,7 +211,7 @@ pub struct Preformatted {
pub struct PreformattedText(String); pub struct PreformattedText(String);
impl PreformattedText { impl PreformattedText {
pub fn new_lossy(line: impl AsRef<str> + Into<String>) -> Self { pub fn new_lossy(line: impl Cowy<str>) -> Self {
Self(lossy_escaped_line(line, &[PREFORMATTED_TOGGLE_START])) Self(lossy_escaped_line(line, &[PREFORMATTED_TOGGLE_START]))
} }
} }
@ -248,7 +249,7 @@ impl Heading {
pub struct HeadingText(String); pub struct HeadingText(String);
impl HeadingText { impl HeadingText {
pub fn new_lossy(line: impl AsRef<str> + Into<String>) -> Self { pub fn new_lossy(line: impl Cowy<str>) -> Self {
let line = strip_newlines(line); let line = strip_newlines(line);
Self(lossy_escaped_line(line, &[HEADING_START])) Self(lossy_escaped_line(line, &[HEADING_START]))
@ -298,7 +299,7 @@ fn starts_with_any(s: &str, starts: &[&str]) -> bool {
false false
} }
fn lossy_escaped_line(line: impl AsRef<str> + Into<String>, escape_starts: &[&str]) -> String { fn lossy_escaped_line(line: impl Cowy<str>, escape_starts: &[&str]) -> String {
let line_ref = line.as_ref(); let line_ref = line.as_ref();
let contains_newline = line_ref.contains('\n'); let contains_newline = line_ref.contains('\n');
let has_special_start = starts_with_any(line_ref, escape_starts); let has_special_start = starts_with_any(line_ref, escape_starts);
@ -320,7 +321,7 @@ fn lossy_escaped_line(line: impl AsRef<str> + Into<String>, escape_starts: &[&st
line line
} }
fn strip_newlines(text: impl AsRef<str> + Into<String>) -> String { fn strip_newlines(text: impl Cowy<str>) -> String {
if !text.as_ref().contains(&['\r', '\n'][..]) { if !text.as_ref().contains(&['\r', '\n'][..]) {
return text.into(); return text.into();
} }

View File

@ -1,5 +1,7 @@
use anyhow::*; use anyhow::*;
use mime::Mime; use crate::Mime;
use crate::util::Cowy;
#[derive(Debug,Clone,PartialEq,Eq,Default)] #[derive(Debug,Clone,PartialEq,Eq,Default)]
pub struct Meta(String); pub struct Meta(String);
@ -9,7 +11,7 @@ impl Meta {
/// Creates a new "Meta" string. /// Creates a new "Meta" string.
/// Fails if `meta` contains `\n`. /// Fails if `meta` contains `\n`.
pub fn new(meta: impl AsRef<str> + Into<String>) -> Result<Self> { pub fn new(meta: impl Cowy<str>) -> Result<Self> {
ensure!(!meta.as_ref().contains("\n"), "Meta must not contain newlines"); ensure!(!meta.as_ref().contains("\n"), "Meta must not contain newlines");
ensure!(meta.as_ref().len() <= Self::MAX_LEN, "Meta must not exceed {} bytes", Self::MAX_LEN); ensure!(meta.as_ref().len() <= Self::MAX_LEN, "Meta must not exceed {} bytes", Self::MAX_LEN);
@ -20,7 +22,7 @@ impl Meta {
/// Truncates `meta` to before: /// Truncates `meta` to before:
/// - the first occurrence of `\n` /// - the first occurrence of `\n`
/// - the character that makes `meta` exceed `Meta::MAX_LEN` /// - the character that makes `meta` exceed `Meta::MAX_LEN`
pub fn new_lossy(meta: impl AsRef<str> + Into<String>) -> Self { pub fn new_lossy(meta: impl Cowy<str>) -> Self {
let meta = meta.as_ref(); let meta = meta.as_ref();
let truncate_pos = meta.char_indices().position(|(i, ch)| { let truncate_pos = meta.char_indices().position(|(i, ch)| {
let is_newline = ch == '\n'; let is_newline = ch == '\n';

View File

@ -1,5 +1,6 @@
use anyhow::*; use anyhow::*;
use crate::types::{ResponseHeader, Body, Mime, Document}; use crate::types::{ResponseHeader, Body, Mime, Document};
use crate::util::Cowy;
use crate::GEMINI_MIME; use crate::GEMINI_MIME;
pub struct Response { pub struct Response {
@ -19,12 +20,12 @@ impl Response {
Self::success(&GEMINI_MIME).with_body(document) Self::success(&GEMINI_MIME).with_body(document)
} }
pub fn input(prompt: impl AsRef<str> + Into<String>) -> Result<Self> { pub fn input(prompt: impl Cowy<str>) -> Result<Self> {
let header = ResponseHeader::input(prompt)?; let header = ResponseHeader::input(prompt)?;
Ok(Self::new(header)) Ok(Self::new(header))
} }
pub fn input_lossy(prompt: impl AsRef<str> + Into<String>) -> Self { pub fn input_lossy(prompt: impl Cowy<str>) -> Self {
let header = ResponseHeader::input_lossy(prompt); let header = ResponseHeader::input_lossy(prompt);
Self::new(header) Self::new(header)
} }
@ -34,7 +35,7 @@ impl Response {
Self::new(header) Self::new(header)
} }
pub fn server_error(reason: impl AsRef<str> + Into<String>) -> Result<Self> { pub fn server_error(reason: impl Cowy<str>) -> Result<Self> {
let header = ResponseHeader::server_error(reason)?; let header = ResponseHeader::server_error(reason)?;
Ok(Self::new(header)) Ok(Self::new(header))
} }

View File

@ -1,5 +1,6 @@
use anyhow::*; use anyhow::*;
use mime::Mime; use crate::Mime;
use crate::util::Cowy;
use crate::types::{Status, Meta}; use crate::types::{Status, Meta};
#[derive(Debug,Clone)] #[derive(Debug,Clone)]
@ -9,14 +10,14 @@ pub struct ResponseHeader {
} }
impl ResponseHeader { impl ResponseHeader {
pub fn input(prompt: impl AsRef<str> + Into<String>) -> Result<Self> { pub fn input(prompt: impl Cowy<str>) -> Result<Self> {
Ok(Self { Ok(Self {
status: Status::INPUT, status: Status::INPUT,
meta: Meta::new(prompt).context("Invalid input prompt")?, meta: Meta::new(prompt).context("Invalid input prompt")?,
}) })
} }
pub fn input_lossy(prompt: impl AsRef<str> + Into<String>) -> Self { pub fn input_lossy(prompt: impl Cowy<str>) -> Self {
Self { Self {
status: Status::INPUT, status: Status::INPUT,
meta: Meta::new_lossy(prompt), meta: Meta::new_lossy(prompt),
@ -30,14 +31,14 @@ impl ResponseHeader {
} }
} }
pub fn server_error(reason: impl AsRef<str> + Into<String>) -> Result<Self> { pub fn server_error(reason: impl Cowy<str>) -> Result<Self> {
Ok(Self { Ok(Self {
status: Status::PERMANENT_FAILURE, status: Status::PERMANENT_FAILURE,
meta: Meta::new(reason).context("Invalid server error reason")?, meta: Meta::new(reason).context("Invalid server error reason")?,
}) })
} }
pub fn server_error_lossy(reason: impl AsRef<str> + Into<String>) -> Self { pub fn server_error_lossy(reason: impl Cowy<str>) -> Self {
Self { Self {
status: Status::PERMANENT_FAILURE, status: Status::PERMANENT_FAILURE,
meta: Meta::new_lossy(reason), meta: Meta::new_lossy(reason),

View File

@ -102,3 +102,19 @@ pub fn guess_mime_from_path<P: AsRef<Path>>(path: P) -> Mime {
mime.parse::<Mime>().unwrap_or(mime::APPLICATION_OCTET_STREAM) mime.parse::<Mime>().unwrap_or(mime::APPLICATION_OCTET_STREAM)
} }
/// A convenience trait alias for `AsRef<T> + Into<T::Owned>`,
/// most commonly used to accept `&str` or `String`:
///
/// `Cowy<str>` ⇔ `AsRef<str> + Into<String>`
pub trait Cowy<T>
where
Self: AsRef<T> + Into<T::Owned>,
T: ToOwned + ?Sized,
{}
impl<C, T> Cowy<T> for C
where
C: AsRef<T> + Into<T::Owned>,
T: ToOwned + ?Sized,
{}