| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- pub mod ctype;
- pub mod url;
- use std::str::FromStr;
- use crate::{db::Update, request::ctype::ContentType, workspace::WorkspaceEntryBase, AppResult};
- use reqwest::{
- header::{self, HeaderMap, HeaderValue},
- Body, Method,
- };
- use serde::{Deserialize, Serialize};
- use sqlx::prelude::FromRow;
- use tauri_plugin_log::log;
- pub const DEFAULT_HEADERS: &'static [(&'static str, &'static str)] = &[
- ("user-agent", "rquest/0.0.1"),
- ("accept", "*/*"),
- ("accept-encoding", "gzip, defalte, br"),
- ];
- pub async fn send(client: reqwest::Client, req: HttpRequestParameters) -> AppResult<HttpResponse> {
- let HttpRequestParameters {
- url,
- method,
- mut headers,
- body,
- } = req;
- fn insert_ct_if_missing(headers: &mut HeaderMap, value: &'static str) {
- if !headers.contains_key(header::CONTENT_TYPE) {
- headers.insert(header::CONTENT_TYPE, HeaderValue::from_static(value));
- }
- }
- let body = match body {
- Some(body) => {
- match body.ty {
- ContentType::Text => insert_ct_if_missing(&mut headers, "text/plain"),
- ContentType::Json => insert_ct_if_missing(&mut headers, "application/json"),
- ContentType::Xml => insert_ct_if_missing(&mut headers, "application/xml"),
- // Handled by reqwest
- ContentType::FormData | ContentType::FormUrlEncoded => {}
- };
- Some(Body::from(body.content))
- }
- None => None,
- };
- let mut req = client.request(method, url).headers(headers);
- if let Some(body) = body {
- req = req.body(body)
- }
- let res = match req.send().await {
- Ok(res) => {
- log::debug!(
- "{} {} {:?} {:#?}",
- res.remote_addr()
- .map(|addr| addr.to_string())
- .unwrap_or(String::new()),
- res.status(),
- res.content_length(),
- res.headers()
- );
- let status = res.status();
- let headers = res.headers().clone();
- let body = ResponseBody::try_from_response(res).await?;
- HttpResponse::new(status.as_u16() as usize, headers, body)
- }
- Err(e) => return Err(e.into()),
- };
- Ok(res)
- }
- #[derive(Debug, Serialize)]
- pub struct WorkspaceRequest {
- /// Workspace entry representing this request.
- pub entry: WorkspaceEntryBase,
- /// Request method.
- pub method: String,
- /// The request URL
- pub url: String,
- /// Request HTTP body.
- pub body: Option<EntryRequestBody>,
- /// HTTP header names => values.
- pub headers: Vec<RequestHeader>,
- /// URL path keys => values.
- pub path_params: Vec<RequestPathParam>,
- }
- impl WorkspaceRequest {
- pub fn new(entry: WorkspaceEntryBase, method: String, url: String) -> Self {
- Self {
- entry,
- method,
- url,
- body: None,
- headers: vec![],
- path_params: vec![],
- }
- }
- pub fn from_params_and_headers(
- entry: WorkspaceEntryBase,
- params: RequestParams,
- headers: Vec<RequestHeader>,
- path_params: Vec<RequestPathParam>,
- ) -> Self {
- let body = match (params.body_id, params.body, params.content_type) {
- (Some(id), Some(body), Some(content_type)) => Some(EntryRequestBody {
- id,
- body,
- content_type,
- }),
- (None, None, None) => None,
- _ => panic!("id, body and content_type must all be present"),
- };
- WorkspaceRequest {
- entry,
- method: params.method,
- url: params.url,
- body,
- headers,
- path_params,
- }
- }
- }
- /// Finalized request parameters obtained from a [WorkspaceRequest].
- #[derive(Debug)]
- pub struct HttpRequestParameters {
- pub url: String,
- pub method: reqwest::Method,
- pub headers: HeaderMap,
- pub body: Option<RequestBody>,
- }
- impl TryFrom<WorkspaceRequest> for HttpRequestParameters {
- type Error = String;
- fn try_from(value: WorkspaceRequest) -> Result<Self, String> {
- let method = match Method::from_str(&value.method) {
- Ok(m) => m,
- Err(e) => return Err(e.to_string()),
- };
- let mut headers = HeaderMap::new();
- for header in value.headers {
- headers.insert(
- reqwest::header::HeaderName::from_str(&header.name).map_err(|e| e.to_string())?,
- HeaderValue::from_str(&header.value).map_err(|e| e.to_string())?,
- );
- }
- Ok(Self {
- url: value.url,
- method,
- headers,
- body: value.body.map(|body| RequestBody {
- content: body.body,
- ty: body.content_type,
- }),
- })
- }
- }
- #[derive(Debug, Serialize)]
- pub struct HttpResponse {
- pub status: usize,
- // pub headers: HeaderMap,
- pub body: Option<ResponseBody>,
- }
- impl HttpResponse {
- pub fn new(status: usize, headers: HeaderMap, body: Option<ResponseBody>) -> Self {
- Self {
- status,
- // headers,
- body,
- }
- }
- }
- #[derive(Debug, Clone, Serialize)]
- pub enum ResponseBody {
- Text(String),
- /// A pretty printed JSON string
- Json(String),
- // TODO:
- // Xml(String),
- // HTML
- // Binary
- }
- impl ResponseBody {
- pub fn len(&self) -> usize {
- match self {
- ResponseBody::Text(t) => t.len(),
- ResponseBody::Json(t) => t.len(),
- }
- }
- pub async fn try_from_response(res: reqwest::Response) -> AppResult<Option<Self>> {
- if res.content_length().is_none() {
- log::debug!("Response no content");
- }
- let Some(ct) = res.headers().get(header::CONTENT_TYPE) else {
- log::warn!("Response does not contain content-type header, attempting to read as text");
- return Ok(Some(Self::Text(res.text().await?)));
- };
- let ct = match ct.to_str() {
- Ok(ct) => ct,
- Err(e) => {
- log::warn!("Unable to parse content-type header: {e}");
- return Err(e.into());
- }
- };
- let ct: mime::Mime = ct.parse()?;
- if ct.subtype() == mime::JSON || ct.suffix().is_some_and(|s| s == mime::JSON) {
- log::debug!("reading body");
- let json = serde_json::to_string_pretty(&res.json::<serde_json::Value>().await?)?;
- log::debug!("body read");
- return Ok(Some(Self::Json(json)));
- }
- if ct.type_() == mime::TEXT {
- log::debug!("reading body");
- let text = res.text().await?;
- log::debug!("body read");
- return Ok(Some(Self::Text(text)));
- }
- log::warn!("Body did not match anything!");
- Ok(None)
- }
- }
- #[derive(Debug)]
- pub struct RequestParams {
- /// ID of the workspace entry representing this request.
- pub id: i64,
- pub method: String,
- pub url: String,
- pub content_type: Option<ContentType>,
- pub body: Option<String>,
- pub body_id: Option<i64>,
- }
- #[derive(Debug, Deserialize, Serialize)]
- pub struct RequestPathParam {
- pub position: i64,
- pub name: String,
- pub value: String,
- }
- #[derive(Debug, Deserialize)]
- pub struct RequestPathUpdate {
- pub position: usize,
- pub name: String,
- pub value: Option<String>,
- }
- #[derive(Debug, Serialize, FromRow)]
- pub struct RequestHeader {
- pub id: i64,
- pub name: String,
- pub value: String,
- }
- #[derive(Debug, Deserialize)]
- pub struct RequestHeaderInsert {
- pub name: String,
- pub value: String,
- }
- #[derive(Debug, Deserialize)]
- pub struct RequestHeaderUpdate {
- pub id: i64,
- pub name: Option<String>,
- pub value: Option<String>,
- }
- #[derive(Debug, Serialize, Deserialize)]
- pub struct RequestBody {
- pub content: String,
- pub ty: ContentType,
- }
- #[derive(Debug, Serialize)]
- pub struct EntryRequestBody {
- pub id: i64,
- pub body: String,
- pub content_type: ContentType,
- }
|