Return structured server errors in FFI

This commit is contained in:
Tyler Hallada 2021-02-28 18:50:10 -05:00
parent e80fb6b9d0
commit dffe435c59
10 changed files with 568 additions and 294 deletions

View File

@ -5,6 +5,65 @@
#include <cassert>
struct FFIServerError {
uint16_t status;
const char *title;
const char *detail;
};
struct FFIError {
enum class Tag : uint8_t {
Server,
Network,
};
struct Server_Body {
FFIServerError _0;
};
struct Network_Body {
const char *_0;
};
Tag tag;
union {
Server_Body server;
Network_Body network;
};
static FFIError Server(const FFIServerError &_0) {
FFIError result;
::new (&result.server._0) (FFIServerError)(_0);
result.tag = Tag::Server;
return result;
}
bool IsServer() const {
return tag == Tag::Server;
}
const FFIServerError& AsServer() const {
assert(IsServer());
return server._0;
}
static FFIError Network(const char *const &_0) {
FFIError result;
::new (&result.network._0) (const char*)(_0);
result.tag = Tag::Network;
return result;
}
bool IsNetwork() const {
return tag == Tag::Network;
}
const char*const & AsNetwork() const {
assert(IsNetwork());
return network._0;
}
};
template<typename T>
struct FFIResult {
enum class Tag : uint8_t {
@ -17,7 +76,7 @@ struct FFIResult {
};
struct Err_Body {
const char *_0;
FFIError _0;
};
Tag tag;
@ -42,9 +101,9 @@ struct FFIResult {
return ok._0;
}
static FFIResult Err(const char *const &_0) {
static FFIResult Err(const FFIError &_0) {
FFIResult result;
::new (&result.err._0) (const char*)(_0);
::new (&result.err._0) (FFIError)(_0);
result.tag = Tag::Err;
return result;
}
@ -53,7 +112,7 @@ struct FFIResult {
return tag == Tag::Err;
}
const char*const & AsErr() const {
const FFIError& AsErr() const {
assert(IsErr());
return err._0;
}

View File

@ -83,29 +83,43 @@ pub fn update_file_caches(
pub fn from_file_cache<T: for<'de> Deserialize<'de>>(cache_path: &Path) -> Result<T> {
#[cfg(not(test))]
let file = File::open(cache_path).context(format!(
let file = File::open(cache_path).with_context(|| {
format!(
"Object not found in API or in cache: {}",
cache_path.file_name().unwrap_or_default().to_string_lossy()
))?;
)
})?;
#[cfg(test)]
let file = tempfile()?; // cache always reads from an empty temp file in cfg(test)
let reader = BufReader::new(file);
info!("returning value from cache: {:?}", cache_path);
Ok(bincode::deserialize_from(reader)?)
Ok(bincode::deserialize_from(reader).with_context(|| {
format!(
"Object not found in API or in cache: {}",
cache_path.file_name().unwrap_or_default().to_string_lossy(),
)
})?)
}
pub fn load_metadata_from_file_cache(cache_path: &Path) -> Result<Metadata> {
#[cfg(not(test))]
let file = File::open(cache_path).context(format!(
let file = File::open(cache_path).with_context(|| {
format!(
"Object not found in API or in cache: {}",
cache_path.file_name().unwrap_or_default().to_string_lossy()
))?;
)
})?;
#[cfg(test)]
let file = tempfile()?; // cache always reads from an empty temp file in cfg(test)
let reader = BufReader::new(file);
info!("returning value from cache: {:?}", cache_path);
let metadata: Metadata = serde_json::from_reader(reader)?;
let metadata: Metadata = serde_json::from_reader(reader).with_context(|| {
format!(
"Object not found in API or in cache: {}",
cache_path.file_name().unwrap_or_default().to_string_lossy(),
)
})?;
Ok(metadata)
}

View File

@ -10,7 +10,11 @@ use log::{error, info};
#[cfg(test)]
use std::{println as info, println as error};
use crate::{log_server_error, result::FFIResult};
use crate::{
error::extract_error_from_response,
log_server_error,
result::{FFIError, FFIResult},
};
#[no_mangle]
pub extern "C" fn init() -> bool {
@ -33,34 +37,30 @@ pub extern "C" fn status_check(api_url: *const c_char) -> FFIResult<bool> {
let api_url = unsafe { CStr::from_ptr(api_url) }.to_string_lossy();
info!("status_check api_url: {:?}", api_url);
fn inner(api_url: &str) -> Result<Response> {
fn inner(api_url: &str) -> Result<()> {
#[cfg(not(test))]
let api_url = Url::parse(api_url)?.join("v1/status")?;
#[cfg(test)]
let api_url = Url::parse(&mockito::server_url())?.join("v1/status")?;
Ok(reqwest::blocking::get(api_url)?)
let resp = reqwest::blocking::get(api_url)?;
let status = resp.status();
let bytes = resp.bytes()?;
if status.is_success() {
Ok(())
} else {
Err(extract_error_from_response(status, &bytes))
}
}
match inner(&api_url) {
Ok(resp) if resp.status() == 200 => {
Ok(()) => {
info!("status_check ok");
FFIResult::Ok(true)
}
Ok(resp) => {
error!("status_check failed. Server error");
log_server_error(resp);
let err_string = CString::new("API returned a non-200 status code".to_string())
.expect("could not create CString")
.into_raw();
FFIResult::Err(err_string)
}
Err(err) => {
error!("status_check failed. {}", err);
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
FFIResult::Err(err_string)
FFIResult::Err(FFIError::from(err))
}
}
}
@ -91,9 +91,17 @@ mod tests {
FFIResult::Ok(success) => {
assert_eq!(success, true);
}
FFIResult::Err(error) => panic!("status_check returned error: {:?}", unsafe {
CStr::from_ptr(error).to_string_lossy()
FFIResult::Err(error) => panic!(
"status_check returned error: {:?}",
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -109,12 +117,16 @@ mod tests {
mock.assert();
match result {
FFIResult::Ok(success) => panic!("status_check returned Ok result: {:?}", success),
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Server(server_error) => {
assert_eq!(server_error.status, 500);
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"API returned a non-200 status code"
unsafe { CStr::from_ptr(server_error.title).to_string_lossy() },
"Internal Server Error"
);
}
_ => panic!("status_check did not return a server error"),
},
}
}
}

View File

@ -1,3 +1,4 @@
use std::fmt;
use std::str;
use anyhow::{anyhow, Error};
@ -10,27 +11,51 @@ use log::error;
#[cfg(test)]
use std::println as error;
#[derive(Debug)]
pub struct ServerError {
pub status: StatusCode,
pub title: String,
pub detail: Option<String>,
}
impl fmt::Display for ServerError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(detail) = &self.detail {
write!(
f,
"Server {} {}: {}",
self.status.as_u16(),
self.title,
detail
)
} else {
write!(f, "Server {} {}", self.status.as_u16(), self.title)
}
}
}
pub fn extract_error_from_response(status: StatusCode, bytes: &Bytes) -> Error {
match serde_json::from_slice::<HttpApiProblem>(bytes) {
Ok(api_problem) => {
let detail = api_problem.detail.unwrap_or("".to_string());
error!(
"Server {}: {}. {}",
status.as_u16(),
api_problem.title,
detail
);
anyhow!(format!(
"Server {}: {}. {}",
status.as_u16(),
api_problem.title,
detail
))
let server_error = ServerError {
status,
title: api_problem.title,
detail: api_problem.detail,
};
error!("{}", server_error);
anyhow!(server_error)
}
Err(_) => {
let detail = str::from_utf8(bytes).unwrap_or("unknown");
error!("Server {}: {}", status.as_u16(), detail);
anyhow!(format!("Server {}: {}", status.as_u16(), detail))
let title = str::from_utf8(bytes)
.unwrap_or_else(|_| &status.canonical_reason().unwrap_or("unknown"))
.to_string();
let server_error = ServerError {
status,
title,
detail: None,
};
error!("{}", server_error);
anyhow!(server_error)
}
}
}

View File

@ -11,9 +11,13 @@ use log::{error, info};
use std::{println as info, println as error};
use crate::{
cache::file_cache_dir, cache::from_file_cache, cache::load_metadata_from_file_cache,
cache::update_file_caches, error::extract_error_from_response, log_server_error,
result::FFIResult,
cache::file_cache_dir,
cache::from_file_cache,
cache::load_metadata_from_file_cache,
cache::update_file_caches,
error::extract_error_from_response,
log_server_error,
result::{FFIError, FFIResult},
};
#[derive(Serialize, Deserialize, Debug)]
@ -334,11 +338,7 @@ pub extern "C" fn create_interior_ref_list(
Ok(interior_ref_list) => FFIResult::Ok(interior_ref_list.id),
Err(err) => {
error!("create_interior_ref_list failed. {}", err);
// TODO: also need to drop this CString once C++ is done reading it
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
FFIResult::Err(err_string)
FFIResult::Err(FFIError::from(err))
}
}
}
@ -419,11 +419,7 @@ pub extern "C" fn update_interior_ref_list(
Ok(interior_ref_list) => FFIResult::Ok(interior_ref_list.id),
Err(err) => {
error!("update_interior_ref_list failed. {}", err);
// TODO: also need to drop this CString once C++ is done reading it
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
FFIResult::Err(err_string)
FFIResult::Err(FFIError::from(err))
}
}
}
@ -526,13 +522,8 @@ pub extern "C" fn get_interior_ref_list(
})
}
Err(err) => {
error!("interior_ref_list failed. {}", err);
// TODO: how to do error handling?
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
// TODO: also need to drop this CString once C++ is done reading it
FFIResult::Err(err_string)
error!("get_interior_ref_list failed. {}", err);
FFIResult::Err(FFIError::from(err))
}
}
}
@ -633,12 +624,7 @@ pub extern "C" fn get_interior_ref_list_by_shop_id(
}
Err(err) => {
error!("get_interior_ref_list_by_shop_id failed. {}", err);
// TODO: how to do error handling?
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
// TODO: also need to drop this CString once C++ is done reading it
FFIResult::Err(err_string)
FFIResult::Err(FFIError::from(err))
}
}
}
@ -742,11 +728,17 @@ mod tests {
FFIResult::Ok(interior_ref_list_id) => {
assert_eq!(interior_ref_list_id, 1);
}
FFIResult::Err(error) => {
panic!("create_interior_ref_list returned error: {:?}", unsafe {
CStr::from_ptr(error).to_string_lossy()
})
FFIResult::Err(error) => panic!(
"create_interior_ref_list returned error: {:?}",
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -805,12 +797,16 @@ mod tests {
"create_interior_ref_list returned Ok result: {:?}",
interior_ref_list_id
),
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Server(server_error) => {
assert_eq!(server_error.status, 500);
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"Server 500: Internal Server Error"
unsafe { CStr::from_ptr(server_error.title).to_string_lossy() },
"Internal Server Error"
);
}
_ => panic!("create_interior_ref_list did not return a server error"),
},
}
}
@ -905,11 +901,17 @@ mod tests {
FFIResult::Ok(interior_ref_list_id) => {
assert_eq!(interior_ref_list_id, 1);
}
FFIResult::Err(error) => {
panic!("update_interior_ref_list returned error: {:?}", unsafe {
CStr::from_ptr(error).to_string_lossy()
})
FFIResult::Err(error) => panic!(
"update_interior_ref_list returned error: {:?}",
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -968,12 +970,16 @@ mod tests {
"update_interior_ref_list returned Ok result: {:?}",
interior_ref_list_id
),
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Server(server_error) => {
assert_eq!(server_error.status, 500);
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"Server 500: Internal Server Error"
unsafe { CStr::from_ptr(server_error.title).to_string_lossy() },
"Internal Server Error"
);
}
_ => panic!("update_interior_ref_list did not return a server error"),
},
}
}
@ -1080,9 +1086,17 @@ mod tests {
assert_eq!(raw_shelf.sort_on, std::ptr::null());
assert_eq!(raw_shelf.sort_asc, true);
}
FFIResult::Err(error) => panic!("get_interior_ref_list returned error: {:?}", unsafe {
CStr::from_ptr(error).to_string_lossy()
FFIResult::Err(error) => panic!(
"get_interior_ref_list returned error: {:?}",
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -1102,12 +1116,15 @@ mod tests {
"get_interior_ref_list returned Ok result: {:#x?}",
raw_interior_ref_vec
),
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Network(network_error) => {
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"io error: failed to fill whole buffer" // empty tempfile
unsafe { CStr::from_ptr(network_error).to_string_lossy() },
"Object not found in API or in cache: interior_ref_list_1.bin",
);
}
_ => panic!("get_interior_ref_list did not return a network error"),
},
}
}
@ -1216,7 +1233,14 @@ mod tests {
}
FFIResult::Err(error) => panic!(
"get_interior_ref_list_by_shop_id returned error: {:?}",
unsafe { CStr::from_ptr(error).to_string_lossy() }
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -1237,12 +1261,15 @@ mod tests {
"get_interior_ref_list_by_shop_id returned Ok result: {:#x?}",
raw_interior_ref_vec
),
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Network(network_error) => {
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"io error: failed to fill whole buffer" // empty tempfile
unsafe { CStr::from_ptr(network_error).to_string_lossy() },
"Object not found in API or in cache: shop_1_interior_ref_list.bin",
);
}
_ => panic!("get_interior_ref_list_by_shop_id did not return a network error"),
},
}
}
}

