uri

URI parser and builder for Moonbit

uri
parser
http
moon add kaashyapan/uri@0.1.2
Download zip
Version
0.1.2
License
Apache-2.0
Last updated
2 months ago
Downloads
19
README

#MoonBit URI Library

#Forked from BigOrangeQWQ/uri

A comprehensive URI parsing and manipulation library for the MoonBit programming language, implementing RFC 3986 specification.

#Overview

This library provides robust parsing, validation, and creation of Uniform Resource Identifiers (URIs). It supports all standard URI components including schemes, authorities, paths, queries, and fragments, with proper percent-encoding handling and comprehensive validation.

#Features

  • Complete RFC 3986 Implementation: Full compliance with URI specification
  • Parsing and Validation: Parse URI strings with comprehensive error reporting
  • Builder Pattern: Fluent API for constructing URIs programmatically
  • Authority Handling: Support for userinfo, hosts (IPv4, IPv6, domain names), and ports
  • Path Management: Support for all path types (absolute, relative, empty, etc.)
  • Query Parameter Support: Build and parse query strings with proper encoding
  • Relative Reference Resolution: Resolve relative URIs against base URIs
  • Percent Encoding: Proper encoding/decoding for URI components
  • Type Safety: Strong typing with comprehensive error reporting

#Installation

Add this package to your MoonBit project:

moon add username/uri

Then import it in your moon.pkg.json:

{ "import": ["username/uri"] }

#Quick Start

#Basic URI Parsing

// Parse a complete URI
let uri_string = "https://example.com:8080/path?query=value#fragment"
match parse_uri(uri_string) {
Ok(uri) => {
println("Scheme: \{uri.scheme}")
println("Host: \{uri.authority}")
println("Path: \{uri.path}")
}
Err(error) => println("Parse error: \{error}")
}

#Creating URIs with Builder

// Build an HTTP URI fluently
let uri = new_builder()
.scheme("https")
.host("api.example.com")
.port(443)
.path("/v1/users")
.add_query_param("limit", "10")
.add_query_param("offset", "20")
.fragment("results")
.build()

match uri {
Ok(u) => println("Built URI: \{uri_to_string(u)}")
Err(e) => println("Build error: \{e}")
}

#API Reference

#Core Types

#URI Structure

pub struct URI {
scheme : String // Required scheme (http, https, ftp, etc.)
authority : Authority? // Optional authority component
path : String // Path component
path_type : PathType // Classification of path type
query : String? // Optional query string
fragment : String? // Optional fragment identifier
}

#Authority Component

pub struct Authority {
userinfo : String? // Optional user information
host : HostType // Host (required in authority)
port : Int? // Optional port number
}

pub enum HostType {
IPv4(String) // IPv4 address (e.g., "192.168.1.1")
IPv6(String) // IPv6 address (e.g., "::1")
IPvFuture(String) // Future IP version
RegName(String) // Regular domain name (e.g., "example.com")
}

#Parsing Functions

#parse_uri(uri_string : String) -> URIResult[URI]

Parse a complete URI string. The URI must contain a scheme.

let result = parse_uri("https://user@example.com:8080/path?q=val#frag")

#parse_uri_reference(uri_ref_string : String) -> URIResult[URI]

Parse either a complete URI or a relative reference.

// Absolute URI
let abs_uri = parse_uri_reference("https://example.com/path")

// Relative reference
let rel_ref = parse_uri_reference("/path?query=value")

#Creation Functions

#create_uri() - Full URI Creation

pub fn create_uri(
scheme : String,
authority : Authority?,
path : String,
query : String?,
fragment : String?
) -> URIResult[URI]

#Convenience Creators

// Simple URI with scheme and path
create_simple_uri("file", "/path/to/file.txt")

// HTTP URI
create_http_uri("example.com", Some(8080), "/api", Some("v=1"), None)

// HTTPS URI
create_https_uri("secure.example.com", None, "/", None, Some("top"))

// File URI
create_file_uri("/home/user/document.pdf")

#URI Builder

The builder pattern provides a fluent interface for constructing URIs:

