moon-uri-template

    RFC 6570 Level 4 URI Template parser and expander for MoonBit

    uri
    url
    template
    rfc6570
    Download zip
    Author
    Version
    0.1.2
    License
    MIT
    Last updated
    last month
    Downloads
    9

    Dependencies

    #moon-uri-template

    基于 MoonBit 纯实现的 RFC 6570 Level 4 URI Template 库。

    moon-uri-template 将 URI 模板解析一次后,可反复使用类型化的标量、列表或关联数组 值进行展开。适用于 HTTP 客户端、OpenAPI 工具、SDK 生成器、超媒体 API 以及 跨后端 MoonBit 应用。

    命名空间:Magic486/moon-uri-template

    #安装

    moon add Magic486/moon-uri-template

    同一模块中的应用可直接导入根包。

    #功能特性

    • RFC 6570 Levels 1–4
    • 全部标准操作符:简单展开、+#./;?&
    • Prefix 与 Explode 修饰符
    • 有序的标量、列表和关联数组值
    • Unicode 码点感知的 Prefix 截取
    • UTF-8 百分号编码
    • 带源位置信息的结构化错误
    • 显式的解析器与展开输出资源限制
    • Wasm、Wasm-GC、JavaScript、Native 四后端确定性展开
    • JSON 变量适配器
    • validatevariablesinspectexpand 四个 CLI 命令
    • 153 个引入的标准互操作性测试用例及项目专属测试

    #库示例

    最小标量展开——解析一次并传入类型化值:

    ///|
    test "expand one scalar" {
    let template = UriTemplate::parse("hello/{name}")
    let variables : Map[String, UriValue] = {
    "name": Scalar("MoonBit"),
    }
    assert_eq(template.expand(variables), "hello/MoonBit")
    }

    真实端点可组合路径、查询参数和列表展开:

    ///|
    test "expand a repository issues URI" {
    let template = UriTemplate::parse(
    "/repos/{owner}/{repo}/issues{?page,labels*}",
    )
    let variables : Map[String, UriValue] = {
    "owner": Scalar("moonbitlang"),
    "repo": Scalar("core"),
    "page": Scalar("2"),
    "labels": List(["bug", "help wanted"]),
    }
    assert_eq(
    template.expand(variables),
    "/repos/moonbitlang/core/issues?page=2&labels=bug&labels=help%20wanted",
    )
    }

    #值模型

    ///|
    pub(all) enum UriValue {
    Scalar(String)
    List(Array[String])
    Assoc(Array[(String, String)])
    }

    列表和关联数组保留输入顺序。Map 中不存在的条目表示未定义变量。空字符串视为已定义; 空列表和空关联数组在 RFC 6570 展开中被视为未定义。

    #Prefix 修饰符

    ///|
    test "prefix length counts Unicode code points" {
    let template = UriTemplate::parse("{value:2}")
    let variables : Map[String, UriValue] = {
    "value": Scalar("月兔Moon"),
    }
    assert_eq(template.expand(variables), "%E6%9C%88%E5%85%94")
    }

    #Explode 修饰符

    ///|
    test "explode a list into repeated query parameters" {
    let template = UriTemplate::parse("{?labels*}")
    let variables : Map[String, UriValue] = {
    "labels": List(["bug", "help wanted"]),
    }
    assert_eq(template.expand(variables), "?labels=bug&labels=help%20wanted")
    }

    关联数组保留键值对顺序,并可展开为命名查询参数:

    ///|
    test "expand an associative value" {
    let template = UriTemplate::parse("{?filters*}")
    let variables : Map[String, UriValue] = {
    "filters": Assoc([("state", "open"), ("author", "月兔")]),
    }
    assert_eq(template.expand(variables), "?state=open&author=%E6%9C%88%E5%85%94")
    }

    #资源限制

    UriTemplate::parse 接受最大 1 MiB 的模板,最多包含 4096 个表达式和每个表达式 256 个变量。接受不可信模板的服务和工具可使用更严格的每次调用上限:

    ///|
    test "parse with application-specific limits" {
    let template = UriTemplate::parse_with_limits(
    "/users/{name}{?page}",
    max_template_length=128,
    max_expressions=2,
    max_variables_per_expression=4,
    )
    assert_true(template.variables() == ["name", "page"])
    }

    展开默认限制为 1 MiB。当应用需要不同的显式限制时,可使用 expand_with_limit

    #错误处理

    解析与展开返回结构化的 UriTemplateError 值。语法错误携带 UTF-16 源偏移量; 值错误和限制错误标识其类别,但不回显完整变量集合。

    ///|
    test "handle a syntax error with its source offset" {
    let rejected = try UriTemplate::parse("{unclosed") catch {
    SyntaxError(offset~, message=_) => offset == 0
    _ => false
    } noraise {
    _ => false
    }
    assert_true(rejected)
    }

    #JSON 变量

    variables_from_json 接受一个 JSON 对象:

    • 字符串 → Scalar
    • 字符串数组 → List
    • 字符串值对象 → 有序 Assoc

    数字、布尔值、null、嵌套数组和非字符串关联值将被拒绝,不做隐式字符串化。

    { "owner": "moonbitlang", "repo": "core", "page": "2", "labels": ["bug", "help wanted"] }

    #CLI

    从本仓库运行:

    moon run cmd/uri-template -- validate '/repos/{owner}/{repo}{?page}' moon run cmd/uri-template -- variables '/repos/{owner}/{repo}{?page}' moon run cmd/uri-template -- inspect '/repos/{owner}/{repo}{?page}' moon run cmd/uri-template -- expand \ '/repos/{owner}/{repo}/issues{?page,labels*}' \ --variables examples/variables.json

    展开命令输出:

    /repos/moonbitlang/core/issues?page=2&labels=bug&labels=help%20wanted

    #标准符合性

    项目引入了 Apache-2.0 许可的 uri-templates/uritemplate-test 测试集(固定于 commit 4171dac22aa67fc710b3f6df308a50bd08552986)。

    测试生成器生成 153 个 MoonBit 测试,覆盖:

    • RFC 规范示例
    • 扩展的互操作用例
    • 非法模板和负向测试
    • 多字节字符的 Prefix 处理
    • 保留字符和百分号编码值
    • 文字编码

    运行:

    powershell -NoProfile -ExecutionPolicy Bypass \ -File tools/generate_conformance_tests.ps1 moon test --target all

    所有生成及本地测试当前在 Wasm、Wasm-GC、JavaScript 和 Native 后端均通过, 使用的 MoonBit 工具链为 2026-07-03 版本。

    #安全边界

    URI Template 展开不是输入校验或授权。

    • {+var}{#var} 刻意允许 URI 保留字符;
    • 不可在对不可信展开值发起 SSRF 敏感网络请求前,先应用独立的 scheme、host、port 和 目标策略;
    • 错误信息不包含完整变量集合;
    • expand 强制施加 1 MiB 的默认输出限制;
    • expand_with_limit 允许应用设置更严格的限制;
    • parse_with_limits 在模板不可信时可限制模板长度、表达式数量和每个表达式的变量数。

    本库不发送 HTTP 请求、不解析相对引用、不检查资源是否存在,也不从 URI 反向匹配变量。

    #开发

    moon check --warn-list +73 moon test --target all moon fmt --check moon info

    项目范围和验收要求在 SPEC.md 中定义。交付优先级和里程碑在 GOALS.md 中跟踪。第三方声明记录在 THIRD_PARTY.md

    #许可证

    项目代码以 MIT 许可证提供。引入的标准测试集保留上游 Apache-2.0 许可证。

    UriTemplateError

    pub(all) suberror UriTemplateError {
    SyntaxError(offset~ : Int, message~ : String)
    InvalidValue(variable~ : String, message~ : String)
    ParseLimitExceeded(kind~ : String, limit~ : Int)
    OutputLimitExceeded(limit~ : Int)
    } derive(
    Debug
    )

    Errors produced while parsing or expanding a URI Template.

    Modifier

    type Modifier derive(Eq,
    Debug
    )

    Operator

    type Operator derive(Eq,
    Debug
    )

    TemplatePart

    type TemplatePart derive(Eq,
    Debug
    )

    UriTemplate

    pub struct UriTemplate {
    source : String
    parts : Array[TemplatePart]
    variables : Array[String]
    level : Int
    }

    A parsed URI Template that can be expanded repeatedly.

    UriTemplate::expand

    fn UriTemplate::expand(self : UriTemplate, variables : Map[String, UriValue]) -> String raise UriTemplateError

    Expand this parsed template with an immutable snapshot of variable values.

    UriTemplate::expand_with_limit

    fn UriTemplate::expand_with_limit(self : UriTemplate, variables : Map[String, UriValue], max_output_length~ : Int) -> String raise UriTemplateError

    Expand this template while enforcing a maximum output length.

    UriTemplate::level

    fn UriTemplate::level(self : UriTemplate) -> Int

    Return the lowest RFC 6570 feature level required by this template.

    UriTemplate::parse

    fn UriTemplate::parse(source : StringView) -> UriTemplate raise UriTemplateError

    Parse and validate an RFC 6570 URI Template using safe default limits.

    UriTemplate::parse_with_limits

    fn UriTemplate::parse_with_limits(source : StringView, max_template_length~ : Int, max_expressions~ : Int, max_variables_per_expression~ : Int) -> UriTemplate raise UriTemplateError

    Parse and validate an RFC 6570 URI Template with explicit resource limits.

    UriTemplate::source

    fn UriTemplate::source(self : UriTemplate) -> String

    Return the original template source.

    UriTemplate::variables

    fn UriTemplate::variables(self : UriTemplate) -> Array[String]

    Return variable names in first-appearance order without duplicates.

    UriValue

    pub(all) enum UriValue {
    Scalar(String)
    List(Array[String])
    Assoc(Array[(String, String)])
    } derive(Eq,
    Debug
    )

    A value supplied to an RFC 6570 URI Template variable.

    Lists and associative arrays preserve their input order so expansion is deterministic across targets.

    VariableSpec

    type VariableSpec derive(Eq,
    Debug
    )

    DEFAULT_MAX_EXPRESSIONS

    let DEFAULT_MAX_EXPRESSIONS : Int

    Default maximum number of expressions in one template.

    DEFAULT_MAX_OUTPUT_LENGTH

    let DEFAULT_MAX_OUTPUT_LENGTH : Int

    Default maximum expanded URI length, measured in ASCII output characters.

    DEFAULT_MAX_TEMPLATE_LENGTH

    let DEFAULT_MAX_TEMPLATE_LENGTH : Int

    Default maximum accepted template length, measured in UTF-16 code units.

    DEFAULT_MAX_VARIABLES_PER_EXPRESSION

    let DEFAULT_MAX_VARIABLES_PER_EXPRESSION : Int

    Default maximum number of variables in one expression.

    is_valid

    fn is_valid(source : StringView) -> Bool

    Return whether a string is a syntactically valid URI Template.

    variables_from_json

    fn variables_from_json(json : Json) -> Map[String, UriValue] raise UriTemplateError

    Convert a JSON object into typed URI Template variables.

    Strings become Scalar, arrays of strings become List, and objects with string values become ordered Assoc values.