Write metadata to file cache

Then, before a GET, read the metadata and add any etag to the headers and handle 304 in response.
This commit is contained in:
Tyler Hallada 2020-11-05 19:19:17 -05:00
parent dcc3590ac3
commit 127a68687e
8 changed files with 299 additions and 101 deletions

34
Cargo.lock generated
View File

@ -109,6 +109,7 @@ dependencies = [
"base64 0.13.0",
"bytes",
"cbindgen",
"chrono",
"dirs",
"http-api-problem",
"log",
@ -183,6 +184,20 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"serde",
"time",
"winapi 0.3.9",
]
[[package]]
name = "clap"
version = "2.33.1"
@ -742,6 +757,25 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.0"

View File

@ -13,6 +13,7 @@ cbindgen = "0.14.4"
anyhow = "1.0"
base64 = "0.13"
bytes = "0.5"
chrono = { version = "0.4", features = ["serde"] }
http-api-problem = "0.17"
mockito = "0.26.0"
reqwest = { version = "0.10", features = ["blocking", "json", "gzip"] }

View File

@ -3,7 +3,9 @@ use std::{fs::create_dir_all, fs::File, io::BufReader, io::Write, path::Path, pa
use anyhow::{Context, Result};
use base64::{encode_config, URL_SAFE_NO_PAD};
use bytes::Bytes;
use serde::Deserialize;
use chrono::{DateTime, Utc};
use reqwest::{blocking::Response, header::HeaderMap};
use serde::{Deserialize, Serialize};
#[cfg(test)]
use tempfile::tempfile;
@ -14,6 +16,12 @@ use std::println as info;
use super::API_VERSION;
#[derive(Debug, Serialize, Deserialize)]
pub struct Metadata {
pub etag: Option<String>,
pub date: Option<DateTime<Utc>>,
}
pub fn file_cache_dir(api_url: &str) -> Result<PathBuf> {
let encoded_url = encode_config(api_url, URL_SAFE_NO_PAD);
let path = Path::new("Data/SKSE/Plugins/BazaarRealmCache")
@ -34,6 +42,34 @@ pub fn update_file_cache(cache_path: &Path, bytes: &Bytes) -> Result<()> {
Ok(())
}
pub fn update_metadata_file_cache(cache_path: &Path, headers: &HeaderMap) -> Result<()> {
#[cfg(not(test))]
let mut file = File::create(cache_path)?;
#[cfg(test)]
let mut file = tempfile()?;
let etag = headers
.get("etag")
.map(|val| val.to_str().unwrap_or("").to_string());
let date = headers
.get("date")
.map(|val| val.to_str().unwrap_or("").parse().unwrap_or(Utc::now()));
let metadata = Metadata { etag, date };
serde_json::to_writer(file, &metadata)?;
Ok(())
}
pub fn update_file_caches(
body_cache_path: &Path,
metadata_cache_path: &Path,
response: Response,
) -> Result<Bytes> {
update_metadata_file_cache(metadata_cache_path, &response.headers())?;
let bytes = response.bytes()?;
update_file_cache(body_cache_path, &bytes)?;
Ok(bytes)
}
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!(
@ -47,3 +83,18 @@ pub fn from_file_cache<T: for<'de> Deserialize<'de>>(cache_path: &Path) -> Resul
info!("returning value from cache: {:?}", cache_path);
Ok(serde_json::from_reader(reader)?)
}
pub fn load_metadata_from_file_cache(cache_path: &Path) -> Result<Metadata> {
#[cfg(not(test))]
let file = File::open(cache_path).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)?;
Ok(metadata)
}

View File

@ -1,7 +1,7 @@
use std::{ffi::CStr, ffi::CString, os::raw::c_char, slice};
use anyhow::Result;
use reqwest::Url;
use reqwest::{StatusCode, Url};
use serde::{Deserialize, Serialize};
#[cfg(not(test))]
@ -10,8 +10,9 @@ use log::{error, info};
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,
cache::file_cache_dir, cache::from_file_cache, cache::load_metadata_from_file_cache,
cache::update_file_cache, cache::update_file_caches, cache::update_metadata_file_cache,
log_server_error, result::FFIResult,
};
#[derive(Serialize, Deserialize, Debug)]
@ -94,6 +95,7 @@ pub struct RawInteriorRefVec {
pub cap: usize,
}
// TODO: delete me if unused
#[no_mangle]
pub extern "C" fn create_interior_ref_list(
api_url: *const c_char,
@ -133,13 +135,20 @@ pub extern "C" fn create_interior_ref_list(
.json(&interior_ref_list)
.send()?;
info!("create interior_ref_list response from api: {:?}", &resp);
let cache_dir = file_cache_dir(api_url)?;
let headers = resp.headers().clone();
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!("interior_ref_list_{}.json", id)),
&cache_dir.join(format!("interior_ref_list_{}.json", id)),
&bytes,
)?;
update_metadata_file_cache(
&cache_dir.join(format!("interior_ref_list_{}_metadata.json", id)),
&headers,
)?;
}
Ok(json)
}
@ -209,14 +218,13 @@ pub extern "C" fn update_interior_ref_list(
.json(&interior_ref_list)
.send()?;
info!("update interior_ref_list response from api: {:?}", &resp);
let bytes = resp.bytes()?;
let cache_dir = file_cache_dir(api_url)?;
let body_cache_path = cache_dir.join(format!("shops_{}_interior_ref_list.json", shop_id));
let metadata_cache_path =
cache_dir.join(format!("shops_{}_interior_ref_list_metadata.json", shop_id));
let bytes = update_file_caches(&body_cache_path, &metadata_cache_path, resp)?;
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)
}
@ -245,6 +253,7 @@ pub extern "C" fn update_interior_ref_list(
}
}
// TODO: delete me if unused
#[no_mangle]
pub extern "C" fn get_interior_ref_list(
api_url: *const c_char,
@ -268,25 +277,38 @@ pub extern "C" fn get_interior_ref_list(
info!("api_url: {:?}", url);
let client = reqwest::blocking::Client::new();
let cache_path = file_cache_dir(api_url)?
.join(format!("interior_ref_list_{}.json", interior_ref_list_id));
let cache_dir = file_cache_dir(api_url)?;
let body_cache_path =
cache_dir.join(format!("interior_ref_list_{}.json", interior_ref_list_id));
let metadata_cache_path = cache_dir.join(format!(
"interior_ref_list_{}_metadata.json",
interior_ref_list_id
));
let mut request = client.get(url).header("Api-Key", api_key);
// TODO: load metadata from in-memory LRU cache first before trying to load from file
if let Ok(metadata) = load_metadata_from_file_cache(&metadata_cache_path) {
if let Some(etag) = metadata.etag {
request = request.header("If-None-Match", etag);
}
}
match client.get(url).header("Api-Key", api_key).send() {
match request.send() {
Ok(resp) => {
info!("get_interior_ref_list response from api: {:?}", &resp);
if resp.status().is_success() {
let bytes = resp.bytes()?;
update_file_cache(&cache_path, &bytes)?;
let bytes = update_file_caches(&body_cache_path, &metadata_cache_path, resp)?;
let json = serde_json::from_slice(&bytes)?;
Ok(json)
} else if resp.status() == StatusCode::NOT_MODIFIED {
from_file_cache(&body_cache_path)
} else {
log_server_error(resp);
from_file_cache(&cache_path)
from_file_cache(&body_cache_path)
}
}
Err(err) => {
error!("get_interior_ref_list api request error: {}", err);
from_file_cache(&cache_path)
from_file_cache(&body_cache_path)
}
}
}
@ -355,23 +377,33 @@ pub extern "C" fn get_interior_ref_list_by_shop_id(
info!("api_url: {:?}", url);
let client = reqwest::blocking::Client::new();
let cache_path =
file_cache_dir(api_url)?.join(format!("shops_{}_interior_ref_list.json", shop_id));
let cache_dir = file_cache_dir(api_url)?;
let body_cache_path = cache_dir.join(format!("shops_{}_interior_ref_list.json", shop_id));
let metadata_cache_path =
cache_dir.join(format!("shops_{}_interior_ref_list_metadata.json", shop_id));
let mut request = client.get(url).header("Api-Key", api_key);
// TODO: load metadata from in-memory LRU cache first before trying to load from file
if let Ok(metadata) = load_metadata_from_file_cache(&metadata_cache_path) {
if let Some(etag) = metadata.etag {
request = request.header("If-None-Match", etag);
}
}
match client.get(url).header("Api-Key", api_key).send() {
match request.send() {
Ok(resp) => {
info!(
"get_interior_ref_list_by_shop_id response from api: {:?}",
&resp
);
if resp.status().is_success() {
let bytes = resp.bytes()?;
update_file_cache(&cache_path, &bytes)?;
let bytes = update_file_caches(&body_cache_path, &metadata_cache_path, resp)?;
let json = serde_json::from_slice(&bytes)?;
Ok(json)
} else if resp.status() == StatusCode::NOT_MODIFIED {
from_file_cache(&body_cache_path)
} else {
log_server_error(resp);
from_file_cache(&cache_path)
from_file_cache(&body_cache_path)
}
}
Err(err) => {
@ -379,7 +411,7 @@ pub extern "C" fn get_interior_ref_list_by_shop_id(
"get_interior_ref_list_by_shop_id api request error: {}",
err
);
from_file_cache(&cache_path)
from_file_cache(&body_cache_path)
}
}
}

View File

@ -1,7 +1,7 @@
use std::{ffi::CStr, ffi::CString, os::raw::c_char, slice};
use anyhow::Result;
use reqwest::Url;
use reqwest::{StatusCode, Url};
use serde::{Deserialize, Serialize};
#[cfg(not(test))]
@ -10,18 +10,19 @@ use log::{error, info};
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,
cache::file_cache_dir, cache::from_file_cache, cache::load_metadata_from_file_cache,
cache::update_file_cache, cache::update_file_caches, cache::update_metadata_file_cache,
log_server_error, result::FFIResult,
};
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MerchandiseList {
pub id: Option<i32>,
pub shop_id: i32,
pub form_list: Vec<Merchandise>,
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Merchandise {
pub mod_name: String,
pub local_form_id: u32,
@ -77,6 +78,7 @@ pub struct RawMerchandiseVec {
pub cap: usize,
}
// TODO: delete me if unused
#[no_mangle]
pub extern "C" fn create_merchandise_list(
api_url: *const c_char,
@ -116,13 +118,20 @@ pub extern "C" fn create_merchandise_list(
.json(&merchandise_list)
.send()?;
info!("create merchandise_list response from api: {:?}", &resp);
let cache_dir = file_cache_dir(api_url)?;
let headers = resp.headers().clone();
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!("merchandise_list_{}.json", id)),
&cache_dir.join(format!("merchandise_list_{}.json", id)),
&bytes,
)?;
update_metadata_file_cache(
&cache_dir.join(format!("merchandise_list_{}_metadata.json", id)),
&headers,
)?;
}
Ok(json)
}
@ -194,14 +203,13 @@ pub extern "C" fn update_merchandise_list(
.json(&merchandise_list)
.send()?;
info!("update merchandise_list response from api: {:?}", &resp);
let bytes = resp.bytes()?;
let cache_dir = file_cache_dir(api_url)?;
let body_cache_path = cache_dir.join(format!("shops_{}_merchandise_list.json", shop_id));
let metadata_cache_path =
cache_dir.join(format!("shops_{}_merchandise_list_metadata.json", shop_id));
let bytes = update_file_caches(&body_cache_path, &metadata_cache_path, resp)?;
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)
}
@ -232,6 +240,7 @@ pub extern "C" fn update_merchandise_list(
}
}
// TODO: delete me if unused
#[no_mangle]
pub extern "C" fn get_merchandise_list(
api_url: *const c_char,
@ -256,25 +265,38 @@ pub extern "C" fn get_merchandise_list(
info!("api_url: {:?}", url);
let client = reqwest::blocking::Client::new();
let cache_path =
file_cache_dir(api_url)?.join(format!("merchandise_list_{}.json", merchandise_list_id));
let cache_dir = file_cache_dir(api_url)?;
let body_cache_path =
cache_dir.join(format!("merchandise_list_{}.json", merchandise_list_id));
let metadata_cache_path = cache_dir.join(format!(
"merchandise_list_{}_metadata.json",
merchandise_list_id
));
let mut request = client.get(url).header("Api-Key", api_key);
// TODO: load metadata from in-memory LRU cache first before trying to load from file
if let Ok(metadata) = load_metadata_from_file_cache(&metadata_cache_path) {
if let Some(etag) = metadata.etag {
request = request.header("If-None-Match", etag);
}
}
match client.get(url).header("Api-Key", api_key).send() {
match request.send() {
Ok(resp) => {
info!("get_merchandise_list response from api: {:?}", &resp);
if resp.status().is_success() {
let bytes = resp.bytes()?;
update_file_cache(&cache_path, &bytes)?;
let bytes = update_file_caches(&body_cache_path, &metadata_cache_path, resp)?;
let json = serde_json::from_slice(&bytes)?;
Ok(json)
} else if resp.status() == StatusCode::NOT_MODIFIED {
from_file_cache(&body_cache_path)
} else {
log_server_error(resp);
from_file_cache(&cache_path)
from_file_cache(&body_cache_path)
}
}
Err(err) => {
error!("get_merchandise_list api request error: {}", err);
from_file_cache(&cache_path)
from_file_cache(&body_cache_path)
}
}
}
@ -336,28 +358,38 @@ pub extern "C" fn get_merchandise_list_by_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));
let cache_dir = file_cache_dir(api_url)?;
let body_cache_path = cache_dir.join(format!("shops_{}_merchandise_list.json", shop_id));
let metadata_cache_path =
cache_dir.join(format!("shops_{}_merchandise_list_metadata.json", shop_id));
let mut request = client.get(url).header("Api-Key", api_key);
// TODO: load metadata from in-memory LRU cache first before trying to load from file
if let Ok(metadata) = load_metadata_from_file_cache(&metadata_cache_path) {
if let Some(etag) = metadata.etag {
request = request.header("If-None-Match", etag);
}
}
match client.get(url).header("Api-Key", api_key).send() {
match request.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 bytes = update_file_caches(&body_cache_path, &metadata_cache_path, resp)?;
let json = serde_json::from_slice(&bytes)?;
Ok(json)
} else if resp.status() == StatusCode::NOT_MODIFIED {
from_file_cache(&body_cache_path)
} else {
log_server_error(resp);
from_file_cache(&cache_path)
from_file_cache(&body_cache_path)
}
}
Err(err) => {
error!("get_merchandise_list_by_shop_id api request error: {}", err);
from_file_cache(&cache_path)
from_file_cache(&body_cache_path)
}
}
}

