moon-problem-details

RFC 9457 Problem Details JSON parser, serializer, validator, registry and audit toolkit for MoonBit.

problem-details
rfc9457
http
api
json
error
moonbit
moon add 6P66006/moon-problem-details@0.1.0-dev
Download zip
Author
Version
0.1.0-dev
License
Apache-2.0
Last updated
4 days ago
Downloads
1
README

#moon-problem-details

Module: 6P66006/moon-problem-details Version: 0.1.0-dev Status: public mature MVP Repository: https://github.com/6P66006/moon-problem-details Maintainer: 崔瑞豪 (3911798739@qq.com)

#Overview

moon-problem-details is an offline MoonBit toolkit for RFC 9457 Problem Details JSON. It parses, preserves, builds, validates, canonicalizes, audits and queries registered HTTP problem types without sending HTTP requests or dereferencing URIs.

#Quick start

Check the package and run the parsing example from the repository root:

moon check --target wasm-gc moon run ./examples/parse --target wasm-gc

For a complete cross-target verification, including tests and CLI smoke checks, run ./scripts/verify_all.ps1.

#Why Problem Details

Problem Details gives HTTP APIs a shared error-document shape while allowing each problem type to define extension members. This library keeps transport status separate: the JSON status member is advisory and never replaces the actual HTTP response status.

#RFC 9457 scope

The mature local MVP covers application/problem+json, the five standard members, extension members, about:blank, URI-reference syntax checks, deterministic serialization, HTTP response context checking, resource limits, structured errors and an offline IANA registry. RFC 9457, which obsoletes RFC 7807, is the implementation target.

#Problem model

ProblemDetails keeps type, title, status, detail and instance optional exactly as received. effective_type() returns about:blank when raw type is absent without modifying the parsed model.

#Parsing

Use parse_problem_json for text or parse_problem_json_bytes for raw UTF-8. MoonBit core parses JSON. A malformed JSON document or non-object root is a structured error. A standard member with the wrong JSON type is ignored and reported in ProblemParseResult.diagnostics, so other members continue to parse as RFC 9457 requires.

#Standard and extension members

Unknown object members are preserved as arbitrary Json values, including strings, numbers, booleans, nulls, arrays and objects. extension, extensions and has_extension query them. Builder extensions cannot collide with standard member names.

#about:blank

Missing type has the effective value about:blank. is_about_blank handles both missing and explicit forms. A nonstandard title for about:blank is an audit warning only because localization is permitted.

#Validation and HTTP context

validate_problem checks URI-reference syntax and the HTTP status range. validate_context checks application/problem+json, status mismatch and whether relative type or instance values have an absolute base URI. The caller supplies context; the library has no server or client.

#Building and serialization

ProblemBuilder::new() supports fluent standard members and arbitrary JSON extensions. problem(status, title) and about_blank(status) are conveniences. serialize_problem emits standard members in type, title, status, detail, instance order and top-level extensions in code-unit lexical order. Object order has no JSON semantics; ordering exists only for reproducibility.

#IANA registry

The vendored official HTTP Problem Types CSV is queried through is_registered_problem_type, lookup_problem_type and registered_problem_types. Runtime operation is entirely offline. The refresh script contacts IANA only with explicit --refresh; verification checks SHA-256, record count and generated data freshness.

Verify the committed snapshot without network access:

python ./scripts/verify_iana_snapshot.py

Maintainers may explicitly download a newer official CSV with python ./scripts/import_iana_problem_types.py --refresh. After reviewing the registry changes, regenerate generated_problem_types.mbt, update the recorded metadata and rerun the offline verifier. Normal builds, tests and CLI commands never invoke the refresh path.

#Audit and security

audit_problem reports advisory findings such as relative URIs, suspicious extension names, missing members, overlong text and debug-looking names. It is heuristic, not a sensitive-data detector. Do not put passwords, tokens, secrets, stack traces, SQL, server paths, internal hosts or private user data in titles, details or extensions.

