Browse Source

fix URL parsing

biblius 2 weeks ago
parent
commit
220296e9ba

+ 3 - 1
src-tauri/migrations/20250922150745_init.up.sql

@@ -43,10 +43,12 @@ CREATE TABLE request_params (
 );
 
 CREATE TABLE request_path_params (
-    position INTEGER PRIMARY KEY NOT NULL,
+    id INTEGER PRIMARY KEY NOT NULL,
+    position INTEGER NOT NULL,
     request_id INTEGER NOT NULL,
     name TEXT NOT NULL,
     value TEXT NOT NULL,
+    UNIQUE(position, request_id),
     FOREIGN KEY (request_id) REFERENCES workspace_entries (id) ON DELETE CASCADE
 );
 

+ 55 - 52
src-tauri/src/cmd.rs

@@ -1,11 +1,9 @@
-use std::collections::HashMap;
-
 use crate::{
     db,
     request::{
         self,
         url::{RequestUrl, RequestUrlOwned, Segment, UrlError},
-        HttpRequestParameters, HttpResponse, RequestPathParam,
+        HttpRequestParameters, HttpResponse, RequestPathParam, RequestPathUpdate,
     },
     state::AppState,
     var::{expand_vars, parse_vars},
@@ -73,26 +71,10 @@ pub async fn update_workspace_entry(
 pub async fn parse_url(
     state: tauri::State<'_, AppState>,
     env_id: Option<i64>,
-    mut url: String,
+    url: String,
 ) -> Result<RequestUrlOwned, UrlError> {
-    if let Some(env_id) = env_id {
-        let vars = match parse_vars(&url) {
-            Ok(vars) => vars.iter().map(|v| v.name).collect::<Vec<_>>(),
-            Err(e) => return Err(UrlError::Var(e.to_string())),
-        };
-
-        if !vars.is_empty() {
-            let vars = match db::get_env_variables(state.db.clone(), env_id, &vars).await {
-                Ok(v) => v,
-                Err(e) => return Err(UrlError::Db(e.to_string())),
-            };
-
-            url = expand_vars(&url, &vars);
-        }
-    }
-
     match RequestUrl::parse(&url) {
-        Ok(url) => Ok(url.into()),
+        Ok(url_parsed) => Ok(url_parsed.into()),
         Err(e) => {
             log::debug!("{e:?}");
             Err(UrlError::Parse(e))
@@ -130,7 +112,7 @@ pub async fn expand_url(
                 Err(e) => return Err(UrlError::Db(e.to_string())),
             };
 
-            url.populate(
+            url.populate_path(
                 params
                     .iter()
                     .map(|p| (p.name.as_str(), p.value.as_str()))
@@ -166,50 +148,69 @@ pub async fn expand_url(
     }
 }
 
+/// Updates a URL.
+///
+/// * `entry_id`: The request entry ID  whose URL will be updated.
+/// * `env_id`: The environment to use for expanding variables.
+/// * `url`: The URL string which will be populated from the env and parsed with [RequestUrl].
+/// * `path_params`: List of dynamic path params to persist for the URL.
 #[tauri::command]
 pub async fn update_url(
     state: tauri::State<'_, AppState>,
     entry_id: i64,
-    env_id: Option<i64>,
+    use_path_params: bool,
     url: String,
-    path_params: HashMap<String, String>,
+    // Dynamic path params
+    path_params: Vec<RequestPathParam>,
 ) -> Result<(RequestUrlOwned, Vec<RequestPathParam>), UrlError> {
-    let url_expanded = if let Some(env_id) = env_id {
-        let vars = match parse_vars(&url) {
-            Ok(vars) => vars.iter().map(|v| v.name).collect::<Vec<_>>(),
-            Err(e) => return Err(UrlError::Var(e.to_string())),
-        };
-
-        let vars = match db::get_env_variables(state.db.clone(), env_id, &vars).await {
-            Ok(v) => v,
-            Err(e) => return Err(UrlError::Db(e.to_string())),
-        };
-
-        Some(expand_vars(&url, &vars))
-    } else {
-        None
-    };
-
-    match RequestUrl::parse(url_expanded.as_ref().unwrap_or(&url)) {
-        Ok(url_parsed) => {
-            let mut insert: Vec<RequestPathParam> = vec![];
+    match RequestUrl::parse(&url) {
+        Ok(mut url_parsed) => {
+            let mut update: Vec<RequestPathUpdate> = vec![];
+            let mut subs = vec![];
 
-            for seg in url_parsed.path.iter() {
+            for seg in url_parsed.path.iter_mut() {
                 if let Segment::Dynamic(seg, position) = seg {
-                    insert.push(RequestPathParam {
-                        position: *position as i64,
-                        name: seg.to_string(),
-                        value: path_params.get(*seg).cloned().unwrap_or_default(),
-                    })
+                    let Some(path_param) = path_params
+                        .iter()
+                        .find(|pp| pp.position as usize == *position || &pp.name == seg)
+                    else {
+                        update.push(RequestPathUpdate {
+                            position: *position,
+                            name: seg.to_string(),
+                            value: None,
+                        });
+                        continue;
+                    };
+
+                    if use_path_params {
+                        update.push(RequestPathUpdate {
+                            position: *position,
+                            name: path_param.name.clone(),
+                            value: Some(path_param.value.clone()),
+                        });
+                        subs.push(Segment::Dynamic(&path_param.name, *position));
+                    } else {
+                        update.push(RequestPathUpdate {
+                            position: *position,
+                            name: seg.to_string(),
+                            value: Some(path_param.value.clone()),
+                        })
+                    }
                 }
             }
 
+            dbg!(&subs);
+            for sub in subs {
+                url_parsed.swap_path_segment(sub);
+            }
+            dbg!(&update, &url_parsed);
+
             db::update_workspace_entry(
                 state.db.clone(),
                 entry_id,
                 WorkspaceEntryUpdate::Request {
-                    path_params: Some(insert.clone()),
-                    url: Some(url.clone()),
+                    path_params: Some(update),
+                    url: Some(url_parsed.to_string()),
                     base: WorkspaceEntryUpdateBase::default(),
                     body: None,
                     headers: None,
@@ -224,6 +225,8 @@ pub async fn update_url(
                 Err(e) => return Err(UrlError::Db(e.to_string())),
             };
 
+            dbg!(&url_parsed, &params);
+
             Ok((url_parsed.into(), params))
         }
         Err(e) => {
@@ -267,7 +270,7 @@ pub async fn send_request(
                 Err(e) => return Err(e.to_string()),
             };
 
-            url.populate(
+            url.populate_path(
                 params
                     .iter()
                     .map(|p| (p.name.as_str(), p.value.as_str()))

+ 11 - 6
src-tauri/src/db.rs

@@ -353,17 +353,22 @@ pub async fn update_workspace_entry(
                     );
 
                     sql.push_values(path_params.iter(), |mut b, path| {
-                        b.push_bind(path.position)
+                        b.push_bind(path.position as i64)
                             .push_bind(entry_id)
-                            .push_bind(&path.name)
-                            .push_bind(&path.value);
+                            .push_bind(&path.name);
+
+                        if let Some(ref path) = path.value {
+                            b.push_bind(path);
+                        } else {
+                            b.push_bind("");
+                        }
                     });
 
                     sql.push(
                         r#"
-                        ON CONFLICT(position) DO UPDATE 
+                        ON CONFLICT(position, request_id) DO UPDATE 
                         SET 
-                            value = COALESCE(NULLIF(excluded.value, ''), value),
+                            value = excluded.value,
                             name = excluded.name;
 
                         DELETE FROM request_path_params 
@@ -375,7 +380,7 @@ pub async fn update_workspace_entry(
                     let mut sep = sql.separated(", ");
 
                     for param in path_params.iter() {
-                        sep.push_bind(param.position);
+                        sep.push_bind(param.position as i64);
                     }
 
                     sep.push_unseparated(")");

+ 1 - 1
src-tauri/src/lib.rs

@@ -20,7 +20,7 @@ pub fn run() {
         .plugin(tauri_plugin_store::Builder::new().build())
         .plugin(
             tauri_plugin_log::Builder::new()
-                .level(tauri_plugin_log::log::LevelFilter::Info)
+                .level(tauri_plugin_log::log::LevelFilter::Debug)
                 .build(),
         )
         .plugin(tauri_plugin_opener::init())

+ 9 - 2
src-tauri/src/request.rs

@@ -3,7 +3,7 @@ pub mod url;
 
 use std::str::FromStr;
 
-use crate::{request::ctype::ContentType, workspace::WorkspaceEntryBase, AppResult};
+use crate::{db::Update, request::ctype::ContentType, workspace::WorkspaceEntryBase, AppResult};
 use reqwest::{
     header::{self, HeaderMap, HeaderValue},
     Body, Method,
@@ -252,13 +252,20 @@ pub struct RequestParams {
     pub body: Option<String>,
 }
 
-#[derive(Debug, Clone, Deserialize, Serialize, FromRow)]
+#[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)]
 pub struct RequestHeader {
     pub id: i64,

+ 163 - 56
src-tauri/src/request/url.rs

@@ -1,17 +1,20 @@
 use nom::{
-    bytes::complete::{tag, take_while, take_while1},
+    bytes::complete::{tag, take_while},
     character::complete::char,
+    combinator::opt,
     multi::many0,
-    sequence::{preceded, separated_pair},
+    sequence::{preceded, separated_pair, terminated},
     Parser,
 };
 use serde::{Deserialize, Serialize};
 use std::{collections::HashMap, fmt::Display};
+use tauri_plugin_log::log;
 
 #[derive(Debug, Serialize, Deserialize)]
 pub struct RequestUrlOwned {
-    pub scheme: String,
-    pub host: String,
+    pub pre: String,
+    // pub scheme: String,
+    // pub host: String,
     pub path: Vec<SegmentOwned>,
     pub query_params: Vec<(String, String)>,
     pub has_query: bool,
@@ -29,10 +32,11 @@ pub enum SegmentOwned {
 #[derive(Debug)]
 pub struct RequestUrl<'a> {
     /// The URL scheme, e.g. `http`.
-    pub scheme: &'a str,
+    // pub scheme: &'a str,
 
     /// The URL host, includes the port if specified.
-    pub host: &'a str,
+    // pub host: &'a str,
+    pub pre: &'a str,
 
     /// The URL path segments.
     ///
@@ -96,14 +100,19 @@ impl<'a> RequestUrl<'a> {
 
         let mut offset = 0;
 
-        let (input, scheme) = match take_while1(char::is_alphabetic)(original_input) {
-            Ok((i, s)) => (i, s),
-            Err(e) => return Err(map_nom_err(original_input, None, e)),
-        };
-
-        let (input, _) = tag("://")(input).map_err(|e| map_nom_err(input, Some("://"), e))?;
+        // Parse the tag if it exists
 
-        offset += scheme.len() + 3;
+        let input =
+            match opt(terminated(take_while(|c| c != ':'), tag("://"))).parse(original_input) {
+                Ok((input, scheme)) => match scheme {
+                    Some(scheme) => {
+                        offset += scheme.len() + 3;
+                        input
+                    }
+                    None => input,
+                },
+                Err(e) => return Err(map_nom_err(original_input, None, e)),
+            };
 
         // Parse until first /
 
@@ -113,19 +122,20 @@ impl<'a> RequestUrl<'a> {
         // We've fully parsed the string, no path
 
         if path.is_empty() {
-            let (query, host) =
+            let (query, remainder) =
                 take_while(|c| c != '?')(host).map_err(|e| map_nom_err(host, None, e))?;
 
-            let (query_params, trailing) =
+            offset += remainder.len();
+
+            let (query_params, trailing_query_pair) =
                 parse_query(query).map_err(|e| map_nom_err(query, None, e))?;
 
             return Ok(RequestUrl {
-                scheme,
-                host,
+                pre: &original_input[..offset],
                 path: vec![],
                 query_params,
                 trailing_query: query == "?",
-                trailing_query_pair: trailing,
+                trailing_query_pair,
             });
         }
 
@@ -146,6 +156,8 @@ impl<'a> RequestUrl<'a> {
             .parse(path)
             .map_err(|e| map_nom_err(path, None, e))?;
 
+        let mut segment_offset = offset;
+
         let mut path = vec![];
 
         for segment in segments {
@@ -157,25 +169,24 @@ impl<'a> RequestUrl<'a> {
             {
                 Ok((remainder, segment)) => {
                     debug_assert_eq!("", remainder);
-                    path.push(Segment::Dynamic(segment, offset));
+                    path.push(Segment::Dynamic(segment, segment_offset));
                     // account for :
-                    offset += segment.len() + 1;
+                    segment_offset += segment.len() + 1;
                 }
                 Err(_) => {
-                    path.push(Segment::Static(segment, offset));
-                    offset += segment.len();
+                    path.push(Segment::Static(segment, segment_offset));
+                    segment_offset += segment.len();
                 }
             }
 
             // account for the parsed /
-            offset += 1;
+            segment_offset += 1;
         }
 
         debug_assert!(remainder.is_empty());
 
         Ok(RequestUrl {
-            scheme,
-            host,
+            pre: &original_input[..offset],
             path,
             query_params,
             trailing_query: query == "?",
@@ -183,13 +194,12 @@ impl<'a> RequestUrl<'a> {
         })
     }
 
-    pub fn populate(&mut self, path_params: HashMap<&'a str, &'a str>) {
+    pub fn populate_path(&mut self, path_params: HashMap<&'a str, &'a str>) {
         let mut total_displaced = 0i64;
 
         for path in self.path.iter_mut() {
             match path {
                 Segment::Dynamic(value, offset) => {
-                    dbg!(*offset, total_displaced);
                     let value_len = value.len();
 
                     let new = path_params.get(value).unwrap_or(&"");
@@ -217,13 +227,40 @@ impl<'a> RequestUrl<'a> {
             }
         }
     }
+
+    pub fn swap_path_segment(&mut self, new: Segment<'a>) {
+        let Some((skip, segment)) = self
+            .path
+            .iter_mut()
+            .enumerate()
+            .map(|(i, s)| (i + 1, s))
+            .find(|(_, s)| s.position() == new.position())
+        else {
+            log::warn!(
+                "Attempted to swap segment with invalid position {}",
+                new.position()
+            );
+            return;
+        };
+
+        let offset = new.len() as i64 - segment.len() as i64;
+
+        *segment = new;
+
+        for path in self.path.iter_mut().skip(skip) {
+            if offset < 0 {
+                path.set_position(path.position() - offset.abs() as usize);
+            } else {
+                path.set_position(path.position() + offset as usize);
+            }
+        }
+    }
 }
 
 impl<'a> From<RequestUrl<'a>> for RequestUrlOwned {
-    fn from(value: RequestUrl<'a>) -> Self {
+    fn from(value: RequestUrl<'_>) -> Self {
         Self {
-            scheme: value.scheme.to_owned(),
-            host: value.host.to_owned(),
+            pre: value.pre.to_string(),
             path: value.path.into_iter().map(Into::into).collect(),
             query_params: value
                 .query_params
@@ -238,8 +275,9 @@ impl<'a> From<RequestUrl<'a>> for RequestUrlOwned {
 impl<'a> Display for RequestUrl<'a> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         let RequestUrl {
-            scheme,
-            host,
+            // scheme,
+            // host,
+            pre,
             path,
             query_params,
             trailing_query,
@@ -279,7 +317,7 @@ impl<'a> Display for RequestUrl<'a> {
             params
         };
 
-        write!(f, "{scheme}://{host}{path}{query}")
+        write!(f, "{pre}{path}{query}")
     }
 }
 
@@ -297,6 +335,29 @@ pub enum Segment<'a> {
     Dynamic(&'a str, usize),
 }
 
+impl<'a> Segment<'a> {
+    pub fn position(&self) -> usize {
+        match self {
+            Segment::Static(_, pos) => *pos,
+            Segment::Dynamic(_, pos) => *pos,
+        }
+    }
+
+    pub fn len(&self) -> usize {
+        match self {
+            Segment::Static(s, _) => s.len(),
+            Segment::Dynamic(s, _) => s.len(),
+        }
+    }
+
+    pub fn set_position(&mut self, pos: usize) {
+        match self {
+            Segment::Static(_, p) => *p = pos,
+            Segment::Dynamic(_, p) => *p = pos,
+        }
+    }
+}
+
 impl<'a> From<Segment<'a>> for SegmentOwned {
     fn from(value: Segment<'a>) -> Self {
         match value {
@@ -398,8 +459,8 @@ mod tests {
 
         let url = RequestUrl::parse(input).unwrap();
 
-        assert_eq!("http", url.scheme);
-        assert_eq!("localhost:4000", url.host);
+        assert_eq!("http://localhost:4000", url.pre);
+
         assert_eq!(expected_path, url.path);
         assert!(url.query_params.is_empty());
     }
@@ -417,8 +478,8 @@ mod tests {
 
         let url = RequestUrl::parse(input).unwrap();
 
-        assert_eq!("http", url.scheme);
-        assert_eq!("localhost:4000", url.host);
+        assert_eq!("http://localhost:4000", url.pre);
+
         assert_eq!(expected_path, url.path);
         assert!(url.query_params.is_empty());
     }
@@ -429,8 +490,8 @@ mod tests {
 
         let url = RequestUrl::parse(input).unwrap();
 
-        assert_eq!("http", url.scheme);
-        assert_eq!("localhost:4000", url.host);
+        assert_eq!("http://localhost:4000", url.pre);
+
         assert!(url.path.is_empty());
         assert!(url.query_params.is_empty());
     }
@@ -441,8 +502,8 @@ mod tests {
 
         let url = RequestUrl::parse(input).unwrap();
 
-        assert_eq!("http", url.scheme);
-        assert_eq!("localhost:4000", url.host);
+        assert_eq!("http://localhost:4000", url.pre);
+
         assert_eq!(
             vec![
                 Segment::Static("", "http://localhost:4000".len()),
@@ -459,8 +520,8 @@ mod tests {
 
         let url = RequestUrl::parse(input).unwrap();
 
-        assert_eq!("http", url.scheme);
-        assert_eq!("localhost:4000", url.host);
+        assert_eq!("http://localhost:4000", url.pre);
+
         assert_eq!(
             vec![
                 Segment::Dynamic("", "http://localhost:4000".len()),
@@ -477,8 +538,8 @@ mod tests {
 
         let url = RequestUrl::parse(input).unwrap();
 
-        assert_eq!("http", url.scheme);
-        assert_eq!("localhost:4000", url.host);
+        assert_eq!("http://localhost:4000", url.pre);
+
         assert_eq!(
             vec![Segment::Static("", "http://localhost:4000".len())],
             url.path
@@ -492,8 +553,8 @@ mod tests {
 
         let url = RequestUrl::parse(input).unwrap();
 
-        assert_eq!("http", url.scheme);
-        assert_eq!("localhost:4000", url.host);
+        assert_eq!("http://localhost:4000", url.pre);
+
         assert!(url.path.is_empty());
         assert_eq!(vec![("foo", "bar"), ("baz", "bax")], url.query_params);
     }
@@ -504,8 +565,8 @@ mod tests {
 
         let url = RequestUrl::parse(input).unwrap();
 
-        assert_eq!("http", url.scheme);
-        assert_eq!("localhost:4000", url.host);
+        assert_eq!("http://localhost:4000", url.pre);
+
         assert_eq!(
             vec![
                 Segment::Static("foo", "http://localhost:4000".len()),
@@ -523,8 +584,8 @@ mod tests {
 
         let url = RequestUrl::parse(input).unwrap();
 
-        assert_eq!("http", url.scheme);
-        assert_eq!("localhost:4000", url.host);
+        assert_eq!("http://localhost:4000", url.pre);
+
         assert_eq!(
             vec![
                 Segment::Static("foo", "http://localhost:4000".len()),
@@ -543,8 +604,8 @@ mod tests {
 
         let mut url = RequestUrl::parse(input).unwrap();
 
-        assert_eq!("http", url.scheme);
-        assert_eq!("localhost:4000", url.host);
+        assert_eq!("http://localhost:4000", url.pre);
+
         assert_eq!(
             vec![
                 Segment::Static("foo", "http://localhost:4000".len()),
@@ -556,7 +617,7 @@ mod tests {
             url.path
         );
 
-        url.populate(HashMap::from([("bar", "VALUE"), ("baz", "EULAV")]));
+        url.populate_path(HashMap::from([("bar", "VALUE"), ("baz", "EULAV")]));
 
         assert_eq!(
             "http://localhost:4000/foo/VALUE/qux/EULAV/final",
@@ -589,7 +650,7 @@ mod tests {
             url.path
         );
 
-        url.populate(HashMap::from([("ID", ""), ("myID", "")]));
+        url.populate_path(HashMap::from([("ID", ""), ("myID", "")]));
 
         assert_eq!("http://foo.com//", url.to_string());
 
@@ -616,7 +677,7 @@ mod tests {
             url.path
         );
 
-        url.populate(HashMap::from([("ID", "FOO")]));
+        url.populate_path(HashMap::from([("ID", "FOO")]));
 
         assert_eq!("http://foo.com/FOO/", url.to_string());
 
@@ -628,4 +689,50 @@ mod tests {
             url.path
         );
     }
+
+    #[test]
+    fn swaps_dynamic_segment_and_offsets() {
+        let input = "http://foo.com/bar/:ID/";
+
+        let mut url = RequestUrl::parse(input).unwrap();
+
+        assert_eq!(
+            vec![
+                Segment::Static("bar", "http://foo.com".len()),
+                Segment::Dynamic("ID", "http://foo.com/bar".len()),
+                Segment::Static("", "http://foo.com/bar/:ID".len()),
+            ],
+            url.path
+        );
+
+        let segment = Segment::Dynamic("AAAAA", "http://foo.com/bar".len());
+        url.swap_path_segment(segment);
+
+        assert_eq!(
+            vec![
+                Segment::Static("bar", "http://foo.com".len()),
+                Segment::Dynamic("AAAAA", "http://foo.com/bar".len()),
+                Segment::Static("", "http://foo.com/bar/:AAAAA".len()),
+            ],
+            url.path
+        );
+    }
+
+    #[test]
+    fn parses_with_placeholders() {
+        let input = "{{BASE_URL}}/bar/:ID/";
+
+        let url = RequestUrl::parse(input).unwrap();
+
+        assert_eq!("{{BASE_URL}}", url.pre);
+
+        assert_eq!(
+            vec![
+                Segment::Static("bar", "{{BASE_URL}}".len()),
+                Segment::Dynamic("ID", "{{BASE_URL}}/bar".len()),
+                Segment::Static("", "{{BASE_URL}}/bar/:ID".len()),
+            ],
+            url.path
+        );
+    }
 }

+ 4 - 2
src-tauri/src/workspace.rs

@@ -3,7 +3,9 @@ use sqlx::prelude::Type;
 
 use crate::{
     db::Update,
-    request::{RequestBody, RequestHeaderUpdate, RequestPathParam, WorkspaceRequest},
+    request::{
+        RequestBody, RequestHeaderUpdate, RequestPathParam, RequestPathUpdate, WorkspaceRequest,
+    },
 };
 
 #[derive(Debug, Serialize)]
@@ -82,7 +84,7 @@ pub enum WorkspaceEntryUpdate {
         url: Option<String>,
         body: Option<Update<RequestBody>>,
         headers: Option<Vec<RequestHeaderUpdate>>,
-        path_params: Option<Vec<RequestPathParam>>,
+        path_params: Option<Vec<RequestPathUpdate>>,
     },
 }
 

+ 29 - 21
src/lib/components/WorkspaceEntry.svelte

@@ -10,7 +10,7 @@
   import { Input } from "$lib/components/ui/input";
   import * as Accordion from "$lib/components/ui/accordion";
   import * as Tabs from "$lib/components/ui/tabs";
-  import type { UrlError } from "$lib/types";
+  import type { RequestPathParam, UrlError } from "$lib/types";
   import Editable from "./Editable.svelte";
 
   let headers = [
@@ -43,12 +43,12 @@
       .catch((e) => console.error("error sending request", e));
   }
 
-  async function handleUrlUpdate(direct = false) {
-    const u = direct ? _state.entry!!.url : constructUrl();
+  async function handleUrlUpdate(direct: boolean = false) {
+    const u = direct ? _state.entry!!.url : reconstructUrl();
     console.log(u);
 
     try {
-      await updateUrl(u);
+      await updateUrl(u, !direct);
     } catch (err) {
       console.error(err);
       const e = err as UrlError;
@@ -67,37 +67,45 @@
       }
       return;
     }
-
-    console.log("constructed URL", _state.entry.url);
   }
 
   /** Construct a URL from the binded input values for query and path parameters. */
-  function constructUrl(): string {
-    console.log($state.snapshot(_state.entry.path));
-    let path = "";
-    if (_state.entry.path.length > 0) {
-      for (const param of _state.entry.path) {
-        if (param.name !== undefined) {
-          path += "/:" + param.name;
-        } else {
-          path += "/" + param.value;
-        }
+  function reconstructUrl(): string {
+    let url = _state.entry.workingUrl.pre;
+
+    for (const param of _state.entry.workingUrl.path) {
+      console.log(param);
+      const [name, position] = param.value;
+
+      if (param.type === "Static") {
+        url += "/" + name;
+        continue;
       }
-    }
 
-    let query = "";
+      const replacement = _state.entry!!.path.find(
+        (p) => p.position === position,
+      );
+
+      if (replacement !== undefined) {
+        url += "/:" + replacement.name;
+      } else {
+        url += "/:" + name;
+      }
+    }
 
     if (_state.entry.workingUrl.query_params.length > 0) {
-      query +=
+      url +=
         "?" +
         _state.entry
           .workingUrl!!.query_params.map((p) => `${p[0]}=${p[1]}`)
           .join("&");
     } else if (_state.entry.workingUrl!!.has_query) {
-      query += "?";
+      url += "?";
     }
 
-    return `${_state.entry.workingUrl!!.scheme}://${_state.entry.workingUrl!!.host}${path}${query}`;
+    console.debug("url constructed", url);
+
+    return url;
   }
 
   function responseContent() {

+ 8 - 13
src/lib/state.svelte.ts

@@ -267,7 +267,10 @@ export async function updateEnvironment() {
 }
 
 export async function sendRequest(): Promise<any> {
-  const res = await invoke("send_request", { reqId: state.entry!!.id });
+  const res = await invoke("send_request", {
+    reqId: state.entry!!.id,
+    envId: state.environment?.id,
+  });
 
   console.debug(res);
 
@@ -307,20 +310,13 @@ export async function parseUrl(url: string) {
   });
 }
 
-export async function updateUrl(u: string): Promise<RequestUrl> {
-  console.debug("updating", $state.snapshot(state.entry));
-
-  const pathParams: Record<string, string> = {};
-
-  for (const path of state.entry.path) {
-    pathParams[path.name] = path.value;
-  }
-
+export async function updateUrl(u: string, usePathParams) {
+  console.log(u);
   const [url, params] = await invoke<any[]>("update_url", {
     entryId: state.entry!!.id,
-    envId: state.environment?.id,
+    usePathParams,
     url: u,
-    pathParams,
+    pathParams: state.entry.path,
   });
 
   state.entry!!.url = u;
@@ -330,7 +326,6 @@ export async function updateUrl(u: string): Promise<RequestUrl> {
   expandUrl();
 
   console.debug("updated", $state.snapshot(state.entry));
-  return url;
 }
 
 export async function expandUrl() {