ultdocs 0.1.1

(Experimental) Static site generator for API's, Postman and markdown documents
Documentation
use crate::config::get_configuration;
use crate::markup;
use crate::postman::{ApiResponse, Collection, Environment, Environments, Item, SingleEnvironment};
use std::fs;

pub struct Parser {
    /// HTML that will be generated
    page: String,
    /// Links on the navbar
    links: String,
    /// Request data e.g. path, method, headers
    requests: String,
    /// The current iteration of the recursive loop
    loop_iter: u16,
    // Used as a key for svelte components to access store data instead of a UUID
    request_iter: u16,
    /// The current folder is used to place routes in the correct
    /// place for the route summary
    current_folder: String,
    /// Environment variables parsed to a string
    environments: String,
}

impl Parser {
    /// loop_iter starts at 1 because it matches up with HTML heading numbers
    /// e.g. `h1`
    pub fn new() -> Parser {
        Parser {
            page: String::new(),
            links: String::new(),
            requests: String::new(),
            loop_iter: 1,
            request_iter: 0,
            current_folder: String::new(),
            environments: String::new(),
        }
    }

    pub fn env_to_ts(&mut self, envs: Vec<Environment>) {
        self.environments.push_str(
            r#"import { writable } from 'svelte/store';
            export const envs = writable({ "#,
        );
        for env in envs {
            let name = env.name;
            self.environments.push_str(&format!("\"{name}\": {{"));
            if let Some(vals) = env.values {
                for val in vals {
                    let key = val.key;
                    let value = val.value;
                    let env_string = format!("\n\"{key}\": \"{value}\",");
                    self.environments.push_str(&env_string);
                }
            }
            self.environments.push_str(&format!("\n}},"));
        }
        self.environments.push_str("\n});")
    }

    /// Loops through items recursively, Postman doesn't have a field for what type the item is,
    /// the item can be a request, response vector or a folder containing more nested items.
    /// Using Option<> to recursively loop, where the absence of a new nested item is the break point
    pub fn loop_items(&mut self, items: &Vec<Item>) {
        for item in items {
            // If request convert JSON to markup
            if let Some(request) = &item.request {
                let path_markup = markup::request(&item, &request, self.request_iter);
                self.page.push_str(&path_markup.0);
                self.requests.push_str(&path_markup.1);
                self.request_iter += 1;
                // If response vector, convert response to markup
            } else if let Some(responses) = &item.responses {
                let response_markup = markup::response(responses);
                self.page.push_str(&response_markup);
                // Otherwise must be folder
            } else {
                self.current_folder = item.name.clone();
                let (folder, link) = markup::folder(&item, self.loop_iter);
                self.page.push_str(&folder);
                self.links.push_str(&link);
                // If there is another nested item, recursively run loop_items
                if let Some(item) = &item.item {
                    self.loop_iter += 1;
                    self.loop_items(&item);
                }
            }
        }
        self.loop_iter -= 1;
    }

    /// Write files to static site, backup collection depending on config
    pub fn write_to_disk(&self) {
        let page = format!(
            "<script>\nimport Request from '../components/Request.svelte';\n\n</script>{}",
            &self.page
        );

        let requests = markup::requests(&self.requests);
        fs::write("./site/src/requests.ts", &requests).expect("Unable to write file");
        fs::write("./site/src/pages/ultdocs.svelte", page).expect("Unable to write file");
        fs::write("./site/src/components/links.svelte", &self.links).expect("Unable to write file");
        fs::write("./site/src/envs.ts", &self.environments).expect("Unable to write file");
    }

    pub fn backup_collection(&self, collection: &Collection, backup_path: String) {
        fs::write(
            backup_path,
            serde_json::to_string_pretty(collection).expect("Unable to parse JSON to string"),
        )
        .expect("Unable to write file");
    }

    pub fn get_environment(&mut self) -> Vec<Environment> {
        let config = &get_configuration();
        println!("Reading environment from Postman remotely");
        let client = reqwest::blocking::Client::new();
        let collection_route = "https://api.getpostman.com/environments".to_string();
        let data = client
            .get(collection_route)
            .header("X-API-Key", &config.postman_api_key.clone())
            .send()
            .expect("Unable to retrieve Response from the Postman API")
            .text()
            .expect("Unable to parse text from Response body");
        let mut environments: Environments =
            serde_json::from_str(&data).expect("JSON was not well-formatted");

        let mut envs = Vec::new();
        for env in &mut environments.environments {
            let env_route = "https://api.getpostman.com/environments/".to_string() + &env.id;
            let env_data = client
                .get(env_route)
                .header("X-API-Key", &config.postman_api_key)
                .send()
                .expect("Unable to retrieve Response from the Postman API")
                .text()
                .expect("Unable to parse text from Response body");
            let env_single: SingleEnvironment =
                serde_json::from_str(&env_data).expect("JSON was not well-formatted");
            envs.push(env_single.environment);
        }
        envs
    }
}

/// Check the config, get the collection from the Postman API if collection_from_api == true,
/// else get the collection from the local disk
pub fn get_collection() -> Collection {
    let config = &get_configuration();
    let data: String;
    let collection: Collection;
    if config.collection_from_api {
        println!("Reading collection from Postman remotely");
        let client = reqwest::blocking::Client::new();
        let mut collection_route = "https://api.getpostman.com/collections/".to_string();
        collection_route.push_str(&config.collection_id);
        data = client
            .get(collection_route)
            .header("X-API-Key", &config.postman_api_key)
            .send()
            .expect("Unable to retrieve Response from the Postman API")
            .text()
            .expect("Unable to parse text from Response body");
        let api_response: ApiResponse =
            serde_json::from_str(&data).expect("JSON was not well-formatted");
        collection = api_response.collection;
        return collection;
    } else {
        data = fs::read_to_string(&config.collection_path).expect("Failed to read file");
        collection = serde_json::from_str(&data).expect("JSON was not well-formatted");
        return collection;
    }
}