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:
parent
dcc3590ac3
commit
127a68687e
34
Cargo.lock
generated
34
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"] }
|
||||
|
53
src/cache.rs
53
src/cache.rs
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
27
src/owner.rs
27
src/owner.rs
@ -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"))
|
||||
|
73
src/shop.rs
73
src/shop.rs
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user