moon-webfinger

RFC 7033 WebFinger and JRD parser, serializer, request builder, query and audit toolkit for MoonBit.

webfinger
rfc7033
jrd
identity
discovery
json
http
moonbit
moon add qianfuzsc/moon-webfinger@0.1.0
Download zip
Author
Version
0.1.0
License
Apache-2.0
Last updated
4 hours ago
Downloads
1
README

#moon-webfinger

  • Module: qianfuzsc/moon-webfinger
  • Version: 0.1.0
  • Status: final-review release candidate
  • Mooncakes: release target qianfuzsc/moon-webfinger@0.1.0
  • Repository: https://github.com/qianfuzsc/moon-webfinger
  • Applicant and maintainer: 赵士超
  • GitHub: qianfuzsc

#Overview

moon-webfinger is a deterministic RFC 7033 WebFinger and JSON Resource Descriptor (JRD) parser, serializer, request builder, query, validation, and audit toolkit for MoonBit. It performs local data processing only and never makes network requests.

#Why WebFinger

WebFinger discovers information about a resource identified by a URI. A caller sends a resource value, optionally filters by one or more rel values, and receives an application/jrd+json descriptor. This library supplies the protocol data layer while leaving HTTP, TLS, redirects, and trust policy to the caller.

#RFC 7033 Scope

Implemented scope includes request-target construction, JRD parsing and deterministic serialization, extension-member preservation, semantic validation, response-context checks, relation queries, advisory auditing, configurable limits, structured errors, and common acct: helpers. See docs/specification-map.md for the normative mapping.

#WebFinger Request

let target = @wf.build_request_target_with_rels(
"acct:alice@example.com",
["http://webfinger.net/rel/profile-page"],
)

Resources and relation filters are percent-encoded as query components. Rel order and duplicates are preserved unless canonicalize_request is explicitly used. build_webfinger_url accepts a caller-supplied HTTPS origin but performs no request.

#JSON Resource Descriptor

JsonResourceDescriptor represents subject, aliases, properties, links, and preserved unknown top-level members. JrdLink represents the required rel plus optional type, href, titles, properties, and preserved unknown members.

#Subject and Aliases

Subject and alias values are treated as absolute URI strings. The parser preserves their order; the validator reports semantic issues without rewriting them.

#Properties

JRD properties support both string and explicit null values through PropertyValue. Null and absence remain distinct.

Link array order is preserved because it may express preference. Hrefs are returned as inert data and are never followed.

#Localized Titles

title, preferred_title, and fallback_title support exact ASCII case-insensitive language-tag lookup and deterministic fallback. Full RFC 4647 negotiation and registry validation are outside this MVP.

#Query Helpers

Helpers find links by relation and media type, return hrefs, count relations, copy aliases, and inspect properties. Every query is local and deterministic.

#Response Validation

validate_response_context checks caller-observed HTTPS URLs, 2xx status, and the JRD media type. It does not validate certificates or remote identity.

#Serialization

Serialization is compact and deterministic. Standard and extension object members are emitted in stable order; arrays retain model order. This ordering is for reproducibility, not an RFC-mandated JSON member order.

#CLI

webfinger-tool request --resource acct:alice@example.com webfinger-tool parse --input-file smoke_jrd.json webfinger-tool query --rel http://openid.net/specs/connect/1.0/issuer --input-file smoke_jrd.json webfinger-tool audit --input-file smoke_jrd.json

Commands are request, parse, validate, query, canonicalize, audit, stats, version, and help. Output is deterministic JSON. The CLI does not accept URLs to fetch.

#Examples

The five local examples cover request construction, JRD parsing, JRD building, relation queries, and response auditing. Run them with moon run examples/build_request and the corresponding package paths.

#Testing

Run moon test for the current target or powershell -File scripts/verify_all.ps1 for formatting, all three targets, CLI smoke tests, examples, metrics, and package listing. Details are in docs/testing.md.

#Security

WebFinger is discovery, not authentication, authorization, identity proof, or a trust system. Treat every JRD value as untrusted. See docs/security.md.

#Limitations

There is no HTTP client or server, DNS, TLS, certificate validation, redirect engine, complete URI/IDNA stack, link dereferencing, account database, or cache. See docs/limitations.md.

#License

Apache License 2.0. See LICENSE. Reconstructed RFC fixtures are attributed in THIRD_PARTY_NOTICES.md.

#Project Status

