use lino_objects_codec::LinoValue;
use crate::headers::Headers;
use crate::problem::LinoHttpError;
use crate::query::{CollectionQuery, QueryParams, form_decode, parse_collection_query};
use crate::router::PathParams;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct QueryDefaults {
pub default_limit: usize,
pub max_limit: usize,
}
impl Default for QueryDefaults {
fn default() -> Self {
Self {
default_limit: crate::query::DEFAULT_LIMIT,
max_limit: crate::query::MAX_LIMIT,
}
}
}
#[derive(Debug, Clone)]
pub struct LinoRequest {
pub method: String,
pub path: String,
pub headers: Headers,
pub params: PathParams,
pub query_string: String,
pub body: Option<LinoValue>,
pub media_type: String,
pub request_media_type: Option<String>,
pub query_defaults: QueryDefaults,
}
impl LinoRequest {
pub fn new<M: Into<String>, P: Into<String>>(method: M, path: P) -> Self {
Self {
method: method.into().to_uppercase(),
path: path.into(),
headers: Headers::new(),
params: PathParams::new(),
query_string: String::new(),
body: None,
media_type: crate::media_type::LINO_CONTENT_TYPE.to_string(),
request_media_type: None,
query_defaults: QueryDefaults::default(),
}
}
pub fn header(&self, name: &str) -> Option<&str> {
self.headers.get(name)
}
pub fn param(&self, name: &str) -> Option<String> {
self.params.get(name).map(|value| percent_decode(value))
}
pub fn query(&self) -> QueryParams {
QueryParams::parse(&self.query_string)
}
pub fn collection_query(&self) -> Result<CollectionQuery, LinoHttpError> {
parse_collection_query(
&self.query(),
self.query_defaults.default_limit,
self.query_defaults.max_limit,
)
}
pub fn require_body(&self) -> Result<&LinoValue, LinoHttpError> {
self.body
.as_ref()
.ok_or_else(|| LinoHttpError::new(400, Some("A request body is required")))
}
pub fn instance(&self) -> String {
if self.query_string.is_empty() {
self.path.clone()
} else {
format!("{}?{}", self.path, self.query_string)
}
}
}
pub fn percent_decode(text: &str) -> String {
form_decode(&text.replace('+', "%2B"))
}
pub fn percent_encode(text: &str) -> String {
let mut encoded = String::with_capacity(text.len());
for byte in text.as_bytes() {
match byte {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' | b'/' => {
encoded.push(*byte as char)
}
other => encoded.push_str(&format!("%{other:02X}")),
}
}
encoded
}