diff --git a/benches/data_loader_bench.rs b/benches/data_loader_bench.rs index c4a743be46..b99aebc647 100644 --- a/benches/data_loader_bench.rs +++ b/benches/data_loader_bench.rs @@ -10,6 +10,8 @@ use criterion::Criterion; use hyper::body::Bytes; use reqwest::Request; use tailcall::core::config::Batch; +use tailcall::core::error::file::FileError; +use tailcall::core::error::http::HttpError; use tailcall::core::http::{DataLoaderRequest, HttpDataLoader, Response}; use tailcall::core::ir::IoId; use tailcall::core::runtime::TargetRuntime; @@ -23,7 +25,8 @@ struct MockHttpClient { #[async_trait::async_trait] impl HttpIO for MockHttpClient { - async fn execute(&self, _req: Request) -> anyhow::Result> { + type Error = HttpError; + async fn execute(&self, _req: Request) -> Result, Self::Error> { Ok(Response::empty()) } } @@ -39,11 +42,12 @@ struct File; #[async_trait::async_trait] impl FileIO for File { - async fn write<'a>(&'a self, _: &'a str, _: &'a [u8]) -> anyhow::Result<()> { + type Error = FileError; + async fn write<'a>(&'a self, _: &'a str, _: &'a [u8]) -> Result<(), Self::Error> { unimplemented!("Not needed for this bench") } - async fn read<'a>(&'a self, _: &'a str) -> anyhow::Result { + async fn read<'a>(&'a self, _: &'a str) -> Result { unimplemented!("Not needed for this bench") } } diff --git a/benches/impl_path_string_for_evaluation_context.rs b/benches/impl_path_string_for_evaluation_context.rs index d3e74c3184..a916b1e6d3 100644 --- a/benches/impl_path_string_for_evaluation_context.rs +++ b/benches/impl_path_string_for_evaluation_context.rs @@ -17,6 +17,8 @@ use reqwest::{Client, Request}; use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use tailcall::core::blueprint::{Server, Upstream}; use tailcall::core::cache::InMemoryCache; +use tailcall::core::error::file::FileError; +use tailcall::core::error::http::HttpError; use tailcall::core::http::{RequestContext, Response}; use tailcall::core::ir::{EvaluationContext, ResolverContextLike}; use tailcall::core::path::PathString; @@ -70,7 +72,8 @@ impl Http { #[async_trait] impl HttpIO for Http { - async fn execute(&self, mut request: Request) -> anyhow::Result> { + type Error = HttpError; + async fn execute(&self, mut request: Request) -> Result, Self::Error> { if self.http2_only { *request.version_mut() = reqwest::Version::HTTP_2; } @@ -90,11 +93,12 @@ impl EnvIO for Env { struct File; #[async_trait] impl FileIO for File { - async fn write<'a>(&'a self, _: &'a str, _: &'a [u8]) -> anyhow::Result<()> { + type Error = FileError; + async fn write<'a>(&'a self, _: &'a str, _: &'a [u8]) -> Result<(), Self::Error> { unimplemented!("Not needed for this bench") } - async fn read<'a>(&'a self, _: &'a str) -> anyhow::Result { + async fn read<'a>(&'a self, _: &'a str) -> Result { unimplemented!("Not needed for this bench") } } diff --git a/src/core/errata.rs b/src/core/errata.rs index ff55619b68..1a6d8110aa 100644 --- a/src/core/errata.rs +++ b/src/core/errata.rs @@ -3,8 +3,8 @@ use std::fmt::{Debug, Display}; use colored::Colorize; use derive_setters::Setters; -use crate::core::valid::ValidationError; use crate::core::error::Error as CoreError; +use crate::core::valid::ValidationError; /// The moral equivalent of a serde_json::Value but for errors. /// It's a data structure like Value that can hold any error in an untyped diff --git a/src/core/error.rs b/src/core/error.rs index 4cc7419aee..7729479223 100644 --- a/src/core/error.rs +++ b/src/core/error.rs @@ -133,7 +133,7 @@ pub mod file { #[derive(From, thiserror::Error, Debug)] pub enum FileError { - #[error("File not found")] + #[error("No such file or directory (os error 2)")] NotFound, #[error("No permission to access the file")] @@ -145,6 +145,12 @@ pub mod file { #[error("Invalid file format")] InvalidFormat, + #[error("Invalid file path")] + InvalidFilePath, + + #[error("Invalid OS string")] + InvalidOsString, + #[error("Failed to read file : {0}")] FileReadFailed(String), @@ -160,6 +166,9 @@ pub mod file { #[error("File writing not supported on Lambda.")] LambdaFileWriteNotSupported, + + #[error("Cannot write to a file in an execution spec")] + ExecutionSpecFileWriteFailed, } } @@ -195,6 +204,25 @@ pub mod http { #[error("Unable to find key {0} in query params")] #[from(ignore)] KeyNotFound(String), + + #[error("Invalid Status Code")] + InvalidStatusCode(hyper::http::status::InvalidStatusCode), + + #[error("Status Code error")] + StatusCode, + + #[error("Invalid Header Value")] + InvalidHeaderValue(hyper::header::InvalidHeaderValue), + + #[error("Invalid Header Name")] + InvalidHeaderName(hyper::header::InvalidHeaderName), + + #[error("No mock found for request: {method} {url} in {spec_path}")] + NoMockFound { + method: String, + url: String, + spec_path: String, + }, } #[derive(From, thiserror::Error, Debug)] diff --git a/src/core/generator/tests/json_to_config_spec.rs b/src/core/generator/tests/json_to_config_spec.rs index 8d208219bb..836b44da4f 100644 --- a/src/core/generator/tests/json_to_config_spec.rs +++ b/src/core/generator/tests/json_to_config_spec.rs @@ -3,11 +3,10 @@ use std::path::Path; use serde::{Deserialize, Serialize}; use serde_json::Value; +use tailcall::core::error::Error; use tailcall::core::generator::{from_json, ConfigGenerationRequest}; use url::Url; -use crate::core::error::Error; - #[derive(Serialize, Deserialize)] struct JsonFixture { url: String, diff --git a/src/main.rs b/src/main.rs index 7a7c845961..16c584cf1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,10 +4,10 @@ use std::cell::Cell; use mimalloc::MiMalloc; +use tailcall::core::error::Error; use tailcall::core::tracing::default_tracing_tailcall; use tailcall::core::Errata; use tracing::subscriber::DefaultGuard; -use tailcall::core::error::Error; #[global_allocator] static GLOBAL: MiMalloc = MiMalloc; diff --git a/tailcall-cloudflare/src/runtime.rs b/tailcall-cloudflare/src/runtime.rs index 69f423c453..9c7243b662 100644 --- a/tailcall-cloudflare/src/runtime.rs +++ b/tailcall-cloudflare/src/runtime.rs @@ -15,7 +15,10 @@ fn init_env(env: Rc) -> Arc { Arc::new(env::CloudflareEnv::init(env)) } -fn init_file(env: Rc, bucket_id: &str) -> anyhow::Result>> { +fn init_file( + env: Rc, + bucket_id: &str, +) -> anyhow::Result>> { Ok(Arc::new(file::CloudflareFileIO::init(env, bucket_id)?)) } diff --git a/tests/core/file.rs b/tests/core/file.rs index 574bd38c5b..e01e6da9cc 100644 --- a/tests/core/file.rs +++ b/tests/core/file.rs @@ -2,7 +2,7 @@ extern crate core; use std::path::PathBuf; -use anyhow::{anyhow, Context}; +use tailcall::core::error::file::FileError; use tailcall::core::FileIO; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -19,20 +19,22 @@ impl File { #[async_trait::async_trait] impl FileIO for File { - async fn write<'a>(&'a self, _path: &'a str, _content: &'a [u8]) -> anyhow::Result<()> { - Err(anyhow!("Cannot write to a file in an execution spec")) + type Error = FileError; + async fn write<'a>(&'a self, _path: &'a str, _content: &'a [u8]) -> Result<(), Self::Error> { + Err(FileError::ExecutionSpecFileWriteFailed) } - async fn read<'a>(&'a self, path: &'a str) -> anyhow::Result { + async fn read<'a>(&'a self, path: &'a str) -> Result { let base = PathBuf::from(path); let path = base .file_name() - .context("Invalid file path")? + .ok_or(FileError::InvalidFilePath)? .to_str() - .context("Invalid OsString")?; + .ok_or(FileError::InvalidOsString)?; + match self.spec.files.get(path) { Some(x) => Ok(x.to_owned()), - None => Err(anyhow!("No such file or directory (os error 2)")), + None => Err(FileError::NotFound), } } } @@ -48,20 +50,17 @@ impl TestFileIO { #[async_trait::async_trait] impl FileIO for TestFileIO { - async fn write<'a>(&'a self, path: &'a str, content: &'a [u8]) -> anyhow::Result<()> { + type Error = FileError; + async fn write<'a>(&'a self, path: &'a str, content: &'a [u8]) -> Result<(), Self::Error> { let mut file = tokio::fs::File::create(path).await?; - file.write_all(content) - .await - .map_err(|e| anyhow!("{}", e))?; + file.write_all(content).await?; Ok(()) } - async fn read<'a>(&'a self, path: &'a str) -> anyhow::Result { + async fn read<'a>(&'a self, path: &'a str) -> Result { let mut file = tokio::fs::File::open(path).await?; let mut buffer = Vec::new(); - file.read_to_end(&mut buffer) - .await - .map_err(|e| anyhow!("{}", e))?; + file.read_to_end(&mut buffer).await?; Ok(String::from_utf8(buffer)?) } } diff --git a/tests/core/http.rs b/tests/core/http.rs index 87192a62b8..39c0b05048 100644 --- a/tests/core/http.rs +++ b/tests/core/http.rs @@ -5,9 +5,9 @@ use std::str::FromStr; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; -use anyhow::anyhow; use hyper::body::Bytes; use reqwest::header::{HeaderName, HeaderValue}; +use tailcall::core::error::http::HttpError; use tailcall::core::http::Response; use tailcall::core::HttpIO; @@ -54,7 +54,8 @@ impl Http { #[async_trait::async_trait] impl HttpIO for Http { - async fn execute(&self, req: reqwest::Request) -> anyhow::Result> { + type Error = HttpError; + async fn execute(&self, req: reqwest::Request) -> Result, Self::Error> { // Try to find a matching mock for the incoming request. let execution_mock = self .mocks @@ -94,12 +95,11 @@ impl HttpIO for Http { }); method_match && url_match && headers_match && body_match }) - .ok_or(anyhow!( - "No mock found for request: {:?} {} in {}", - req.method(), - req.url(), - self.spec_path - ))?; + .ok_or_else(|| HttpError::NoMockFound { + method: req.method().to_string(), + url: req.url().to_string(), + spec_path: self.spec_path.to_string(), + })?; execution_mock.actual_hits.fetch_add(1, Ordering::Relaxed); @@ -110,7 +110,7 @@ impl HttpIO for Http { let status_code = reqwest::StatusCode::from_u16(mock_response.0.status)?; if status_code.is_client_error() || status_code.is_server_error() { - return Err(anyhow::format_err!("Status code error")); + return Err(HttpError::StatusCode); } let mut response = Response { status: status_code, ..Default::default() }; diff --git a/tests/core/parse.rs b/tests/core/parse.rs index 10fef0528a..a6d43bd915 100644 --- a/tests/core/parse.rs +++ b/tests/core/parse.rs @@ -15,6 +15,7 @@ use tailcall::cli::javascript; use tailcall::core::blueprint::Blueprint; use tailcall::core::cache::InMemoryCache; use tailcall::core::config::{ConfigModule, Source}; +use tailcall::core::error::worker::WorkerError; use tailcall::core::http::AppContext; use tailcall::core::runtime::TargetRuntime; use tailcall::core::worker::{Command, Event}; @@ -277,19 +278,19 @@ impl ExecutionSpec { let http2_only = http.clone(); - let http_worker: Option>> = + let http_worker: Option>> = if let Some(script) = script.clone() { Some(javascript::init_worker_io(script)) } else { None }; - let worker: Option>> = if let Some(script) = script - { - Some(javascript::init_worker_io(script)) - } else { - None - }; + let worker: Option>> = + if let Some(script) = script { + Some(javascript::init_worker_io(script)) + } else { + None + }; let runtime = TargetRuntime { http, diff --git a/tests/core/spec.rs b/tests/core/spec.rs index f054475ddf..b9aab9aeea 100644 --- a/tests/core/spec.rs +++ b/tests/core/spec.rs @@ -14,6 +14,7 @@ use tailcall::core::async_graphql_hyper::{GraphQLBatchRequest, GraphQLRequest}; use tailcall::core::blueprint::Blueprint; use tailcall::core::config::reader::ConfigReader; use tailcall::core::config::{Config, ConfigModule, Source}; +use tailcall::core::error::Error; use tailcall::core::http::{handle_request, AppContext}; use tailcall::core::merge_right::MergeRight; use tailcall::core::print_schema::print_schema; @@ -301,7 +302,7 @@ pub async fn load_and_test_execution_spec(path: &Path) -> anyhow::Result<()> { async fn run_test( app_ctx: Arc, request: &APIRequest, -) -> anyhow::Result> { +) -> Result, Error> { let body = request .body .as_ref() diff --git a/tests/server_spec.rs b/tests/server_spec.rs index 297b4a9572..d2b7ac69dc 100644 --- a/tests/server_spec.rs +++ b/tests/server_spec.rs @@ -5,7 +5,7 @@ pub mod test { use std::sync::Arc; use std::time::Duration; - use anyhow::{anyhow, Result}; + use anyhow::Result; use async_graphql::Value; use http_cache_reqwest::{Cache, CacheMode, HttpCache, HttpCacheOptions}; use hyper::body::Bytes; @@ -14,6 +14,8 @@ pub mod test { use tailcall::cli::javascript::init_worker_io; use tailcall::core::blueprint::{Script, Upstream}; use tailcall::core::cache::InMemoryCache; + use tailcall::core::error::file::FileError; + use tailcall::core::error::http::HttpError; use tailcall::core::http::Response; use tailcall::core::runtime::TargetRuntime; use tailcall::core::worker::{Command, Event}; @@ -73,7 +75,8 @@ pub mod test { #[async_trait::async_trait] impl HttpIO for TestHttp { - async fn execute(&self, request: reqwest::Request) -> Result> { + type Error = HttpError; + async fn execute(&self, request: reqwest::Request) -> Result, HttpError> { let response = self.client.execute(request).await; Response::from_reqwest( response? @@ -95,20 +98,17 @@ pub mod test { #[async_trait::async_trait] impl FileIO for TestFileIO { - async fn write<'a>(&'a self, path: &'a str, content: &'a [u8]) -> anyhow::Result<()> { + type Error = FileError; + async fn write<'a>(&'a self, path: &'a str, content: &'a [u8]) -> Result<(), Self::Error> { let mut file = tokio::fs::File::create(path).await?; - file.write_all(content) - .await - .map_err(|e| anyhow!("{}", e))?; + file.write_all(content).await?; Ok(()) } - async fn read<'a>(&'a self, path: &'a str) -> anyhow::Result { + async fn read<'a>(&'a self, path: &'a str) -> Result { let mut file = tokio::fs::File::open(path).await?; let mut buffer = Vec::new(); - file.read_to_end(&mut buffer) - .await - .map_err(|e| anyhow!("{}", e))?; + file.read_to_end(&mut buffer).await?; Ok(String::from_utf8(buffer)?) } }