View File

@ -13,7 +13,7 @@ use std::{println as info, println as error};
use crate::{
cache::file_cache_dir, cache::from_file_cache, cache::load_metadata_from_file_cache,
cache::update_file_caches, error::extract_error_from_response, log_server_error,
result::FFIResult,
result::{FFIError, FFIResult},
};
#[derive(Serialize, Deserialize, Debug, Clone)]
@ -207,12 +207,7 @@ pub extern "C" fn create_merchandise_list(
}
Err(err) => {
error!("create_merchandise_list failed. {}", err);
// TODO: how to do error handling?
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
// TODO: also need to drop this CString once C++ is done reading it
FFIResult::Err(err_string)
FFIResult::Err(FFIError::from(err))
}
}
}
@ -312,12 +307,7 @@ pub extern "C" fn update_merchandise_list(
}
Err(err) => {
error!("update_merchandise_list failed. {}", err);
// TODO: how to do error handling?
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
// TODO: also need to drop this CString once C++ is done reading it
FFIResult::Err(err_string)
FFIResult::Err(FFIError::from(err))
}
}
}
@ -428,13 +418,8 @@ pub extern "C" fn get_merchandise_list(
FFIResult::Ok(RawMerchandiseVec { ptr, len, cap })
}
Err(err) => {
error!("merchandise_list failed. {}", err);
// TODO: how to do error handling?
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
// TODO: also need to drop this CString once C++ is done reading it
FFIResult::Err(err_string)
error!("get_merchandise_list failed. {}", err);
FFIResult::Err(FFIError::from(err))
}
}
}
@ -539,12 +524,7 @@ pub extern "C" fn get_merchandise_list_by_shop_id(
}
Err(err) => {
error!("get_merchandise_list_by_shop_id failed. {}", err);
// TODO: how to do error handling?
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
// TODO: also need to drop this CString once C++ is done reading it
FFIResult::Err(err_string)
FFIResult::Err(FFIError::from(err))
}
}
}
@ -627,11 +607,17 @@ mod tests {
assert_eq!(raw_merchandise.is_food, false);
assert_eq!(raw_merchandise.price, 100);
}
FFIResult::Err(error) => {
panic!("create_merchandise_list returned error: {:?}", unsafe {
CStr::from_ptr(error).to_string_lossy()
})
FFIResult::Err(error) => panic!(
"create_merchandise_list returned error: {:?}",
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -666,12 +652,16 @@ mod tests {
"create_merchandise_list returned Ok result: {:#x?}",
raw_merchandise_vec
),
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Server(server_error) => {
assert_eq!(server_error.status, 500);
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"Server 500: Internal Server Error"
unsafe { CStr::from_ptr(server_error.title).to_string_lossy() },
"Internal Server Error"
);
}
_ => panic!("create_merchandise_list did not return a server error"),
},
}
}
@ -745,11 +735,17 @@ mod tests {
assert_eq!(raw_merchandise.is_food, false);
assert_eq!(raw_merchandise.price, 100);
}
FFIResult::Err(error) => {
panic!("update_merchandise_list returned error: {:?}", unsafe {
CStr::from_ptr(error).to_string_lossy()
})
FFIResult::Err(error) => panic!(
"update_merchandise_list returned error: {:?}",
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -784,12 +780,16 @@ mod tests {
"update_merchandise_list returned Ok result: {:#x?}",
raw_merchandise_vec
),
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Server(server_error) => {
assert_eq!(server_error.status, 500);
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"Server 500: Internal Server Error"
unsafe { CStr::from_ptr(server_error.title).to_string_lossy() },
"Internal Server Error"
);
}
_ => panic!("update_merchandise_list did not return a server error"),
},
}
}
#[test]
@ -857,9 +857,17 @@ mod tests {
"VendorItemWeapon".to_string(),
);
}
FFIResult::Err(error) => panic!("get_merchandise_list returned error: {:?}", unsafe {
CStr::from_ptr(error).to_string_lossy()
FFIResult::Err(error) => panic!(
"get_merchandise_list returned error: {:?}",
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -879,12 +887,15 @@ mod tests {
"get_merchandise_list returned Ok result: {:#x?}",
raw_merchandise_vec
),
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Network(network_error) => {
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"io error: failed to fill whole buffer" // empty tempfile
unsafe { CStr::from_ptr(network_error).to_string_lossy() },
"Object not found in API or in cache: merchandise_list_1.bin",
);
}
_ => panic!("get_merchandise_list did not return a network error"),
},
}
}
@ -955,7 +966,14 @@ mod tests {
}
FFIResult::Err(error) => panic!(
"get_merchandise_list_by_shop_id returned error: {:?}",
unsafe { CStr::from_ptr(error).to_string_lossy() }
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -976,12 +994,15 @@ mod tests {
"get_merchandise_list_by_shop_id returned Ok result: {:#x?}",
raw_merchandise_vec
),
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Network(network_error) => {
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"io error: failed to fill whole buffer" // empty tempfile
unsafe { CStr::from_ptr(network_error).to_string_lossy() },
"Object not found in API or in cache: shop_1_merchandise_list.bin",
);
}
_ => panic!("get_merchandise_list_by_shop_id did not return a network error"),
},
}
}
}