new_builder()
.scheme("https") // Set scheme
.host("api.example.com") // Set host (creates authority)
.port(443) // Set port
.userinfo("user:pass") // Set userinfo
.path("/v1/endpoint") // Set path
.add_path_segment("users") // Add path segment
.query("format=json") // Set query string
.add_query_param("page", "1") // Add single parameter
.fragment("section1") // Set fragment
.build() // Build final URI

#Utility Functions

#uri_to_string(uri : URI) -> String

Convert a URI back to its string representation.

let uri_string = uri_to_string(parsed_uri)

#resolve_reference(base : URI, reference : URI) -> URIResult[URI]

Resolve a relative reference against a base URI according to RFC 3986.

let base = parse_uri("https://example.com/base/path")
let reference = parse_uri_reference("../other/file")
let resolved = resolve_reference(base, reference)

#Error Handling

The library uses a comprehensive error system:

pub enum URIError {
InvalidScheme(String)
InvalidAuthority(String)
InvalidHost(String)
InvalidPort(String)
InvalidPath(String)
InvalidQuery(String)
InvalidFragment(String)
InvalidPercentEncoding(String)
MalformedURI(String)
}

All parsing and creation functions return URIResult[T] which is an alias for Result[T, URIError].

#Examples

#Simple Web URL

fn example_web_url() {
let url = new_builder()
.scheme("https")
.host("www.example.com")
.add_path_segment("products")
.add_path_segment("123")
.add_query_param("color", "blue")
.add_query_param("size", "large")
.build()

match url {
Ok(uri) => {
// Output: https://www.example.com/products/123?color=blue&size=large
println(uri_to_string(uri))
}
Err(e) => println("Error: \{e}")
}
}

#File URI

fn example_file_uri() {
let file_uri = create_file_uri("/home/user/documents/report.pdf")
match file_uri {
Ok(uri) => {
// Output: file:///home/user/documents/report.pdf
println(uri_to_string(uri))
}
Err(e) => println("Error: \{e}")
}
}

#Complex URI with IPv6 Host

fn example_ipv6_uri() {
let host_type = HostType::IPv6("2001:db8::1")
let authority = { userinfo: None, host: host_type, port: Some(8080) }

let uri = create_uri(
"http",
Some(authority),
"/api/v1/data",
Some("format=json"),
Some("results")
)

match uri {
Ok(u) => {
// Output: http://[2001:db8::1]:8080/api/v1/data?format=json#results
println(uri_to_string(u))
}
Err(e) => println("Error: \{e}")
}
}

#Parsing and Validating User Input

fn validate_user_uri(input : String) -> Bool {
match parse_uri_reference(input) {
Ok(uri) => {
// Additional validation can be performed here
true
}
Err(_) => false
}
}

fn example_validation() {
let test_urls = [
"https://example.com",
"ftp://files.example.com/path",
"/relative/path",
"invalid::uri",
]

for url in test_urls {
if validate_user_uri(url) {
println("\{url} is valid")
} else {
println("\{url} is invalid")
}
}
}

#Working with Query Parameters

fn example_query_handling() {
let params = [
("search", "moon programming"),
("page", "1"),
("sort", "date"),
("order", "desc")
]

let uri = new_builder()
.scheme("https")
.host("search.example.com")
.path("/results")
.query_params(params)
.build()

match uri {
Ok(u) => {
// Properly encoded query string
println(uri_to_string(u))
}
Err(e) => println("Error: \{e}")
}
}

#Testing

The library includes comprehensive tests for all functionality. Run tests with:

moon test

#Compliance

This library implements RFC 3986 (Uniform Resource Identifier) specification with full support for:

  • URI syntax and components
  • Percent-encoding mechanisms
  • Reserved and unreserved characters
  • Authority component parsing (userinfo, host, port)
  • Path normalization and classification
  • Query and fragment handling
  • Relative reference resolution

#License

Licensed under Apache 2.0 License.

#Contributing

Contributions are welcome. Please ensure all tests pass and follow the existing code style.

#
URIResult

