chore: add method placeholder
221c2989
5 file(s) · +85 −10
| 188 | 188 | ||
| 189 | 189 | [[package]] |
|
| 190 | 190 | name = "cielago" |
|
| 191 | - | version = "0.1.0" |
|
| 191 | + | version = "0.1.1" |
|
| 192 | 192 | dependencies = [ |
|
| 193 | 193 | "anyhow", |
|
| 194 | 194 | "clap", |
| 21 | 21 | ||
| 22 | 22 | use crate::http::{HttpResponse, OAuthToken, SendOutcome, send_with_auth, split_url_input}; |
|
| 23 | 23 | use crate::model::{ |
|
| 24 | - | AuthKind, Collection, KeyValueRow, LabelMode, OAuthConfig, SavedRequest, variables_map, |
|
| 24 | + | AuthKind, Collection, KeyValueRow, LabelMode, Method, OAuthConfig, SavedRequest, variables_map, |
|
| 25 | 25 | }; |
|
| 26 | 26 | use crate::store::{self, AppConfig}; |
|
| 27 | 27 | use crate::{input, ui}; |
|
| 753 | 753 | .selected_request() |
|
| 754 | 754 | .map(|r| r.name.clone()) |
|
| 755 | 755 | .unwrap_or_default(), |
|
| 756 | - | // Prefill the path only, not `base_url() + path`: re-serializing |
|
| 756 | + | // Prefill `method path`, not `base_url() + path`: re-serializing |
|
| 757 | 757 | // the full URL would rebuild the query from the table and lose each |
|
| 758 | 758 | // row's `enabled` flag. The origin is visible in the URL bar anyway. |
|
| 759 | - | // A bare `/` (what `SavedRequest::blank` gives a new request) is |
|
| 760 | - | // dropped, so pasting a URL into a fresh request isn't prefixed by it. |
|
| 759 | + | // The leading verb doubles as the method editor — `apply_url_input` |
|
| 760 | + | // parses it back — and shows the current/default method up front. A |
|
| 761 | + | // bare `/` (what `SavedRequest::blank` gives a new request) is |
|
| 762 | + | // dropped, so a fresh request prefills as `GET ` awaiting a route. |
|
| 761 | 763 | EditTarget::Url => self |
|
| 762 | 764 | .selected_request() |
|
| 763 | - | .map(|r| r.path.clone()) |
|
| 764 | - | .filter(|p| p != "/") |
|
| 765 | + | .map(|r| { |
|
| 766 | + | let path = if r.path == "/" { "" } else { &r.path }; |
|
| 767 | + | format!("{} {path}", r.method) |
|
| 768 | + | }) |
|
| 765 | 769 | .unwrap_or_default(), |
|
| 766 | 770 | EditTarget::NewRequest | EditTarget::EnvNew => String::new(), |
|
| 767 | 771 | EditTarget::AuthField(i) => self.auth_field_value(i), |
|
| 858 | 862 | self.status = "No request selected".into(); |
|
| 859 | 863 | return; |
|
| 860 | 864 | }; |
|
| 865 | + | // A leading HTTP verb sets the method and is stripped before URL |
|
| 866 | + | // parsing, so `POST /pets` fixes method and path in one edit — the same |
|
| 867 | + | // single field a new request chains into after naming. A bare path |
|
| 868 | + | // leaves the current method untouched. The split is on the first space |
|
| 869 | + | // only, and the token must parse as a method, so a pathless `delete` |
|
| 870 | + | // typed alone stays a path, not a verb. |
|
| 871 | + | let mut set_method: Option<Method> = None; |
|
| 872 | + | let input = match input.trim().split_once(char::is_whitespace) { |
|
| 873 | + | Some((head, rest)) if !rest.trim().is_empty() => match Method::parse(head) { |
|
| 874 | + | Some(m) => { |
|
| 875 | + | set_method = Some(m); |
|
| 876 | + | rest.trim() |
|
| 877 | + | } |
|
| 878 | + | None => input, |
|
| 879 | + | }, |
|
| 880 | + | _ => input, |
|
| 881 | + | }; |
|
| 861 | 882 | // Something that names a scheme but isn't http(s) is a typo, not a |
|
| 862 | 883 | // relative path — say so rather than filing it under `path`. The |
|
| 863 | 884 | // scheme-shape check matters: it keeps a stray `/api/https://…` out of |
|
| 899 | 920 | } |
|
| 900 | 921 | ||
| 901 | 922 | let req = &mut self.collection.requests[i]; |
|
| 923 | + | if let Some(m) = set_method |
|
| 924 | + | && req.method != m |
|
| 925 | + | { |
|
| 926 | + | req.method = m; |
|
| 927 | + | notes.push(format!("method → {m}")); |
|
| 928 | + | } |
|
| 902 | 929 | req.path = parts.path; |
|
| 903 | 930 | if let Some(query) = parts.query { |
|
| 904 | 931 | notes.push(format!("{} query param(s)", query.len())); |
|
| 510 | 510 | }, |
|
| 511 | 511 | EditTarget::Rename => "rename", |
|
| 512 | 512 | EditTarget::NewRequest => "new request", |
|
| 513 | - | EditTarget::Url => "url", |
|
| 513 | + | EditTarget::Url => "url (verb path)", |
|
| 514 | 514 | EditTarget::EnvNew => "server url", |
|
| 515 | 515 | EditTarget::AuthField(_) => "auth", |
|
| 516 | 516 | }; |
|
| 595 | 595 | Line::raw(" space enable/disable row"), |
|
| 596 | 596 | Line::raw(" d delete row · m cycle method · r rename"), |
|
| 597 | 597 | Line::raw(" p edit URL / path (paste a full URL to set"), |
|
| 598 | - | Line::raw(" the server; ?query fills the Params tab)"), |
|
| 598 | + | Line::raw(" the server; ?query fills the Params tab;"), |
|
| 599 | + | Line::raw(" a leading verb sets the method, e.g."), |
|
| 600 | + | Line::raw(" `post /pets`)"), |
|
| 599 | 601 | Line::raw(""), |
|
| 600 | 602 | Line::styled("Body tab", Style::default().add_modifier(Modifier::BOLD)), |
|
| 601 | 603 | Line::raw(" j/k, d/u scroll · g/G top/bottom"), |
|
| 59 | 59 | assert!(uuid::Uuid::parse_str(id).is_ok(), "got {id}"); |
|
| 60 | 60 | } |
|
| 61 | 61 | ||
| 62 | + | #[test] |
|
| 63 | + | fn url_input_leading_verb_sets_method() { |
|
| 64 | + | let mut app = app_with("http://example.com".into()); |
|
| 65 | + | app.select_request(0); |
|
| 66 | + | ||
| 67 | + | // A leading verb sets the method and is stripped from the path. |
|
| 68 | + | app.apply_url_input("post /pets"); |
|
| 69 | + | assert_eq!(app.collection.requests[0].method, Method::Post); |
|
| 70 | + | assert_eq!(app.collection.requests[0].path, "/pets"); |
|
| 71 | + | ||
| 72 | + | // A bare path leaves the method untouched. |
|
| 73 | + | app.apply_url_input("/pets/1"); |
|
| 74 | + | assert_eq!(app.collection.requests[0].method, Method::Post); |
|
| 75 | + | assert_eq!(app.collection.requests[0].path, "/pets/1"); |
|
| 76 | + | ||
| 77 | + | // A lone verb with no remainder is a path, not a method. |
|
| 78 | + | app.apply_url_input("delete"); |
|
| 79 | + | assert_eq!(app.collection.requests[0].method, Method::Post); |
|
| 80 | + | assert_eq!(app.collection.requests[0].path, "/delete"); |
|
| 81 | + | } |
|
| 82 | + | ||
| 62 | 83 | #[tokio::test] |
|
| 63 | 84 | async fn send_with_oauth_fetches_and_caches_token() { |
|
| 64 | 85 | let server = MockServer::start().await; |
|
| 124 | 145 | assert!(!app.sending); |
|
| 125 | 146 | assert!(app.status.contains("No server configured")); |
|
| 126 | 147 | } |
|
| 148 | + | ||
| 149 | + | // After naming a new request, the flow chains into the URL edit prefilled |
|
| 150 | + | // with the default method so `GET ` is shown awaiting a route. |
|
| 151 | + | #[test] |
|
| 152 | + | fn new_request_chains_to_url_prefilled_with_method() { |
|
| 153 | + | let mut app = app_with("http://example.com".into()); |
|
| 154 | + | app.start_edit(cielago::app::EditTarget::NewRequest); |
|
| 155 | + | app.input.set("make thing"); |
|
| 156 | + | app.commit_edit(); |
|
| 157 | + | assert_eq!(app.editing, Some(cielago::app::EditTarget::Url)); |
|
| 158 | + | assert_eq!(app.input.buf, "GET "); |
|
| 159 | + | } |
|
| 215 | 215 | let mut app = test_app(); |
|
| 216 | 216 | app.start_edit(cielago::app::EditTarget::Url); |
|
| 217 | 217 | let buf = render(&mut app, 100, 40); |
|
| 218 | - | assert!(screen(&buf).contains("url> /pets")); |
|
| 218 | + | assert!(screen(&buf).contains("url (verb path)> POST /pets")); |
|
| 219 | 219 | } |
|
| 220 | 220 | ||
| 221 | 221 | #[test] |
|
| 232 | 232 | assert!(bottom.contains(":new")); |
|
| 233 | 233 | assert!(bottom.contains(":open")); |
|
| 234 | 234 | } |
|
| 235 | + | ||
| 236 | + | #[test] |
|
| 237 | + | fn tmp_new_request_render_shows_get() { |
|
| 238 | + | let mut app = test_app(); |
|
| 239 | + | app.start_edit(cielago::app::EditTarget::NewRequest); |
|
| 240 | + | app.input.set("make thing"); |
|
| 241 | + | app.commit_edit(); |
|
| 242 | + | let buf = render(&mut app, 100, 40); |
|
| 243 | + | let s = screen(&buf); |
|
| 244 | + | let line = s.lines().find(|l| l.contains("url (verb path)")).unwrap_or("<none>"); |
|
| 245 | + | println!("PROMPT LINE: {:?}", line); |
|
| 246 | + | assert!(s.contains("url (verb path)> GET"), "screen missing GET prefill"); |
|
| 247 | + | } |
|