Skip to content

Add minimal ActivityPub vocab types#13

Open
sij411 wants to merge 1 commit into
fedify-dev:nextfrom
sij411:phase1-portable-core
Open

Add minimal ActivityPub vocab types#13
sij411 wants to merge 1 commit into
fedify-dev:nextfrom
sij411:phase1-portable-core

Conversation

@sij411
Copy link
Copy Markdown
Contributor

@sij411 sij411 commented Jun 2, 2026

Summary

  • Add minimal ActivityPub/ActivityStreams vocab types for Phase 1: Actor, Note, Follow, Accept, and Create<T>
  • Add singleton ActivityStreams type marker enums such as FollowType, AcceptType, and CreateType
  • Add Reference<T> for ID-or-embedded-object protocol fields
  • Add OneOrMany<T> for scalar-or-array protocol fields
  • Add JSON roundtrip and public API shape tests for Follow, Accept, and Create(Note)

Closes #5.

Design Notes

This keeps crates/feder-vocab scoped to protocol data only. It does not add core behavior, runtime I/O, fetching, storage, delivery, or full JSON-LD expansion.

Reference<T> exists because ActivityPub object slots can appear as either an IRI string or an embedded object. For example, actor, object, and attributedTo may be represented as:

"object": "https://example.com/users/alice"

or:

"object": {
  "type": "Person",
  "id": "https://example.com/users/alice"
}

So Reference<Actor> means "this protocol field contains either an actor IRI or an embedded actor object." It does not fetch, cache, dereference, or resolve the actor.

OneOrMany<T> exists because ActivityStreams fields are often represented as a single value or an array depending on cardinality. Absence is still modeled by Option<OneOrMany<T>> on future containing types.

The singleton *Type enums follow the same broad serde pattern used by activitypub-federation: concrete structs carry a fixed #[serde(rename = "type")] kind field, so deserializing a JSON object with the wrong ActivityStreams type fails early. Flexible shape selection is handled with #[serde(untagged)] only where the protocol genuinely allows multiple JSON shapes.

JSON-LD Scope

This crate intentionally models a compact ActivityStreams profile rather than full JSON-LD processing.

The Phase 1 vocab types use ordinary serde over common ActivityPub field names such as id, type, actor, object, attributedTo, and content. Helpers like Reference<T> and OneOrMany<T> cover common fediverse JSON shape variants without requiring JSON-LD expansion, compaction, RDF conversion, or remote context loading.

This is intended to keep the portable core usable by constrained runtimes. A future Linux runtime may add optional full JSON-LD normalization, but feder-core should not depend on it.

Validation

  • cargo fmt --check
  • cargo test
  • mise run check

Summary by CodeRabbit

  • New Features

    • Added ActivityStreams vocabulary types and structures for federated communications support.
  • Tests

    • Added comprehensive test coverage for ActivityStreams JSON serialization and deserialization validation.
  • Chores

    • Reorganized project dependencies as workspace-managed shared versions.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR establishes a minimal ActivityPub vocabulary crate for Feder by introducing shared workspace dependencies (serde, serde_json) and implementing core ActivityStreams Phase 1 types (Actor, Note, Follow, Accept, Create<T>) with generic containers for polymorphic references and single/multi-value fields, backed by JSON serialization tests.

Changes

Minimal ActivityPub Vocabulary Implementation

Layer / File(s) Summary
Workspace dependency setup
Cargo.toml, crates/feder-vocab/Cargo.toml
Workspace defines shared versions for serde (with derive feature, version 1.0.219) and serde_json (version 1.0.140); feder-vocab crate adopts these via *.workspace entries.
Vocabulary foundations and generic containers
crates/feder-vocab/src/lib.rs
Introduces ACTIVITYSTREAMS_CONTEXT constant and Iri type alias; implements Reference<T> untagged enum for IRI vs embedded object representations and OneOrMany<T> for scalar vs array values, each with helper constructors.
ActivityStreams type system
crates/feder-vocab/src/lib.rs
Macro activitystreams_type! generates type enumerations (PersonType, NoteType, FollowType, AcceptType, CreateType) with default variants for ActivityStreams type field discrimination.
Activity vocabulary structs
crates/feder-vocab/src/lib.rs
Core vocabulary types: Actor (person with inbox/outbox/preferred username), Note (content with attribution and publication date), Follow (actor following actor), Accept (actor accepting activity), and generic Create<T> (actor creating object). All use serde field renames, optional fields, and constructors that initialize context and type.
Unit and integration tests
crates/feder-vocab/src/lib.rs, crates/feder-vocab/tests/phase1_shapes.rs
Inline tests validate JSON round-trips, type rejection, and scalar/array deserialization; integration tests verify Follow deserialization from mixed id/embedded actor shapes, Accept wrapping Follow, Create wrapping Note, Reference id vs object representation, and OneOrMany single/multiple recipient handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • fedify-dev/feder#12: Establishes the workspace root and initial crates/feder-vocab manifest structure that this PR extends with shared dependencies and vocabulary implementation.