View File

@ -11,8 +11,10 @@ use log::{error, info};
use std::{println as info, println as error};
use crate::{
cache::file_cache_dir, cache::update_file_caches, error::extract_error_from_response,
result::FFIResult,
cache::file_cache_dir,
cache::update_file_caches,
error::extract_error_from_response,
result::{FFIError, FFIResult},
};
#[derive(Serialize, Deserialize, Debug)]
@ -112,11 +114,7 @@ pub extern "C" fn create_owner(
}
Err(err) => {
error!("create_owner failed. {}", err);
// TODO: also need to drop this CString once C++ is done reading it
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
FFIResult::Err(err_string)
FFIResult::Err(FFIError::from(err))
}
}
}
@ -182,11 +180,7 @@ pub extern "C" fn update_owner(
}
Err(err) => {
error!("update_owner failed. {}", err);
// TODO: also need to drop this CString once C++ is done reading it
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
FFIResult::Err(err_string)
FFIResult::Err(FFIError::from(err))
}
}
}
@ -229,9 +223,17 @@ mod tests {
);
assert_eq!(raw_owner.mod_version, 1);
}
FFIResult::Err(error) => panic!("create_owner returned error: {:?}", unsafe {
CStr::from_ptr(error).to_string_lossy()
FFIResult::Err(error) => panic!(
"create_owner returned error: {:?}",
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -252,12 +254,16 @@ mod tests {
FFIResult::Ok(raw_owner) => {
panic!("create_owner returned Ok result: {:#x?}", raw_owner)
}
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Server(server_error) => {
assert_eq!(server_error.status, 500);
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"Server 500: Internal Server Error"
unsafe { CStr::from_ptr(server_error.title).to_string_lossy() },
"Internal Server Error"
);
}
_ => panic!("create_owner did not return a server error"),
},
}
}
@ -291,9 +297,17 @@ mod tests {
);
assert_eq!(raw_owner.mod_version, 1);
}
FFIResult::Err(error) => panic!("update_owner returned error: {:?}", unsafe {
CStr::from_ptr(error).to_string_lossy()
FFIResult::Err(error) => panic!(
"update_owner returned error: {:?}",
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -314,12 +328,16 @@ mod tests {
FFIResult::Ok(raw_owner) => {
panic!("update_owner returned Ok result: {:#x?}", raw_owner)
}
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Server(server_error) => {
assert_eq!(server_error.status, 500);
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"Server 500: Internal Server Error"
unsafe { CStr::from_ptr(server_error.title).to_string_lossy() },
"Internal Server Error"
);
}
_ => panic!("update_owner did not return a server error"),
},
}
}
}