type URIResult[T] = Result[T, URIError]

Result type for URI operations

#
Authority

pub(all) struct Authority {
userinfo : String?
host : HostType
port : Int?
} derive(Eq,
Debug
)

Represents a parsed URI authority component

#
HostType

pub(all) enum HostType {
IPv4(String)
IPv6(String)
IPvFuture(String)
RegName(String)
} derive(Eq,
Debug
)

Represents different types of hosts

#
PathType

pub(all) enum PathType {
AbEmpty
Absolute
NoScheme
Rootless
Empty
} derive(Eq,
Debug
)

Represents the different types of paths in a URI

#
URI

pub(all) struct URI {
scheme : String
authority : Authority?
path : String
path_type : PathType
query : String?
fragment : String?
} derive(Eq,
Debug
)

Represents a parsed URI

#
URIBuilder

pub struct URIBuilder {
scheme : String
authority : Authority?
path : String
query : String?
fragment : String?
} derive(
Debug
)

URI Builder for fluent construction

#
URIBuilder::add_path_segment

fn URIBuilder::add_path_segment(self : URIBuilder, segment : String) -> URIBuilder

Add a path segment

#
URIBuilder::add_query_param

fn URIBuilder::add_query_param(self : URIBuilder, key : String, value : String) -> URIBuilder

Add a single query parameter

#
URIBuilder::authority

fn URIBuilder::authority(self : URIBuilder, authority : Authority) -> URIBuilder

Set the authority

#
URIBuilder::build

fn URIBuilder::build(self : URIBuilder) -> Result[URI, URIError]

Build the URI

#
URIBuilder::fragment

fn URIBuilder::fragment(self : URIBuilder, fragment : String) -> URIBuilder

Set the fragment

#
URIBuilder::host

fn URIBuilder::host(self : URIBuilder, host : String) -> URIBuilder

Set the host (creates authority if needed)

#
URIBuilder::path

fn URIBuilder::path(self : URIBuilder, path : String) -> URIBuilder

Set the path

#
URIBuilder::port

fn URIBuilder::port(self : URIBuilder, port : Int) -> URIBuilder

Set the port

#
URIBuilder::query

fn URIBuilder::query(self : URIBuilder, query : String) -> URIBuilder

Set the query

#
URIBuilder::query_params

fn URIBuilder::query_params(self : URIBuilder, params : Array[(String, String)]) -> URIBuilder

Set query parameters

#
URIBuilder::scheme

fn URIBuilder::scheme(self : URIBuilder, scheme : String) -> URIBuilder

Set the scheme

#
URIBuilder::userinfo

fn URIBuilder::userinfo(self : URIBuilder, userinfo : String) -> URIBuilder

Set the userinfo

#
URIError

pub enum URIError {
InvalidScheme(String)
InvalidAuthority(String)
InvalidHost(String)
InvalidPort(String)
InvalidPath(String)
InvalidQuery(String)
InvalidFragment(String)
InvalidPercentEncoding(String)
MalformedURI(String)
} derive(Eq,
Debug
)

Represents parsing errors

#
authority_to_string

fn authority_to_string(auth : Authority) -> String

Convert authority back to string

#
build_query_string

fn build_query_string(params : Array[(String, String)]) -> String

Build query string from key-value pairs

#
classify_and_validate_path

fn classify_and_validate_path(path : String) -> Result[(PathType, String), URIError]

Determine path type and validate

#
create_authority

fn create_authority(userinfo : String?, host : HostType, port : Int?) -> Authority

Create authority from components

#
create_file_uri

fn create_file_uri(path : String) -> Result[URI, URIError]

Create a file URI

#
create_http_uri

fn create_http_uri(host : String, port : Int?, path : String, query : String?, fragment : String?) -> Result[URI, URIError]

Create an HTTP URI

#
create_https_uri

fn create_https_uri(host : String, port : Int?, path : String, query : String?, fragment : String?) -> Result[URI, URIError]

Create an HTTPS URI

#
create_simple_uri

fn create_simple_uri(scheme : String, path : String) -> Result[URI, URIError]

