vertigo-cli 0.2.0-alpha

Reactive Real-DOM library for Rust - packaging tool
Documentation
use std::{sync::Arc, collections::{HashMap, VecDeque}};
use reqwest::Response;
use serde_json::{Value, Number, Map};

use crate::serve::{
    wasm::{FetchRequest, FetchResponse},
    js_value::JsJson
};

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RequestBody {
    Text(String),
    Json(JsJson),
    Binary(Vec<u8>),
}

fn convert_to_jsjson(value: Value) -> JsJson {
    match value {
        Value::Bool(true) => JsJson::True,
        Value::Bool(false) => JsJson::False,
        Value::Null => JsJson::Null,
        Value::Number(value) => {
            if let Some(value) = value.as_f64() {
                return JsJson::Number(value);
            }

            if let Some(value) = value.as_i64() {
                return JsJson::Number(value as f64);
            }

            if let Some(value) = value.as_u64() {
                return JsJson::Number(value as f64);
            }

            unreachable!();
        }
        Value::String(value) => JsJson::String(value),
        Value::Array(list) => {
            JsJson::List(
                list
                    .into_iter()
                    .map(convert_to_jsjson)
                    .collect::<Vec<_>>()
            )
        },
        Value::Object(object) => {
            let mut result = HashMap::new();

            for (prop_name, prop_value) in object {
                result.insert(prop_name, convert_to_jsjson(prop_value));
            }

            JsJson::Object(result)
        }
    }
}

fn convert_to_jsvalue(value: JsJson) -> Value {
    match value {
        JsJson::True => Value::Bool(true),
        JsJson::False => Value::Bool(false),
        JsJson::Null => Value::Null,
        JsJson::Number(value) => {
            Value::Number(Number::from_f64(value).unwrap())
        },
        JsJson::String(value) => Value::String(value),
        JsJson::List(list) => {
            let list = list
                .into_iter()
                .map(convert_to_jsvalue)
                .collect::<Vec<_>>();
            Value::Array(list)
        }
        JsJson::Object(object) => {
            let mut map = Map::new();

            for (prop_name, prop_value) in object {
                map.insert(prop_name, convert_to_jsvalue(prop_value));
            }

            Value::Object(map)
        }
    }
}

pub async fn send_request(request_params: Arc<FetchRequest>) -> FetchResponse {
    match send_request_inner(request_params).await {
        Some((status, body)) => {
            FetchResponse {
                success: true,
                status,
                body,
            }
        },
        None => {
            FetchResponse {
                success: false,
                status: 0,
                body: RequestBody::Text(String::from("")),
            }
        }
    }
}

enum BodyToSend {
    None,
    String(String),
    Vec(Vec<u8>),
}

fn get_headers_and_body(mut headers: HashMap<String, String>, body: &Option<RequestBody>) -> (HashMap<String, String>, BodyToSend) {
    match body.clone() {
        None => {
            (headers, BodyToSend::None)
        }
        Some(RequestBody::Text(text)) => {
            if !headers.contains_key("content-type") {
                headers.insert("content-type".into(), "text/plain; charset=utf-8".into());
            }

            (headers, BodyToSend::String(text))
        }
        Some(RequestBody::Binary(buffer)) => {
            (headers, BodyToSend::Vec(buffer))
        }
        Some(RequestBody::Json(json)) => {
            if !headers.contains_key("content-type") {
                headers.insert("content-type".into(), "application/json; charset=utf-8".into());
            }

            let value = convert_to_jsvalue(json);
            let json_str = serde_json::to_string(&value).unwrap();

            (headers, BodyToSend::String(json_str))
        }
    }
}

fn clear_headers(headers: &HashMap<String, String>) -> HashMap<String, String> {
    headers.iter()
        .map(|(key, value)| {
            let key = key.to_lowercase().trim().to_string();
            (key, value.to_string())
        })
        .collect::<HashMap<_, _>>()
}

enum ResponseType {
    Json,
    Text,
    Bin
}

fn get_response_type(response: &Response) -> ResponseType {
    let content_type = response.headers().get("content-type");

    let Some(content_type) = content_type else {
        return ResponseType::Bin;
    };

    let content_type = match content_type.to_str() {
        Ok(content_type) => content_type.to_string(),
        Err(_) => {
            return ResponseType::Bin;
        }
    };

    let mut chunks = content_type.split(';').collect::<VecDeque<_>>();

    match chunks.pop_front() {
        Some(chunk_content_type) => {
            let chunk_content_type = chunk_content_type.trim().to_lowercase();

            if chunk_content_type == "application/json" {
                return ResponseType::Json;
            }

            if chunk_content_type == "text/plain" {
                return ResponseType::Text;
            }

            ResponseType::Bin
        },
        None => {
            ResponseType::Bin
        }
    }
}

async fn send_request_inner(request_params: Arc<FetchRequest>) -> Option<(u32, RequestBody)> {
    let client = reqwest::Client::new();

    let mut request = match request_params.method.trim().to_lowercase().as_str() {
        "get" => client.get(&request_params.url),
        "post" => client.post(&request_params.url),
        _ => {
            unreachable!();
        }
    };

    let headers = clear_headers(&request_params.headers);
    let (headers, body) = get_headers_and_body(headers, &request_params.body);

    for (key, value) in headers {
        request = request.header(key, value);
    }

    match body {
        BodyToSend::None => {},
        BodyToSend::String(body) => {
            request = request.body(body);
        },
        BodyToSend::Vec(buffer) => {
            request = request.body(buffer);
        },
    }

    let response = match request.send().await {
        Ok(response) => response,
        Err(error) => {
            log::error!("Error send: {error}");
            return None;
        }
    };

    let status = response.status().as_u16() as u32;
    let response_type = get_response_type(&response);

    let buffer = match response.bytes().await {
        Ok(response) => response.to_vec(),
        Err(error) => {
            log::error!("data fetching error: {error}");
            return None;
        }
    };

    match response_type {
        ResponseType::Text => {
            let Ok(body) = String::from_utf8(buffer) else {
                log::error!("response decoding problem");
                return None;
            };

            Some((status, RequestBody::Text(body)))
        }
        ResponseType::Json => {
            match serde_json::from_slice::<Value>(buffer.as_slice()) {
                Ok(json) => {
                    let json = convert_to_jsjson(json);
                    Some((status, RequestBody::Json(json)))
                },
                Err(error) => {
                    log::error!("response decoding json problem error={error}");
                    None
                }
            }
        }
        ResponseType::Bin => {
            Some((status, RequestBody::Binary(buffer)))
        }
    }
}