moonbit-project

    MoonBit HTTP recording, replay, response contract validation, and offline testing library

    http
    testing
    record-replay
    offline
    Download zip
    Version
    0.3.2
    License
    Apache-2.0
    Last updated
    1 hour ago
    Downloads
    55

    #MoonVCR

    MoonVCR:MoonBit HTTP 交互录制回放与离线契约测试库

    MoonVCR 把经过允许的 HTTP 请求和响应保存为可审阅的 JSON cassette,随后在开发机或 CI 中严格离线回放。它解决 SDK、内部 API 和服务端测试依赖真实网络、第三方服务不稳定、测试凭据容易泄露、接口变更难定位的问题。

    项目由两层组成:

    • 无 IO 核心:数据模型、确定性匹配、Record/Replay/StrictOffline 会话、脱敏、诊断和响应契约,可在 wasm 环境运行;
    • native 适配层:基于官方 moonbitlang/async 的真实 HTTP 客户端和 cassette 文件读写,当前由 Ubuntu GitHub CI 验证。

    MoonVCR 不拦截系统流量。录制只会调用使用者显式选择的 transport;Replay 和 StrictOffline 不会回退联网。

    #当前状态

    • 当前源码修复版本:0.3.2;发布状态与对应 SHA/CI 证据见 FINAL_ACCEPTANCE.md;
    • 0.3.1 及更早版本存在已确认的脱敏绕过或错误匹配,不能依据旧 CI 全绿认定安全;请升级并审阅重录旧档案;
    • 0.3.2 对独立审核发现的七类问题补充修复,包括精确 JSON 路径、重复参数顺序、native 头信息、有界文件读取和诊断一致性;逐项说明见 AUDIT_REMEDIATION_0.3.2.md,兼容行为见 MIGRATION.md。

    #已实现能力

    • 版本化 Request、Response、Interaction 和 Cassette 模型;
    • 确定性 JSON 编解码,拒绝损坏 JSON、错误结构和未知格式版本;
    • 方法、URL/query、选定 header 和 body 的确定性匹配;
    • 无分隔符歧义的长度编码匹配键,以及重复交互按顺序消费;
    • Record、Replay、StrictOffline 会话,剩余交互计数和完整消费断言;
    • URL 用户信息、fragment、百分号编码敏感参数、常见敏感 header/query 脱敏;
    • 请求与响应独立的必填/可选 JSON 路径脱敏,失败时不写入 cassette;
    • 不直接回显请求/响应值的 replay miss 与 mismatch;契约报告包含配置的规则和字段路径,不应在这些名称中配置凭据;
    • 状态码、header 存在性/精确值、body 类型、必填/可选 JSON 字段和数组元素契约;
    • native 真实 HTTP GET/POST、header、JSON、二进制 body、连接失败和超时转换;
    • native cassette 大小限制、默认禁止覆盖、显式替换、同目录临时文件和同步写入;
    • 分页 SDK、订单兼容性、受限网络 CI 三组成功与故障场景;
    • 100、1,000、10,000 条交互的确定性容量验收。

    #安装

    要求 MoonBit 编译器 moonc >= 0.10.14。先运行 moon version --all,注意核对其中的 moonc 行;工具链过旧时运行 moon upgrade。CI 在检查和 native 两个作业开始时都会执行最低版本门禁。

    安装 0.3.2(正式发布状态以 FINAL_ACCEPTANCE.md 为准):

    moon add chenqi-arch/moonbit-project@0.3.2

    Mooncakes 发布包、Git 标签和 GitHub 验收证据见 FINAL_ACCEPTANCE.md。

    最新赛事九项标准与证据逐条对应见 COMPETITION_ACCEPTANCE.md。PowerShell 下可运行 ./scripts/check-toolchain.ps1 核对最低编译器版本,运行 ./scripts/verify-public-consumer.ps1 自动复现下述快速开始;两个脚本也由 Ubuntu CI 执行。

    #核心快速开始

    下面是可以完整运行的独立消费者。先用 moon new moonvcr-consumer 创建项目,在该目录运行上面的 moon add 命令。将 cmd/main/moon.pkg 改为:

    import { "chenqi-arch/moonbit-project" @moonvcr, } pkgtype(kind: "executable")

    将 cmd/main/main.mbt 改为:

    fn main { let cassette_text = @moonvcr.Cassette::encode(@moonvcr.example_cassette()) let cassette = @moonvcr.Cassette::decode(cassette_text) catch { _ => abort("cassette decode failed") } let request : @moonvcr.Request = { method: "GET", url: "https://api.example.test/v1/items?page=1", headers: [{ name: "accept", value: "application/json", }], body: @moonvcr.Empty, } let session = @moonvcr.Session::with_defaults( cassette, @moonvcr.StrictOffline, ) let response = match session.replay(request) { Ok(response) => response Err(error) => abort(error.summary()) } guard response.status == 200 else { abort("unexpected status") } match session.assert_complete() { Ok(_) => () Err(error) => abort(error.summary()) } println("offline consumer passed status=200") }

    运行 moon check、moon build、moon run cmd/main,应输出 offline consumer passed status=200。该示例使用内存档案,合成域名不会被访问。

    Session::replay 没有 transport 参数,因此不具备联网回退路径。需要与既有同步 transport 兼容时可使用 Session::send;Replay/StrictOffline 分支仍不调用 transport。

    #显式录制与脱敏

    let request_rules = RedactionConfig::default()
    request_rules.json_paths.push("/credentials/token")
    let response_rules = RedactionConfig::default()
    response_rules.optional_json_paths.push("/session/token")

    let session = Session::new_with_redaction_policy(
    Cassette::empty(),
    Record,
    MatchConfig::default(),
    { request: request_rules, response: response_rules, },
    )
    let result = session.record_response(request, response)
    let text = session.cassette().encode()

    json_paths 是必填规则:路径缺失、路径非法或 body 不是有效 Text JSON 时失败关闭,当前交互不会写入 cassette。optional_json_paths 只允许有效 Text JSON 中的字段缺失;它不允许 Empty、Base64、损坏 JSON 或非法路径绕过检查。任一 JSON 规则存在时,非 Text Body 一律拒绝,不会尝试猜测解码。未配置 JSON 规则时,Empty/Text/Base64 仍按原样保留,默认不会自动识别 Body 内的秘密。

    GET 空 Body 与 POST JSON 应使用独立的请求/响应策略和会话,不能把 POST 的必填路径直接套在空 GET 请求上。完整真实示例见 cmd/moonvcr-native-record。录制成功仍把原始响应交给调用方业务代码,脱敏保护的是保存的副本。

    query 名称单次 UTF-8 百分号解码后转小写;URL 原始 + 视为空格,%2B 视为字面加号,自定义规则中的 + 按字面解释。无效编码保持原样,不递归解码。URL fragment 完全丢弃,userinfo 全部替换。

    JSON Pointer 路径必须原文以 / 开始,键中的空格、Tab、换行和 ~0/~1 转义精确保留;只有 dot shorthand 会裁剪格式空白。本库支持字段寻址子集,空字符串根路径不支持,/ 可以定位空对象键。数组只接受 0 或无前导零的十进制索引;对象键 "01" 仍合法。必填/可选规则先在原始 JSON 上验证,再按深度从深到浅覆盖,父子重叠规则不受配置次序影响。

    匹配允许不同 query 名称、不同 header 名称换序,但保留每个同名组内部的值顺序;同名 query 的编码别名也归到同组。query 值不裁剪空白。不同名称之间的全局顺序不参与匹配;依赖该顺序的接口不在当前匹配策略范围内。原始百分号拼写仍参与最终匹配,等价编码可能保守地未命中。

    #响应契约

    let contract : ResponseContract = {
    allowed_statuses: [200, 201],
    required_headers: ["content-type"],
    required_header_values: [
    { name: "content-type", expected_value: "application/json", },
    ],
    body_kind: Some(ExpectTextBody),
    json_fields: [{ path: "/id", expected_kind: JsonNumber, }],
    optional_json_fields: [{ path: "/note", expected_kind: JsonString, }],
    json_array_elements: [{ path: "/items", expected_kind: JsonObject, }],
    }
    let report = response.validate_contract(contract)
    guard report.is_valid() else { abort(report.summary()) }

    报告包含规则、路径、类型、长度和非密码学短指纹,不复制响应值。指纹可被枚举,不提供加密或匿名化保证;不要公开含敏感结构信息的诊断。该能力是面向 cassette 的小型确定性检查,不是完整 OpenAPI/JSON Schema 实现,也不会自动监测未录制的线上变化。

    #Native HTTP 与文件档案

    已发布的 native 包使用 moonbitlang/async@0.22.1;以下为异步函数内的 API 片段,完整入口见 cmd/moonvcr-native-record 与 cmd/moonvcr-native-replay:

    let response = @native.send_http_with_timeout(request, 2_000)
    let recorded = @native.record_http(session, request, timeout_millis=2_000)
    @native.save_cassette_new("fixtures/orders.json", session.cassette())
    let cassette = @native.load_cassette_default("fixtures/orders.json")

    • 超时、连接/协议失败、非法方法和非法 body 被转换为不含 URL/凭据的安全错误;
    • 非 2xx 和 3xx 原样返回给业务契约判断;
    • TLS 证书验证使用官方客户端默认值,没有“关闭证书验证”选项;
    • 不承诺自动跟随重定向,验收固定检查 302 保持为 302;
    • native 底层头部 Map 无法无损发送重复请求头,因此同名请求头(大小写不敏感)会在发网前返回 UnsupportedDuplicateHeaders,不能静默覆盖;
    • 原始业务响应保留各个 Set-Cookie 及公开解析属性;按依赖解析结果规范化重建,不保证 HTTP 字节拼写。录制副本才按规则逐条脱敏;
    • save_cassette_new 默认拒绝覆盖,save_cassette_replace 才允许显式替换;
    • 默认加载上限为 10 MB,同一打开句柄最多读取上限加一字节,在 UTF-8/JSON 解码前拒绝超限;文件增长/路径替换不绕过读取预算。TooLarge(n) 的 n 是观察到的字节数下界,不是完整文件大小。

    record_response 只允许 Record 模式;其他模式返回 InvalidMode。native.record_http 遇到 Replay/StrictOffline 时直接离线回放,不执行 HTTP;显式 send_http 仍是调用方主动请求网络的入口。

    #可运行场景

    成功场景:

    moon run cmd/moonvcr-pagination moon run cmd/moonvcr-order-contract moon run cmd/moonvcr-restricted-ci

    预期输出:

    pagination SDK offline regression passed pages=2 order compatibility contract passed status headers JSON array optional-field restricted-network CI passed network_calls=0

    故障场景必须返回非零:

    moon run cmd/moonvcr-pagination-failure moon run cmd/moonvcr-order-contract-failure moon run cmd/moonvcr-restricted-ci-failure

    它们分别证明请求变化会 ReplayMiss、订单字段/header 违约会阻断、严格离线未命中不会调用 transport。

    #验收命令

    moon fmt --check moon check moon build moon test moon run cmd/moonvcr-demo moon run cmd/moonvcr-pagination moon run cmd/moonvcr-order-contract moon run cmd/moonvcr-restricted-ci moon run cmd/moonvcr-security-acceptance moon package --list

    native 真 HTTP 验收由 GitHub Ubuntu CI 自动完成:启动 MoonBit loopback 服务,验证 GET、POST、header、JSON、二进制、超时、连接失败、非 2xx 和 302;随后真实录制并保存 cassette,关闭服务,再由新进程加载并严格离线回放。

    #支持矩阵

    能力wasm(Windows 本地已验证)Linux native(GitHub CI)Windows native
    核心模型/匹配/会话/脱敏/契约支持支持类型检查通过
    内存与脚本 transport支持支持类型检查通过
    官方 async HTTP + 文件 IO不适用支持并集成验收未声明运行支持
    跨进程文件回放不适用支持并集成验收未声明运行支持

    本机 Windows 缺少 C 编译器,因此只完成 native 类型检查;运行支持只声明 CI 实测的 Ubuntu。JS/Node、macOS 和 Windows native 当前不列为已支持。

    #主要 API

    • Cassette::empty/decode/encode
    • MatchConfig::default
    • RedactionConfig::default、RedactionPolicy::default/symmetric
    • Session::with_defaults/new/new_with_redaction/new_with_redaction_policy
    • Session::send/record_response/replay/diagnose/cassette
    • Session::remaining_interactions/assert_complete
    • Response::validate_contract、ContractReport::is_valid/summary
    • ScriptedTransport::new/send/remaining
    • native:send_http/send_http_with_timeout/record_http
    • native:load_cassette/load_cassette_default/save_cassette_new/save_cassette_replace

    完整接口可用 moon info --target native 生成;根包接口 pkg.generated.mbti 保存在仓库中。Session 内部字段私有,请用 cassette() 取得隔离副本,用 mode() 查看模式。

    #故障排查

    • ReplayMiss:调用 session.diagnose(request);检查 URL/query、选定 header、body 策略和重复调用次数;
    • IncompleteReplay:有录制交互未被业务流程消费;
    • RedactionFailed:必填 JSON 路径缺失、路径无效或 body 不是 JSON;
    • HTTP request timed out:增大明确期限或排查服务延迟;
    • cassette JSON decode failed:检查文件是否截断、结构和格式版本;
    • native 构建提示无 C 编译器:安装受支持的 C 工具链,或使用 Ubuntu CI 验收。

    #边界与限制

    • 不做 TLS MITM、系统代理、浏览器录制或任意进程流量拦截;
    • 不自动发现所有业务敏感字段;
    • native HTTP 当前使用依赖的完整响应 API,没有响应字节数硬上限;只应对可信、大小受控的服务录制,cassette 文件上限不约束网络响应大小;
    • Cassette::encode、文件保存和手工构造的 Request/Response 不是自动脱敏入口;应通过 Record 会话录制、审阅副本后保存,不能直接序列化原始数据;
    • 不实现完整 OpenAPI/JSON Schema;
    • cassette 默认加载上限 10 MB,容量验收覆盖到 10,000 条,不承诺无限规模;
    • matcher 为确定性线性候选扫描,大 cassette 应按服务或测试套件拆分;
    • 并发共享同一个可变 Session 不在当前保证范围内;
    • moonbitlang/async API 仍可能变化,依赖版本被固定并由 CI 验证。

    #开源与安全

    MoonVCR 使用 Apache License 2.0。依赖来源和许可证见 THIRD_PARTY_NOTICES.md,安全边界见 SECURITY.md,参与开发见 CONTRIBUTING.md,版本变化见 CHANGELOG.md 和 MIGRATION.md。

    所有示例均使用合成域名和合成凭据。MoonVCR_submission.md 含赛事联系方式,只保存在本地并被 Git 与发布包排除。

    CassetteError

    pub(all) suberror CassetteError {
    InvalidJson
    InvalidSchema
    UnsupportedVersion(Int)
    } derive(Eq,
    Debug
    )

    Errors raised while reading a cassette.

    Body

    pub(all) enum Body {
    Empty
    Text(String)
    Base64(String)
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A request or response body.

    BodyMatchMode

    pub(all) enum BodyMatchMode {
    Exact
    Ignore
    } derive(Eq,
    Debug
    )

    Controls which request fields participate in cassette matching.

    Cassette

    pub(all) struct Cassette {
    format_version : Int
    interactions : Array[Interaction]
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A versioned collection of recorded HTTP interactions.

    Cassette::decode

    fn Cassette::decode(text : String) -> Cassette raise CassetteError

    Decode a cassette and reject schema versions this release cannot handle.

    Cassette::empty

    fn Cassette::empty() -> Cassette

    Create an empty cassette using the current schema version.

    Cassette::encode

    fn Cassette::encode(self : Cassette) -> String

    Encode a cassette as deterministic JSON text.

    ContractBodyKind

    pub(all) enum ContractBodyKind {
    ExpectEmptyBody
    ExpectTextBody
    ExpectBase64Body
    } derive(Eq,
    Debug
    )

    The response body representation expected by an offline contract.

    ContractBodyKind::label

    fn ContractBodyKind::label(self : ContractBodyKind) -> String

    ContractReport

    pub(all) struct ContractReport {
    violations : Array[ContractViolation]
    } derive(Eq,
    Debug
    )

    Stable contract validation output suitable for CI assertions.

    ContractReport::is_valid

    fn ContractReport::is_valid(self : ContractReport) -> Bool

    ContractReport::summary

    fn ContractReport::summary(self : ContractReport) -> String

    Render deterministic, value-safe output for CI and review.

    ContractViolation

    pub(all) struct ContractViolation {
    kind : ContractViolationKind
    path : String
    expected : String
    actual : String
    } derive(Eq,
    Debug
    )

    A safe response contract difference.

    expected and actual only contain status or type descriptors. Response header values, JSON values, and body text are never copied into this type.

    ContractViolationKind

    pub(all) enum ContractViolationKind {
    StatusViolation
    MissingHeaderViolation
    HeaderValueViolation
    BodyKindViolation
    InvalidJsonViolation
    InvalidJsonPathViolation
    MissingJsonFieldViolation
    JsonTypeViolation
    } derive(Eq,
    Debug
    )

    The category of one response contract violation.

    ContractViolationKind::label

    fn ContractViolationKind::label(self : ContractViolationKind) -> String

    pub(all) struct Header {
    name : String
    value : String
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A HTTP header stored in a cassette.

    HeaderValueContract

    pub(all) struct HeaderValueContract {
    name : String
    expected_value : String
    } derive(Eq,
    Debug
    )

    One required response header value. The value is used only for an exact comparison; reports expose a length and fingerprint instead of the value.

    Interaction

    pub(all) struct Interaction {
    request : Request
    response : Response
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    One request/response pair in a cassette.

    JsonArrayElementContract

    pub(all) struct JsonArrayElementContract {
    path : String
    expected_kind : JsonValueKind
    } derive(Eq,
    Debug
    )

    A rule for the direct elements of a JSON array field. The array itself is located by path; every element must have expected_kind.

    JsonFieldContract

    pub(all) struct JsonFieldContract {
    path : String
    expected_kind : JsonValueKind
    } derive(Eq,
    Debug
    )

    One required JSON field and its expected value kind.

    Paths use the same JSON Pointer or dot shorthand accepted by redaction.

    JsonValueKind

    pub(all) enum JsonValueKind {
    JsonNull
    JsonBoolean
    JsonNumber
    JsonString
    JsonArray
    JsonObject
    } derive(Eq,
    Debug
    )

    JSON value kinds supported by field-level response contracts.

    JsonValueKind::label

    fn JsonValueKind::label(self : JsonValueKind) -> String

    MatchCandidate

    pub(all) struct MatchCandidate {
    index : Int
    key : String
    } derive(Eq,
    Debug
    )

    A cassette entry considered during request matching.

    key is a value-safe summary. It is intentionally not the internal normalized matching key, so a replay error cannot expose recorded headers, query values, or request bodies.

    MatchConfig

    pub(all) struct MatchConfig {
    include_headers : Array[String]
    ignore_headers : Array[String]
    body_mode : BodyMatchMode
    } derive(Eq,
    Debug
    )

    Header and body matching policy for a session.

    MatchConfig::default

    fn MatchConfig::default() -> MatchConfig

    Return the conservative default matching policy.

    MatchResult

    pub(all) enum MatchResult {
    Matched(Int)
    NoMatch(Array[MatchCandidate])
    } derive(Eq,
    Debug
    )

    The result of matching a request against the unconsumed cassette entries.

    Mismatch

    pub(all) struct Mismatch {
    candidate_index : Int
    kind : MismatchKind
    expected : String
    actual : String
    } derive(Eq,
    Debug
    )

    One safe, structured difference for a candidate cassette entry.

    MismatchKind

    pub(all) enum MismatchKind {
    NoCandidate
    Method
    Url
    Query
    Headers
    Body
    Sequence
    } derive(Eq,
    Debug
    )

    The field that prevented a request from matching a cassette entry.

    MismatchKind::label

    fn MismatchKind::label(self : MismatchKind) -> String

    MismatchReport

    pub(all) struct MismatchReport {
    request_key : String
    differences : Array[Mismatch]
    } derive(Eq,
    Debug
    )

    A deterministic mismatch report suitable for test output.

    MismatchReport::summary

    fn MismatchReport::summary(self : MismatchReport) -> String

    Render a stable human-readable report. Values are already reduced to safe descriptors by the diagnostic builder.

    RedactionConfig

    pub(all) struct RedactionConfig {
    headers : Array[String]
    query_parameters : Array[String]
    json_paths : Array[String]
    optional_json_paths : Array[String]
    } derive(Eq,
    Debug
    )

    Additional redaction rules supplied by the caller.

    Header and query names are compared case-insensitively. json_paths accepts JSON Pointer paths such as /credentials/token and a dot shorthand such as credentials.token. Array indexes are supported by JSON Pointer segments.

    RedactionConfig::default

    Conservative defaults for credentials commonly sent by HTTP clients.

    RedactionError

    pub(all) enum RedactionError {
    InvalidJson(String)
    InvalidPath(String)
    MissingPath(String)
    } derive(Eq,
    Debug
    )

    Errors raised when a configured value cannot be removed safely.

    RedactionPolicy

    pub(all) struct RedactionPolicy {
    request : RedactionConfig
    response : RedactionConfig
    } derive(Eq,
    Debug
    )

    Independent request and response rules for a recorded interaction.

    Keeping the two sides separate avoids requiring a request-only path in a response body (or vice versa). RedactionConfig remains available for the symmetric compatibility API.

    RedactionPolicy::default

    RedactionPolicy::symmetric

    Apply one configuration to both sides for compatibility with the original MoonVCR API.

    Request

    pub(all) struct Request {
    method : String
    url : String
    headers : Array[Header]
    body : Body
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    The request captured in one interaction.

    Response

    pub(all) struct Response {
    status : Int
    headers : Array[Header]
    body : Body
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    The response captured in one interaction.

    Response::validate_contract

    fn Response::validate_contract(self : Response, contract : ResponseContract) -> ContractReport

    Validate a response without performing network or file-system access.

    Violations are emitted in a stable order: status, required headers, header values, body kind, required JSON fields, optional JSON fields, then array element rules in caller-provided order.

    ResponseContract

    pub(all) struct ResponseContract {
    allowed_statuses : Array[Int]
    required_headers : Array[String]
    required_header_values : Array[HeaderValueContract]
    body_kind : ContractBodyKind?
    json_fields : Array[JsonFieldContract]
    optional_json_fields : Array[JsonFieldContract]
    json_array_elements : Array[JsonArrayElementContract]
    } derive(Eq,
    Debug
    )

    A small, transport-independent contract for a recorded or replayed response. Empty arrays mean that the corresponding property is unrestricted.

    ResponseContract::permissive

    Create a contract that accepts every response.

    ScriptedTransport

    pub(all) struct ScriptedTransport {
    responses : Array[Response]
    cursor : Int
    } derive(
    Debug
    )

    A deterministic transport adapter backed by a finite response script.

    The adapter is useful for examples and tests: it never opens a socket, and once the script is exhausted it returns a typed error instead of guessing a response.

    ScriptedTransport::new

    ScriptedTransport::remaining

    fn ScriptedTransport::remaining(self : ScriptedTransport) -> Int

    ScriptedTransport::send

    Return the next scripted response, or fail when no response remains.

    Session

    pub struct Session {
    // private fields
    }

    Stateful record/replay session. Replay never invokes its transport.

    Session::assert_complete

    fn Session::assert_complete(self : Session) -> Result[Unit, SessionError]

    Fail a replay assertion when the caller did not consume the whole cassette.

    Session::cassette

    fn Session::cassette(self : Session) -> Cassette

    Return the cassette, including interactions recorded so far.

    Session::diagnose

    fn Session::diagnose(self : Session, request : Request) -> MismatchReport

    Explain why a request would miss the current cassette.

    Session::mode

    fn Session::mode(self : Session) -> SessionMode

    Session::new

    fn Session::new(cassette : Cassette, mode : SessionMode, config : MatchConfig) -> Session

    Create a session with an explicit matching policy.

    Session::new_with_redaction

    fn Session::new_with_redaction(cassette : Cassette, mode : SessionMode, config : MatchConfig, redaction : RedactionConfig) -> Session

    Create a session with explicit matching and redaction policies.

    Session::new_with_redaction_policy

    fn Session::new_with_redaction_policy(cassette : Cassette, mode : SessionMode, config : MatchConfig, redaction : RedactionPolicy) -> Session

    Create a session whose request and response redaction rules are independent.

    Session::record_response

    fn Session::record_response(self : Session, request : Request, response : Response) -> Result[Response, SessionError]

    Persist one response obtained by an external, possibly asynchronous transport. The response returned to the caller remains unredacted.

    Session::remaining_interactions

    fn Session::remaining_interactions(self : Session) -> Int

    Number of replay entries that have not been consumed yet.

    Session::replay

    fn Session::replay(self : Session, request : Request) -> Result[Response, SessionError]

    Replay one request without accepting a transport callback. This is useful to platform adapters that need an asynchronous HTTP layer.

    Session::send

    fn Session::send(self : Session, request : Request, transport : (Request) -> Result[Response, TransportError]) -> Result[Response, SessionError]

    Send one request through the selected mode.

    Session::with_defaults

    fn Session::with_defaults(cassette : Cassette, mode : SessionMode) -> Session

    Create a session using the conservative default matching policy.

    SessionError

    pub(all) enum SessionError {
    ReplayMiss(Array[MatchCandidate])
    TransportFailed(TransportError)
    RedactionFailed(RedactionError)
    IncompleteReplay(Int)
    InvalidMode
    } derive(Eq,
    Debug
    )

    Errors returned by the record/replay session.

    SessionError::summary

    fn SessionError::summary(self : SessionError) -> String

    Render a safe summary without copying transport messages or secrets.

    SessionMode

    pub(all) enum SessionMode {
    Record
    Replay
    StrictOffline
    } derive(Eq,
    Debug
    )

    Selects whether a session records through a transport or serves a cassette.

    TransportError

    pub(all) enum TransportError {
    Failed(String)
    } derive(Eq)

    A transport adapter supplied by the host application.

    CURRENT_FORMAT_VERSION

    let CURRENT_FORMAT_VERSION : Int

    The cassette schema supported by this release.

    REDACTION_MARKER

    let REDACTION_MARKER : String

    The fixed marker written in place of a value that must not enter a cassette.

    diagnose_mismatch

    fn diagnose_mismatch(request : Request, cassette : Cassette, config : MatchConfig, consumed : Array[Bool]) -> MismatchReport

    Build a report using the default conservative redaction rules.

    diagnose_mismatch_with_redaction

    fn diagnose_mismatch_with_redaction(request : Request, cassette : Cassette, config : MatchConfig, consumed : Array[Bool], redaction : RedactionConfig) -> MismatchReport

    Build a safe mismatch report while honoring consumed-entry sequence.

    example_cassette

    fn example_cassette() -> Cassette

    Return a small, useful example for documentation and tests.

    match_candidates

    fn match_candidates(request : Request, cassette : Cassette, config : MatchConfig, consumed : Array[Bool]) -> Array[MatchCandidate]

    Return every unconsumed entry with a value-safe summary for diagnostics.

    match_request

    fn match_request(request : Request, cassette : Cassette, config : MatchConfig, consumed : Array[Bool]) -> MatchResult

    Match the first unconsumed interaction with an identical normalized key.

    normalize_request

    fn normalize_request(request : Request, config : MatchConfig) -> String

    Build a deterministic, transport-independent key for a request.

    normalize_url

    fn normalize_url(url : String) -> String

    Remove fragments and order different query names without changing the relative order of repeated names or trimming query values.

    redact_interaction

    fn redact_interaction(interaction : Interaction, config : RedactionConfig) -> Result[Interaction, RedactionError]

    Redact both sides of an interaction before it is persisted.

    redact_interaction_with_policy

    fn redact_interaction_with_policy(interaction : Interaction, policy : RedactionPolicy) -> Result[Interaction, RedactionError]

    Redact a recorded request and response with independent policies.

    redact_request

    fn redact_request(request : Request, config : RedactionConfig) -> Result[Request, RedactionError]

    Return a request safe to store in a cassette.

    redact_response

    fn redact_response(response : Response, config : RedactionConfig) -> Result[Response, RedactionError]

    Return a response safe to store in a cassette.

    request_match_key

    fn request_match_key(request : Request, config : MatchConfig) -> String

    Build the same key used by the matcher for diagnostics and tooling.

    request_safe_summary

    fn request_safe_summary(request : Request) -> String

    Describe a request without copying URL, header, query, or body values. This is suitable for public candidate errors.

    Powered by MoonBit

    Site sourceReport issueModulesSkillsBuild queueStatistics

    © 2026 mooncakes.io