Add create_transaction function
This commit is contained in:
parent
1805bbb4dc
commit
7847b19c3e
21
bindings.h
21
bindings.h
@ -79,7 +79,7 @@ struct RawMerchandise {
|
||||
const char *name;
|
||||
uint32_t quantity;
|
||||
uint32_t form_type;
|
||||
uint8_t is_food;
|
||||
bool is_food;
|
||||
uint32_t price;
|
||||
};
|
||||
|
||||
@ -95,6 +95,16 @@ struct RawShop {
|
||||
const char *description;
|
||||
};
|
||||
|
||||
struct RawTransaction {
|
||||
uint32_t id;
|
||||
uint32_t shop_id;
|
||||
const char *mod_name;
|
||||
uint32_t local_form_id;
|
||||
bool is_sell;
|
||||
uint32_t quantity;
|
||||
uint32_t amount;
|
||||
};
|
||||
|
||||
struct RawInteriorRefVec {
|
||||
RawInteriorRef *ptr;
|
||||
uintptr_t len;
|
||||
@ -153,6 +163,15 @@ FFIResult<RawShop> create_shop(const char *api_url,
|
||||
const char *name,
|
||||
const char *description);
|
||||
|
||||
FFIResult<RawTransaction> create_transaction(const char *api_url,
|
||||
const char *api_key,
|
||||
uint32_t shop_id,
|
||||
const char *mod_name,
|
||||
uint32_t local_form_id,
|
||||
bool is_sell,
|
||||
uint32_t quantity,
|
||||
uint32_t amount);
|
||||
|
||||
void free_string(char *ptr);
|
||||
|
||||
char *generate_api_key();
|
||||
|
@ -211,7 +211,7 @@ pub extern "C" fn update_interior_ref_list(
|
||||
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 {
|
||||
if let Some(_id) = json.id {
|
||||
update_file_cache(
|
||||
&file_cache_dir(api_url)?.join(format!("shops_{}_interior_ref_list.json", shop_id)),
|
||||
&bytes,
|
||||
|
@ -19,6 +19,7 @@ mod merchandise_list;
|
||||
mod owner;
|
||||
mod result;
|
||||
mod shop;
|
||||
mod transaction;
|
||||
|
||||
pub const API_VERSION: &'static str = "v1";
|
||||
|
||||
|
@ -49,7 +49,7 @@ impl MerchandiseList {
|
||||
.to_string(),
|
||||
quantity: rec.quantity,
|
||||
form_type: rec.form_type,
|
||||
is_food: rec.is_food == 1,
|
||||
is_food: rec.is_food,
|
||||
price: rec.price,
|
||||
})
|
||||
.collect(),
|
||||
@ -65,7 +65,7 @@ pub struct RawMerchandise {
|
||||
pub name: *const c_char,
|
||||
pub quantity: u32,
|
||||
pub form_type: u32,
|
||||
pub is_food: u8,
|
||||
pub is_food: bool,
|
||||
pub price: u32,
|
||||
}
|
||||
|
||||
@ -211,10 +211,10 @@ pub extern "C" fn update_merchandise_list(
|
||||
FFIResult::Ok(id)
|
||||
} else {
|
||||
error!(
|
||||
"update_merchandise failed. API did not return an interior ref list with an ID"
|
||||
"update_merchandise failed. API did not return a merchandise list with an ID"
|
||||
);
|
||||
let err_string =
|
||||
CString::new("API did not return an interior ref list with an ID".to_string())
|
||||
CString::new("API did not return a merchandise 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
|
||||
@ -294,7 +294,7 @@ pub extern "C" fn get_merchandise_list(
|
||||
.into_raw(),
|
||||
quantity: merchandise.quantity,
|
||||
form_type: merchandise.form_type,
|
||||
is_food: merchandise.is_food as u8,
|
||||
is_food: merchandise.is_food,
|
||||
price: merchandise.price,
|
||||
})
|
||||
.collect::<Vec<RawMerchandise>>()
|
||||
@ -377,7 +377,7 @@ pub extern "C" fn get_merchandise_list_by_shop_id(
|
||||
.into_raw(),
|
||||
quantity: merchandise.quantity,
|
||||
form_type: merchandise.form_type,
|
||||
is_food: merchandise.is_food as u8,
|
||||
is_food: merchandise.is_food,
|
||||
price: merchandise.price,
|
||||
})
|
||||
.collect::<Vec<RawMerchandise>>()
|
||||
@ -420,7 +420,7 @@ mod tests {
|
||||
name: CString::new("Iron Sword").unwrap().into_raw(),
|
||||
quantity: 1,
|
||||
form_type: 1,
|
||||
is_food: 0,
|
||||
is_food: false,
|
||||
price: 100,
|
||||
}]
|
||||
.into_raw_parts();
|
||||
@ -439,7 +439,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_interior_ref_list_server_error() {
|
||||
fn test_create_merchandise_list_server_error() {
|
||||
let mock = mock("POST", "/v1/merchandise_lists")
|
||||
.with_status(500)
|
||||
.with_body("Internal Server Error")
|
||||
@ -453,7 +453,7 @@ mod tests {
|
||||
name: CString::new("Iron Sword").unwrap().into_raw(),
|
||||
quantity: 1,
|
||||
form_type: 1,
|
||||
is_food: 0,
|
||||
is_food: false,
|
||||
price: 100,
|
||||
}]
|
||||
.into_raw_parts();
|
||||
@ -489,7 +489,7 @@ mod tests {
|
||||
name: CString::new("Iron Sword").unwrap().into_raw(),
|
||||
quantity: 1,
|
||||
form_type: 1,
|
||||
is_food: 0,
|
||||
is_food: false,
|
||||
price: 100,
|
||||
}]
|
||||
.into_raw_parts();
|
||||
@ -508,7 +508,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_interior_ref_list_server_error() {
|
||||
fn test_update_merchandise_list_server_error() {
|
||||
let mock = mock("PATCH", "/v1/shops/1/merchandise_list")
|
||||
.with_status(500)
|
||||
.with_body("Internal Server Error")
|
||||
@ -522,7 +522,7 @@ mod tests {
|
||||
name: CString::new("Iron Sword").unwrap().into_raw(),
|
||||
quantity: 1,
|
||||
form_type: 1,
|
||||
is_food: 0,
|
||||
is_food: false,
|
||||
price: 100,
|
||||
}]
|
||||
.into_raw_parts();
|
||||
@ -594,7 +594,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(raw_merchandise.quantity, 1);
|
||||
assert_eq!(raw_merchandise.form_type, 1);
|
||||
assert_eq!(raw_merchandise.is_food, 0);
|
||||
assert_eq!(raw_merchandise.is_food, false);
|
||||
assert_eq!(raw_merchandise.price, 100);
|
||||
}
|
||||
FFIResult::Err(error) => panic!("get_merchandise_list returned error: {:?}", unsafe {
|
||||
@ -681,7 +681,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(raw_merchandise.quantity, 1);
|
||||
assert_eq!(raw_merchandise.form_type, 1);
|
||||
assert_eq!(raw_merchandise.is_food, 0);
|
||||
assert_eq!(raw_merchandise.is_food, false);
|
||||
assert_eq!(raw_merchandise.price, 100);
|
||||
}
|
||||
FFIResult::Err(error) => panic!(
|
||||
|
254
src/transaction.rs
Normal file
254
src/transaction.rs
Normal file
@ -0,0 +1,254 @@
|
||||
use std::{convert::TryFrom, ffi::CStr, ffi::CString, os::raw::c_char, slice};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use reqwest::Url;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(not(test))]
|
||||
use log::{error, info};
|
||||
#[cfg(test)]
|
||||
use std::{println as info, println as error};
|
||||
|
||||
use crate::{
|
||||
cache::file_cache_dir, cache::from_file_cache, cache::update_file_cache, log_server_error,
|
||||
result::FFIResult,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Transaction {
|
||||
pub id: Option<u32>,
|
||||
pub shop_id: u32,
|
||||
pub mod_name: String,
|
||||
pub local_form_id: u32,
|
||||
pub is_sell: bool,
|
||||
pub quantity: u32,
|
||||
pub amount: u32,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub fn from_game(
|
||||
shop_id: u32,
|
||||
mod_name: &str,
|
||||
local_form_id: u32,
|
||||
is_sell: bool,
|
||||
quantity: u32,
|
||||
amount: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
shop_id,
|
||||
mod_name: mod_name.to_string(),
|
||||
local_form_id,
|
||||
is_sell,
|
||||
quantity,
|
||||
amount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RawTransaction> for Transaction {
|
||||
fn from(raw_transaction: RawTransaction) -> Self {
|
||||
Self {
|
||||
id: Some(raw_transaction.id),
|
||||
shop_id: raw_transaction.shop_id,
|
||||
mod_name: unsafe { CStr::from_ptr(raw_transaction.mod_name) }
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
local_form_id: raw_transaction.local_form_id,
|
||||
is_sell: raw_transaction.is_sell,
|
||||
quantity: raw_transaction.quantity,
|
||||
amount: raw_transaction.amount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct RawTransaction {
|
||||
pub id: u32,
|
||||
pub shop_id: u32,
|
||||
pub mod_name: *const c_char,
|
||||
pub local_form_id: u32,
|
||||
pub is_sell: bool,
|
||||
pub quantity: u32,
|
||||
pub amount: u32,
|
||||
}
|
||||
|
||||
impl TryFrom<Transaction> for RawTransaction {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(transaction: Transaction) -> Result<Self> {
|
||||
if let Some(id) = transaction.id {
|
||||
Ok(Self {
|
||||
id,
|
||||
shop_id: transaction.shop_id,
|
||||
mod_name: CString::new(transaction.mod_name)
|
||||
.unwrap_or_default()
|
||||
.into_raw(),
|
||||
local_form_id: transaction.local_form_id,
|
||||
is_sell: transaction.is_sell,
|
||||
quantity: transaction.quantity,
|
||||
amount: transaction.amount,
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!("transaction.id is None"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct RawTransactionVec {
|
||||
pub ptr: *mut RawTransaction,
|
||||
pub len: usize,
|
||||
pub cap: usize,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn create_transaction(
|
||||
api_url: *const c_char,
|
||||
api_key: *const c_char,
|
||||
shop_id: u32,
|
||||
mod_name: *const c_char,
|
||||
local_form_id: u32,
|
||||
is_sell: bool,
|
||||
quantity: u32,
|
||||
amount: u32,
|
||||
) -> FFIResult<RawTransaction> {
|
||||
let api_url = unsafe { CStr::from_ptr(api_url) }.to_string_lossy();
|
||||
let api_key = unsafe { CStr::from_ptr(api_key) }.to_string_lossy();
|
||||
let mod_name = unsafe { CStr::from_ptr(mod_name) }.to_string_lossy();
|
||||
let transaction =
|
||||
Transaction::from_game(shop_id, &mod_name, local_form_id, is_sell, quantity, amount);
|
||||
info!(
|
||||
"create_transaction api_url: {:?}, api_key: {:?}, transaction: {:?}",
|
||||
api_url, api_key, transaction
|
||||
);
|
||||
|
||||
fn inner(api_url: &str, api_key: &str, transaction: Transaction) -> Result<Transaction> {
|
||||
#[cfg(not(test))]
|
||||
let url = Url::parse(api_url)?.join("v1/transactions")?;
|
||||
#[cfg(test)]
|
||||
let url = Url::parse(&mockito::server_url())?.join("v1/transactions")?;
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let resp = client
|
||||
.post(url)
|
||||
.header("Api-Key", api_key)
|
||||
.json(&transaction)
|
||||
.send()?;
|
||||
info!("create transaction response from api: {:?}", &resp);
|
||||
let bytes = resp.bytes()?;
|
||||
let json: Transaction = serde_json::from_slice(&bytes)?;
|
||||
if let Some(id) = json.id {
|
||||
update_file_cache(
|
||||
&file_cache_dir(api_url)?.join(format!("transaction_{}.json", id)),
|
||||
&bytes,
|
||||
)?;
|
||||
}
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
match inner(&api_url, &api_key, transaction) {
|
||||
Ok(transaction) => {
|
||||
if let Ok(raw_transaction) = RawTransaction::try_from(transaction) {
|
||||
FFIResult::Ok(raw_transaction)
|
||||
} else {
|
||||
error!("create_transaction failed. API did not return a transaction with an ID");
|
||||
let err_string =
|
||||
CString::new("API did not return a transaction 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!("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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ffi::CString;
|
||||
|
||||
use super::*;
|
||||
use mockito::mock;
|
||||
|
||||
#[test]
|
||||
fn test_create_transaction() {
|
||||
let mock = mock("POST", "/v1/transactions")
|
||||
.with_status(201)
|
||||
.with_header("content-type", "application/json")
|
||||
.with_body(
|
||||
r#"{
|
||||
"amount": 100,
|
||||
"created_at": "2020-08-18T00:00:00.000",
|
||||
"id": 1,
|
||||
"is_sell": false,
|
||||
"local_form_id": 1,
|
||||
"mod_name": "Skyrim.esm",
|
||||
"owner_id": 1,
|
||||
"quantity": 1,
|
||||
"shop_id": 1,
|
||||
"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 mod_name = CString::new("Skyrim.esm").unwrap().into_raw();
|
||||
let result = create_transaction(api_url, api_key, 1, mod_name, 1, false, 1, 100);
|
||||
mock.assert();
|
||||
match result {
|
||||
FFIResult::Ok(raw_transaction) => {
|
||||
assert_eq!(raw_transaction.id, 1);
|
||||
assert_eq!(raw_transaction.shop_id, 1);
|
||||
assert_eq!(
|
||||
unsafe { CStr::from_ptr(raw_transaction.mod_name).to_string_lossy() },
|
||||
"Skyrim.esm"
|
||||
);
|
||||
assert_eq!(raw_transaction.local_form_id, 1);
|
||||
assert_eq!(raw_transaction.is_sell, false);
|
||||
assert_eq!(raw_transaction.quantity, 1);
|
||||
assert_eq!(raw_transaction.amount, 100);
|
||||
}
|
||||
FFIResult::Err(error) => panic!("create_transaction returned error: {:?}", unsafe {
|
||||
CStr::from_ptr(error).to_string_lossy()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_transaction_server_error() {
|
||||
let mock = mock("POST", "/v1/transactions")
|
||||
.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 mod_name = CString::new("Skyrim.esm").unwrap().into_raw();
|
||||
let result = create_transaction(api_url, api_key, 1, mod_name, 1, false, 1, 100);
|
||||
mock.assert();
|
||||
match result {
|
||||
FFIResult::Ok(raw_transaction) => panic!(
|
||||
"create_transaction returned Ok result: {:#?}",
|
||||
raw_transaction
|
||||
),
|
||||
FFIResult::Err(error) => {
|
||||
assert_eq!(
|
||||
unsafe { CStr::from_ptr(error).to_string_lossy() },
|
||||
"expected value at line 1 column 1"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user