This is a mature MVP maintained by 赵士超 (qianfuzsc). Version 0.1.0 is the final-review release target for Mooncakes.

#
WebFingerError

pub suberror WebFingerError {
WebFingerError(WebFingerErrorStage, WebFingerErrorKind, Int?, String)
}

The single structured error type of this package.

#
WebFingerError::context

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

A human-readable, one-line description of the failure.

#
WebFingerError::kind

The concrete category of this error.

#
WebFingerError::kind_name

fn WebFingerError::kind_name(self : WebFingerError) -> String

The kind name, for consumer packages that cannot match the enum.

#
WebFingerError::offset

fn WebFingerError::offset(self : WebFingerError) -> Int?

A precise UTF-8 byte offset into the original input, or None when the failing component cannot report one (notably JSON syntax errors).

#
WebFingerError::stage

The processing area of this error.

#
WebFingerError::stage_name

fn WebFingerError::stage_name(self : WebFingerError) -> String

The stage name, for consumer packages that cannot match the enum.

#
WebFingerError::to_display

fn WebFingerError::to_display(self : WebFingerError) -> String

A single-line rendering like Uri error InvalidUri: not an absolute URI (offset: None).

#
AcctUri

pub struct AcctUri {
localpart : String
host : String
}

A parsed acct: URI. localpart is the (possibly percent-encoded) userpart exactly as it appeared in the URI; host is the domain as it appeared.

#
AcctUri::to_uri_string

fn AcctUri::to_uri_string(self : AcctUri) -> String

The canonical reconstructed URI acct:<localpart>@<host>.

#
AuditFinding

pub struct AuditFinding {
code : String
severity : AuditSeverity
message : String
}

A single advisory finding.

#
AuditFinding::severity_name

fn AuditFinding::severity_name(self : AuditFinding) -> String

The severity name, for consumer packages that cannot match the enum.

#
AuditSeverity

pub enum AuditSeverity {
Warning
Info
}

Severity of an audit finding.

#
CliOptions

pub struct CliOptions {
resource : String?
rels : Array[String]
origin : String?
input : String?
input_file : String?
media_type : String?
}

The parsed command-line options shared by all commands.

#
JrdBuilder

pub struct JrdBuilder {
subject : String?
aliases : Array[String]
properties : Map[String, PropertyValue]
links : Array[JrdLink]
extensions : Map[String, Json]
}

Builds a JsonResourceDescriptor fluently.

#
JrdBuilder::alias

fn JrdBuilder::alias(self : JrdBuilder, a : String) -> JrdBuilder

Add an alias (validated on build).

#
JrdBuilder::build

Build the JRD, validating subject, aliases, property names, links and extension names, and enforcing Limits::default() bounds.

#
JrdBuilder::extension

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

Add an unknown (extension) top-level member, preserved for round-tripping. The name must not collide with a reserved member (checked on build).
fn JrdBuilder::link(self : JrdBuilder, link : JrdLink) -> JrdBuilder

Add a link (the link itself is validated on build).

#
JrdBuilder::new

fn JrdBuilder::new() -> JrdBuilder

Start an empty JRD builder.

#
JrdBuilder::property

fn JrdBuilder::property(self : JrdBuilder, uri : String, value : PropertyValue) -> JrdBuilder

Add a subject-level property with an explicit value (name validated on build).

#
JrdBuilder::property_null

fn JrdBuilder::property_null(self : JrdBuilder, uri : String) -> JrdBuilder

Add a subject-level null property (name validated on build).

#
JrdBuilder::property_string

fn JrdBuilder::property_string(self : JrdBuilder, uri : String, s : String) -> JrdBuilder

Add a subject-level string property (name validated on build).

#
JrdBuilder::subject

fn JrdBuilder::subject(self : JrdBuilder, s : String) -> JrdBuilder

Set the subject (validated on build).
pub struct JrdLink {
rel : String
media_type : String?
href : String?
titles : Map[String, String]
properties : Map[String, PropertyValue]
extensions : Map[String, Json]
}

A link relation object as defined by RFC 7033 Section 4.4.4.

Members:

  • rel — REQUIRED; exactly one URI or registered relation type.
  • media_type — OPTIONAL; the media type of the link target (type).
  • href — OPTIONAL; the target URI.
  • titles — OPTIONAL; language tag (or "und") to human-readable title. RFC 7033 says a JRD SHOULD NOT repeat a language tag, but repetition MUST NOT be treated as an error; the underlying JSON parser collapses duplicate object keys before the library sees them (see docs/limitations.md).
  • properties — OPTIONAL; property URI to string-or-null value.
  • extensions — unknown link members, preserved for round-tripping.