View File

@ -1,8 +1,62 @@
use anyhow::Error;
use std::convert::From;
use std::ffi::CString;
use std::os::raw::c_char;
use std::ptr::null;
use crate::error::ServerError;
#[derive(Debug, PartialEq)]
#[repr(C)]
pub struct FFIServerError {
pub status: u16,
pub title: *const c_char,
pub detail: *const c_char,
}
impl From<&ServerError> for FFIServerError {
fn from(server_error: &ServerError) -> Self {
FFIServerError {
status: server_error.status.as_u16(),
// TODO: may need to drop these CStrings once C++ is done reading them
title: CString::new(server_error.title.clone())
.expect("could not create CString")
.into_raw(),
detail: match &server_error.detail {
Some(detail) => CString::new(detail.clone())
.expect("could not create CString")
.into_raw(),
None => null(),
},
}
}
}
#[derive(Debug, PartialEq)]
#[repr(C, u8)]
pub enum FFIError {
Server(FFIServerError),
Network(*const c_char),
}
impl From<Error> for FFIError {
fn from(error: Error) -> Self {
if let Some(server_error) = error.downcast_ref::<ServerError>() {
FFIError::Server(FFIServerError::from(server_error))
} else {
// TODO: also need to drop this CString once C++ is done reading it
let err_string = CString::new(error.to_string())
.expect("could not create CString")
.into_raw();
FFIError::Network(err_string)
}
}
}
#[derive(Debug, PartialEq)]
#[repr(C, u8)]
pub enum FFIResult<T> {
Ok(T),
Err(*const c_char),
Err(FFIError),
}

