fetch-js 1.0.0-alpha.1

A fetch API implementation in Rust
Documentation
use std::collections::HashMap;
use std::fmt::Display;

#[derive(Debug)]
pub struct URL {
  hash: String,
  pathname: String,
  port: String,
  protocol: String,
  username: String,
  password: String,
  hostname: String,
  pub search_params: URLSearchParams,
}

impl URL {
  fn parse_protocol(url: &mut String) -> Result<String, Box<dyn std::error::Error>> {
    let protocol_end = url.find("//").ok_or("Invalid URL")?;
    let protocol = &url.clone()[..protocol_end];
    *url = url[protocol_end + 2..].to_string();
    Ok(protocol.to_string())
  }
  
  fn parse_username_and_password(url: &mut String) -> Result<(String, String), Box<dyn std::error::Error>> {
    let username_end = url.find(":").ok_or("Invalid URL")?;
    let username = &url.clone()[..username_end];
    *url = url[username_end + 1..].to_string();
    let password_end = url.find("@").ok_or("Invalid URL")?;
    let password = &url.clone()[..password_end];
    *url = url[password_end + 1..].to_string();
    Ok((username.to_string(), password.to_string()))
  }
  
  fn parse_hostname_and_port(url: &mut String) -> Result<(String, String), Box<dyn std::error::Error>> {
    let hostname_end = url.find(":").ok_or("Invalid URL")?;
    let hostname = &url.clone()[..hostname_end];
    *url = url[hostname_end + 1..].to_string();
    let port_end = url.find("/").ok_or("Invalid URL")?;
    let port = &url.clone()[..port_end];
    *url = url[port_end + 1..].to_string();
    Ok((hostname.to_string(), port.to_string()))
  }
  
  fn parse_pathname(url: &mut String) -> Result<String, Box<dyn std::error::Error>> {
    let pathname_end = url.find("?").ok_or("Invalid URL")?;
    let pathname = &url.clone()[..pathname_end];
    *url = url[pathname_end + 1..].to_string();
    Ok(pathname.to_string())
  }
  
  fn parse_search_params_and_hash(url: &mut String) -> Result<(URLSearchParams, String), Box<dyn std::error::Error>> {
    let search_end = url.find("#").ok_or("Invalid URL")?;
    let search = &url.clone()[..search_end];
    *url = url[search_end + 1..].to_string();
    let hash = url.clone();
    Ok((URLSearchParams::new(search), hash.to_string()))
  }
  
  pub fn new(url: &str) -> Result<Self, Box<dyn std::error::Error>> {
    let mut url = url.to_string();
    let protocol = Self::parse_protocol(&mut url).unwrap_or_else(|_| "http".to_string());
    let (username, password) = Self::parse_username_and_password(&mut url).unwrap_or_else(|_| ("".to_string(), "".to_string()));
    let (hostname, port) = Self::parse_hostname_and_port(&mut url).unwrap_or_else(|_| ("".to_string(), "".to_string()));
    let pathname = Self::parse_pathname(&mut url).unwrap_or_else(|_| "".to_string());
    let (search_params, hash) = Self::parse_search_params_and_hash(&mut url).unwrap_or_else(|_| (URLSearchParams::new(""), "".to_string()));
    Ok(Self {
      hash,
      pathname,
      port,
      protocol,
      username,
      password,
      hostname,
      search_params, 
    })
  }
}

impl URL {
  pub fn get_protocol(&self) -> String {
    self.protocol.clone()
  }
  
  pub fn set_protocol(&mut self, protocol: &str) {
    self.protocol = protocol.to_string();
  }
  
  pub fn get_username(&self) -> String {
    self.username.clone()
  }
  
  pub fn set_username(&mut self, username: &str) {
    self.username = username.to_string();
  }
  
  pub fn get_password(&self) -> String {
    self.password.clone()
  }
  
  pub fn set_password(&mut self, password: &str) {
    self.password = password.to_string();
  }
  
  pub fn get_hostname(&self) -> String {
    self.hostname.clone()
  }
  