#
JrdLink::fallback_title

fn JrdLink::fallback_title(self : JrdLink) -> String?

A deterministic fallback title when the caller has no language preference: the "und" title if present, otherwise the title with the lexicographically smallest language tag, otherwise None.

#
JrdLink::has_any_title

fn JrdLink::has_any_title(self : JrdLink) -> Bool

Whether this link has a title in any language.

#
JrdLink::has_property

fn JrdLink::has_property(self : JrdLink, uri : String) -> Bool

Whether this link has a property with the given URI.

#
JrdLink::has_title

fn JrdLink::has_title(self : JrdLink, language : String) -> Bool

Whether this link carries a title for exactly the given language tag. The match is exact first, then ASCII case-insensitive; full RFC 4647 language negotiation is out of scope (see docs/limitations.md).

#
JrdLink::new

fn JrdLink::new(rel : String) -> JrdLink

Create an empty link with the given (non-validated) relation type.

#
JrdLink::preferred_title

fn JrdLink::preferred_title(self : JrdLink, languages : Array[String]) -> String?

The first title whose language tag appears in languages, in the caller-given priority order, or None. Exact, ASCII case-insensitive matching; no RFC 4647 negotiation.

#
JrdLink::property

fn JrdLink::property(self : JrdLink, uri : String) -> PropertyValue?

A link property value, or None if absent.

#
JrdLink::title

fn JrdLink::title(self : JrdLink, language : String) -> String?

The title for exactly the given language tag (exact match preferred, then ASCII case-insensitive), or None.

#
JrdLinkBuilder

pub struct JrdLinkBuilder {
rel : String
media_type : String?
href : String?
titles : Map[String, String]
properties : Map[String, PropertyValue]
extensions : Map[String, Json]
}

Builds a JrdLink fluently.

#
JrdLinkBuilder::build

fn JrdLinkBuilder::build(self : JrdLinkBuilder) -> Result[JrdLink, WebFingerError]

Build the link, validating rel, href, title keys, property names and extension names.

#
JrdLinkBuilder::extension

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

Add an unknown link member, preserved for round-tripping (name checked on build).

#
JrdLinkBuilder::href

fn JrdLinkBuilder::href(self : JrdLinkBuilder, h : String) -> JrdLinkBuilder

Set the target URI (validated on build).

#
JrdLinkBuilder::media_type

fn JrdLinkBuilder::media_type(self : JrdLinkBuilder, t : String) -> JrdLinkBuilder

Set the target media type (the type member).

#
JrdLinkBuilder::new

fn JrdLinkBuilder::new(rel : String) -> JrdLinkBuilder

Start a link builder with its (required) relation type, validated on build.

#
JrdLinkBuilder::property

fn JrdLinkBuilder::property(self : JrdLinkBuilder, uri : String, value : PropertyValue) -> JrdLinkBuilder

Add a link property with an explicit value (name validated on build).

#
JrdLinkBuilder::property_null

fn JrdLinkBuilder::property_null(self : JrdLinkBuilder, uri : String) -> JrdLinkBuilder

Add a null link property.

#
JrdLinkBuilder::property_string

fn JrdLinkBuilder::property_string(self : JrdLinkBuilder, uri : String, s : String) -> JrdLinkBuilder

Add a string link property.

#
JrdLinkBuilder::title

fn JrdLinkBuilder::title(self : JrdLinkBuilder, language : String, text : String) -> JrdLinkBuilder

Add a title in a language (key validated on build; und allowed).

#
JsonResourceDescriptor

pub struct JsonResourceDescriptor {
subject : String?
aliases : Array[String]
properties : Map[String, PropertyValue]
links : Array[JrdLink]
extensions : Map[String, Json]
}

A JSON Resource Descriptor as defined by RFC 7033 Section 4.4.

#
JsonResourceDescriptor::empty

Create an empty JRD (no subject, no aliases, no properties, no links).

#
JsonResourceDescriptor::has_extensions

fn JsonResourceDescriptor::has_extensions(self : JsonResourceDescriptor) -> Bool

Whether this JRD carries any unknown (extension) top-level members.

#
JsonResourceDescriptor::has_property