#CLI

problem-tool provides exactly these commands: parse, validate, canonicalize, make, registry, audit, stats, plus version and help. All commands are offline and emit deterministic JSON where practical.

moon run ./cmd/problem-tool -- parse --input '{"status":404}' moon run ./cmd/problem-tool -- validate --status 404 --input '{"status":404}' moon run ./cmd/problem-tool -- registry about:blank moon run ./cmd/problem-tool -- make --status 404 --title "Not Found"

Wrongly typed standard members are ignored with diagnostics instead of aborting the document:

moon run ./cmd/problem-tool -- parse --input '{"title":123,"status":404,"request_id":"abc"}'

The result retains status and request_id, omits the invalid title, and reports one diagnostic.

#Examples and testing

Four executable examples under examples/ demonstrate parsing, building, validation and audit. Tests cover RFC-style fixtures, wrong-type member behavior, extensions, limits, the registry, 800 deterministic property cases and every byte prefix of complex UTF-8 documents. Run the fail-fast gate with:

.\scripts\verify_all.ps1

It formats, checks and tests wasm-gc, JS and native; smoke-tests every CLI command and example; verifies the snapshot; counts code; and lists the package.

#Limitations

No application/problem+xml, XML, HTTP server/client, automatic URI dereference, full RFC 3986 resolver, JSON Schema, OpenAPI, exception framework or sensitive-data guarantee. See docs/limitations.md.

#License and local development status

Apache-2.0. The source repository is public on GitHub. The module remains a development version and has not been published to Mooncakes.

#
ProblemError

pub(all) suberror ProblemError {
ProblemError(ProblemErrorStage, ProblemErrorKind, String, Int?)
}

A structured error. JSON parser offsets are not invented when unavailable.

#
ProblemError::byte_offset

fn ProblemError::byte_offset(self : ProblemError) -> Int?

#
ProblemError::context

fn ProblemError::context(self : ProblemError) -> String

#
ProblemError::kind

#
ProblemError::stage

#
ProblemError::to_string

fn ProblemError::to_string(self : ProblemError) -> String

#
AuditIssue

pub struct AuditIssue {
kind : AuditIssueKind
severity : AuditSeverity
context : String
} derive(Eq,
Debug
)

#
AuditIssue::context

fn AuditIssue::context(self : AuditIssue) -> String

#
AuditIssue::kind

#
AuditIssue::severity

fn AuditIssue::severity(self : AuditIssue) -> AuditSeverity

#
AuditIssueKind

pub(all) enum AuditIssueKind {
RelativeTypeUri
RelativeInstanceUri
SuspiciousExtensionName
OverlongTitle
OverlongDetail
AboutBlankTitleMismatch
MissingTitle
MissingStatus
SensitiveLookingExtension
DebugInformationRisk
} derive(Eq,
Debug
)

Audit advice and heuristics; these are not parser errors.

#
AuditIssueKind::name

fn AuditIssueKind::name(self : AuditIssueKind) -> String

#
AuditReport

pub struct AuditReport {
issues : Array[AuditIssue]
} derive(Eq,
Debug
)

#
AuditReport::is_clean

fn AuditReport::is_clean(self : AuditReport) -> Bool

#
AuditReport::issues

fn AuditReport::issues(self : AuditReport) -> Array[AuditIssue]

#
AuditSeverity

pub(all) enum AuditSeverity {
Info
Warning
} derive(Eq,
Debug
)

#
ContextIssue

pub struct ContextIssue {
kind : ContextIssueKind
context : String
} derive(Eq,
Debug
)

#
ContextIssue::context

fn ContextIssue::context(self : ContextIssue) -> String

#
ContextIssue::kind

#
ContextIssueKind

pub(all) enum ContextIssueKind {
InvalidContentType
StatusMismatch
MissingBaseUri
InvalidBaseUri
} derive(Eq,
Debug
)

#
ContextIssueKind::name

