112 lines
3.2 KiB
Rust
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")
|
||
|
}
|
||
|
}
|