View File

@ -9,7 +9,10 @@ use log::{error, info};
#[cfg(test)]
use std::{println as info, println as error};
use crate::{cache::file_cache_dir, cache::update_file_cache, result::FFIResult};
use crate::{
cache::file_cache_dir, cache::update_file_cache, cache::update_file_caches,
cache::update_metadata_file_cache, result::FFIResult,
};
#[derive(Serialize, Deserialize, Debug)]
pub struct Owner {
@ -69,12 +72,16 @@ pub extern "C" fn create_owner(
.json(&owner)
.send()?;
info!("create owner response from api: {:?}", &resp);
let cache_dir = file_cache_dir(api_url)?;
let headers = resp.headers().clone();
let bytes = resp.bytes()?;
let json: Owner = serde_json::from_slice(&bytes)?;
if let Some(id) = json.id {
update_file_cache(
&file_cache_dir(api_url)?.join(format!("owner_{}.json", id)),
&bytes,
update_file_cache(&cache_dir.join(format!("owner_{}.json", id)), &bytes)?;
update_metadata_file_cache(
&cache_dir.join(format!("owner_{}_metadata.json", id)),
&headers,
)?;
}
Ok(json)
@ -144,14 +151,12 @@ pub extern "C" fn update_owner(
.json(&owner)
.send()?;
info!("update owner response from api: {:?}", &resp);
let bytes = resp.bytes()?;
let cache_dir = file_cache_dir(api_url)?;
let body_cache_path = cache_dir.join(format!("owner_{}.json", id));
let metadata_cache_path = cache_dir.join(format!("owner_{}_metadata.json", id));
let bytes = update_file_caches(&body_cache_path, &metadata_cache_path, resp)?;
let json: Owner = serde_json::from_slice(&bytes)?;
if let Some(id) = json.id {
update_file_cache(
&file_cache_dir(api_url)?.join(format!("owner_{}.json", id)),
&bytes,
)?;
}
Ok(json)
} else {
Err(anyhow!("api-key not defined"))

View File

@ -1,7 +1,7 @@
use std::{convert::TryFrom, ffi::CStr, ffi::CString, os::raw::c_char};
use anyhow::{anyhow, Result};
use reqwest::Url;
use reqwest::{StatusCode, Url};
use serde::{Deserialize, Serialize};
#[cfg(not(test))]
@ -10,8 +10,9 @@ use log::{error, info};
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,
cache::file_cache_dir, cache::from_file_cache, cache::load_metadata_from_file_cache,
cache::update_file_cache, cache::update_file_caches, cache::update_metadata_file_cache,
log_server_error, result::FFIResult,
};
#[derive(Serialize, Deserialize, Debug)]
@ -110,12 +111,16 @@ pub extern "C" fn create_shop(
.json(&shop)
.send()?;
info!("create shop response from api: {:?}", &resp);
let cache_dir = file_cache_dir(api_url)?;
let headers = resp.headers().clone();
let bytes = resp.bytes()?;
let json: Shop = serde_json::from_slice(&bytes)?;
if let Some(id) = json.id {
update_file_cache(
&file_cache_dir(api_url)?.join(format!("shop_{}.json", id)),
&bytes,
update_file_cache(&cache_dir.join(format!("shop_{}.json", id)), &bytes)?;
update_metadata_file_cache(
&cache_dir.join(format!("shop_{}_metadata.json", id)),
&headers,
)?;
}
Ok(json)
@ -179,14 +184,12 @@ pub extern "C" fn update_shop(
.json(&shop)
.send()?;
info!("update shop response from api: {:?}", &resp);
let bytes = resp.bytes()?;
let cache_dir = file_cache_dir(api_url)?;
let body_cache_path = cache_dir.join(format!("shop_{}.json", id));
let metadata_cache_path = cache_dir.join(format!("shop_{}_metadata.json", id));
let bytes = update_file_caches(&body_cache_path, &metadata_cache_path, resp)?;
let json: Shop = serde_json::from_slice(&bytes)?;
if let Some(id) = json.id {
update_file_cache(
&file_cache_dir(api_url)?.join(format!("shop_{}.json", id)),
&bytes,
)?;
}
Ok(json)
}
@ -237,24 +240,34 @@ pub extern "C" fn get_shop(
info!("api_url: {:?}", url);
let client = reqwest::blocking::Client::new();
let cache_path = file_cache_dir(api_url)?.join(format!("shop_{}.json", shop_id));
let cache_dir = file_cache_dir(api_url)?;
let body_cache_path = cache_dir.join(format!("shop_{}.json", shop_id));
let metadata_cache_path = cache_dir.join(format!("shop_{}_metadata.json", shop_id));
let mut request = client.get(url).header("Api-Key", api_key);
// TODO: load metadata from in-memory LRU cache first before trying to load from file
if let Ok(metadata) = load_metadata_from_file_cache(&metadata_cache_path) {
if let Some(etag) = metadata.etag {
request = request.header("If-None-Match", etag);
}
}
match client.get(url).header("Api-Key", api_key).send() {
match request.send() {
Ok(resp) => {
info!("get_shop response from api: {:?}", &resp);
if resp.status().is_success() {
let bytes = resp.bytes()?;
update_file_cache(&cache_path, &bytes)?;
let bytes = update_file_caches(&body_cache_path, &metadata_cache_path, resp)?;
let json = serde_json::from_slice(&bytes)?;
Ok(json)
} else if resp.status() == StatusCode::NOT_MODIFIED {
from_file_cache(&body_cache_path)
} else {
log_server_error(resp);
from_file_cache(&cache_path)
from_file_cache(&body_cache_path)
}
}
Err(err) => {
error!("get_shop api request error: {}", err);
from_file_cache(&cache_path)
from_file_cache(&body_cache_path)
}
}
}
@ -301,24 +314,34 @@ pub extern "C" fn list_shops(
info!("api_url: {:?}", url);
let client = reqwest::blocking::Client::new();
let cache_path = file_cache_dir(api_url)?.join("shops.json");
let cache_dir = file_cache_dir(api_url)?;
let body_cache_path = cache_dir.join("shops.json");
let metadata_cache_path = cache_dir.join("shops_metadata.json");
let mut request = client.get(url).header("Api-Key", api_key);
// TODO: load metadata from in-memory LRU cache first before trying to load from file
if let Ok(metadata) = load_metadata_from_file_cache(&metadata_cache_path) {
if let Some(etag) = metadata.etag {
request = request.header("If-None-Match", etag);
}
}
match client.get(url).header("Api-Key", api_key).send() {
match request.send() {
Ok(resp) => {
info!("list_shops response from api: {:?}", &resp);
if resp.status().is_success() {
let bytes = resp.bytes()?;
update_file_cache(&cache_path, &bytes)?;
let bytes = update_file_caches(&body_cache_path, &metadata_cache_path, resp)?;
let json = serde_json::from_slice(&bytes)?;
Ok(json)
} else if resp.status() == StatusCode::NOT_MODIFIED {
from_file_cache(&body_cache_path)
} else {
log_server_error(resp);
from_file_cache(&cache_path)
from_file_cache(&body_cache_path)
}
}
Err(err) => {
error!("list_shops api request error: {}", err);
from_file_cache(&cache_path)
from_file_cache(&body_cache_path)
}
}
}

View File

@ -11,8 +11,8 @@ use log::{error, info};
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,
cache::file_cache_dir, cache::from_file_cache, cache::update_file_cache,
cache::update_metadata_file_cache, log_server_error, result::FFIResult,
};
#[derive(Serialize, Deserialize, Debug)]
@ -157,31 +157,43 @@ pub extern "C" fn create_transaction(
.json(&transaction)
.send()?;
info!("create transaction response from api: {:?}", &resp);
let cache_dir = file_cache_dir(api_url)?;
let headers = resp.headers().clone();
let status = resp.status();
let bytes = resp.bytes()?;
if status.is_success() {
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,
update_file_cache(&cache_dir.join(format!("transaction_{}.json", id)), &bytes)?;
update_metadata_file_cache(
&cache_dir.join(format!("transaction_{}_metadata.json", id)),
&headers,
)?;
}
Ok(json)
} else {
// TODO: abstract this away into a separate helper
match serde_json::from_slice::<HttpApiProblem>(&bytes) {
Ok(api_problem) => {
let detail = api_problem.detail.unwrap_or("".to_string());
error!("Server {} error: {}. {}", status, api_problem.title, detail);
error!(
"Server {}: {}. {}",
status.as_u16(),
api_problem.title,
detail
);
Err(anyhow!(format!(
"Server {} error: {}. {}",
status, api_problem.title, detail
"Server {}: {}. {}",
status.as_u16(),
api_problem.title,
detail
)))
}
Err(_) => {
let detail = str::from_utf8(&bytes).unwrap_or("unknown");
error!("Server {} error: {}", status, detail);
Err(anyhow!(format!("Server {} error: {}", status, detail)))
error!("Server {}: {}", status.as_u16(), detail);
Err(anyhow!(format!("Server {}: {}", status.as_u16(), detail)))
}
}
}
@ -293,7 +305,15 @@ mod tests {
fn test_create_transaction_server_error() {
let mock = mock("POST", "/v1/transactions")
.with_status(500)
.with_body("Internal Server Error")
.with_header("content-type", "application/problem+json")
.with_body(
r#"{
"detail": "Some error detail",
"instance": "https://httpstatuses.com/500",
"status": 500,
"title": "Internal Server Error"
}"#,
)
.create();
let api_url = CString::new("url").unwrap().into_raw();
@ -323,7 +343,7 @@ mod tests {
FFIResult::Err(error) => {
assert_eq!(
unsafe { CStr::from_ptr(error).to_string_lossy() },
"expected value at line 1 column 1"
"Server 500: Internal Server Error. Some error detail"
);
}
}