acceptkit

    Dependency-free HTTP representation negotiation toolkit for MoonBit.

    http
    accept
    content-negotiation
    headers
    wasm
    Download zip
    Version
    0.1.0
    License
    MIT
    Last updated
    2 months ago
    Downloads
    35

    No dependencies

    #acceptkit

    acceptkit 是一个 MoonBit 原生、无外部依赖的 HTTP 表示协商工具库,用于解析 Accept、Accept-Language、Accept-Encoding、Accept-Charset、Vary、Cache-Control 和 ETag 条件请求相关头,并为服务端候选响应选择最合适的表示。

    它解决的问题是:MoonBit Web 服务、Wasm HTTP 组件、接口 Mock、边缘函数和测试工具经常需要处理内容协商,但直接手写字符串判断容易漏掉 q 权重、通配符、显式拒绝、参数匹配、稳定排序、响应头生成和缓存相关边界。acceptkit 提供结构化解析结果、确定性的协商算法、候选矩阵和可读解释文本,便于在库、工具和示例工程中复用。

    #适用场景

    • HTTP 服务端根据 Accept 选择 application/json、text/html、application/problem+json 等响应格式;
    • API 工具或 Mock 服务根据 Accept-Language 选择本地化语言;
    • 服务端根据 Accept-Encoding 选择 br、gzip 或 identity;
    • 服务端根据 Accept-Charset 选择 utf-8、iso-8859-1 等字符集;
    • Web 框架中间件生成 Content-Type、Content-Language、Content-Encoding 和 Vary;
    • 测试工具记录候选矩阵,解释“为什么选择该响应格式”;
    • 缓存层检查 Cache-Control、Vary 和 ETag 条件请求行为。

    #安装

    moon add XiaoMao-MYY/acceptkit

    Mooncakes 包名:

    XiaoMao-MYY/acceptkit

    #最小使用示例

    在使用方 moon.pkg 中导入:

    import {
    "XiaoMao-MYY/acceptkit" @acceptkit,
    }

    MoonBit 代码:

    fn choose() -> String {
    let result = @acceptkit.negotiate_response(
    @acceptkit.RequestPreferences(
    accept="application/json, text/html;q=0.6",
    accept_language="zh-CN, en;q=0.8",
    accept_encoding="br, gzip;q=0.7",
    accept_charset="utf-8",
    ),
    ["text/html", "application/json"],
    ["en-US", "zh-CN"],
    ["gzip", "br"],
    ["utf-8"],
    )
    match result {
    Ok(choice) => @acceptkit.response_header_block(choice.summary)
    Err(_) => "invalid negotiation header"
    }
    }

    #本地运行

    moon check moon build moon test moon run cmd/main

    示例入口会输出媒体类型协商、语言协商、组合响应协商和响应头结果。

    #核心 API

    • parse_accept(header):解析 Accept 头,返回 MediaRange 列表;
    • parse_content_type(value):解析服务端候选媒体类型,返回 MediaOffer;
    • negotiate_media(header, offers):在候选媒体类型中选择最佳结果;
    • parse_accept_language(header)、negotiate_language(header, offers):解析语言偏好并选择语言;
    • parse_accept_encoding(header)、negotiate_encoding(header, offers):解析压缩编码偏好并选择编码;
    • parse_accept_charset(header)、negotiate_charset(header, offers):解析字符集偏好并选择字符集;
    • ServerVariant、negotiate_variant(preferences, variants):对媒体、语言、编码、字符集和服务端权重做组合协商;
    • build_response_variants(...)、negotiate_response(...):从常见服务端候选数组生成变体并返回响应摘要;
    • response_headers(summary)、response_header_block(summary):生成响应头;
    • parse_vary(value)、merge_vary(left, right):解析、规范化和合并 Vary;
    • parse_cache_policy(header)、format_cache_summary(summary):提取缓存策略摘要;
    • parse_etag(value)、evaluate_if_none_match(method, header, current)、evaluate_if_match(header, current):处理 ETag 条件请求;
    • audit_preferences(preferences)、audit_cache_control_header(header):输出可检查的风险提示;
    • negotiation_matrix_report(preferences, variants):生成候选矩阵报告。

    #支持范围

    • Accept 中的 type/subtype、type/*、*/*;
    • 媒体类型参数匹配,例如 text/html;level=1;
    • q 权重,使用 0 到 1000 的整数表示,支持三位小数;
    • q=0 显式拒绝,且拒绝会优先于通配符回退;
    • 引号包裹的参数值,分割时会避开引号内的逗号和分号;
    • Accept-Language 基础语言范围、通配符和前缀匹配;
    • Accept-Encoding 的通配符、identity 默认可接受语义和显式拒绝;
    • Accept-Charset 常见别名规范化,例如 utf8、latin1、us_ascii;
    • 组合变体协商、响应摘要、响应头生成和 406 摘要;
    • Vary 规范化、合并和缓存键风险审计;
    • Cache-Control 常见缓存指令摘要;
    • 强/弱 ETag 解析和 If-None-Match、If-Match 判断;
    • 稳定排序:权重、具体度、参数数量、头部顺序、服务端权重、候选顺序。

    #边界

    • 不解析完整 HTTP 报文;
    • 不做网络 I/O、文件 I/O 或框架绑定;
    • 不查询媒体类型注册表;
    • 不实现完整自然语言回退策略;
    • 不包含压缩、字符集转码或缓存存储实现。

    #测试与验收命令

    moon check moon build moon test moon run cmd/main moon publish --dry-run

    #开源许可证和第三方说明

    本项目采用 MIT License。核心实现为原创 MoonBit 代码,不移植第三方源码,不包含第三方素材。实现语义参考 HTTP 内容协商常见规则和 RFC 系列标准描述。

    CacheDirective

    pub(all) struct CacheDirective {
    name : String
    value : String?
    order : Int
    raw : String
    } derive(Eq,
    Debug
    )

    CachePolicySummary

    pub(all) struct CachePolicySummary {
    no_store : Bool
    no_cache : Bool
    is_public : Bool
    is_private : Bool
    must_revalidate : Bool
    immutable : Bool
    max_age : Int?
    s_maxage : Int?
    stale_while_revalidate : Int?
    stale_if_error : Int?
    unknown : Array[CacheDirective]
    } derive(Eq,
    Debug
    )

    CharsetCandidate

    pub(all) struct CharsetCandidate {
    offer : CharsetOffer
    matched_range : CharsetRange
    quality : Int
    specificity : Int
    reason : String
    } derive(Eq,
    Debug
    )

    CharsetDecision

    pub(all) struct CharsetDecision {
    selected : String?
    quality : Int
    candidate : CharsetCandidate?
    matches : Array[CharsetCandidate]
    explanation : String
    } derive(Eq,
    Debug
    )

    CharsetOffer

    pub(all) struct CharsetOffer {
    charset : String
    canonical : String
    index : Int
    } derive(Eq,
    Debug
    )

    CharsetRange

    pub(all) struct CharsetRange {
    charset : String
    q : Int
    order : Int
    raw : String
    } derive(Eq,
    Debug
    )

    ConditionalDecision

    pub(all) struct ConditionalDecision {
    matched : Bool
    status : Int
    reason : String
    } derive(Eq,
    Debug
    )

    EncodingCandidate

    pub(all) struct EncodingCandidate {
    offer : EncodingOffer
    matched_range : EncodingRange
    quality : Int
    specificity : Int
    reason : String
    } derive(Eq,
    Debug
    )

    EncodingDecision

    pub(all) struct EncodingDecision {
    selected : String?
    quality : Int
    candidate : EncodingCandidate?
    matches : Array[EncodingCandidate]
    explanation : String
    } derive(Eq,
    Debug
    )

    EncodingOffer

    pub(all) struct EncodingOffer {
    coding : String
    index : Int
    } derive(Eq,
    Debug
    )

    EncodingRange

    pub(all) struct EncodingRange {
    coding : String
    q : Int
    order : Int
    raw : String
    } derive(Eq,
    Debug
    )

    EntityTag

    pub(all) struct EntityTag {
    weak : Bool
    tag : String
    raw : String
    } derive(Eq,
    Debug
    )

    HeaderAudit

    pub(all) struct HeaderAudit {
    ok : Bool
    warnings : Array[String]
    errors : Array[String]
    } derive(Eq,
    Debug
    )

    LanguageCandidate

    pub(all) struct LanguageCandidate {
    offer : LanguageOffer
    matched_range : LanguageRange
    quality : Int
    specificity : Int
    reason : String
    } derive(Eq,
    Debug
    )

    LanguageDecision

    pub(all) struct LanguageDecision {
    selected : String?
    quality : Int
    candidate : LanguageCandidate?
    matches : Array[LanguageCandidate]
    explanation : String
    } derive(Eq,
    Debug
    )

    LanguageOffer

    pub(all) struct LanguageOffer {
    tag : String
    primary : String
    subtags : Array[String]
    index : Int
    } derive(Eq,
    Debug
    )

    LanguageRange

    pub(all) struct LanguageRange {
    primary : String
    subtags : Array[String]
    q : Int
    order : Int
    raw : String
    } derive(Eq,
    Debug
    )

    MediaCandidate

    pub(all) struct MediaCandidate {
    offer : MediaOffer
    matched_range : MediaRange
    quality : Int
    specificity : Int
    parameter_matches : Int
    reason : String
    } derive(Eq,
    Debug
    )

    MediaDecision

    pub(all) struct MediaDecision {
    selected : String?
    quality : Int
    candidate : MediaCandidate?
    matches : Array[MediaCandidate]
    explanation : String
    } derive(Eq,
    Debug
    )

    MediaOffer

    pub(all) struct MediaOffer {
    value : String
    top : String
    sub : String
    params : Array[Param]
    index : Int
    } derive(Eq,
    Debug
    )

    MediaRange

    pub(all) struct MediaRange {
    top : String
    sub : String
    params : Array[Param]
    extensions : Array[Param]
    q : Int
    order : Int
    raw : String
    } derive(Eq,
    Debug
    )

    Param

    pub(all) struct Param {
    name : String
    value : String
    } derive(Eq,
    Debug
    )

    ParseError

    pub(all) enum ParseError {
    EmptyItem(Int)
    MissingSlash(Int, String)
    InvalidToken(Int, String, String)
    InvalidParameter(Int, String)
    InvalidQ(Int, String)
    InvalidWildcard(Int, String)
    InvalidOffer(Int, String)
    InvalidLanguageTag(Int, String)
    } derive(Eq,
    Debug
    )

    RequestPreferences

    pub(all) struct RequestPreferences {
    accept : String
    accept_language : String
    accept_encoding : String
    accept_charset : String
    } derive(Eq,
    Debug
    )

    RequestPreferences::RequestPreferences

    fn RequestPreferences::RequestPreferences(accept? : StringView, accept_language? : StringView, accept_encoding? : StringView, accept_charset? : StringView) -> RequestPreferences

    ResponseNegotiation

    pub(all) struct ResponseNegotiation {
    decision : VariantDecision
    summary : ResponseSummary
    headers : Array[Param]
    header_block : String
    } derive(Eq,
    Debug
    )

    ResponseSummary

    pub(all) struct ResponseSummary {
    status : Int
    content_type : String?
    content_language : String?
    content_encoding : String?
    charset : String?
    vary : Array[String]
    explanation : String
    } derive(Eq,
    Debug
    )

    ServerVariant

    pub(all) struct ServerVariant {
    id : String
    media : String
    language : String?
    encoding : String?
    charset : String?
    server_weight : Int
    index : Int
    } derive(Eq,
    Debug
    )

    ServerVariant::ServerVariant

    fn ServerVariant::ServerVariant(id : StringView, media : StringView, language? : StringView, encoding? : StringView, charset? : StringView, server_weight? : Int, index? : Int) -> ServerVariant

    VariantCandidate

    pub(all) struct VariantCandidate {
    variant : ServerVariant
    media : MediaCandidate?
    language : LanguageCandidate?
    encoding : EncodingCandidate?
    charset : CharsetCandidate?
    quality : Int
    reason : String
    } derive(Eq,
    Debug
    )

    VariantDecision

    pub(all) struct VariantDecision {
    selected : ServerVariant?
    quality : Int
    matches : Array[VariantCandidate]
    vary : Array[String]
    explanation : String
    } derive(Eq,
    Debug
    )

    api_preferences_json_zh_gzip

    fn api_preferences_json_zh_gzip() -> RequestPreferences

    api_response_summary

    fn api_response_summary() -> Result[ResponseSummary, ParseError]

    api_variants

    fn api_variants() -> Array[ServerVariant]

    audit_accept

    fn audit_accept(header : StringView) -> HeaderAudit

    audit_accept_charset

    fn audit_accept_charset(header : StringView) -> HeaderAudit

    audit_accept_encoding

    fn audit_accept_encoding(header : StringView) -> HeaderAudit

    audit_accept_language

    fn audit_accept_language(header : StringView) -> HeaderAudit

    audit_api_scenario

    fn audit_api_scenario() -> HeaderAudit

    audit_browser_scenario

    fn audit_browser_scenario() -> HeaderAudit

    audit_cache_control_header

    fn audit_cache_control_header(header : StringView) -> HeaderAudit

    audit_preferences

    fn audit_preferences(preferences : RequestPreferences) -> HeaderAudit

    audit_vary_header

    fn audit_vary_header(header : StringView) -> HeaderAudit

    browser_preferences_html_en_br

    fn browser_preferences_html_en_br() -> RequestPreferences

    browser_response_summary

    fn browser_response_summary() -> Result[ResponseSummary, ParseError]

    build_response_variants

    fn build_response_variants(media_offers : Array[String], language_offers : Array[String], encoding_offers : Array[String], charset_offers : Array[String]) -> Array[ServerVariant]

    cache_control_has

    fn cache_control_has(directives : Array[CacheDirective], name : StringView) -> Bool

    cache_control_value

    fn cache_control_value(directives : Array[CacheDirective], name : StringView) -> String?

    cache_is_storable

    fn cache_is_storable(summary : CachePolicySummary) -> Bool

    cache_private_fresh_lifetime

    fn cache_private_fresh_lifetime(summary : CachePolicySummary) -> Int?

    cache_requires_revalidation

    fn cache_requires_revalidation(summary : CachePolicySummary) -> Bool

    cache_shared_fresh_lifetime

    fn cache_shared_fresh_lifetime(summary : CachePolicySummary) -> Int?

    canonical_charset

    fn canonical_charset(input : StringView) -> String

    charset_candidate_line

    fn charset_candidate_line(candidate : CharsetCandidate) -> String

    charset_is_acceptable

    fn charset_is_acceptable(accept_charset : StringView, charset : StringView) -> Result[Bool, ParseError]

    conservative_preferences

    fn conservative_preferences() -> RequestPreferences

    decision_vary_header

    fn decision_vary_header(decision : VariantDecision) -> String

    document_variants

    fn document_variants() -> Array[ServerVariant]

    encoding_candidate_line

    fn encoding_candidate_line(candidate : EncodingCandidate) -> String

    encoding_is_acceptable

    fn encoding_is_acceptable(accept_encoding : StringView, coding : StringView) -> Result[Bool, ParseError]

    etag_list_matches

    fn etag_list_matches(header_value : StringView, current : EntityTag, strong? : Bool) -> Result[Bool, ParseError]

    evaluate_if_match

    fn evaluate_if_match(header_value : StringView, current : EntityTag) -> Result[ConditionalDecision, ParseError]

    evaluate_if_none_match

    fn evaluate_if_none_match(http_method : StringView, header_value : StringView, current : EntityTag) -> Result[ConditionalDecision, ParseError]

    explain_charset_candidates

    fn explain_charset_candidates(accept_charset : StringView, offers : Array[String]) -> Result[Array[String], ParseError]

    explain_encoding_candidates

    fn explain_encoding_candidates(accept_encoding : StringView, offers : Array[String]) -> Result[Array[String], ParseError]

    explain_language_candidates

    fn explain_language_candidates(accept_language : StringView, offers : Array[String]) -> Result[Array[String], ParseError]

    explain_media_candidates

    fn explain_media_candidates(accept_header : StringView, offers : Array[String]) -> Result[Array[String], ParseError]

    explain_variant_candidates

    fn explain_variant_candidates(decision : VariantDecision) -> Array[String]

    format_audit

    fn format_audit(audit : HeaderAudit) -> String

    format_cache_control

    fn format_cache_control(directives : Array[CacheDirective]) -> String

    format_cache_summary

    fn format_cache_summary(summary : CachePolicySummary) -> String

    format_charset_decision

    fn format_charset_decision(decision : CharsetDecision) -> String

    format_conditional_decision

    fn format_conditional_decision(decision : ConditionalDecision) -> String

    format_encoding_decision

    fn format_encoding_decision(decision : EncodingDecision) -> String

    format_etag

    fn format_etag(tag : EntityTag) -> String

    format_language_decision

    fn format_language_decision(decision : LanguageDecision) -> String

    format_lines

    fn format_lines(lines : Array[String]) -> String

    format_media_decision

    fn format_media_decision(decision : MediaDecision) -> String

    format_response_headers

    fn format_response_headers(headers : Array[Param]) -> String

    format_response_summary

    fn format_response_summary(summary : ResponseSummary) -> String

    format_variant_decision

    fn format_variant_decision(decision : VariantDecision) -> String

    join_header_names

    fn join_header_names(names : Array[String]) -> String

    language_candidate_line

    fn language_candidate_line(candidate : LanguageCandidate) -> String

    media_candidate_line

    fn media_candidate_line(candidate : MediaCandidate) -> String

    merge_vary

    fn merge_vary(left : Array[String], right : Array[String]) -> Result[Array[String], ParseError]

    negotiate_api_scenario

    fn negotiate_api_scenario() -> Result[VariantDecision, ParseError]

    negotiate_browser_document_scenario

    fn negotiate_browser_document_scenario() -> Result[VariantDecision, ParseError]

    negotiate_charset

    fn negotiate_charset(accept_charset : StringView, offers : Array[String]) -> Result[CharsetDecision, ParseError]

    negotiate_conservative_api_scenario

    fn negotiate_conservative_api_scenario() -> Result[VariantDecision, ParseError]

    negotiate_encoding

    fn negotiate_encoding(accept_encoding : StringView, offers : Array[String]) -> Result[EncodingDecision, ParseError]

    negotiate_language

    fn negotiate_language(accept_language : StringView, offers : Array[String]) -> Result[LanguageDecision, ParseError]

    negotiate_media

    fn negotiate_media(accept_header : StringView, offers : Array[String]) -> Result[MediaDecision, ParseError]

    negotiate_response

    fn negotiate_response(preferences : RequestPreferences, media_offers : Array[String], language_offers : Array[String], encoding_offers : Array[String], charset_offers : Array[String]) -> Result[ResponseNegotiation, ParseError]

    negotiate_response_summary

    fn negotiate_response_summary(preferences : RequestPreferences, media_offers : Array[String], language_offers : Array[String], encoding_offers : Array[String], charset_offers : Array[String]) -> Result[ResponseSummary, ParseError]

    negotiate_variant

    fn negotiate_variant(preferences : RequestPreferences, variants : Array[ServerVariant]) -> Result[VariantDecision, ParseError]

    negotiation_matrix_report

    fn negotiation_matrix_report(preferences : RequestPreferences, variants : Array[ServerVariant]) -> Result[String, ParseError]

    normalize_header_name

    fn normalize_header_name(name : StringView) -> Result[String, ParseError]

    parse_accept

    fn parse_accept(header : StringView) -> Result[Array[MediaRange], ParseError]

    parse_accept_charset

    fn parse_accept_charset(header : StringView) -> Result[Array[CharsetRange], ParseError]

    parse_accept_encoding

    fn parse_accept_encoding(header : StringView) -> Result[Array[EncodingRange], ParseError]

    parse_accept_language

    fn parse_accept_language(header : StringView) -> Result[Array[LanguageRange], ParseError]

    parse_cache_control

    fn parse_cache_control(header : StringView) -> Result[Array[CacheDirective], ParseError]

    parse_cache_policy

    fn parse_cache_policy(header : StringView) -> Result[CachePolicySummary, ParseError]

    parse_charset

    fn parse_charset(value : StringView) -> Result[CharsetOffer, ParseError]

    parse_content_encoding

    fn parse_content_encoding(value : StringView) -> Result[EncodingOffer, ParseError]

    parse_content_type

    fn parse_content_type(value : StringView) -> Result[MediaOffer, ParseError]

    parse_etag

    fn parse_etag(value : StringView) -> Result[EntityTag, ParseError]

    parse_etag_list

    fn parse_etag_list(value : StringView) -> Result[Array[EntityTag], ParseError]

    parse_header_list

    fn parse_header_list(value : StringView) -> Result[Array[String], ParseError]

    parse_language_tag

    fn parse_language_tag(tag : StringView) -> Result[LanguageOffer, ParseError]

    parse_vary

    fn parse_vary(value : StringView) -> Result[Array[String], ParseError]

    quality_to_string

    fn quality_to_string(q : Int) -> String

    response_header_block

    fn response_header_block(summary : ResponseSummary) -> String

    response_header_value

    fn response_header_value(summary : ResponseSummary, name : StringView) -> String?

    response_headers

    fn response_headers(summary : ResponseSummary) -> Array[Param]

    scenario_report

    fn scenario_report() -> String

    strong_etag_match

    fn strong_etag_match(left : EntityTag, right : EntityTag) -> Bool

    summarize_cache_control

    fn summarize_cache_control(directives : Array[CacheDirective]) -> Result[CachePolicySummary, ParseError]

    summarize_response

    fn summarize_response(decision : VariantDecision) -> ResponseSummary

    variant_candidate_line

    fn variant_candidate_line(candidate : VariantCandidate) -> String

    vary_contains

    fn vary_contains(vary : Array[String], field : StringView) -> Bool

    weak_etag_match

    fn weak_etag_match(left : EntityTag, right : EntityTag) -> Bool