curl_parser_ruben4ick/
lib.rs1#![doc = include_str!("../README.md")]
2
3use pest::Parser;
4use pest::error::Error as PestError;
5use pest_derive::Parser;
6use std::collections::HashMap;
7use std::fmt::{Display, Formatter, Result as FmtResult};
8use thiserror::Error;
9
10#[derive(Parser)]
11#[grammar = "./curl_grammar.pest"]
12pub struct CurlParser;
13
14#[derive(Debug, Error)]
15pub enum ParseError {
16 #[error("parse error: {0}")]
17 Pest(#[from] Box<PestError<Rule>>),
18
19 #[error("missing URL in curl command")]
20 MissingUrl,
21
22 #[error("missing value for flag {0}")]
23 MissingValue(&'static str),
24}
25
26#[derive(Debug)]
27pub struct CurlRequest {
28 pub method: String,
29 pub url: String,
30 pub headers: HashMap<String, String>,
31 pub body: String,
32}
33
34impl Display for CurlRequest {
35 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
36 writeln!(f, "method: {}", self.method)?;
37 writeln!(f, "url: {}", self.url)?;
38 writeln!(f, "headers:")?;
39 for (k, v) in &self.headers {
40 writeln!(f, " {}: {}", k, v)?;
41 }
42 writeln!(f, "body:\n{}", self.body)
43 }
44}
45
46impl CurlRequest {
47 pub fn parse_input(input: &str) -> Result<Self, ParseError> {
48 let pairs =
49 CurlParser::parse(Rule::curl, input).map_err(|e| ParseError::Pest(Box::new(e)))?;
50
51 let mut method = String::new();
52 let mut url = String::new();
53 let mut headers = HashMap::new();
54 let mut body = String::new();
55
56 for pair in pairs {
57 if pair.as_rule() != Rule::curl {
58 continue;
59 }
60 for p in pair.into_inner() {
61 match p.as_rule() {
62 Rule::url => {
63 if url.is_empty() {
64 url = strip_quotes(p.as_str()).to_string();
65 }
66 }
67 Rule::method_flag => {
68 let v = p
69 .into_inner()
70 .next()
71 .ok_or(ParseError::MissingValue("-X/--request"))?;
72 method = strip_quotes(v.as_str()).to_uppercase();
73 }
74 Rule::header_flag => {
75 let v = p
76 .into_inner()
77 .next()
78 .ok_or(ParseError::MissingValue("-H/--header"))?;
79 if let Some((k, v)) = split_header(strip_quotes(v.as_str())) {
80 headers.insert(k, v);
81 }
82 }
83 Rule::data_flag => {
84 let v = p
85 .into_inner()
86 .next()
87 .ok_or(ParseError::MissingValue("-d/--data"))?;
88 body = strip_quotes(v.as_str()).to_string();
89 }
90 _ => {}
91 }
92 }
93 }
94
95 if url.is_empty() {
96 return Err(ParseError::MissingUrl);
97 }
98
99 Ok(CurlRequest {
100 method,
101 url,
102 headers,
103 body,
104 })
105 }
106}
107
108pub fn parse(input: &str) -> Result<CurlRequest, ParseError> {
109 CurlRequest::parse_input(input)
110}
111
112fn strip_quotes(s: &str) -> &str {
113 if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
114 &s[1..s.len() - 1]
115 } else {
116 s
117 }
118}
119
120fn split_header(raw: &str) -> Option<(String, String)> {
121 let (k, v) = raw.split_once(':')?;
122 Some((k.trim().to_string(), v.trim().to_string()))
123}