AssetPath source parse fix (#11543)
# Objective Fixes #11533 When `AssetPath`s are created from a string type, they are parsed into an `AssetSource`, a `Path`, and a `Label`. The current method of parsing has some unnecessary quirks: - The presence of a `:` character is assumed to be the start of an asset source indicator. - This is not necessarily true. There are valid uses of a `:` character in an asset path, for example an http source's port such as `localhost:80`. - If there are multiple instances of `://`, the last one is assumed to be the asset source deliminator. - This has some unexpected behavior. Even in a fully formed path, such as `http://localhost:80`, the `:` between `localhost` and `80` is assumed to be the start of an asset source, causing an error since it does not form the full sequence `://`. ## Solution Changes the `AssetPath`'s `parse_internal` method to be more permissive. - Only the exact sequence `://` is taken to be the asset source deliminator, and only the first one if there are multiple. - As a consequence, it is no longer possible to detect a malformed asset source deliminator, and so the corresponding error was removed.
This commit is contained in:
parent
7ae36a99c8
commit
182c21dc58
@ -78,12 +78,19 @@ impl<'a> Display for AssetPath<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An error that occurs when parsing a string type to create an [`AssetPath`] fails, such as during [`AssetPath::parse`] or [`AssetPath::from<'static str>`].
|
||||||
#[derive(Error, Debug, PartialEq, Eq)]
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
pub enum ParseAssetPathError {
|
pub enum ParseAssetPathError {
|
||||||
#[error("Asset source must be followed by '://'")]
|
/// Error that occurs when the [`AssetPath::source`] section of a path string contains the [`AssetPath::label`] delimiter `#`. E.g. `bad#source://file.test`.
|
||||||
|
#[error("Asset source must not contain a `#` character")]
|
||||||
InvalidSourceSyntax,
|
InvalidSourceSyntax,
|
||||||
|
/// Error that occurs when the [`AssetPath::label`] section of a path string contains the [`AssetPath::source`] delimiter `://`. E.g. `source://file.test#bad://label`.
|
||||||
|
#[error("Asset label must not contain a `://` substring")]
|
||||||
|
InvalidLabelSyntax,
|
||||||
|
/// Error that occurs when a path string has an [`AssetPath::source`] delimiter `://` with no characters preceeding it. E.g. `://file.test`.
|
||||||
#[error("Asset source must be at least one character. Either specify the source before the '://' or remove the `://`")]
|
#[error("Asset source must be at least one character. Either specify the source before the '://' or remove the `://`")]
|
||||||
MissingSource,
|
MissingSource,
|
||||||
|
/// Error that occurs when a path string has an [`AssetPath::label`] delimiter `#` with no characters succeeding it. E.g. `file.test#`
|
||||||
#[error("Asset label must be at least one character. Either specify the label after the '#' or remove the '#'")]
|
#[error("Asset label must be at least one character. Either specify the label after the '#' or remove the '#'")]
|
||||||
MissingLabel,
|
MissingLabel,
|
||||||
}
|
}
|
||||||
@ -126,40 +133,70 @@ impl<'a> AssetPath<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attempts to Parse a &str into an `AssetPath`'s `AssetPath::source`, `AssetPath::path`, and `AssetPath::label` components.
|
||||||
fn parse_internal(
|
fn parse_internal(
|
||||||
asset_path: &str,
|
asset_path: &str,
|
||||||
) -> Result<(Option<&str>, &Path, Option<&str>), ParseAssetPathError> {
|
) -> Result<(Option<&str>, &Path, Option<&str>), ParseAssetPathError> {
|
||||||
let mut chars = asset_path.char_indices();
|
let chars = asset_path.char_indices();
|
||||||
let mut source_range = None;
|
let mut source_range = None;
|
||||||
let mut path_range = 0..asset_path.len();
|
let mut path_range = 0..asset_path.len();
|
||||||
let mut label_range = None;
|
let mut label_range = None;
|
||||||
while let Some((index, char)) = chars.next() {
|
|
||||||
|
// Loop through the characters of the passed in &str to accomplish the following:
|
||||||
|
// 1. Seach for the first instance of the `://` substring. If the `://` substring is found,
|
||||||
|
// store the range of indices representing everything before the `://` substring as the `source_range`.
|
||||||
|
// 2. Seach for the last instance of the `#` character. If the `#` character is found,
|
||||||
|
// store the range of indices representing everything after the `#` character as the `label_range`
|
||||||
|
// 3. Set the `path_range` to be everything in between the `source_range` and `label_range`,
|
||||||
|
// excluding the `://` substring and `#` character.
|
||||||
|
// 4. Verify that there are no `#` characters in the `AssetPath::source` and no `://` substrings in the `AssetPath::label`
|
||||||
|
let mut source_delimiter_chars_matched = 0;
|
||||||
|
let mut last_found_source_index = 0;
|
||||||
|
for (index, char) in chars {
|
||||||
match char {
|
match char {
|
||||||
':' => {
|
':' => {
|
||||||
let (_, char) = chars
|
source_delimiter_chars_matched = 1;
|
||||||
.next()
|
|
||||||
.ok_or(ParseAssetPathError::InvalidSourceSyntax)?;
|
|
||||||
if char != '/' {
|
|
||||||
return Err(ParseAssetPathError::InvalidSourceSyntax);
|
|
||||||
}
|
}
|
||||||
let (index, char) = chars
|
'/' => {
|
||||||
.next()
|
match source_delimiter_chars_matched {
|
||||||
.ok_or(ParseAssetPathError::InvalidSourceSyntax)?;
|
1 => {
|
||||||
if char != '/' {
|
source_delimiter_chars_matched = 2;
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
// If we haven't found our first `AssetPath::source` yet, check to make sure it is valid and then store it.
|
||||||
|
if source_range.is_none() {
|
||||||
|
// If the `AssetPath::source` containes a `#` character, it is invalid.
|
||||||
|
if label_range.is_some() {
|
||||||
return Err(ParseAssetPathError::InvalidSourceSyntax);
|
return Err(ParseAssetPathError::InvalidSourceSyntax);
|
||||||
}
|
}
|
||||||
source_range = Some(0..index - 2);
|
source_range = Some(0..index - 2);
|
||||||
path_range.start = index + 1;
|
path_range.start = index + 1;
|
||||||
}
|
}
|
||||||
'#' => {
|
last_found_source_index = index - 2;
|
||||||
path_range.end = index;
|
source_delimiter_chars_matched = 0;
|
||||||
label_range = Some(index + 1..asset_path.len());
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
'#' => {
|
||||||
|
path_range.end = index;
|
||||||
|
label_range = Some(index + 1..asset_path.len());
|
||||||
|
source_delimiter_chars_matched = 0;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
source_delimiter_chars_matched = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we found an `AssetPath::label`
|
||||||
|
if let Some(range) = label_range.clone() {
|
||||||
|
// If the `AssetPath::label` contained a `://` substring, it is invalid.
|
||||||
|
if range.start <= last_found_source_index {
|
||||||
|
return Err(ParseAssetPathError::InvalidLabelSyntax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Try to parse the range of indices that represents the `AssetPath::source` portion of the `AssetPath` to make sure it is not empty.
|
||||||
|
// This would be the case if the input &str was something like `://some/file.test`
|
||||||
let source = match source_range {
|
let source = match source_range {
|
||||||
Some(source_range) => {
|
Some(source_range) => {
|
||||||
if source_range.is_empty() {
|
if source_range.is_empty() {
|
||||||
@ -169,6 +206,8 @@ impl<'a> AssetPath<'a> {
|
|||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
// Try to parse the range of indices that represents the `AssetPath::label` portion of the `AssetPath` to make sure it is not empty.
|
||||||
|
// This would be the case if the input &str was something like `some/file.test#`.
|
||||||
let label = match label_range {
|
let label = match label_range {
|
||||||
Some(label_range) => {
|
Some(label_range) => {
|
||||||
if label_range.is_empty() {
|
if label_range.is_empty() {
|
||||||
@ -700,6 +739,33 @@ mod tests {
|
|||||||
Ok((Some("http"), Path::new("a/b.test"), Some("Foo")))
|
Ok((Some("http"), Path::new("a/b.test"), Some("Foo")))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let result = AssetPath::parse_internal("localhost:80/b.test");
|
||||||
|
assert_eq!(result, Ok((None, Path::new("localhost:80/b.test"), None)));
|
||||||
|
|
||||||
|
let result = AssetPath::parse_internal("http://localhost:80/b.test");
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Ok((Some("http"), Path::new("localhost:80/b.test"), None))
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = AssetPath::parse_internal("http://localhost:80/b.test#Foo");
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Ok((Some("http"), Path::new("localhost:80/b.test"), Some("Foo")))
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = AssetPath::parse_internal("#insource://a/b.test");
|
||||||
|
assert_eq!(result, Err(crate::ParseAssetPathError::InvalidSourceSyntax));
|
||||||
|
|
||||||
|
let result = AssetPath::parse_internal("source://a/b.test#://inlabel");
|
||||||
|
assert_eq!(result, Err(crate::ParseAssetPathError::InvalidLabelSyntax));
|
||||||
|
|
||||||
|
let result = AssetPath::parse_internal("#insource://a/b.test#://inlabel");
|
||||||
|
assert!(
|
||||||
|
result == Err(crate::ParseAssetPathError::InvalidSourceSyntax)
|
||||||
|
|| result == Err(crate::ParseAssetPathError::InvalidLabelSyntax)
|
||||||
|
);
|
||||||
|
|
||||||
let result = AssetPath::parse_internal("http://");
|
let result = AssetPath::parse_internal("http://");
|
||||||
assert_eq!(result, Ok((Some("http"), Path::new(""), None)));
|
assert_eq!(result, Ok((Some("http"), Path::new(""), None)));
|
||||||
|
|
||||||
@ -708,9 +774,6 @@ mod tests {
|
|||||||
|
|
||||||
let result = AssetPath::parse_internal("a/b.test#");
|
let result = AssetPath::parse_internal("a/b.test#");
|
||||||
assert_eq!(result, Err(crate::ParseAssetPathError::MissingLabel));
|
assert_eq!(result, Err(crate::ParseAssetPathError::MissingLabel));
|
||||||
|
|
||||||
let result = AssetPath::parse_internal("http:/");
|
|
||||||
assert_eq!(result, Err(crate::ParseAssetPathError::InvalidSourceSyntax));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
Loading…
Reference in New Issue
Block a user