deft 0.15.0

Cross platform ui framework
use crate as deft;
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc;

use crate::js::JsPo;
use crate::{js_serialize, js_value};
use anyhow::{anyhow, Error};
use deft_macros::js_methods;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use reqwest::{Method, Response};
use serde::{Deserialize, Serialize};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use tokio::sync::Mutex;

#[derive(Clone)]
pub struct FetchResponse {
    response: Arc<Mutex<Response>>,
}

js_value!(FetchResponse);

#[derive(Serialize, Deserialize)]
pub struct Header {
    pub name: String,
    pub value: String,
}

#[derive(Serialize, Deserialize)]
pub struct FetchOptions {
    pub method: Option<String>,
    pub headers: Option<HashMap<String, String>>,
    pub body: Option<String>,
    pub proxy: Option<String>,
}

#[allow(nonstandard_style)]
pub struct fetch;

js_serialize!(Header);

#[js_methods]
impl fetch {
    #[js_func]
    pub async fn create(
        url: String,
        options: Option<JsPo<FetchOptions>>,
    ) -> Result<FetchResponse, Error> {
        let mut client_builder = reqwest::Client::builder();
        let mut method = Method::GET;
        let mut headers = HeaderMap::new();
        let mut body = None;
        if let Some(options) = &options {
            if let Some(proxy) = &options.proxy {
                if proxy.is_empty() {
                    client_builder = client_builder.no_proxy();
                } else {
                    client_builder = client_builder.proxy(reqwest::Proxy::all(proxy)?);
                }
            }
            if let Some(m) = &options.method {
                method = match m.to_lowercase().as_str() {
                    "get" => Method::GET,
                    "post" => Method::POST,
                    "put" => Method::PUT,
                    "delete" => Method::DELETE,
                    "head" => Method::HEAD,
                    "options" => Method::OPTIONS,
                    m => return Err(anyhow!("invalid method: {}", m)),
                };
            }
            if let Some(hds) = &options.headers {
                for (k, v) in hds {
                    headers.insert(HeaderName::from_str(k)?, HeaderValue::from_str(v)?);
                }
            }
            body = options.body.clone();
        }
        let client = client_builder.build()?;
        let mut req_builder = client.request(method, url).headers(headers);
        if let Some(body) = body {
            req_builder = req_builder.body(body);
        }
        let rsp = req_builder.send().await?;
        Ok(FetchResponse {
            response: Arc::new(Mutex::new(rsp)),
        })
    }

    #[js_func]
    pub async fn response_status(response: FetchResponse) -> Result<u16, Error> {
        let rsp = response.response.lock().await;
        Ok(rsp.status().as_u16())
    }

    #[js_func]
    pub async fn response_headers(response: FetchResponse) -> Result<Vec<Header>, Error> {
        let rsp = response.response.lock().await;
        let mut headers = Vec::new();
        rsp.headers().iter().for_each(|(k, v)| {
            if let Ok(v) = v.to_str() {
                let hd = Header {
                    name: k.to_string(),
                    value: v.to_string(),
                };
                headers.push(hd);
            }
        });
        Ok(headers)
    }

    #[js_func]
    pub async fn response_body_string(response: FetchResponse) -> Result<String, Error> {
        let mut rsp = response.response.lock().await;
        let mut result = Vec::new();
        while let Some(c) = rsp.chunk().await? {
            let mut data = c.to_vec();
            result.append(&mut data);
        }
        Ok(String::from_utf8(result)?)
    }

    #[js_func]
    pub async fn response_save(response: FetchResponse, path: String) -> Result<usize, Error> {
        let mut file = File::create_new(path).await?;
        let response = response.clone();
        let mut rsp = response.response.lock().await;
        let mut size = 0;
        while let Some(c) = rsp.chunk().await? {
            let data = c.to_vec();
            file.write_all(&data).await?;
            size += data.len();
        }
        Ok(size)
    }
}