  pub fn set_hostname(&mut self, hostname: &str) {
    self.hostname = hostname.to_string();
  }
  
  pub fn get_port(&self) -> String {
    self.port.clone()
  }
  
  pub fn set_port(&mut self, port: &str) {
    self.port = port.to_string();
  }
  
  pub fn get_pathname(&self) -> String {
    self.pathname.clone()
  }
  
  pub fn set_pathname(&mut self, pathname: &str) {
    self.pathname = pathname.to_string();
  }
  
  pub fn get_hash(&self) -> String {
    self.hash.clone()
  }
  
  pub fn set_hash(&mut self, hash: &str) {
    self.hash = hash.to_string();
  }
}

impl URL {
  pub fn get_href(&self) -> String {
    format!("{}://{}:{}{}?{}#{}", self.protocol, self.hostname, self.port, self.pathname, self.search_params, self.hash)
  }
}

#[derive(Debug)]
pub struct URLSearchParams {
  params: HashMap<String, String>,
}

impl URLSearchParams {
  pub fn new(param_str: &str) -> Self {
    let param_strings = param_str.split('&').collect::<Vec<&str>>();
    let mut params = HashMap::new();
    for param in param_strings {
      let parts = param.split('=').collect::<Vec<&str>>();
      params.insert(parts[0].to_string(), parts[1].to_string());
    };
    Self {
      params,
    }
  }
}

impl URLSearchParams {
  pub fn get(&self, name: &str) -> Option<String> {
    self.params.get(name).map(|v| v.to_string())
  }

  pub fn set(&mut self, name: &str, value: &str) {
    self.params.insert(name.to_string(), value.to_string());
  }

  pub fn has(&self, name: &str) -> bool {
    self.params.contains_key(name)
  }

  pub fn delete(&mut self, name: &str) {
    self.params.remove(name);
  }

  pub fn entries(&self) -> SearchParamsIter {
    SearchParamsIter::new(self.params.clone())
  }

  pub fn for_each<F>(&self, callback: F) where F: Fn(&str, &str, &Self) {
    for (k, v) in self.params.iter() {
      callback(k, v, self);
    }
  }

  pub fn keys(&self) -> StringIter {
    StringIter::new(self.params.keys().map(|k| k.to_string()).collect())
  }

  pub fn values(&self) -> StringIter {
    StringIter::new(self.params.values().map(|v| v.to_string()).collect())
  }
}

impl IntoIterator for URLSearchParams {
  type Item = (String, String);
  type IntoIter = SearchParamsIter;
  fn into_iter(self) -> Self::IntoIter {
    SearchParamsIter::new(self.params)
  }
}

impl Display for URLSearchParams {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    let mut param_strings = vec![];
    for (k, v) in self.params.iter() {
      param_strings.push(format!("{}={}", k, v));
    };
    write!(f, "{}", param_strings.join("&"))
  }
}

#[derive(Debug)]
pub struct SearchParamsIter {
  params: Vec<(String, String)>,
  index: usize,
}

impl SearchParamsIter {
  pub fn new(params: HashMap<String, String>) -> Self {
    let mut param_vec = vec![];
    for (k, v) in params.iter() {
      param_vec.push((k.to_string(), v.to_string()));
    };
    Self {
      params: param_vec,
      index: 0,
    }
  }
}

impl Iterator for SearchParamsIter {
  type Item = (String, String);
  fn next(&mut self) -> Option<Self::Item> {
    if self.index < self.params.len() {
      let param = self.params[self.index].clone();
      self.index += 1;
      Some(param)
    } else {
      None
    }
  }
}

#[derive(Debug)]
pub struct StringIter {
  strings: Vec<String>,
  index: usize,
}

impl StringIter {
  pub fn new(strings: Vec<String>) -> Self {
    Self {
      strings,
      index: 0,
    }
  }
}

impl Iterator for StringIter {
  type Item = String;
  fn next(&mut self) -> Option<Self::Item> {
    if self.index < self.strings.len() {
      let string = self.strings[self.index].clone();
      self.index += 1;
      Some(string)
    } else {
      None
    }
  }
}