Create a simple URI with just scheme and path

#
create_uri

fn create_uri(scheme : String, authority : Authority?, path : String, query : String?, fragment : String?) -> Result[URI, URIError]

Create a new URI with all components

#
encode_fragment

fn encode_fragment(s : String) -> String

Encode a string for use in URI fragment

#
encode_path_segment

fn encode_path_segment(s : String) -> String

Encode a string for use in URI path segments

#
encode_query

fn encode_query(s : String) -> String

Encode a string for use in URI query

#
encode_reg_name

fn encode_reg_name(s : String) -> String

Encode a string for use in URI reg-name (domain names)

#
encode_userinfo

fn encode_userinfo(s : String) -> String

Encode a string for use in URI userinfo

#
find_char_index

fn find_char_index(s : String, c : Char) -> Int?

Find first occurrence of character in string

#
find_last_index_str

fn find_last_index_str(s : String, substr : String) -> Int?

Parse authority followed by path-abempty Find last occurrence of substring in string (helper function)

#
hex_to_int

fn hex_to_int(c : Char) -> Int?

Convert hex character to its numeric value

#
int_to_hex

fn int_to_hex(n : Int) -> Char?

Convert integer to hex character (lowercase)

#
is_absolute_uri

fn is_absolute_uri(uri : URI) -> Bool

Check if URI is absolute (has scheme)

#
is_fragment_char

fn is_fragment_char(c : Char) -> Bool

Check if character is valid for fragment: pchar / "/" / "?"

#
is_gen_delim

fn is_gen_delim(c : Char) -> Bool

Check if character is a general delimiter: ":" / "/" / "?" / "#" / "[" / "]" / "@"

#
is_pchar

fn is_pchar(c : Char) -> Bool

Check if character is valid for pchar: unreserved / pct-encoded / sub-delims / ":" / "@"

#
is_query_char

fn is_query_char(c : Char) -> Bool

Check if character is valid for query: pchar / "/" / "?"

#
is_reg_name_char

fn is_reg_name_char(c : Char) -> Bool

Check if character is valid for reg-name: unreserved / pct-encoded / sub-delims

#
is_relative_uri

fn is_relative_uri(uri : URI) -> Bool

Check if URI is relative (no scheme)

#
is_reserved

fn is_reserved(c : Char) -> Bool

Check if character is reserved: gen-delims / sub-delims

#
is_scheme_char

fn is_scheme_char(c : Char) -> Bool

Check if character is valid for scheme: ALPHA / DIGIT / "+" / "-" / "."

#
is_segment_nz_nc_char

fn is_segment_nz_nc_char(c : Char) -> Bool

Check if character is valid in segment-nz-nc: unreserved / pct-encoded / sub-delims / "@" (non-zero-length segment without any colon ":")

#
is_sub_delim

fn is_sub_delim(c : Char) -> Bool

Check if character is a sub-delimiter: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="

#
is_unreserved

fn is_unreserved(c : Char) -> Bool

Check if character is unreserved: ALPHA / DIGIT / "-" / "." / "_" / "~"

#
is_userinfo_char

fn is_userinfo_char(c : Char) -> Bool

Check if character is valid for userinfo: unreserved / pct-encoded / sub-delims / ":"

#
is_valid_encoded_string

fn is_valid_encoded_string(s : String, char_validator : (Char) -> Bool) -> Bool

Check if a string is already properly percent-encoded and contains only valid characters

#
is_valid_percent_encoding

fn is_valid_percent_encoding(s : String) -> Bool

Check if a string contains valid percent-encoding pct-encoded = "%" HEXDIG HEXDIG

#
is_valid_uri

fn is_valid_uri(uri_string : String) -> Bool

Validate a complete URI string without parsing it fully

#
is_valid_uri_reference

fn is_valid_uri_reference(uri_ref_string : String) -> Bool

Validate a URI reference string

#
join_paths

fn join_paths(base : String, path : String) -> String

Join two paths

#
make_ipv4_host

fn make_ipv4_host(addr : String) -> HostType