fn JsonResourceDescriptor::has_property(self : JsonResourceDescriptor, uri : String) -> Bool

Whether this JRD has a subject-level property with the given URI.
fn JsonResourceDescriptor::link_count(self : JsonResourceDescriptor) -> Int

The number of links in this JRD.

#
JsonResourceDescriptor::property

fn JsonResourceDescriptor::property(self : JsonResourceDescriptor, uri : String) -> PropertyValue?

A subject-level property value, or None if absent.

#
Limits

pub struct Limits {
max_input_bytes : Int
max_subject_bytes : Int
max_aliases : Int
max_alias_bytes : Int
max_properties : Int
max_property_name_bytes : Int
max_property_value_bytes : Int
max_links : Int
max_titles_per_link : Int
max_properties_per_link : Int
max_string_bytes : Int
max_extension_members : Int
max_nesting_depth : Int
}

All tunable bounds of JRD processing.

#
Limits::default

fn Limits::default() -> Limits

Recommended limits for general local processing.

#
Limits::permissive

fn Limits::permissive() -> Limits

Wide limits for large but still bounded descriptors.

#
Limits::strict

fn Limits::strict() -> Limits

Tight limits for constrained or hostile-input environments.

#
PropertyValue

pub enum PropertyValue {
StringValue(String)
NullValue
} derive(Eq)

A JRD property value: RFC 7033 allows properties (both subject-level and link-level) to hold either a string or JSON null.

#
PropertyValue::as_string

fn PropertyValue::as_string(self : PropertyValue) -> String?

The string payload, or None for a null value.

#
PropertyValue::is_null

fn PropertyValue::is_null(self : PropertyValue) -> Bool

Whether this property value is null.

#
PropertyValue::null

Construct a null property value. Provided because the enum variants themselves are read-only for consumer packages.

#
PropertyValue::string

fn PropertyValue::string(s : String) -> PropertyValue

Construct a string property value. Provided because the enum variants themselves are read-only for consumer packages.

#
PropertyValue::to_display

fn PropertyValue::to_display(self : PropertyValue) -> String

A stable display form for tests and CLI output.

#
RelationCount

pub struct RelationCount {
rel : String
count : Int
}

A relation type and how many links carry it. Produced by relation_summary in lexically sorted rel order.

#
ValidationIssue

pub struct ValidationIssue {
severity : ValidationSeverity
code : String
message : String
}

A single validation issue. code is a stable machine-readable name (e.g. MissingSubject), message a human-readable description.

#
ValidationIssue::severity_name

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

The severity name, for consumer packages that cannot match the enum.

#
ValidationSeverity

pub enum ValidationSeverity {
Error
Warning
Info
}

Severity of a validation issue.

#
WebFingerErrorKind

pub enum WebFingerErrorKind {
InvalidJson
RootNotObject
MissingResource
InvalidResource
InvalidUri
InvalidPercentEncoding
WrongMemberType
MissingRequiredMember
InvalidLink
InvalidProperty
InvalidContentType
LimitExceeded
DuplicateReservedExtension
InvalidRelValue
InvalidLanguageTag
InvalidAcctUri
InvalidOrigin
NonHttpsOrigin
InvalidHttpStatus
NonHttpsFinalUrl
EmptyInput
}

The concrete error category of a WebFingerError.

#
WebFingerErrorStage

pub enum WebFingerErrorStage {
Input
Json
Request
Uri
Jrd
Subject
Alias
Property
Link
Context
Limit
Builder
}

The processing area that produced a WebFingerError.

#
WebFingerRequestContext

pub struct WebFingerRequestContext {
request_url : String
resource : String
rels : Array[String]
}

The caller-observed facts about a WebFinger request. resource and rels are the values actually sent.

#
WebFingerRequestContext::new

fn WebFingerRequestContext::new(request_url : String, resource : String, rels : Array[String]) -> WebFingerRequestContext

Build a request context. No validation happens here; use validate_response_context on the paired response context.

#
WebFingerResponseContext

pub struct WebFingerResponseContext {
request_url : String?
final_url : String
status : Int
content_type : String?
body_bytes : Int?
}

The caller-observed facts about a WebFinger response.

#
WebFingerResponseContext::new

fn WebFingerResponseContext::new(request_url : String?, final_url : String, status : Int, content_type : String?, body_bytes : Int?) -> WebFingerResponseContext

Build a response context. status is the HTTP status code, content_type the full Content-Type header value (parameters allowed), body_bytes the response body size in bytes when known.

