RFC 6570 Level 4 URI Template parser and expander
/users/{id}{?page,tags*} + id=42, page=2, tags=[sdk, api]
→ /users/42?page=2&tags=sdk&tags=api// npm install moonbit-uri-template
import { compileTemplate } from 'moonbit-uri-template';
const user = compileTemplate('/users/{id}{?page}', { required: ['id'] });
user.expand({ id: 42 }); // /users/42
user.expand({ id: 42, page: 2 }); // /users/42?page=2
user.expandMany([{ id: 1 }, { id: 2 }]); // 任一失败则整批抛错,不返回半批结果// 下游 moon.pkg
import { "cjyhjy/uri_template" @uri }let vars = @uri.Variables::from_entries([
("id", @uri.Scalar("42")),
("page", @uri.Scalar("2")),
]).unwrap()
let template = @uri.parse("/users/{id}{?page}").unwrap()
println(template.expand(vars).unwrap()) // /users/42?page=2
// 有界并检查必填:
template.expand(vars, required=["id"], limits=@uri.ExpansionLimits::new())node tools/cli.mjs expand '/search{?q,tags*}' --vars '{"q":"moon bit","tags":["sdk","api"]}'
# /search?q=moon%20bit&tags=sdk&tags=api
node tools/cli.mjs check '/users/{id}{?page}' --vars '{"paeg":2}'
# 报告 missing: id, page;unused: paeg;退出码 1
node tools/cli.mjs validate '/users/{id'
# UnclosedExpression at 1:8 (code points 7..10): expected closing brace
# /users/{id
# ^^^
node tools/cli.mjs project integrations/github-audit/requests.json --github
# 一次检查整个请求配置,输出 GitHub Actions 注解
node tools/cli.mjs openapi api.github.com.json
# api.github.com.json:29475:50 EquivalentPaths: same path hierarchy as /orgs/{org}/attestations/{attestation_id}python3 tools/bootstrap.py # 在 .toolchain/ 内安装固定版本的 MoonBit 工具链
npm ci --ignore-scripts
npm run build
npm test # 四后端测试、SDK、CLI、差分回放、打包与独立消费者
npm run playground # 浏览器工作台 http://127.0.0.1:4173| 文档 | 内容 |
|---|---|
| 评审指南 | 与同类库的差异、证据和复现命令 |
| SDK | Node/TypeScript 接入 |
| API | MoonBit 公共接口 |
| 一致性 | RFC 语义选择、上游用例与差分结果 |
| 同类对比 | 与 Magic486、JSJ222 的功能和行为对比 |
| OpenAPI | 从 OpenAPI 描述生成模板、检查与公开文档实测 |
| 性能 | 基准方法与结果 |
| 架构 | 模块划分与测试策略 |
| 发布 | 打包、发布与工具链维护 |
| MoonBit 原生客户端 | 从 Mooncakes 安装、按 OpenAPI 发真实请求的 MoonBit 程序 |
| GitHub 接入案例 | Node 下游客户端:请求配置检查、升级审查 |
| 公共 API 客户端 | npm、Open Library 只读客户端 |
| AI 辅助说明 | 开发方式 |
pub(all) struct BatchLimits {
max_items : Int
max_total_input_codepoints : Int
max_total_output_bytes : Int
} derive(Debug)fn BatchLimits::new(max_items? : Int, max_total_input_codepoints? : Int, max_total_output_bytes? : Int) -> BatchLimitspub(all) struct BindingReport {
referenced : Array[String]
missing : Array[BindingIssue]
omitted : Array[BindingIssue]
unused : Array[String]
invalid : Array[TemplateError]
} derive(Debug)type BoundTemplatefn BoundTemplate::expand(self : BoundTemplate, variables : Variables, required? : Array[String], limits? : ExpansionLimits) -> Result[String, TemplateError]fn BoundTemplate::measure(self : BoundTemplate, variables : Variables, required? : Array[String], limits? : ExpansionLimits) -> Result[Int, TemplateError]pub(all) struct CatalogAudit {
entries : Array[CatalogEntryAudit]
total_findings : Int
} derive(Debug)pub(all) struct CatalogContractDiff {
added : Array[String]
removed : Array[String]
changed : Array[(String, DetailedContractDiff)]
} derive(Debug)pub(all) struct CatalogEntryAudit {
name : String
expression_count : Int
variable_count : Int
max_level : Int
findings : Array[TemplateError]
} derive(Debug)fn CatalogRequest::new(name : String, variables : Variables, required? : Array[String]) -> CatalogRequesttype ContractTemplatefn ContractTemplate::expand(self : ContractTemplate, variables : Variables, limits? : ExpansionLimits) -> Result[String, TemplateError]fn ContractTemplate::expand_mapped(self : ContractTemplate, variables : Variables, limits? : ExpansionLimits) -> Result[Expansion, TemplateError]fn ContractTemplate::inspect(self : ContractTemplate, variables : Variables, limits? : ExpansionLimits, max_findings? : Int) -> Result[Array[BindingFinding], TemplateError]fn ContractTemplate::measure(self : ContractTemplate, variables : Variables, limits? : ExpansionLimits) -> Result[Int, TemplateError]fn ContractTemplate::validate(self : ContractTemplate, variables : Variables, limits? : ExpansionLimits) -> Result[Unit, TemplateError]pub(all) struct DetailedContractDiff {
added_variables : Array[String]
removed_variables : Array[String]
became_scalar_only : Array[String]
became_composite_capable : Array[String]
new_repeated_variables : Array[String]
old_level : Int
new_level : Int
reserved_added : Bool
fragment_added : Bool
literals_changed : Bool
expression_layout_changed : Bool
variable_changes : Array[VariableChange]
source_changed : Bool
} derive(Debug)pub(all) struct Expansion {
output : String
bytes : Int
origins : Array[OutputOrigin]
} derive(Debug)pub(all) struct ExpansionLimits {
max_bindings : Int
max_members : Int
max_value_codepoints : Int
max_total_value_codepoints : Int
max_output_bytes : Int
} derive(Debug)fn ExpansionLimits::new(max_bindings? : Int, max_members? : Int, max_value_codepoints? : Int, max_total_value_codepoints? : Int, max_output_bytes? : Int) -> ExpansionLimitstype LinkSessionfn LinkSession::update(self : LinkSession, changes : Array[BindingChange]) -> Result[LinkUpdate, CatalogError]type LinkUpdatefn OrderedEntries::from_entries(entries : Array[(String, String)]) -> Result[OrderedEntries, TemplateError]pub(all) struct ParseLimits {
max_template_codepoints : Int
max_expressions : Int
max_variables_per_expression : Int
max_total_variables : Int
} derive(Debug)fn ParseLimits::new(max_template_codepoints? : Int, max_expressions? : Int, max_variables_per_expression? : Int, max_total_variables? : Int) -> ParseLimitspub(all) struct RequestAudit {
requests : Array[RequestInspection]
total_findings : Int
} derive(Debug)pub(all) struct RequestInspection {
index : Int
name : String
findings : Array[BindingFinding]
} derive(Debug)fn Template::audit(self : Template, policy : TemplatePolicy, max_findings? : Int) -> Result[Array[TemplateError], TemplateError]fn Template::bind(self : Template, fixed : Variables, limits? : ExpansionLimits) -> Result[BoundTemplate, TemplateError]fn Template::check_bindings(self : Template, variables : Variables, limits? : ExpansionLimits) -> Result[BindingReport, TemplateError]fn Template::check_contract(self : Template, variables : Variables, required? : Array[String], reject_unused? : Bool, limits? : ExpansionLimits, max_findings? : Int) -> Result[Array[BindingFinding], TemplateError]fn Template::expand(self : Template, variables : Variables, required? : Array[String], reject_unused? : Bool, limits? : ExpansionLimits) -> Result[String, TemplateError]fn Template::expand_batch(self : Template, inputs : Array[Variables], required? : Array[String], reject_unused? : Bool, limits? : ExpansionLimits, batch_limits? : BatchLimits) -> Result[Array[String], BatchError]fn Template::expand_mapped(self : Template, variables : Variables, required? : Array[String], reject_unused? : Bool, limits? : ExpansionLimits) -> Result[Expansion, TemplateError]fn Template::measure(self : Template, variables : Variables, required? : Array[String], reject_unused? : Bool, limits? : ExpansionLimits) -> Result[Int, TemplateError]fn Template::with_contract(self : Template, rules : Array[BindingRule], required? : Array[String], reject_unused? : Bool, max_rules? : Int, max_rule_codepoints? : Int) -> Result[ContractTemplate, TemplateError]pub(all) struct TemplateAnalysis {
expressions : Array[ExpressionInfo]
uses : Array[VariableUse]
profiles : Array[VariableProfile]
max_level : Int
has_reserved_expansion : Bool
has_fragment : Bool
} derive(Debug)type TemplateCatalogfn TemplateCatalog::audit(self : TemplateCatalog, policy : TemplatePolicy, max_findings? : Int) -> Result[CatalogAudit, CatalogError]fn TemplateCatalog::expand(self : TemplateCatalog, name : String, variables : Variables, required? : Array[String], reject_unused? : Bool, limits? : ExpansionLimits) -> Result[String, CatalogError]fn TemplateCatalog::expand_batch(self : TemplateCatalog, requests : Array[CatalogRequest], reject_unused? : Bool, limits? : ExpansionLimits, batch_limits? : BatchLimits) -> Result[Array[String], CatalogBatchError]fn TemplateCatalog::inspect_requests(self : TemplateCatalog, requests : Array[CatalogRequest], reject_unused? : Bool, limits? : ExpansionLimits, batch_limits? : BatchLimits, max_findings? : Int) -> Result[RequestAudit, CatalogBatchError]fn TemplateCatalog::measure(self : TemplateCatalog, name : String, variables : Variables, required? : Array[String], limits? : ExpansionLimits) -> Result[Int, CatalogError]fn TemplateCatalog::measure_batch(self : TemplateCatalog, requests : Array[CatalogRequest], reject_unused? : Bool, limits? : ExpansionLimits, batch_limits? : BatchLimits) -> Result[Array[Int], CatalogBatchError]fn TemplateCatalog::start_session(self : TemplateCatalog, variables : Variables, contracts? : Array[(String, ContractTemplate)], limits? : ExpansionLimits, max_total_output_bytes? : Int, max_patch_bindings? : Int, max_dependency_edges? : Int) -> Result[LinkSession, CatalogError]fn TemplatePolicy::new(allow_reserved? : Bool, allow_fragment? : Bool, allow_prefix? : Bool, allow_explode? : Bool, allowed_variables? : Array[String]?, max_level? : Int, max_references? : Int, forbid_repeated_variables? : Bool) -> TemplatePolicypub(all) struct VariableChange {
name : String
before : VariableProfile
after : VariableProfile
operators_changed : Bool
prefixes_changed : Bool
explode_changed : Bool
occurrences_changed : Bool
uses_changed : Bool
} derive(Debug)pub(all) struct VariableProfile {
name : String
occurrences : Int
first_span : Span
last_span : Span
scalar_only : Bool
uses_explode : Bool
uses_reserved : Bool
uses_query : Bool
uses_fragment : Bool
maximum_prefix : Int?
operators : Array[String]
prefixes : Array[Int]
uses : Array[VariableUse]
} derive(Debug)fn compile_catalog(entries : Array[(String, String)], max_templates? : Int, max_total_codepoints? : Int, limits? : ParseLimits, strict? : Bool) -> Result[TemplateCatalog, CatalogError]fn parse_with_limits(source : String, limits : ParseLimits, strict? : Bool) -> Result[Template, TemplateError]