feat: allow Url to accept invalid UTF-8 characters (#2889)

This commit is contained in:
三咲雅 misaki masa 2025-06-17 18:24:43 +08:00 committed by GitHub
parent c8148d2d75
commit ec90f26ac3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 222 additions and 162 deletions

View file

@ -1,6 +1,7 @@
use std::{ops::Deref, path::Path};
use mlua::{AnyUserData, ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};
use yazi_shared::IntoOsStr;
use crate::{Urn, cached_field};
@ -32,6 +33,14 @@ impl From<Url> for yazi_shared::url::Url {
fn from(value: Url) -> Self { value.inner }
}
impl TryFrom<&[u8]> for Url {
type Error = mlua::Error;
fn try_from(value: &[u8]) -> mlua::Result<Self> {
Ok(Self::new(yazi_shared::url::Url::try_from(value)?))
}
}
impl Url {
pub fn new(url: impl Into<yazi_shared::url::Url>) -> Self {
Self {
@ -52,7 +61,7 @@ impl Url {
"Url",
lua.create_function(|_, value: Value| {
Ok(match value {
Value::String(s) => Self::new(s.to_str()?.as_ref()),
Value::String(s) => Self::try_from(s.as_bytes().as_ref())?,
Value::UserData(ud) => Self::new(ud.borrow::<Self>()?.inner.clone()),
_ => Err("Expected a string or a Url".into_lua_err())?,
})
@ -89,7 +98,7 @@ impl UserData for Url {
cached_field!(fields, base, |_, me| {
Ok(if me.base().as_os_str().is_empty() { None } else { Some(Self::new(me.base())) })
});
cached_field!(fields, frag, |lua, me| lua.create_string(me.frag()));
cached_field!(fields, frag, |lua, me| lua.create_string(me.frag().as_encoded_bytes()));
fields.add_field_method_get("is_regular", |_, me| Ok(me.is_regular()));
fields.add_field_method_get("is_search", |_, me| Ok(me.is_search()));
@ -130,7 +139,7 @@ impl UserData for Url {
});
methods.add_function_mut("into_search", |_, (ud, frag): (AnyUserData, mlua::String)| {
Ok(Self::new(ud.take::<Self>()?.inner.into_search(&frag.to_str()?)))
Ok(Self::new(ud.take::<Self>()?.inner.into_search(frag.as_bytes().as_ref().into_os_str()?)))
});
methods.add_meta_method(MetaMethod::Eq, |_, me, other: UrlRef| Ok(me.inner == other.inner));

View file

@ -28,7 +28,7 @@ impl Mgr {
let updates = opt
.updates
.into_iter()
.map(|(url, mime)| (Url::from(url.as_ref()), mime))
.flat_map(|(url, mime)| Url::try_from(url.as_ref()).map(|u| (u, mime)))
.filter(|(url, mime)| self.mimetype.by_url(url) != Some(mime))
.fold(HashMap::new(), |mut map, (u, m)| {
for u in linked.from_file(&u) {

View file

@ -1,7 +1,7 @@
use std::{borrow::Cow, collections::HashMap, path::PathBuf};
use yazi_fs::File;
use yazi_shared::{MIME_DIR, url::{Url, UrlScheme}};
use yazi_shared::{MIME_DIR, url::{Scheme, Url}};
#[derive(Default)]
pub struct Mimetype(HashMap<PathBuf, String>);
@ -10,10 +10,10 @@ impl Mimetype {
#[inline]
pub fn by_url(&self, url: &Url) -> Option<&str> {
match url.scheme() {
UrlScheme::Regular => self.0.get(url.as_path()),
UrlScheme::Search => None,
UrlScheme::SearchItem => self.0.get(url.as_path()),
UrlScheme::Archive => None,
Scheme::Regular => self.0.get(url.as_path()),
Scheme::Search => None,
Scheme::SearchItem => self.0.get(url.as_path()),
Scheme::Archive => None,
}
.map(|s| s.as_str())
}
@ -36,10 +36,10 @@ impl Mimetype {
#[inline]
pub fn contains(&self, url: &Url) -> bool {
match url.scheme() {
UrlScheme::Regular => self.0.contains_key(url.as_path()),
UrlScheme::Search => false,
UrlScheme::SearchItem => self.0.contains_key(url.as_path()),
UrlScheme::Archive => false,
Scheme::Regular => self.0.contains_key(url.as_path()),
Scheme::Search => false,
Scheme::SearchItem => self.0.contains_key(url.as_path()),
Scheme::Archive => false,
}
}
@ -47,10 +47,10 @@ impl Mimetype {
self.0.extend(iter.into_iter().filter_map(|(u, s)| {
Some((
match u.scheme() {
UrlScheme::Regular => u.into_path(),
UrlScheme::Search => None?,
UrlScheme::SearchItem => u.into_path(),
UrlScheme::Archive => None?,
Scheme::Regular => u.into_path(),
Scheme::Search => None?,
Scheme::SearchItem => u.into_path(),
Scheme::Archive => None?,
},
s,
))

View file

@ -39,7 +39,7 @@ impl Tab {
handle.abort();
}
let cwd = self.cwd().to_search(&opt.subject);
let cwd = self.cwd().to_search(opt.subject.as_ref());
let hidden = self.pref.show_hidden;
self.search = Some(tokio::spawn(async move {

View file

@ -118,12 +118,14 @@ impl Selected {
mod tests {
use super::*;
fn url(s: &str) -> Url { Url::try_from(s).unwrap() }
#[test]
fn test_insert_non_conflicting() {
let mut s = Selected::default();
assert!(s.add(&Url::from("/a/b")));
assert!(s.add(&Url::from("/c/d")));
assert!(s.add(&url("/a/b")));
assert!(s.add(&url("/c/d")));
assert_eq!(s.inner.len(), 2);
}
@ -131,27 +133,27 @@ mod tests {
fn test_insert_conflicting_parent() {
let mut s = Selected::default();
assert!(s.add(&Url::from("/a")));
assert!(!s.add(&Url::from("/a/b")));
assert!(s.add(&url("/a")));
assert!(!s.add(&url("/a/b")));
}
#[test]
fn test_insert_conflicting_child() {
let mut s = Selected::default();
assert!(s.add(&Url::from("/a/b/c")));
assert!(!s.add(&Url::from("/a/b")));
assert!(s.add(&Url::from("/a/b/d")));
assert!(s.add(&url("/a/b/c")));
assert!(!s.add(&url("/a/b")));
assert!(s.add(&url("/a/b/d")));
}
#[test]
fn test_remove() {
let mut s = Selected::default();
assert!(s.add(&Url::from("/a/b")));
assert!(!s.remove(&Url::from("/a/c")));
assert!(s.remove(&Url::from("/a/b")));
assert!(!s.remove(&Url::from("/a/b")));
assert!(s.add(&url("/a/b")));
assert!(!s.remove(&url("/a/c")));
assert!(s.remove(&url("/a/b")));
assert!(!s.remove(&url("/a/b")));
assert!(s.inner.is_empty());
assert!(s.parents.is_empty());
}
@ -162,11 +164,7 @@ mod tests {
assert_eq!(
3,
s.add_same(&[
&Url::from("/parent/child1"),
&Url::from("/parent/child2"),
&Url::from("/parent/child3")
])
s.add_same(&[&url("/parent/child1"), &url("/parent/child2"), &url("/parent/child3")])
);
}
@ -174,16 +172,16 @@ mod tests {
fn insert_many_with_existing_parent_fails() {
let mut s = Selected::default();
s.add(&Url::from("/parent"));
assert_eq!(0, s.add_same(&[&Url::from("/parent/child1"), &Url::from("/parent/child2")]));
s.add(&url("/parent"));
assert_eq!(0, s.add_same(&[&url("/parent/child1"), &url("/parent/child2")]));
}
#[test]
fn insert_many_with_existing_child_fails() {
let mut s = Selected::default();
s.add(&Url::from("/parent/child1"));
assert_eq!(2, s.add_same(&[&Url::from("/parent/child1"), &Url::from("/parent/child2")]));
s.add(&url("/parent/child1"));
assert_eq!(2, s.add_same(&[&url("/parent/child1"), &url("/parent/child2")]));
}
#[test]
@ -197,51 +195,48 @@ mod tests {
fn insert_many_with_parent_as_child_of_another_url() {
let mut s = Selected::default();
s.add(&Url::from("/parent/child"));
assert_eq!(
0,
s.add_same(&[&Url::from("/parent/child/child1"), &Url::from("/parent/child/child2")])
);
s.add(&url("/parent/child"));
assert_eq!(0, s.add_same(&[&url("/parent/child/child1"), &url("/parent/child/child2")]));
}
#[test]
fn insert_many_with_direct_parent_fails() {
let mut s = Selected::default();
s.add(&Url::from("/a"));
assert_eq!(0, s.add_same(&[&Url::from("/a/b")]));
s.add(&url("/a"));
assert_eq!(0, s.add_same(&[&url("/a/b")]));
}
#[test]
fn insert_many_with_nested_child_fails() {
let mut s = Selected::default();
s.add(&Url::from("/a/b"));
assert_eq!(0, s.add_same(&[&Url::from("/a")]));
assert_eq!(1, s.add_same(&[&Url::from("/b"), &Url::from("/a")]));
s.add(&url("/a/b"));
assert_eq!(0, s.add_same(&[&url("/a")]));
assert_eq!(1, s.add_same(&[&url("/b"), &url("/a")]));
}
#[test]
fn insert_many_sibling_directories_success() {
let mut s = Selected::default();
assert_eq!(2, s.add_same(&[&Url::from("/a/b"), &Url::from("/a/c")]));
assert_eq!(2, s.add_same(&[&url("/a/b"), &url("/a/c")]));
}
#[test]
fn insert_many_with_grandchild_fails() {
let mut s = Selected::default();
s.add(&Url::from("/a/b"));
assert_eq!(0, s.add_same(&[&Url::from("/a/b/c")]));
s.add(&url("/a/b"));
assert_eq!(0, s.add_same(&[&url("/a/b/c")]));
}
#[test]
fn test_insert_many_with_remove() {
let mut s = Selected::default();
let child1 = Url::from("/parent/child1");
let child2 = Url::from("/parent/child2");
let child3 = Url::from("/parent/child3");
let child1 = url("/parent/child1");
let child2 = url("/parent/child2");
let child3 = url("/parent/child3");
assert_eq!(3, s.add_same(&[&child1, &child2, &child3]));
assert!(s.remove(&child1));

View file

@ -1,4 +1,4 @@
use std::path::{MAIN_SEPARATOR_STR, Path};
use std::path::MAIN_SEPARATOR_STR;
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, List, ListItem, Widget}};
use yazi_adapter::Dimension;
@ -26,8 +26,7 @@ impl Widget for Cmp<'_> {
let icon = if x.is_dir { &THEME.cmp.icon_folder } else { &THEME.cmp.icon_file };
let slash = if x.is_dir { MAIN_SEPARATOR_STR } else { "" };
// FIXME: Bump Rust to 1.87.0 and use `OsStr::display()` instead
let mut item = ListItem::new(format!(" {icon} {}{slash}", Path::new(&x.name).display()));
let mut item = ListItem::new(format!(" {icon} {}{slash}", x.name.display()));
if i == self.cx.cmp.rel_cursor() {
item = item.style(THEME.cmp.active);
} else {

View file

@ -13,7 +13,7 @@ impl TabProxy {
}
pub fn reveal(target: &Url) {
emit!(Call(Cmd::args("mgr:reveal", [target]).with("no-dummy", true)));
emit!(Call(Cmd::args("mgr:reveal", [target]).with("raw", true).with("no-dummy", true)));
}
pub fn arrow(step: impl Into<Cow<'static, str>>) {

11
yazi-shared/src/bytes.rs Normal file
View file

@ -0,0 +1,11 @@
pub trait BytesExt {
fn split_by_seq(&self, sep: &[u8]) -> Option<(&[u8], &[u8])>;
}
impl BytesExt for [u8] {
fn split_by_seq(&self, sep: &[u8]) -> Option<(&[u8], &[u8])> {
let idx = memchr::memmem::find(self, sep)?;
let (left, right) = self.split_at(idx);
Some((left, &right[sep.len()..]))
}
}

View file

@ -56,7 +56,7 @@ impl Data {
#[inline]
pub fn into_url(self) -> Option<Url> {
match self {
Data::String(s) => Some(Url::from(s.as_ref())),
Data::String(s) => Url::try_from(s.as_ref()).ok(),
Data::Url(u) => Some(u),
_ => None,
}
@ -79,7 +79,7 @@ impl Data {
#[inline]
pub fn to_url(&self) -> Option<Url> {
match self {
Self::String(s) => Some(Url::from(s.as_ref())),
Self::String(s) => Url::try_from(s.as_ref()).ok(),
Self::Url(u) => Some(u.clone()),
_ => None,
}

View file

@ -2,7 +2,7 @@
yazi_macro::mod_pub!(errors event shell translit url);
yazi_macro::mod_flat!(chars condition debounce either env id layer natsort number os rand ro_cell sync_cell terminal throttle time utf8);
yazi_macro::mod_flat!(bytes chars condition debounce either env id layer natsort number os osstr rand ro_cell sync_cell terminal throttle time utf8);
pub fn init() {
LOG_LEVEL.replace(<_>::from(std::env::var("YAZI_LOG").unwrap_or_default()));

35
yazi-shared/src/osstr.rs Normal file
View file

@ -0,0 +1,35 @@
use std::{borrow::Cow, ffi::{OsStr, OsString}};
pub trait IntoOsStr<'a> {
type Error;
fn into_os_str(self) -> Result<Cow<'a, OsStr>, Self::Error>;
}
impl<'a> IntoOsStr<'a> for Cow<'a, [u8]> {
type Error = anyhow::Error;
fn into_os_str(self) -> Result<Cow<'a, OsStr>, Self::Error> {
#[cfg(unix)]
{
use std::os::unix::ffi::{OsStrExt, OsStringExt};
Ok(match self {
Cow::Borrowed(b) => Cow::Borrowed(OsStr::from_bytes(b)),
Cow::Owned(b) => Cow::Owned(OsString::from_vec(b)),
})
}
#[cfg(windows)]
{
Ok(match self {
Cow::Borrowed(b) => Cow::Borrowed(OsStr::new(str::from_utf8(b)?)),
Cow::Owned(b) => Cow::Owned(OsString::from(String::from_utf8(b)?)),
})
}
}
}
impl<'a> IntoOsStr<'a> for &'a [u8] {
type Error = anyhow::Error;
fn into_os_str(self) -> Result<Cow<'a, OsStr>, Self::Error> { Cow::Borrowed(self).into_os_str() }
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(loc url urn);
yazi_macro::mod_flat!(loc scheme url urn);

View file

@ -0,0 +1,36 @@
use std::fmt::Display;
use anyhow::bail;
#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Scheme {
#[default]
Regular,
Search,
SearchItem,
Archive,
}
impl TryFrom<&[u8]> for Scheme {
type Error = anyhow::Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Ok(match value {
b"regular" => Scheme::Regular,
b"search" => Scheme::Search,
b"archive" => Scheme::Archive,
_ => bail!("Unknown URL scheme: {}", String::from_utf8_lossy(value)),
})
}
}
impl Display for Scheme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Scheme::Regular => write!(f, "regular"),
Scheme::Search => write!(f, "search"),
Scheme::SearchItem => write!(f, "search_item"),
Scheme::Archive => write!(f, "archive"),
}
}
}

View file

@ -1,27 +1,18 @@
use std::{ffi::OsStr, fmt::{Debug, Display, Formatter}, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}};
use std::{borrow::Cow, ffi::{OsStr, OsString}, fmt::{Debug, Display, Formatter}, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}};
use percent_encoding::{AsciiSet, CONTROLS, percent_decode_str, percent_encode};
use percent_encoding::{AsciiSet, CONTROLS, percent_decode, percent_encode};
use serde::{Deserialize, Serialize};
use super::UrnBuf;
use crate::url::Loc;
use crate::{BytesExt, IntoOsStr, url::{Loc, Scheme}};
const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#');
#[derive(Clone, Default, Eq, Ord, PartialOrd)]
pub struct Url {
loc: Loc,
scheme: UrlScheme,
frag: String,
}
#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum UrlScheme {
#[default]
Regular,
Search,
SearchItem,
Archive,
scheme: Scheme,
frag: OsString,
}
impl Deref for Url {
@ -33,10 +24,10 @@ impl Deref for Url {
impl Debug for Url {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.scheme {
UrlScheme::Regular => write!(f, "Regular({:?})", self.loc),
UrlScheme::Search => write!(f, "Search({:?}, {})", self.loc, self.frag),
UrlScheme::SearchItem => write!(f, "SearchItem({:?})", self.loc),
UrlScheme::Archive => write!(f, "Archive({:?})", self.loc),
Scheme::Regular => write!(f, "Regular({:?})", self.loc),
Scheme::Search => write!(f, "Search({:?}, {})", self.loc, self.frag.display()),
Scheme::SearchItem => write!(f, "SearchItem({:?})", self.loc),
Scheme::Archive => write!(f, "Archive({:?})", self.loc),
}
}
}
@ -57,44 +48,45 @@ impl From<&Path> for Url {
fn from(path: &Path) -> Self { path.to_owned().into() }
}
impl From<&str> for Url {
fn from(mut path: &str) -> Self {
impl TryFrom<&[u8]> for Url {
type Error = anyhow::Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let mut url = Url::default();
match path.split_once("://").map(|(a, b)| (UrlScheme::from(a), b)) {
None => {
url.loc = Loc::new(PathBuf::from(path));
return url;
}
Some((UrlScheme::Regular, b)) => {
url.loc = Loc::new(PathBuf::from(b));
return url;
}
Some((a, b)) => {
url.scheme = a;
path = b;
}
let Some((scheme, rest)) = value.split_by_seq(b"://") else {
url.loc = Loc::new(value.into_os_str()?.into_owned().into());
return Ok(url);
};
url.scheme = Scheme::try_from(scheme)?;
if url.scheme == Scheme::Regular {
url.loc = Loc::new(rest.into_os_str()?.into_owned().into());
return Ok(url);
}
match path.split_once('#') {
None => {
// FIXME: use `Loc::from(base, path)` instead
url.loc = Loc::new(percent_decode_str(path).decode_utf8_lossy().into_owned().into());
}
Some((a, b)) => {
// FIXME: use `Loc::from(base, path)` instead
url.loc = Loc::new(percent_decode_str(a).decode_utf8_lossy().into_owned().into());
url.frag = b.to_string();
}
}
url
let (loc, frag) = match rest.split_by_seq(b"#") {
None => (rest, OsString::new()),
Some((a, b)) => (a, b.into_os_str()?.into_owned()),
};
// FIXME: use `Loc::from(base, path)` instead
url.loc = Loc::new(Cow::from(percent_decode(loc)).into_os_str()?.into_owned().into());
url.frag = frag;
Ok(url)
}
}
impl From<String> for Url {
fn from(path: String) -> Self { path.as_str().into() }
impl TryFrom<&str> for Url {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> { value.as_bytes().try_into() }
}
impl From<&String> for Url {
fn from(path: &String) -> Self { path.as_str().into() }
impl TryFrom<String> for Url {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self, Self::Error> { value.as_bytes().try_into() }
}
impl AsRef<Url> for Url {
@ -111,20 +103,15 @@ impl AsRef<OsStr> for Url {
impl Display for Url {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if matches!(self.scheme, UrlScheme::Regular | UrlScheme::SearchItem) {
if matches!(self.scheme, Scheme::Regular | Scheme::SearchItem) {
return write!(f, "{}", self.loc.display());
}
let scheme = match self.scheme {
UrlScheme::Regular | UrlScheme::SearchItem => unreachable!(),
UrlScheme::Search => "search://",
UrlScheme::Archive => "archive://",
};
let path = percent_encode(self.loc.as_os_str().as_encoded_bytes(), ENCODE_SET);
let loc = percent_encode(self.loc.as_os_str().as_encoded_bytes(), ENCODE_SET);
write!(f, "{}://{loc}", self.scheme)?;
write!(f, "{scheme}{path}")?;
if !self.frag.is_empty() {
write!(f, "#{}", self.frag)?;
write!(f, "#{}", percent_encode(self.frag.as_encoded_bytes(), ENCODE_SET))?;
}
Ok(())
@ -139,16 +126,16 @@ impl Url {
#[inline]
pub fn join(&self, path: impl AsRef<Path>) -> Self {
match self.scheme {
UrlScheme::Regular => Self::from(self.loc.join(path)),
UrlScheme::Search => {
Scheme::Regular => Self::from(self.loc.join(path)),
Scheme::Search => {
let loc = Loc::from(&self.loc, self.loc.join(path));
Self::from(loc).into_search_item()
}
UrlScheme::SearchItem => {
Scheme::SearchItem => {
let loc = Loc::from(self.loc.base(), self.loc.join(path));
Self::from(loc).into_search_item()
}
UrlScheme::Archive => Self::from(self.loc.join(path)).into_archive(),
Scheme::Archive => Self::from(self.loc.join(path)).into_archive(),
}
}
@ -156,15 +143,15 @@ impl Url {
pub fn parent_url(&self) -> Option<Url> {
let p = self.loc.parent()?;
Some(match self.scheme {
UrlScheme::Regular | UrlScheme::Search => Self::from(p),
UrlScheme::SearchItem => {
Scheme::Regular | Scheme::Search => Self::from(p),
Scheme::SearchItem => {
if p == self.loc.base() {
Self::from(p).into_search("")
} else {
Self::from(p).into_search_item()
}
}
UrlScheme::Archive => Self::from(p),
Scheme::Archive => Self::from(p),
})
}
@ -184,58 +171,56 @@ impl Url {
impl Url {
// --- Regular
#[inline]
pub fn is_regular(&self) -> bool { self.scheme == UrlScheme::Regular }
pub fn is_regular(&self) -> bool { self.scheme == Scheme::Regular }
#[inline]
pub fn to_regular(&self) -> Self {
Self { loc: self.loc.clone(), scheme: UrlScheme::Regular, frag: String::new() }
}
pub fn to_regular(&self) -> Self { Self { loc: self.loc.clone(), ..Default::default() } }
#[inline]
pub fn into_regular(mut self) -> Self {
self.scheme = UrlScheme::Regular;
self.frag = String::new();
self.scheme = Scheme::Regular;
self.frag = OsString::new();
self
}
// --- Search
#[inline]
pub fn is_search(&self) -> bool { self.scheme == UrlScheme::Search }
pub fn is_search(&self) -> bool { self.scheme == Scheme::Search }
#[inline]
pub fn to_search(&self, frag: &str) -> Self {
Self { loc: self.loc.clone(), scheme: UrlScheme::Search, frag: frag.to_owned() }
pub fn to_search(&self, frag: impl AsRef<OsStr>) -> Self {
Self { loc: self.loc.clone(), scheme: Scheme::Search, frag: frag.as_ref().to_owned() }
}
#[inline]
pub fn into_search(mut self, frag: &str) -> Self {
self.scheme = UrlScheme::Search;
self.frag = frag.to_owned();
pub fn into_search(mut self, frag: impl AsRef<OsStr>) -> Self {
self.scheme = Scheme::Search;
self.frag = frag.as_ref().to_owned();
self
}
#[inline]
pub fn is_search_item(&self) -> bool { self.scheme == UrlScheme::SearchItem }
pub fn is_search_item(&self) -> bool { self.scheme == Scheme::SearchItem }
#[inline]
pub fn into_search_item(mut self) -> Self {
self.scheme = UrlScheme::SearchItem;
self.frag = String::new();
self.scheme = Scheme::SearchItem;
self.frag = OsString::new();
self
}
#[inline]
pub fn is_archive(&self) -> bool { self.scheme == UrlScheme::Archive }
pub fn is_archive(&self) -> bool { self.scheme == Scheme::Archive }
#[inline]
pub fn to_archive(&self) -> Self {
Self { loc: self.loc.clone(), scheme: UrlScheme::Archive, frag: String::new() }
Self { loc: self.loc.clone(), scheme: Scheme::Archive, ..Default::default() }
}
#[inline]
pub fn into_archive(mut self) -> Self {
self.scheme = UrlScheme::Archive;
self.frag = String::new();
self.scheme = Scheme::Archive;
self.frag = OsString::new();
self
}
@ -251,21 +236,11 @@ impl Url {
// --- Scheme
#[inline]
pub fn scheme(&self) -> UrlScheme { self.scheme }
pub fn scheme(&self) -> Scheme { self.scheme }
// --- Frag
#[inline]
pub fn frag(&self) -> &str { &self.frag }
}
impl From<&str> for UrlScheme {
fn from(value: &str) -> Self {
match value {
"search" => UrlScheme::Search,
"archive" => UrlScheme::Archive,
_ => UrlScheme::Regular,
}
}
pub fn frag(&self) -> &OsStr { &self.frag }
}
impl Hash for Url {
@ -273,8 +248,8 @@ impl Hash for Url {
self.loc.hash(state);
match self.scheme {
UrlScheme::Regular | UrlScheme::SearchItem => {}
UrlScheme::Search | UrlScheme::Archive => {
Scheme::Regular | Scheme::SearchItem => {}
Scheme::Search | Scheme::Archive => {
self.scheme.hash(state);
self.frag.hash(state);
}
@ -285,7 +260,7 @@ impl Hash for Url {
impl PartialEq for Url {
fn eq(&self, other: &Self) -> bool {
match (self.scheme, other.scheme) {
(UrlScheme::Regular | UrlScheme::SearchItem, UrlScheme::Regular | UrlScheme::SearchItem) => {
(Scheme::Regular | Scheme::SearchItem, Scheme::Regular | Scheme::SearchItem) => {
self.loc == other.loc
}
_ => self.loc == other.loc && self.scheme == other.scheme,
@ -305,6 +280,6 @@ impl<'de> Deserialize<'de> for Url {
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(Url::from(s))
Url::try_from(s).map_err(serde::de::Error::custom)
}
}