#
JRD_MEDIA_TYPE

let JRD_MEDIA_TYPE : String

The registered JRD media type.

#
WELL_KNOWN_PATH

let WELL_KNOWN_PATH : String

The well-known WebFinger path.

#
acct_resource

fn acct_resource(localpart : String, host : String) -> Result[String, WebFingerError]

Build an acct URI from a raw localpart and host. The localpart is percent-encoded as needed (unreserved characters and sub-delims pass through; note that a literal % is treated as data and encoded, so pass the raw, unencoded localpart). The host must pass check_hostname.

#
all_aliases

fn all_aliases(jrd : JsonResourceDescriptor) -> Array[String]

A copy of the aliases array (model order preserved).

#
audit_jrd

fn audit_jrd(jrd : JsonResourceDescriptor) -> Array[AuditFinding]

Audit a parsed JRD and return all advisory findings in a deterministic order.

#
audit_response

fn audit_response(ctx : WebFingerResponseContext) -> Array[AuditFinding]

Audit a response context and return advisory findings about the transport facts the caller observed. Mirrors validate_response_context, but everything here is advisory.

#
build_request_target

fn build_request_target(resource : String) -> Result[String, WebFingerError]

Build the WebFinger request target for a resource without rel filters.

#
build_request_target_with_rels

fn build_request_target_with_rels(resource : String, rels : Array[String]) -> Result[String, WebFingerError]

Build the WebFinger request target for a resource plus zero or more rel filters. Rel order and duplicates are preserved, matching RFC 7033 Section 4.3.

#
build_webfinger_url

fn build_webfinger_url(origin : String, resource : String, rels : Array[String]) -> Result[String, WebFingerError]

Build a full WebFinger URL from a caller-supplied HTTPS origin plus the resource and optional rel filters. The origin is validated syntactically (https scheme, domain name host, optional numeric port) but never contacted: no DNS, no TLS, no network.

#
canonicalize_jrd

fn canonicalize_jrd(jrd : JsonResourceDescriptor) -> String

Canonicalize a JRD model: stable member order, stable whitespace (compact), stable escaping. Nothing else is changed — no URI case folding, no alias dropping, no link merging or reordering.

#
canonicalize_jrd_text

fn canonicalize_jrd_text(input : String) -> Result[String, WebFingerError]

Canonicalize a raw JRD document (parse, then re-serialize canonically). Fails with a structured error on invalid JSON or on an invalid JRD.

#
canonicalize_request

fn canonicalize_request(resource : String, rels : Array[String]) -> Result[String, WebFingerError]

Canonicalize a request target for the same resource and rels: the resource parameter always comes first, rels are sorted and deduplicated. Parameter order is not semantic (RFC 7033 Section 4.1), so this normalization is safe, but it is provided as a separate function because ordinary request building preserves caller order.

#
check_absolute_uri

fn check_absolute_uri(s : String) -> Result[Unit, WebFingerError]

Check that s is an absolute URI within the WebFinger scope. Errors use stage Uri and kind InvalidUri (or InvalidPercentEncoding for bad escapes).

#
check_hostname

fn check_hostname(host : String) -> Result[Unit, WebFingerError]

Check that host is a plausible DNS domain name for use in acct URIs and WebFinger origins. Advisory-level strictness: this is a conservative syntactic heuristic, not a resolver.

#
check_language_tag

fn check_language_tag(tag : String) -> Bool

Whether a language tag (or und) passes the RFC 5646 basic syntax check used by the validator and builders.

#
check_rel_value

fn check_rel_value(rel : String) -> Result[Unit, WebFingerError]

Check a link relation value: per RFC 7033 Section 4.4.4.1 a rel value must be exactly one URI or one registered relation type (token). This is a syntactic check — the library cannot consult the IANA registry.

#
cli_file_input_supported

fn cli_file_input_supported() -> Bool

fn find_links_by_rel(jrd : JsonResourceDescriptor, rel : String) -> Array[JrdLink]

All links whose rel equals rel exactly, in model order.
fn find_links_by_rel_and_type(jrd : JsonResourceDescriptor, rel : String, media_type : String) -> Array[JrdLink]

All links whose rel equals rel exactly and whose type equals media_type exactly, in model order.

#
first_href

fn first_href(jrd : JsonResourceDescriptor, rel : String) -> String?

