Add update by shop id functions

This commit is contained in:
Tyler Hallada 2020-10-25 03:17:20 -04:00
parent 32aaa29522
commit 1805bbb4dc
3 changed files with 487 additions and 3 deletions

View File

@ -169,6 +169,10 @@ FFIResult<RawMerchandiseVec> get_merchandise_list(const char *api_url,
const char *api_key,
int32_t merchandise_list_id);
FFIResult<RawMerchandiseVec> get_merchandise_list_by_shop_id(const char *api_url,
const char *api_key,
int32_t shop_id);
FFIResult<RawShop> get_shop(const char *api_url, const char *api_key, int32_t shop_id);
bool init();
@ -177,6 +181,18 @@ FFIResult<RawShopVec> list_shops(const char *api_url, const char *api_key);
FFIResult<bool> status_check(const char *api_url);
FFIResult<int32_t> update_interior_ref_list(const char *api_url,
const char *api_key,
int32_t shop_id,
const RawInteriorRef *raw_interior_ref_ptr,
uintptr_t raw_interior_ref_len);
FFIResult<int32_t> update_merchandise_list(const char *api_url,
const char *api_key,
int32_t shop_id,
const RawMerchandise *raw_merchandise_ptr,
uintptr_t raw_merchandise_len);
FFIResult<RawOwner> update_owner(const char *api_url,
const char *api_key,
uint32_t id,

View File

@ -94,7 +94,6 @@ pub struct RawInteriorRefVec {
pub cap: usize,
}
// Because C++ does not have Result, -1 means that the request was unsuccessful
#[no_mangle]
pub extern "C" fn create_interior_ref_list(
api_url: *const c_char,
@ -170,6 +169,82 @@ pub extern "C" fn create_interior_ref_list(
}
}
#[no_mangle]
pub extern "C" fn update_interior_ref_list(
api_url: *const c_char,
api_key: *const c_char,
shop_id: i32,
raw_interior_ref_ptr: *const RawInteriorRef,
raw_interior_ref_len: usize,
) -> FFIResult<i32> {
let api_url = unsafe { CStr::from_ptr(api_url) }.to_string_lossy();
let api_key = unsafe { CStr::from_ptr(api_key) }.to_string_lossy();
info!("update_interior_ref_list api_url: {:?}, api_key: {:?}, shop_id: {:?}, raw_interior_ref_len: {:?}", api_url, api_key, shop_id, raw_interior_ref_len);
let raw_interior_ref_slice = unsafe {
assert!(!raw_interior_ref_ptr.is_null());
slice::from_raw_parts(raw_interior_ref_ptr, raw_interior_ref_len)
};
fn inner(
api_url: &str,
api_key: &str,
shop_id: i32,
raw_interior_ref_slice: &[RawInteriorRef],
) -> Result<InteriorRefList> {
#[cfg(not(test))]
let url = Url::parse(api_url)?.join(&format!("v1/shops/{}/interior_ref_list", shop_id))?;
#[cfg(test)]
let url = Url::parse(&mockito::server_url())?
.join(&format!("v1/shops/{}/interior_ref_list", shop_id))?;
let interior_ref_list = InteriorRefList::from_game(shop_id, raw_interior_ref_slice);
info!(
"created interior_ref_list from game: shop_id: {}",
&interior_ref_list.shop_id
);
let client = reqwest::blocking::Client::new();
let resp = client
.patch(url)
.header("Api-Key", api_key)
.json(&interior_ref_list)
.send()?;
info!("update interior_ref_list response from api: {:?}", &resp);
let bytes = resp.bytes()?;
let json: InteriorRefList = serde_json::from_slice(&bytes)?;
if let Some(id) = json.id {
update_file_cache(
&file_cache_dir(api_url)?.join(format!("shops_{}_interior_ref_list.json", shop_id)),
&bytes,
)?;
}
Ok(json)
}
match inner(&api_url, &api_key, shop_id, raw_interior_ref_slice) {
Ok(interior_ref_list) => {
if let Some(id) = interior_ref_list.id {
FFIResult::Ok(id)
} else {
error!("update_interior_ref_list failed. API did not return an interior ref list with an ID");
let err_string =
CString::new("API did not return an interior ref list with an ID".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)
}
}
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)
}
}
}
#[no_mangle]
pub extern "C" fn get_interior_ref_list(
api_url: *const c_char,
@ -281,7 +356,7 @@ pub extern "C" fn get_interior_ref_list_by_shop_id(
let client = reqwest::blocking::Client::new();
let cache_path =
file_cache_dir(api_url)?.join(format!("interior_ref_list_{}.json", shop_id));
file_cache_dir(api_url)?.join(format!("shops_{}_interior_ref_list.json", shop_id));
match client.get(url).header("Api-Key", api_key).send() {
Ok(resp) => {
@ -435,6 +510,83 @@ mod tests {
}
}
#[test]
fn test_update_interior_ref_list() {
let mock = mock("PATCH", "/v1/shops/1/interior_ref_list")
.with_status(201)
.with_header("content-type", "application/json")
.with_body(r#"{ "created_at": "2020-08-18T00:00:00.000", "id": 1, "shop_id": 1, "ref_list": [], "updated_at": "2020-08-18T00:00:00.000" }"#)
.create();
let api_url = CString::new("url").unwrap().into_raw();
let api_key = CString::new("api-key").unwrap().into_raw();
let (ptr, len, _cap) = vec![RawInteriorRef {
base_mod_name: CString::new("Skyrim.esm").unwrap().into_raw(),
base_local_form_id: 1,
ref_mod_name: CString::new("BazaarRealm.esp").unwrap().into_raw(),
ref_local_form_id: 1,
position_x: 100.,
position_y: 0.,
position_z: 100.,
angle_x: 0.,
angle_y: 0.,
angle_z: 0.,
scale: 1,
}]
.into_raw_parts();
let result = update_interior_ref_list(api_url, api_key, 1, ptr, len);
mock.assert();
match result {
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()
})
}
}
}
#[test]
fn test_update_interior_ref_list_server_error() {
let mock = mock("PATCH", "/v1/shops/1/interior_ref_list")
.with_status(500)
.with_body("Internal Server Error")
.create();
let api_url = CString::new("url").unwrap().into_raw();
let api_key = CString::new("api-key").unwrap().into_raw();
let (ptr, len, _cap) = vec![RawInteriorRef {
base_mod_name: CString::new("Skyrim.esm").unwrap().into_raw(),
base_local_form_id: 1,
ref_mod_name: CString::new("BazaarRealm.esp").unwrap().into_raw(),
ref_local_form_id: 1,
position_x: 100.,
position_y: 0.,
position_z: 100.,
angle_x: 0.,
angle_y: 0.,
angle_z: 0.,
scale: 1,
}]
.into_raw_parts();
let result = update_interior_ref_list(api_url, api_key, 1, ptr, len);
mock.assert();
match result {
FFIResult::Ok(interior_ref_list_id) => panic!(
"update_interior_ref_list returned Ok result: {:?}",
interior_ref_list_id
),
FFIResult::Err(error) => {
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"expected value at line 1 column 1"
);
}
}
}
#[test]
fn test_get_interior_ref_list() {
let mock = mock("GET", "/v1/interior_ref_lists/1")

View File

@ -154,7 +154,84 @@ pub extern "C" fn create_merchandise_list(
}
}
// TODO: fetch by shop_id
#[no_mangle]
pub extern "C" fn update_merchandise_list(
api_url: *const c_char,
api_key: *const c_char,
shop_id: i32,
raw_merchandise_ptr: *const RawMerchandise,
raw_merchandise_len: usize,
) -> FFIResult<i32> {
let api_url = unsafe { CStr::from_ptr(api_url) }.to_string_lossy();
let api_key = unsafe { CStr::from_ptr(api_key) }.to_string_lossy();
info!("create_merchandise_list api_url: {:?}, api_key: {:?}, shop_id: {:?}, raw_merchandise_len: {:?}", api_url, api_key, shop_id, raw_merchandise_len);
let raw_merchandise_slice = unsafe {
assert!(!raw_merchandise_ptr.is_null());
slice::from_raw_parts(raw_merchandise_ptr, raw_merchandise_len)
};
fn inner(
api_url: &str,
api_key: &str,
shop_id: i32,
raw_merchandise_slice: &[RawMerchandise],
) -> Result<MerchandiseList> {
#[cfg(not(test))]
let url = Url::parse(api_url)?.join(&format!("v1/shops/{}/merchandise_list", shop_id))?;
#[cfg(test)]
let url = Url::parse(&mockito::server_url())?
.join(&format!("v1/shops/{}/merchandise_list", shop_id))?;
let merchandise_list = MerchandiseList::from_game(shop_id, raw_merchandise_slice);
info!(
"created merchandise_list from game: shop_id: {}",
&merchandise_list.shop_id
);
let client = reqwest::blocking::Client::new();
let resp = client
.patch(url)
.header("Api-Key", api_key)
.json(&merchandise_list)
.send()?;
info!("update merchandise_list response from api: {:?}", &resp);
let bytes = resp.bytes()?;
let json: MerchandiseList = serde_json::from_slice(&bytes)?;
if let Some(id) = json.id {
update_file_cache(
&file_cache_dir(api_url)?.join(format!("shops_{}_merchandise_list.json", id)),
&bytes,
)?;
}
Ok(json)
}
match inner(&api_url, &api_key, shop_id, raw_merchandise_slice) {
Ok(merchandise_list) => {
if let Some(id) = merchandise_list.id {
FFIResult::Ok(id)
} else {
error!(
"update_merchandise failed. API did not return an interior ref list with an ID"
);
let err_string =
CString::new("API did not return an interior ref list with an ID".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)
}
}
Err(err) => {
error!("update_merchandise_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)
}
}
}
#[no_mangle]
pub extern "C" fn get_merchandise_list(
api_url: *const c_char,
@ -237,6 +314,89 @@ pub extern "C" fn get_merchandise_list(
}
}
#[no_mangle]
pub extern "C" fn get_merchandise_list_by_shop_id(
api_url: *const c_char,
api_key: *const c_char,
shop_id: i32,
) -> FFIResult<RawMerchandiseVec> {
let api_url = unsafe { CStr::from_ptr(api_url) }.to_string_lossy();
let api_key = unsafe { CStr::from_ptr(api_key) }.to_string_lossy();
info!(
"get_merchandise_list_by_shop_id api_url: {:?}, api_key: {:?}, shop_id: {:?}",
api_url, api_key, shop_id
);
fn inner(api_url: &str, api_key: &str, shop_id: i32) -> Result<MerchandiseList> {
#[cfg(not(test))]
let url = Url::parse(api_url)?.join(&format!("v1/shops/{}/merchandise_list", shop_id))?;
#[cfg(test)]
let url = Url::parse(&mockito::server_url())?
.join(&format!("v1/shops/{}/merchandise_list", shop_id))?;
info!("api_url: {:?}", url);
let client = reqwest::blocking::Client::new();
let cache_path =
file_cache_dir(api_url)?.join(format!("shops_{}_merchandise_list.json", shop_id));
match client.get(url).header("Api-Key", api_key).send() {
Ok(resp) => {
info!(
"get_merchandise_list_by_shop_id response from api: {:?}",
&resp
);
if resp.status().is_success() {
let bytes = resp.bytes()?;
update_file_cache(&cache_path, &bytes)?;
let json = serde_json::from_slice(&bytes)?;
Ok(json)
} else {
log_server_error(resp);
from_file_cache(&cache_path)
}
}
Err(err) => {
error!("get_merchandise_list_by_shop_id api request error: {}", err);
from_file_cache(&cache_path)
}
}
}
match inner(&api_url, &api_key, shop_id) {
Ok(merchandise_list) => {
let (ptr, len, cap) = merchandise_list
.form_list
.into_iter()
.map(|merchandise| RawMerchandise {
mod_name: CString::new(merchandise.mod_name)
.unwrap_or_default()
.into_raw(),
local_form_id: merchandise.local_form_id,
name: CString::new(merchandise.name)
.unwrap_or_default()
.into_raw(),
quantity: merchandise.quantity,
form_type: merchandise.form_type,
is_food: merchandise.is_food as u8,
price: merchandise.price,
})
.collect::<Vec<RawMerchandise>>()
.into_raw_parts();
// TODO: need to pass this back into Rust once C++ is done with it so it can be manually dropped and the CStrings dropped from raw pointers.
FFIResult::Ok(RawMerchandiseVec { ptr, len, cap })
}
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)
}
}
}
#[cfg(test)]
mod tests {
use std::ffi::CString;
@ -313,6 +473,74 @@ mod tests {
}
}
#[test]
fn test_update_merchandise_list() {
let mock = mock("PATCH", "/v1/shops/1/merchandise_list")
.with_status(201)
.with_header("content-type", "application/json")
.with_body(r#"{ "created_at": "2020-08-18T00:00:00.000", "id": 1, "shop_id": 1, "form_list": [], "updated_at": "2020-08-18T00:00:00.000" }"#)
.create();
let api_url = CString::new("url").unwrap().into_raw();
let api_key = CString::new("api-key").unwrap().into_raw();
let (ptr, len, _cap) = vec![RawMerchandise {
mod_name: CString::new("Skyrim.esm").unwrap().into_raw(),
local_form_id: 1,
name: CString::new("Iron Sword").unwrap().into_raw(),
quantity: 1,
form_type: 1,
is_food: 0,
price: 100,
}]
.into_raw_parts();
let result = update_merchandise_list(api_url, api_key, 1, ptr, len);
mock.assert();
match result {
FFIResult::Ok(merchandise_list_id) => {
assert_eq!(merchandise_list_id, 1);
}
FFIResult::Err(error) => {
panic!("update_merchandise_list returned error: {:?}", unsafe {
CStr::from_ptr(error).to_string_lossy()
})
}
}
}
#[test]
fn test_update_interior_ref_list_server_error() {
let mock = mock("PATCH", "/v1/shops/1/merchandise_list")
.with_status(500)
.with_body("Internal Server Error")
.create();
let api_url = CString::new("url").unwrap().into_raw();
let api_key = CString::new("api-key").unwrap().into_raw();
let (ptr, len, _cap) = vec![RawMerchandise {
mod_name: CString::new("Skyrim.esm").unwrap().into_raw(),
local_form_id: 1,
name: CString::new("Iron Sword").unwrap().into_raw(),
quantity: 1,
form_type: 1,
is_food: 0,
price: 100,
}]
.into_raw_parts();
let result = update_merchandise_list(api_url, api_key, 1, ptr, len);
mock.assert();
match result {
FFIResult::Ok(merchandise_list_id) => panic!(
"update_merchandise_list returned Ok result: {:?}",
merchandise_list_id
),
FFIResult::Err(error) => {
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"expected value at line 1 column 1"
);
}
}
}
#[test]
fn test_get_merchandise_list() {
let mock = mock("GET", "/v1/merchandise_lists/1")
@ -399,4 +627,92 @@ mod tests {
}
}
}
#[test]
fn test_get_merchandise_list_by_shop_id() {
let mock = mock("GET", "/v1/shops/1/merchandise_list")
.with_status(201)
.with_header("content-type", "application/json")
.with_body(
r#"{
"created_at": "2020-08-18T00:00:00.000",
"id": 1,
"shop_id": 1,
"form_list": [
{
"mod_name": "Skyrim.esm",
"local_form_id": 1,
"name": "Iron Sword",
"quantity": 1,
"form_type": 1,
"is_food": false,
"price": 100
}
],
"updated_at": "2020-08-18T00:00:00.000"
}"#,
)
.create();
let api_url = CString::new("url").unwrap().into_raw();
let api_key = CString::new("api-key").unwrap().into_raw();
let result = get_merchandise_list_by_shop_id(api_url, api_key, 1);
mock.assert();
match result {
FFIResult::Ok(raw_merchandise_vec) => {
assert_eq!(raw_merchandise_vec.len, 1);
let raw_merchandise_slice = unsafe {
assert!(!raw_merchandise_vec.ptr.is_null());
slice::from_raw_parts(raw_merchandise_vec.ptr, raw_merchandise_vec.len)
};
let raw_merchandise = &raw_merchandise_slice[0];
assert_eq!(
unsafe { CStr::from_ptr(raw_merchandise.mod_name) }
.to_string_lossy()
.to_string(),
"Skyrim.esm".to_string(),
);
assert_eq!(raw_merchandise.local_form_id, 1);
assert_eq!(
unsafe { CStr::from_ptr(raw_merchandise.name) }
.to_string_lossy()
.to_string(),
"Iron Sword".to_string(),
);
assert_eq!(raw_merchandise.quantity, 1);
assert_eq!(raw_merchandise.form_type, 1);
assert_eq!(raw_merchandise.is_food, 0);
assert_eq!(raw_merchandise.price, 100);
}
FFIResult::Err(error) => panic!(
"get_merchandise_list_by_shop_id returned error: {:?}",
unsafe { CStr::from_ptr(error).to_string_lossy() }
),
}
}
#[test]
fn test_get_merchandise_list_server_error_by_shop_id() {
let mock = mock("GET", "/v1/shops/1/merchandise_list")
.with_status(500)
.with_body("Internal Server Error")
.create();
let api_url = CString::new("url").unwrap().into_raw();
let api_key = CString::new("api-key").unwrap().into_raw();
let result = get_merchandise_list_by_shop_id(api_url, api_key, 1);
mock.assert();
match result {
FFIResult::Ok(raw_merchandise_vec) => panic!(
"get_merchandise_list_by_shop_id returned Ok result: {:#x?}",
raw_merchandise_vec
),
FFIResult::Err(error) => {
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"EOF while parsing a value at line 1 column 0" // empty tempfile
);
}
}
}
}