use std::ops::Deref;
use crate::AstNode;
use crate::TreeNode;
use crate::v1::Expr;
use crate::v1::LiteralExpr;
pub mod uri;
pub use uri::Uri;
use wdl_grammar::SyntaxNode;
#[derive(Debug)]
pub enum ParseUriError {
InvalidExpressionInArray(String),
Uri(uri::Error),
}
impl std::fmt::Display for ParseUriError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ParseUriError::Uri(err) => write!(f, "uri error: {err}"),
ParseUriError::InvalidExpressionInArray(expr) => {
write!(f, "uri cannot be created from the expression: {expr}")
}
}
}
}
#[derive(Debug)]
pub enum Error {
ParseString(ParseUriError),
ParseArray(Vec<ParseUriError>),
InvalidExpression(String),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::ParseString(error) => write!(f, "parse uri error: {error}"),
Error::ParseArray(errors) => {
write!(
f,
"multiple parse uri errors: {errors}",
errors = errors
.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join("; ")
)
}
Error::InvalidExpression(expr) => {
write!(f, "uri cannot be created from the expression: {expr}")
}
}
}
}
impl std::error::Error for Error {}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Eq, PartialEq)]
pub enum Kind<N: TreeNode = SyntaxNode> {
String(Uri<N>),
Array(Vec<Uri<N>>),
}
impl<N: TreeNode> Kind<N> {
pub fn uris(&self) -> Box<dyn Iterator<Item = &Uri<N>> + '_> {
match self {
Kind::String(uri) => Box::new(std::iter::once(uri)),
Kind::Array(uris) => Box::new(uris.iter()),
}
}
pub fn as_single_uri(&self) -> Option<&Uri<N>> {
match self {
Kind::String(uri) => Some(uri),
_ => None,
}
}
pub fn into_single_uri(self) -> Option<Uri<N>> {
match self {
Kind::String(uri) => Some(uri),
_ => None,
}
}
pub fn unwrap_single_uri(self) -> Uri<N> {
self.into_single_uri()
.expect("value is not a single, string literal URI")
}
pub fn as_multiple_uris(&self) -> Option<&Vec<Uri<N>>> {
match self {
Kind::Array(uri) => Some(uri),
_ => None,
}
}
pub fn into_multiple_uris(self) -> Option<Vec<Uri<N>>> {
match self {
Kind::Array(uri) => Some(uri),
_ => None,
}
}
pub fn unwrap_multiple_uris(self) -> Vec<Uri<N>> {
self.into_multiple_uris()
.expect("value is not an array of uris")
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct Value<N: TreeNode = SyntaxNode> {
kind: Kind<N>,
expr: Expr<N>,
}
impl<N: TreeNode> Value<N> {
pub fn kind(&self) -> &Kind<N> {
&self.kind
}
pub fn into_kind(self) -> Kind<N> {
self.kind
}
pub fn expr(&self) -> &Expr<N> {
&self.expr
}
pub fn into_expr(self) -> Expr<N> {
self.expr
}
pub fn into_parts(self) -> (Kind<N>, Expr<N>) {
(self.kind, self.expr)
}
}
impl<N: TreeNode> Deref for Value<N> {
type Target = Kind<N>;
fn deref(&self) -> &Self::Target {
&self.kind
}
}
impl<N: TreeNode> TryFrom<Expr<N>> for Value<N> {
type Error = Error;
fn try_from(expr: Expr<N>) -> Result<Self> {
if let Expr::Literal(literal) = &expr {
match literal {
LiteralExpr::String(s) => {
return Uri::<N>::try_from(s.clone())
.map(|uri| Value {
kind: Kind::String(uri),
expr,
})
.map_err(|e| Error::ParseString(ParseUriError::Uri(e)));
}
LiteralExpr::Array(a) => {
let mut errors: Vec<ParseUriError> = Vec::new();
let uris: Vec<_> = a
.elements()
.filter_map(|expr| {
match expr
.clone()
.into_literal()
.and_then(|literal| literal.into_string())
{
Some(s) => match Uri::try_from(s) {
Ok(uri) => Some(uri),
Err(e) => {
errors.push(ParseUriError::Uri(e));
None
}
},
None => {
errors.push(ParseUriError::InvalidExpressionInArray(
expr.text().to_string(),
));
None
}
}
})
.collect();
if !errors.is_empty() {
return Err(Error::ParseArray(errors));
}
return Ok(Value {
kind: Kind::Array(uris),
expr,
});
}
_ => {}
}
}
Err(Error::InvalidExpression(expr.text().to_string()))
}
}
#[cfg(test)]
mod tests {
use crate::Document;
use crate::v1::task::requirements::item::container::Container;
fn get_container(document: Document) -> Container {
document
.ast()
.as_v1()
.expect("v1 ast")
.tasks()
.next()
.expect("the 'hello' task to exist")
.requirements()
.expect("the 'requirements' block to exist")
.items()
.find_map(|p| p.into_container())
.expect("at least one 'container' item to exist")
}
#[test]
fn it_parses_a_valid_single_uri_value() {
let (document, diagnostics) = Document::parse(
r#"version 1.2
task hello {
requirements {
container: 'ubuntu:latest'
}
}"#,
None,
);
assert!(diagnostics.is_empty());
let container = get_container(document);
let entry = container
.value()
.expect("value to be parsed")
.into_kind()
.unwrap_single_uri()
.into_kind()
.unwrap_entry();
assert!(entry.protocol().is_none());
assert_eq!(entry.image(), "ubuntu");
assert_eq!(entry.tag().unwrap(), "latest");
assert!(!entry.immutable());
}
#[test]
fn it_parses_a_valid_any_value() {
let (document, diagnostics) = Document::parse(
r#"version 1.2
task hello {
requirements {
container: '*'
}
}"#,
None,
);
assert!(diagnostics.is_empty());
let container = get_container(document);
let kind = container
.value()
.expect("value to be parsed")
.into_kind()
.unwrap_single_uri()
.into_kind();
assert!(kind.is_any());
}
#[test]
fn it_fails_to_parse_an_any_within_an_array() {
let (document, diagnostics) = Document::parse(
r#"version 1.2
task hello {
requirements {
container: ['ubuntu:latest', '*']
}
}"#,
None,
);
assert!(diagnostics.is_empty());
let container = get_container(document);
let mut uris = container
.value()
.expect("value to be parsed")
.into_kind()
.unwrap_multiple_uris()
.into_iter();
let entry = uris.next().unwrap().into_kind().unwrap_entry();
assert!(entry.protocol().is_none());
assert_eq!(entry.image(), "ubuntu");
assert_eq!(entry.tag().unwrap(), "latest");
assert!(!entry.immutable());
let kind = uris.next().unwrap().into_kind();
assert!(kind.is_any());
assert!(uris.next().is_none());
}
}