fn ContextIssueKind::name(self : ContextIssueKind) -> String

#
ExtensionMember

pub struct ExtensionMember {
name : String
value : Json
} derive(Eq,
Debug
)

One extension member. Its value is any JSON value, without coercion.

#
ExtensionMember::name

fn ExtensionMember::name(self : ExtensionMember) -> String

#
ExtensionMember::value

fn ExtensionMember::value(self : ExtensionMember) -> Json

#
Limits

pub struct Limits {
max_input_bytes : Int
max_members : Int
max_extension_members : Int
max_string_bytes : Int
max_title_bytes : Int
max_detail_bytes : Int
max_nesting_depth : Int
} derive(Eq,
Debug
)

Parser and traversal resource limits.

#
Limits::default

fn Limits::default() -> Limits

#
Limits::max_detail_bytes

fn Limits::max_detail_bytes(self : Limits) -> Int

#
Limits::max_extension_members

fn Limits::max_extension_members(self : Limits) -> Int

#
Limits::max_input_bytes

fn Limits::max_input_bytes(self : Limits) -> Int

#
Limits::max_members

fn Limits::max_members(self : Limits) -> Int

#
Limits::max_nesting_depth

fn Limits::max_nesting_depth(self : Limits) -> Int

#
Limits::max_string_bytes

fn Limits::max_string_bytes(self : Limits) -> Int

#
Limits::max_title_bytes

fn Limits::max_title_bytes(self : Limits) -> Int

#
Limits::permissive

fn Limits::permissive() -> Limits

#
Limits::strict

fn Limits::strict() -> Limits

#
ProblemBuilder

pub struct ProblemBuilder {
type_raw : String?
title : String?
status : Int?
detail : String?
instance : String?
extensions : Array[ExtensionMember]
error : ProblemError?
}

Mutable fluent builder. Build returns a structured error on invalid state.

#
ProblemBuilder::build

#
ProblemBuilder::detail

fn ProblemBuilder::detail(self : ProblemBuilder, value : String) -> ProblemBuilder

#
ProblemBuilder::extension

fn ProblemBuilder::extension(self : ProblemBuilder, name : String, value : Json) -> ProblemBuilder

#
ProblemBuilder::instance

fn ProblemBuilder::instance(self : ProblemBuilder, value : String) -> ProblemBuilder

#
ProblemBuilder::new

#
ProblemBuilder::status

fn ProblemBuilder::status(self : ProblemBuilder, value : Int) -> ProblemBuilder

#
ProblemBuilder::title

fn ProblemBuilder::title(self : ProblemBuilder, value : String) -> ProblemBuilder

#
ProblemBuilder::type_uri

fn ProblemBuilder::type_uri(self : ProblemBuilder, value : String) -> ProblemBuilder

#
ProblemContext

pub struct ProblemContext {
actual_status : Int?
content_type : String?
base_uri : String?
} derive(Eq,
Debug
)

HTTP response metadata supplied by the caller; this library sends no HTTP.

#
ProblemDetails

pub struct ProblemDetails {
type_raw : String?
title : String?
status : Int?
detail : String?
instance : String?
extensions : Array[ExtensionMember]
} derive(Eq,
Debug
)

RFC 9457 Problem Details data model. Missing members remain missing.

#
ProblemDetails::detail

fn ProblemDetails::detail(self : ProblemDetails) -> String?

#
ProblemDetails::effective_type

fn ProblemDetails::effective_type(self : ProblemDetails) -> String

Missing type is semantically about:blank; parsing does not insert it.

#
ProblemDetails::extension

fn ProblemDetails::extension(self : ProblemDetails, name : String) -> Json?

#
ProblemDetails::extensions

#
ProblemDetails::has_extension

fn ProblemDetails::has_extension(self : ProblemDetails, name : String) -> Bool

#
ProblemDetails::instance

fn ProblemDetails::instance(self : ProblemDetails) -> String?