View File

@ -11,9 +11,13 @@ use log::{error, info};
use std::{println as info, println as error};
use crate::{
cache::file_cache_dir, cache::from_file_cache, cache::load_metadata_from_file_cache,
cache::update_file_caches, error::extract_error_from_response, log_server_error,
result::FFIResult,
cache::file_cache_dir,
cache::from_file_cache,
cache::load_metadata_from_file_cache,
cache::update_file_caches,
error::extract_error_from_response,
log_server_error,
result::{FFIError, FFIResult},
};
#[derive(Serialize, Deserialize, Debug)]
@ -155,11 +159,7 @@ pub extern "C" fn create_shop(
}
Err(err) => {
error!("create_shop failed. {}", err);
// TODO: also need to drop this CString once C++ is done reading it
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
FFIResult::Err(err_string)
FFIResult::Err(FFIError::from(err))
}
}
}
@ -272,11 +272,7 @@ pub extern "C" fn update_shop(
}
Err(err) => {
error!("update_shop failed. {}", err);
// TODO: also need to drop this CString once C++ is done reading it
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
FFIResult::Err(err_string)
FFIResult::Err(FFIError::from(err))
}
}
}
@ -346,12 +342,8 @@ pub extern "C" fn get_shop(
FFIResult::Ok(RawShop::from(shop))
}
Err(err) => {
error!("get_shop_list failed. {}", err);
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
// TODO: also need to drop this CString once C++ is done reading it
FFIResult::Err(err_string)
error!("get_shop failed. {}", err);
FFIResult::Err(FFIError::from(err))
}
}
}
@ -419,11 +411,7 @@ pub extern "C" fn list_shops(
}
Err(err) => {
error!("list_shops failed. {}", err);
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
// TODO: also need to drop this CString once C++ is done reading it
FFIResult::Err(err_string)
FFIResult::Err(FFIError::from(err))
}
}
}
@ -490,9 +478,17 @@ mod tests {
);
assert_eq!(raw_shop.vendor_keywords_exclude, true);
}
FFIResult::Err(error) => panic!("create_shop returned error: {:?}", unsafe {
CStr::from_ptr(error).to_string_lossy()
FFIResult::Err(error) => panic!(
"create_shop returned error: {:?}",
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -511,12 +507,16 @@ mod tests {
mock.assert();
match result {
FFIResult::Ok(raw_shop) => panic!("create_shop returned Ok result: {:#x?}", raw_shop),
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Server(server_error) => {
assert_eq!(server_error.status, 500);
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"Server 500: Internal Server Error"
unsafe { CStr::from_ptr(server_error.title).to_string_lossy() },
"Internal Server Error"
);
}
_ => panic!("create_shop did not return a server error"),
},
}
}
@ -589,9 +589,17 @@ mod tests {
);
assert_eq!(raw_shop.vendor_keywords_exclude, true);
}
FFIResult::Err(error) => panic!("update_shop returned error: {:?}", unsafe {
CStr::from_ptr(error).to_string_lossy()
FFIResult::Err(error) => panic!(
"update_shop returned error: {:?}",
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -625,12 +633,16 @@ mod tests {
mock.assert();
match result {
FFIResult::Ok(raw_shop) => panic!("update_shop returned Ok result: {:#x?}", raw_shop),
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Server(server_error) => {
assert_eq!(server_error.status, 500);
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"Server 500: Internal Server Error"
unsafe { CStr::from_ptr(server_error.title).to_string_lossy() },
"Internal Server Error"
);
}
_ => panic!("update_shop did not return a server error"),
},
}
}
@ -686,9 +698,17 @@ mod tests {
);
assert_eq!(raw_shop.vendor_keywords_exclude, true);
}
FFIResult::Err(error) => panic!("get_shop returned error: {:?}", unsafe {
CStr::from_ptr(error).to_string_lossy()
FFIResult::Err(error) => panic!(
"get_shop returned error: {:?}",
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -705,12 +725,15 @@ mod tests {
mock.assert();
match result {
FFIResult::Ok(raw_shop) => panic!("get_shop returned Ok result: {:#x?}", raw_shop),
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Network(network_error) => {
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"io error: failed to fill whole buffer" // empty tempfile
unsafe { CStr::from_ptr(network_error).to_string_lossy() },
"Object not found in API or in cache: shop_1.bin",
);
}
_ => panic!("get_shop did not return a network error"),
},
}
}
@ -771,9 +794,17 @@ mod tests {
);
assert_eq!(raw_shop.vendor_keywords_exclude, true);
}
FFIResult::Err(error) => panic!("list_shops returned error: {:?}", unsafe {
CStr::from_ptr(error).to_string_lossy()
FFIResult::Err(error) => panic!(
"list_shops returned error: {:?}",
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -790,12 +821,15 @@ mod tests {
mock.assert();
match result {
FFIResult::Ok(raw_shop) => panic!("list_shops returned Ok result: {:#x?}", raw_shop),
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Network(network_error) => {
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"io error: failed to fill whole buffer" // empty tempfile
unsafe { CStr::from_ptr(network_error).to_string_lossy() },
"Object not found in API or in cache: shops.bin",
);
}
_ => panic!("list_shops did not return a network error"),
},
}
}
}

