URL
Create, manipulate and generate URLs.
module main
import khalyomede.url { Url }
fn main() {
link := Url.parse("https://example.com/contact?theme=dark#twitter")!
assert link.host == "example.com"
assert link.path == "/contact"
}
Summary
About
I created this library to be able to parse URLs so that my router can react to the correct routes.
It can be used for a wide range of use cases, such as generating valid URLs given a set of basic information (like the scheme and the host) using a chain of methods.
Features
- Can parse an URL
- Can generate an URL
- Can chain methods to modify an URL
- Supports fragments, queries and ports
- Normalize URLs parts (uppercases scheme/host are always returned lowercase)
Installation
### Using V installer
In your terminal, run this command:
v install khalyomede.url
Examples
- Parsing
- Generating
- Retrieving
- Comparing
- Casting
Parse an URL
module main
import khalyomede.url { Url }
fn main() {
link := Url.parse("https://example.com")!
}
URLs are automatically decoded when parsing them.
module main
import khalyomede.url { Url }
fn main() {
link := Url.parse("https://example.com/contact?subject=Sales%20order")
assert link.query["subject"] or { "" } == "Sales order"
}
Note that when the URL is not valid (meaning it does not have at least a scheme and a domain), you should handle this Error.
This means
relatives URL
module main
import khalyomede.url { Url }
fn main() {
link := Url.parse("example.com/contact-us") or {
assert err.str() == "The scheme is missing."
panic(err)
}
}
You can perform fine grained error handling if you prefer.
module main
import khalyomede.url { Url, MissingScheme, MissingDomain, TraversingAboveRoot, BadlyEncodedPath, BadlyEncodedFragment, BadlyEncodedQuery }
fn main() {
link := Url.parse("contact-us") or {
error_message := match err {
MissingScheme { "The scheme is missing" }
MissingDomain { "The domain is missing" }
TraversingAboveRoot { "Trying to go above root" }
BadlyEncodedPath { "The path is not well encoded" }
BadlyEncodedFragment { "The fragment is not well encoded" }
BadlyEncodedQuery { "The query is not well encoded" }
}
panic(error_message + " (${err}).")
}
}
When an URL contains relative accessors (".", ".."), the absolute URL is resolved.
module main
import khalyomede.url { Url }
fn main() {
link := Url.parse("https://example.com/user/1/../create")!
assert link.str() == "https://example.com/user/create"
}
Doubles slashes are automatically simplified.
module main
import khalyomede.url { Url }
fn main() {
link := Url.parse("https://example.com/user//create//")!
assert link.str() == "https://example.com/user/create/"
}
This library automatically strips any ending slashes, for consistancy.
module main
import khalyomede.url { Url }
fn main() {
link := Url.parse("https://example.com/settings/")
assert link.str() == "https://example.com/settings"
}
Lastly, you can get the originally parsed url.
module main
fn main() {
link := Url.parse("HTTPS://example.com");
assert link.str() == "https://example.com"
assert link.original == "HTTPS://example.com"
}
Create a new URL
You need to specify at least the scheme and domain when creating a new URL.
module main
import khalyomede.url { Url, Https }
fn main() {
link := Url{
scheme: Https{}
domain: "example.com"
}
}
You are free to specify more information if want.
module main
import khalyomede.url { Url, Https }
fn main() {
link := Url{
scheme: Https{}
domain: "example.com"
segments: ["contact-us"]
port: 8080 // port is a ?u16
query: {
"lang": "fr"
"theme": "dark"
}
fragment: "whatsapp"
}
assert link.str() == "https://example.com/contact-us?lang=fr&theme=dark#whatsapp"
}
The
Url
module main
import khalyomede.url { Url, Https }
fn main() {
mut link := Url{
scheme: Https{}
domain: "example.com"
}
link = Url{
...link
query: {
lang: "es"
}
}
assert link.query["lang"] or { "en" } == "es"
}
Get the scheme from an URL
You get a
Scheme
module main
import khalyomede.url { Url, Https }
fn main() {
link := Url.parse("https://example.com")
assert link.scheme == Https{}
assert link.scheme.str() == "https"
}
Note that when the scheme is not listed among the common ones (
Http{}
Https{}
Other{}
module main
import khalyomede.url { Url, Other }
fn main() {
link := Url.parse("facebook://user/johndoe")
assert link.scheme == Other{ value: "facebook" }
assert link.scheme.str() == "facebook"
}
You can match over all different schemes if you need it:
module main
import khalyomede.url { Url, Http, Https, Ftp, Ftps, Ssh, Git, File, Other }
fn main() {
link := Url.parse("facebook://user/johndoe")
message := match link.scheme {
Http { "Url is HTTP" }
Https { "Url is HTTPS" }
Ftp { "Url is FTP" }
Ftps { "Url is FTPS" }
Ssh { "Url is SSH" }
Git { "Url is Git" }
File { "Url is file" }
Other { "Url is other (${link.scheme.value})" }
}
assert message == "Url is other (facebook)"
}
Get the port from an URL
module main
import khalyomede.url { Url }
fn main() {
link := Url.parse("http://localhost:1234");
assert link.port or { 80 } == 1234
}
Note that when there is no port, the value will be the one provided as a default when handling the Option value.
module main
import khalyomede.url { Url }
fn main() {
link := Url.parse("https://example.com");
assert link.port or { 80 } == 80
}
Get the host from an URL
module main
import khalyomede.url { Url }
fn main() {
link := Url.parse("https://example.com")
assert lunk.host == "example.com"
}
Get the path from an URL
module import
import khalyomede.url { Url }
fn main() {
link := Url.parse("https://example.com/settings/notifications");
assert link.path == "/settings/notifications"
assert link.segments == ["settings", "notifications"]
}
Note that query strings and fragments are ignored.
module import
import khalyomede.url { Url }
fn main() {
link := Url.parse("https://example.com/contact?lang=fr#slack");
assert link.path == "/contact"
}
Also, when the path is empty (root of the domain), the returned path will be "/". Keep in mind the path always starts with a leading slash.
module main
import khalyomede.url { Url }
fn main() {
link := Url.parse("https://example.com");
assert link.path == "/"
assert link.segments == []
}
Get a query string by key from an URL
module main
import khalyomede.url { Url }
fn main() {
link := Url.parse("https://example.com?lang=fr");
assert link.query["lang"] or { "en" } == "fr"
}
Note that if there is duplicate keys, only the last value will be preserved during parsing.
module main
import khalyomede.url { Url }
fn main() {
link := Url.parse("https://example.com?lang=fr&lang=en");
assert link.query["lang"] or { "en" } == "en"
}
This library does not support multi-level queries (for example, "filter[name]=john") since it has not been standardized.
Instead, we advise you use JSON for complex/multi-level values.
module main
import khalyomede.url { Url }
import json
struct Filter {
name ?string
}
fn main() {
link := Url.parse('https://example.com/users?filter={"name":"john"}')
raw_filter := link.query["filter"] or { '{}' }
filter := json.decode(Filter, raw_filter)!
assert filter.name or { "" } == "john"
}
Get the raw original query string
module main
import khalyomede.url { Url }
import net.urllib { query_unescape }
fn main() {
link := Url.parse("https://example.com?search=V%20lang")
assert link.raw_query == "search=V%20lang"
assert query_unescape(link.raw_query) == "search=V lang"
}
Get the fragment from an URL
module main
fn main() {
link := Url.parse("https://example.com/contact#whatsapp");
assert link.fragment == "whatsapp"
}
Compare two URLs are equivalent
Just parse your two URLs and cast them to string then perform equality.
module main
import khalyomede.url { Url }
fn main() {
first_url := Url.parse("HTTPS://example.com")
second_url := Url.parse("https://example.com/")
assert first_url.str() == second_url.str()
}
### Compare two URLs are equivalent
Comparing two
url
In one hand, consider cases when the scheme is one time in uppercase, another time in lowercase.
In another hand, think about scheme default ports. For example, you may have one time an URL that specify the port (
https://example.com:443/contact-us
For handling these edge cases, you can use this method.
module main
import khalyomede.url { Url }
fn main() {
first_link := Url.parse("https://example.com:443");
second_link := Url.parse("HTTPS://example.com");
assert first_link.is_equivalent_to(second_link)
}
Rendering your Url as a string
URLs are automatically encoded when casted to string.
module main
import khalyomede.url { Url, Https }
fn main() {
link := Url{
scheme: Https{}
domain: "example.com"
path: "contact"
query: {
"subject": "Sales order"
}
}
assert link.str() == "https://example.com/contact?subject=Sales+order"
}