Suggested reviewers

  • dahlia

Poem

🐰 A rabbit hops through ActivityStreams, so fine,
With References and OneOrMany types that align,
Follow, Accept, and Create all in place,
JSON round-trips dance at a JSON pace! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding minimal ActivityPub vocabulary types as the core of this changeset.
Linked Issues check ✅ Passed The PR successfully implements all core requirements: Actor, Follow, Accept, Note, Create(T) types with serde (de)serialization and JSON roundtrip tests covering basic shapes.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing minimal ActivityPub types; no unrelated modifications like full JSON-LD expansion, additional activity types, or runtime behavior are present.
Docstring Coverage ✅ Passed Docstring coverage is 86.96% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sij411 sij411 marked this pull request as ready for review June 2, 2026 13:44
@sij411
Copy link
Copy Markdown
Contributor Author

sij411 commented Jun 2, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/feder-vocab/src/lib.rs`:
- Around line 77-83: The Actor struct is currently constrained to Person via the
PersonType discriminator (pub kind: PersonType), which rejects non-person
ActivityPub actors; change this public API by introducing a broader ActorType
enum (e.g., ActorType with variants Person, Service, Group, Application, etc.)
and replace PersonType with ActorType in the Actor struct, updating serde
attributes as needed to preserve the "type" field; then update all usages
referenced by symbol names (Follow.actor, Follow.object, Accept.actor,
Create.actor, Note.attributedTo) to accept ActorType instead of PersonType (or
alternatively rename the struct to Person if you intend to keep Person-only
semantics across those symbols). Ensure serialization/deserialization continues
to map the "type" field correctly and add tests for non-Person actor types.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 25a1d38f-662f-4d93-b45e-98f64eb449d2

📥 Commits

Reviewing files that changed from the base of the PR and between a635fd8 and 069dd9a.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • Cargo.toml
  • crates/feder-vocab/Cargo.toml
  • crates/feder-vocab/src/lib.rs
  • crates/feder-vocab/tests/phase1_shapes.rs

Comment on lines +77 to +83
/// A minimal ActivityPub actor for Phase 1 core tests.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Actor {
#[serde(rename = "@context", skip_serializing_if = "Option::is_none")]
pub context: Option<Iri>,
#[serde(rename = "type")]
pub kind: PersonType,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Actor is currently hard-coded to Person.

Line 83 makes the public Actor model reject any valid ActivityPub actor whose "type" is not "Person". That breaks non-person actors end-to-end in Follow.actor, Follow.object, Accept.actor, Create.actor, and Note.attributedTo, even though the API is named generically as Actor. Either rename this model to Person or widen the discriminator to an ActorType enum before locking in the public API.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/feder-vocab/src/lib.rs` around lines 77 - 83, The Actor struct is
currently constrained to Person via the PersonType discriminator (pub kind:
PersonType), which rejects non-person ActivityPub actors; change this public API
by introducing a broader ActorType enum (e.g., ActorType with variants Person,
Service, Group, Application, etc.) and replace PersonType with ActorType in the
Actor struct, updating serde attributes as needed to preserve the "type" field;
then update all usages referenced by symbol names (Follow.actor, Follow.object,
Accept.actor, Create.actor, Note.attributedTo) to accept ActorType instead of
PersonType (or alternatively rename the struct to Person if you intend to keep
Person-only semantics across those symbols). Ensure
serialization/deserialization continues to map the "type" field correctly and
add tests for non-Person actor types.

pub const ACTIVITYSTREAMS_CONTEXT: &str = "https://www.w3.org/ns/activitystreams";

/// An absolute ActivityPub/ActivityStreams identifier.
pub type Iri = String;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of aliasing type Iri = String, how about introducing RiStr from the iri-string crate?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants