perf: Don't parse for userinfo if not necessary
If the URI doesn't contain @ then there can't be a userinfo so completely ignore this part of parsing to improve performance
This commit is contained in:
@@ -10,5 +10,5 @@
|
|||||||
|
|
||||||
## v2.0.1
|
## v2.0.1
|
||||||
|
|
||||||
- Improved parsing performance slightly and reduced memory usage up to 50%
|
- Improved parsing performance significantly and reduced memory usage up to 50%
|
||||||
- Significantly improved IPV4 parsing performance
|
- Significantly improved IPV4 parsing performance
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import gleam/bool
|
||||||
import gleam/int
|
import gleam/int
|
||||||
import gleam/list
|
import gleam/list
|
||||||
import gleam/option.{None, Some}
|
import gleam/option.{type Option, None, Some}
|
||||||
import gleam/result
|
import gleam/result
|
||||||
import gleam/string
|
import gleam/string
|
||||||
import gleam/uri.{type Uri, Uri, empty}
|
import gleam/uri.{type Uri, Uri, empty}
|
||||||
@@ -19,9 +20,9 @@ pub fn parse(uri: String) -> Result(Uri, Nil) {
|
|||||||
Ok(#(scheme, rest)) -> {
|
Ok(#(scheme, rest)) -> {
|
||||||
use #(rel_part, rest) <- result.try(parse_hier_part(rest))
|
use #(rel_part, rest) <- result.try(parse_hier_part(rest))
|
||||||
|
|
||||||
use #(query, rest) <- result.try(parse_query(rest))
|
let #(query, rest) = parse_query(rest)
|
||||||
|
|
||||||
use #(fragment, rest) <- result.try(parse_fragment(rest))
|
let #(fragment, rest) = parse_fragment(rest)
|
||||||
|
|
||||||
case rest {
|
case rest {
|
||||||
"" -> Ok(combine_uris([scheme, rel_part, query, fragment]))
|
"" -> Ok(combine_uris([scheme, rel_part, query, fragment]))
|
||||||
@@ -31,9 +32,9 @@ pub fn parse(uri: String) -> Result(Uri, Nil) {
|
|||||||
Error(_) -> {
|
Error(_) -> {
|
||||||
use #(rel_part, rest) <- result.try(parse_relative_part(uri))
|
use #(rel_part, rest) <- result.try(parse_relative_part(uri))
|
||||||
|
|
||||||
use #(query, rest) <- result.try(parse_query(rest))
|
let #(query, rest) = parse_query(rest)
|
||||||
|
|
||||||
use #(fragment, rest) <- result.try(parse_fragment(rest))
|
let #(fragment, rest) = parse_fragment(rest)
|
||||||
|
|
||||||
case rest {
|
case rest {
|
||||||
"" -> Ok(combine_uris([rel_part, query, fragment]))
|
"" -> Ok(combine_uris([rel_part, query, fragment]))
|
||||||
@@ -60,26 +61,26 @@ fn parse_hier_part(str: String) -> Result(#(Uri, String), Nil) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// query = *( pchar / "/" / "?" )
|
// query = *( pchar / "/" / "?" )
|
||||||
fn parse_query(str: String) -> Result(#(Uri, String), Nil) {
|
fn parse_query(str: String) -> #(Uri, String) {
|
||||||
case str {
|
case str {
|
||||||
"?" <> rest -> {
|
"?" <> rest -> {
|
||||||
let #(query, rest) =
|
let #(query, rest) =
|
||||||
parse_optional(rest, parse_multiple(_, parse_query_fragment))
|
parse_optional(rest, parse_multiple(_, parse_query_fragment))
|
||||||
Ok(#(Uri(..empty, query: Some(query)), rest))
|
#(Uri(..empty, query: Some(query)), rest)
|
||||||
}
|
}
|
||||||
_ -> Ok(#(empty, str))
|
_ -> #(empty, str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fragment = *( pchar / "/" / "?" )
|
// fragment = *( pchar / "/" / "?" )
|
||||||
fn parse_fragment(str: String) -> Result(#(Uri, String), Nil) {
|
fn parse_fragment(str: String) -> #(Uri, String) {
|
||||||
case str {
|
case str {
|
||||||
"#" <> rest -> {
|
"#" <> rest -> {
|
||||||
let #(fragment, rest) =
|
let #(fragment, rest) =
|
||||||
parse_optional(rest, parse_multiple(_, parse_query_fragment))
|
parse_optional(rest, parse_multiple(_, parse_query_fragment))
|
||||||
Ok(#(Uri(..empty, fragment: Some(fragment)), rest))
|
#(Uri(..empty, fragment: Some(fragment)), rest)
|
||||||
}
|
}
|
||||||
_ -> Ok(#(empty, str))
|
_ -> #(empty, str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,50 +168,43 @@ fn parse_authority(str: String) -> Result(#(Uri, String), Nil) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_authority_part(str: String) -> Result(#(Uri, String), Nil) {
|
fn parse_authority_part(str: String) -> Result(#(Uri, String), Nil) {
|
||||||
let #(ui, rest) = case parse_userinfo(str, "") {
|
let #(userinfo, rest) = parse_userinfo(str, "")
|
||||||
Ok(#(ui, rest)) -> #(Some(ui), rest)
|
|
||||||
Error(_) -> #(None, str)
|
|
||||||
}
|
|
||||||
|
|
||||||
use #(host, rest) <- result.try(parse_host(rest))
|
use #(host, rest) <- result.try(parse_host(rest))
|
||||||
|
|
||||||
let #(port, rest) = case parse_port(rest) {
|
let #(port, rest) = parse_port(rest)
|
||||||
Ok(#("", rest)) -> #(None, rest)
|
|
||||||
Error(_) -> #(None, rest)
|
|
||||||
Ok(#(port, rest)) -> {
|
|
||||||
#(int.parse(port) |> option.from_result, rest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let #(path, rest) = parse_path_abempty(rest)
|
let #(path, rest) = parse_path_abempty(rest)
|
||||||
|
|
||||||
Ok(#(Uri(None, ui, Some(host), port, path, None, None), rest))
|
Ok(#(Uri(None, userinfo, Some(host), port, path, None, None), rest))
|
||||||
}
|
}
|
||||||
|
|
||||||
// userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
|
// userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
|
||||||
fn parse_userinfo(
|
fn parse_userinfo(str: String, userinfo: String) -> #(Option(String), String) {
|
||||||
str: String,
|
use <- bool.guard(when: !string.contains(str, "@"), return: #(None, str))
|
||||||
userinfo: String,
|
|
||||||
) -> Result(#(String, String), Nil) {
|
|
||||||
case str {
|
case str {
|
||||||
"@" <> rest -> Ok(#(userinfo, rest))
|
"@" <> rest -> #(Some(userinfo), rest)
|
||||||
"" -> Error(Nil)
|
"" -> #(None, userinfo <> str)
|
||||||
_ -> {
|
_ -> {
|
||||||
use #(part, rest) <- result.try(try_parsers(
|
case
|
||||||
[
|
try_parsers(
|
||||||
parse_unreserved,
|
[
|
||||||
parse_pct_encoded,
|
parse_unreserved,
|
||||||
parse_sub_delim,
|
parse_pct_encoded,
|
||||||
fn(str: String) {
|
parse_sub_delim,
|
||||||
case str {
|
fn(str: String) {
|
||||||
":" as l <> rest -> Ok(#(l, rest))
|
case str {
|
||||||
_ -> Error(Nil)
|
":" as l <> rest -> Ok(#(l, rest))
|
||||||
}
|
_ -> Error(Nil)
|
||||||
},
|
}
|
||||||
],
|
},
|
||||||
str,
|
],
|
||||||
))
|
str,
|
||||||
parse_userinfo(rest, userinfo <> part)
|
)
|
||||||
|
{
|
||||||
|
Ok(#(part, rest)) -> parse_userinfo(rest, userinfo <> part)
|
||||||
|
Error(_) -> #(None, userinfo <> str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,12 +215,13 @@ fn parse_host(str: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// port = *DIGIT
|
// port = *DIGIT
|
||||||
fn parse_port(str: String) {
|
fn parse_port(str: String) -> #(Option(Int), String) {
|
||||||
case str {
|
case str {
|
||||||
":" <> rest -> {
|
":" <> rest -> {
|
||||||
Ok(parse_digits(rest, ""))
|
let #(port, rest) = parse_digits(rest, "")
|
||||||
|
#(int.parse(port) |> option.from_result, rest)
|
||||||
}
|
}
|
||||||
_ -> Error(Nil)
|
_ -> #(None, str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user