types.rs
219 lignes · 8176 octets
//! JSON types serialised on federation endpoints (Person, WebFinger JRD, //! NodeInfo). All field names are stabilised to match the de facto Mastodon //! / W3C ActivityStreams contracts. use serde::{Deserialize, Serialize}; use url::Url; /// JSON-LD `@context` shared by every actor we publish. /// /// Includes: ActivityStreams 2.0 core vocabulary, W3C security vocabulary /// (`publicKey` namespace), and Mastodon's `toot:` extension namespace /// (covers `discoverable`, `indexable`, `Emoji`). pub fn actor_context() -> serde_json::Value { serde_json::json!([ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", { "toot": "http://joinmastodon.org/ns#", "discoverable": "toot:discoverable", "indexable": "toot:indexable", "Emoji": "toot:Emoji" } ]) } /// ActivityPub Person actor (W3C ActivityStreams 2.0 + Security V1 + /// Mastodon extensions). /// /// Field `public_key` is `None` until [RFC 0005](../../rfcs/0005-actor-key-storage.md) /// (`actor-key-storage`) lands. Until then, the actor is **discoverable** but /// not **federable** — Mastodon will refuse to deliver activities to an actor /// without a published `publicKey`. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PersonJson { /// JSON-LD context, see [`actor_context`]. #[serde(rename = "@context")] pub context: serde_json::Value, /// Always the literal string `"Person"`. #[serde(rename = "type")] pub kind: String, /// Canonical actor URL, e.g. `https://example.org/users/alice`. pub id: Url, /// Local username (the `alice` part of `@alice@example.org`). pub preferred_username: String, /// Optional display name shown in clients. #[serde(skip_serializing_if = "Option::is_none")] pub name: Option<String>, /// HTML bio (sanitised before insertion into DB; rendered as-is here). #[serde(skip_serializing_if = "Option::is_none")] pub summary: Option<String>, /// Profile page URL (HTML). #[serde(skip_serializing_if = "Option::is_none")] pub url: Option<Url>, /// Inbox endpoint where remote servers POST activities for this actor. pub inbox: Url, /// Outbox endpoint listing this actor's published activities. pub outbox: Url, /// Followers collection URL. pub followers: Url, /// Following collection URL. pub following: Url, /// Mastodon-compatible shared inbox endpoint (instance-wide). #[serde(skip_serializing_if = "Option::is_none")] pub endpoints: Option<EndpointsJson>, /// Public RSA key used to verify HTTP signatures. `None` until RFC 0005. #[serde(skip_serializing_if = "Option::is_none")] pub public_key: Option<PublicKeyJson>, /// When `true`, follow requests await manual approval. #[serde(default)] pub manually_approves_followers: bool, /// Mastodon `toot:discoverable` flag. #[serde(default = "default_true")] pub discoverable: bool, } fn default_true() -> bool { true } /// Public RSA key embedded in a [`PersonJson`]. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PublicKeyJson { /// Key identifier, conventionally `{actor_id}#main-key`. pub id: Url, /// URL of the actor that owns this key. pub owner: Url, /// PEM-encoded SubjectPublicKeyInfo (RSA-2048+). pub public_key_pem: String, } /// Mastodon-compatible `endpoints` object on a Person actor. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EndpointsJson { /// Shared inbox URL (one per instance, used for fan-out optimisation). pub shared_inbox: Url, } // ───────────────────────── WebFinger (RFC 7033) ───────────────────────── /// WebFinger JRD response document. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WebFingerResponse { /// Subject URI, must echo the `resource` query parameter. pub subject: String, /// Optional alternative URIs for the same subject. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub aliases: Vec<String>, /// Typed links pointing at the various representations of the subject. pub links: Vec<WebFingerLink>, } /// Single typed link inside a [`WebFingerResponse`]. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WebFingerLink { /// Link relation, e.g. `"self"` or /// `"http://webfinger.net/rel/profile-page"`. pub rel: String, /// MIME type of the resource. #[serde(rename = "type", skip_serializing_if = "Option::is_none")] pub media_type: Option<String>, /// Concrete URL of the linked resource (mutually exclusive with /// `template`). #[serde(skip_serializing_if = "Option::is_none")] pub href: Option<String>, /// URI template (RFC 6570) when the relation is interaction-driven. #[serde(skip_serializing_if = "Option::is_none")] pub template: Option<String>, } // ───────────────────────── NodeInfo (Diaspora) ────────────────────────── /// NodeInfo discovery document (`/.well-known/nodeinfo`). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NodeInfoDiscovery { /// Links pointing at concrete NodeInfo schema versions. pub links: Vec<NodeInfoLink>, } /// Single link in a [`NodeInfoDiscovery`] document. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NodeInfoLink { /// Schema relation, e.g. /// `http://nodeinfo.diaspora.software/ns/schema/2.1`. pub rel: String, /// URL of the concrete NodeInfo resource. pub href: String, } /// NodeInfo 2.1 detail document (`/nodeinfo/2.1`). #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct NodeInfo { /// Always `"2.1"`. pub version: String, /// Software name + version metadata. pub software: NodeInfoSoftware, /// Federation protocols supported, always at least `["activitypub"]`. pub protocols: Vec<String>, /// Inbound/outbound services bridged by this instance (empty for now). pub services: NodeInfoServices, /// Aggregated usage counters. pub usage: NodeInfoUsage, /// Whether new account registration is open to the public. pub open_registrations: bool, /// Free-form metadata (instance description, contact, …). #[serde(default)] pub metadata: serde_json::Value, } /// Software identification block of a [`NodeInfo`]. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NodeInfoSoftware { /// Lowercase software name, e.g. `"rustwarden"`. pub name: String, /// SemVer software version. pub version: String, /// Optional VCS repository URL. #[serde(skip_serializing_if = "Option::is_none")] pub repository: Option<String>, /// Optional homepage URL. #[serde(skip_serializing_if = "Option::is_none")] pub homepage: Option<String>, } /// Services bridge advertised by NodeInfo. #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct NodeInfoServices { /// Inbound bridges (empty for Rustwarden). #[serde(default)] pub inbound: Vec<String>, /// Outbound bridges (empty for Rustwarden). #[serde(default)] pub outbound: Vec<String>, } /// Aggregated usage counters in [`NodeInfo`]. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct NodeInfoUsage { /// User counters split by activity window. pub users: NodeInfoUsageUsers, /// Total number of locally-authored posts. pub local_posts: u64, } /// User-count split returned in [`NodeInfoUsage`]. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct NodeInfoUsageUsers { /// Total registered users. pub total: u64, /// Users active in the last 30 days. pub active_month: u64, /// Users active in the last 180 days. pub active_halfyear: u64, }
GitRust