The href of the first link with the given rel, or None. The href is returned as data only; it is never fetched.
fn first_link_by_rel(jrd : JsonResourceDescriptor, rel : String) -> JrdLink?

The first link with the given rel (model order — i.e. the preferred one when the server uses order to express preference), or None.

#
first_validation_error

fn first_validation_error(jrd : JsonResourceDescriptor) -> ValidationIssue?

The first Error-severity issue, or None when the JRD is valid.

#
hrefs_by_rel

fn hrefs_by_rel(jrd : JsonResourceDescriptor, rel : String) -> Array[String]

The hrefs of all links with the given rel, in model order. Links without an href are skipped.

#
is_acct_uri

fn is_acct_uri(s : String) -> Bool

Quick, syntactic-only test: does this string use the acct scheme (case-insensitively) and contain the userpart@host shape? This does not validate the localpart or the host; use parse_acct_uri for a strict parse.

#
is_jrd_content_type

fn is_jrd_content_type(content_type : String) -> Bool

Whether a Content-Type header value denotes the JRD media type. Parameters (after ;) are ignored and the media type comparison is ASCII case-insensitive. Malformed values simply return false.
fn jrd_link_semantic_equal(a : JrdLink, b : JrdLink) -> Bool

Semantic equality of two links: all members compared structurally, with map and array key/element order ignored.
fn jrd_links_semantic_equal(a : Array[JrdLink], b : Array[JrdLink]) -> Bool

Semantic equality of two links arrays (order matters: RFC 7033 says links array order MAY indicate preference, so it is semantic).

#
jrd_semantic_equal

fn jrd_semantic_equal(a : JsonResourceDescriptor, b : JsonResourceDescriptor) -> Bool

Semantic equality of two JRDs. Used by the deterministic property tests (model -> serialize -> parse -> compare). Member order inside JSON objects is ignored; links array order is preserved because it can carry preference information.

#
jrd_to_json

fn jrd_to_json(jrd : JsonResourceDescriptor) -> Json

The model as a core Json value with the canonical member order: subject, aliases, properties, links, then preserved extensions. Note that JSON object member order carries no semantics; this order exists only to make serialization deterministic.

#
json_depth

fn json_depth(j : Json) -> Int

The nesting depth of a JSON value: 1 for a scalar, one more for each enclosing container. Implemented iteratively so that deeply nested inputs cannot overflow the stack.

#
json_to_jrd

fn json_to_jrd(j : Json, limits : Limits) -> Result[JsonResourceDescriptor, WebFingerError]

Convert a parsed core Json value into the JRD model under the given limits. Public, but parse_jrd is the usual entry point.

#
library_version

fn library_version() -> String

The library version. Mirrors version in moon.mod; both must be updated together (single source discipline documented in docs/reproduction.md).
fn link_count_by_rel(jrd : JsonResourceDescriptor, rel : String) -> Int

The number of links with the given rel.
fn link_model_to_json(link : JrdLink) -> Json

A single link model as a core Json value with the canonical member order (rel, type, href, titles, properties, extensions). Useful for CLI output and tooling.

A copy of the links array (model order preserved; RFC 7033 says links array order MAY indicate preference).
fn links_by_type(jrd : JsonResourceDescriptor, media_type : String) -> Array[JrdLink]

All links whose type equals media_type exactly, in model order.

#
normalize_cli_args

fn normalize_cli_args(args : Array[String]) -> Array[String]

Drop leading program/runtime paths from the argument list. The argv shape differs per backend:

native / wasm-gc: [program, args...] js: [node, program.js, args...]

Program paths are recognised by shape (a path ending in a program extension, or a known runtime binary name) and dropped until the first known command or the user's first argument is reached, so the backend never leaks into run_cli.

#
origin_of

fn origin_of(url : String) -> String?

The origin (scheme + host + port) of a URL-shaped string, lowercased scheme and host, or None when it cannot be split. Syntactic only.

#
parse_acct_uri

fn parse_acct_uri(s : String) -> Result[AcctUri, WebFingerError]

Strictly parse an acct URI per RFC 7565. Returns the localpart and host, or a structured error (InvalidAcctUri / InvalidUri / InvalidPercentEncoding).

#
parse_jrd

fn parse_jrd(input : String) -> Result[JsonResourceDescriptor, WebFingerError]

Parse a JRD document under Limits::default().

#
parse_jrd_with_limits

fn parse_jrd_with_limits(input : String, limits : Limits) -> Result[JsonResourceDescriptor, WebFingerError]