#
ProblemDetails::is_about_blank

fn ProblemDetails::is_about_blank(self : ProblemDetails) -> Bool

#
ProblemDetails::member_count

fn ProblemDetails::member_count(self : ProblemDetails) -> Int

#
ProblemDetails::standard_member_count

fn ProblemDetails::standard_member_count(self : ProblemDetails) -> Int

#
ProblemDetails::status

fn ProblemDetails::status(self : ProblemDetails) -> Int?

#
ProblemDetails::title

fn ProblemDetails::title(self : ProblemDetails) -> String?

#
ProblemDetails::type_raw

fn ProblemDetails::type_raw(self : ProblemDetails) -> String?

#
ProblemDiagnostic

pub struct ProblemDiagnostic {
kind : ProblemDiagnosticKind
member_name : String
expected : String
actual : String
} derive(Eq,
Debug
)

#
ProblemDiagnostic::actual

fn ProblemDiagnostic::actual(self : ProblemDiagnostic) -> String

#
ProblemDiagnostic::expected

fn ProblemDiagnostic::expected(self : ProblemDiagnostic) -> String

#
ProblemDiagnostic::kind

#
ProblemDiagnostic::member_name

fn ProblemDiagnostic::member_name(self : ProblemDiagnostic) -> String

#
ProblemDiagnosticKind

pub(all) enum ProblemDiagnosticKind {
IgnoredMemberWrongType
} derive(Eq,
Debug
)

Non-fatal interpretation diagnostic required by RFC 9457 consumer semantics.

#
ProblemErrorKind

pub(all) enum ProblemErrorKind {
InvalidJson
InvalidUtf8
RootNotObject
InvalidUriReference
InvalidPercentEncoding
InvalidStatus
DuplicateReservedExtension
LimitExceeded
InvalidBuilderState
} derive(Eq,
Debug
)

Machine-readable error category.

#
ProblemErrorKind::name

fn ProblemErrorKind::name(self : ProblemErrorKind) -> String

#
ProblemErrorStage

pub(all) enum ProblemErrorStage {
Input
Json
Member
Uri
Status
Limit
Registry
Builder
Context
} derive(Eq,
Debug
)

Structured processing stage for errors.

#
ProblemErrorStage::name

fn ProblemErrorStage::name(self : ProblemErrorStage) -> String

#
ProblemParseResult

pub struct ProblemParseResult {
problem : ProblemDetails
diagnostics : Array[ProblemDiagnostic]
} derive(Eq,
Debug
)

Successful parse plus non-fatal diagnostics.

#
ProblemParseResult::diagnostics

#
ProblemParseResult::problem

#
ProblemTypeRecord

pub struct ProblemTypeRecord {
type_uri : String
title : String
recommended_status : Int?
reference : String
} derive(Eq,
Debug
)

One offline IANA HTTP Problem Types registration.

#
ProblemTypeRecord::recommended_status

fn ProblemTypeRecord::recommended_status(self : ProblemTypeRecord) -> Int?

#
ProblemTypeRecord::reference

fn ProblemTypeRecord::reference(self : ProblemTypeRecord) -> String

#
ProblemTypeRecord::title

fn ProblemTypeRecord::title(self : ProblemTypeRecord) -> String

#
ProblemTypeRecord::type_uri

fn ProblemTypeRecord::type_uri(self : ProblemTypeRecord) -> String

#
UriReferenceInfo

pub struct UriReferenceInfo {
absolute : Bool
relative : Bool
} derive(Eq,
Debug
)

Result of the intentionally small RFC 3986 URI-reference syntax check.

#
UriReferenceInfo::is_absolute

fn UriReferenceInfo::is_absolute(self : UriReferenceInfo) -> Bool

#
UriReferenceInfo::is_relative

fn UriReferenceInfo::is_relative(self : UriReferenceInfo) -> Bool

#
ValidationIssue

pub struct ValidationIssue {
kind : ValidationIssueKind
context : String
} derive(Eq,
Debug
)