Constructor functions for HostType to enable blackbox testing

#
make_ipv6_host

fn make_ipv6_host(addr : String) -> HostType

#
make_ipv_future_host

fn make_ipv_future_host(addr : String) -> HostType

#
make_reg_name_host

fn make_reg_name_host(name : String) -> HostType

#
new_builder

fn new_builder() -> URIBuilder

Create a new URI builder

#
normalize_path

fn normalize_path(path : String) -> String

Normalize path by removing unnecessary components

#
normalize_uri_case

fn normalize_uri_case(uri : URI) -> URI

Normalize URI component case (schemes and host names are case-insensitive)

#
parse_authority

fn parse_authority(s : String) -> Result[Authority, URIError]

Parse authority component

#
parse_fragment

fn parse_fragment(s : String) -> Result[String, URIError]

Parse fragment: *( pchar / "/" / "?" )

#
parse_host

fn parse_host(s : String) -> Result[HostType, URIError]

Parse any host type: IP-literal / IPv4address / reg-name

#
parse_ip_literal

fn parse_ip_literal(s : String) -> Result[HostType, URIError]

Parse IP literal: "[" ( IPv6address / IPvFuture ) "]"

#
parse_ipv4_address

fn parse_ipv4_address(s : String) -> Result[String, URIError]

Parse IPv4 address: dec-octet "." dec-octet "." dec-octet "." dec-octet

#
parse_ipv6_address

fn parse_ipv6_address(s : String) -> Result[String, URIError]

Simplified IPv6 address validation This is a simplified version that checks basic structure A full implementation would need to handle all IPv6 compression rules

#
parse_ipv_future

fn parse_ipv_future(s : String) -> Result[String, URIError]

Parse IPvFuture: "v" 1HEXDIG "." 1( unreserved / sub-delims / ":" )

#
parse_query

fn parse_query(s : String) -> Result[String, URIError]

Parse query: *( pchar / "/" / "?" )

#
parse_query_params

fn parse_query_params(query : String) -> Array[(String, String)]

Split query string into key-value pairs

#
parse_scheme

fn parse_scheme(s : String) -> Result[String, URIError]

Parse scheme: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )

#
parse_uri

fn parse_uri(uri_string : String) -> Result[URI, URIError]

Parse a complete URI string

#
parse_uri_reference

fn parse_uri_reference(uri_ref_string : String) -> Result[URI, URIError]

Parse a URI reference (URI or relative reference) URI-reference = URI / relative-ref

#
path_segments

fn path_segments(path : String) -> Array[String]

Split path into segments

#
percent_decode

fn percent_decode(s : String) -> Result[String, URIError]

Decode a percent-encoded string

#
percent_encode

fn percent_encode(s : String, should_encode : (Char) -> Bool) -> String

Encode a string with percent-encoding for characters that need it The predicate function determines which characters should be encoded

#
resolve_reference

fn resolve_reference(base : URI, reference : URI) -> Result[URI, URIError]

Resolve a relative reference against a base URI

#
schemes_equal

fn schemes_equal(scheme1 : String, scheme2 : String) -> Bool

Case-insensitive scheme comparison

#
uri_to_string

fn uri_to_string(uri : URI) -> String

Convert URI back to string representation

#
uris_equivalent

fn uris_equivalent(uri1 : URI, uri2 : URI) -> Bool

Check if two URIs are equivalent (after normalization)

#
validate_path_abempty

fn validate_path_abempty(path : String) -> Result[String, URIError]

Validate path-abempty: *( "/" segment )

#
validate_scheme_specific

fn validate_scheme_specific(uri : URI) -> Result[Unit, URIError]

Validate URI according to specific scheme requirements

#
validate_uri_components

fn validate_uri_components(scheme : String, authority : Authority?, path : String, query : String?, fragment : String?) -> Result[Unit, URIError]

Validate that all URI components are properly formed

#
validate_uri_comprehensive

fn validate_uri_comprehensive(uri : URI) -> Result[Unit, URIError]

Perform comprehensive URI validation

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io