twink/vendor/sourcery.dny.nu/pana/processor.go
2025-12-05 12:20:05 +01:00

115 lines
3.1 KiB
Go

package pana
import (
"fmt"
"log/slog"
ld "sourcery.dny.nu/longdistance"
"sourcery.dny.nu/pana/internal/json"
"sourcery.dny.nu/pana/internal/loader"
"sourcery.dny.nu/pana/vocab/schema"
as "sourcery.dny.nu/pana/vocab/w3/activitystreams"
)
// Processor is used to process incoming messages and prepare outgoing messages.
//
// Your application only needs a single processor instance.
type Processor struct {
ldproc *ld.Processor
l *loader.Builtin
}
// NewProcessor initialised a new [Processor] suitable for dealing with
// ActivityStreams messages.
//
// It uses [loader.Builtin] to retrieve contexts and does not retrieve them over
// the network.
func NewProcessor(
logger *slog.Logger,
) *Processor {
if logger == nil {
logger = slog.New(slog.DiscardHandler)
}
loader := loader.New()
return &Processor{
l: loader,
ldproc: ld.NewProcessor(
ld.WithLogger(logger),
ld.WithCompactArrays(true),
ld.WithCompactToRelative(false), // Avoids compacting to relative IRIs
ld.With10Processing(false), // Misskey seems to do some JSON-LD 1.1
ld.WithRemoteContextLoader(loader.Get),
ld.WithExcludeIRIsFromCompaction(as.PublicCollection),
ld.WithRemapPrefixIRIs("http://schema.org#", schema.Namespace),
),
}
}
// Marshal takes an [Activity] and returns JSON-LD in compacted document form.
//
// This is the shape of JSON you want to exchange with other servers and
// clients.
//
// The compaction context is a JSON document representing the JSON-LD context
// that should be used for compaction.
func (p *Processor) Marshal(
compactionContext json.RawMessage,
activity Activity,
) (json.RawMessage, error) {
if compactionContext[0] == '{' {
ctx, err := json.GetContextDocument(compactionContext)
if err != nil {
return nil, err
}
compactionContext = ctx
}
return p.ldproc.Compact(
compactionContext,
[]ld.Node{ld.Node(activity)},
"",
)
}
// Unmarshal takes a JSON document and returns an [Activity] that represents it
// using JSON-LD expanded document form.
//
// In JSON-LD the result of expanding a document is always a list. But in the
// case of ActivityPub we only ever send out a single document at a time, so it
// returns [Activity]. If the result happens to have more than one object an
// error is raised so it doesn't go unnoticed.
func (p *Processor) Unmarshal(
document json.RawMessage,
) (*Activity, error) {
res, err := p.ldproc.Expand(document, "")
if err != nil {
return nil, err
}
if lres := len(res); lres != 1 {
return nil, fmt.Errorf("expected a single document, got: %d", lres)
}
return (*Activity)(&res[0]), nil
}
// RegisterContextURL adds or overrides a context document for the specified
// remote context URL in the loader.
func (p *Processor) RegisterContextURL(
url string,
doc json.RawMessage,
) error {
return p.l.RegisterContextURL(url, doc)
}
// RegisterContextPath adds or overrides a context document for the specified
// remote path in the loader.
//
// Paths are always matches as URL suffixes.
func (p *Processor) RegisterContextPath(
path string,
doc json.RawMessage,
) error {
return p.l.RegisterContextPath(path, doc)
}