#
ValidationIssue::context

fn ValidationIssue::context(self : ValidationIssue) -> String

#
ValidationIssue::kind

#
ValidationIssueKind

pub(all) enum ValidationIssueKind {
InvalidTypeUri
InvalidInstanceUri
InvalidStatusCode
} derive(Eq,
Debug
)

Hard, objectively verifiable semantic issue.

#
ValidationIssueKind::name

fn ValidationIssueKind::name(self : ValidationIssueKind) -> String

#
about_blank

fn about_blank(status : Int) -> Result[ProblemDetails, ProblemError]

#
audit_problem

fn audit_problem(problem : ProblemDetails) -> AuditReport

Heuristic security and interoperability audit. It makes no leak-detection guarantee.

#
canonicalize_problem_json

fn canonicalize_problem_json(input : String) -> Result[String, ProblemError]

#
extension_member

fn extension_member(name : String, value : Json) -> ExtensionMember

#
is_absolute_uri_reference

fn is_absolute_uri_reference(value : String) -> Bool

#
is_registered_problem_type

fn is_registered_problem_type(uri : String) -> Bool

#
is_valid_http_status

fn is_valid_http_status(code : Int) -> Bool

#
is_valid_problem

fn is_valid_problem(problem : ProblemDetails) -> Bool

#
library_version

fn library_version() -> String

#
lookup_problem_type

fn lookup_problem_type(uri : String) -> ProblemTypeRecord?

#
parse_problem

fn parse_problem(input : String) -> Result[ProblemDetails, ProblemError]

Convenience API preserving errors while discarding diagnostics.

#
parse_problem_json

fn parse_problem_json(input : String) -> Result[ProblemParseResult, ProblemError]

#
parse_problem_json_bytes

fn parse_problem_json_bytes(input : Bytes, limits? : Limits) -> Result[ProblemParseResult, ProblemError]

Byte-oriented entry point for network payloads and truncation testing.

#
parse_problem_json_with_limits

fn parse_problem_json_with_limits(input : String, limits : Limits) -> Result[ProblemParseResult, ProblemError]

Parse JSON using core's parser, then interpret RFC 9457 members.

#
problem

fn problem(status : Int, title : String) -> Result[ProblemDetails, ProblemError]

#
problem_context

fn problem_context(actual_status : Int?, content_type : String?, base_uri : String?) -> ProblemContext

#
problem_details

fn problem_details(type_raw : String?, title : String?, status : Int?, detail : String?, instance : String?, extensions : Array[ExtensionMember]) -> ProblemDetails

#
problem_error

fn problem_error(stage : ProblemErrorStage, kind : ProblemErrorKind, context : String, byte_offset? : Int) -> ProblemError

#
problem_media_type

fn problem_media_type() -> String

#
problem_type_record

fn problem_type_record(type_uri : String, title : String, recommended_status : Int?, reference : String) -> ProblemTypeRecord

#
registered_problem_types

fn registered_problem_types() -> Array[ProblemTypeRecord]

#
registry_snapshot_date

fn registry_snapshot_date() -> String

#
registry_snapshot_sha256

fn registry_snapshot_sha256() -> String

#
serialize_problem

fn serialize_problem(problem : ProblemDetails) -> String

Deterministic top-level serializer: RFC members first, extensions sorted.

#
status_reason

fn status_reason(code : Int) -> String?

Common HTTP reason phrases. Unknown valid status codes return None.

#
validate_context

fn validate_context(problem : ProblemDetails, context : ProblemContext) -> Array[ContextIssue]

#
validate_problem

fn validate_problem(problem : ProblemDetails) -> Array[ValidationIssue]

Validate semantic constraints separately from parsing and audit advice.

#
validate_uri_reference

fn validate_uri_reference(value : String) -> Result[UriReferenceInfo, ProblemError]

Validate the syntax needed by RFC 9457 without resolving or dereferencing.