Sign in

    uri

    URI parser and builder for Moonbit

    uri
    parser
    http
    Download zip
    Version
    0.1.5
    License
    Apache-2.0
    Last updated
    last month
    Downloads
    85

    #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, ToJson,
    Debug
    ,
    FromJson
    )

    Represents a parsed URI authority component

    HostType

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

    Represents different types of hosts

    PathType

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

    Represents the different types of paths in a URI

    URI

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

    Represents a parsed URI

    URI::get_query

    fn URI::get_query(self : URI, key : String) -> String?

    Get the value of a query parameter from a URI. Returns Some(value) when the key exists with a value (Some("") for "key="), None for a bare key without a value, and None when the key is absent or the URI has no query at all.

    URIBuilder

    pub struct URIBuilder {
    scheme : String
    authority : Authority?
    path : String
    query : Map[String, 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 from a raw query string

    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. None values render as a bare key; Some("") renders as "key=".

    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 : Map[String, String?], fragment : String?) -> Result[URI, URIError]

    Create an HTTP URI

    create_https_uri

    fn create_https_uri(host : String, port : Int?, path : String, query : Map[String, 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 : Map[String, 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_map

    fn parse_query_map(query : String) -> Result[Map[String, String?], URIError]

    Parse and validate a raw query string into a map of key-value pairs. Keys without '=' map to None; an empty/absent query yields an empty map.

    parse_query_params

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

    Split query string into key-value pairs. Keys without '=' map to None; "key=" maps to Some("").

    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-escape sequences are collected as raw bytes; literal characters contribute their UTF-8 bytes. The byte sequence is then decoded as UTF-8, so multi-byte sequences produced by percent_encode round-trip correctly.

    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 Characters are encoded as their UTF-8 byte sequences, per RFC 3986 A '%' that begins a valid pct-encoded triplet ("%HH") is preserved as-is so already-encoded strings pass through unchanged; any other '%' is encoded as "%25" (RFC 3986 section 2.4)

    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 : Map[String, 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