View File

@ -11,8 +11,10 @@ use log::{error, info};
use std::{println as info, println as error};
use crate::{
cache::file_cache_dir, cache::update_file_caches, error::extract_error_from_response,
result::FFIResult,
cache::file_cache_dir,
cache::update_file_caches,
error::extract_error_from_response,
result::{FFIError, FFIResult},
};
#[derive(Serialize, Deserialize, Debug)]
@ -193,11 +195,7 @@ pub extern "C" fn create_transaction(
Ok(transaction) => FFIResult::Ok(RawTransaction::from(transaction)),
Err(err) => {
error!("create_transaction failed. {}", err);
// TODO: also need to drop this CString once C++ is done reading it
let err_string = CString::new(err.to_string())
.expect("could not create CString")
.into_raw();
FFIResult::Err(err_string)
FFIResult::Err(FFIError::from(err))
}
}
}
@ -294,9 +292,17 @@ mod tests {
vec!["VendorItemMisc".to_string()]
);
}
FFIResult::Err(error) => panic!("create_transaction returned error: {:?}", unsafe {
CStr::from_ptr(error).to_string_lossy()
FFIResult::Err(error) => panic!(
"create_transaction returned error: {:?}",
match error {
FFIError::Server(server_error) =>
format!("{} {}", server_error.status, unsafe {
CStr::from_ptr(server_error.title).to_string_lossy()
}),
FFIError::Network(network_error) =>
unsafe { CStr::from_ptr(network_error).to_string_lossy() }.to_string(),
}
),
}
}
@ -344,12 +350,16 @@ mod tests {
"create_transaction returned Ok result: {:#?}",
raw_transaction
),
FFIResult::Err(error) => {
FFIResult::Err(error) => match error {
FFIError::Server(server_error) => {
assert_eq!(server_error.status, 500);
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"Server 500: Internal Server Error. Some error detail"
unsafe { CStr::from_ptr(server_error.title).to_string_lossy() },
"Internal Server Error"
);
}
_ => panic!("create_transaction did not return a server error"),
},
}
}
}