1use reqwest::StatusCode;
2use serde::Deserialize;
3
4pub type Result<T> = std::result::Result<T, Error>;
5
6#[derive(Debug, thiserror::Error)]
7pub enum Error {
8 #[error("I/O error: {0}")]
9 IoError(#[from] std::io::Error),
10
11 #[error("Json error: {0}")]
12 JsonError(#[from] serde_json::Error),
13
14 #[error("HTTP error: {0}")]
15 HttpError(#[from] reqwest::Error),
16
17 #[error("TOML error: {0}")]
18 TomlError(#[from] toml::ser::Error),
19
20 #[error("URL error: {0}")]
21 UrlError(#[from] url::ParseError),
22
23 #[error("Failed to get relative path")]
24 RelativePathError(#[from] std::path::StripPrefixError),
25
26 #[error("{error}")]
27 ApiResponseError { status: StatusCode, error: String },
28
29 #[error("Forc.toml not found in the current directory")]
30 ForcTomlNotFound,
31
32 #[error("Invalid forc.toml: {0}")]
33 InvalidForcToml(#[from] anyhow::Error),
34
35 #[error("missing version field in [project] section of Forc.toml")]
36 MissingVersionField,
37
38 #[error("Workspace is not supported yet, deploy each member separately")]
39 WorkspaceNotSupported,
40
41 #[error("{0} is not a forc.pub dependency, depend on it using version.")]
42 DependencyMissingVersion(String),
43
44 #[error("Server error")]
45 ServerError,
46
47 #[error("Readme pre-process error: {0}")]
48 MDPreProcessError(#[from] crate::md_pre_process::error::MDPreProcessError),
49}
50
51#[derive(Deserialize)]
52pub struct ApiErrorResponse {
53 error: String,
54}
55
56impl Error {
57 pub async fn from_response(response: reqwest::Response) -> Self {
59 let status = response.status();
60 match response.json::<ApiErrorResponse>().await {
61 Ok(parsed_error) => Error::ApiResponseError {
62 status,
63 error: parsed_error.error,
64 },
65 Err(err) => Error::ApiResponseError {
66 status,
67 error: format!("Unexpected API error: {err}"),
68 },
69 }
70 }
71}
72
73#[cfg(test)]
74mod test {
75 use super::*;
76 use reqwest::StatusCode;
77 use serde_json::json;
78 use wiremock::matchers::{method, path};
79 use wiremock::{Mock, MockServer, ResponseTemplate};
80
81 #[tokio::test]
82 async fn test_error_from_response_with_valid_json() {
83 let mock_server = MockServer::start().await;
84
85 let error_json = json!({
87 "error": "Invalid request data"
88 });
89
90 Mock::given(method("POST"))
91 .and(path("/test"))
92 .respond_with(ResponseTemplate::new(400).set_body_json(&error_json))
93 .mount(&mock_server)
94 .await;
95
96 let client = reqwest::Client::new();
97 let response = client
98 .post(format!("{}/test", mock_server.uri()))
99 .send()
100 .await
101 .unwrap();
102
103 let error = Error::from_response(response).await;
104
105 match error {
106 Error::ApiResponseError { status, error } => {
107 assert_eq!(status, StatusCode::BAD_REQUEST);
108 assert_eq!(error, "Invalid request data");
109 }
110 _ => panic!("Expected ApiResponseError"),
111 }
112 }
113
114 #[tokio::test]
115 async fn test_error_from_response_with_invalid_json() {
116 let mock_server = MockServer::start().await;
117
118 let invalid_json = "not a json object";
120
121 Mock::given(method("POST"))
122 .and(path("/test"))
123 .respond_with(ResponseTemplate::new(500).set_body_string(invalid_json))
124 .mount(&mock_server)
125 .await;
126
127 let client = reqwest::Client::new();
128 let response = client
129 .post(format!("{}/test", mock_server.uri()))
130 .send()
131 .await
132 .unwrap();
133
134 let error = Error::from_response(response).await;
135
136 match error {
137 Error::ApiResponseError { status, error } => {
138 assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
139 assert_eq!(error, "Unexpected API error: error decoding response body");
140 }
141 _ => panic!("Expected ApiResponseError"),
142 }
143 }
144}