Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide Path, like old url crate #14

Closed
seanmonstar opened this issue Aug 26, 2014 · 16 comments
Closed

Provide Path, like old url crate #14

seanmonstar opened this issue Aug 26, 2014 · 16 comments

Comments

@seanmonstar
Copy link
Contributor

Defining a web server would likely be defining a bunch of paths, and it'd be nice to be able to parse them to ensure they are correct, as well as being able to modify the Path, such as path.join(otherPath), path.add_query_param(), etc.

http://doc.rust-lang.org/url/struct.Path.html

@SimonSapin
Copy link
Member

In what context would this be use? Should it include the query string? The fragment identifier? Note that HTTP’s origin-form (the path as typically found in the start line of a request) includes a query but no fragment.

What would be the semantics of path.join(otherPath)? Should it distinguish absolute paths like /foo/bar from relative paths like foo/bar?

What is add_query_param? Note that the url::form_urlencoded module already provides conversions between strings in the application/x-www-form-urlencoded format and vectors of key/value pairs. I’m not convinced that adding a map-like API belongs in rust-url.

In general, I prefer having simple features that can be composed and built on top of to achieved various things, rather than a bunch of elaborate but narrow-purpose features.

@Valve
Copy link

Valve commented Aug 27, 2014

What would be the semantics of path.join(otherPath)? Should it distinguish absolute paths like /foo/bar from relative paths like foo/bar?

My case:
I'm building an API client and wondering what would the idiomatic way of handling multiple paths be.
For example, I have an api constructor, which accepts a base URL.

let api = API::new(Url.parse("https://service.com/api/v1/").unwrap());

Inside I have multiple operations (users, posts, tags, topics etc), each operation can have multiple sub-operations (create/update/read/update_all/delete_all), sub operations are not RESTful, so sub-paths are
required.

So inside my api instance I'll need to join 2 paths all the time,

 baseUrl.join(updatePostsPath)
baseUrl.join(readTagsPath)
...
...

2nd case:

Once I have the correct path (i.e. /api/v1/users/delete_all), I will want to build a query string.
For example I may need to have /api/v1/users/delete_all?q=city:Dallas

How should I handle this with current design of rust-url?

To generalise my use cases:

Once I have a base URL (url with host,port and path), I will need to construct other URLs that start with base url, but append sub-paths and modify query string

@SimonSapin
Copy link
Member

@Valve, you can do this by parsing with a base URL:

let api_base = Url::parse("https://service.com/api/v1/").unwrap();
let delete = UrlParser::new().base_url(&api_base).parse("users/delete_all").unwrap();
assert!(delete.to_string() == "https://service.com/api/v1/users/delete_all".to_string());
let dallas = UrlParser::new().base_url(&api_base).parse("?q=city:Dallas").unwrap();
assert!(dallas.to_string() == "https://service.com/api/v1/users/delete_all?q=city:Dallas".to_string());

Alternatively, for the query string, you can set key/value pairs. This will deal with percent-encoding if your string contains & or non-ASCII characters.

let mut dallas2 = delete.clone();
dallas2.set_query_from_pairs([("q", "city:Dallas")].iter())
assert!(dallas2 == dallas);

(I haven’t tried the code above, so maybe it doesn’t compile. But you get the idea.)

But this is all with full URLs. This issues (if I understand correctly) is about manipulating paths without the rest of the URL.

@Valve
Copy link

Valve commented Aug 27, 2014

@SimonSapin thanks for the update, this is roughly what I've been doing.
Regarding the paths without baser URLs, I think the OP mostly had web server scenarios in mind. I never had them developing a client api library

@seanmonstar
Copy link
Contributor Author

Yes, looking at some of the web frameworks (conduit, iron, nicket), I see this code a lot app.get("/foo/bar", handler). Seems like a candidate for that to be a Path instead of &str. Likewise, since this frameworks are following the pattern in express.js, I could imagine being able to "mount" sub-apps at a path. Something like mainApp.mount("/app", app) from above, which would mean the route was really at /app/foo/bar.

For an HTTP library, I'd rather the Request struct have a Path instead of a String for it's uri:

struct Request {
    uri: RequestUri,
    // ...
}

enum RequestUri {
    Star,
    AbsoluteUri(Url),
    AbsolutePath(Path), // <-- Path instead of String
    Authority(Authority),
}

@SimonSapin
Copy link
Member

It’s still not clear to me: what functionality do you think Path should have that Vec<String> does not already have?

@seanmonstar
Copy link
Contributor Author

  1. its a better type than Vec<String>, as it's more descriptive. If it doesn't make sense in this crate, I'll just make a wrapper struct pub struct Path(String) for my own sanity.
  2. On a server, you tend to deal with pathes instead of complete urls. So, join, normalize, relativeTo, or whatever could be useful.
let route = Path::new("/app/foo/bar"); // pull off req.url?
let js = route.join("../foo.js");
let css = route.join("./bar.css");

@SimonSapin
Copy link
Member

Could you explain the semantics of join, normalize, and relativeTo? What do they do?

@SimonSapin
Copy link
Member

In particular, for join, should "absolute" paths (starting with /) be treated differently from relative ones?

@seanmonstar
Copy link
Contributor Author

I'm just making up functionality that sounds like one might expect a Path to do. If these methods existed, here's my expected results:

  • join: I'd probably expect that join("/foo", "/bar") would notice /bar is absolute, and leave it alone. Another option could be that join only accepts RelativePaths.
  • normalize: I'd expect Path::new("/app/foo/bar/../baz/./img.png").normalize() to create /app/foo/baz/img.png
  • relativeTo: Path::new("/foo/bar/baz").relativeTo(Path::new("/foo/moo/meow.js")) to provide ../../moo/meow.js. (Again, perhaps as a RelativePath?)

@seanmonstar
Copy link
Contributor Author

Likewise, to deal with multiple absolute Paths, perhaps path.append("/bar") would give /foo/bar.

@SimonSapin
Copy link
Member

I'm just making up functionality that sounds like one might expect a Path to do.

It sounds like you don’t need any of this yourself. Do you? I’d rather wait until someone with a use case asks for something specific than speculate.

@seanmonstar
Copy link
Contributor Author

True, the specific functionality I don't need myself. I'm working on an http crate, instead of waiting on Teepee, so I would like to be able to set request.uri = AbsolutePath(Path(someStr)).

@SimonSapin
Copy link
Member

I’ve added parse_path() which, like the relevant part of the "main" URL parser, normalizes . and .. components. It returns (Vec<String>, Option<String>, Option<String>) for the path, query string, and fragment identifier.

Web browsers string the fragment identifier when sending an HTTP request, but a misbehaved client could still send it. After some quick testing with netcat against a few servers, it appears that servers typically parse the fragment identifier (rather than let it be part of the path or query string) but then ignore it. So I’d recommend something like this for an HTTP server:

struct PathRequestTarget {
     path: Vec<String>,
     query: Option<String>
}

let (path, query, _fragment) = url::parse_path(input).unwrap();
PathRequestTarget { path: path, query: query }

@SimonSapin
Copy link
Member

@seanmonstar By the way, is your HTTP library only for the server-side, or also clients? Is it available online yet?

@seanmonstar
Copy link
Contributor Author

I'd like for it to do both. I'm aiming at it being a valid options for extern crate http. Server is getting close, and then client. I planned to push it to github once it actually worked (in the simplest sense).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants