blob: acf5d617eaed8a77108c5b7286ddd2e35f4a6426 [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
pub use crate::errors::ParseError;
pub use crate::parse::{validate_package_path_segment, validate_resource_path};
use crate::{Scheme, UrlParts};
pub const SCHEME: &str = "fuchsia-builtin";
/// Decoded representation of a builtin URL.
///
/// fuchsia-builtin://#resource
///
/// Builtin component declarations are used to bootstrap the ELF runner.
/// They are never packaged.
///
/// The path in builtin URLs must be "/". Following that, they may contain a fragment.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BuiltinUrl {
resource: Option<String>,
}
impl BuiltinUrl {
pub fn parse(input: &str) -> Result<Self, ParseError> {
Self::try_from_parts(UrlParts::parse(input)?)
}
fn try_from_parts(
UrlParts { scheme, host, path, hash, resource }: UrlParts,
) -> Result<Self, ParseError> {
if scheme.ok_or(ParseError::MissingScheme)? != Scheme::Builtin {
return Err(ParseError::InvalidScheme);
}
if host.is_some() {
return Err(ParseError::HostMustBeEmpty);
}
if hash.is_some() {
return Err(ParseError::CannotContainHash);
}
if path != "/" {
return Err(ParseError::PathMustBeRoot);
}
Ok(Self { resource })
}
pub fn resource(&self) -> Option<&str> {
self.resource.as_ref().map(|s| s.as_str())
}
}
impl std::fmt::Display for BuiltinUrl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}://", SCHEME)?;
if let Some(ref resource) = self.resource {
write!(f, "#{}", percent_encoding::utf8_percent_encode(resource, crate::FRAGMENT))?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::{PackagePathSegmentError, ResourcePathError};
use assert_matches::assert_matches;
#[test]
fn test_parse_ok() {
assert_eq!(BuiltinUrl::parse("fuchsia-builtin://").unwrap().resource(), None);
assert_eq!(BuiltinUrl::parse("fuchsia-builtin://#a").unwrap().resource(), Some("a"));
assert_eq!(
BuiltinUrl::parse("fuchsia-builtin://#elf_runner.cm").unwrap().resource(),
Some("elf_runner.cm")
);
}
#[test]
fn test_parse_error_wrong_scheme() {
assert_matches!(BuiltinUrl::parse("foobar://").unwrap_err(), ParseError::InvalidScheme);
assert_matches!(
BuiltinUrl::parse("fuchsia-boot://").unwrap_err(),
ParseError::InvalidScheme
);
assert_matches!(
BuiltinUrl::parse("fuchsia-pkg://").unwrap_err(),
ParseError::InvalidScheme
);
}
#[test]
fn test_parse_error_missing_scheme() {
assert_matches!(BuiltinUrl::parse("package").unwrap_err(), ParseError::MissingScheme);
}
#[test]
fn test_parse_error_invalid_path() {
assert_matches!(
BuiltinUrl::parse("fuchsia-builtin:////").unwrap_err(),
ParseError::InvalidPathSegment(PackagePathSegmentError::Empty)
);
}
#[test]
fn test_parse_error_invalid_character() {
assert_matches!(
BuiltinUrl::parse("fuchsia-builtin:///package:1234").unwrap_err(),
ParseError::InvalidPathSegment(PackagePathSegmentError::InvalidCharacter {
character: ':'
})
);
}
#[test]
fn test_parse_error_host_must_be_empty() {
assert_matches!(
BuiltinUrl::parse("fuchsia-builtin://hello").unwrap_err(),
ParseError::HostMustBeEmpty
);
}
#[test]
fn test_parse_error_resource_cannot_be_slash() {
assert_matches!(
BuiltinUrl::parse("fuchsia-builtin://#/").unwrap_err(),
ParseError::InvalidResourcePath(ResourcePathError::PathStartsWithSlash)
);
}
}