This commit is contained in:
Sivert V. Sæther 2024-11-07 14:27:37 +01:00
commit 244e8caa9f
9 changed files with 1664 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

1412
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

16
Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "ripht"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "ripht"
[features]
default = []
rustls = []
[dependencies]
clap = { version = "4.5.8", features = ["derive"] }
reqwest = "0.12.5"
json = "0.12.4"

6
ripht.json Normal file
View File

@ -0,0 +1,6 @@
{
"catfacts": {
"base": "https://cat-fact.herokuapp.com",
"tests": "tests/catfacts"
}
}

35
src/bin/ripht.rs Normal file
View File

@ -0,0 +1,35 @@
use clap::{Parser, ArgAction};
use ripht::{Config, Ripht};
use std::fs;
/// RipHT CLI HTTP REST API client
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
/// Config file
#[arg(short, long, default_value = "ripht.json")]
conf: String,
/// Verbosity
#[arg(short, long, action = ArgAction::Count)]
verbosity: u8,
}
impl Into<Config> for Args {
fn into(self) -> Config {
Config {
verbosity: self.verbosity,
parallel: false,
}
}
}
fn main() {
println!("Warming up...");
let args = Args::parse();
let json = json::parse(std::str::from_utf8(
&fs::read(&args.conf).unwrap()
).unwrap()).unwrap();
let rip = Ripht::with_json(args.into(), json);
println!("Letting it RIP!");
rip.work();
}

61
src/lib.rs Normal file
View File

@ -0,0 +1,61 @@
use std::collections::HashMap;
use json::JsonValue;
use reqwest::Client;
mod parse;
#[derive(Debug)]
pub struct Config {
pub parallel: bool,
pub verbosity: u8,
}
#[derive(Debug)]
struct Request {
headers: HashMap<String, String>,
meth: String,
path: String,
body: String,
}
#[derive(Debug)]
struct Target {
reqs: Vec<Request>,
base: String,
name: String,
}
#[derive(Debug)]
pub struct Ripht {
targets: Vec<Target>,
client: Client,
config: Config,
}
impl Ripht {
pub fn with_json(config: Config, json: JsonValue) -> Self {
let targets = match json {
JsonValue::Object(obj) => parse::obj(obj),
JsonValue::Array(arr) => parse::arr(arr),
_ => panic!("Root level of ripht.json config has to be a JSON object or array!"),
};
let client = Client::new();
Self { targets, client, config }
}
pub fn work(self) {
println!("{self:?}");
}
}
/*
pub fn ripit(targets: Vec<Target>) {
let mut curl = Easy::new();
for target in targets {
curl.url(target.base).unwrap();
curl.write_function(|data| {
println!("{data}");
Ok(data.len())
}).unwrap();
curl.perform().unwrap();
}
}
*/

31
src/parse/mod.rs Normal file
View File

@ -0,0 +1,31 @@
use crate::{Request, Target};
mod req;
impl Into<Request> for req::Req<'_> {
fn into(self) -> Request {
Request {
meth: self.meth.to_string(), path: self.path.to_string(), body: self.body.to_string(),
headers: self.head.into_iter().map(
|(name, val)| (name.to_string(), val.to_string())
).collect(),
}
}
}
pub fn obj(json: json::object::Object) -> Vec<Target> {
let mut targets = vec![];
for (name, conf) in json.iter() {
targets.push(Target {
reqs: req::reqs(conf["tests"].to_string()),
base: conf["base"].to_string(),
name: name.to_string(),
})
}
return targets;
}
pub fn arr(targets: json::Array) -> Vec<Target> {
panic!("Not implemented!");
}

100
src/parse/req.rs Normal file
View File

@ -0,0 +1,100 @@
use std::{
fs::{self, ReadDir, DirEntry},
collections::HashMap,
io::Read, str::Chars,
};
use reqwest::dns::Name;
use crate::Request;
pub fn reqs<'ctx>(path: String) -> Vec<Request> {
// Find path
// -> Open files
// -> Ppass content to req::Req::parse().into()
// Collect
dir(fs::read_dir(path).unwrap()).iter().map(
|entry| {
let mut contents = String::new();
fs::File::open(entry.path()).unwrap().read_to_string(&mut contents).unwrap();
Req::parse(&contents).into()
}
).collect()
}
fn dir(read: ReadDir) -> Vec<DirEntry> {
let mut files = vec![];
for dir_result in read {
let dir_entry = dir_result.unwrap();
let dir_type = dir_entry.file_type().unwrap();
if dir_type.is_dir() {
files.append(&mut dir(fs::read_dir(dir_entry.path().to_owned()).unwrap()));
}
if dir_type.is_file() {
files.push(dir_entry);
}
}
files
}
pub struct Req<'ctx> {
pub head: HashMap<&'ctx str, &'ctx str>,
pub meth: &'ctx str,
pub path: &'ctx str,
pub body: &'ctx str,
pub raw: &'ctx str,
}
impl<'ctx> Req<'ctx> {
pub fn parse(raw: &'ctx str) -> Self {
let mut head = HashMap::new();
let mut chars = raw.chars();
let mut idx = 0usize;
let skip = |mut idx, chars: &mut Chars| {
while chars.next().unwrap().is_whitespace() { idx += 1; }
idx
};
idx = skip(idx, &mut chars);
let (meth, mut idx) = parse_word(idx, raw, &mut chars);
idx = skip(idx, &mut chars);
let (path, mut idx) = parse_word(idx, raw, &mut chars);
idx = skip(idx, &mut chars);
let mut lnc = 0;
while lnc < 1 {
// Unwrap error here should only mean that the body is empty and there is no newline after the headers
let next = chars.next().unwrap_or('\n');
if next == '\n' {
lnc += 1;
}
if next.is_alphanumeric() {
let (name, value, mut idx) = parse_head(idx, raw, &mut chars);
head.insert(name, value);
}
idx += 1;
}
idx += 2;
let body = if chars.next().is_none() { "" }
else { &raw[idx..] };
Self { head, meth, path, body, raw }
}
}
fn parse_word<'ctx>(start: usize, raw: &'ctx str, chars: &mut Chars) -> (&'ctx str, usize) {
let mut end = start;
while chars.next().unwrap().is_alphabetic() { end += 1; }
end += 1;
let word = &raw[start..end];
end += 1;
(word, end)
}
fn parse_head<'ctx>(start: usize, raw: &'ctx str, chars: &mut Chars) -> (&'ctx str, &'ctx str, usize) {
let mut end = start;
dbg!("parse headers here");
end += 1;
("", "", end)
}

2
tests/catfacts/facts.rht Normal file
View File

@ -0,0 +1,2 @@
GET /facts
Accept: application/json