Parse a JRD document under caller-supplied limits.

#
percent_decode

fn percent_decode(input : String) -> Result[String, WebFingerError]

Strictly percent-decode a string: every % must start a %XX escape with two hex digits; raw non-ASCII characters are rejected (URIs are ASCII; non-ASCII text must be percent-encoded); the decoded byte stream must be valid UTF-8.

#
percent_encode_acct_localpart

fn percent_encode_acct_localpart(input : String) -> String

Percent-encode the localpart of an acct URI (RFC 7565 Section 7): unreserved characters and sub-delims pass through.

#
percent_encode_component

fn percent_encode_component(input : String) -> String

Percent-encode a query component value per RFC 7033 Section 4.1: unreserved characters pass through, everything else (including =, &, and non-ASCII text) is percent-encoded.

#
property_string

fn property_string(jrd : JsonResourceDescriptor, uri : String) -> String?

A subject-level property rendered for display: the string value, or "null" for an explicit null, or None when absent.

#
property_value

fn property_value(jrd : JsonResourceDescriptor, uri : String) -> PropertyValue?

A subject-level property value, or None. A null property returns Some(PropertyValue::null()); use is_null to distinguish it from an absent property.

#
relation_summary

fn relation_summary(jrd : JsonResourceDescriptor) -> Array[RelationCount]

Per-rel link counts, sorted lexically by rel.

#
run_cli

fn run_cli(args : Array[String]) -> String

Run the CLI on already-normalized arguments and return the complete JSON output line. Pure: never exits, never panics.

#
same_strings

fn same_strings(a : Array[String], b : Array[String]) -> Bool

Element-wise equality of string arrays (MoonBit Array has no structural ==, so this helper exists).

#
scheme_is

fn scheme_is(s : String, scheme : String) -> Bool

Case-insensitive scheme comparison.

#
serialize_jrd

fn serialize_jrd(jrd : JsonResourceDescriptor) -> String

Serialize a JRD model to a compact, deterministic application/jrd+json document.

#
serialize_jrd_with_indent

fn serialize_jrd_with_indent(jrd : JsonResourceDescriptor, indent : Int) -> String

Serialize a JRD model to an indented, deterministic application/jrd+json document.

#
stringify_json

fn stringify_json(j : Json) -> String

Serialize any core Json value to a compact JSON string with object keys in sorted order. Used by the CLI for deterministic output.

#
stringify_json_with_indent

fn stringify_json_with_indent(j : Json, indent : Int) -> String

Serialize any core Json value to an indented JSON string with object keys in sorted order. indent is the number of spaces per nesting level.

#
uri_scheme

fn uri_scheme(s : String) -> String?

Extract the scheme of a URI-shaped string (the part before :), lowercased, or None when no valid scheme is present. Does not validate the rest of the string.

#
utf8_byte_length

fn utf8_byte_length(s : String) -> Int

The UTF-8 byte length of a string, counted without materializing the encoded copy of the whole string. Used to enforce byte limits cheaply before any other processing.

#
validate_for_resource

fn validate_for_resource(jrd : JsonResourceDescriptor, requested_resource : String) -> Array[ValidationIssue]

Check a parsed JRD against the resource it was queried with.

RFC 7033 Section 4.4.1: the subject MAY differ from the queried resource (identity changes, canonical forms), so a differing subject is reported as Info, not as an error. A subject (or alias) equal to the requested resource is also reported as Info for callers that want to confirm the match.

#
validate_jrd

fn validate_jrd(jrd : JsonResourceDescriptor) -> Array[ValidationIssue]

Validate a parsed JRD and collect all issues (Error, Warning and Info) in a deterministic order.

#
validate_jrd_all

fn validate_jrd_all(jrd : JsonResourceDescriptor) -> Bool

Whether a parsed JRD passes validation with no Error-severity issue. Warnings (e.g. a missing subject) and Info issues do not fail this check, matching the RFC's own SHOULD-level requirements.

#
validate_response_context

fn validate_response_context(ctx : WebFingerResponseContext) -> Result[Unit, WebFingerError]

Validate a response context against the checkable RFC 7033 transport invariants. Returns the first violation found as a structured error; advisory observations (such as a same-scheme redirect) are reported by audit_response instead.

#
within_byte_limit

fn within_byte_limit(s : String, limit : Int) -> Bool

Whether s fits within limit UTF-8 bytes.