rust-galmon-web/src/api/apiservice.rs

112 lines
3.2 KiB
Rust

use failure::{format_err, Error, ResultExt};
use serde::Deserialize;
use std::cell::{Cell, RefCell};
use std::fmt;
use yew::prelude::*;
use yew::{
format::{Json, Nothing, Text},
services::fetch::{Request, Response, FetchService, FetchTask as YewFetchTask},
};
use crate::config::Config;
pub struct FetchTask {
_task: YewFetchTask,
}
impl FetchTask {
fn new(task: YewFetchTask) -> Self {
Self { _task: task }
}
}
impl fmt::Debug for FetchTask {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("FetchTask")
}
}
pub struct APIService<Message: 'static> {
send_message: Callback<Message>,
fetch_service: RefCell<FetchService>,
}
impl APIService<()> {
pub fn new_no_message() -> Self {
Self::new(Callback::from(|()| ()))
}
}
impl<Message: 'static> APIService<Message> {
pub fn new(send_message: Callback<Message>) -> Self
{
Self {
send_message,
fetch_service: RefCell::new(FetchService::new()),
}
}
pub fn fetch<T, F, IN>(&self, req: Request<IN>, callback: F) -> Option<FetchTask>
where
T: for<'de> Deserialize<'de>,
F: FnOnce(Result<T, Error>) -> Message + 'static,
IN: Into<Text>,
{
let method = req.method().clone();
let url = req.uri().clone();
let callback = Cell::new(Some(callback));
let send_message = self.send_message.clone();
let decode_response = move |response: Response<Text>| {
// only works once
let callback = callback.replace(None).unwrap();
let (parts, body) = response.into_parts();
if !parts.status.is_success() {
if parts.headers.is_empty() {
// CORS failure
jslog!("{} {:?} failed due to CORS, can't see real error (status: {})", method, url, parts.status);
return send_message.emit(callback(Err(format_err!("{} {:?} failed due to CORS", method, url))));
}
let e = format!("{} {:?} failed with {}", method, url, parts.status);
crate::log(&e);
return send_message.emit(callback(Err(failure::err_msg(e))));
}
let body = match body {
Err(e) => {
let e = format!("{} {:?} failed request body although status {} is fine: {}", method, url, parts.status, e);
crate::log(&e);
return send_message.emit(callback(Err(failure::err_msg(e))));
},
Ok(v) => v
};
send_message.emit(callback(Json::<Result<T, Error>>::from(Ok(body)).0.with_context(|e| format!("parsing response failed: {}", e)).map_err(Error::from)));
};
let mut service = self.fetch_service.borrow_mut();
Some(FetchTask::new(service.fetch(req, Callback::<Response<Text>>::from(decode_response))))
}
pub fn get<T, F>(&self, url: &str, callback: F) -> Option<FetchTask>
where
T: for<'de> Deserialize<'de>,
F: FnOnce(Result<T, Error>) -> Message + 'static,
{
self.fetch(Request::get(url).body(Nothing).unwrap(), callback)
}
pub fn api_get<T, F>(&self, config: &Config, path: fmt::Arguments<'_>, callback: F) -> Option<FetchTask>
where
T: for<'de> Deserialize<'de>,
F: FnOnce(Result<T, Error>) -> Message + 'static,
{
let url = format!("{}{}", config.base_url, path);
let req = Request::get(url).body(Nothing).unwrap();
self.fetch(req, callback)
}
}
impl<Message> fmt::Debug for APIService<Message> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("APIService")
}
}