Skip to content

Commit a578ee8

Browse files
committed
chore(config/reader): add and use ConfigReaderError to propagate errors better
1 parent d343032 commit a578ee8

File tree

3 files changed

+103
-8
lines changed

3 files changed

+103
-8
lines changed

src/config/reader.rs

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,105 @@
11
use std::collections::{HashMap, VecDeque};
22

33
use anyhow::Context;
4+
use async_graphql::{ErrorExtensionValues, ServerError};
5+
use async_graphql_value::ConstValue;
46
use async_std::path::{Path, PathBuf};
57
use futures_util::future::join_all;
68
use futures_util::TryFutureExt;
79
use prost_reflect::prost_types::{FileDescriptorProto, FileDescriptorSet};
810
use protox::file::{FileResolver, GoogleFileResolver};
911
use url::Url;
1012

11-
use super::{ConfigModule, Content, Link, LinkType};
13+
use super::{ConfigModule, Content, Link, LinkType, ParseError, UnsupportedFileFormat};
14+
use crate::async_graphql_hyper::GraphQLResponse;
1215
use crate::config::{Config, Source};
1316
use crate::target_runtime::TargetRuntime;
17+
use crate::valid::ValidationError;
18+
19+
#[derive(Debug)]
20+
pub enum ConfigReaderError {
21+
Parse(ParseError),
22+
Validation(ValidationError<String>),
23+
UnsupportedFileFormat(UnsupportedFileFormat),
24+
Anyhow(anyhow::Error),
25+
}
26+
27+
impl std::fmt::Display for ConfigReaderError {
28+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29+
match self {
30+
Self::Parse(x) => std::fmt::Display::fmt(x, f),
31+
Self::Validation(x) => std::fmt::Display::fmt(x, f),
32+
Self::UnsupportedFileFormat(x) => std::fmt::Display::fmt(x, f),
33+
Self::Anyhow(x) => std::fmt::Display::fmt(x, f),
34+
}
35+
}
36+
}
37+
38+
impl std::error::Error for ConfigReaderError {}
39+
40+
impl From<ParseError> for ConfigReaderError {
41+
fn from(value: ParseError) -> Self {
42+
ConfigReaderError::Parse(value)
43+
}
44+
}
45+
46+
impl From<UnsupportedFileFormat> for ConfigReaderError {
47+
fn from(value: UnsupportedFileFormat) -> Self {
48+
ConfigReaderError::UnsupportedFileFormat(value)
49+
}
50+
}
51+
52+
impl From<anyhow::Error> for ConfigReaderError {
53+
fn from(value: anyhow::Error) -> Self {
54+
ConfigReaderError::Anyhow(value)
55+
}
56+
}
57+
58+
impl From<ConfigReaderError> for GraphQLResponse {
59+
fn from(e: ConfigReaderError) -> GraphQLResponse {
60+
let mut response = async_graphql::Response::default();
61+
62+
match e {
63+
ConfigReaderError::Validation(e)
64+
| ConfigReaderError::Parse(ParseError::Validation(e)) => {
65+
response.errors = e
66+
.as_vec()
67+
.iter()
68+
.map(|cause| {
69+
let mut error = ServerError::new(cause.message.to_owned(), None);
70+
71+
let mut ext: ErrorExtensionValues = ErrorExtensionValues::default();
72+
73+
if let Some(description) = &cause.description {
74+
ext.set("description", ConstValue::String(description.to_owned()));
75+
}
76+
77+
if !cause.trace.is_empty() {
78+
ext.set(
79+
"trace",
80+
ConstValue::List(cause.trace.iter().map(|x| x.into()).collect()),
81+
);
82+
}
83+
84+
error.extensions = Some(ext);
85+
error
86+
})
87+
.collect();
88+
}
89+
ConfigReaderError::Parse(ParseError::GraphQL(e)) => {
90+
response.errors = vec![ServerError::from(e)];
91+
}
92+
_ => {
93+
response.errors = vec![ServerError::new(
94+
format!("Failed to read config: {}", e),
95+
None,
96+
)]
97+
}
98+
}
99+
100+
GraphQLResponse::from(response)
101+
}
102+
}
14103

15104
/// Reads the configuration from a file or from an HTTP URL and resolves all linked extensions to create a ConfigModule.
16105
pub struct ConfigReader {
@@ -142,12 +231,15 @@ impl ConfigReader {
142231
}
143232

144233
/// Reads a single file and returns the config
145-
pub async fn read<T: ToString>(&self, file: T) -> anyhow::Result<ConfigModule> {
234+
pub async fn read<T: ToString>(&self, file: T) -> Result<ConfigModule, ConfigReaderError> {
146235
self.read_all(&[file]).await
147236
}
148237

149238
/// Reads all the files and returns a merged config
150-
pub async fn read_all<T: ToString>(&self, files: &[T]) -> anyhow::Result<ConfigModule> {
239+
pub async fn read_all<T: ToString>(
240+
&self,
241+
files: &[T],
242+
) -> Result<ConfigModule, ConfigReaderError> {
151243
let files = self.read_files(files).await?;
152244
let mut config_set = ConfigModule::default();
153245

src/http/showcase.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,7 @@ pub async fn create_app_ctx<T: DeserializeOwned + GraphQLRequestLike>(
4343
let config = match reader.read(config_url).await {
4444
Ok(config) => config,
4545
Err(e) => {
46-
let mut response = async_graphql::Response::default();
47-
let server_error = ServerError::new(format!("Failed to read config: {}", e), None);
48-
response.errors = vec![server_error];
49-
return Ok(Err(GraphQLResponse::from(response).to_response()?));
46+
return Ok(Err(GraphQLResponse::from(e).to_response()?));
5047
}
5148
};
5249

tests/snapshots/execution_spec__showcase.md_assert_3.snap

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ expression: response
1111
"data": null,
1212
"errors": [
1313
{
14-
"message": "Failed to read config: Validation Error\n• --> 1:1\n |\n1 | \"dsjfsjdfjdsfjkdskjfjkds\"\n | ^---\n |\n = expected type_system_definition\n"
14+
"message": " --> 1:1\n |\n1 | \"dsjfsjdfjdsfjkdskjfjkds\"\n | ^---\n |\n = expected type_system_definition",
15+
"locations": [
16+
{
17+
"line": 1,
18+
"column": 1
19+
}
20+
]
1521
}
1622
]
1723
}

0 commit comments

Comments
 (0)