blob: 2f6e9b6fe6f2bacdfd9126d7d3a334c948accfff [file] [log] [blame]
// Copyright 2019 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.
use {
fidl_fuchsia_sys2 as fsys,
futures::future,
futures::future::FutureObj,
std::{collections::HashMap, error, fmt},
url::Url,
};
/// Resolves a component URI to its content.
/// TODO: Consider defining an internal representation for `fsys::Component` so as to
/// further isolate the `Model` from FIDL interfacting concerns.
pub trait Resolver {
fn resolve<'a>(
&'a self, component_uri: &'a str,
) -> FutureObj<'a, Result<fsys::Component, ResolverError>>;
}
/// Resolves a component URI using a resolver selected based on the URI's scheme.
pub struct ResolverRegistry {
resolvers: HashMap<String, Box<dyn Resolver>>,
}
impl ResolverRegistry {
pub fn new() -> ResolverRegistry {
ResolverRegistry {
resolvers: HashMap::new(),
}
}
pub fn register(&mut self, scheme: String, resolver: Box<dyn Resolver>) {
self.resolvers.insert(scheme, resolver);
}
}
impl Resolver for ResolverRegistry {
fn resolve<'a>(
&'a self, component_uri: &'a str,
) -> FutureObj<'a, Result<fsys::Component, ResolverError>> {
match Url::parse(component_uri) {
Ok(parsed_uri) => {
if let Some(ref resolver) = self.resolvers.get(parsed_uri.scheme()) {
resolver.resolve(component_uri)
} else {
FutureObj::new(Box::new(future::err(ResolverError::SchemeNotRegistered)))
}
}
Err(err) => FutureObj::new(Box::new(future::err(ResolverError::from(err)))),
}
}
}
/// Errors produced by `Resolver`.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ResolverError {
ComponentNotAvailable,
SchemeNotRegistered,
UrlParseError(url::ParseError),
}
impl fmt::Display for ResolverError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ResolverError::ComponentNotAvailable => write!(f, "component not available"),
ResolverError::SchemeNotRegistered => write!(f, "component uri scheme not registered"),
ResolverError::UrlParseError(ref err) => err.fmt(f),
}
}
}
impl error::Error for ResolverError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
ResolverError::ComponentNotAvailable => None,
ResolverError::SchemeNotRegistered => None,
ResolverError::UrlParseError(ref err) => err.source(),
}
}
}
impl From<url::ParseError> for ResolverError {
fn from(err: url::ParseError) -> Self {
ResolverError::UrlParseError(err)
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockOkResolver {
pub expected_uri: String,
pub resolved_uri: String,
}
impl Resolver for MockOkResolver {
fn resolve(
&self, component_uri: &str,
) -> FutureObj<Result<fsys::Component, ResolverError>> {
assert_eq!(self.expected_uri, component_uri);
FutureObj::new(Box::new(future::ok(fsys::Component {
resolved_uri: Some(self.resolved_uri.clone()),
decl: Some(fsys::ComponentDecl {
program: None,
uses: None,
exposes: None,
offers: None,
facets: None,
children: None,
}),
package: None,
})))
}
}
struct MockErrorResolver {
pub expected_uri: String,
pub error: ResolverError,
}
impl Resolver for MockErrorResolver {
fn resolve(
&self, component_uri: &str,
) -> FutureObj<Result<fsys::Component, ResolverError>> {
assert_eq!(self.expected_uri, component_uri);
FutureObj::new(Box::new(future::err(self.error.clone())))
}
}
#[fuchsia_async::run_until_stalled(test)]
async fn register_and_resolve() {
let mut registry = ResolverRegistry::new();
registry.register(
"foo".to_string(),
Box::new(MockOkResolver {
expected_uri: "foo://uri".to_string(),
resolved_uri: "foo://resolved".to_string(),
}),
);
registry.register(
"bar".to_string(),
Box::new(MockErrorResolver {
expected_uri: "bar://uri".to_string(),
error: ResolverError::ComponentNotAvailable,
}),
);
// Resolve known scheme that returns success.
let component = await!(registry.resolve("foo://uri")).unwrap();
assert_eq!("foo://resolved", component.resolved_uri.unwrap());
// Resolve a different scheme that produces an error.
assert_eq!(
Err(ResolverError::ComponentNotAvailable),
await!(registry.resolve("bar://uri"))
);
// Resolve an unknown scheme.
assert_eq!(
Err(ResolverError::SchemeNotRegistered),
await!(registry.resolve("unknown://uri")),
);
// Resolve an URL lacking a scheme.
assert_eq!(
Err(ResolverError::UrlParseError(
url::ParseError::RelativeUrlWithoutBase
)),
await!(registry.resolve("xxx")),
);
}
}