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 { send_message: Callback, fetch_service: RefCell, } impl APIService<()> { pub fn new_no_message() -> Self { Self::new(Callback::from(|()| ())) } } impl APIService { pub fn new(send_message: Callback) -> Self { Self { send_message, fetch_service: RefCell::new(FetchService::new()), } } pub fn fetch(&self, req: Request, callback: F) -> Option where T: for<'de> Deserialize<'de>, F: FnOnce(Result) -> Message + 'static, IN: Into, { 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| { // 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::>::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::>::from(decode_response)))) } pub fn get(&self, url: &str, callback: F) -> Option where T: for<'de> Deserialize<'de>, F: FnOnce(Result) -> Message + 'static, { self.fetch(Request::get(url).body(Nothing).unwrap(), callback) } pub fn api_get(&self, config: &Config, path: fmt::Arguments<'_>, callback: F) -> Option where T: for<'de> Deserialize<'de>, F: FnOnce(Result) -> Message + 'static, { let url = format!("{}{}", config.base_url, path); let req = Request::get(url).body(Nothing).unwrap(); self.fetch(req, callback) } } impl fmt::Debug for APIService { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("APIService") } }