aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/gleam/uri.gleam128
-rw-r--r--test/gleam/uri_test.gleam77
2 files changed, 199 insertions, 6 deletions
diff --git a/src/gleam/uri.gleam b/src/gleam/uri.gleam
index 9224adf..8f8d754 100644
--- a/src/gleam/uri.gleam
+++ b/src/gleam/uri.gleam
@@ -110,7 +110,7 @@ pub fn query_to_string(query: List(tuple(String, String))) -> String {
|> result.unwrap("")
}
-fn do_path_segments(input, accumulator) {
+fn do_remove_dot_segments(input, accumulator) {
case input {
[] -> list.reverse(accumulator)
[segment, ..rest] -> {
@@ -121,18 +121,22 @@ fn do_path_segments(input, accumulator) {
"..", [_, ..accumulator] -> accumulator
segment, accumulator -> [segment, ..accumulator]
}
- do_path_segments(rest, accumulator)
+ do_remove_dot_segments(rest, accumulator)
}
}
}
+fn remove_dot_segments(input) {
+ do_remove_dot_segments(input, [])
+}
+
/// Split the path section of a URI into it's constituent segments.
///
/// Removes empty segments and resolves dot-segments as specified in
/// [section 5.2](https://www.ietf.org/rfc/rfc3986.html#section-5.2) of the RFC.
///
pub fn path_segments(path) {
- do_path_segments(string.split(path, "/"), [])
+ remove_dot_segments(string.split(path, "/"))
}
external fn erl_to_string(Map(UriKey, Dynamic)) -> Dynamic =
@@ -165,12 +169,12 @@ pub fn to_string(uri: Uri) -> String {
|> result.unwrap("")
}
-/// Fetch the origin of a url
+/// Fetch the origin of a uri
///
-/// Return the origin of a url as defined in
+/// Return the origin of a uri as defined in
/// https://tools.ietf.org/html/rfc6454
///
-/// The supported url schemes are `http` and `https`
+/// The supported uri schemes are `http` and `https`
/// Urls without a scheme will return Error
pub fn origin(uri: Uri) -> Result(String, Nil) {
let Uri(scheme: scheme, host: host, port: port, ..) = uri
@@ -182,3 +186,115 @@ pub fn origin(uri: Uri) -> Result(String, Nil) {
_ -> Error(Nil)
}
}
+
+fn option_or(first: Option(a), second: Option(a)) -> Option(a) {
+ case first {
+ Some(_) -> first
+ None -> second
+ }
+}
+
+fn drop_last(elements) {
+ let tuple(keep, _last) = list.split(elements, list.length(elements) - 1)
+ keep
+}
+
+fn join_segments(segments) {
+ case segments {
+ [] -> ""
+ _ -> string.append("/", string.join(segments, "/"))
+ }
+}
+
+/// Resolve a uri with respect to the given base uri
+///
+/// The base uri must be an absolute uri or this function will return an error.
+/// The algorithm for merging uris is described in [RFC 3986](https://tools.ietf.org/html/rfc3986#section-5.2)
+pub fn merge(base: Uri, relative: Uri) -> Result(Uri, Nil) {
+ case base {
+ Uri(scheme: Some(_), host: Some(_), ..) -> case relative {
+ Uri(scheme: Some(_), ..) -> {
+ let path = string.split(relative.path, "/")
+ |> remove_dot_segments()
+ |> join_segments()
+ let resolved = Uri(
+ relative.scheme,
+ None,
+ relative.host,
+ relative.port,
+ path,
+ relative.query,
+ relative.fragment,
+ )
+ Ok(resolved)
+ }
+ Uri(scheme: None, host: Some(_), ..) -> {
+ let path = string.split(relative.path, "/")
+ |> remove_dot_segments()
+ |> join_segments()
+ let resolved = Uri(
+ base.scheme,
+ None,
+ relative.host,
+ relative.port,
+ path,
+ relative.query,
+ relative.fragment,
+ )
+ Ok(resolved)
+ }
+ Uri(scheme: None, host: None, path: "", ..) -> {
+ let resolved = Uri(
+ base.scheme,
+ None,
+ base.host,
+ base.port,
+ base.path,
+ option_or(relative.query, base.query),
+ relative.fragment,
+ )
+ Ok(resolved)
+ }
+ Uri(
+ scheme: None,
+ host: None,
+ path: path,
+ ..,
+ ) -> case string.starts_with(path, "/") {
+ True -> {
+ let path = string.split(relative.path, "/")
+ |> remove_dot_segments()
+ |> join_segments()
+ let resolved = Uri(
+ base.scheme,
+ None,
+ base.host,
+ base.port,
+ path,
+ relative.query,
+ relative.fragment,
+ )
+ Ok(resolved)
+ }
+ False -> {
+ let path = string.split(base.path, "/")
+ |> drop_last()
+ |> list.append(string.split(relative.path, "/"))
+ |> remove_dot_segments()
+ |> join_segments()
+ let resolved = Uri(
+ base.scheme,
+ None,
+ base.host,
+ base.port,
+ path,
+ relative.query,
+ relative.fragment,
+ )
+ Ok(resolved)
+ }
+ }
+ }
+ _ -> Error(Nil)
+ }
+}
diff --git a/test/gleam/uri_test.gleam b/test/gleam/uri_test.gleam
index 9f102e0..3e638fd 100644
--- a/test/gleam/uri_test.gleam
+++ b/test/gleam/uri_test.gleam
@@ -131,3 +131,80 @@ pub fn origin_test() {
uri.origin(parsed)
|> should.equal(Error(Nil))
}
+
+pub fn merge_test() {
+ let Ok(a) = uri.parse("/relative")
+ let Ok(b) = uri.parse("")
+ uri.merge(a, b)
+ |> should.equal(Error(Nil))
+
+ let Ok(a) = uri.parse("http://google.com/foo")
+ let Ok(b) = uri.parse("http://example.com/baz")
+ uri.merge(a, b)
+ |> should.equal(uri.parse("http://example.com/baz"))
+
+ let Ok(a) = uri.parse("http://google.com/foo")
+ let Ok(b) = uri.parse("http://example.com/.././bar/../../baz")
+ uri.merge(a, b)
+ |> should.equal(uri.parse("http://example.com/baz"))
+
+ let Ok(a) = uri.parse("http://google.com/foo")
+ let Ok(b) = uri.parse("//example.com/baz")
+ uri.merge(a, b)
+ |> should.equal(uri.parse("http://example.com/baz"))
+
+ let Ok(a) = uri.parse("http://google.com/foo")
+ let Ok(b) = uri.parse("//example.com/.././bar/../../../baz")
+ uri.merge(a, b)
+ |> should.equal(uri.parse("http://example.com/baz"))
+
+ let Ok(a) = uri.parse("http://example.com/foo/bar")
+ let Ok(b) = uri.parse("/baz")
+ uri.merge(a, b)
+ |> should.equal(uri.parse("http://example.com/baz"))
+
+ let Ok(a) = uri.parse("http://example.com/foo/bar")
+ let Ok(b) = uri.parse("baz")
+ uri.merge(a, b)
+ |> should.equal(uri.parse("http://example.com/foo/baz"))
+
+ let Ok(a) = uri.parse("http://example.com/foo/")
+ let Ok(b) = uri.parse("baz")
+ uri.merge(a, b)
+ |> should.equal(uri.parse("http://example.com/foo/baz"))
+
+ let Ok(a) = uri.parse("http://example.com")
+ let Ok(b) = uri.parse("baz")
+ uri.merge(a, b)
+ |> should.equal(uri.parse("http://example.com/baz"))
+
+ let Ok(a) = uri.parse("http://example.com")
+ let Ok(b) = uri.parse("/.././bar/../../../baz")
+ uri.merge(a, b)
+ |> should.equal(uri.parse("http://example.com/baz"))
+
+ let Ok(a) = uri.parse("http://example.com/foo/bar")
+ let Ok(b) = uri.parse("")
+ uri.merge(a, b)
+ |> should.equal(uri.parse("http://example.com/foo/bar"))
+
+ let Ok(a) = uri.parse("http://example.com/foo/bar")
+ let Ok(b) = uri.parse("#fragment")
+ uri.merge(a, b)
+ |> should.equal(uri.parse("http://example.com/foo/bar#fragment"))
+
+ let Ok(a) = uri.parse("http://example.com/foo/bar")
+ let Ok(b) = uri.parse("?query")
+ uri.merge(a, b)
+ |> should.equal(uri.parse("http://example.com/foo/bar?query"))
+
+ let Ok(a) = uri.parse("http://example.com/foo/bar?query1")
+ let Ok(b) = uri.parse("?query2")
+ uri.merge(a, b)
+ |> should.equal(uri.parse("http://example.com/foo/bar?query2"))
+
+ let Ok(a) = uri.parse("http://example.com/foo/bar?query")
+ let Ok(b) = uri.parse("")
+ uri.merge(a, b)
+ |> should.equal(uri.parse("http://example.com/foo/bar?query"))
+}