Merge branch 'feature/handlers' into develop

This commit is contained in:
Maxim Lebedev 2024-02-15 11:56:37 +06:00
commit 326b7a478a
Signed by: toby3d
GPG Key ID: 1F14E25B7C119FC5
98 changed files with 20955 additions and 218 deletions

8
go.mod
View File

@ -14,8 +14,16 @@ require (
gopkg.in/yaml.v3 v3.0.1
)
require (
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 // indirect
github.com/valyala/fastjson v1.6.4 // indirect
)
require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/go-ap/activitypub v0.0.0-20240211124657-820024a66b78
github.com/valyala/bytebufferpool v1.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

10
go.sum
View File

@ -1,3 +1,5 @@
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
@ -7,6 +9,12 @@ github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA=
github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18=
github.com/go-ap/activitypub v0.0.0-20240211124657-820024a66b78 h1:M3QPsOk2J/AyyIODQQf2Jm9vsp6Jor0NQWyIBzI3oSM=
github.com/go-ap/activitypub v0.0.0-20240211124657-820024a66b78/go.mod h1:cJ9Ye0ZNSMN7RzZDBRY3E+8M3Bpf/R1JX22Ir9yX6WI=
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 h1:I2nuhyVI/48VXoRCCZR2hYBgnSXa+EuDJf/VyX06TC0=
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7/go.mod h1:5x8a6P/dhmMGFxWLcyYlyOuJ2lRNaHGhRv+yu8BaTSI=
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 h1:GMKIYXyXPGIp+hYiWOhfqK4A023HdgisDT4YGgf99mw=
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@ -15,6 +23,8 @@ github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM=
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=

View File

@ -0,0 +1,72 @@
package http
import (
"encoding/json"
"net/http"
"time"
"github.com/go-ap/activitypub"
"source.toby3d.me/toby3d/home/internal/common"
"source.toby3d.me/toby3d/home/internal/domain"
)
type (
Handler struct{}
/* TODO(toby3d)
Profile struct {
*activitypub.Person
// Mastodon related
// Pinned posts. See [Featured collection].
//
// [Featured collection]: https://docs.joinmastodon.org/spec/activitypub/#featured
Featured []interface{} `json:"featured"`
// Required for Move activity.
AlsoKnownAs []string `json:"alsoKnownAs"`
// Will be shown as a locked account.
ManuallyApprovesFollowers bool `json:"manuallyApprovesFollowers"`
// Will be shown in the profile directory.
// See [Discoverability flag].
//
// [Discoverability flag]: https://docs.joinmastodon.org/spec/activitypub/#discoverable
Discoverable bool `json:"discoverable"`
}
*/
)
func NewHandler() *Handler {
return &Handler{}
}
func (Handler) HandleProfile(site *domain.Site) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
langRef := activitypub.LangRef(site.DefaultLanguage.Lang())
person := activitypub.PersonNew(activitypub.IRI(site.BaseURL.String()))
person.URL = person.ID
person.Name.Add(activitypub.LangRefValueNew(langRef, "Maxim Lebedev"))
person.Summary.Add(activitypub.LangRefValueNew(langRef, "Creative dude from russia"))
person.PreferredUsername.Add(activitypub.LangRefValueNew(langRef, "toby3d"))
person.Published = time.Date(2009, time.February, 0, 0, 0, 0, 0, time.UTC)
w.Header().Set(common.HeaderContentType, common.MIMEApplicationActivityJSONCharsetUTF8)
_ = json.NewEncoder(w).Encode(person)
})
}
func (Handler) HandleEntry(site *domain.Site, entry *domain.Entry) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
resp := activitypub.ObjectNew(activitypub.NoteType)
resp.ID = activitypub.ID(site.BaseURL.JoinPath(entry.File.Path()).String())
resp.Content.Add(activitypub.LangRefValueNew(activitypub.LangRef(entry.Language.Lang()),
string(entry.Content)))
w.Header().Set(common.HeaderContentType, common.MIMEApplicationActivityJSONCharsetUTF8)
_ = json.NewEncoder(w).Encode(resp)
})
}

View File

@ -1,34 +1,34 @@
package home
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"io/fs"
"log"
"mime"
"net"
"net/http"
"os"
"strings"
"time"
"golang.org/x/text/language"
activitypubhttpdelivery "source.toby3d.me/toby3d/home/internal/activitypub/delivery/http"
"source.toby3d.me/toby3d/home/internal/common"
"source.toby3d.me/toby3d/home/internal/domain"
"source.toby3d.me/toby3d/home/internal/entry"
pagefsrepo "source.toby3d.me/toby3d/home/internal/entry/repository/fs"
pageucase "source.toby3d.me/toby3d/home/internal/entry/usecase"
"source.toby3d.me/toby3d/home/internal/middleware"
resourcehttpdelivery "source.toby3d.me/toby3d/home/internal/resource/delivery/http"
resourcefsrepo "source.toby3d.me/toby3d/home/internal/resource/repository/fs"
resourceucase "source.toby3d.me/toby3d/home/internal/resource/usecase"
servercase "source.toby3d.me/toby3d/home/internal/server/usecase"
sitehttpdelivery "source.toby3d.me/toby3d/home/internal/site/delivery/http"
sitefsrepo "source.toby3d.me/toby3d/home/internal/site/repository/fs"
siteucase "source.toby3d.me/toby3d/home/internal/site/usecase"
statichttpdelivery "source.toby3d.me/toby3d/home/internal/static/delivery/http"
staticfsrepo "source.toby3d.me/toby3d/home/internal/static/repository/fs"
staticucase "source.toby3d.me/toby3d/home/internal/static/usecase"
themehttpdelivery "source.toby3d.me/toby3d/home/internal/theme/delivery/http"
themefsrepo "source.toby3d.me/toby3d/home/internal/theme/repository/fs"
themeucase "source.toby3d.me/toby3d/home/internal/theme/usecase"
"source.toby3d.me/toby3d/home/internal/urlutil"
@ -62,80 +62,25 @@ func NewApp(logger *log.Logger, config *domain.Config) (*App, error) {
serverer := servercase.NewServerUseCase(sites)
webfingerer := webfingerucase.NewWebFingerUseCase(sites)
webfingerHandler := webfingerhttpdelivery.NewHandler(webfingerer)
resourceHandler := resourcehttpdelivery.NewHandler(resourcer, contentDir)
staticHandler := statichttpdelivery.NewHandler(staticer)
siteHandler := sitehttpdelivery.NewHandler(siter)
themeHandler := themehttpdelivery.NewHandler(themer)
activityPubHadnler := activitypubhttpdelivery.NewHandler()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// NOTE(toby3d): any static file is public and unprotected by
// design, so it's safe to search it first before deep down to
// any page or it's resource which might be protected by
// middleware or something else.
if static, err := staticer.Do(r.Context(), strings.TrimPrefix(r.URL.Path, "/")); err == nil {
http.ServeContent(w, r, static.Name(), static.ModTime(), static)
// NOTE(toby3d): any file in $HOME_STATIC_DIR is public and
// unprotected by design, so it's safe to search it first before
// deep down to any page or it's resource which might be
// protected by middleware or something else.
if handler, err := staticHandler.Handle(r.Context(), r.URL.Path); err == nil {
handler.ServeHTTP(w, r)
return
}
head, tail := urlutil.ShiftPath(r.URL.Path)
lang := domain.LanguageUnd
// NOTE(toby3d): read $HOME_CONTENT_DIR/index.md as a source of
// truth and global settings for any child entry.
s, err := siter.Do(r.Context(), lang)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
switch strings.ToLower(head) {
case "":
if s.IsMultiLingual() {
supported := make([]language.Tag, len(s.Languages))
for i := range s.Languages {
supported[i] = language.Make(s.Languages[i].Lang())
}
if s.DefaultLanguage != domain.LanguageUnd {
supported = append([]language.Tag{language.Make(s.DefaultLanguage.Code())},
supported...)
}
requested, _, _ := language.ParseAcceptLanguage(r.Header.Get(
common.HeaderAcceptLanguage))
if len(requested) == 0 {
requested = append(requested, language.English)
}
matched, _, _ := language.NewMatcher(supported).Match(requested...)
lang = domain.NewLanguage(matched.String())
http.Redirect(w, r, "/"+lang.Lang()+"/", http.StatusSeeOther)
return
}
e, err := entrier.Do(r.Context(), lang, r.URL.Path)
if err != nil {
if errors.Is(err, entry.ErrNotExist) {
http.NotFound(w, r)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
template, err := themer.Do(r.Context(), s, e)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set(common.HeaderContentType, common.MIMETextHTMLCharsetUTF8)
if err = template(w); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
case ".well-known":
switch strings.ToLower(tail) {
case "/webfinger":
@ -145,83 +90,53 @@ func NewApp(logger *log.Logger, config *domain.Config) (*App, error) {
}
}
if s.IsMultiLingual() {
// NOTE(toby3d): $HOME_CONTENT_DIR contains at least two
// index.md with different language codes.
head, tail = urlutil.ShiftPath(r.URL.Path)
// NOTE(toby3d): client request '/:lang/...', try to
// understand which language code in subdir is requested.
lang = domain.NewLanguage(head)
// NOTE(toby3d): get localized site config for requested
// subdir if exists.
if s, err = siter.Do(r.Context(), lang); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
if res, err := resourcer.Do(r.Context(), r.URL.Path); err == nil {
// TODO(toby3d) : ugly workaround, must be refactored
resFile, err := contentDir.Open(res.File.Path())
if err != nil {
http.Error(w, "cannot open: "+err.Error(), http.StatusInternalServerError)
return
}
defer resFile.Close()
resBytes, err := io.ReadAll(resFile)
if err != nil {
http.Error(w, "cannot read all: "+err.Error(), http.StatusInternalServerError)
return
}
defer resFile.Close()
http.ServeContent(w, r, res.Name(), domain.ResourceModTime(res), bytes.NewReader(resBytes))
// NOTE(toby3d): read $HOME_CONTENT_DIR/index.md as a source of
// truth and global settings for any child entry.
site, handler, err := siteHandler.Handle(r.Context(), head)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// NOTE(toby3d): search entry for requested URL and language
// code in subdir.
e, err := entrier.Do(r.Context(), lang, tail)
if err != nil {
if errors.Is(err, entry.ErrNotExist) {
http.NotFound(w, r)
mediaType, _, _ := mime.ParseMediaType(r.Header.Get(common.HeaderAccept))
if handler != nil {
if strings.EqualFold(mediaType, common.MIMEApplicationLdJSON) {
activityPubHadnler.HandleProfile(site).ServeHTTP(w, r)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
handler.ServeHTTP(w, r)
}
return
}
// NOTE(toby3d): wrap founded entry into theme template and
// answer to client.
contentLanguage := make([]string, len(e.Translations))
for i := range e.Translations {
contentLanguage[i] = e.Translations[i].Language.Code()
if site.IsMultiLingual() {
r.URL.Path = tail
}
w.Header().Set(common.HeaderContentLanguage, strings.Join(contentLanguage, ", "))
template, err := themer.Do(r.Context(), s, e)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
if handler, err = resourceHandler.Handle(r.Context(), r.URL.Path); err == nil {
handler.ServeHTTP(w, r)
return
}
w.Header().Set(common.HeaderContentType, common.MIMETextHTMLCharsetUTF8)
if err = template(w); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
entry, err := entrier.Do(r.Context(), site.Language, r.URL.Path)
if err != nil {
http.NotFound(w, r)
return
}
switch strings.ToLower(mediaType) {
default:
themeHandler.Handle(site, entry).ServeHTTP(w, r)
case common.MIMEApplicationLdJSON:
activityPubHadnler.HandleEntry(site, entry).ServeHTTP(w, r)
}
})
chain := middleware.Chain{
middleware.LogFmt(),
// middleware.LogFmt(),
middleware.Redirect(middleware.RedirectConfig{Serverer: serverer}),
middleware.Header(middleware.HeaderConfig{Serverer: serverer}),
}

View File

@ -1,5 +1,10 @@
package domain
import (
"path"
"testing"
)
type Entry struct {
Language Language
Params map[string]any
@ -18,3 +23,18 @@ func (e Entry) IsHome() bool {
func (e Entry) IsTranslated() bool {
return 1 < len(e.Translations)
}
func TestEntry(tb testing.TB) *Entry {
tb.Helper()
return &Entry{
Language: NewLanguage("en"),
Params: make(map[string]any),
File: NewPath(path.Join("sample-page.en.md")),
Description: "do not use in production",
Title: "Sample Page",
Content: []byte("Hello, world!"),
Resources: make([]*Resource, 0),
Translations: make([]*Entry, 0),
}
}

View File

@ -2,6 +2,7 @@ package domain
import (
"crypto/md5"
"path"
"path/filepath"
"strings"
)
@ -19,16 +20,16 @@ type Path struct {
uniqueId string
}
func NewPath(path string) Path {
func NewPath(p string) Path {
out := Path{
Language: LanguageUnd,
baseFileName: "",
contentBaseName: "",
dir: filepath.Dir(path),
ext: strings.TrimPrefix(filepath.Ext(path), "."),
filename: path,
logicalName: filepath.Base(path),
path: path,
dir: filepath.Dir(p),
ext: strings.TrimPrefix(filepath.Ext(p), "."),
filename: p,
logicalName: filepath.Base(p),
path: "",
translationBaseName: "",
uniqueId: "",
}
@ -54,6 +55,8 @@ func NewPath(path string) Path {
out.contentBaseName = filepath.Base(out.dir)
}
out.path = path.Join(out.Language.code, out.dir, out.contentBaseName)
hash := md5.New()
_, _ = hash.Write([]byte(out.path))
out.uniqueId = string(hash.Sum(nil))
@ -99,6 +102,7 @@ func (p Path) Ext() string {
return p.ext
}
// Filename returns the file path relative to $HOME_CONTENT_DIR.
func (p Path) Filename() string {
return p.filename
}
@ -113,6 +117,7 @@ func (p Path) LogicalName() string {
return p.logicalName
}
// Path returns the relative URL path to the file.
func (p Path) Path() string {
return p.path
}

View File

@ -0,0 +1,73 @@
package memory
import (
"context"
"errors"
"fmt"
"io/fs"
"sync"
"testing/fstest"
"github.com/adrg/frontmatter"
"gopkg.in/yaml.v2"
"source.toby3d.me/toby3d/home/internal/domain"
"source.toby3d.me/toby3d/home/internal/entry"
)
type (
Page struct {
Title string `yaml:"title"`
Description string `yaml:"description"`
Params map[string]any `yaml:",inline"`
Content []byte `yaml:"-"`
}
memoryEntryRepository struct {
mutex *sync.RWMutex
files fstest.MapFS
}
)
var FrontMatterFormats = []*frontmatter.Format{
frontmatter.NewFormat(`---`, `---`, yaml.Unmarshal),
}
func (repo *memoryEntryRepository) Get(_ context.Context, lang domain.Language, p string) (*domain.Entry, error) {
f, err := repo.files.Open(p)
if err != nil {
return nil, fmt.Errorf("cannot get entry from memory: %w", err)
}
defer f.Close()
data := &Page{
Params: make(map[string]any),
}
if data.Content, err = frontmatter.Parse(f, data, FrontMatterFormats...); err != nil {
return nil, fmt.Errorf("cannot parse entry content as FrontMatter: %w", err)
}
return &domain.Entry{
File: domain.NewPath(p),
Language: lang,
Title: data.Title,
Content: data.Content,
Description: data.Description,
Params: data.Params,
Resources: make([]*domain.Resource, 0),
Translations: make([]*domain.Entry, 0),
}, nil
}
func (repo *memoryEntryRepository) Stat(_ context.Context, l domain.Language, p string) (bool, error) {
_, err := fs.Stat(repo.files, p)
return errors.Is(err, fs.ErrExist), nil
}
func NewMemoryEntryRepository(files fstest.MapFS) entry.Repository {
return &memoryEntryRepository{
mutex: new(sync.RWMutex),
files: files,
}
}

View File

@ -1,41 +0,0 @@
package memory
import (
"context"
"errors"
"fmt"
"io/fs"
"sync"
"testing/fstest"
"source.toby3d.me/toby3d/home/internal/domain"
"source.toby3d.me/toby3d/home/internal/entry"
)
type memoryEntryRepository struct {
mutex *sync.RWMutex
files fstest.MapFS
}
func (repo *memoryEntryRepository) Get(_ context.Context, _ domain.Language, p string) (*domain.Page, error) {
f, err := repo.files.Open(p)
if err != nil {
return nil, fmt.Errorf("cannot get entry from memory: %w", err)
}
defer f.Close()
return nil, nil
}
func (repo *memoryEntryRepository) Stat(_ context.Context, l domain.Language, p string) (bool, error) {
_, err := fs.Stat(repo.files, p)
return errors.Is(err, fs.ErrExist), nil
}
func NewMemoryEntryRepository(files fstest.MapFS) entry.Repository {
return &memoryEntryRepository{
mutex: new(sync.RWMutex),
files: files,
}
}

View File

@ -12,10 +12,17 @@ import (
"source.toby3d.me/toby3d/home/internal/urlutil"
)
type entryUseCase struct {
entries entry.Repository
resources resource.Repository
}
type (
entryUseCase struct {
entries entry.Repository
resources resource.Repository
}
stubEntryUseCase struct {
entry *domain.Entry
err error
}
)
func NewEntryUseCase(entries entry.Repository, resources resource.Repository) entry.UseCase {
return &entryUseCase{
@ -78,3 +85,14 @@ func (ucase *entryUseCase) Do(ctx context.Context, lang domain.Language, p strin
return nil, fmt.Errorf("cannot find page on path '%s': %w", p, entry.ErrNotExist)
}
func NewStubEntryUseCase(entry *domain.Entry, err error) entry.UseCase {
return &stubEntryUseCase{
entry: entry,
err: err,
}
}
func (ucase *stubEntryUseCase) Do(_ context.Context, _ domain.Language, _ string) (*domain.Entry, error) {
return ucase.entry, ucase.err
}

View File

@ -0,0 +1,48 @@
package http
import (
"bytes"
"context"
"fmt"
"io"
"io/fs"
"net/http"
"source.toby3d.me/toby3d/home/internal/domain"
"source.toby3d.me/toby3d/home/internal/resource"
)
type Handler struct {
contentDir fs.FS
resources resource.UseCase
}
func NewHandler(resources resource.UseCase, contentDir fs.FS) *Handler {
return &Handler{
contentDir: contentDir,
resources: resources,
}
}
func (h *Handler) Handle(ctx context.Context, path string) (http.Handler, error) {
res, err := h.resources.Do(ctx, path)
if err != nil {
return nil, err
}
file, err := h.contentDir.Open(res.File.Filename())
if err != nil {
return nil, fmt.Errorf("cannot open resource: %w", err)
}
defer file.Close()
resBytes, err := io.ReadAll(file)
if err != nil {
return nil, fmt.Errorf("cannot read resource: %w", err)
}
// TODO(toby3d): ugly workaround, refactor that.
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.ServeContent(w, r, res.Name(), domain.ResourceModTime(res), bytes.NewReader(resBytes))
}), nil
}

View File

@ -2,6 +2,7 @@ package resource
import (
"context"
"errors"
"source.toby3d.me/toby3d/home/internal/domain"
)
@ -13,3 +14,5 @@ type Repository interface {
// Fetch returns all resources from dir recursevly.
Fetch(ctx context.Context, pattern string) (domain.Resources, int, error)
}
var ErrIsDir error = errors.New("resource is a directory")

View File

@ -25,6 +25,10 @@ func (repo *fileServerResourceRepository) Get(ctx context.Context, p string) (*d
return nil, fmt.Errorf("cannot stat resource on path '%s': %w", p, err)
}
if info.IsDir() {
return nil, fmt.Errorf("cannot open resource on path '%s': %w", p, resource.ErrIsDir)
}
f, err := repo.root.Open(p)
if err != nil {
return nil, fmt.Errorf("cannot open resource on path '%s': %w", p, err)

View File

@ -21,9 +21,7 @@ func NewResourceUseCase(resources resource.Repository) resource.UseCase {
}
func (ucase *resourceUseCase) Do(ctx context.Context, p string) (*domain.Resource, error) {
p = strings.TrimPrefix(path.Clean(p), "/")
r, err := ucase.resources.Get(ctx, p)
r, err := ucase.resources.Get(ctx, strings.TrimPrefix(path.Clean(p), "/"))
if err != nil {
return nil, fmt.Errorf("cannot get resource file: %w", err)
}

View File

@ -15,7 +15,7 @@ func TestDo(t *testing.T) {
site := domain.TestSite(t)
actual, err := usecase.NewServerUseCase().Do(context.Background(), *site)
actual, err := usecase.NewServerUseCase().Do(context.Background(), site)
if err != nil {
t.Fatal(err)
}

View File

@ -0,0 +1,54 @@
package http
import (
"context"
"fmt"
"net/http"
"golang.org/x/text/language"
"source.toby3d.me/toby3d/home/internal/common"
"source.toby3d.me/toby3d/home/internal/domain"
"source.toby3d.me/toby3d/home/internal/site"
)
type Handler struct {
sites site.UseCase
}
func NewHandler(sites site.UseCase) *Handler {
return &Handler{sites: sites}
}
func (h *Handler) Handle(ctx context.Context, head string) (*domain.Site, http.Handler, error) {
lang := domain.NewLanguage(head)
site, err := h.sites.Do(ctx, lang)
if err != nil {
return nil, nil, fmt.Errorf("cannot handle site: %w", err)
}
if !site.IsMultiLingual() || head != "" {
return site, nil, nil
}
supported := make([]language.Tag, len(site.Languages))
for i := range site.Languages {
supported[i] = language.Make(site.Languages[i].Lang())
}
if site.DefaultLanguage != domain.LanguageUnd {
supported = append([]language.Tag{language.Make(site.DefaultLanguage.Code())}, supported...)
}
return site, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requested, _, _ := language.ParseAcceptLanguage(r.Header.Get(common.HeaderAcceptLanguage))
if len(requested) == 0 {
requested = append(requested, language.English)
}
matched, _, _ := language.NewMatcher(supported).Match(requested...)
http.Redirect(w, r, "/"+domain.NewLanguage(matched.String()).Lang()+"/", http.StatusSeeOther)
}), nil
}

View File

@ -1,11 +1,9 @@
package http
import (
"errors"
"io/fs"
"context"
"fmt"
"net/http"
"path"
"strings"
"source.toby3d.me/toby3d/home/internal/static"
)
@ -18,17 +16,13 @@ func NewHandler(static static.UseCase) *Handler {
return &Handler{static: static}
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s, err := h.static.Do(r.Context(), strings.TrimPrefix(path.Clean(r.URL.Path), "/"))
func (h *Handler) Handle(ctx context.Context, path string) (http.Handler, error) {
static, err := h.static.Do(ctx, path)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
http.NotFound(w, r)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
return nil, fmt.Errorf("cannot handle static: %w", err)
}
http.ServeContent(w, r, s.Name(), s.ModTime(), s)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.ServeContent(w, r, static.Name(), static.ModTime(), static)
}), nil
}

View File

@ -1,6 +1,7 @@
package http_test
import (
"context"
"net/http"
"net/http/httptest"
"strings"
@ -18,11 +19,16 @@ func TestHandler_ServeHTTP(t *testing.T) {
t.Parallel()
req := httptest.NewRequest(http.MethodGet, "/robots.txt", nil)
w := httptest.NewRecorder()
testStatic := domain.NewStatic(strings.NewReader("User-agent: *\nAllow: /"), time.Now().UTC(), "robots.txt")
delivery.NewHandler(usecase.NewStaticUseCase(repository.NewStubStaticRepository(nil, testStatic, false))).
ServeHTTP(w, req)
staticService := usecase.NewStaticUseCase(repository.NewStubStaticRepository(nil, testStatic, false))
handler, err := delivery.NewHandler(staticService).Handle(context.Background(), req.URL.Path)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
resp := w.Result()
if expect := http.StatusOK; resp.StatusCode != expect {

View File

@ -21,9 +21,7 @@ func NewStaticUseCase(store static.Repository) static.UseCase {
}
func (ucase *staticUseCase) Do(ctx context.Context, p string) (*domain.Static, error) {
p = strings.TrimPrefix(path.Clean(p), "/")
s, err := ucase.store.Get(ctx, p)
s, err := ucase.store.Get(ctx, strings.TrimPrefix(path.Clean(p), "/"))
if err != nil {
return nil, fmt.Errorf("cannot get static file: %w", err)
}

View File

@ -0,0 +1,49 @@
package http
import (
"net/http"
"strings"
"source.toby3d.me/toby3d/home/internal/common"
"source.toby3d.me/toby3d/home/internal/domain"
"source.toby3d.me/toby3d/home/internal/theme"
)
type Handler struct {
themes theme.UseCase
}
func NewHandler(themes theme.UseCase) *Handler {
return &Handler{
themes: themes,
}
}
func (h *Handler) Handle(site *domain.Site, entry *domain.Entry) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// TODO(toby3d): handle home page.
// TODO(toby3d): handle sections.
// TODO(toby3d): handle errors.
// NOTE(toby3d): wrap founded entry into theme template and
// answer to client.
contentLanguage := make([]string, len(entry.Translations))
for i := range entry.Translations {
contentLanguage[i] = entry.Translations[i].Language.Code()
}
w.Header().Set(common.HeaderContentLanguage, strings.Join(contentLanguage, ", "))
template, err := h.themes.Do(r.Context(), site, entry)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set(common.HeaderContentType, common.MIMETextHTMLCharsetUTF8)
if err = template(w); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
}

View File

@ -11,10 +11,14 @@ import (
"source.toby3d.me/toby3d/home/internal/theme"
)
type themeUseCase struct {
partials fs.FS
themes theme.Repository
}
type (
themeUseCase struct {
partials fs.FS
themes theme.Repository
}
dummyThemeUseCase struct{}
)
func NewThemeUseCase(partials fs.FS, themes theme.Repository) theme.UseCase {
return &themeUseCase{
@ -36,3 +40,11 @@ func (ucase *themeUseCase) Do(ctx context.Context, s *domain.Site, e *domain.Ent
})
}, nil
}
func NewDummyThemeUseCase() theme.UseCase {
return dummyThemeUseCase{}
}
func (dummyThemeUseCase) Do(_ context.Context, _ *domain.Site, _ *domain.Entry) (theme.Writer, error) {
return nil, nil
}

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Go xsd:duration
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,244 @@
// Handles xsd:duration to time.Duration conversion
package xsd
import (
"bytes"
"fmt"
"strconv"
"time"
)
// Extending time constants with the values for day, week, month, year
const (
Day = time.Hour * 24
// These values are not precise, probably why the time package does not implement them
// We need them here because xsd:duration uses them
Monthish = Day * 30
Yearish = Day * 356
)
// durationPair holds information about a pair of (uint/ufloat)(PeriodTag) values in a xsd:duration string
type durationPair struct {
v time.Duration
typ byte
}
func getTimeBaseDuration(b byte) time.Duration {
switch b {
case tagHour:
return time.Hour
case tagMinute:
return time.Minute
case tagSecond:
return time.Second
}
return 0
}
func getDateBaseDuration(b byte) time.Duration {
switch b {
case tagYear:
return Yearish
case tagMonth:
return Monthish
case tagDay:
return Day
}
return 0
}
// validByteForFloats checks
func validByteForFloats(b byte) bool {
// + , - . 0 1 2 3 4 5 6 7 8 9
return (b >= 43 && b <= 46) || (b >= 48 && b <= 57)
}
func parseTagWithValue(data []byte, start, tagPos int, isTime bool) (*durationPair, error) {
d := new(durationPair)
d.typ = data[tagPos]
if d.typ == tagSecond {
// seconds can be represented in float, we need to parse accordingly
if v, err := strconv.ParseFloat(string(data[start:tagPos]), 32); err == nil {
d.v = time.Duration(float64(time.Second) * v)
}
} else {
if v, err := strconv.ParseInt(string(data[start:tagPos]), 10, 32); err == nil {
if isTime {
d.v = getTimeBaseDuration(d.typ) * time.Duration(v)
} else {
d.v = getDateBaseDuration(d.typ) * time.Duration(v)
}
}
}
if d.v < 0 {
return nil, fmt.Errorf("the minus sign must appear first in the duration")
}
return d, nil
}
// loadUintVal receives the data and position at which to try to load the duration element
// isTime is used for distinguishing between P1M - 1 month, and PT1M - 1 minute
// it returns a duration pair if it can read one at the start of data,
// and the number of bytes read from data
func loadUintVal(data []byte, start int, isTime bool) (*durationPair, int, error) {
for i := start; i < len(data); i++ {
chr := data[i]
if validTag(data[i]) {
d, err := parseTagWithValue(data, start, i, isTime)
return d, i, err
}
if validByteForFloats(chr) {
continue
}
return nil, i, fmt.Errorf("invalid character %c at pos %d", chr, i)
}
return nil, len(data), fmt.Errorf("unable to recognize any duration value")
}
const (
tagDuration = 'P'
tagTime = 'T'
tagYear = 'Y'
tagMonth = 'M'
tagDay = 'D'
tagHour = 'H'
tagMinute = 'M'
tagSecond = 'S'
)
func validTag(b byte) bool {
return b == tagYear || b == tagMonth || b == tagDay || b == tagHour || b == tagMinute || b == tagSecond
}
// Unmarshal takes a byte array and unmarshals it to a time.Duration value
// It is used to parse values in the following format: -PuYuMuDTuHuMufS, where:
// * - shows if the duration is negative
// * P is the duration tag
// * T is the time tag separator
// * Y,M,D,H,M,S are tags for year, month, day, hour, minute, second values
// * u is an unsigned integer value
// * uf is an unsigned float value (just for seconds)
func Unmarshal(data []byte, d *time.Duration) error {
if len(data) == 0 {
return fmt.Errorf("invalid xsd:duration: empty value")
}
pos := 0
// loading if the value is negative
negative := data[pos] == '-'
if negative {
// skipping over the minus
pos++
}
if data[pos] != tagDuration {
return fmt.Errorf("invalid xsd:duration: first character must be %q", 'P')
}
// skipping over the "P"
pos++
onePastEnd := len(data)
if pos >= onePastEnd {
return fmt.Errorf("invalid xsd:duration: at least one number and designator are required")
}
isTime := false
duration := time.Duration(0)
for {
if data[pos] == tagTime {
pos++
isTime = true
}
p, cnt, err := loadUintVal(data, pos, isTime)
if err != nil {
return fmt.Errorf("invalid xsd:duration: %w", err)
}
duration += p.v
pos = cnt + 1
if pos+1 >= onePastEnd {
break
}
}
if negative {
duration *= -1
}
if onePastEnd > pos+1 {
return fmt.Errorf("data contains more bytes than we are able to parse")
}
if d == nil {
return fmt.Errorf("unable to store time.Duration to nil pointer")
}
*d = duration
return nil
}
func Days(d time.Duration) float64 {
dd := d / Day
h := d % Day
return float64(dd) + float64(h)/(24*60*60*1e9)
}
func Months(d time.Duration) float64 {
m := d / Monthish
w := d % Monthish
return float64(m) + float64(w)/(4*7*24*60*60*1e9)
}
func Years(d time.Duration) float64 {
y := d / Yearish
m := d % Yearish
return float64(y) + float64(m)/(12*4*7*24*60*60*1e9)
}
func Marshal(d time.Duration) ([]byte, error) {
if d == 0 {
return []byte{tagDuration, tagTime, '0', tagSecond}, nil
}
neg := d < 0
if neg {
d = -d
}
y := Years(d)
d -= time.Duration(y) * Yearish
m := Months(d)
d -= time.Duration(m) * Monthish
dd := Days(d)
d -= time.Duration(dd) * Day
H := d.Hours()
d -= time.Duration(H) * time.Hour
M := d.Minutes()
d -= time.Duration(M) * time.Minute
s := d.Seconds()
d -= time.Duration(s) * time.Second
b := bytes.Buffer{}
if neg {
b.Write([]byte{'-'})
}
b.Write([]byte{'P'})
if int(y) > 0 {
b.WriteString(fmt.Sprintf("%d%c", int64(y), tagYear))
}
if int(m) > 0 {
b.WriteString(fmt.Sprintf("%d%c", int64(m), tagMonth))
}
if int(dd) > 0 {
b.WriteString(fmt.Sprintf("%d%c", int64(dd), tagDay))
}
if H+M+s > 0 {
b.Write([]byte{tagTime})
if int(H) > 0 {
b.WriteString(fmt.Sprintf("%d%c", int64(H), tagHour))
}
if int(M) > 0 {
b.WriteString(fmt.Sprintf("%d%c", int64(M), tagMinute))
}
if int(s) > 0 {
if s-float64(int(s)) < 0.01 {
b.WriteString(fmt.Sprintf("%d%c", int(s), tagSecond))
} else {
b.WriteString(fmt.Sprintf("%.1f%c", s, tagSecond))
}
}
}
return b.Bytes(), nil
}

15
vendor/github.com/go-ap/activitypub/.build.yml generated vendored Normal file
View File

@ -0,0 +1,15 @@
image: archlinux
packages:
- go
sources:
- https://github.com/go-ap/activitypub
environment:
GO111MODULE: 'on'
tasks:
- tests: |
cd activitypub
make test
make TEST_TARGET=./tests test
- coverage: |
set -a +x
cd activitypub && make coverage

14
vendor/github.com/go-ap/activitypub/.gitignore generated vendored Normal file
View File

@ -0,0 +1,14 @@
# Gogland
.idea/
# Binaries for programs and plugins
*.so
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tools
*.out
*.coverprofile
*pkg

21
vendor/github.com/go-ap/activitypub/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Golang ActitvityPub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

29
vendor/github.com/go-ap/activitypub/Makefile generated vendored Normal file
View File

@ -0,0 +1,29 @@
SHELL := bash
.ONESHELL:
.SHELLFLAGS := -eu -o pipefail -c
.DELETE_ON_ERROR:
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
GO ?= go
TEST := $(GO) test
TEST_FLAGS ?= -v
TEST_TARGET ?= .
GO111MODULE = on
PROJECT_NAME := $(shell basename $(PWD))
.PHONY: test coverage clean download
download:
$(GO) mod download all
$(GO) mod tidy
test: download
$(TEST) $(TEST_FLAGS) $(TEST_TARGET)
coverage: TEST_TARGET := .
coverage: TEST_FLAGS += -covermode=count -coverprofile $(PROJECT_NAME).coverprofile
coverage: test
clean:
$(RM) -v *.coverprofile

80
vendor/github.com/go-ap/activitypub/README.md generated vendored Normal file
View File

@ -0,0 +1,80 @@
# About GoActivityPub: Vocabulary
[![MIT Licensed](https://img.shields.io/github/license/go-ap/activitypub.svg)](https://raw.githubusercontent.com/go-ap/activitypub/master/LICENSE)
[![Build Status](https://builds.sr.ht/~mariusor/activitypub.svg)](https://builds.sr.ht/~mariusor/activitypub)
[![Test Coverage](https://img.shields.io/codecov/c/github/go-ap/activitypub.svg)](https://codecov.io/gh/go-ap/activitypub)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-ap/activitypub)](https://goreportcard.com/report/github.com/go-ap/activitypub)
This project is part of the [GoActivityPub](https://github.com/go-ap) library which helps with creating ActivityPub applications using the Go programming language.
It contains data types for most of the [Activity Vocabulary](https://www.w3.org/TR/activitystreams-vocabulary/) and the [ActivityPub](https://www.w3.org/TR/activitypub/) extension.
They are documented accordingly with annotations from these specifications.
You can find an expanded documentation about the whole library [on SourceHut](https://man.sr.ht/~mariusor/go-activitypub/go-ap/index.md).
For discussions about the projects you can write to the discussions mailing list: [~mariusor/go-activitypub-discuss@lists.sr.ht](mailto:~mariusor/go-activitypub-discuss@lists.sr.ht)
For patches and bug reports please use the dev mailing list: [~mariusor/go-activitypub-dev@lists.sr.ht](mailto:~mariusor/go-activitypub-dev@lists.sr.ht)
## Usage
```go
import vocab "github.com/go-ap/activitypub"
follow := vocab.Activity{
Type: vocab.FollowType,
Actor: vocab.IRI("https://example.com/alice"),
Object: vocab.IRI("https://example.com/janedoe"),
}
```
## Note about generics
The module contains helper functions which make it simpler to deal with the `vocab.Item`
interfaces and they come in two flavours: explicit `OnXXX` and `ToXXX` functions corresponding
to each type and, a generic pair of functions `On[T]` and `To[T]`.
```go
import (
"fmt"
vocab "github.com/go-ap/activitypub"
)
var it vocab.Item = ... // an ActivityPub object unmarshaled from a request
err := vocab.OnActivity(it, func(act *vocab.Activity) error {
if vocab.ContentManagementActivityTypes.Contains(act.Type) {
fmt.Printf("This is a Content Management type activity: %q", act.Type)
}
return nil
})
err := vocab.On[vocab.Activity](it, func(act *vocab.Activity) error {
if vocab.ReactionsActivityTypes.Contains(act.Type) {
fmt.Printf("This is a Reaction type activity: %q", act.Type)
}
return nil
})
```
Before using the generic versions you should consider that they come with a pretty heavy performance penalty:
```
goos: linux
goarch: amd64
pkg: github.com/go-ap/activitypub
cpu: Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz
Benchmark_OnT_vs_On_T/OnObject-8 752387791 1.633 ns/op
Benchmark_OnT_vs_On_T/On_T_Object-8 4656264 261.8 ns/op
Benchmark_OnT_vs_On_T/OnActor-8 739833261 1.596 ns/op
Benchmark_OnT_vs_On_T/On_T_Actor-8 4035148 301.9 ns/op
Benchmark_OnT_vs_On_T/OnActivity-8 751173854 1.604 ns/op
Benchmark_OnT_vs_On_T/On_T_Activity-8 4062598 285.9 ns/op
Benchmark_OnT_vs_On_T/OnIntransitiveActivity-8 675824500 1.640 ns/op
Benchmark_OnT_vs_On_T/On_T_IntransitiveActivity-8 4372798 274.1 ns/op
PASS
ok github.com/go-ap/activitypub 11.350s
```

950
vendor/github.com/go-ap/activitypub/activity.go generated vendored Normal file
View File

@ -0,0 +1,950 @@
package activitypub
import (
"bytes"
"encoding/gob"
"fmt"
"io"
"reflect"
"strings"
"time"
"github.com/valyala/fastjson"
)
// Activity Types
const (
AcceptType ActivityVocabularyType = "Accept"
AddType ActivityVocabularyType = "Add"
AnnounceType ActivityVocabularyType = "Announce"
ArriveType ActivityVocabularyType = "Arrive"
BlockType ActivityVocabularyType = "Block"
CreateType ActivityVocabularyType = "Create"
DeleteType ActivityVocabularyType = "Delete"
DislikeType ActivityVocabularyType = "Dislike"
FlagType ActivityVocabularyType = "Flag"
FollowType ActivityVocabularyType = "Follow"
IgnoreType ActivityVocabularyType = "Ignore"
InviteType ActivityVocabularyType = "Invite"
JoinType ActivityVocabularyType = "Join"
LeaveType ActivityVocabularyType = "Leave"
LikeType ActivityVocabularyType = "Like"
ListenType ActivityVocabularyType = "Listen"
MoveType ActivityVocabularyType = "Move"
OfferType ActivityVocabularyType = "Offer"
QuestionType ActivityVocabularyType = "Question"
RejectType ActivityVocabularyType = "Reject"
ReadType ActivityVocabularyType = "Read"
RemoveType ActivityVocabularyType = "Remove"
TentativeRejectType ActivityVocabularyType = "TentativeReject"
TentativeAcceptType ActivityVocabularyType = "TentativeAccept"
TravelType ActivityVocabularyType = "Travel"
UndoType ActivityVocabularyType = "Undo"
UpdateType ActivityVocabularyType = "Update"
ViewType ActivityVocabularyType = "View"
)
func (a ActivityVocabularyTypes) Contains(typ ActivityVocabularyType) bool {
for _, v := range a {
if strings.EqualFold(string(v), string(typ)) {
return true
}
}
return false
}
// ContentManagementActivityTypes use case primarily deals with activities that involve the creation, modification or
// deletion of content.
//
// https://www.w3.org/TR/activitystreams-vocabulary/#motivations-crud
//
// This includes, for instance, activities such as "John created a new note", "Sally updated an article", and
// "Joe deleted the photo".
var ContentManagementActivityTypes = ActivityVocabularyTypes{
CreateType,
DeleteType,
UpdateType,
}
// CollectionManagementActivityTypes use case primarily deals with activities involving the management of content within
// collections.
//
// https://www.w3.org/TR/activitystreams-vocabulary/#motivations-collection
//
// Examples of collections include things like folders, albums, friend lists, etc.
// This includes, for instance, activities such as "Sally added a file to Folder A", "John moved the file from Folder A
// to Folder B", etc.
var CollectionManagementActivityTypes = ActivityVocabularyTypes{
AddType,
MoveType,
RemoveType,
}
// ReactionsActivityTypes use case primarily deals with reactions to content.
//
// https://www.w3.org/TR/activitystreams-vocabulary/#motivations-reactions
//
// This can include activities such as liking or disliking content, ignoring updates, flagging content as being
// inappropriate, accepting or rejecting objects, etc.
var ReactionsActivityTypes = ActivityVocabularyTypes{
AcceptType,
BlockType,
DislikeType,
FlagType,
IgnoreType,
LikeType,
RejectType,
TentativeAcceptType,
TentativeRejectType,
}
// EventRSVPActivityTypes use case primarily deals with invitations to events and RSVP type responses.
//
// https://www.w3.org/TR/activitystreams-vocabulary/#motivations-rsvp
var EventRSVPActivityTypes = ActivityVocabularyTypes{
AcceptType,
IgnoreType,
InviteType,
RejectType,
TentativeAcceptType,
TentativeRejectType,
}
// GroupManagementActivityTypes use case primarily deals with management of groups.
//
// https://www.w3.org/TR/activitystreams-vocabulary/#motivations-group
//
// It can include, for instance, activities such as "John added Sally to Group A", "Sally joined Group A",
// "Joe left Group A", etc.
var GroupManagementActivityTypes = ActivityVocabularyTypes{
AddType,
JoinType,
LeaveType,
RemoveType,
}
// ContentExperienceActivityTypes use case primarily deals with describing activities involving listening to, reading,
// or viewing content.
//
// https://www.w3.org/TR/activitystreams-vocabulary/#motivations-experience
//
// For instance, "Sally read the article", "Joe listened to the song".
var ContentExperienceActivityTypes = ActivityVocabularyTypes{
ListenType,
ReadType,
ViewType,
}
// GeoSocialEventsActivityTypes use case primarily deals with activities involving geo-tagging type activities.
//
// https://www.w3.org/TR/activitystreams-vocabulary/#motivations-geo
//
// For instance, it can include activities such as "Joe arrived at work", "Sally left work", and
// "John is travel from home to work".
var GeoSocialEventsActivityTypes = ActivityVocabularyTypes{
ArriveType,
LeaveType,
TravelType,
}
// NotificationActivityTypes use case primarily deals with calling attention to particular objects or notifications.
//
// https://www.w3.org/TR/activitystreams-vocabulary/#motivations-notification
var NotificationActivityTypes = ActivityVocabularyTypes{
AnnounceType,
}
// QuestionActivityTypes use case primarily deals with representing inquiries of any type.
//
// https://www.w3.org/TR/activitystreams-vocabulary/#motivations-questions
//
// See 5.4 Representing Questions for more information.
// https://www.w3.org/TR/activitystreams-vocabulary/#questions
var QuestionActivityTypes = ActivityVocabularyTypes{
QuestionType,
}
// RelationshipManagementActivityTypes use case primarily deals with representing activities involving the management of
// interpersonal and social relationships
//
// https://www.w3.org/TR/activitystreams-vocabulary/#motivations-relationships
//
// (e.g. friend requests, management of social network, etc). See 5.2 Representing Relationships Between Entities
// for more information.
// https://www.w3.org/TR/activitystreams-vocabulary/#connections
var RelationshipManagementActivityTypes = ActivityVocabularyTypes{
AcceptType,
AddType,
BlockType,
CreateType,
DeleteType,
FollowType,
IgnoreType,
InviteType,
RejectType,
}
// NegatingActivityTypes use case primarily deals with the ability to redact previously completed activities.
//
// https://www.w3.org/TR/activitystreams-vocabulary/#motivations-undo
//
// See 5.5 Inverse Activities and "Undo" for more information.
// https://www.w3.org/TR/activitystreams-vocabulary/#inverse
var NegatingActivityTypes = ActivityVocabularyTypes{
UndoType,
}
// OffersActivityTypes use case deals with activities involving offering one object to another.
//
// https://www.w3.org/TR/activitystreams-vocabulary/#motivations-offers
//
// It can include, for instance, activities such as "Company A is offering a discount on purchase of Product Z to Sally",
// "Sally is offering to add a File to Folder A", etc.
var OffersActivityTypes = ActivityVocabularyTypes{
OfferType,
}
var IntransitiveActivityTypes = ActivityVocabularyTypes{
ArriveType,
TravelType,
QuestionType,
}
var ActivityTypes = ActivityVocabularyTypes{
AcceptType,
AddType,
AnnounceType,
BlockType,
CreateType,
DeleteType,
DislikeType,
FlagType,
FollowType,
IgnoreType,
InviteType,
JoinType,
LeaveType,
LikeType,
ListenType,
MoveType,
OfferType,
RejectType,
ReadType,
RemoveType,
TentativeRejectType,
TentativeAcceptType,
UndoType,
UpdateType,
ViewType,
}
// HasRecipients is an interface implemented by activities to return their audience
// for further propagation
//
// Please take care to the fact that the de-duplication functionality requires a pointer receiver
// therefore a valid Item interface that wraps around an Object struct, can not be type asserted
// to HasRecipients.
type HasRecipients interface {
// Recipients is a method that should do a recipients de-duplication step and then return
// the remaining recipients.
Recipients() ItemCollection
// Clean is a method that removes BCC/Bto recipients in preparation for public consumption of
// the Object.
Clean()
}
type Activities interface {
Activity
}
// Activity is a subtype of Object that describes some form of action that may happen,
// is currently happening, or has already happened.
// The Activity type itself serves as an abstract base type for all types of activities.
// It is important to note that the Activity type itself does not carry any specific semantics
// about the kind of action being taken.
type Activity struct {
// ID provides the globally unique identifier for anActivity Pub Object or Link.
ID ID `jsonld:"id,omitempty"`
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
Type ActivityVocabularyType `jsonld:"type,omitempty"`
// Name a simple, human-readable, plain-text name for the object.
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
// The intent is to provide a model that is at least semantically similar to attachments in email.
Attachment Item `jsonld:"attachment,omitempty"`
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
// For instance, an object might be attributed to the completion of another activity.
AttributedTo Item `jsonld:"attributedTo,omitempty"`
// Audience identifies one or more entities that represent the total population of entities
// for which the object can considered to be relevant.
Audience ItemCollection `jsonld:"audience,omitempty"`
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
// By default, the value of content is HTML.
// The mediaType property can be used in the object to indicate a different content type.
// (The content MAY be expressed using multiple language-tagged values.)
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
// Context identifies the context within which the object exists or an activity was performed.
// The notion of "context" used is intentionally vague.
// The intended function is to serve as a means of grouping objects and activities that share a
// common originating context or purpose. An example could be all activities relating to a common project or event.
Context Item `jsonld:"context,omitempty"`
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
// If not specified, the content property is assumed to contain text/html content.
MediaType MimeType `jsonld:"mediaType,omitempty"`
// EndTime the date and time describing the actual or expected ending time of the object.
// When used with an Activity object, for instance, the endTime property specifies the moment
// the activity concluded or is expected to conclude.
EndTime time.Time `jsonld:"endTime,omitempty"`
// Generator identifies the entity (e.g. an application) that generated the object.
Generator Item `jsonld:"generator,omitempty"`
// Icon indicates an entity that describes an icon for this object.
// The image should have an aspect ratio of one (horizontal) to one (vertical)
// and should be suitable for presentation at a small size.
Icon Item `jsonld:"icon,omitempty"`
// Image indicates an entity that describes an image for this object.
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
Image Item `jsonld:"image,omitempty"`
// InReplyTo indicates one or more entities for which this object is considered a response.
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
// Location indicates one or more physical or logical locations associated with the object.
Location Item `jsonld:"location,omitempty"`
// Preview identifies an entity that provides a preview of this object.
Preview Item `jsonld:"preview,omitempty"`
// Published the date and time at which the object was published
Published time.Time `jsonld:"published,omitempty"`
// Replies identifies a Collection containing objects considered to be responses to this object.
Replies Item `jsonld:"replies,omitempty"`
// StartTime the date and time describing the actual or expected starting time of the object.
// When used with an Activity object, for instance, the startTime property specifies
// the moment the activity began or is scheduled to begin.
StartTime time.Time `jsonld:"startTime,omitempty"`
// Summary a natural language summarization of the object encoded as HTML.
// *Multiple language tagged summaries may be provided.)
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
// The key difference between attachment and tag is that the former implies association by inclusion,
// while the latter implies associated by reference.
Tag ItemCollection `jsonld:"tag,omitempty"`
// Updated the date and time at which the object was updated
Updated time.Time `jsonld:"updated,omitempty"`
// URL identifies one or more links to representations of the object
URL Item `jsonld:"url,omitempty"`
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
To ItemCollection `jsonld:"to,omitempty"`
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
Bto ItemCollection `jsonld:"bto,omitempty"`
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
CC ItemCollection `jsonld:"cc,omitempty"`
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
BCC ItemCollection `jsonld:"bcc,omitempty"`
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
// the duration property indicates the object's approximate duration.
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
Duration time.Duration `jsonld:"duration,omitempty"`
// This is a list of all Like activities with this object as the object property, added as a side effect.
// The likes collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Likes Item `jsonld:"likes,omitempty"`
// This is a list of all Announce activities with this object as the object property, added as a side effect.
// The shares collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Shares Item `jsonld:"shares,omitempty"`
// Source property is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
// In general, clients do the conversion from source to content, not the other way around.
Source Source `jsonld:"source,omitempty"`
// CanReceiveActivities describes one or more entities that either performed or are expected to perform the activity.
// Any single activity can have multiple actors. The actor may be specified using an indirect Link.
Actor Item `jsonld:"actor,omitempty"`
// Target describes the indirect object, or target, of the activity.
// The precise meaning of the target is largely dependent on the type of action being described
// but will often be the object of the English preposition "to".
// For instance, in the activity "John added a movie to his wishlist",
// the target of the activity is John's wishlist. An activity can have more than one target.
Target Item `jsonld:"target,omitempty"`
// Result describes the result of the activity. For instance, if a particular action results in the creation
// of a new resource, the result property can be used to describe that new resource.
Result Item `jsonld:"result,omitempty"`
// Origin describes an indirect object of the activity from which the activity is directed.
// The precise meaning of the origin is the object of the English preposition "from".
// For instance, in the activity "John moved an item to List B from List A", the origin of the activity is "List A".
Origin Item `jsonld:"origin,omitempty"`
// Instrument identifies one or more objects used (or to be used) in the completion of an Activity.
Instrument Item `jsonld:"instrument,omitempty"`
// Object When used within an Activity, describes the direct object of the activity.
// For instance, in the activity "John added a movie to his wishlist",
// the object of the activity is the movie added.
// When used within a Relationship describes the entity to which the subject is related.
Object Item `jsonld:"object,omitempty"`
}
// GetType returns the ActivityVocabulary type of the current Activity
func (a Activity) GetType() ActivityVocabularyType {
return a.Type
}
// IsLink returns false for Activity objects
func (a Activity) IsLink() bool {
return false
}
// GetID returns the ID corresponding to the Activity object
func (a Activity) GetID() ID {
return a.ID
}
// GetLink returns the IRI corresponding to the Activity object
func (a Activity) GetLink() IRI {
return IRI(a.ID)
}
// IsObject returns true for Activity objects
func (a Activity) IsObject() bool {
return true
}
// IsCollection returns false for Activity objects
func (a Activity) IsCollection() bool {
return false
}
func removeFromCollection(col ItemCollection, items ...Item) ItemCollection {
result := make(ItemCollection, 0)
if len(items) == 0 {
return col
}
for _, ob := range col {
found := false
for _, it := range items {
if ob.GetID().Equals(it.GetID(), false) {
found = true
break
}
}
if !found {
result = append(result, ob)
}
}
return result
}
func removeFromAudience(a *Activity, items ...Item) error {
if a.To != nil {
a.To = removeFromCollection(a.To, items...)
}
if a.Bto != nil {
a.Bto = removeFromCollection(a.Bto, items...)
}
if a.CC != nil {
a.CC = removeFromCollection(a.CC, items...)
}
if a.BCC != nil {
a.BCC = removeFromCollection(a.BCC, items...)
}
if a.Audience != nil {
a.Audience = removeFromCollection(a.Audience, items...)
}
return nil
}
// Recipients performs recipient de-duplication on the Activity's To, Bto, CC and BCC properties
func (a *Activity) Recipients() ItemCollection {
var alwaysRemove ItemCollection
if a.GetType() == BlockType && a.Object != nil {
alwaysRemove = append(alwaysRemove, a.Object)
}
if a.Actor != nil {
alwaysRemove = append(alwaysRemove, a.Actor)
}
if len(alwaysRemove) > 0 {
_ = removeFromAudience(a, alwaysRemove...)
}
return ItemCollectionDeduplication(&a.To, &a.Bto, &a.CC, &a.BCC, &a.Audience)
}
// CleanRecipients checks if the "it" Item has recipients and cleans them if it does
func CleanRecipients(it Item) Item {
if IsNil(it) {
return nil
}
if s, ok := it.(HasRecipients); ok {
s.Clean()
}
return it
}
// Clean removes Bto and BCC properties
func (a *Activity) Clean() {
_ = OnObject(a, func(o *Object) error {
o.Clean()
return nil
})
CleanRecipients(a.Object)
CleanRecipients(a.Actor)
CleanRecipients(a.Target)
}
type (
// Accept indicates that the actor accepts the object. The target property can be used in certain circumstances to indicate
// the context into which the object has been accepted.
Accept = Activity
// Add indicates that the actor has added the object to the target. If the target property is not explicitly specified,
// the target would need to be determined implicitly by context.
// The origin can be used to identify the context from which the object originated.
Add = Activity
// Announce indicates that the actor is calling the target's attention the object.
// The origin typically has no defined meaning.
Announce = Activity
// Block indicates that the actor is blocking the object. Blocking is a stronger form of Ignore.
// The typical use is to support social systems that allow one user to block activities or content of other users.
// The target and origin typically have no defined meaning.
Block = Ignore
// Create indicates that the actor has created the object.
Create = Activity
// Delete indicates that the actor has deleted the object.
// If specified, the origin indicates the context from which the object was deleted.
Delete = Activity
// Dislike indicates that the actor dislikes the object.
Dislike = Activity
// Flag indicates that the actor is "flagging" the object.
// Flagging is defined in the sense common to many social platforms as reporting content as being
// inappropriate for any number of reasons.
Flag = Activity
// Follow indicates that the actor is "following" the object. Following is defined in the sense typically used within
// Social systems in which the actor is interested in any activity performed by or on the object.
// The target and origin typically have no defined meaning.
Follow = Activity
// Ignore indicates that the actor is ignoring the object. The target and origin typically have no defined meaning.
Ignore = Activity
// Invite is a specialization of Offer in which the actor is extending an invitation for the object to the target.
Invite = Offer
// Join indicates that the actor has joined the object. The target and origin typically have no defined meaning.
Join = Activity
// Leave indicates that the actor has left the object. The target and origin typically have no meaning.
Leave = Activity
// Like indicates that the actor likes, recommends or endorses the object.
// The target and origin typically have no defined meaning.
Like = Activity
// Listen inherits all properties from Activity.
Listen = Activity
// Move indicates that the actor has moved object from origin to target.
// If the origin or target are not specified, either can be determined by context.
Move = Activity
// Offer indicates that the actor is offering the object.
// If specified, the target indicates the entity to which the object is being offered.
Offer = Activity
// Reject indicates that the actor is rejecting the object. The target and origin typically have no defined meaning.
Reject = Activity
// Read indicates that the actor has read the object.
Read = Activity
// Remove indicates that the actor is removing the object. If specified,
// the origin indicates the context from which the object is being removed.
Remove = Activity
// TentativeReject is a specialization of Reject in which the rejection is considered tentative.
TentativeReject = Reject
// TentativeAccept is a specialization of Accept indicating that the acceptance is tentative.
TentativeAccept = Accept
// Undo indicates that the actor is undoing the object. In most cases, the object will be an Activity describing
// some previously performed action (for instance, a person may have previously "liked" an article but,
// for whatever reason, might choose to undo that like at some later point in time).
// The target and origin typically have no defined meaning.
Undo = Activity
// Update indicates that the actor has updated the object. Note, however, that this vocabulary does not define a mechanism
// for describing the actual set of modifications made to object.
// The target and origin typically have no defined meaning.
Update = Activity
// View indicates that the actor has viewed the object.
View = Activity
)
// AcceptNew initializes an Accept activity
func AcceptNew(id ID, ob Item) *Accept {
a := ActivityNew(id, AcceptType, ob)
o := Accept(*a)
return &o
}
// AddNew initializes an Add activity
func AddNew(id ID, ob, trgt Item) *Add {
a := ActivityNew(id, AddType, ob)
o := Add(*a)
o.Target = trgt
return &o
}
// AnnounceNew initializes an Announce activity
func AnnounceNew(id ID, ob Item) *Announce {
a := ActivityNew(id, AnnounceType, ob)
o := Announce(*a)
return &o
}
// BlockNew initializes a Block activity
func BlockNew(id ID, ob Item) *Block {
a := ActivityNew(id, BlockType, ob)
o := Block(*a)
return &o
}
// CreateNew initializes a Create activity
func CreateNew(id ID, ob Item) *Create {
a := ActivityNew(id, CreateType, ob)
o := Create(*a)
return &o
}
// DeleteNew initializes a Delete activity
func DeleteNew(id ID, ob Item) *Delete {
a := ActivityNew(id, DeleteType, ob)
o := Delete(*a)
return &o
}
// DislikeNew initializes a Dislike activity
func DislikeNew(id ID, ob Item) *Dislike {
a := ActivityNew(id, DislikeType, ob)
o := Dislike(*a)
return &o
}
// FlagNew initializes a Flag activity
func FlagNew(id ID, ob Item) *Flag {
a := ActivityNew(id, FlagType, ob)
o := Flag(*a)
return &o
}
// FollowNew initializes a Follow activity
func FollowNew(id ID, ob Item) *Follow {
a := ActivityNew(id, FollowType, ob)
o := Follow(*a)
return &o
}
// IgnoreNew initializes an Ignore activity
func IgnoreNew(id ID, ob Item) *Ignore {
a := ActivityNew(id, IgnoreType, ob)
o := Ignore(*a)
return &o
}
// InviteNew initializes an Invite activity
func InviteNew(id ID, ob Item) *Invite {
a := ActivityNew(id, InviteType, ob)
o := Invite(*a)
return &o
}
// JoinNew initializes a Join activity
func JoinNew(id ID, ob Item) *Join {
a := ActivityNew(id, JoinType, ob)
o := Join(*a)
return &o
}
// LeaveNew initializes a Leave activity
func LeaveNew(id ID, ob Item) *Leave {
a := ActivityNew(id, LeaveType, ob)
o := Leave(*a)
return &o
}
// LikeNew initializes a Like activity
func LikeNew(id ID, ob Item) *Like {
a := ActivityNew(id, LikeType, ob)
o := Like(*a)
return &o
}
// ListenNew initializes a Listen activity
func ListenNew(id ID, ob Item) *Listen {
a := ActivityNew(id, ListenType, ob)
o := Listen(*a)
return &o
}
// MoveNew initializes a Move activity
func MoveNew(id ID, ob Item) *Move {
a := ActivityNew(id, MoveType, ob)
o := Move(*a)
return &o
}
// OfferNew initializes an Offer activity
func OfferNew(id ID, ob Item) *Offer {
a := ActivityNew(id, OfferType, ob)
o := Offer(*a)
return &o
}
// RejectNew initializes a Reject activity
func RejectNew(id ID, ob Item) *Reject {
a := ActivityNew(id, RejectType, ob)
o := Reject(*a)
return &o
}
// ReadNew initializes a Read activity
func ReadNew(id ID, ob Item) *Read {
a := ActivityNew(id, ReadType, ob)
o := Read(*a)
return &o
}
// RemoveNew initializes a Remove activity
func RemoveNew(id ID, ob, trgt Item) *Remove {
a := ActivityNew(id, RemoveType, ob)
o := Remove(*a)
o.Target = trgt
return &o
}
// TentativeRejectNew initializes a TentativeReject activity
func TentativeRejectNew(id ID, ob Item) *TentativeReject {
a := ActivityNew(id, TentativeRejectType, ob)
o := TentativeReject(*a)
return &o
}
// TentativeAcceptNew initializes a TentativeAccept activity
func TentativeAcceptNew(id ID, ob Item) *TentativeAccept {
a := ActivityNew(id, TentativeAcceptType, ob)
o := TentativeAccept(*a)
return &o
}
// UndoNew initializes an Undo activity
func UndoNew(id ID, ob Item) *Undo {
a := ActivityNew(id, UndoType, ob)
o := Undo(*a)
return &o
}
// UpdateNew initializes an Update activity
func UpdateNew(id ID, ob Item) *Update {
a := ActivityNew(id, UpdateType, ob)
u := Update(*a)
return &u
}
// ViewNew initializes a View activity
func ViewNew(id ID, ob Item) *View {
a := ActivityNew(id, ViewType, ob)
o := View(*a)
return &o
}
// ActivityNew initializes a basic activity
func ActivityNew(id ID, typ ActivityVocabularyType, ob Item) *Activity {
if !ActivityTypes.Contains(typ) {
typ = ActivityType
}
a := Activity{ID: id, Type: typ}
a.Name = NaturalLanguageValuesNew()
a.Content = NaturalLanguageValuesNew()
a.Object = ob
return &a
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (a *Activity) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadActivity(val, a)
}
func fmtActivityProps(w io.Writer) func(*Activity) error {
return func(a *Activity) error {
if !IsNil(a.Object) {
_, _ = fmt.Fprintf(w, " object: %s", a.Object)
}
return OnIntransitiveActivity(a, fmtIntransitiveActivityProps(w))
}
}
func (a Activity) Format(s fmt.State, verb rune) {
switch verb {
case 's':
if a.Type != "" && a.ID != "" {
_, _ = fmt.Fprintf(s, "%T[%s]( %s )", a, a.Type, a.ID)
} else if a.ID != "" {
_, _ = fmt.Fprintf(s, "%T( %s )", a, a.ID)
} else {
_, _ = fmt.Fprintf(s, "%T[%p]", a, &a)
}
case 'v':
_, _ = fmt.Fprintf(s, "%T[%s] {", a, a.Type)
fmtActivityProps(s)(&a)
_, _ = io.WriteString(s, " }")
}
}
// ToActivity
func ToActivity(it Item) (*Activity, error) {
switch i := it.(type) {
case *Activity:
return i, nil
case Activity:
return &i, nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(Activity))
if reflect.TypeOf(it).ConvertibleTo(typ) {
if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Activity); ok {
return i, nil
}
}
}
return nil, ErrorInvalidType[Activity](it)
}
// MarshalJSON encodes the receiver object to a JSON document.
func (a Activity) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
JSONWrite(&b, '{')
if !JSONWriteActivityValue(&b, a) {
return nil, nil
}
JSONWrite(&b, '}')
return b, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (a *Activity) UnmarshalBinary(data []byte) error {
return a.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (a Activity) MarshalBinary() ([]byte, error) {
return a.GobEncode()
}
func mapIntransitiveActivityProperties(mm map[string][]byte, a *IntransitiveActivity) (hasData bool, err error) {
err = OnObject(a, func(o *Object) error {
hasData, err = mapObjectProperties(mm, o)
return err
})
if a.Actor != nil {
if mm["actor"], err = gobEncodeItem(a.Actor); err != nil {
return hasData, err
}
hasData = true
}
if a.Target != nil {
if mm["target"], err = gobEncodeItem(a.Target); err != nil {
return hasData, err
}
hasData = true
}
if a.Result != nil {
if mm["result"], err = gobEncodeItem(a.Result); err != nil {
return hasData, err
}
hasData = true
}
if a.Instrument != nil {
if mm["instrument"], err = gobEncodeItem(a.Instrument); err != nil {
return hasData, err
}
hasData = true
}
return hasData, err
}
func mapActivityProperties(mm map[string][]byte, a *Activity) (hasData bool, err error) {
err = OnIntransitiveActivity(a, func(a *IntransitiveActivity) error {
hasData, err = mapIntransitiveActivityProperties(mm, a)
return err
})
if a.Object != nil {
if mm["object"], err = gobEncodeItem(a.Object); err != nil {
return hasData, err
}
hasData = true
}
return hasData, err
}
// GobEncode
func (a Activity) GobEncode() ([]byte, error) {
mm := make(map[string][]byte)
hasData, err := mapActivityProperties(mm, &a)
if err != nil {
return nil, err
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
// GobDecode
func (a *Activity) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm, err := gobDecodeObjectAsMap(data)
if err != nil {
return err
}
return unmapActivityProperties(mm, a)
}
// Equals verifies if our receiver Object is equals with the "with" Object
func (a Activity) Equals(with Item) bool {
result := true
err := OnActivity(with, func(w *Activity) error {
_ = OnIntransitiveActivity(a, func(oi *IntransitiveActivity) error {
result = oi.Equals(w)
return nil
})
if w.Object != nil {
if !ItemsEqual(a.Object, w.Object) {
result = false
return nil
}
}
return nil
})
if err != nil {
result = false
}
return result
}

622
vendor/github.com/go-ap/activitypub/actor.go generated vendored Normal file
View File

@ -0,0 +1,622 @@
package activitypub
import (
"bytes"
"encoding/gob"
"encoding/json"
"fmt"
"reflect"
"time"
"github.com/valyala/fastjson"
)
// CanReceiveActivities Types
const (
ApplicationType ActivityVocabularyType = "Application"
GroupType ActivityVocabularyType = "Group"
OrganizationType ActivityVocabularyType = "Organization"
PersonType ActivityVocabularyType = "Person"
ServiceType ActivityVocabularyType = "Service"
)
// ActorTypes represent the valid Actor types.
var ActorTypes = ActivityVocabularyTypes{
ApplicationType,
GroupType,
OrganizationType,
PersonType,
ServiceType,
}
// CanReceiveActivities is generally one of the ActivityStreams Actor Types, but they don't have to be.
// For example, a Profile object might be used as an actor, or a type from an ActivityStreams extension.
// Actors are retrieved like any other Object in ActivityPub.
// Like other ActivityStreams objects, actors have an id, which is a URI.
type CanReceiveActivities Item
type Actors interface {
Actor
}
// Actor is generally one of the ActivityStreams actor Types, but they don't have to be.
// For example, a Profile object might be used as an actor, or a type from an ActivityStreams extension.
// Actors are retrieved like any other Object in ActivityPub.
// Like other ActivityStreams objects, actors have an id, which is a URI.
type Actor struct {
// ID provides the globally unique identifier for anActivity Pub Object or Link.
ID ID `jsonld:"id,omitempty"`
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
Type ActivityVocabularyType `jsonld:"type,omitempty"`
// Name a simple, human-readable, plain-text name for the object.
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
// The intent is to provide a model that is at least semantically similar to attachments in email.
Attachment Item `jsonld:"attachment,omitempty"`
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
// For instance, an object might be attributed to the completion of another activity.
AttributedTo Item `jsonld:"attributedTo,omitempty"`
// Audience identifies one or more entities that represent the total population of entities
// for which the object can considered to be relevant.
Audience ItemCollection `jsonld:"audience,omitempty"`
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
// By default, the value of content is HTML.
// The mediaType property can be used in the object to indicate a different content type.
// (The content MAY be expressed using multiple language-tagged values.)
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
// Context identifies the context within which the object exists or an activity was performed.
// The notion of "context" used is intentionally vague.
// The intended function is to serve as a means of grouping objects and activities that share a
// common originating context or purpose. An example could be all activities relating to a common project or event.
Context Item `jsonld:"context,omitempty"`
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
// If not specified, the content property is assumed to contain text/html content.
MediaType MimeType `jsonld:"mediaType,omitempty"`
// EndTime the date and time describing the actual or expected ending time of the object.
// When used with an Activity object, for instance, the endTime property specifies the moment
// the activity concluded or is expected to conclude.
EndTime time.Time `jsonld:"endTime,omitempty"`
// Generator identifies the entity (e.g. an application) that generated the object.
Generator Item `jsonld:"generator,omitempty"`
// Icon indicates an entity that describes an icon for this object.
// The image should have an aspect ratio of one (horizontal) to one (vertical)
// and should be suitable for presentation at a small size.
Icon Item `jsonld:"icon,omitempty"`
// Image indicates an entity that describes an image for this object.
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
Image Item `jsonld:"image,omitempty"`
// InReplyTo indicates one or more entities for which this object is considered a response.
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
// Location indicates one or more physical or logical locations associated with the object.
Location Item `jsonld:"location,omitempty"`
// Preview identifies an entity that provides a preview of this object.
Preview Item `jsonld:"preview,omitempty"`
// Published the date and time at which the object was published
Published time.Time `jsonld:"published,omitempty"`
// Replies identifies a Collection containing objects considered to be responses to this object.
Replies Item `jsonld:"replies,omitempty"`
// StartTime the date and time describing the actual or expected starting time of the object.
// When used with an Activity object, for instance, the startTime property specifies
// the moment the activity began or is scheduled to begin.
StartTime time.Time `jsonld:"startTime,omitempty"`
// Summary a natural language summarization of the object encoded as HTML.
// *Multiple language tagged summaries may be provided.)
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
// The key difference between attachment and tag is that the former implies association by inclusion,
// while the latter implies associated by reference.
Tag ItemCollection `jsonld:"tag,omitempty"`
// Updated the date and time at which the object was updated
Updated time.Time `jsonld:"updated,omitempty"`
// URL identifies one or more links to representations of the object
URL Item `jsonld:"url,omitempty"`
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
To ItemCollection `jsonld:"to,omitempty"`
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
Bto ItemCollection `jsonld:"bto,omitempty"`
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
CC ItemCollection `jsonld:"cc,omitempty"`
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
BCC ItemCollection `jsonld:"bcc,omitempty"`
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
// the duration property indicates the object's approximate duration.
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
Duration time.Duration `jsonld:"duration,omitempty"`
// This is a list of all Like activities with this object as the object property, added as a side effect.
// The likes collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Likes Item `jsonld:"likes,omitempty"`
// This is a list of all Announce activities with this object as the object property, added as a side effect.
// The shares collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Shares Item `jsonld:"shares,omitempty"`
// Source property is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
// In general, clients do the conversion from source to content, not the other way around.
Source Source `jsonld:"source,omitempty"`
// A reference to an [ActivityStreams] OrderedCollection comprised of all the messages received by the actor;
// see 5.2 Inbox.
Inbox Item `jsonld:"inbox,omitempty"`
// An [ActivityStreams] OrderedCollection comprised of all the messages produced by the actor;
// see 5.1 outbox.
Outbox Item `jsonld:"outbox,omitempty"`
// A link to an [ActivityStreams] collection of the actors that this actor is following;
// see 5.4 Following Collection
Following Item `jsonld:"following,omitempty"`
// A link to an [ActivityStreams] collection of the actors that follow this actor;
// see 5.3 Followers Collection.
Followers Item `jsonld:"followers,omitempty"`
// A link to an [ActivityStreams] collection of objects this actor has liked;
// see 5.5 Liked Collection.
Liked Item `jsonld:"liked,omitempty"`
// A short username which may be used to refer to the actor, with no uniqueness guarantees.
PreferredUsername NaturalLanguageValues `jsonld:"preferredUsername,omitempty,collapsible"`
// A json object which maps additional (typically server/domain-wide) endpoints which may be useful either
// for this actor or someone referencing this actor.
// This mapping may be nested inside the actor document as the value or may be a link
// to a JSON-LD document with these properties.
Endpoints *Endpoints `jsonld:"endpoints,omitempty"`
// A list of supplementary Collections which may be of interest.
Streams ItemCollection `jsonld:"streams,omitempty"`
PublicKey PublicKey `jsonld:"publicKey,omitempty"`
}
// GetID returns the ID corresponding to the current Actor
func (a Actor) GetID() ID {
return a.ID
}
// GetLink returns the IRI corresponding to the current Actor
func (a Actor) GetLink() IRI {
return IRI(a.ID)
}
// GetType returns the type of the current Actor
func (a Actor) GetType() ActivityVocabularyType {
return a.Type
}
// IsLink validates if currentActivity Pub Actor is a Link
func (a Actor) IsLink() bool {
return false
}
// IsObject validates if currentActivity Pub Actor is an Object
func (a Actor) IsObject() bool {
return true
}
// IsCollection returns false for Actor Objects
func (a Actor) IsCollection() bool {
return false
}
// PublicKey holds the ActivityPub compatible public key data
// The document reference can be found at:
// https://web-payments.org/vocabs/security#publicKey
type PublicKey struct {
ID ID `jsonld:"id,omitempty"`
Owner IRI `jsonld:"owner,omitempty"`
PublicKeyPem string `jsonld:"publicKeyPem,omitempty"`
}
func (p *PublicKey) UnmarshalJSON(data []byte) error {
par := fastjson.Parser{}
val, err := par.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadPublicKey(val, p)
}
func (p PublicKey) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
notEmpty := true
JSONWrite(&b, '{')
if v, err := p.ID.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = !JSONWriteProp(&b, "id", v)
}
if len(p.Owner) > 0 {
notEmpty = JSONWriteIRIProp(&b, "owner", p.Owner) || notEmpty
}
if len(p.PublicKeyPem) > 0 {
if pem, err := json.Marshal(p.PublicKeyPem); err == nil {
notEmpty = JSONWriteProp(&b, "publicKeyPem", pem) || notEmpty
}
}
if notEmpty {
JSONWrite(&b, '}')
return b, nil
}
return nil, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (a *Actor) UnmarshalBinary(data []byte) error {
return a.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (a Actor) MarshalBinary() ([]byte, error) {
return a.GobEncode()
}
func (a Actor) GobEncode() ([]byte, error) {
mm := make(map[string][]byte)
hasData, err := mapActorProperties(mm, &a)
if err != nil {
return nil, err
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
func (a *Actor) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm, err := gobDecodeObjectAsMap(data)
if err != nil {
return err
}
return unmapActorProperties(mm, a)
}
type (
// Application describes a software application.
Application = Actor
// Group represents a formal or informal collective of Actors.
Group = Actor
// Organization represents an organization.
Organization = Actor
// Person represents an individual person.
Person = Actor
// Service represents a service of any kind.
Service = Actor
)
// ActorNew initializes an CanReceiveActivities type actor
func ActorNew(id ID, typ ActivityVocabularyType) *Actor {
if !ActorTypes.Contains(typ) {
typ = ActorType
}
a := Actor{ID: id, Type: typ}
a.Name = NaturalLanguageValuesNew()
a.Content = NaturalLanguageValuesNew()
a.Summary = NaturalLanguageValuesNew()
a.PreferredUsername = NaturalLanguageValuesNew()
return &a
}
// ApplicationNew initializes an Application type actor
func ApplicationNew(id ID) *Application {
a := ActorNew(id, ApplicationType)
o := Application(*a)
return &o
}
// GroupNew initializes a Group type actor
func GroupNew(id ID) *Group {
a := ActorNew(id, GroupType)
o := Group(*a)
return &o
}
// OrganizationNew initializes an Organization type actor
func OrganizationNew(id ID) *Organization {
a := ActorNew(id, OrganizationType)
o := Organization(*a)
return &o
}
// PersonNew initializes a Person type actor
func PersonNew(id ID) *Person {
a := ActorNew(id, PersonType)
o := Person(*a)
return &o
}
// ServiceNew initializes a Service type actor
func ServiceNew(id ID) *Service {
a := ActorNew(id, ServiceType)
o := Service(*a)
return &o
}
func (a *Actor) Recipients() ItemCollection {
return ItemCollectionDeduplication(&a.To, &a.Bto, &a.CC, &a.BCC, &a.Audience)
}
func (a *Actor) Clean() {
_ = OnObject(a, func(o *Object) error {
o.Clean()
return nil
})
}
func (a *Actor) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadActor(val, a)
}
func (a Actor) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
notEmpty := false
JSONWrite(&b, '{')
OnObject(a, func(o *Object) error {
notEmpty = JSONWriteObjectValue(&b, *o)
return nil
})
if a.Inbox != nil {
notEmpty = JSONWriteItemProp(&b, "inbox", a.Inbox) || notEmpty
}
if a.Outbox != nil {
notEmpty = JSONWriteItemProp(&b, "outbox", a.Outbox) || notEmpty
}
if a.Following != nil {
notEmpty = JSONWriteItemProp(&b, "following", a.Following) || notEmpty
}
if a.Followers != nil {
notEmpty = JSONWriteItemProp(&b, "followers", a.Followers) || notEmpty
}
if a.Liked != nil {
notEmpty = JSONWriteItemProp(&b, "liked", a.Liked) || notEmpty
}
if a.PreferredUsername != nil {
notEmpty = JSONWriteNaturalLanguageProp(&b, "preferredUsername", a.PreferredUsername) || notEmpty
}
if a.Endpoints != nil {
if v, err := a.Endpoints.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = JSONWriteProp(&b, "endpoints", v) || notEmpty
}
}
if len(a.Streams) > 0 {
notEmpty = JSONWriteItemCollectionProp(&b, "streams", a.Streams, false)
}
if len(a.PublicKey.PublicKeyPem)+len(a.PublicKey.ID) > 0 {
if v, err := a.PublicKey.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = JSONWriteProp(&b, "publicKey", v) || notEmpty
}
}
if notEmpty {
JSONWrite(&b, '}')
return b, nil
}
return nil, nil
}
func (a Actor) Format(s fmt.State, verb rune) {
switch verb {
case 's':
if a.Type != "" && a.ID != "" {
_, _ = fmt.Fprintf(s, "%T[%s]( %s )", a, a.Type, a.ID)
} else if a.ID != "" {
_, _ = fmt.Fprintf(s, "%T( %s )", a, a.ID)
} else {
_, _ = fmt.Fprintf(s, "%T[%p]", a, &a)
}
case 'v':
_, _ = fmt.Fprintf(s, "%T[%s] { }", a, a.Type)
}
}
// Endpoints a json object which maps additional (typically server/domain-wide)
// endpoints which may be useful either for this actor or someone referencing this actor.
// This mapping may be nested inside the actor document as the value or may be a link to
// a JSON-LD document with these properties.
type Endpoints struct {
// UploadMedia Upload endpoint URI for this user for binary data.
UploadMedia Item `jsonld:"uploadMedia,omitempty"`
// OauthAuthorizationEndpoint Endpoint URI so this actor's clients may access remote ActivityStreams objects which require authentication
// to access. To use this endpoint, the client posts an x-www-form-urlencoded id parameter with the value being
// the id of the requested ActivityStreams object.
OauthAuthorizationEndpoint Item `jsonld:"oauthAuthorizationEndpoint,omitempty"`
// OauthTokenEndpoint If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions,
// this endpoint specifies a URI at which a browser-authenticated user may obtain a new authorization grant.
OauthTokenEndpoint Item `jsonld:"oauthTokenEndpoint,omitempty"`
// ProvideClientKey If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions,
// this endpoint specifies a URI at which a client may acquire an access token.
ProvideClientKey Item `jsonld:"provideClientKey,omitempty"`
// SignClientKey If Linked Data Signatures and HTTP Signatures are being used for authentication and authorization,
// this endpoint specifies a URI at which browser-authenticated users may authorize a client's public
// key for client to server interactions.
SignClientKey Item `jsonld:"signClientKey,omitempty"`
// SharedInbox An optional endpoint used for wide delivery of publicly addressed activities and activities sent to followers.
// SharedInbox endpoints SHOULD also be publicly readable OrderedCollection objects containing objects addressed to the
// Public special collection. Reading from the sharedInbox endpoint MUST NOT present objects which are not addressed to the Public endpoint.
SharedInbox Item `jsonld:"sharedInbox,omitempty"`
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (e *Endpoints) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
e.OauthAuthorizationEndpoint = JSONGetItem(val, "oauthAuthorizationEndpoint")
e.OauthTokenEndpoint = JSONGetItem(val, "oauthTokenEndpoint")
e.UploadMedia = JSONGetItem(val, "uploadMedia")
e.ProvideClientKey = JSONGetItem(val, "provideClientKey")
e.SignClientKey = JSONGetItem(val, "signClientKey")
e.SharedInbox = JSONGetItem(val, "sharedInbox")
return nil
}
// MarshalJSON encodes the receiver object to a JSON document.
func (e Endpoints) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
notEmpty := false
JSONWrite(&b, '{')
if e.OauthAuthorizationEndpoint != nil {
notEmpty = JSONWriteItemProp(&b, "oauthAuthorizationEndpoint", e.OauthAuthorizationEndpoint) || notEmpty
}
if e.OauthTokenEndpoint != nil {
notEmpty = JSONWriteItemProp(&b, "oauthTokenEndpoint", e.OauthTokenEndpoint) || notEmpty
}
if e.ProvideClientKey != nil {
notEmpty = JSONWriteItemProp(&b, "provideClientKey", e.ProvideClientKey) || notEmpty
}
if e.SignClientKey != nil {
notEmpty = JSONWriteItemProp(&b, "signClientKey", e.SignClientKey) || notEmpty
}
if e.SharedInbox != nil {
notEmpty = JSONWriteItemProp(&b, "sharedInbox", e.SharedInbox) || notEmpty
}
if e.UploadMedia != nil {
notEmpty = JSONWriteItemProp(&b, "uploadMedia", e.UploadMedia) || notEmpty
}
if notEmpty {
JSONWrite(&b, '}')
return b, nil
}
return nil, nil
}
// ToActor
func ToActor(it Item) (*Actor, error) {
switch i := it.(type) {
case *Actor:
return i, nil
case Actor:
return &i, nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(Actor))
if reflect.TypeOf(it).ConvertibleTo(typ) {
if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Actor); ok {
return i, nil
}
}
}
return nil, ErrorInvalidType[Actor](it)
}
// Equals verifies if our receiver Object is equals with the "with" Object
func (a Actor) Equals(with Item) bool {
result := true
err := OnActor(with, func(w *Actor) error {
_ = OnObject(a, func(oa *Object) error {
result = oa.Equals(w)
return nil
})
if w.Inbox != nil {
if !ItemsEqual(a.Inbox, w.Inbox) {
result = false
return nil
}
}
if w.Outbox != nil {
if !ItemsEqual(a.Outbox, w.Outbox) {
result = false
return nil
}
}
if w.Liked != nil {
if !ItemsEqual(a.Liked, w.Liked) {
result = false
return nil
}
}
if w.PreferredUsername != nil {
if !a.PreferredUsername.Equals(w.PreferredUsername) {
result = false
return nil
}
}
return nil
})
if err != nil {
result = false
}
return result
}
func (e Endpoints) GobEncode() ([]byte, error) {
return nil, nil
}
func (e *Endpoints) GobDecode(data []byte) error {
return nil
}
func (p PublicKey) GobEncode() ([]byte, error) {
var (
mm = make(map[string][]byte)
err error
hasData bool
)
if len(p.ID) > 0 {
if mm["id"], err = p.ID.GobEncode(); err != nil {
return nil, err
}
hasData = true
}
if len(p.PublicKeyPem) > 0 {
mm["publicKeyPem"] = []byte(p.PublicKeyPem)
hasData = true
}
if len(p.Owner) > 0 {
if mm["owner"], err = gobEncodeItem(p.Owner); err != nil {
return nil, err
}
hasData = true
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
func (p *PublicKey) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm, err := gobDecodeObjectAsMap(data)
if err != nil {
return err
}
if raw, ok := mm["id"]; ok {
if err = p.ID.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["owner"]; ok {
if err = p.Owner.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["publicKeyPem"]; ok {
p.PublicKeyPem = string(raw)
}
return nil
}

438
vendor/github.com/go-ap/activitypub/collection.go generated vendored Normal file
View File

@ -0,0 +1,438 @@
package activitypub
import (
"bytes"
"encoding/gob"
"fmt"
"reflect"
"time"
"unsafe"
"github.com/valyala/fastjson"
)
const CollectionOfIRIs ActivityVocabularyType = "IRICollection"
const CollectionOfItems ActivityVocabularyType = "ItemCollection"
var CollectionTypes = ActivityVocabularyTypes{
CollectionOfItems,
CollectionType,
OrderedCollectionType,
CollectionPageType,
OrderedCollectionPageType,
}
// Collections
//
// https://www.w3.org/TR/activitypub/#collections
//
// [ActivityStreams] defines the collection concept; ActivityPub defines several collections with special behavior.
//
// Note that ActivityPub makes use of ActivityStreams paging to traverse large sets of objects.
//
// Note that some of these collections are specified to be of type OrderedCollection specifically,
// while others are permitted to be either a Collection or an OrderedCollection.
// An OrderedCollection MUST be presented consistently in reverse chronological order.
//
// NOTE
// What property is used to determine the reverse chronological order is intentionally left as an implementation detail.
// For example, many SQL-style databases use an incrementing integer as an identifier, which can be reasonably used for
// handling insertion order in most cases. In other databases, an insertion time timestamp may be preferred.
// What is used isn't important, but the ordering of elements must remain intact, with newer items first.
// A property which changes regularly, such a "last updated" timestamp, should not be used.
type Collections interface {
Collection | CollectionPage | OrderedCollection | OrderedCollectionPage | ItemCollection | IRIs
}
type CollectionInterface interface {
ObjectOrLink
Collection() ItemCollection
Append(ob ...Item) error
Count() uint
Contains(Item) bool
}
// Collection is a subtype of Activity Pub Object that represents ordered or unordered sets of Activity Pub Object or Link instances.
type Collection struct {
// ID provides the globally unique identifier for anActivity Pub Object or Link.
ID ID `jsonld:"id,omitempty"`
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
Type ActivityVocabularyType `jsonld:"type,omitempty"`
// Name a simple, human-readable, plain-text name for the object.
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
// The intent is to provide a model that is at least semantically similar to attachments in email.
Attachment Item `jsonld:"attachment,omitempty"`
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
// For instance, an object might be attributed to the completion of another activity.
AttributedTo Item `jsonld:"attributedTo,omitempty"`
// Audience identifies one or more entities that represent the total population of entities
// for which the object can considered to be relevant.
Audience ItemCollection `jsonld:"audience,omitempty"`
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
// By default, the value of content is HTML.
// The mediaType property can be used in the object to indicate a different content type.
// (The content MAY be expressed using multiple language-tagged values.)
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
// Context identifies the context within which the object exists or an activity was performed.
// The notion of "context" used is intentionally vague.
// The intended function is to serve as a means of grouping objects and activities that share a
// common originating context or purpose. An example could be all activities relating to a common project or event.
Context Item `jsonld:"context,omitempty"`
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
// If not specified, the content property is assumed to contain text/html content.
MediaType MimeType `jsonld:"mediaType,omitempty"`
// EndTime the date and time describing the actual or expected ending time of the object.
// When used with an Activity object, for instance, the endTime property specifies the moment
// the activity concluded or is expected to conclude.
EndTime time.Time `jsonld:"endTime,omitempty"`
// Generator identifies the entity (e.g. an application) that generated the object.
Generator Item `jsonld:"generator,omitempty"`
// Icon indicates an entity that describes an icon for this object.
// The image should have an aspect ratio of one (horizontal) to one (vertical)
// and should be suitable for presentation at a small size.
Icon Item `jsonld:"icon,omitempty"`
// Image indicates an entity that describes an image for this object.
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
Image Item `jsonld:"image,omitempty"`
// InReplyTo indicates one or more entities for which this object is considered a response.
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
// Location indicates one or more physical or logical locations associated with the object.
Location Item `jsonld:"location,omitempty"`
// Preview identifies an entity that provides a preview of this object.
Preview Item `jsonld:"preview,omitempty"`
// Published the date and time at which the object was published
Published time.Time `jsonld:"published,omitempty"`
// Replies identifies a Collection containing objects considered to be responses to this object.
Replies Item `jsonld:"replies,omitempty"`
// StartTime the date and time describing the actual or expected starting time of the object.
// When used with an Activity object, for instance, the startTime property specifies
// the moment the activity began or is scheduled to begin.
StartTime time.Time `jsonld:"startTime,omitempty"`
// Summary a natural language summarization of the object encoded as HTML.
// *Multiple language tagged summaries may be provided.)
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
// The key difference between attachment and tag is that the former implies association by inclusion,
// while the latter implies associated by reference.
Tag ItemCollection `jsonld:"tag,omitempty"`
// Updated the date and time at which the object was updated
Updated time.Time `jsonld:"updated,omitempty"`
// URL identifies one or more links to representations of the object
URL Item `jsonld:"url,omitempty"`
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
To ItemCollection `jsonld:"to,omitempty"`
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
Bto ItemCollection `jsonld:"bto,omitempty"`
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
CC ItemCollection `jsonld:"cc,omitempty"`
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
BCC ItemCollection `jsonld:"bcc,omitempty"`
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
// the duration property indicates the object's approximate duration.
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
Duration time.Duration `jsonld:"duration,omitempty"`
// This is a list of all Like activities with this object as the object property, added as a side effect.
// The likes collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Likes Item `jsonld:"likes,omitempty"`
// This is a list of all Announce activities with this object as the object property, added as a side effect.
// The shares collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Shares Item `jsonld:"shares,omitempty"`
// Source property is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
// In general, clients do the conversion from source to content, not the other way around.
Source Source `jsonld:"source,omitempty"`
// In a paged Collection, indicates the page that contains the most recently updated member items.
Current ObjectOrLink `jsonld:"current,omitempty"`
// In a paged Collection, indicates the furthest preceding page of items in the collection.
First ObjectOrLink `jsonld:"first,omitempty"`
// In a paged Collection, indicates the furthest proceeding page of the collection.
Last ObjectOrLink `jsonld:"last,omitempty"`
// A non-negative integer specifying the total number of objects contained by the logical view of the collection.
// This number might not reflect the actual number of items serialized within the Collection object instance.
TotalItems uint `jsonld:"totalItems"`
// Identifies the items contained in a collection. The items might be ordered or unordered.
Items ItemCollection `jsonld:"items,omitempty"`
}
type (
// FollowersCollection is a collection of followers
FollowersCollection = Collection
// FollowingCollection is a list of everybody that the actor has followed, added as a side effect.
// The following collection MUST be either an OrderedCollection or a Collection and MAY
// be filtered on privileges of an authenticated user or as appropriate when no authentication is given.
FollowingCollection = Collection
)
// CollectionNew initializes a new Collection
func CollectionNew(id ID) *Collection {
c := Collection{ID: id, Type: CollectionType}
c.Name = NaturalLanguageValuesNew()
c.Content = NaturalLanguageValuesNew()
c.Summary = NaturalLanguageValuesNew()
return &c
}
// OrderedCollectionNew initializes a new OrderedCollection
func OrderedCollectionNew(id ID) *OrderedCollection {
o := OrderedCollection{ID: id, Type: OrderedCollectionType}
o.Name = NaturalLanguageValuesNew()
o.Content = NaturalLanguageValuesNew()
return &o
}
// GetID returns the ID corresponding to the Collection object
func (c Collection) GetID() ID {
return c.ID
}
// GetType returns the Collection's type
func (c Collection) GetType() ActivityVocabularyType {
return c.Type
}
// IsLink returns false for a Collection object
func (c Collection) IsLink() bool {
return false
}
// IsObject returns true for a Collection object
func (c Collection) IsObject() bool {
return true
}
// IsCollection returns true for Collection objects
func (c Collection) IsCollection() bool {
return true
}
// GetLink returns the IRI corresponding to the Collection object
func (c Collection) GetLink() IRI {
return IRI(c.ID)
}
// Collection returns the Collection's items
func (c Collection) Collection() ItemCollection {
return c.Items
}
// Append adds an element to a Collection
func (c *Collection) Append(it ...Item) error {
for _, ob := range it {
if c.Items.Contains(ob) {
continue
}
c.Items = append(c.Items, ob)
}
return nil
}
// Count returns the maximum between the length of Items in collection and its TotalItems property
func (c *Collection) Count() uint {
if c == nil {
return 0
}
return uint(len(c.Items))
}
// Contains verifies if Collection array contains the received one
func (c Collection) Contains(r Item) bool {
if len(c.Items) == 0 {
return false
}
for _, it := range c.Items {
if ItemsEqual(it, r) {
return true
}
}
return false
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (c *Collection) UnmarshalJSON(data []byte) error {
par := fastjson.Parser{}
val, err := par.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadCollection(val, c)
}
// MarshalJSON encodes the receiver object to a JSON document.
func (c Collection) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
notEmpty := false
JSONWrite(&b, '{')
OnObject(c, func(o *Object) error {
notEmpty = JSONWriteObjectValue(&b, *o)
return nil
})
if c.Current != nil {
notEmpty = JSONWriteItemProp(&b, "current", c.Current) || notEmpty
}
if c.First != nil {
notEmpty = JSONWriteItemProp(&b, "first", c.First) || notEmpty
}
if c.Last != nil {
notEmpty = JSONWriteItemProp(&b, "last", c.Last) || notEmpty
}
notEmpty = JSONWriteIntProp(&b, "totalItems", int64(c.TotalItems)) || notEmpty
if c.Items != nil {
notEmpty = JSONWriteItemCollectionProp(&b, "items", c.Items, false) || notEmpty
}
if notEmpty {
JSONWrite(&b, '}')
return b, nil
}
return nil, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (c *Collection) UnmarshalBinary(data []byte) error {
return c.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (c Collection) MarshalBinary() ([]byte, error) {
return c.GobEncode()
}
func (c Collection) GobEncode() ([]byte, error) {
mm := make(map[string][]byte)
hasData, err := mapCollectionProperties(mm, c)
if err != nil {
return nil, err
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
func (c *Collection) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm, err := gobDecodeObjectAsMap(data)
if err != nil {
return err
}
return unmapCollectionProperties(mm, c)
}
func (c Collection) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'v':
_, _ = fmt.Fprintf(s, "%T[%s] { totalItems: %d }", c, c.Type, c.TotalItems)
}
}
// ToCollection
func ToCollection(it Item) (*Collection, error) {
switch i := it.(type) {
case *Collection:
return i, nil
case Collection:
return &i, nil
case *CollectionPage:
return (*Collection)(unsafe.Pointer(i)), nil
case CollectionPage:
return (*Collection)(unsafe.Pointer(&i)), nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(Collection))
val := reflect.ValueOf(it)
if val.IsValid() && typ.Elem().Name() == val.Type().Elem().Name() {
conv := val.Convert(typ)
if i, ok := conv.Interface().(*Collection); ok {
return i, nil
}
}
}
return nil, ErrorInvalidType[Collection](it)
}
// ItemsMatch
func (c Collection) ItemsMatch(col ...Item) bool {
for _, it := range col {
if match := c.Items.Contains(it); !match {
return false
}
}
return true
}
// Equals
func (c Collection) Equals(with Item) bool {
if IsNil(with) {
return false
}
if !with.IsCollection() {
return false
}
result := true
_ = OnCollection(with, func(w *Collection) error {
_ = OnObject(w, func(wo *Object) error {
if !wo.Equals(c) {
result = false
return nil
}
return nil
})
if w.TotalItems > 0 {
if w.TotalItems != c.TotalItems {
result = false
return nil
}
}
if w.Current != nil {
if !ItemsEqual(c.Current, w.Current) {
result = false
return nil
}
}
if w.First != nil {
if !ItemsEqual(c.First, w.First) {
result = false
return nil
}
}
if w.Last != nil {
if !ItemsEqual(c.Last, w.Last) {
result = false
return nil
}
}
if w.Items != nil {
if !c.Items.Equals(w.Items) {
result = false
return nil
}
}
return nil
})
return result
}
func (c *Collection) Recipients() ItemCollection {
return ItemCollectionDeduplication(&c.To, &c.Bto, &c.CC, &c.BCC, &c.Audience)
}
func (c *Collection) Clean() {
_ = OnObject(c, func(o *Object) error {
o.Clean()
return nil
})
}

435
vendor/github.com/go-ap/activitypub/collection_page.go generated vendored Normal file
View File

@ -0,0 +1,435 @@
package activitypub
import (
"bytes"
"encoding/gob"
"fmt"
"reflect"
"time"
"github.com/valyala/fastjson"
)
// CollectionPage is a Collection that contains a large number of items and when it becomes impractical
// for an implementation to serialize every item contained by a Collection using the items
// property alone. In such cases, the items within a Collection can be divided into distinct subsets or "pages".
type CollectionPage struct {
// ID provides the globally unique identifier for anActivity Pub Object or Link.
ID ID `jsonld:"id,omitempty"`
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
Type ActivityVocabularyType `jsonld:"type,omitempty"`
// Name a simple, human-readable, plain-text name for the object.
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
// The intent is to provide a model that is at least semantically similar to attachments in email.
Attachment Item `jsonld:"attachment,omitempty"`
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
// For instance, an object might be attributed to the completion of another activity.
AttributedTo Item `jsonld:"attributedTo,omitempty"`
// Audience identifies one or more entities that represent the total population of entities
// for which the object can considered to be relevant.
Audience ItemCollection `jsonld:"audience,omitempty"`
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
// By default, the value of content is HTML.
// The mediaType property can be used in the object to indicate a different content type.
// (The content MAY be expressed using multiple language-tagged values.)
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
// Context identifies the context within which the object exists or an activity was performed.
// The notion of "context" used is intentionally vague.
// The intended function is to serve as a means of grouping objects and activities that share a
// common originating context or purpose. An example could be all activities relating to a common project or event.
Context Item `jsonld:"context,omitempty"`
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
// If not specified, the content property is assumed to contain text/html content.
MediaType MimeType `jsonld:"mediaType,omitempty"`
// EndTime the date and time describing the actual or expected ending time of the object.
// When used with an Activity object, for instance, the endTime property specifies the moment
// the activity concluded or is expected to conclude.
EndTime time.Time `jsonld:"endTime,omitempty"`
// Generator identifies the entity (e.g. an application) that generated the object.
Generator Item `jsonld:"generator,omitempty"`
// Icon indicates an entity that describes an icon for this object.
// The image should have an aspect ratio of one (horizontal) to one (vertical)
// and should be suitable for presentation at a small size.
Icon Item `jsonld:"icon,omitempty"`
// Image indicates an entity that describes an image for this object.
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
Image Item `jsonld:"image,omitempty"`
// InReplyTo indicates one or more entities for which this object is considered a response.
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
// Location indicates one or more physical or logical locations associated with the object.
Location Item `jsonld:"location,omitempty"`
// Preview identifies an entity that provides a preview of this object.
Preview Item `jsonld:"preview,omitempty"`
// Published the date and time at which the object was published
Published time.Time `jsonld:"published,omitempty"`
// Replies identifies a Collection containing objects considered to be responses to this object.
Replies Item `jsonld:"replies,omitempty"`
// StartTime the date and time describing the actual or expected starting time of the object.
// When used with an Activity object, for instance, the startTime property specifies
// the moment the activity began or is scheduled to begin.
StartTime time.Time `jsonld:"startTime,omitempty"`
// Summary a natural language summarization of the object encoded as HTML.
// *Multiple language tagged summaries may be provided.)
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
// The key difference between attachment and tag is that the former implies association by inclusion,
// while the latter implies associated by reference.
Tag ItemCollection `jsonld:"tag,omitempty"`
// Updated the date and time at which the object was updated
Updated time.Time `jsonld:"updated,omitempty"`
// URL identifies one or more links to representations of the object
URL Item `jsonld:"url,omitempty"`
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
To ItemCollection `jsonld:"to,omitempty"`
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
Bto ItemCollection `jsonld:"bto,omitempty"`
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
CC ItemCollection `jsonld:"cc,omitempty"`
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
BCC ItemCollection `jsonld:"bcc,omitempty"`
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
// the duration property indicates the object's approximate duration.
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
Duration time.Duration `jsonld:"duration,omitempty"`
// This is a list of all Like activities with this object as the object property, added as a side effect.
// The likes collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Likes Item `jsonld:"likes,omitempty"`
// This is a list of all Announce activities with this object as the object property, added as a side effect.
// The shares collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Shares Item `jsonld:"shares,omitempty"`
// Source property is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
// In general, clients do the conversion from source to content, not the other way around.
Source Source `jsonld:"source,omitempty"`
// In a paged Collection, indicates the page that contains the most recently updated member items.
Current ObjectOrLink `jsonld:"current,omitempty"`
// In a paged Collection, indicates the furthest preceding page of items in the collection.
First ObjectOrLink `jsonld:"first,omitempty"`
// In a paged Collection, indicates the furthest proceeding page of the collection.
Last ObjectOrLink `jsonld:"last,omitempty"`
// A non-negative integer specifying the total number of objects contained by the logical view of the collection.
// This number might not reflect the actual number of items serialized within the Collection object instance.
TotalItems uint `jsonld:"totalItems"`
// Identifies the items contained in a collection. The items might be unordered.
Items ItemCollection `jsonld:"items,omitempty"`
// Identifies the Collection to which a CollectionPage objects items belong.
PartOf Item `jsonld:"partOf,omitempty"`
// In a paged Collection, indicates the next page of items.
Next Item `jsonld:"next,omitempty"`
// In a paged Collection, identifies the previous page of items.
Prev Item `jsonld:"prev,omitempty"`
}
// GetID returns the ID corresponding to the CollectionPage object
func (c CollectionPage) GetID() ID {
return c.ID
}
// GetType returns the CollectionPage's type
func (c CollectionPage) GetType() ActivityVocabularyType {
return c.Type
}
// IsLink returns false for a CollectionPage object
func (c CollectionPage) IsLink() bool {
return false
}
// IsObject returns true for a CollectionPage object
func (c CollectionPage) IsObject() bool {
return true
}
// IsCollection returns true for CollectionPage objects
func (c CollectionPage) IsCollection() bool {
return true
}
// GetLink returns the IRI corresponding to the CollectionPage object
func (c CollectionPage) GetLink() IRI {
return IRI(c.ID)
}
// Collection returns the ColleCollectionPagection items
func (c CollectionPage) Collection() ItemCollection {
return c.Items
}
// Count returns the maximum between the length of Items in the collection page and its TotalItems property
func (c *CollectionPage) Count() uint {
if c == nil {
return 0
}
return uint(len(c.Items))
}
// Append adds an element to a CollectionPage
func (c *CollectionPage) Append(it ...Item) error {
for _, ob := range it {
if c.Items.Contains(ob) {
continue
}
c.Items = append(c.Items, ob)
}
return nil
}
// Contains verifies if CollectionPage array contains the received one
func (c CollectionPage) Contains(r Item) bool {
if len(c.Items) == 0 {
return false
}
for _, it := range c.Items {
if ItemsEqual(it, r) {
return true
}
}
return false
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (c *CollectionPage) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadCollectionPage(val, c)
}
// MarshalJSON encodes the receiver object to a JSON document.
func (c CollectionPage) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
notEmpty := false
JSONWrite(&b, '{')
OnObject(c, func(o *Object) error {
notEmpty = JSONWriteObjectValue(&b, *o)
return nil
})
if c.PartOf != nil {
notEmpty = JSONWriteItemProp(&b, "partOf", c.PartOf) || notEmpty
}
if c.Current != nil {
notEmpty = JSONWriteItemProp(&b, "current", c.Current) || notEmpty
}
if c.First != nil {
notEmpty = JSONWriteItemProp(&b, "first", c.First) || notEmpty
}
if c.Last != nil {
notEmpty = JSONWriteItemProp(&b, "last", c.Last) || notEmpty
}
if c.Next != nil {
notEmpty = JSONWriteItemProp(&b, "next", c.Next) || notEmpty
}
if c.Prev != nil {
notEmpty = JSONWriteItemProp(&b, "prev", c.Prev) || notEmpty
}
notEmpty = JSONWriteIntProp(&b, "totalItems", int64(c.TotalItems)) || notEmpty
if c.Items != nil {
notEmpty = JSONWriteItemCollectionProp(&b, "items", c.Items, false) || notEmpty
}
if notEmpty {
JSONWrite(&b, '}')
return b, nil
}
return nil, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (c *CollectionPage) UnmarshalBinary(data []byte) error {
return c.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (c CollectionPage) MarshalBinary() ([]byte, error) {
return c.GobEncode()
}
func (c CollectionPage) GobEncode() ([]byte, error) {
mm := make(map[string][]byte)
hasData, err := mapCollectionPageProperties(mm, c)
if err != nil {
return nil, err
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
func (c *CollectionPage) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm, err := gobDecodeObjectAsMap(data)
if err != nil {
return err
}
return unmapCollectionPageProperties(mm, c)
}
// CollectionNew initializes a new CollectionPage
func CollectionPageNew(parent CollectionInterface) *CollectionPage {
p := CollectionPage{
PartOf: parent.GetLink(),
}
if pc, ok := parent.(*Collection); ok {
copyCollectionToPage(pc, &p)
}
p.Type = CollectionPageType
return &p
}
func copyCollectionToPage(c *Collection, p *CollectionPage) error {
p.Type = CollectionPageType
p.Name = c.Name
p.Content = c.Content
p.Summary = c.Summary
p.Context = c.Context
p.URL = c.URL
p.MediaType = c.MediaType
p.Generator = c.Generator
p.AttributedTo = c.AttributedTo
p.Attachment = c.Attachment
p.Location = c.Location
p.Published = c.Published
p.StartTime = c.StartTime
p.EndTime = c.EndTime
p.Duration = c.Duration
p.Icon = c.Icon
p.Preview = c.Preview
p.Image = c.Image
p.Updated = c.Updated
p.InReplyTo = c.InReplyTo
p.To = c.To
p.Audience = c.Audience
p.Bto = c.Bto
p.CC = c.CC
p.BCC = c.BCC
p.Replies = c.Replies
p.Tag = c.Tag
p.TotalItems = c.TotalItems
p.Items = c.Items
p.Current = c.Current
p.First = c.First
p.PartOf = c.GetLink()
return nil
}
// ToCollectionPage
func ToCollectionPage(it Item) (*CollectionPage, error) {
switch i := it.(type) {
case *CollectionPage:
return i, nil
case CollectionPage:
return &i, nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(CollectionPage))
val := reflect.ValueOf(it)
if val.IsValid() && typ.Elem().Name() == val.Type().Elem().Name() {
conv := val.Convert(typ)
if i, ok := conv.Interface().(*CollectionPage); ok {
return i, nil
}
}
}
return nil, ErrorInvalidType[CollectionPage](it)
}
// ItemsMatch
func (c CollectionPage) ItemsMatch(col ...Item) bool {
for _, it := range col {
if match := c.Items.Contains(it); !match {
return false
}
}
return true
}
// Equals
func (c CollectionPage) Equals(with Item) bool {
if IsNil(with) {
return false
}
if !with.IsCollection() {
return false
}
result := true
OnCollectionPage(with, func(w *CollectionPage) error {
OnCollection(w, func(wo *Collection) error {
if !wo.Equals(c) {
result = false
return nil
}
return nil
})
if w.PartOf != nil {
if !ItemsEqual(c.PartOf, w.PartOf) {
result = false
return nil
}
}
if w.Current != nil {
if !ItemsEqual(c.Current, w.Current) {
result = false
return nil
}
}
if w.First != nil {
if !ItemsEqual(c.First, w.First) {
result = false
return nil
}
}
if w.Last != nil {
if !ItemsEqual(c.Last, w.Last) {
result = false
return nil
}
}
if w.Next != nil {
if !ItemsEqual(c.Next, w.Next) {
result = false
return nil
}
}
if w.Prev != nil {
if !ItemsEqual(c.Prev, w.Prev) {
result = false
return nil
}
}
return nil
})
return result
}
func (c CollectionPage) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'v':
_, _ = fmt.Fprintf(s, "%T[%s] { totalItems: %d }", c, c.Type, c.TotalItems)
}
}
func (c *CollectionPage) Recipients() ItemCollection {
return ItemCollectionDeduplication(&c.To, &c.Bto, &c.CC, &c.BCC, &c.Audience)
}
func (c *CollectionPage) Clean() {
_ = OnObject(c, func(o *Object) error {
o.Clean()
return nil
})
}

234
vendor/github.com/go-ap/activitypub/copy.go generated vendored Normal file
View File

@ -0,0 +1,234 @@
package activitypub
import (
"fmt"
)
func CopyOrderedCollectionPageProperties(to, from *OrderedCollectionPage) (*OrderedCollectionPage, error) {
to.PartOf = replaceIfItem(to.PartOf, from.PartOf)
to.Next = replaceIfItem(to.Next, from.Next)
to.Prev = replaceIfItem(to.Prev, from.Prev)
oldCol, _ := ToOrderedCollection(to)
newCol, _ := ToOrderedCollection(from)
_, err := CopyOrderedCollectionProperties(oldCol, newCol)
if err != nil {
return to, err
}
return to, nil
}
func CopyCollectionPageProperties(to, from *CollectionPage) (*CollectionPage, error) {
to.PartOf = replaceIfItem(to.PartOf, from.PartOf)
to.Next = replaceIfItem(to.Next, from.Next)
to.Prev = replaceIfItem(to.Prev, from.Prev)
toCol, _ := ToCollection(to)
fromCol, _ := ToCollection(from)
_, err := CopyCollectionProperties(toCol, fromCol)
return to, err
}
func CopyOrderedCollectionProperties(to, from *OrderedCollection) (*OrderedCollection, error) {
to.First = replaceIfItem(to.First, from.First)
to.Last = replaceIfItem(to.Last, from.Last)
to.OrderedItems = replaceIfItemCollection(to.OrderedItems, from.OrderedItems)
if to.TotalItems == 0 {
to.TotalItems = from.TotalItems
}
oldOb, _ := ToObject(to)
newOb, _ := ToObject(from)
_, err := CopyObjectProperties(oldOb, newOb)
return to, err
}
func CopyCollectionProperties(to, from *Collection) (*Collection, error) {
to.First = replaceIfItem(to.First, from.First)
to.Last = replaceIfItem(to.Last, from.Last)
to.Items = replaceIfItemCollection(to.Items, from.Items)
if to.TotalItems == 0 {
to.TotalItems = from.TotalItems
}
oldOb, _ := ToObject(to)
newOb, _ := ToObject(from)
_, err := CopyObjectProperties(oldOb, newOb)
return to, err
}
// CopyObjectProperties updates the "old" object properties with the "new's"
// Including ID and Type
func CopyObjectProperties(to, from *Object) (*Object, error) {
to.ID = from.ID
to.Type = from.Type
to.Name = replaceIfNaturalLanguageValues(to.Name, from.Name)
to.Attachment = replaceIfItem(to.Attachment, from.Attachment)
to.AttributedTo = replaceIfItem(to.AttributedTo, from.AttributedTo)
to.Audience = replaceIfItemCollection(to.Audience, from.Audience)
to.Content = replaceIfNaturalLanguageValues(to.Content, from.Content)
to.Context = replaceIfItem(to.Context, from.Context)
if len(from.MediaType) > 0 {
to.MediaType = from.MediaType
}
if !from.EndTime.IsZero() {
to.EndTime = from.EndTime
}
to.Generator = replaceIfItem(to.Generator, from.Generator)
to.Icon = replaceIfItem(to.Icon, from.Icon)
to.Image = replaceIfItem(to.Image, from.Image)
to.InReplyTo = replaceIfItem(to.InReplyTo, from.InReplyTo)
to.Location = replaceIfItem(to.Location, from.Location)
to.Preview = replaceIfItem(to.Preview, from.Preview)
if to.Published.IsZero() && !from.Published.IsZero() {
to.Published = from.Published
}
if to.Updated.IsZero() && !from.Updated.IsZero() {
to.Updated = from.Updated
}
to.Replies = replaceIfItem(to.Replies, from.Replies)
if !from.StartTime.IsZero() {
to.StartTime = from.StartTime
}
to.Summary = replaceIfNaturalLanguageValues(to.Summary, from.Summary)
to.Tag = replaceIfItemCollection(to.Tag, from.Tag)
if from.URL != nil {
to.URL = from.URL
}
to.To = replaceIfItemCollection(to.To, from.To)
to.Bto = replaceIfItemCollection(to.Bto, from.Bto)
to.CC = replaceIfItemCollection(to.CC, from.CC)
to.BCC = replaceIfItemCollection(to.BCC, from.BCC)
if from.Duration == 0 {
to.Duration = from.Duration
}
to.Source = replaceIfSource(to.Source, from.Source)
return to, nil
}
func copyAllItemProperties(to, from Item) (Item, error) {
if CollectionType == to.GetType() {
o, err := ToCollection(to)
if err != nil {
return o, err
}
n, err := ToCollection(from)
if err != nil {
return o, err
}
return CopyCollectionProperties(o, n)
}
if CollectionPageType == to.GetType() {
o, err := ToCollectionPage(to)
if err != nil {
return o, err
}
n, err := ToCollectionPage(from)
if err != nil {
return o, err
}
return CopyCollectionPageProperties(o, n)
}
if OrderedCollectionType == to.GetType() {
o, err := ToOrderedCollection(to)
if err != nil {
return o, err
}
n, err := ToOrderedCollection(from)
if err != nil {
return o, err
}
return CopyOrderedCollectionProperties(o, n)
}
if OrderedCollectionPageType == to.GetType() {
o, err := ToOrderedCollectionPage(to)
if err != nil {
return o, err
}
n, err := ToOrderedCollectionPage(from)
if err != nil {
return o, err
}
return CopyOrderedCollectionPageProperties(o, n)
}
if ActorTypes.Contains(to.GetType()) {
o, err := ToActor(to)
if err != nil {
return o, err
}
n, err := ToActor(from)
if err != nil {
return o, err
}
return UpdatePersonProperties(o, n)
}
if ObjectTypes.Contains(to.GetType()) || to.GetType() == "" {
o, err := ToObject(to)
if err != nil {
return o, err
}
n, err := ToObject(from)
if err != nil {
return o, err
}
return CopyObjectProperties(o, n)
}
return to, fmt.Errorf("could not process objects with type %s", to.GetType())
}
// CopyItemProperties delegates to the correct per type functions for copying
// properties between matching Activity Objects
func CopyItemProperties(to, from Item) (Item, error) {
if to == nil {
return to, fmt.Errorf("nil object to update")
}
if from == nil {
return to, fmt.Errorf("nil object for update")
}
if !to.GetLink().Equals(from.GetLink(), false) {
return to, fmt.Errorf("object IDs don't match")
}
if to.GetType() != "" && to.GetType() != from.GetType() {
return to, fmt.Errorf("invalid object types for update %s(old) and %s(new)", from.GetType(), to.GetType())
}
return copyAllItemProperties(to, from)
}
// UpdatePersonProperties
func UpdatePersonProperties(to, from *Actor) (*Actor, error) {
to.Inbox = replaceIfItem(to.Inbox, from.Inbox)
to.Outbox = replaceIfItem(to.Outbox, from.Outbox)
to.Following = replaceIfItem(to.Following, from.Following)
to.Followers = replaceIfItem(to.Followers, from.Followers)
to.Liked = replaceIfItem(to.Liked, from.Liked)
to.PreferredUsername = replaceIfNaturalLanguageValues(to.PreferredUsername, from.PreferredUsername)
oldOb, _ := ToObject(to)
newOb, _ := ToObject(from)
_, err := CopyObjectProperties(oldOb, newOb)
return to, err
}
func replaceIfItem(old, new Item) Item {
if new == nil {
return old
}
return new
}
func replaceIfItemCollection(old, new ItemCollection) ItemCollection {
if new == nil {
return old
}
return new
}
func replaceIfNaturalLanguageValues(old, new NaturalLanguageValues) NaturalLanguageValues {
if new == nil {
return old
}
return new
}
func replaceIfSource(to, from Source) Source {
if from.MediaType != to.MediaType {
return from
}
to.Content = replaceIfNaturalLanguageValues(to.Content, from.Content)
return to
}

722
vendor/github.com/go-ap/activitypub/decoding_gob.go generated vendored Normal file
View File

@ -0,0 +1,722 @@
package activitypub
import (
"bytes"
"encoding/gob"
"errors"
"time"
)
func GobDecode(data []byte) (Item, error) {
return gobDecodeItem(data)
}
func gobDecodeUint(i *uint, data []byte) error {
g := gob.NewDecoder(bytes.NewReader(data))
return g.Decode(i)
}
func gobDecodeFloat64(f *float64, data []byte) error {
g := gob.NewDecoder(bytes.NewReader(data))
return g.Decode(f)
}
func gobDecodeInt64(i *int64, data []byte) error {
g := gob.NewDecoder(bytes.NewReader(data))
return g.Decode(i)
}
func gobDecodeBool(b *bool, data []byte) error {
g := gob.NewDecoder(bytes.NewReader(data))
return g.Decode(b)
}
func unmapActorProperties(mm map[string][]byte, a *Actor) error {
err := OnObject(a, func(ob *Object) error {
return unmapObjectProperties(mm, ob)
})
if err != nil {
return err
}
if raw, ok := mm["inbox"]; ok {
if a.Inbox, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["outbox"]; ok {
if a.Outbox, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["following"]; ok {
if a.Following, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["followers"]; ok {
if a.Followers, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["liked"]; ok {
if a.Liked, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["preferredUsername"]; ok {
if a.PreferredUsername, err = gobDecodeNaturalLanguageValues(raw); err != nil {
return err
}
}
if raw, ok := mm["endpoints"]; ok {
if err = a.Endpoints.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["streams"]; ok {
if a.Streams, err = gobDecodeItems(raw); err != nil {
return err
}
}
if raw, ok := mm["publicKey"]; ok {
if err = a.PublicKey.GobDecode(raw); err != nil {
return err
}
}
return nil
}
func unmapIntransitiveActivityProperties(mm map[string][]byte, act *IntransitiveActivity) error {
err := OnObject(act, func(ob *Object) error {
return unmapObjectProperties(mm, ob)
})
if err != nil {
return err
}
if raw, ok := mm["actor"]; ok {
if act.Actor, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["target"]; ok {
if act.Target, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["result"]; ok {
if act.Result, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["origin"]; ok {
if act.Origin, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["instrument"]; ok {
if act.Instrument, err = gobDecodeItem(raw); err != nil {
return err
}
}
return nil
}
func unmapActivityProperties(mm map[string][]byte, act *Activity) error {
err := OnIntransitiveActivity(act, func(act *IntransitiveActivity) error {
return unmapIntransitiveActivityProperties(mm, act)
})
if err != nil {
return err
}
if raw, ok := mm["object"]; ok {
if act.Object, err = gobDecodeItem(raw); err != nil {
return err
}
}
return nil
}
func unmapLinkProperties(mm map[string][]byte, l *Link) error {
if raw, ok := mm["id"]; ok {
if err := l.ID.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["type"]; ok {
if err := l.Type.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["mediaType"]; ok {
if err := l.MediaType.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["href"]; ok {
if err := l.Href.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["hrefLang"]; ok {
if err := l.HrefLang.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["name"]; ok {
if err := l.Name.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["rel"]; ok {
if err := l.Rel.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["width"]; ok {
if err := gobDecodeUint(&l.Width, raw); err != nil {
return err
}
}
if raw, ok := mm["height"]; ok {
if err := gobDecodeUint(&l.Height, raw); err != nil {
return err
}
}
return nil
}
func unmapObjectProperties(mm map[string][]byte, o *Object) error {
var err error
if raw, ok := mm["id"]; ok {
if err = o.ID.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["type"]; ok {
if err = o.Type.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["name"]; ok {
if err = o.Name.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["attachment"]; ok {
if o.Attachment, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["attributedTo"]; ok {
if o.AttributedTo, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["audience"]; ok {
if o.Audience, err = gobDecodeItems(raw); err != nil {
return err
}
}
if raw, ok := mm["content"]; ok {
if o.Content, err = gobDecodeNaturalLanguageValues(raw); err != nil {
return err
}
}
if raw, ok := mm["context"]; ok {
if o.Context, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["mediaType"]; ok {
if err = o.MediaType.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["endTime"]; ok {
if err = o.EndTime.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["generator"]; ok {
if o.Generator, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["icon"]; ok {
if o.Icon, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["image"]; ok {
if o.Image, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["inReplyTo"]; ok {
if o.InReplyTo, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["location"]; ok {
if o.Location, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["preview"]; ok {
if o.Preview, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["published"]; ok {
if err = o.Published.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["replies"]; ok {
if o.Replies, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["startTime"]; ok {
if err = o.StartTime.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["summary"]; ok {
if o.Summary, err = gobDecodeNaturalLanguageValues(raw); err != nil {
return err
}
}
if raw, ok := mm["tag"]; ok {
if o.Tag, err = gobDecodeItems(raw); err != nil {
return err
}
}
if raw, ok := mm["updated"]; ok {
if err = o.Updated.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["url"]; ok {
if o.URL, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["to"]; ok {
if o.To, err = gobDecodeItems(raw); err != nil {
return err
}
}
if raw, ok := mm["bto"]; ok {
if o.Bto, err = gobDecodeItems(raw); err != nil {
return err
}
}
if raw, ok := mm["cc"]; ok {
if o.CC, err = gobDecodeItems(raw); err != nil {
return err
}
}
if raw, ok := mm["bcc"]; ok {
if o.BCC, err = gobDecodeItems(raw); err != nil {
return err
}
}
if raw, ok := mm["duration"]; ok {
if o.Duration, err = gobDecodeDuration(raw); err != nil {
return err
}
}
if raw, ok := mm["likes"]; ok {
if o.Likes, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["shares"]; ok {
if o.Shares, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["source"]; ok {
if err := o.Source.GobDecode(raw); err != nil {
return err
}
}
return nil
}
func tryDecodeItems(items *ItemCollection, data []byte) error {
tt := make([][]byte, 0)
g := gob.NewDecoder(bytes.NewReader(data))
if err := g.Decode(&tt); err != nil {
return err
}
for _, it := range tt {
ob, err := gobDecodeItem(it)
if err != nil {
return err
}
*items = append(*items, ob)
}
return nil
}
func tryDecodeIRIs(iris *IRIs, data []byte) error {
return iris.GobDecode(data)
}
func tryDecodeIRI(iri *IRI, data []byte) error {
return iri.GobDecode(data)
}
func gobDecodeDuration(data []byte) (time.Duration, error) {
var d time.Duration
err := gob.NewDecoder(bytes.NewReader(data)).Decode(&d)
return d, err
}
func gobDecodeNaturalLanguageValues(data []byte) (NaturalLanguageValues, error) {
n := make(NaturalLanguageValues, 0)
err := n.GobDecode(data)
return n, err
}
func gobDecodeItems(data []byte) (ItemCollection, error) {
items := make(ItemCollection, 0)
if err := tryDecodeItems(&items, data); err != nil {
return nil, err
}
return items, nil
}
func gobDecodeItem(data []byte) (Item, error) {
items := make(ItemCollection, 0)
if err := tryDecodeItems(&items, data); err == nil {
return items, nil
}
iris := make(IRIs, 0)
if err := tryDecodeIRIs(&iris, data); err == nil {
return iris, nil
}
isObject := false
typ := ActivityVocabularyType("")
mm, err := gobDecodeObjectAsMap(data)
if err == nil {
var sTyp []byte
sTyp, isObject = mm["type"]
if isObject {
typ = ActivityVocabularyType(sTyp)
} else {
_, isObject = mm["id"]
}
}
if isObject {
it, err := ItemTyperFunc(typ)
if err != nil {
return nil, err
}
switch it.GetType() {
case IRIType:
case "", ObjectType, ArticleType, AudioType, DocumentType, EventType, ImageType, NoteType, PageType, VideoType:
err = OnObject(it, func(ob *Object) error {
return unmapObjectProperties(mm, ob)
})
case LinkType, MentionType:
err = OnLink(it, func(l *Link) error {
return unmapLinkProperties(mm, l)
})
case ActivityType, AcceptType, AddType, AnnounceType, BlockType, CreateType, DeleteType, DislikeType,
FlagType, FollowType, IgnoreType, InviteType, JoinType, LeaveType, LikeType, ListenType, MoveType, OfferType,
RejectType, ReadType, RemoveType, TentativeRejectType, TentativeAcceptType, UndoType, UpdateType, ViewType:
err = OnActivity(it, func(act *Activity) error {
return unmapActivityProperties(mm, act)
})
case IntransitiveActivityType, ArriveType, TravelType:
err = OnIntransitiveActivity(it, func(act *IntransitiveActivity) error {
return unmapIntransitiveActivityProperties(mm, act)
})
case ActorType, ApplicationType, GroupType, OrganizationType, PersonType, ServiceType:
err = OnActor(it, func(a *Actor) error {
return unmapActorProperties(mm, a)
})
case CollectionType:
err = OnCollection(it, func(c *Collection) error {
return unmapCollectionProperties(mm, c)
})
case OrderedCollectionType:
err = OnOrderedCollection(it, func(c *OrderedCollection) error {
return unmapOrderedCollectionProperties(mm, c)
})
case CollectionPageType:
err = OnCollectionPage(it, func(p *CollectionPage) error {
return unmapCollectionPageProperties(mm, p)
})
case OrderedCollectionPageType:
err = OnOrderedCollectionPage(it, func(p *OrderedCollectionPage) error {
return unmapOrderedCollectionPageProperties(mm, p)
})
case PlaceType:
err = OnPlace(it, func(p *Place) error {
return unmapPlaceProperties(mm, p)
})
case ProfileType:
err = OnProfile(it, func(p *Profile) error {
return unmapProfileProperties(mm, p)
})
case RelationshipType:
err = OnRelationship(it, func(r *Relationship) error {
return unmapRelationshipProperties(mm, r)
})
case TombstoneType:
err = OnTombstone(it, func(t *Tombstone) error {
return unmapTombstoneProperties(mm, t)
})
case QuestionType:
err = OnQuestion(it, func(q *Question) error {
return unmapQuestionProperties(mm, q)
})
}
return it, err
}
iri := IRI("")
if err := tryDecodeIRI(&iri, data); err == nil {
return iri, err
}
return nil, errors.New("unable to gob decode to any known ActivityPub types")
}
func gobDecodeObjectAsMap(data []byte) (map[string][]byte, error) {
mm := make(map[string][]byte)
g := gob.NewDecoder(bytes.NewReader(data))
if err := g.Decode(&mm); err != nil {
return nil, err
}
return mm, nil
}
func unmapIncompleteCollectionProperties(mm map[string][]byte, c *Collection) error {
err := OnObject(c, func(ob *Object) error {
return unmapObjectProperties(mm, ob)
})
if err != nil {
return err
}
if raw, ok := mm["current"]; ok {
if c.Current, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["first"]; ok {
if c.First, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["last"]; ok {
if c.Last, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["totalItems"]; ok {
if err = gobDecodeUint(&c.TotalItems, raw); err != nil {
return err
}
}
return nil
}
func unmapCollectionProperties(mm map[string][]byte, c *Collection) error {
err := unmapIncompleteCollectionProperties(mm, c)
if err != nil {
return err
}
if raw, ok := mm["items"]; ok {
if c.Items, err = gobDecodeItems(raw); err != nil {
return err
}
}
return err
}
func unmapCollectionPageProperties(mm map[string][]byte, c *CollectionPage) error {
err := OnCollection(c, func(c *Collection) error {
return unmapCollectionProperties(mm, c)
})
if err != nil {
return err
}
if raw, ok := mm["partOf"]; ok {
if c.PartOf, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["next"]; ok {
if c.Next, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["prev"]; ok {
if c.Prev, err = gobDecodeItem(raw); err != nil {
return err
}
}
return err
}
func unmapOrderedCollectionProperties(mm map[string][]byte, o *OrderedCollection) error {
err := OnCollection(o, func(c *Collection) error {
return unmapIncompleteCollectionProperties(mm, c)
})
if err != nil {
return err
}
if raw, ok := mm["orderedItems"]; ok {
if o.OrderedItems, err = gobDecodeItems(raw); err != nil {
return err
}
}
return err
}
func unmapOrderedCollectionPageProperties(mm map[string][]byte, c *OrderedCollectionPage) error {
err := OnOrderedCollection(c, func(c *OrderedCollection) error {
return unmapOrderedCollectionProperties(mm, c)
})
if err != nil {
return err
}
if raw, ok := mm["partOf"]; ok {
if c.PartOf, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["next"]; ok {
if c.Next, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["prev"]; ok {
if c.Prev, err = gobDecodeItem(raw); err != nil {
return err
}
}
return err
}
func unmapPlaceProperties(mm map[string][]byte, p *Place) error {
err := OnObject(p, func(ob *Object) error {
return unmapObjectProperties(mm, ob)
})
if err != nil {
return err
}
if raw, ok := mm["accuracy"]; ok {
if err = gobDecodeFloat64(&p.Accuracy, raw); err != nil {
return err
}
}
if raw, ok := mm["altitude"]; ok {
if err = gobDecodeFloat64(&p.Altitude, raw); err != nil {
return err
}
}
if raw, ok := mm["latitude"]; ok {
if err = gobDecodeFloat64(&p.Latitude, raw); err != nil {
return err
}
}
if raw, ok := mm["radius"]; ok {
if err = gobDecodeInt64(&p.Radius, raw); err != nil {
return err
}
}
if raw, ok := mm["units"]; ok {
p.Units = string(raw)
}
return nil
}
func unmapProfileProperties(mm map[string][]byte, p *Profile) error {
err := OnObject(p, func(ob *Object) error {
return unmapObjectProperties(mm, ob)
})
if err != nil {
return err
}
if raw, ok := mm["Describes"]; ok {
if p.Describes, err = gobDecodeItem(raw); err != nil {
return err
}
}
return nil
}
func unmapRelationshipProperties(mm map[string][]byte, r *Relationship) error {
err := OnObject(r, func(ob *Object) error {
return unmapObjectProperties(mm, ob)
})
if err != nil {
return err
}
if raw, ok := mm["subject"]; ok {
if r.Subject, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["object"]; ok {
if r.Object, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["relationship"]; ok {
if r.Relationship, err = gobDecodeItem(raw); err != nil {
return err
}
}
return nil
}
func unmapTombstoneProperties(mm map[string][]byte, t *Tombstone) error {
err := OnObject(t, func(ob *Object) error {
return unmapObjectProperties(mm, ob)
})
if err != nil {
return err
}
if raw, ok := mm["formerType"]; ok {
if err = t.FormerType.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["deleted"]; ok {
if err = t.Deleted.GobDecode(raw); err != nil {
return err
}
}
return nil
}
func unmapQuestionProperties(mm map[string][]byte, q *Question) error {
err := OnIntransitiveActivity(q, func(act *IntransitiveActivity) error {
return unmapIntransitiveActivityProperties(mm, act)
})
if err != nil {
return err
}
if raw, ok := mm["oneOf"]; ok {
if q.OneOf, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["anyOf"]; ok {
if q.AnyOf, err = gobDecodeItem(raw); err != nil {
return err
}
}
if raw, ok := mm["closed"]; ok {
if err = gobDecodeBool(&q.Closed, raw); err != nil {
return err
}
}
return nil
}

662
vendor/github.com/go-ap/activitypub/decoding_json.go generated vendored Normal file
View File

@ -0,0 +1,662 @@
package activitypub
import (
"encoding"
"encoding/json"
"fmt"
"net/url"
"reflect"
"strings"
"time"
"github.com/valyala/fastjson"
)
var (
apUnmarshalerType = reflect.TypeOf(new(Item)).Elem()
unmarshalerType = reflect.TypeOf(new(json.Unmarshaler)).Elem()
textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
)
// ItemTyperFunc will return an instance of a struct that implements activitypub.Item
// The default for this package is GetItemByType but can be overwritten
var ItemTyperFunc TyperFn = GetItemByType
// JSONItemUnmarshal can be set externally to populate a custom object based on its type
var JSONItemUnmarshal JSONUnmarshalerFn = nil
// IsNotEmpty checks if an object is empty
var IsNotEmpty NotEmptyCheckerFn = NotEmpty
// TyperFn is the type of the function which returns an Item struct instance
// for a specific ActivityVocabularyType
type TyperFn func(ActivityVocabularyType) (Item, error)
// JSONUnmarshalerFn is the type of the function that will load the data from a fastjson.Value into an Item
// that the current package doesn't know about.
type JSONUnmarshalerFn func(ActivityVocabularyType, *fastjson.Value, Item) error
// NotEmptyCheckerFn is the type of the function that checks if an object is empty
type NotEmptyCheckerFn func(Item) bool
func JSONGetID(val *fastjson.Value) ID {
i := val.Get("id").GetStringBytes()
return ID(i)
}
func JSONGetType(val *fastjson.Value) ActivityVocabularyType {
t := val.Get("type").GetStringBytes()
return ActivityVocabularyType(t)
}
func JSONGetMimeType(val *fastjson.Value, prop string) MimeType {
if !val.Exists(prop) {
return ""
}
t := val.GetStringBytes(prop)
return MimeType(t)
}
func JSONGetInt(val *fastjson.Value, prop string) int64 {
if !val.Exists(prop) {
return 0
}
i := val.Get(prop).GetInt64()
return i
}
func JSONGetFloat(val *fastjson.Value, prop string) float64 {
if !val.Exists(prop) {
return 0.0
}
f := val.Get(prop).GetFloat64()
return f
}
func JSONGetString(val *fastjson.Value, prop string) string {
if !val.Exists(prop) {
return ""
}
s := val.Get(prop).GetStringBytes()
return string(s)
}
func JSONGetBytes(val *fastjson.Value, prop string) []byte {
if !val.Exists(prop) {
return nil
}
s := val.Get(prop).GetStringBytes()
return s
}
func JSONGetBoolean(val *fastjson.Value, prop string) bool {
if !val.Exists(prop) {
return false
}
t, _ := val.Get(prop).Bool()
return t
}
func JSONGetNaturalLanguageField(val *fastjson.Value, prop string) NaturalLanguageValues {
n := NaturalLanguageValues{}
if val == nil {
return n
}
v := val.Get(prop)
if v == nil {
return nil
}
switch v.Type() {
case fastjson.TypeObject:
ob, _ := v.Object()
ob.Visit(func(key []byte, v *fastjson.Value) {
l := LangRefValue{}
l.Ref = LangRef(key)
if err := l.Value.UnmarshalJSON(v.GetStringBytes()); err == nil {
if l.Ref != NilLangRef || len(l.Value) > 0 {
n = append(n, l)
}
}
})
case fastjson.TypeString:
l := LangRefValue{}
if err := l.UnmarshalJSON(v.GetStringBytes()); err == nil {
n = append(n, l)
}
}
return n
}
func JSONGetTime(val *fastjson.Value, prop string) time.Time {
t := time.Time{}
if val == nil {
return t
}
if str := val.Get(prop).GetStringBytes(); len(str) > 0 {
t.UnmarshalText(str)
return t.UTC()
}
return t
}
func JSONGetDuration(val *fastjson.Value, prop string) time.Duration {
if str := val.Get(prop).GetStringBytes(); len(str) > 0 {
// TODO(marius): this needs to be replaced to be compatible with xsd:duration
d, _ := time.ParseDuration(string(str))
return d
}
return 0
}
func JSONGetPublicKey(val *fastjson.Value, prop string) PublicKey {
key := PublicKey{}
if val == nil {
return key
}
val = val.Get(prop)
if val == nil {
return key
}
JSONLoadPublicKey(val, &key)
return key
}
func JSONItemsFn(val *fastjson.Value) (Item, error) {
if val.Type() == fastjson.TypeArray {
it := val.GetArray()
items := make(ItemCollection, 0)
for _, v := range it {
if it, _ := JSONLoadItem(v); it != nil {
items.Append(it)
}
}
return items, nil
}
return JSONLoadItem(val)
}
func JSONLoadItem(val *fastjson.Value) (Item, error) {
typ := JSONGetType(val)
if typ == "" && val.Type() == fastjson.TypeString {
// try to see if it's an IRI
if i, ok := asIRI(val); ok {
return i, nil
}
}
i, err := ItemTyperFunc(typ)
if err != nil || IsNil(i) {
return nil, nil
}
var empty = func(i Item) bool { return !IsNotEmpty(i) }
switch typ {
case "":
// NOTE(marius): this handles Tags which usually don't have types
fallthrough
case ObjectType, ArticleType, AudioType, DocumentType, EventType, ImageType, NoteType, PageType, VideoType:
err = OnObject(i, func(ob *Object) error {
return JSONLoadObject(val, ob)
})
case LinkType, MentionType:
err = OnLink(i, func(l *Link) error {
return JSONLoadLink(val, l)
})
case ActivityType, AcceptType, AddType, AnnounceType, BlockType, CreateType, DeleteType, DislikeType,
FlagType, FollowType, IgnoreType, InviteType, JoinType, LeaveType, LikeType, ListenType, MoveType, OfferType,
RejectType, ReadType, RemoveType, TentativeRejectType, TentativeAcceptType, UndoType, UpdateType, ViewType:
err = OnActivity(i, func(act *Activity) error {
return JSONLoadActivity(val, act)
})
case IntransitiveActivityType, ArriveType, TravelType:
err = OnIntransitiveActivity(i, func(act *IntransitiveActivity) error {
return JSONLoadIntransitiveActivity(val, act)
})
case ActorType, ApplicationType, GroupType, OrganizationType, PersonType, ServiceType:
err = OnActor(i, func(a *Actor) error {
return JSONLoadActor(val, a)
})
case CollectionType:
err = OnCollection(i, func(c *Collection) error {
return JSONLoadCollection(val, c)
})
case OrderedCollectionType:
err = OnOrderedCollection(i, func(c *OrderedCollection) error {
return JSONLoadOrderedCollection(val, c)
})
case CollectionPageType:
err = OnCollectionPage(i, func(p *CollectionPage) error {
return JSONLoadCollectionPage(val, p)
})
case OrderedCollectionPageType:
err = OnOrderedCollectionPage(i, func(p *OrderedCollectionPage) error {
return JSONLoadOrderedCollectionPage(val, p)
})
case PlaceType:
err = OnPlace(i, func(p *Place) error {
return JSONLoadPlace(val, p)
})
case ProfileType:
err = OnProfile(i, func(p *Profile) error {
return JSONLoadProfile(val, p)
})
case RelationshipType:
err = OnRelationship(i, func(r *Relationship) error {
return JSONLoadRelationship(val, r)
})
case TombstoneType:
err = OnTombstone(i, func(t *Tombstone) error {
return JSONLoadTombstone(val, t)
})
case QuestionType:
err = OnQuestion(i, func(q *Question) error {
return JSONLoadQuestion(val, q)
})
default:
if JSONItemUnmarshal == nil {
return nil, fmt.Errorf("unable to unmarshal custom type %s, you need to set a correct function for JSONItemUnmarshal", typ)
}
err = JSONItemUnmarshal(typ, val, i)
}
if err != nil {
return nil, err
}
if empty(i) {
return nil, nil
}
return i, nil
}
func JSONUnmarshalToItem(val *fastjson.Value) Item {
var (
i Item
err error
)
switch val.Type() {
case fastjson.TypeArray:
i, err = JSONItemsFn(val)
case fastjson.TypeObject:
i, err = JSONLoadItem(val)
case fastjson.TypeString:
if iri, ok := asIRI(val); ok {
// try to see if it's an IRI
i = iri
}
}
if err != nil {
return nil
}
return i
}
func asIRI(val *fastjson.Value) (IRI, bool) {
if val == nil {
return NilIRI, true
}
s := strings.Trim(val.String(), `"`)
u, err := url.ParseRequestURI(s)
if err == nil && len(u.Scheme) > 0 && len(u.Host) > 0 {
// try to see if it's an IRI
return IRI(s), true
}
return EmptyIRI, false
}
func JSONGetItem(val *fastjson.Value, prop string) Item {
if val == nil {
return nil
}
if val = val.Get(prop); val == nil {
return nil
}
switch val.Type() {
case fastjson.TypeString:
if i, ok := asIRI(val); ok {
// try to see if it's an IRI
return i
}
case fastjson.TypeArray:
it, _ := JSONItemsFn(val)
return it
case fastjson.TypeObject:
it, _ := JSONLoadItem(val)
return it
case fastjson.TypeNumber:
fallthrough
case fastjson.TypeNull:
fallthrough
default:
return nil
}
return nil
}
func JSONGetURIItem(val *fastjson.Value, prop string) Item {
if val == nil {
return nil
}
if val = val.Get(prop); val == nil {
return nil
}
switch val.Type() {
case fastjson.TypeObject:
if it, _ := JSONLoadItem(val); it != nil {
return it
}
case fastjson.TypeArray:
if it, _ := JSONItemsFn(val); it != nil {
return it
}
case fastjson.TypeString:
return IRI(val.GetStringBytes())
}
return nil
}
func JSONGetItems(val *fastjson.Value, prop string) ItemCollection {
if val == nil {
return nil
}
if val = val.Get(prop); val == nil {
return nil
}
it := make(ItemCollection, 0)
switch val.Type() {
case fastjson.TypeArray:
for _, v := range val.GetArray() {
if i, _ := JSONLoadItem(v); i != nil {
it.Append(i)
}
}
case fastjson.TypeObject:
if i := JSONGetItem(val, prop); i != nil {
it.Append(i)
}
case fastjson.TypeString:
if iri := val.GetStringBytes(); len(iri) > 0 {
it.Append(IRI(iri))
}
}
if len(it) == 0 {
return nil
}
return it
}
func JSONGetLangRefField(val *fastjson.Value, prop string) LangRef {
s := val.Get(prop).GetStringBytes()
return LangRef(s)
}
func JSONGetIRI(val *fastjson.Value, prop string) IRI {
s := val.Get(prop).GetStringBytes()
return IRI(s)
}
// UnmarshalJSON tries to detect the type of the object in the json data and then outputs a matching
// ActivityStreams object, if possible
func UnmarshalJSON(data []byte) (Item, error) {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return nil, err
}
return JSONUnmarshalToItem(val), nil
}
func GetItemByType(typ ActivityVocabularyType) (Item, error) {
switch typ {
case ObjectType, ArticleType, AudioType, DocumentType, EventType, ImageType, NoteType, PageType, VideoType:
return ObjectNew(typ), nil
case LinkType, MentionType:
return &Link{Type: typ}, nil
case ActivityType, AcceptType, AddType, AnnounceType, BlockType, CreateType, DeleteType, DislikeType,
FlagType, FollowType, IgnoreType, InviteType, JoinType, LeaveType, LikeType, ListenType, MoveType, OfferType,
RejectType, ReadType, RemoveType, TentativeRejectType, TentativeAcceptType, UndoType, UpdateType, ViewType:
return &Activity{Type: typ}, nil
case IntransitiveActivityType, ArriveType, TravelType:
return &IntransitiveActivity{Type: typ}, nil
case ActorType, ApplicationType, GroupType, OrganizationType, PersonType, ServiceType:
return &Actor{Type: typ}, nil
case CollectionType:
return &Collection{Type: typ}, nil
case OrderedCollectionType:
return &OrderedCollection{Type: typ}, nil
case CollectionPageType:
return &CollectionPage{Type: typ}, nil
case OrderedCollectionPageType:
return &OrderedCollectionPage{Type: typ}, nil
case PlaceType:
return &Place{Type: typ}, nil
case ProfileType:
return &Profile{Type: typ}, nil
case RelationshipType:
return &Relationship{Type: typ}, nil
case TombstoneType:
return &Tombstone{Type: typ}, nil
case QuestionType:
return &Question{Type: typ}, nil
case "":
fallthrough
default:
// when no type is available use a plain Object
return &Object{}, nil
}
return nil, fmt.Errorf("empty ActivityStreams type")
}
func JSONGetActorEndpoints(val *fastjson.Value, prop string) *Endpoints {
if val == nil {
return nil
}
if val = val.Get(prop); val == nil {
return nil
}
e := Endpoints{}
e.UploadMedia = JSONGetURIItem(val, "uploadMedia")
e.OauthAuthorizationEndpoint = JSONGetURIItem(val, "oauthAuthorizationEndpoint")
e.OauthTokenEndpoint = JSONGetURIItem(val, "oauthTokenEndpoint")
e.SharedInbox = JSONGetURIItem(val, "sharedInbox")
e.ProvideClientKey = JSONGetURIItem(val, "provideClientKey")
e.SignClientKey = JSONGetURIItem(val, "signClientKey")
return &e
}
func JSONLoadObject(val *fastjson.Value, o *Object) error {
o.ID = JSONGetID(val)
o.Type = JSONGetType(val)
o.Name = JSONGetNaturalLanguageField(val, "name")
o.Content = JSONGetNaturalLanguageField(val, "content")
o.Summary = JSONGetNaturalLanguageField(val, "summary")
o.Context = JSONGetItem(val, "context")
o.URL = JSONGetURIItem(val, "url")
o.MediaType = JSONGetMimeType(val, "mediaType")
o.Generator = JSONGetItem(val, "generator")
o.AttributedTo = JSONGetItem(val, "attributedTo")
o.Attachment = JSONGetItem(val, "attachment")
o.Location = JSONGetItem(val, "location")
o.Published = JSONGetTime(val, "published")
o.StartTime = JSONGetTime(val, "startTime")
o.EndTime = JSONGetTime(val, "endTime")
o.Duration = JSONGetDuration(val, "duration")
o.Icon = JSONGetItem(val, "icon")
o.Preview = JSONGetItem(val, "preview")
o.Image = JSONGetItem(val, "image")
o.Updated = JSONGetTime(val, "updated")
o.InReplyTo = JSONGetItem(val, "inReplyTo")
o.To = JSONGetItems(val, "to")
o.Audience = JSONGetItems(val, "audience")
o.Bto = JSONGetItems(val, "bto")
o.CC = JSONGetItems(val, "cc")
o.BCC = JSONGetItems(val, "bcc")
o.Replies = JSONGetItem(val, "replies")
o.Tag = JSONGetItems(val, "tag")
o.Likes = JSONGetItem(val, "likes")
o.Shares = JSONGetItem(val, "shares")
o.Source = GetAPSource(val)
return nil
}
func JSONLoadIntransitiveActivity(val *fastjson.Value, i *IntransitiveActivity) error {
i.Actor = JSONGetItem(val, "actor")
i.Target = JSONGetItem(val, "target")
i.Result = JSONGetItem(val, "result")
i.Origin = JSONGetItem(val, "origin")
i.Instrument = JSONGetItem(val, "instrument")
return OnObject(i, func(o *Object) error {
return JSONLoadObject(val, o)
})
}
func JSONLoadActivity(val *fastjson.Value, a *Activity) error {
a.Object = JSONGetItem(val, "object")
return OnIntransitiveActivity(a, func(i *IntransitiveActivity) error {
return JSONLoadIntransitiveActivity(val, i)
})
}
func JSONLoadQuestion(val *fastjson.Value, q *Question) error {
q.OneOf = JSONGetItem(val, "oneOf")
q.AnyOf = JSONGetItem(val, "anyOf")
q.Closed = JSONGetBoolean(val, "closed")
return OnIntransitiveActivity(q, func(i *IntransitiveActivity) error {
return JSONLoadIntransitiveActivity(val, i)
})
}
func JSONLoadActor(val *fastjson.Value, a *Actor) error {
a.PreferredUsername = JSONGetNaturalLanguageField(val, "preferredUsername")
a.Followers = JSONGetItem(val, "followers")
a.Following = JSONGetItem(val, "following")
a.Inbox = JSONGetItem(val, "inbox")
a.Outbox = JSONGetItem(val, "outbox")
a.Liked = JSONGetItem(val, "liked")
a.Endpoints = JSONGetActorEndpoints(val, "endpoints")
a.Streams = JSONGetItems(val, "streams")
a.PublicKey = JSONGetPublicKey(val, "publicKey")
return OnObject(a, func(o *Object) error {
return JSONLoadObject(val, o)
})
}
func JSONLoadCollection(val *fastjson.Value, c *Collection) error {
c.Current = JSONGetItem(val, "current")
c.First = JSONGetItem(val, "first")
c.Last = JSONGetItem(val, "last")
c.TotalItems = uint(JSONGetInt(val, "totalItems"))
c.Items = JSONGetItems(val, "items")
return OnObject(c, func(o *Object) error {
return JSONLoadObject(val, o)
})
}
func JSONLoadCollectionPage(val *fastjson.Value, c *CollectionPage) error {
c.Next = JSONGetItem(val, "next")
c.Prev = JSONGetItem(val, "prev")
c.PartOf = JSONGetItem(val, "partOf")
return OnCollection(c, func(c *Collection) error {
return JSONLoadCollection(val, c)
})
}
func JSONLoadOrderedCollection(val *fastjson.Value, c *OrderedCollection) error {
c.Current = JSONGetItem(val, "current")
c.First = JSONGetItem(val, "first")
c.Last = JSONGetItem(val, "last")
c.TotalItems = uint(JSONGetInt(val, "totalItems"))
c.OrderedItems = JSONGetItems(val, "orderedItems")
return OnObject(c, func(o *Object) error {
return JSONLoadObject(val, o)
})
}
func JSONLoadOrderedCollectionPage(val *fastjson.Value, c *OrderedCollectionPage) error {
c.Next = JSONGetItem(val, "next")
c.Prev = JSONGetItem(val, "prev")
c.PartOf = JSONGetItem(val, "partOf")
c.StartIndex = uint(JSONGetInt(val, "startIndex"))
return OnOrderedCollection(c, func(c *OrderedCollection) error {
return JSONLoadOrderedCollection(val, c)
})
}
func JSONLoadPlace(val *fastjson.Value, p *Place) error {
p.Accuracy = JSONGetFloat(val, "accuracy")
p.Altitude = JSONGetFloat(val, "altitude")
p.Latitude = JSONGetFloat(val, "latitude")
p.Longitude = JSONGetFloat(val, "longitude")
p.Radius = JSONGetInt(val, "radius")
p.Units = JSONGetString(val, "units")
return OnObject(p, func(o *Object) error {
return JSONLoadObject(val, o)
})
}
func JSONLoadProfile(val *fastjson.Value, p *Profile) error {
p.Describes = JSONGetItem(val, "describes")
return OnObject(p, func(o *Object) error {
return JSONLoadObject(val, o)
})
}
func JSONLoadRelationship(val *fastjson.Value, r *Relationship) error {
r.Subject = JSONGetItem(val, "subject")
r.Object = JSONGetItem(val, "object")
r.Relationship = JSONGetItem(val, "relationship")
return OnObject(r, func(o *Object) error {
return JSONLoadObject(val, o)
})
}
func JSONLoadTombstone(val *fastjson.Value, t *Tombstone) error {
t.FormerType = ActivityVocabularyType(JSONGetString(val, "formerType"))
t.Deleted = JSONGetTime(val, "deleted")
return OnObject(t, func(o *Object) error {
return JSONLoadObject(val, o)
})
}
func JSONLoadLink(val *fastjson.Value, l *Link) error {
l.ID = JSONGetID(val)
l.Type = JSONGetType(val)
l.MediaType = JSONGetMimeType(val, "mediaType")
l.Preview = JSONGetItem(val, "preview")
if h := JSONGetInt(val, "height"); h != 0 {
l.Height = uint(h)
}
if w := JSONGetInt(val, "width"); w != 0 {
l.Width = uint(w)
}
l.Name = JSONGetNaturalLanguageField(val, "name")
if hrefLang := JSONGetLangRefField(val, "hrefLang"); len(hrefLang) > 0 {
l.HrefLang = hrefLang
}
if href := JSONGetURIItem(val, "href"); href != nil {
ll := href.GetLink()
if len(ll) > 0 {
l.Href = ll
}
}
if rel := JSONGetURIItem(val, "rel"); rel != nil {
rr := rel.GetLink()
if len(rr) > 0 {
l.Rel = rr
}
}
return nil
}
func JSONLoadPublicKey(val *fastjson.Value, p *PublicKey) error {
p.ID = JSONGetID(val)
p.Owner = JSONGetIRI(val, "owner")
if pub := val.GetStringBytes("publicKeyPem"); len(pub) > 0 {
p.PublicKeyPem = string(pub)
}
return nil
}

792
vendor/github.com/go-ap/activitypub/encoding_gob.go generated vendored Normal file
View File

@ -0,0 +1,792 @@
package activitypub
import (
"bytes"
"encoding/gob"
)
func GobEncode(it Item) ([]byte, error) {
return gobEncodeItem(it)
}
// TODO(marius): when migrating to go1.18, use a numeric constraint for this
func gobEncodeInt64(i int64) ([]byte, error) {
b := bytes.Buffer{}
gg := gob.NewEncoder(&b)
if err := gg.Encode(i); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// TODO(marius): when migrating to go1.18, use a numeric constraint for this
func gobEncodeUint(i uint) ([]byte, error) {
b := bytes.Buffer{}
gg := gob.NewEncoder(&b)
if err := gg.Encode(i); err != nil {
return nil, err
}
return b.Bytes(), nil
}
func gobEncodeFloat64(f float64) ([]byte, error) {
b := bytes.Buffer{}
gg := gob.NewEncoder(&b)
if err := gg.Encode(f); err != nil {
return nil, err
}
return b.Bytes(), nil
}
func gobEncodeBool(t bool) ([]byte, error) {
b := bytes.Buffer{}
gg := gob.NewEncoder(&b)
if err := gg.Encode(t); err != nil {
return nil, err
}
return b.Bytes(), nil
}
func gobEncodeStringLikeType(g *gob.Encoder, s []byte) error {
if err := g.Encode(s); err != nil {
return err
}
return nil
}
func gobEncodeItems(col ItemCollection) ([]byte, error) {
b := bytes.Buffer{}
tt := make([][]byte, 0)
for _, it := range col.Collection() {
single, err := gobEncodeItem(it)
if err != nil {
return nil, err
}
tt = append(tt, single)
}
err := gob.NewEncoder(&b).Encode(tt)
return b.Bytes(), err
}
func gobEncodeIRIs(col IRIs) ([]byte, error) {
b := bytes.Buffer{}
err := gob.NewEncoder(&b).Encode(col)
return b.Bytes(), err
}
func gobEncodeItemOrLink(it LinkOrIRI) ([]byte, error) {
if ob, ok := it.(Item); ok {
return gobEncodeItem(ob)
}
b := bytes.Buffer{}
err := OnLink(it, func(l *Link) error {
bytes, err := l.GobEncode()
b.Write(bytes)
return err
})
return b.Bytes(), err
}
func gobEncodeItem(it Item) ([]byte, error) {
if IsIRI(it) {
if i, ok := it.(IRI); ok {
return []byte(i), nil
}
return []byte{}, nil
}
b := bytes.Buffer{}
var err error
if IsIRIs(it) {
err = OnIRIs(it, func(iris *IRIs) error {
bytes, err := gobEncodeIRIs(*iris)
b.Write(bytes)
return err
})
}
if IsItemCollection(it) {
err = OnItemCollection(it, func(col *ItemCollection) error {
bytes, err := gobEncodeItems(*col)
b.Write(bytes)
return err
})
}
if IsObject(it) {
switch it.GetType() {
case IRIType:
var bytes []byte
bytes, err = it.(IRI).GobEncode()
b.Write(bytes)
case "", ObjectType, ArticleType, AudioType, DocumentType, EventType, ImageType, NoteType, PageType, VideoType:
err = OnObject(it, func(ob *Object) error {
bytes, err := ob.GobEncode()
b.Write(bytes)
return err
})
case LinkType, MentionType:
// TODO(marius): this shouldn't work, as Link does not implement Item? (or rather, should not)
err = OnLink(it, func(l *Link) error {
bytes, err := l.GobEncode()
b.Write(bytes)
return err
})
case ActivityType, AcceptType, AddType, AnnounceType, BlockType, CreateType, DeleteType, DislikeType,
FlagType, FollowType, IgnoreType, InviteType, JoinType, LeaveType, LikeType, ListenType, MoveType, OfferType,
RejectType, ReadType, RemoveType, TentativeRejectType, TentativeAcceptType, UndoType, UpdateType, ViewType:
err = OnActivity(it, func(act *Activity) error {
bytes, err := act.GobEncode()
b.Write(bytes)
return err
})
case IntransitiveActivityType, ArriveType, TravelType:
err = OnIntransitiveActivity(it, func(act *IntransitiveActivity) error {
bytes, err := act.GobEncode()
b.Write(bytes)
return err
})
case ActorType, ApplicationType, GroupType, OrganizationType, PersonType, ServiceType:
err = OnActor(it, func(a *Actor) error {
bytes, err := a.GobEncode()
b.Write(bytes)
return err
})
case CollectionType:
err = OnCollection(it, func(c *Collection) error {
bytes, err := c.GobEncode()
b.Write(bytes)
return err
})
case OrderedCollectionType:
err = OnOrderedCollection(it, func(c *OrderedCollection) error {
bytes, err := c.GobEncode()
b.Write(bytes)
return err
})
case CollectionPageType:
err = OnCollectionPage(it, func(p *CollectionPage) error {
bytes, err := p.GobEncode()
b.Write(bytes)
return err
})
case OrderedCollectionPageType:
err = OnOrderedCollectionPage(it, func(p *OrderedCollectionPage) error {
bytes, err := p.GobEncode()
b.Write(bytes)
return err
})
case PlaceType:
err = OnPlace(it, func(p *Place) error {
bytes, err := p.GobEncode()
b.Write(bytes)
return err
})
case ProfileType:
err = OnProfile(it, func(p *Profile) error {
bytes, err := p.GobEncode()
b.Write(bytes)
return err
})
case RelationshipType:
err = OnRelationship(it, func(r *Relationship) error {
bytes, err := r.GobEncode()
b.Write(bytes)
return err
})
case TombstoneType:
err = OnTombstone(it, func(t *Tombstone) error {
bytes, err := t.GobEncode()
b.Write(bytes)
return err
})
case QuestionType:
err = OnQuestion(it, func(q *Question) error {
bytes, err := q.GobEncode()
b.Write(bytes)
return err
})
}
}
return b.Bytes(), err
}
func mapObjectProperties(mm map[string][]byte, o *Object) (hasData bool, err error) {
if len(o.ID) > 0 {
if mm["id"], err = o.ID.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
if len(o.Type) > 0 {
if mm["type"], err = o.Type.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
if len(o.MediaType) > 0 {
if mm["mediaType"], err = o.MediaType.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
if len(o.Name) > 0 {
if mm["name"], err = o.Name.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
if o.Attachment != nil {
if mm["attachment"], err = gobEncodeItem(o.Attachment); err != nil {
return hasData, err
}
hasData = true
}
if o.AttributedTo != nil {
if mm["attributedTo"], err = gobEncodeItem(o.AttributedTo); err != nil {
return hasData, err
}
hasData = true
}
if o.Audience != nil {
if mm["audience"], err = gobEncodeItem(o.Audience); err != nil {
return hasData, err
}
hasData = true
}
if o.Content != nil {
if mm["content"], err = o.Content.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
if o.Context != nil {
if mm["context"], err = gobEncodeItem(o.Context); err != nil {
return hasData, err
}
hasData = true
}
if len(o.MediaType) > 0 {
if mm["mediaType"], err = o.MediaType.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
if !o.EndTime.IsZero() {
if mm["endTime"], err = o.EndTime.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
if o.Generator != nil {
if mm["generator"], err = gobEncodeItem(o.Generator); err != nil {
return hasData, err
}
hasData = true
}
if o.Icon != nil {
if mm["icon"], err = gobEncodeItem(o.Icon); err != nil {
return hasData, err
}
hasData = true
}
if o.Image != nil {
if mm["image"], err = gobEncodeItem(o.Image); err != nil {
return hasData, err
}
hasData = true
}
if o.InReplyTo != nil {
if mm["inReplyTo"], err = gobEncodeItem(o.InReplyTo); err != nil {
return hasData, err
}
hasData = true
}
if o.Location != nil {
if mm["location"], err = gobEncodeItem(o.Location); err != nil {
return hasData, err
}
hasData = true
}
if o.Preview != nil {
if mm["preview"], err = gobEncodeItem(o.Preview); err != nil {
return hasData, err
}
hasData = true
}
if !o.Published.IsZero() {
if mm["published"], err = o.Published.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
if o.Replies != nil {
if mm["replies"], err = gobEncodeItem(o.Replies); err != nil {
return hasData, err
}
hasData = true
}
if !o.StartTime.IsZero() {
if mm["startTime"], err = o.StartTime.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
if len(o.Summary) > 0 {
if mm["summary"], err = o.Summary.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
if o.Tag != nil {
if mm["tag"], err = gobEncodeItem(o.Tag); err != nil {
return hasData, err
}
hasData = true
}
if !o.Updated.IsZero() {
if mm["updated"], err = o.Updated.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
if o.Tag != nil {
if mm["tag"], err = gobEncodeItem(o.Tag); err != nil {
return hasData, err
}
hasData = true
}
if !o.Updated.IsZero() {
if mm["updated"], err = o.Updated.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
if o.URL != nil {
if mm["url"], err = gobEncodeItemOrLink(o.URL); err != nil {
return hasData, err
}
hasData = true
}
if o.To != nil {
if mm["to"], err = gobEncodeItem(o.To); err != nil {
return hasData, err
}
hasData = true
}
if o.Bto != nil {
if mm["bto"], err = gobEncodeItem(o.Bto); err != nil {
return hasData, err
}
hasData = true
}
if o.CC != nil {
if mm["cc"], err = gobEncodeItem(o.CC); err != nil {
return hasData, err
}
hasData = true
}
if o.BCC != nil {
if mm["bcc"], err = gobEncodeItem(o.BCC); err != nil {
return hasData, err
}
hasData = true
}
if o.Duration > 0 {
if mm["duration"], err = gobEncodeInt64(int64(o.Duration)); err != nil {
return hasData, err
}
hasData = true
}
if o.Likes != nil {
if mm["likes"], err = gobEncodeItem(o.Likes); err != nil {
return hasData, err
}
hasData = true
}
if o.Shares != nil {
if mm["shares"], err = gobEncodeItem(o.Shares); err != nil {
return hasData, err
}
hasData = true
}
if o.Shares != nil {
if mm["shares"], err = gobEncodeItem(o.Shares); err != nil {
return hasData, err
}
hasData = true
}
if len(o.Source.MediaType)+len(o.Source.Content) > 0 {
if mm["source"], err = o.Source.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
return hasData, nil
}
func mapActorProperties(mm map[string][]byte, a *Actor) (hasData bool, err error) {
err = OnObject(a, func(o *Object) error {
hasData, err = mapObjectProperties(mm, o)
return err
})
if a.Inbox != nil {
if mm["inbox"], err = gobEncodeItem(a.Inbox); err != nil {
return hasData, err
}
hasData = true
}
if a.Inbox != nil {
if mm["inbox"], err = gobEncodeItem(a.Inbox); err != nil {
return hasData, err
}
hasData = true
}
if a.Outbox != nil {
if mm["outbox"], err = gobEncodeItem(a.Outbox); err != nil {
return hasData, err
}
hasData = true
}
if a.Following != nil {
if mm["following"], err = gobEncodeItem(a.Following); err != nil {
return hasData, err
}
hasData = true
}
if a.Followers != nil {
if mm["followers"], err = gobEncodeItem(a.Followers); err != nil {
return hasData, err
}
hasData = true
}
if a.Liked != nil {
if mm["liked"], err = gobEncodeItem(a.Liked); err != nil {
return hasData, err
}
hasData = true
}
if len(a.PreferredUsername) > 0 {
if mm["preferredUsername"], err = a.PreferredUsername.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
if a.Endpoints != nil {
if mm["endpoints"], err = a.Endpoints.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
if len(a.Streams) > 0 {
if mm["streams"], err = gobEncodeItems(a.Streams); err != nil {
return hasData, err
}
hasData = true
}
if len(a.PublicKey.PublicKeyPem)+len(a.PublicKey.ID) > 0 {
if mm["publicKey"], err = a.PublicKey.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
return hasData, err
}
func mapIncompleteCollectionProperties(mm map[string][]byte, c Collection) (hasData bool, err error) {
err = OnObject(c, func(o *Object) error {
hasData, err = mapObjectProperties(mm, o)
return err
})
if c.Current != nil {
if mm["current"], err = gobEncodeItem(c.Current); err != nil {
return hasData, err
}
hasData = true
}
if c.First != nil {
if mm["first"], err = gobEncodeItem(c.First); err != nil {
return hasData, err
}
hasData = true
}
if c.Last != nil {
if mm["last"], err = gobEncodeItem(c.Last); err != nil {
return hasData, err
}
hasData = true
}
if c.TotalItems > 0 {
hasData = true
}
if mm["totalItems"], err = gobEncodeUint(c.TotalItems); err != nil {
return hasData, err
}
return
}
func mapCollectionProperties(mm map[string][]byte, c Collection) (hasData bool, err error) {
hasData, err = mapIncompleteCollectionProperties(mm, c)
if err != nil {
return hasData, err
}
if c.Items != nil {
if mm["items"], err = gobEncodeItems(c.Items); err != nil {
return hasData, err
}
hasData = true
}
return
}
func mapOrderedCollectionProperties(mm map[string][]byte, c OrderedCollection) (hasData bool, err error) {
err = OnCollection(c, func(c *Collection) error {
hasData, err = mapIncompleteCollectionProperties(mm, *c)
return err
})
if c.OrderedItems != nil {
if mm["orderedItems"], err = gobEncodeItems(c.OrderedItems); err != nil {
return hasData, err
}
hasData = true
}
return
}
func mapCollectionPageProperties(mm map[string][]byte, c CollectionPage) (hasData bool, err error) {
err = OnCollection(c, func(c *Collection) error {
hasData, err = mapCollectionProperties(mm, *c)
return err
})
if c.PartOf != nil {
if mm["partOf"], err = gobEncodeItem(c.PartOf); err != nil {
return hasData, err
}
hasData = true
}
if c.Next != nil {
if mm["next"], err = gobEncodeItem(c.Next); err != nil {
return hasData, err
}
hasData = true
}
if c.Prev != nil {
if mm["prev"], err = gobEncodeItem(c.Prev); err != nil {
return hasData, err
}
hasData = true
}
return
}
func mapOrderedCollectionPageProperties(mm map[string][]byte, c OrderedCollectionPage) (hasData bool, err error) {
err = OnOrderedCollection(c, func(c *OrderedCollection) error {
hasData, err = mapOrderedCollectionProperties(mm, *c)
return err
})
if c.PartOf != nil {
if mm["partOf"], err = gobEncodeItem(c.PartOf); err != nil {
return hasData, err
}
hasData = true
}
if c.Next != nil {
if mm["next"], err = gobEncodeItem(c.Next); err != nil {
return hasData, err
}
hasData = true
}
if c.Prev != nil {
if mm["prev"], err = gobEncodeItem(c.Prev); err != nil {
return hasData, err
}
hasData = true
}
return
}
func mapLinkProperties(mm map[string][]byte, l Link) (hasData bool, err error) {
if len(l.ID) > 0 {
if mm["id"], err = l.ID.GobEncode(); err != nil {
return
}
hasData = true
}
if len(l.Type) > 0 {
if mm["type"], err = l.Type.GobEncode(); err != nil {
return
}
hasData = true
}
if len(l.MediaType) > 0 {
if mm["mediaType"], err = l.MediaType.GobEncode(); err != nil {
return
}
hasData = true
}
if len(l.Href) > 0 {
if mm["href"], err = l.Href.GobEncode(); err != nil {
return
}
hasData = true
}
if len(l.HrefLang) > 0 {
if mm["hrefLang"], err = l.HrefLang.GobEncode(); err != nil {
return
}
hasData = true
}
if len(l.Name) > 0 {
if mm["name"], err = l.Name.GobEncode(); err != nil {
return
}
hasData = true
}
if len(l.Rel) > 0 {
if mm["rel"], err = l.Rel.GobEncode(); err != nil {
return
}
hasData = true
}
if l.Width > 0 {
if mm["width"], err = gobEncodeUint(l.Width); err != nil {
return
}
hasData = true
}
if l.Height > 0 {
if mm["height"], err = gobEncodeUint(l.Height); err != nil {
return
}
hasData = true
}
return
}
func mapPlaceProperties(mm map[string][]byte, p Place) (hasData bool, err error) {
err = OnObject(p, func(o *Object) error {
hasData, err = mapObjectProperties(mm, o)
return err
})
if p.Accuracy > 0 {
if mm["accuracy"], err = gobEncodeFloat64(p.Accuracy); err != nil {
return
}
hasData = true
}
if p.Altitude > 0 {
if mm["altitude"], err = gobEncodeFloat64(p.Altitude); err != nil {
return
}
hasData = true
}
if p.Latitude > 0 {
if mm["latitude"], err = gobEncodeFloat64(p.Latitude); err != nil {
return
}
hasData = true
}
if p.Longitude > 0 {
if mm["longitude"], err = gobEncodeFloat64(p.Longitude); err != nil {
return
}
hasData = true
}
if p.Radius > 0 {
if mm["radius"], err = gobEncodeInt64(p.Radius); err != nil {
return
}
hasData = true
}
if len(p.Units) > 0 {
mm["units"] = []byte(p.Units)
hasData = true
}
return
}
func mapProfileProperties(mm map[string][]byte, p Profile) (hasData bool, err error) {
err = OnObject(p, func(o *Object) error {
hasData, err = mapObjectProperties(mm, o)
return err
})
if p.Describes != nil {
if mm["describes"], err = gobEncodeItem(p.Describes); err != nil {
return
}
hasData = true
}
return
}
func mapRelationshipProperties(mm map[string][]byte, r Relationship) (hasData bool, err error) {
err = OnObject(r, func(o *Object) error {
hasData, err = mapObjectProperties(mm, o)
return err
})
if r.Subject != nil {
if mm["subject"], err = gobEncodeItem(r.Subject); err != nil {
return
}
hasData = true
}
if r.Object != nil {
if mm["object"], err = gobEncodeItem(r.Object); err != nil {
return
}
hasData = true
}
if r.Relationship != nil {
if mm["relationship"], err = gobEncodeItem(r.Relationship); err != nil {
return
}
hasData = true
}
return
}
func mapTombstoneProperties(mm map[string][]byte, t Tombstone) (hasData bool, err error) {
err = OnObject(t, func(o *Object) error {
hasData, err = mapObjectProperties(mm, o)
return err
})
if len(t.FormerType) > 0 {
if mm["formerType"], err = t.FormerType.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
if !t.Deleted.IsZero() {
if mm["deleted"], err = t.Deleted.GobEncode(); err != nil {
return hasData, err
}
hasData = true
}
return
}
func mapQuestionProperties(mm map[string][]byte, q Question) (hasData bool, err error) {
err = OnIntransitiveActivity(q, func(i *IntransitiveActivity) error {
hasData, err = mapIntransitiveActivityProperties(mm, i)
return err
})
if q.OneOf != nil {
if mm["oneOf"], err = gobEncodeItem(q.OneOf); err != nil {
return
}
hasData = true
}
if q.AnyOf != nil {
if mm["anyOf"], err = gobEncodeItem(q.AnyOf); err != nil {
return
}
hasData = true
}
if q.Closed {
hasData = true
}
if hasData {
if mm["closed"], err = gobEncodeBool(q.Closed); err != nil {
return
}
}
return
}

387
vendor/github.com/go-ap/activitypub/encoding_json.go generated vendored Normal file
View File

@ -0,0 +1,387 @@
package activitypub
import (
"encoding/json"
"fmt"
"time"
xsd "git.sr.ht/~mariusor/go-xsd-duration"
"github.com/go-ap/jsonld"
)
func JSONWriteComma(b *[]byte) {
if len(*b) > 1 && (*b)[len(*b)-1] != ',' {
*b = append(*b, ',')
}
}
func JSONWriteProp(b *[]byte, name string, val []byte) (notEmpty bool) {
if len(val) == 0 {
return false
}
JSONWriteComma(b)
success := JSONWritePropName(b, name) && JSONWriteValue(b, val)
if !success {
*b = (*b)[:len(*b)-1]
}
return success
}
func JSONWrite(b *[]byte, c ...byte) {
*b = append(*b, c...)
}
func JSONWriteS(b *[]byte, s string) {
*b = append(*b, s...)
}
func JSONWritePropName(b *[]byte, s string) (notEmpty bool) {
if len(s) == 0 {
return false
}
JSONWrite(b, '"')
JSONWriteS(b, s)
JSONWrite(b, '"', ':')
return true
}
func JSONWriteValue(b *[]byte, s []byte) (notEmpty bool) {
if len(s) == 0 {
return false
}
JSONWrite(b, s...)
return true
}
func JSONWriteNaturalLanguageProp(b *[]byte, n string, nl NaturalLanguageValues) (notEmpty bool) {
l := nl.Count()
if l > 1 {
n += "Map"
}
if v, err := nl.MarshalJSON(); err == nil && len(v) > 0 {
return JSONWriteProp(b, n, v)
}
return false
}
func JSONWriteStringProp(b *[]byte, n string, s string) (notEmpty bool) {
return JSONWriteProp(b, n, []byte(fmt.Sprintf(`"%s"`, s)))
}
func JSONWriteBoolProp(b *[]byte, n string, t bool) (notEmpty bool) {
return JSONWriteProp(b, n, []byte(fmt.Sprintf(`"%t"`, t)))
}
func JSONWriteIntProp(b *[]byte, n string, d int64) (notEmpty bool) {
return JSONWriteProp(b, n, []byte(fmt.Sprintf("%d", d)))
}
func JSONWriteFloatProp(b *[]byte, n string, f float64) (notEmpty bool) {
return JSONWriteProp(b, n, []byte(fmt.Sprintf("%f", f)))
}
func JSONWriteTimeProp(b *[]byte, n string, t time.Time) (notEmpty bool) {
var tb []byte
JSONWrite(&tb, '"')
JSONWriteS(&tb, t.UTC().Format(time.RFC3339))
JSONWrite(&tb, '"')
return JSONWriteProp(b, n, tb)
}
func JSONWriteDurationProp(b *[]byte, n string, d time.Duration) (notEmpty bool) {
var tb []byte
if v, err := xsd.Marshal(d); err == nil {
JSONWrite(&tb, '"')
JSONWrite(&tb, v...)
JSONWrite(&tb, '"')
}
return JSONWriteProp(b, n, tb)
}
func JSONWriteIRIProp(b *[]byte, n string, i LinkOrIRI) (notEmpty bool) {
url := i.GetLink().String()
if len(url) == 0 {
return false
}
JSONWriteStringProp(b, n, url)
return true
}
func JSONWriteItemProp(b *[]byte, n string, i Item) (notEmpty bool) {
if i == nil {
return notEmpty
}
if im, ok := i.(json.Marshaler); ok {
v, err := im.MarshalJSON()
if err != nil {
return false
}
return JSONWriteProp(b, n, v)
}
return notEmpty
}
func JSONWriteStringValue(b *[]byte, s string) (notEmpty bool) {
if len(s) == 0 {
return false
}
JSONWrite(b, '"')
JSONWriteS(b, s)
JSONWrite(b, '"')
return true
}
func JSONWriteItemCollectionValue(b *[]byte, col ItemCollection, compact bool) (notEmpty bool) {
if len(col) == 0 {
return notEmpty
}
if len(col) == 1 && compact {
it := col[0]
im, ok := it.(json.Marshaler)
if !ok {
return false
}
v, err := im.MarshalJSON()
if err != nil {
return false
}
if len(v) == 0 {
return false
}
JSONWrite(b, v...)
return true
}
writeCommaIfNotEmpty := func(notEmpty bool) {
if notEmpty {
JSONWrite(b, ',')
}
}
JSONWrite(b, '[')
skipComma := true
for _, it := range col {
im, ok := it.(json.Marshaler)
if !ok {
continue
}
v, err := im.MarshalJSON()
if err != nil {
return false
}
if len(v) == 0 {
continue
}
writeCommaIfNotEmpty(!skipComma)
JSONWrite(b, v...)
skipComma = false
}
JSONWrite(b, ']')
return true
}
func JSONWriteItemCollectionProp(b *[]byte, n string, col ItemCollection, compact bool) (notEmpty bool) {
if len(col) == 0 {
return notEmpty
}
JSONWriteComma(b)
success := JSONWritePropName(b, n) && JSONWriteItemCollectionValue(b, col, compact)
if !success {
*b = (*b)[:len(*b)-1]
}
return success
}
func JSONWriteObjectValue(b *[]byte, o Object) (notEmpty bool) {
if v, err := o.ID.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = JSONWriteProp(b, "id", v) || notEmpty
}
if v, err := o.Type.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = JSONWriteProp(b, "type", v) || notEmpty
}
if v, err := o.MediaType.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = JSONWriteProp(b, "mediaType", v) || notEmpty
}
if len(o.Name) > 0 {
notEmpty = JSONWriteNaturalLanguageProp(b, "name", o.Name) || notEmpty
}
if len(o.Summary) > 0 {
notEmpty = JSONWriteNaturalLanguageProp(b, "summary", o.Summary) || notEmpty
}
if len(o.Content) > 0 {
notEmpty = JSONWriteNaturalLanguageProp(b, "content", o.Content) || notEmpty
}
if o.Attachment != nil {
notEmpty = JSONWriteItemProp(b, "attachment", o.Attachment) || notEmpty
}
if o.AttributedTo != nil {
notEmpty = JSONWriteItemProp(b, "attributedTo", o.AttributedTo) || notEmpty
}
if o.Audience != nil {
notEmpty = JSONWriteItemProp(b, "audience", o.Audience) || notEmpty
}
if o.Context != nil {
notEmpty = JSONWriteItemProp(b, "context", o.Context) || notEmpty
}
if o.Generator != nil {
notEmpty = JSONWriteItemProp(b, "generator", o.Generator) || notEmpty
}
if o.Icon != nil {
notEmpty = JSONWriteItemProp(b, "icon", o.Icon) || notEmpty
}
if o.Image != nil {
notEmpty = JSONWriteItemProp(b, "image", o.Image) || notEmpty
}
if o.InReplyTo != nil {
notEmpty = JSONWriteItemProp(b, "inReplyTo", o.InReplyTo) || notEmpty
}
if o.Location != nil {
notEmpty = JSONWriteItemProp(b, "location", o.Location) || notEmpty
}
if o.Preview != nil {
notEmpty = JSONWriteItemProp(b, "preview", o.Preview) || notEmpty
}
if o.Replies != nil {
notEmpty = JSONWriteItemProp(b, "replies", o.Replies) || notEmpty
}
if o.Tag != nil {
notEmpty = JSONWriteItemCollectionProp(b, "tag", o.Tag, false) || notEmpty
}
if o.URL != nil {
notEmpty = JSONWriteItemProp(b, "url", o.URL) || notEmpty
}
if o.To != nil {
notEmpty = JSONWriteItemCollectionProp(b, "to", o.To, false) || notEmpty
}
if o.Bto != nil {
notEmpty = JSONWriteItemCollectionProp(b, "bto", o.Bto, false) || notEmpty
}
if o.CC != nil {
notEmpty = JSONWriteItemCollectionProp(b, "cc", o.CC, false) || notEmpty
}
if o.BCC != nil {
notEmpty = JSONWriteItemCollectionProp(b, "bcc", o.BCC, false) || notEmpty
}
if !o.Published.IsZero() {
notEmpty = JSONWriteTimeProp(b, "published", o.Published) || notEmpty
}
if !o.Updated.IsZero() {
notEmpty = JSONWriteTimeProp(b, "updated", o.Updated) || notEmpty
}
if !o.StartTime.IsZero() {
notEmpty = JSONWriteTimeProp(b, "startTime", o.StartTime) || notEmpty
}
if !o.EndTime.IsZero() {
notEmpty = JSONWriteTimeProp(b, "endTime", o.EndTime) || notEmpty
}
if o.Duration != 0 {
// TODO(marius): maybe don't use 0 as a nil value for Object types
// which can have a valid duration of 0 - (Video, Audio, etc)
notEmpty = JSONWriteDurationProp(b, "duration", o.Duration) || notEmpty
}
if o.Likes != nil {
notEmpty = JSONWriteItemProp(b, "likes", o.Likes) || notEmpty
}
if o.Shares != nil {
notEmpty = JSONWriteItemProp(b, "shares", o.Shares) || notEmpty
}
if v, err := o.Source.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = JSONWriteProp(b, "source", v) || notEmpty
}
return notEmpty
}
func JSONWriteActivityValue(b *[]byte, a Activity) (notEmpty bool) {
OnIntransitiveActivity(a, func(i *IntransitiveActivity) error {
if i == nil {
return nil
}
notEmpty = JSONWriteIntransitiveActivityValue(b, *i) || notEmpty
return nil
})
if a.Object != nil {
notEmpty = JSONWriteItemProp(b, "object", a.Object) || notEmpty
}
return notEmpty
}
func JSONWriteIntransitiveActivityValue(b *[]byte, i IntransitiveActivity) (notEmpty bool) {
OnObject(i, func(o *Object) error {
if o == nil {
return nil
}
notEmpty = JSONWriteObjectValue(b, *o) || notEmpty
return nil
})
if i.Actor != nil {
notEmpty = JSONWriteItemProp(b, "actor", i.Actor) || notEmpty
}
if i.Target != nil {
notEmpty = JSONWriteItemProp(b, "target", i.Target) || notEmpty
}
if i.Result != nil {
notEmpty = JSONWriteItemProp(b, "result", i.Result) || notEmpty
}
if i.Origin != nil {
notEmpty = JSONWriteItemProp(b, "origin", i.Origin) || notEmpty
}
if i.Instrument != nil {
notEmpty = JSONWriteItemProp(b, "instrument", i.Instrument) || notEmpty
}
return notEmpty
}
func JSONWriteQuestionValue(b *[]byte, q Question) (notEmpty bool) {
OnIntransitiveActivity(q, func(i *IntransitiveActivity) error {
if i == nil {
return nil
}
notEmpty = JSONWriteIntransitiveActivityValue(b, *i) || notEmpty
return nil
})
if q.OneOf != nil {
notEmpty = JSONWriteItemProp(b, "oneOf", q.OneOf) || notEmpty
}
if q.AnyOf != nil {
notEmpty = JSONWriteItemProp(b, "anyOf", q.AnyOf) || notEmpty
}
notEmpty = JSONWriteBoolProp(b, "closed", q.Closed) || notEmpty
return notEmpty
}
func JSONWriteLinkValue(b *[]byte, l Link) (notEmpty bool) {
if v, err := l.ID.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = JSONWriteProp(b, "id", v) || notEmpty
}
if v, err := l.Type.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = JSONWriteProp(b, "type", v) || notEmpty
}
if v, err := l.MediaType.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = JSONWriteProp(b, "mediaType", v) || notEmpty
}
if len(l.Name) > 0 {
notEmpty = JSONWriteNaturalLanguageProp(b, "name", l.Name) || notEmpty
}
if v, err := l.Rel.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = JSONWriteProp(b, "rel", v) || notEmpty
}
if l.Height > 0 {
notEmpty = JSONWriteIntProp(b, "height", int64(l.Height))
}
if l.Width > 0 {
notEmpty = JSONWriteIntProp(b, "width", int64(l.Width))
}
if l.Preview != nil {
notEmpty = JSONWriteItemProp(b, "rel", l.Preview) || notEmpty
}
if v, err := l.Href.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = JSONWriteProp(b, "href", v) || notEmpty
}
if len(l.HrefLang) > 0 {
notEmpty = JSONWriteStringProp(b, "hrefLang", string(l.HrefLang)) || notEmpty
}
return notEmpty
}
// MarshalJSON represents just a wrapper for the jsonld.Marshal function
func MarshalJSON(it Item) ([]byte, error) {
return jsonld.Marshal(it)
}

127
vendor/github.com/go-ap/activitypub/flatten.go generated vendored Normal file
View File

@ -0,0 +1,127 @@
package activitypub
// FlattenActivityProperties flattens the Activity's properties from Object type to IRI
func FlattenActivityProperties(act *Activity) *Activity {
OnIntransitiveActivity(act, func(in *IntransitiveActivity) error {
FlattenIntransitiveActivityProperties(in)
return nil
})
act.Object = FlattenToIRI(act.Object)
return act
}
// FlattenIntransitiveActivityProperties flattens the Activity's properties from Object type to IRI
func FlattenIntransitiveActivityProperties(act *IntransitiveActivity) *IntransitiveActivity {
act.Actor = FlattenToIRI(act.Actor)
act.Target = FlattenToIRI(act.Target)
act.Result = FlattenToIRI(act.Result)
act.Origin = FlattenToIRI(act.Origin)
act.Result = FlattenToIRI(act.Result)
act.Instrument = FlattenToIRI(act.Instrument)
OnObject(act, func(o *Object) error {
FlattenObjectProperties(o)
return nil
})
return act
}
// FlattenItemCollection flattens an Item Collection to their respective IRIs
func FlattenItemCollection(col ItemCollection) ItemCollection {
if col == nil {
return col
}
for k, it := range ItemCollectionDeduplication(&col) {
if iri := it.GetLink(); iri != "" {
col[k] = iri
}
}
return col
}
// FlattenCollection flattens a Collection's objects to their respective IRIs
func FlattenCollection(col *Collection) *Collection {
if col == nil {
return col
}
col.Items = FlattenItemCollection(col.Items)
return col
}
// FlattenOrderedCollection flattens an OrderedCollection's objects to their respective IRIs
func FlattenOrderedCollection(col *OrderedCollection) *OrderedCollection {
if col == nil {
return col
}
col.OrderedItems = FlattenItemCollection(col.OrderedItems)
return col
}
// FlattenActorProperties flattens the Actor's properties from Object types to IRI
func FlattenActorProperties(a *Actor) *Actor {
OnObject(a, func(o *Object) error {
FlattenObjectProperties(o)
return nil
})
return a
}
// FlattenObjectProperties flattens the Object's properties from Object types to IRI
func FlattenObjectProperties(o *Object) *Object {
o.Replies = Flatten(o.Replies)
o.Shares = Flatten(o.Shares)
o.Likes = Flatten(o.Likes)
o.AttributedTo = Flatten(o.AttributedTo)
o.To = FlattenItemCollection(o.To)
o.Bto = FlattenItemCollection(o.Bto)
o.CC = FlattenItemCollection(o.CC)
o.BCC = FlattenItemCollection(o.BCC)
o.Audience = FlattenItemCollection(o.Audience)
// o.Tag = FlattenItemCollection(o.Tag)
return o
}
// FlattenProperties flattens the Item's properties from Object types to IRI
func FlattenProperties(it Item) Item {
typ := it.GetType()
if IntransitiveActivityTypes.Contains(typ) {
OnIntransitiveActivity(it, func(a *IntransitiveActivity) error {
FlattenIntransitiveActivityProperties(a)
return nil
})
} else if ActivityTypes.Contains(typ) {
OnActivity(it, func(a *Activity) error {
FlattenActivityProperties(a)
return nil
})
}
if ActorTypes.Contains(typ) {
OnActor(it, func(a *Actor) error {
FlattenActorProperties(a)
return nil
})
}
if ObjectTypes.Contains(typ) {
OnObject(it, func(o *Object) error {
FlattenObjectProperties(o)
return nil
})
}
return it
}
// Flatten checks if Item can be flattened to an IRI or array of IRIs and returns it if so
func Flatten(it Item) Item {
if IsNil(it) {
return nil
}
if it.IsCollection() {
OnCollectionIntf(it, func(c CollectionInterface) error {
it = FlattenItemCollection(c.Collection()).Normalize()
return nil
})
return it
}
return it.GetLink()
}

532
vendor/github.com/go-ap/activitypub/helpers.go generated vendored Normal file
View File

@ -0,0 +1,532 @@
package activitypub
import (
"fmt"
)
// WithLinkFn represents a function type that can be used as a parameter for OnLink helper function
type WithLinkFn func(*Link) error
// WithObjectFn represents a function type that can be used as a parameter for OnObject helper function
type WithObjectFn func(*Object) error
// WithActivityFn represents a function type that can be used as a parameter for OnActivity helper function
type WithActivityFn func(*Activity) error
// WithIntransitiveActivityFn represents a function type that can be used as a parameter for OnIntransitiveActivity helper function
type WithIntransitiveActivityFn func(*IntransitiveActivity) error
// WithQuestionFn represents a function type that can be used as a parameter for OnQuestion helper function
type WithQuestionFn func(*Question) error
// WithActorFn represents a function type that can be used as a parameter for OnActor helper function
type WithActorFn func(*Actor) error
// WithCollectionInterfaceFn represents a function type that can be used as a parameter for OnCollectionIntf helper function
type WithCollectionInterfaceFn func(CollectionInterface) error
// WithCollectionFn represents a function type that can be used as a parameter for OnCollection helper function
type WithCollectionFn func(*Collection) error
// WithCollectionPageFn represents a function type that can be used as a parameter for OnCollectionPage helper function
type WithCollectionPageFn func(*CollectionPage) error
// WithOrderedCollectionFn represents a function type that can be used as a parameter for OnOrderedCollection helper function
type WithOrderedCollectionFn func(*OrderedCollection) error
// WithOrderedCollectionPageFn represents a function type that can be used as a parameter for OnOrderedCollectionPage helper function
type WithOrderedCollectionPageFn func(*OrderedCollectionPage) error
// WithItemCollectionFn represents a function type that can be used as a parameter for OnItemCollection helper function
type WithItemCollectionFn func(*ItemCollection) error
// WithIRIsFn represents a function type that can be used as a parameter for OnIRIs helper function
type WithIRIsFn func(*IRIs) error
// OnLink calls function fn on it Item if it can be asserted to type *Link
//
// This function should be safe to use for all types with a structure compatible
// with the Link type
func OnLink(it LinkOrIRI, fn WithLinkFn) error {
if it == nil {
return nil
}
ob, err := ToLink(it)
if err != nil {
return err
}
return fn(ob)
}
func To[T Item](it Item) (*T, error) {
if ob, ok := it.(T); ok {
return &ob, nil
}
return nil, fmt.Errorf("invalid cast for object %T", it)
}
// On handles in a generic way the call to fn(*T) if the "it" Item can be asserted to one of the Objects type.
// It also covers the case where "it" is a collection of items that match the assertion.
func On[T Item](it Item, fn func(*T) error) error {
if !IsItemCollection(it) {
ob, err := To[T](it)
if err != nil {
return err
}
return fn(ob)
}
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if err := On(it, fn); err != nil {
return err
}
}
return nil
})
}
// OnObject calls function fn on it Item if it can be asserted to type *Object
//
// This function should be safe to be called for all types with a structure compatible
// to the Object type.
func OnObject(it Item, fn WithObjectFn) error {
if it == nil {
return nil
}
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if IsLink(it) {
continue
}
if err := OnObject(it, fn); err != nil {
return err
}
}
return nil
})
}
ob, err := ToObject(it)
if err != nil {
return err
}
return fn(ob)
}
// OnActivity calls function fn on it Item if it can be asserted to type *Activity
//
// This function should be called if trying to access the Activity specific properties
// like "object", for the other properties OnObject, or OnIntransitiveActivity
// should be used instead.
func OnActivity(it Item, fn WithActivityFn) error {
if it == nil {
return nil
}
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if IsLink(it) {
continue
}
if err := OnActivity(it, fn); err != nil {
return err
}
}
return nil
})
}
act, err := ToActivity(it)
if err != nil {
return err
}
return fn(act)
}
// OnIntransitiveActivity calls function fn on it Item if it can be asserted
// to type *IntransitiveActivity
//
// This function should be called if trying to access the IntransitiveActivity
// specific properties like "actor", for the other properties OnObject
// should be used instead.
func OnIntransitiveActivity(it Item, fn WithIntransitiveActivityFn) error {
if it == nil {
return nil
}
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if err := OnIntransitiveActivity(it, fn); err != nil {
return err
}
}
return nil
})
}
act, err := ToIntransitiveActivity(it)
if err != nil {
return err
}
return fn(act)
}
// OnQuestion calls function fn on it Item if it can be asserted to type Question
//
// This function should be called if trying to access the Questions specific
// properties like "anyOf", "oneOf", "closed", etc. For the other properties
// OnObject or OnIntransitiveActivity should be used instead.
func OnQuestion(it Item, fn WithQuestionFn) error {
if it == nil {
return nil
}
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if err := OnQuestion(it, fn); err != nil {
return err
}
}
return nil
})
}
act, err := ToQuestion(it)
if err != nil {
return err
}
return fn(act)
}
// OnActor calls function fn on it Item if it can be asserted to type *Actor
//
// This function should be called if trying to access the Actor specific
// properties like "preferredName", "publicKey", etc. For the other properties
// OnObject should be used instead.
func OnActor(it Item, fn WithActorFn) error {
if it == nil {
return nil
}
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if IsLink(it) {
continue
}
if err := OnActor(it, fn); err != nil {
return err
}
}
return nil
})
}
act, err := ToActor(it)
if err != nil {
return err
}
return fn(act)
}
// OnItemCollection calls function fn on it Item if it can be asserted to type ItemCollection
//
// It should be used when Item represents an Item collection and it's usually used as a way
// to wrap functionality for other functions that will be called on each item in the collection.
func OnItemCollection(it Item, fn WithItemCollectionFn) error {
if it == nil {
return nil
}
col, err := ToItemCollection(it)
if err != nil {
return err
}
return fn(col)
}
// OnIRIs calls function fn on it Item if it can be asserted to type IRIs
//
// It should be used when Item represents an IRI slice.
func OnIRIs(it Item, fn WithIRIsFn) error {
if it == nil {
return nil
}
col, err := ToIRIs(it)
if err != nil {
return err
}
return fn(col)
}
// OnCollectionIntf calls function fn on it Item if it can be asserted to a type
// that implements the CollectionInterface
//
// This function should be called if Item represents a collection of ActivityPub
// objects. It basically wraps functionality for the different collection types
// supported by the package.
func OnCollectionIntf(it Item, fn WithCollectionInterfaceFn) error {
if it == nil {
return nil
}
switch it.GetType() {
case CollectionOfItems:
col, err := ToItemCollection(it)
if err != nil {
return err
}
return fn(col)
case CollectionOfIRIs:
col, err := ToIRIs(it)
if err != nil {
return err
}
itCol := col.Collection()
return fn(&itCol)
case CollectionType:
col, err := ToCollection(it)
if err != nil {
return err
}
return fn(col)
case CollectionPageType:
return OnCollectionPage(it, func(p *CollectionPage) error {
col, err := ToCollectionPage(p)
if err != nil {
return err
}
return fn(col)
})
case OrderedCollectionType:
col, err := ToOrderedCollection(it)
if err != nil {
return err
}
return fn(col)
case OrderedCollectionPageType:
return OnOrderedCollectionPage(it, func(p *OrderedCollectionPage) error {
col, err := ToOrderedCollectionPage(p)
if err != nil {
return err
}
return fn(col)
})
default:
return fmt.Errorf("%T[%s] can't be converted to a Collection type", it, it.GetType())
}
}
// OnCollection calls function fn on it Item if it can be asserted to type *Collection
//
// This function should be called if trying to access the Collection specific
// properties like "totalItems", "items", etc. For the other properties
// OnObject should be used instead.
func OnCollection(it Item, fn WithCollectionFn) error {
if it == nil {
return nil
}
col, err := ToCollection(it)
if err != nil {
return err
}
return fn(col)
}
// OnCollectionPage calls function fn on it Item if it can be asserted to
// type *CollectionPage
//
// This function should be called if trying to access the CollectionPage specific
// properties like "partOf", "next", "perv". For the other properties
// OnObject or OnCollection should be used instead.
func OnCollectionPage(it Item, fn WithCollectionPageFn) error {
if it == nil {
return nil
}
col, err := ToCollectionPage(it)
if err != nil {
return err
}
return fn(col)
}
// OnOrderedCollection calls function fn on it Item if it can be asserted
// to type *OrderedCollection
//
// This function should be called if trying to access the Collection specific
// properties like "totalItems", "orderedItems", etc. For the other properties
// OnObject should be used instead.
func OnOrderedCollection(it Item, fn WithOrderedCollectionFn) error {
if it == nil {
return nil
}
col, err := ToOrderedCollection(it)
if err != nil {
return err
}
return fn(col)
}
// OnOrderedCollectionPage calls function fn on it Item if it can be asserted
// to type *OrderedCollectionPage
//
// This function should be called if trying to access the OrderedCollectionPage specific
// properties like "partOf", "next", "perv". For the other properties
// OnObject or OnOrderedCollection should be used instead.
func OnOrderedCollectionPage(it Item, fn WithOrderedCollectionPageFn) error {
if it == nil {
return nil
}
col, err := ToOrderedCollectionPage(it)
if err != nil {
return err
}
return fn(col)
}
// ItemOrderTimestamp is used for ordering a ItemCollection slice using the slice.Sort function
// It orders i1 and i2 based on their Published and Updated timestamps.
func ItemOrderTimestamp(i1, i2 Item) bool {
o1, e1 := ToObject(i1)
o2, e2 := ToObject(i2)
if e1 != nil || e2 != nil {
return false
}
t1 := o1.Published
if !o1.Updated.IsZero() {
t1 = o1.Updated
}
t2 := o2.Published
if !o2.Updated.IsZero() {
t2 = o2.Updated
}
return t1.Sub(t2) > 0
}
func notEmptyLink(l *Link) bool {
return len(l.ID) > 0 ||
LinkTypes.Contains(l.Type) ||
len(l.MediaType) > 0 ||
l.Preview != nil ||
l.Name != nil ||
len(l.Href) > 0 ||
len(l.Rel) > 0 ||
len(l.HrefLang) > 0 ||
l.Height > 0 ||
l.Width > 0
}
func notEmptyObject(o *Object) bool {
if o == nil {
return false
}
return len(o.ID) > 0 ||
len(o.Type) > 0 ||
ActivityTypes.Contains(o.Type) ||
o.Content != nil ||
o.Attachment != nil ||
o.AttributedTo != nil ||
o.Audience != nil ||
o.BCC != nil ||
o.Bto != nil ||
o.CC != nil ||
o.Context != nil ||
o.Duration > 0 ||
!o.EndTime.IsZero() ||
o.Generator != nil ||
o.Icon != nil ||
o.Image != nil ||
o.InReplyTo != nil ||
o.Likes != nil ||
o.Location != nil ||
len(o.MediaType) > 0 ||
o.Name != nil ||
o.Preview != nil ||
!o.Published.IsZero() ||
o.Replies != nil ||
o.Shares != nil ||
o.Source.MediaType != "" ||
o.Source.Content != nil ||
!o.StartTime.IsZero() ||
o.Summary != nil ||
o.Tag != nil ||
o.To != nil ||
!o.Updated.IsZero() ||
o.URL != nil
}
func notEmptyInstransitiveActivity(i *IntransitiveActivity) bool {
notEmpty := i.Actor != nil ||
i.Target != nil ||
i.Result != nil ||
i.Origin != nil ||
i.Instrument != nil
if notEmpty {
return true
}
OnObject(i, func(ob *Object) error {
notEmpty = notEmptyObject(ob)
return nil
})
return notEmpty
}
func notEmptyActivity(a *Activity) bool {
var notEmpty bool
OnIntransitiveActivity(a, func(i *IntransitiveActivity) error {
notEmpty = notEmptyInstransitiveActivity(i)
return nil
})
return notEmpty || a.Object != nil
}
func notEmptyActor(a *Actor) bool {
var notEmpty bool
OnObject(a, func(o *Object) error {
notEmpty = notEmptyObject(o)
return nil
})
return notEmpty ||
a.Inbox != nil ||
a.Outbox != nil ||
a.Following != nil ||
a.Followers != nil ||
a.Liked != nil ||
a.PreferredUsername != nil ||
a.Endpoints != nil ||
a.Streams != nil ||
len(a.PublicKey.ID)+len(a.PublicKey.Owner)+len(a.PublicKey.PublicKeyPem) > 0
}
// NotEmpty tells us if a Item interface value has a non nil value for various types
// that implement
func NotEmpty(i Item) bool {
if IsNil(i) {
return false
}
var notEmpty bool
if IsIRI(i) {
notEmpty = len(i.GetLink()) > 0
}
if i.IsCollection() {
OnCollectionIntf(i, func(c CollectionInterface) error {
notEmpty = c != nil || len(c.Collection()) > 0
return nil
})
}
if ActivityTypes.Contains(i.GetType()) {
OnActivity(i, func(a *Activity) error {
notEmpty = notEmptyActivity(a)
return nil
})
} else if ActorTypes.Contains(i.GetType()) {
OnActor(i, func(a *Actor) error {
notEmpty = notEmptyActor(a)
return nil
})
} else if i.IsLink() {
OnLink(i, func(l *Link) error {
notEmpty = notEmptyLink(l)
return nil
})
} else {
OnObject(i, func(o *Object) error {
notEmpty = notEmptyObject(o)
return nil
})
}
return notEmpty
}

View File

@ -0,0 +1,384 @@
package activitypub
import (
"bytes"
"encoding/gob"
"fmt"
"io"
"reflect"
"time"
"unsafe"
"github.com/valyala/fastjson"
)
type IntransitiveActivities interface {
IntransitiveActivity | Question
}
// IntransitiveActivity Instances of IntransitiveActivity are a subtype of Activity representing intransitive actions.
// The object property is therefore inappropriate for these activities.
type IntransitiveActivity struct {
// ID provides the globally unique identifier for anActivity Pub Object or Link.
ID ID `jsonld:"id,omitempty"`
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
Type ActivityVocabularyType `jsonld:"type,omitempty"`
// Name a simple, human-readable, plain-text name for the object.
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
// The intent is to provide a model that is at least semantically similar to attachments in email.
Attachment Item `jsonld:"attachment,omitempty"`
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
// For instance, an object might be attributed to the completion of another activity.
AttributedTo Item `jsonld:"attributedTo,omitempty"`
// Audience identifies one or more entities that represent the total population of entities
// for which the object can considered to be relevant.
Audience ItemCollection `jsonld:"audience,omitempty"`
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
// By default, the value of content is HTML.
// The mediaType property can be used in the object to indicate a different content type.
// (The content MAY be expressed using multiple language-tagged values.)
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
// Context identifies the context within which the object exists or an activity was performed.
// The notion of "context" used is intentionally vague.
// The intended function is to serve as a means of grouping objects and activities that share a
// common originating context or purpose. An example could be all activities relating to a common project or event.
Context Item `jsonld:"context,omitempty"`
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
// If not specified, the content property is assumed to contain text/html content.
MediaType MimeType `jsonld:"mediaType,omitempty"`
// EndTime the date and time describing the actual or expected ending time of the object.
// When used with an Activity object, for instance, the endTime property specifies the moment
// the activity concluded or is expected to conclude.
EndTime time.Time `jsonld:"endTime,omitempty"`
// Generator identifies the entity (e.g. an application) that generated the object.
Generator Item `jsonld:"generator,omitempty"`
// Icon indicates an entity that describes an icon for this object.
// The image should have an aspect ratio of one (horizontal) to one (vertical)
// and should be suitable for presentation at a small size.
Icon Item `jsonld:"icon,omitempty"`
// Image indicates an entity that describes an image for this object.
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
Image Item `jsonld:"image,omitempty"`
// InReplyTo indicates one or more entities for which this object is considered a response.
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
// Location indicates one or more physical or logical locations associated with the object.
Location Item `jsonld:"location,omitempty"`
// Preview identifies an entity that provides a preview of this object.
Preview Item `jsonld:"preview,omitempty"`
// Published the date and time at which the object was published
Published time.Time `jsonld:"published,omitempty"`
// Replies identifies a Collection containing objects considered to be responses to this object.
Replies Item `jsonld:"replies,omitempty"`
// StartTime the date and time describing the actual or expected starting time of the object.
// When used with an Activity object, for instance, the startTime property specifies
// the moment the activity began or is scheduled to begin.
StartTime time.Time `jsonld:"startTime,omitempty"`
// Summary a natural language summarization of the object encoded as HTML.
// *Multiple language tagged summaries may be provided.)
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
// The key difference between attachment and tag is that the former implies association by inclusion,
// while the latter implies associated by reference.
Tag ItemCollection `jsonld:"tag,omitempty"`
// Updated the date and time at which the object was updated
Updated time.Time `jsonld:"updated,omitempty"`
// URL identifies one or more links to representations of the object
URL Item `jsonld:"url,omitempty"`
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
To ItemCollection `jsonld:"to,omitempty"`
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
Bto ItemCollection `jsonld:"bto,omitempty"`
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
CC ItemCollection `jsonld:"cc,omitempty"`
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
BCC ItemCollection `jsonld:"bcc,omitempty"`
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
// the duration property indicates the object's approximate duration.
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
Duration time.Duration `jsonld:"duration,omitempty"`
// This is a list of all Like activities with this object as the object property, added as a side effect.
// The likes collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Likes Item `jsonld:"likes,omitempty"`
// This is a list of all Announce activities with this object as the object property, added as a side effect.
// The shares collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Shares Item `jsonld:"shares,omitempty"`
// Source property is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
// In general, clients do the conversion from source to content, not the other way around.
Source Source `jsonld:"source,omitempty"`
// CanReceiveActivities describes one or more entities that either performed or are expected to perform the activity.
// Any single activity can have multiple actors. The actor may be specified using an indirect Link.
Actor CanReceiveActivities `jsonld:"actor,omitempty"`
// Target describes the indirect object, or target, of the activity.
// The precise meaning of the target is largely dependent on the type of action being described
// but will often be the object of the English preposition "to".
// For instance, in the activity "John added a movie to his wishlist",
// the target of the activity is John's wishlist. An activity can have more than one target.
Target Item `jsonld:"target,omitempty"`
// Result describes the result of the activity. For instance, if a particular action results in the creation
// of a new resource, the result property can be used to describe that new resource.
Result Item `jsonld:"result,omitempty"`
// Origin describes an indirect object of the activity from which the activity is directed.
// The precise meaning of the origin is the object of the English preposition "from".
// For instance, in the activity "John moved an item to List B from List A", the origin of the activity is "List A".
Origin Item `jsonld:"origin,omitempty"`
// Instrument identifies one or more objects used (or to be used) in the completion of an Activity.
Instrument Item `jsonld:"instrument,omitempty"`
}
type (
// Arrive is an IntransitiveActivity that indicates that the actor has arrived at the location.
// The origin can be used to identify the context from which the actor originated.
// The target typically has no defined meaning.
Arrive = IntransitiveActivity
// Travel indicates that the actor is traveling to target from origin.
// Travel is an IntransitiveObject whose actor specifies the direct object.
// If the target or origin are not specified, either can be determined by context.
Travel = IntransitiveActivity
)
// Recipients performs recipient de-duplication on the IntransitiveActivity's To, Bto, CC and BCC properties
func (i *IntransitiveActivity) Recipients() ItemCollection {
return ItemCollectionDeduplication(&ItemCollection{i.Actor}, &i.To, &i.Bto, &i.CC, &i.BCC, &i.Audience)
}
// Clean removes Bto and BCC properties
func (i *IntransitiveActivity) Clean() {
_ = OnObject(i, func(o *Object) error {
o.Clean()
return nil
})
}
// GetType returns the ActivityVocabulary type of the current Intransitive Activity
func (i IntransitiveActivity) GetType() ActivityVocabularyType {
return i.Type
}
// IsLink returns false for Activity objects
func (i IntransitiveActivity) IsLink() bool {
return false
}
// GetID returns the ID corresponding to the IntransitiveActivity object
func (i IntransitiveActivity) GetID() ID {
return i.ID
}
// GetLink returns the IRI corresponding to the IntransitiveActivity object
func (i IntransitiveActivity) GetLink() IRI {
return IRI(i.ID)
}
// IsObject returns true for IntransitiveActivity objects
func (i IntransitiveActivity) IsObject() bool {
return true
}
// IsCollection returns false for IntransitiveActivity objects
func (i IntransitiveActivity) IsCollection() bool {
return false
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (i *IntransitiveActivity) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadIntransitiveActivity(val, i)
}
// MarshalJSON encodes the receiver object to a JSON document.
func (i IntransitiveActivity) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
JSONWrite(&b, '{')
if !JSONWriteIntransitiveActivityValue(&b, i) {
return nil, nil
}
JSONWrite(&b, '}')
return b, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (i *IntransitiveActivity) UnmarshalBinary(data []byte) error {
return i.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (i IntransitiveActivity) MarshalBinary() ([]byte, error) {
return i.GobEncode()
}
func (i IntransitiveActivity) GobEncode() ([]byte, error) {
mm := make(map[string][]byte)
hasData, err := mapIntransitiveActivityProperties(mm, &i)
if err != nil {
return nil, err
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
func (i *IntransitiveActivity) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm, err := gobDecodeObjectAsMap(data)
if err != nil {
return err
}
return unmapIntransitiveActivityProperties(mm, i)
}
// IntransitiveActivityNew initializes a intransitive activity
func IntransitiveActivityNew(id ID, typ ActivityVocabularyType) *IntransitiveActivity {
if !IntransitiveActivityTypes.Contains(typ) {
typ = IntransitiveActivityType
}
i := IntransitiveActivity{ID: id, Type: typ}
i.Name = NaturalLanguageValuesNew()
i.Content = NaturalLanguageValuesNew()
return &i
}
// ToIntransitiveActivity tries to convert it Item to an IntransitiveActivity object
func ToIntransitiveActivity(it Item) (*IntransitiveActivity, error) {
switch i := it.(type) {
case *IntransitiveActivity:
return i, nil
case IntransitiveActivity:
return &i, nil
case *Question:
return (*IntransitiveActivity)(unsafe.Pointer(i)), nil
case Question:
return (*IntransitiveActivity)(unsafe.Pointer(&i)), nil
case *Activity:
return (*IntransitiveActivity)(unsafe.Pointer(i)), nil
case Activity:
return (*IntransitiveActivity)(unsafe.Pointer(&i)), nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(IntransitiveActivity))
if reflect.TypeOf(it).ConvertibleTo(typ) {
if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*IntransitiveActivity); ok {
return i, nil
}
}
}
return nil, ErrorInvalidType[IntransitiveActivity](it)
}
// ArriveNew initializes an Arrive activity
func ArriveNew(id ID) *Arrive {
a := IntransitiveActivityNew(id, ArriveType)
o := Arrive(*a)
return &o
}
// TravelNew initializes a Travel activity
func TravelNew(id ID) *Travel {
a := IntransitiveActivityNew(id, TravelType)
o := Travel(*a)
return &o
}
// Equals verifies if our receiver Object is equals with the "with" Object
func (i IntransitiveActivity) Equals(with Item) bool {
result := true
err := OnIntransitiveActivity(with, func(w *IntransitiveActivity) error {
_ = OnObject(i, func(oa *Object) error {
result = oa.Equals(w)
return nil
})
if w.Actor != nil {
if !ItemsEqual(i.Actor, w.Actor) {
result = false
return nil
}
}
if w.Target != nil {
if !ItemsEqual(i.Target, w.Target) {
result = false
return nil
}
}
if w.Result != nil {
if !ItemsEqual(i.Result, w.Result) {
result = false
return nil
}
}
if w.Origin != nil {
if !ItemsEqual(i.Origin, w.Origin) {
result = false
return nil
}
}
if w.Instrument != nil {
if !ItemsEqual(i.Instrument, w.Instrument) {
result = false
return nil
}
}
return nil
})
if err != nil {
result = false
}
return result
}
func (i IntransitiveActivity) Format(s fmt.State, verb rune) {
switch verb {
case 's':
if i.Type != "" && i.ID != "" {
_, _ = fmt.Fprintf(s, "%T[%s]( %s )", i, i.Type, i.ID)
} else if i.ID != "" {
_, _ = fmt.Fprintf(s, "%T( %s )", i, i.ID)
} else {
_, _ = fmt.Fprintf(s, "%T[%p]", i, &i)
}
case 'v':
_, _ = fmt.Fprintf(s, "%T[%s] {", i, i.Type)
_ = fmtIntransitiveActivityProps(s)(&i)
_, _ = io.WriteString(s, " }")
}
}
func fmtIntransitiveActivityProps(w io.Writer) func(*IntransitiveActivity) error {
return func(ia *IntransitiveActivity) error {
if !IsNil(ia.Actor) {
_, _ = fmt.Fprintf(w, " actor: %s", ia.Actor)
}
if !IsNil(ia.Target) {
_, _ = fmt.Fprintf(w, " target: %s", ia.Target)
}
if !IsNil(ia.Result) {
_, _ = fmt.Fprintf(w, " result: %s", ia.Result)
}
if !IsNil(ia.Origin) {
_, _ = fmt.Fprintf(w, " origin: %s", ia.Origin)
}
if !IsNil(ia.Instrument) {
_, _ = fmt.Fprintf(w, " instrument: %s", ia.Instrument)
}
return OnObject(ia, fmtObjectProps(w))
}
}

429
vendor/github.com/go-ap/activitypub/iri.go generated vendored Normal file
View File

@ -0,0 +1,429 @@
package activitypub
import (
"bytes"
"encoding/gob"
"fmt"
"io"
"net/url"
"path/filepath"
"strings"
"github.com/valyala/fastjson"
)
const (
// ActivityBaseURI the URI for the ActivityStreams namespace
ActivityBaseURI = IRI("https://www.w3.org/ns/activitystreams")
// SecurityContextURI the URI for the security namespace (for an Actor's PublicKey)
SecurityContextURI = IRI("https://w3id.org/security/v1")
// PublicNS is the reference to the Public entity in the ActivityStreams namespace.
//
// Public Addressing
//
// https://www.w3.org/TR/activitypub/#public-addressing
//
// In addition to [ActivityStreams] collections and objects, Activities may additionally be addressed to the
// special "public" collection, with the identifier https://www.w3.org/ns/activitystreams#Public. For example:
//
// {
// "@context": "https://www.w3.org/ns/activitystreams",
// "id": "https://www.w3.org/ns/activitystreams#Public",
// "type": "Collection"
// }
// Activities addressed to this special URI shall be accessible to all users, without authentication.
// Implementations MUST NOT deliver to the "public" special collection; it is not capable of receiving
// actual activities. However, actors MAY have a sharedInbox endpoint which is available for efficient
// shared delivery of public posts (as well as posts to followers-only); see 7.1.3 Shared Inbox Delivery.
//
// NOTE
// Compacting an ActivityStreams object using the ActivityStreams JSON-LD context might result in
// https://www.w3.org/ns/activitystreams#Public being represented as simply Public or as:Public which are valid
// representations of the Public collection. Implementations which treat ActivityStreams objects as simply JSON
// rather than converting an incoming activity over to a local context using JSON-LD tooling should be aware
// of this and should be prepared to accept all three representations.
PublicNS = ActivityBaseURI + "#Public"
)
// JsonLDContext is a slice of IRIs that form the default context for the objects in the
// GoActivitypub vocabulary.
// It does not represent just the default ActivityStreams public namespace, but it also
// has the W3 Permanent Identifier Community Group's Security namespace, which appears
// in the Actor type objects, which contain public key related data.
var JsonLDContext = []IRI{
ActivityBaseURI,
SecurityContextURI,
}
type (
// IRI is a Internationalized Resource Identifiers (IRIs) RFC3987
IRI string
IRIs []IRI
)
func (i IRI) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'v':
_, _ = io.WriteString(s, i.String())
}
}
// String returns the String value of the IRI object
func (i IRI) String() string {
return string(i)
}
// GetLink
func (i IRI) GetLink() IRI {
return i
}
// URL
func (i IRI) URL() (*url.URL, error) {
return url.Parse(i.String())
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (i *IRI) UnmarshalJSON(s []byte) error {
*i = IRI(strings.Trim(string(s), "\""))
return nil
}
// MarshalJSON encodes the receiver object to a JSON document.
func (i IRI) MarshalJSON() ([]byte, error) {
if i == "" {
return nil, nil
}
b := make([]byte, 0)
JSONWrite(&b, '"')
JSONWriteS(&b, i.String())
JSONWrite(&b, '"')
return b, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (i *IRI) UnmarshalBinary(data []byte) error {
return i.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (i IRI) MarshalBinary() ([]byte, error) {
return i.GobEncode()
}
// GobEncode
func (i IRI) GobEncode() ([]byte, error) {
return []byte(i), nil
}
// GobEncode
func (i IRIs) GobEncode() ([]byte, error) {
if len(i) == 0 {
return []byte{}, nil
}
b := bytes.Buffer{}
gg := gob.NewEncoder(&b)
bb := make([][]byte, 0)
for _, iri := range i {
bb = append(bb, []byte(iri))
}
if err := gg.Encode(bb); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// GobDecode
func (i *IRI) GobDecode(data []byte) error {
*i = IRI(data)
return nil
}
func (i *IRIs) GobDecode(data []byte) error {
if len(data) == 0 {
// NOTE(marius): this behaviour diverges from vanilla gob package
return nil
}
err := gob.NewDecoder(bytes.NewReader(data)).Decode(i)
if err == nil {
return nil
}
bb := make([][]byte, 0)
err = gob.NewDecoder(bytes.NewReader(data)).Decode(&bb)
if err != nil {
return err
}
for _, b := range bb {
*i = append(*i, IRI(b))
}
return nil
}
// AddPath concatenates el elements as a path to i
func (i IRI) AddPath(el ...string) IRI {
iri := strings.TrimRight(i.String(), "/")
return IRI(iri + filepath.Clean(filepath.Join("/", filepath.Join(el...))))
}
// GetID
func (i IRI) GetID() ID {
return i
}
// GetType
func (i IRI) GetType() ActivityVocabularyType {
return IRIType
}
// IsLink
func (i IRI) IsLink() bool {
return true
}
// IsObject
func (i IRI) IsObject() bool {
return false
}
// IsCollection returns false for IRI objects
func (i IRI) IsCollection() bool {
return false
}
// FlattenToIRI checks if Item can be flatten to an IRI and returns it if so
func FlattenToIRI(it Item) Item {
if !IsNil(it) && it.IsObject() && len(it.GetLink()) > 0 {
return it.GetLink()
}
return it
}
func (i IRIs) MarshalJSON() ([]byte, error) {
if len(i) == 0 {
return []byte{'[', ']'}, nil
}
b := make([]byte, 0)
writeCommaIfNotEmpty := func(notEmpty bool) {
if notEmpty {
JSONWriteS(&b, ",")
}
}
JSONWrite(&b, '[')
for k, iri := range i {
writeCommaIfNotEmpty(k > 0)
JSONWrite(&b, '"')
JSONWriteS(&b, iri.String())
JSONWrite(&b, '"')
}
JSONWrite(&b, ']')
return b, nil
}
func (i *IRIs) UnmarshalJSON(data []byte) error {
if i == nil {
return nil
}
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
switch val.Type() {
case fastjson.TypeString:
if iri, ok := asIRI(val); ok && len(iri) > 0 {
*i = append(*i, iri)
}
case fastjson.TypeArray:
for _, v := range val.GetArray() {
if iri, ok := asIRI(v); ok && len(iri) > 0 {
*i = append(*i, iri)
}
}
}
return nil
}
// GetID returns the ID corresponding to ItemCollection
func (i IRIs) GetID() ID {
return EmptyID
}
// GetLink returns the empty IRI
func (i IRIs) GetLink() IRI {
return EmptyIRI
}
// GetType returns the ItemCollection's type
func (i IRIs) GetType() ActivityVocabularyType {
return CollectionOfIRIs
}
// IsLink returns false for an ItemCollection object
func (i IRIs) IsLink() bool {
return false
}
// IsObject returns true for a ItemCollection object
func (i IRIs) IsObject() bool {
return false
}
// IsCollection returns true for IRI slices
func (i IRIs) IsCollection() bool {
return true
}
// Append facilitates adding elements to IRI slices
// and ensures IRIs implements the Collection interface
func (i *IRIs) Append(it ...Item) error {
for _, ob := range it {
if (*i).Contains(ob.GetLink()) {
continue
}
*i = append(*i, ob.GetLink())
}
return nil
}
func (i *IRIs) Collection() ItemCollection {
res := make(ItemCollection, len(*i))
for k, iri := range *i {
res[k] = iri
}
return res
}
func (i *IRIs) Count() uint {
return uint(len(*i))
}
// Contains verifies if IRIs array contains the received one
func (i IRIs) Contains(r Item) bool {
if len(i) == 0 {
return false
}
for _, iri := range i {
if r.GetLink().Equals(iri, false) {
return true
}
}
return false
}
func validURL(u *url.URL) bool {
return len(u.Scheme) > 0 && len(u.Host) > 0
}
// Equals verifies if our receiver IRI is equals with the "with" IRI
func (i IRI) Equals(with IRI, checkScheme bool) bool {
if checkScheme {
if strings.EqualFold(string(i), string(with)) {
return true
}
} else {
is := string(i)
ws := string(with)
ip := strings.Index(is, "://")
if ip < 0 {
ip = 0
}
wp := strings.Index(ws, "://")
if wp < 0 {
wp = 0
}
if strings.EqualFold(is[ip:], ws[wp:]) {
return true
}
}
u, e := i.URL()
uw, ew := with.URL()
if e != nil || ew != nil || !validURL(u) || !validURL(uw) {
return strings.EqualFold(i.String(), with.String())
}
if checkScheme {
if !strings.EqualFold(u.Scheme, uw.Scheme) {
return false
}
}
if !strings.EqualFold(u.Host, uw.Host) {
return false
}
if !(u.Path == "/" && uw.Path == "" || u.Path == "" && uw.Path == "/") &&
!strings.EqualFold(filepath.Clean(u.Path), filepath.Clean(uw.Path)) {
return false
}
uq := u.Query()
uwq := uw.Query()
if len(uq) != len(uwq) {
return false
}
for k, uqv := range uq {
uwqv, ok := uwq[k]
if !ok {
return false
}
if len(uqv) != len(uwqv) {
return false
}
for _, uqvv := range uqv {
eq := false
for _, uwqvv := range uwqv {
if uwqvv == uqvv {
eq = true
continue
}
}
if !eq {
return false
}
}
}
return true
}
func hostSplit(h string) (string, string) {
pieces := strings.Split(h, ":")
if len(pieces) == 0 {
return "", ""
}
if len(pieces) == 1 {
return pieces[0], ""
}
return pieces[0], pieces[1]
}
func (i IRI) Contains(what IRI, checkScheme bool) bool {
u, e := i.URL()
uw, ew := what.URL()
if e != nil || ew != nil {
return strings.Contains(i.String(), what.String())
}
if checkScheme {
if u.Scheme != uw.Scheme {
return false
}
}
uHost, _ := hostSplit(u.Host)
uwHost, _ := hostSplit(uw.Host)
if uHost != uwHost {
return false
}
p := u.Path
if p != "" {
p = filepath.Clean(p)
}
pw := uw.Path
if pw != "" {
pw = filepath.Clean(pw)
}
return strings.Contains(p, pw)
}
func (i IRI) ItemsMatch(col ...Item) bool {
for _, it := range col {
if match := it.GetLink().Contains(i, false); !match {
return false
}
}
return true
}

162
vendor/github.com/go-ap/activitypub/item.go generated vendored Normal file
View File

@ -0,0 +1,162 @@
package activitypub
import (
"fmt"
"reflect"
)
// Item struct
type Item = ObjectOrLink
const (
// EmptyIRI represents a zero length IRI
EmptyIRI IRI = ""
// NilIRI represents by convention an IRI which is nil
// Its use is mostly to check if a property of an ActivityPub Item is nil
NilIRI IRI = "-"
// EmptyID represents a zero length ID
EmptyID = EmptyIRI
// NilID represents by convention an ID which is nil, see details of NilIRI
NilID = NilIRI
)
// ItemsEqual checks if it and with Items are equal
func ItemsEqual(it, with Item) bool {
if IsNil(it) || IsNil(with) {
return with == it
}
result := false
if it.IsCollection() {
if it.GetType() == CollectionOfItems {
_ = OnItemCollection(it, func(c *ItemCollection) error {
result = c.Equals(with)
return nil
})
}
if it.GetType() == CollectionType {
_ = OnCollection(it, func(c *Collection) error {
result = c.Equals(with)
return nil
})
}
if it.GetType() == OrderedCollectionType {
_ = OnOrderedCollection(it, func(c *OrderedCollection) error {
result = c.Equals(with)
return nil
})
}
if it.GetType() == CollectionPageType {
_ = OnCollectionPage(it, func(c *CollectionPage) error {
result = c.Equals(with)
return nil
})
}
if it.GetType() == OrderedCollectionPageType {
_ = OnOrderedCollectionPage(it, func(c *OrderedCollectionPage) error {
result = c.Equals(with)
return nil
})
}
} else if it.IsObject() {
if ActivityTypes.Contains(with.GetType()) {
_ = OnActivity(it, func(i *Activity) error {
result = i.Equals(with)
return nil
})
} else if ActorTypes.Contains(with.GetType()) {
_ = OnActor(it, func(i *Actor) error {
result = i.Equals(with)
return nil
})
} else {
_ = OnObject(it, func(i *Object) error {
result = i.Equals(with)
return nil
})
}
}
if with.IsLink() {
result = with.GetLink().Equals(it.GetLink(), false)
}
return result
}
// IsItemCollection returns if the current Item interface holds a Collection
func IsItemCollection(it Item) bool {
_, ok := it.(ItemCollection)
_, okP := it.(*ItemCollection)
return ok || okP
}
// IsIRI returns if the current Item interface holds an IRI
func IsIRI(it Item) bool {
_, okV := it.(IRI)
_, okP := it.(*IRI)
return okV || okP
}
// IsIRIs returns if the current Item interface holds an IRI slice
func IsIRIs(it Item) bool {
_, okV := it.(IRIs)
_, okP := it.(*IRIs)
return okV || okP
}
// IsLink returns if the current Item interface holds a Link
func IsLink(it Item) bool {
_, okV := it.(Link)
_, okP := it.(*Link)
return okV || okP
}
// IsObject returns if the current Item interface holds an Object
func IsObject(it Item) bool {
switch ob := it.(type) {
case Actor, *Actor,
Object, *Object, Profile, *Profile, Place, *Place, Relationship, *Relationship, Tombstone, *Tombstone,
Activity, *Activity, IntransitiveActivity, *IntransitiveActivity, Question, *Question,
Collection, *Collection, CollectionPage, *CollectionPage,
OrderedCollection, *OrderedCollection, OrderedCollectionPage, *OrderedCollectionPage:
return ob != nil
default:
return false
}
}
// IsNil checks if the object matching an ObjectOrLink interface is nil
func IsNil(it Item) bool {
if it == nil {
return true
}
// This is the default if the argument can't be cast to Object, as is the case for an ItemCollection
isNil := false
if IsItemCollection(it) {
OnItemCollection(it, func(c *ItemCollection) error {
isNil = c == nil
return nil
})
} else if IsObject(it) {
OnObject(it, func(o *Object) error {
isNil = o == nil
return nil
})
} else if IsLink(it) {
OnLink(it, func(l *Link) error {
isNil = l == nil
return nil
})
} else if IsIRI(it) {
isNil = len(it.GetLink()) == 0
} else {
// NOTE(marius): we're not dealing with a type that we know about, so we use slow reflection
// as we still care about the result
v := reflect.ValueOf(it)
isNil = v.Kind() == reflect.Pointer && v.IsNil()
}
return isNil
}
func ErrorInvalidType[T Objects | Links | IRIs](received Item) error {
return fmt.Errorf("unable to convert %T to %T", received, new(T))
}

290
vendor/github.com/go-ap/activitypub/item_collection.go generated vendored Normal file
View File

@ -0,0 +1,290 @@
package activitypub
import (
"reflect"
"sort"
)
// ItemCollection represents an array of items
type ItemCollection []Item
// GetID returns the ID corresponding to ItemCollection
func (i ItemCollection) GetID() ID {
return EmptyID
}
// GetLink returns the empty IRI
func (i ItemCollection) GetLink() IRI {
return EmptyIRI
}
// GetType returns the ItemCollection's type
func (i ItemCollection) GetType() ActivityVocabularyType {
return CollectionOfItems
}
// IsLink returns false for an ItemCollection object
func (i ItemCollection) IsLink() bool {
return false
}
// IsObject returns true for a ItemCollection object
func (i ItemCollection) IsObject() bool {
return false
}
// MarshalJSON encodes the receiver object to a JSON document.
func (i ItemCollection) MarshalJSON() ([]byte, error) {
if i == nil {
return nil, nil
}
b := make([]byte, 0)
JSONWriteItemCollectionValue(&b, i, true)
return b, nil
}
// Append facilitates adding elements to Item arrays
// and ensures ItemCollection implements the Collection interface
func (i *ItemCollection) Append(it ...Item) error {
for _, ob := range it {
if i.Contains(ob) {
continue
}
*i = append(*i, ob)
}
return nil
}
// Count returns the length of Items in the item collection
func (i *ItemCollection) Count() uint {
if i == nil {
return 0
}
return uint(len(*i))
}
// First returns the ID corresponding to ItemCollection
func (i ItemCollection) First() Item {
if len(i) == 0 {
return nil
}
return i[0]
}
// Normalize returns the first item if the collection contains only one,
// the full collection if the collection contains more than one item,
// or nil
func (i ItemCollection) Normalize() Item {
if len(i) == 0 {
return nil
}
if len(i) == 1 {
return i[0]
}
return i
}
// Collection returns the current object as collection interface
func (i *ItemCollection) Collection() ItemCollection {
return *i
}
// IsCollection returns true for ItemCollection arrays
func (i ItemCollection) IsCollection() bool {
return true
}
// Contains verifies if IRIs array contains the received one
func (i ItemCollection) Contains(r Item) bool {
if len(i) == 0 {
return false
}
for _, it := range i {
if ItemsEqual(it, r) {
return true
}
}
return false
}
// Remove removes the r Item from the i ItemCollection if it contains it
func (i *ItemCollection) Remove(r Item) {
li := len(*i)
if li == 0 {
return
}
if r == nil {
return
}
remIdx := -1
for idx, it := range *i {
if ItemsEqual(it, r) {
remIdx = idx
}
}
if remIdx == -1 {
return
}
if remIdx < li-1 {
*i = append((*i)[:remIdx], (*i)[remIdx+1:]...)
} else {
*i = (*i)[:remIdx]
}
}
// ItemCollectionDeduplication normalizes the received arguments lists into a single unified one
func ItemCollectionDeduplication(recCols ...*ItemCollection) ItemCollection {
rec := make(ItemCollection, 0)
for _, recCol := range recCols {
if recCol == nil {
continue
}
toRemove := make([]int, 0)
for i, cur := range *recCol {
save := true
if cur == nil {
continue
}
var testIt IRI
if cur.IsObject() {
testIt = cur.GetID()
} else if cur.IsLink() {
testIt = cur.GetLink()
} else {
continue
}
for _, it := range rec {
if testIt.Equals(it.GetID(), false) {
// mark the element for removal
toRemove = append(toRemove, i)
save = false
}
}
if save {
rec = append(rec, testIt)
}
}
sort.Sort(sort.Reverse(sort.IntSlice(toRemove)))
for _, idx := range toRemove {
*recCol = append((*recCol)[:idx], (*recCol)[idx+1:]...)
}
}
return rec
}
// ToItemCollection
func ToItemCollection(it Item) (*ItemCollection, error) {
switch i := it.(type) {
case *ItemCollection:
return i, nil
case ItemCollection:
return &i, nil
case *OrderedCollection:
return &i.OrderedItems, nil
case *OrderedCollectionPage:
return &i.OrderedItems, nil
case *Collection:
return &i.Items, nil
case *CollectionPage:
return &i.Items, nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(ItemCollection))
if reflect.TypeOf(it).ConvertibleTo(typ) {
if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*ItemCollection); ok {
return i, nil
}
}
}
return nil, ErrorInvalidType[ItemCollection](it)
}
// ToIRIs
func ToIRIs(it Item) (*IRIs, error) {
switch i := it.(type) {
case *IRIs:
return i, nil
case IRIs:
return &i, nil
case ItemCollection:
iris := make(IRIs, len(i))
for j, ob := range i {
iris[j] = ob.GetLink()
}
return &iris, nil
case *ItemCollection:
iris := make(IRIs, len(*i))
for j, ob := range *i {
iris[j] = ob.GetLink()
}
return &iris, nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(IRIs))
if reflect.TypeOf(it).ConvertibleTo(typ) {
if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*IRIs); ok {
return i, nil
}
}
}
return nil, ErrorInvalidType[IRIs](it)
}
// ItemsMatch
func (i ItemCollection) ItemsMatch(col ...Item) bool {
for _, it := range col {
if match := i.Contains(it); !match {
return false
}
}
return true
}
// Equals
func (i ItemCollection) Equals(with Item) bool {
if IsNil(with) {
return false
}
if !with.IsCollection() {
return false
}
if with.GetType() != CollectionOfItems {
return false
}
result := true
OnItemCollection(with, func(w *ItemCollection) error {
if w.Count() != i.Count() {
result = false
return nil
}
for _, it := range i {
if !w.Contains(it.GetLink()) {
result = false
return nil
}
}
return nil
})
return result
}
// Clean removes Bto and BCC properties on all the members of the collection
func (i ItemCollection) Clean() {
for j, it := range i {
i[j] = CleanRecipients(it)
}
}
func (i ItemCollection) Recipients() ItemCollection {
all := make(ItemCollection, 0)
for _, it := range i {
_ = OnObject(it, func(ob *Object) error {
_ = all.Append(ItemCollectionDeduplication(&ob.To, &ob.Bto, &ob.CC, &ob.BCC, &ob.Audience)...)
return nil
})
}
return ItemCollectionDeduplication(&all)
}

165
vendor/github.com/go-ap/activitypub/link.go generated vendored Normal file
View File

@ -0,0 +1,165 @@
package activitypub
import (
"bytes"
"encoding/gob"
"fmt"
"github.com/valyala/fastjson"
)
// LinkTypes represent the valid values for a Link object
var LinkTypes = ActivityVocabularyTypes{
LinkType,
MentionType,
}
type Links interface {
Link | IRI
}
// A Link is an indirect, qualified reference to a resource identified by a URL.
// The fundamental model for links is established by [ RFC5988].
// Many of the properties defined by the Activity Vocabulary allow values that are either instances of APObject or Link.
// When a Link is used, it establishes a qualified relation connecting the subject
// (the containing object) to the resource identified by the href.
// Properties of the Link are properties of the reference as opposed to properties of the resource.
type Link struct {
// Provides the globally unique identifier for an APObject or Link.
ID ID `jsonld:"id,omitempty"`
// Identifies the APObject or Link type. Multiple values may be specified.
Type ActivityVocabularyType `jsonld:"type,omitempty"`
// A simple, human-readable, plain-text name for the object.
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
// A link relation associated with a Link. The value must conform to both the [HTML5] and
// [RFC5988](https://tools.ietf.org/html/rfc5988) "link relation" definitions.
// In the [HTML5], any string not containing the "space" U+0020, "tab" (U+0009), "LF" (U+000A),
// "FF" (U+000C), "CR" (U+000D) or "," (U+002C) characters can be used as a valid link relation.
Rel IRI `jsonld:"rel,omitempty"`
// When used on a Link, identifies the MIME media type of the referenced resource.
MediaType MimeType `jsonld:"mediaType,omitempty"`
// On a Link, specifies a hint as to the rendering height in device-independent pixels of the linked resource.
Height uint `jsonld:"height,omitempty"`
// On a Link, specifies a hint as to the rendering width in device-independent pixels of the linked resource.
Width uint `jsonld:"width,omitempty"`
// Identifies an entity that provides a preview of this object.
Preview Item `jsonld:"preview,omitempty"`
// The target resource pointed to by a Link.
Href IRI `jsonld:"href,omitempty"`
// Hints as to the language used by the target resource.
// Value must be a [BCP47](https://tools.ietf.org/html/bcp47) Language-Tag.
HrefLang LangRef `jsonld:"hrefLang,omitempty"`
}
// Mention is a specialized Link that represents an @mention.
type Mention = Link
// LinkNew initializes a new Link
func LinkNew(id ID, typ ActivityVocabularyType) *Link {
if !LinkTypes.Contains(typ) {
typ = LinkType
}
return &Link{ID: id, Type: typ}
}
// MentionNew initializes a new Mention
func MentionNew(id ID) *Mention {
return &Mention{ID: id, Type: MentionType}
}
// IsLink validates if current Link is a Link
func (l Link) IsLink() bool {
return l.Type == LinkType || LinkTypes.Contains(l.Type)
}
// IsObject validates if current Link is an GetID
func (l Link) IsObject() bool {
return l.Type == ObjectType || ObjectTypes.Contains(l.Type)
}
// IsCollection returns false for Link objects
func (l Link) IsCollection() bool {
return false
}
// GetID returns the ID corresponding to the Link object
func (l Link) GetID() ID {
return l.ID
}
// GetLink returns the IRI corresponding to the current Link
func (l Link) GetLink() IRI {
return IRI(l.ID)
}
// GetType returns the Type corresponding to the Mention object
func (l Link) GetType() ActivityVocabularyType {
return l.Type
}
// MarshalJSON encodes the receiver object to a JSON document.
func (l Link) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
JSONWrite(&b, '{')
if JSONWriteLinkValue(&b, l) {
JSONWrite(&b, '}')
return b, nil
}
return nil, nil
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (l *Link) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadLink(val, l)
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (l *Link) UnmarshalBinary(data []byte) error {
return l.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (l Link) MarshalBinary() ([]byte, error) {
return l.GobEncode()
}
func (l Link) GobEncode() ([]byte, error) {
mm := make(map[string][]byte)
hasData, err := mapLinkProperties(mm, l)
if err != nil {
return nil, err
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
func (l *Link) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm, err := gobDecodeObjectAsMap(data)
if err != nil {
return err
}
return unmapLinkProperties(mm, l)
}
func (l Link) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'v':
_, _ = fmt.Fprintf(s, "%T[%s] { }", l, l.Type)
}
}

View File

@ -0,0 +1,812 @@
package activitypub
import (
"bytes"
"encoding/gob"
"fmt"
"io"
"strings"
"unicode/utf8"
"github.com/valyala/fastjson"
)
// NilLangRef represents a convention for a nil language reference.
// It is used for LangRefValue objects without an explicit language key.
const NilLangRef LangRef = "-"
// DefaultLang represents the default language reference used when using the convenience content generation.
var DefaultLang = NilLangRef
type (
// LangRef is the type for a language reference code, should be an ISO639-1 language specifier.
LangRef string
Content []byte
// LangRefValue is a type for storing per language values
LangRefValue struct {
Ref LangRef
Value Content
}
// NaturalLanguageValues is a mapping for multiple language values
NaturalLanguageValues []LangRefValue
)
func NaturalLanguageValuesNew(values ...LangRefValue) NaturalLanguageValues {
n := make(NaturalLanguageValues, len(values))
for i, val := range values {
n[i] = val
}
return n
}
func DefaultNaturalLanguageValue(content string) NaturalLanguageValues {
return NaturalLanguageValuesNew(DefaultLangRef(content))
}
func (n NaturalLanguageValues) String() string {
cnt := len(n)
if cnt == 1 {
return n[0].String()
}
s := strings.Builder{}
s.Write([]byte{'['})
for k, v := range n {
s.WriteString(v.String())
if k != cnt-1 {
s.Write([]byte{','})
}
}
s.Write([]byte{']'})
return s.String()
}
func (n NaturalLanguageValues) Get(ref LangRef) Content {
for _, val := range n {
if val.Ref == ref {
return val.Value
}
}
return nil
}
// Set sets a language, value pair in a NaturalLanguageValues array
func (n *NaturalLanguageValues) Set(ref LangRef, v Content) error {
found := false
for k, vv := range *n {
if vv.Ref == ref {
(*n)[k] = LangRefValue{ref, v}
found = true
}
}
if !found {
n.Append(ref, v)
}
return nil
}
func (n *NaturalLanguageValues) Add(ref LangRefValue) {
*n = append(*n, ref)
}
var hex = "0123456789abcdef"
// safeSet holds the value true if the ASCII character with the given array
// position can be represented inside a JSON string without any further
// escaping.
//
// All values are true except for the ASCII control characters (0-31), the
// double quote ("), and the backslash character ("\").
var safeSet = [utf8.RuneSelf]bool{
' ': true,
'!': true,
'"': false,
'#': true,
'$': true,
'%': true,
'&': true,
'\'': true,
'(': true,
')': true,
'*': true,
'+': true,
',': true,
'-': true,
'.': true,
'/': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
':': true,
';': true,
'<': true,
'=': true,
'>': true,
'?': true,
'@': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'V': true,
'W': true,
'X': true,
'Y': true,
'Z': true,
'[': true,
'\\': false,
']': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'{': true,
'|': true,
'}': true,
'~': true,
'\u007f': true,
}
// htmlSafeSet holds the value true if the ASCII character with the given
// array position can be safely represented inside a JSON string, embedded
// inside of HTML <script> tags, without any additional escaping.
//
// All values are true except for the ASCII control characters (0-31), the
// double quote ("), the backslash character ("\"), HTML opening and closing
// tags ("<" and ">"), and the ampersand ("&").
var htmlSafeSet = [utf8.RuneSelf]bool{
' ': true,
'!': true,
'"': false,
'#': true,
'$': true,
'%': true,
'&': false,
'\'': true,
'(': true,
')': true,
'*': true,
'+': true,
',': true,
'-': true,
'.': true,
'/': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
':': true,
';': true,
'<': false,
'=': true,
'>': false,
'?': true,
'@': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'V': true,
'W': true,
'X': true,
'Y': true,
'Z': true,
'[': true,
'\\': false,
']': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'{': true,
'|': true,
'}': true,
'~': true,
'\u007f': true,
}
// NOTE: keep in sync with string above.
func stringBytes(e *bytes.Buffer, s []byte, escapeHTML bool) {
e.WriteRune('"')
start := 0
for i := 0; i < len(s); {
if b := s[i]; b < utf8.RuneSelf {
if htmlSafeSet[b] || (!escapeHTML && safeSet[b]) {
i++
continue
}
if start < i {
e.Write(s[start:i])
}
e.WriteRune('\\')
switch b {
case '\\', '"':
e.WriteRune(rune(b))
case '\n':
e.WriteRune('n')
case '\r':
e.WriteRune('r')
case '\t':
e.WriteRune('t')
default:
// This encodes bytes < 0x20 except for \t, \n and \r.
// If escapeHTML is set, it also escapes <, >, and &
// because they can lead to security holes when
// user-controlled strings are rendered into JSON
// and served to some browsers.
e.WriteString(`u00`)
e.WriteByte(hex[b>>4])
e.WriteByte(hex[b&0xF])
}
i++
start = i
continue
}
c, size := utf8.DecodeRune(s[i:])
if c == utf8.RuneError && size == 1 {
if start < i {
e.Write(s[start:i])
}
e.WriteString(`\ufffd`)
i += size
start = i
continue
}
// U+2028 is LINE SEPARATOR.
// U+2029 is PARAGRAPH SEPARATOR.
// They are both technically valid characters in JSON strings,
// but don't work in JSONP, which has to be evaluated as JavaScript,
// and can lead to security holes there. It is valid JSON to
// escape them, so we do so unconditionally.
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
if c == '\u2028' || c == '\u2029' {
if start < i {
e.Write(s[start:i])
}
e.WriteString(`\u202`)
e.WriteByte(hex[c&0xF])
i += size
start = i
continue
}
i += size
}
if start < len(s) {
e.Write(s[start:])
}
e.WriteByte('"')
}
// MarshalJSON encodes the receiver object to a JSON document.
func (n NaturalLanguageValues) MarshalJSON() ([]byte, error) {
l := len(n)
if l <= 0 {
return nil, nil
}
b := bytes.Buffer{}
if l == 1 {
v := n[0]
if len(v.Value) > 0 {
v.Value = unescape(v.Value)
stringBytes(&b, v.Value, false)
return b.Bytes(), nil
}
}
b.Write([]byte{'{'})
empty := true
for _, val := range n {
if len(val.Ref) == 0 || len(val.Value) == 0 {
continue
}
if !empty {
b.Write([]byte{','})
}
if v, err := val.MarshalJSON(); err == nil && len(v) > 0 {
l, err := b.Write(v)
if err == nil && l > 0 {
empty = false
}
}
}
b.Write([]byte{'}'})
if !empty {
return b.Bytes(), nil
}
return nil, nil
}
// First returns the first element in the array
func (n NaturalLanguageValues) First() LangRefValue {
for _, v := range n {
return v
}
return LangRefValue{}
}
// MarshalText serializes the NaturalLanguageValues into Text
func (n NaturalLanguageValues) MarshalText() ([]byte, error) {
for _, v := range n {
return []byte(fmt.Sprintf("%q", v)), nil
}
return nil, nil
}
func (n NaturalLanguageValues) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'q':
_, _ = io.WriteString(s, "[")
for _, nn := range n {
nn.Format(s, verb)
}
_, _ = io.WriteString(s, "]")
case 'v':
_, _ = io.WriteString(s, "[")
for _, nn := range n {
nn.Format(s, verb)
}
_, _ = io.WriteString(s, "]")
}
}
// Append is syntactic sugar for resizing the NaturalLanguageValues map
// and appending an element
func (n *NaturalLanguageValues) Append(lang LangRef, value Content) error {
*n = append(*n, LangRefValue{lang, value})
return nil
}
// Count returns the length of Items in the item collection
func (n *NaturalLanguageValues) Count() uint {
if n == nil {
return 0
}
return uint(len(*n))
}
// String adds support for Stringer interface. It returns the Value[LangRef] text or just Value if LangRef is NIL
func (l LangRefValue) String() string {
if l.Ref == NilLangRef {
return l.Value.String()
}
return fmt.Sprintf("%s[%s]", l.Value, l.Ref)
}
func DefaultLangRef(value string) LangRefValue {
return LangRefValue{Ref: DefaultLang, Value: Content(value)}
}
func LangRefValueNew(lang LangRef, value string) LangRefValue {
return LangRefValue{Ref: lang, Value: Content(value)}
}
func (l LangRefValue) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'q':
if l.Ref == NilLangRef {
_, _ = io.WriteString(s, string(l.Value))
} else {
_, _ = fmt.Fprintf(s, "%q[%s]", l.Value, l.Ref)
}
case 'v':
if l.Ref == NilLangRef {
_, _ = fmt.Fprintf(s, "%q", string(l.Value))
} else {
_, _ = fmt.Fprintf(s, "%q[%s]", string(l.Value), l.Ref)
}
}
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (l *LangRefValue) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
l.Ref = NilLangRef
l.Value = unescape(data)
return nil
}
switch val.Type() {
case fastjson.TypeObject:
o, _ := val.Object()
o.Visit(func(key []byte, v *fastjson.Value) {
l.Ref = LangRef(key)
l.Value = unescape(v.GetStringBytes())
})
case fastjson.TypeString:
l.Ref = NilLangRef
l.Value = unescape(val.GetStringBytes())
}
return nil
}
// UnmarshalText implements the TextEncoder interface
func (l *LangRefValue) UnmarshalText(data []byte) error {
l.Ref = NilLangRef
l.Value = unescape(data)
return nil
}
func (l LangRef) GobEncode() ([]byte, error) {
if len(l) == 0 {
return []byte{}, nil
}
b := new(bytes.Buffer)
gg := gob.NewEncoder(b)
if err := gobEncodeStringLikeType(gg, []byte(l)); err != nil {
return nil, err
}
return b.Bytes(), nil
}
func (l *LangRef) GobDecode(data []byte) error {
if len(data) == 0 {
// NOTE(marius): this behaviour diverges from vanilla gob package
return nil
}
var bb []byte
if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&bb); err != nil {
return err
}
*l = LangRef(bb)
return nil
}
// MarshalJSON encodes the receiver object to a JSON document.
func (l LangRefValue) MarshalJSON() ([]byte, error) {
buf := bytes.Buffer{}
if l.Ref != NilLangRef && len(l.Ref) > 0 {
if l.Value.Equals(Content("")) {
return nil, nil
}
stringBytes(&buf, []byte(l.Ref), false)
buf.Write([]byte{':'})
}
stringBytes(&buf, l.Value, false)
return buf.Bytes(), nil
}
// MarshalText serializes the LangRefValue into JSON
func (l LangRefValue) MarshalText() ([]byte, error) {
if l.Ref != NilLangRef && l.Value.Equals(Content("")) {
return nil, nil
}
buf := bytes.Buffer{}
buf.WriteString(l.Value.String())
if l.Ref != NilLangRef {
buf.WriteByte('[')
buf.WriteString(l.Ref.String())
buf.WriteByte(']')
}
return buf.Bytes(), nil
}
type kv struct {
K []byte
V []byte
}
func (l LangRefValue) GobEncode() ([]byte, error) {
if len(l.Value) == 0 && len(l.Ref) == 0 {
return []byte{}, nil
}
b := new(bytes.Buffer)
gg := gob.NewEncoder(b)
mm := kv{
K: []byte(l.Ref),
V: []byte(l.Value),
}
if err := gg.Encode(mm); err != nil {
return nil, err
}
return b.Bytes(), nil
}
func (l *LangRefValue) GobDecode(data []byte) error {
if len(data) == 0 {
// NOTE(marius): this behaviour diverges from vanilla gob package
return nil
}
mm := kv{}
if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&mm); err != nil {
return err
}
l.Ref = LangRef(mm.K)
l.Value = mm.V
return nil
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (l *LangRef) UnmarshalJSON(data []byte) error {
return l.UnmarshalText(data)
}
// UnmarshalText implements the TextEncoder interface
func (l *LangRef) UnmarshalText(data []byte) error {
*l = ""
if len(data) == 0 {
return nil
}
if len(data) > 2 {
if data[0] == '"' && data[len(data)-1] == '"' {
*l = LangRef(data[1 : len(data)-1])
}
} else {
*l = LangRef(data)
}
return nil
}
func (l LangRef) String() string {
return string(l)
}
func (l LangRefValue) Equals(other LangRefValue) bool {
return l.Ref == other.Ref && l.Value.Equals(other.Value)
}
func (c *Content) UnmarshalJSON(data []byte) error {
return c.UnmarshalText(data)
}
func (c *Content) UnmarshalText(data []byte) error {
*c = Content{}
if len(data) == 0 {
return nil
}
if len(data) > 2 {
if data[0] == '"' && data[len(data)-1] == '"' {
*c = Content(data[1 : len(data)-1])
}
} else {
*c = Content(data)
}
return nil
}
func (c Content) GobEncode() ([]byte, error) {
if len(c) == 0 {
return []byte{}, nil
}
b := new(bytes.Buffer)
gg := gob.NewEncoder(b)
if err := gobEncodeStringLikeType(gg, c); err != nil {
return nil, err
}
return b.Bytes(), nil
}
func (c *Content) GobDecode(data []byte) error {
if len(data) == 0 {
// NOTE(marius): this behaviour diverges from vanilla gob package
return nil
}
bb := make([]byte, 0)
if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&bb); err != nil {
return err
}
*c = bb
return nil
}
func (c Content) String() string {
return string(c)
}
func (c Content) Equals(other Content) bool {
return bytes.Equal(c, other)
}
func (c Content) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'q':
_, _ = io.WriteString(s, string(c))
case 'v':
_, _ = fmt.Fprintf(s, "%q", string(c))
}
}
func unescape(b []byte) []byte {
// FIXME(marius): I feel like I'm missing something really obvious about encoding/decoding from Json regarding
// escape characters, and that this function is just a hack. Be better future Marius, find the real problem!
b = bytes.ReplaceAll(b, []byte{'\\', 'a'}, []byte{'\a'})
b = bytes.ReplaceAll(b, []byte{'\\', 'f'}, []byte{'\f'})
b = bytes.ReplaceAll(b, []byte{'\\', 'n'}, []byte{'\n'})
b = bytes.ReplaceAll(b, []byte{'\\', 'r'}, []byte{'\r'})
b = bytes.ReplaceAll(b, []byte{'\\', 't'}, []byte{'\t'})
b = bytes.ReplaceAll(b, []byte{'\\', 'v'}, []byte{'\v'})
b = bytes.ReplaceAll(b, []byte{'\\', '"'}, []byte{'"'})
b = bytes.ReplaceAll(b, []byte{'\\', '\\'}, []byte{'\\'}) // this should cover the case of \\u -> \u
return b
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (n *NaturalLanguageValues) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
// try our luck if data contains an unquoted string
n.Append(NilLangRef, unescape(data))
return nil
}
switch val.Type() {
case fastjson.TypeObject:
ob, _ := val.Object()
ob.Visit(func(key []byte, v *fastjson.Value) {
if dat := v.GetStringBytes(); len(dat) > 0 {
n.Append(LangRef(key), unescape(dat))
}
})
case fastjson.TypeString:
if dat := val.GetStringBytes(); len(dat) > 0 {
n.Append(NilLangRef, unescape(dat))
}
case fastjson.TypeArray:
for _, v := range val.GetArray() {
l := LangRefValue{}
l.UnmarshalJSON([]byte(v.String()))
if len(l.Value) > 0 {
n.Append(l.Ref, l.Value)
}
}
}
return nil
}
// UnmarshalText tries to load the NaturalLanguage array from the incoming Text value
func (n *NaturalLanguageValues) UnmarshalText(data []byte) error {
if data[0] == '"' {
// a quoted string - loading it to c.URL
if data[len(data)-1] != '"' {
return fmt.Errorf("invalid string value when unmarshaling %T value", n)
}
n.Append(LangRef(NilLangRef), Content(data[1:len(data)-1]))
}
return nil
}
func (n NaturalLanguageValues) GobEncode() ([]byte, error) {
if len(n) == 0 {
return []byte{}, nil
}
b := new(bytes.Buffer)
gg := gob.NewEncoder(b)
mm := make([]kv, len(n))
for i, l := range n {
mm[i] = kv{K: []byte(l.Ref), V: l.Value}
}
if err := gg.Encode(mm); err != nil {
return nil, err
}
return b.Bytes(), nil
}
func (n *NaturalLanguageValues) GobDecode(data []byte) error {
if len(data) == 0 {
// NOTE(marius): this behaviour diverges from vanilla gob package
return nil
}
mm := make([]kv, 0)
if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&mm); err != nil {
return err
}
for _, m := range mm {
*n = append(*n, LangRefValue{Ref: LangRef(m.K), Value: m.V})
}
return nil
}
// Equals
func (n NaturalLanguageValues) Equals(with NaturalLanguageValues) bool {
if n.Count() != with.Count() {
return false
}
for _, wv := range with {
for _, nv := range n {
if nv.Equals(wv) {
continue
}
return false
}
}
return true
}

999
vendor/github.com/go-ap/activitypub/object.go generated vendored Normal file
View File

@ -0,0 +1,999 @@
package activitypub
import (
"bytes"
"encoding/gob"
"fmt"
"io"
"reflect"
"strings"
"time"
"unsafe"
"github.com/valyala/fastjson"
)
const (
IRIType ActivityVocabularyType = "IRI"
ObjectType ActivityVocabularyType = "Object"
LinkType ActivityVocabularyType = "Link"
ActivityType ActivityVocabularyType = "Activity"
IntransitiveActivityType ActivityVocabularyType = "IntransitiveActivity"
ActorType ActivityVocabularyType = "Actor"
CollectionType ActivityVocabularyType = "Collection"
OrderedCollectionType ActivityVocabularyType = "OrderedCollection"
CollectionPageType ActivityVocabularyType = "CollectionPage"
OrderedCollectionPageType ActivityVocabularyType = "OrderedCollectionPage"
// ActivityPub Object Types
ArticleType ActivityVocabularyType = "Article"
AudioType ActivityVocabularyType = "Audio"
DocumentType ActivityVocabularyType = "Document"
EventType ActivityVocabularyType = "Event"
ImageType ActivityVocabularyType = "Image"
NoteType ActivityVocabularyType = "Note"
PageType ActivityVocabularyType = "Page"
PlaceType ActivityVocabularyType = "Place"
ProfileType ActivityVocabularyType = "Profile"
RelationshipType ActivityVocabularyType = "Relationship"
TombstoneType ActivityVocabularyType = "Tombstone"
VideoType ActivityVocabularyType = "Video"
// MentionType is a link type for @mentions
MentionType ActivityVocabularyType = "Mention"
)
var GenericTypes = ActivityVocabularyTypes{
ActivityType,
IntransitiveActivityType,
ObjectType,
ActorType,
}
var ObjectTypes = ActivityVocabularyTypes{
ArticleType,
AudioType,
DocumentType,
EventType,
ImageType,
NoteType,
PageType,
PlaceType,
ProfileType,
RelationshipType,
TombstoneType,
VideoType,
}
type (
// ActivityVocabularyType is the data type for an Activity type object
ActivityVocabularyType string
// ActivityObject is a subtype of Object that describes some form of action that may happen,
// is currently happening, or has already happened
ActivityObject interface {
// GetID returns the dereferenceable ActivityStreams object id
GetID() ID
// GetType returns the ActivityStreams type
GetType() ActivityVocabularyType
}
// LinkOrIRI is an interface that Object and Link structs implement, and at the same time
// they are kept disjointed
LinkOrIRI interface {
// GetLink returns the object id in IRI type
GetLink() IRI
}
// ObjectOrLink describes the requirements of an ActivityStreams object
ObjectOrLink interface {
ActivityObject
LinkOrIRI
// IsLink shows if current item represents a Link object or an IRI
IsLink() bool
// IsObject shows if current item represents an ActivityStreams object
IsObject() bool
// IsCollection shows if the current item represents an ItemCollection
IsCollection() bool
}
// Mapper interface allows external objects to implement their own mechanism for loading information
// from an ActivityStreams vocabulary object
Mapper interface {
// FromActivityStreams maps an ActivityStreams object to another struct representation
FromActivityStreams(Item) error
}
// MimeType is the type for representing MIME types in certain ActivityStreams properties
MimeType string
)
func (a ActivityVocabularyType) MarshalJSON() ([]byte, error) {
if len(a) == 0 {
return nil, nil
}
b := make([]byte, 0)
JSONWriteStringValue(&b, string(a))
return b, nil
}
// GobEncode
func (a ActivityVocabularyType) GobEncode() ([]byte, error) {
return []byte(a), nil
}
// GobDecode
func (a *ActivityVocabularyType) GobDecode(data []byte) error {
*a = ActivityVocabularyType(data)
return nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (a *ActivityVocabularyType) UnmarshalBinary(data []byte) error {
return a.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (a ActivityVocabularyType) MarshalBinary() ([]byte, error) {
return a.GobEncode()
}
type Objects interface {
Object | Tombstone | Place | Profile | Relationship |
Actors |
Activities |
IntransitiveActivities |
Collections |
IRI
}
// Object describes an ActivityPub object of any kind.
// It serves as the base type for most of the other kinds of objects defined in the Activity
// Vocabulary, including other Core types such as Activity, IntransitiveActivity, Collection and OrderedCollection.
type Object struct {
// ID provides the globally unique identifier for anActivity Pub Object or Link.
ID ID `jsonld:"id,omitempty"`
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
Type ActivityVocabularyType `jsonld:"type,omitempty"`
// Name a simple, human-readable, plain-text name for the object.
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
// The intent is to provide a model that is at least semantically similar to attachments in email.
Attachment Item `jsonld:"attachment,omitempty"`
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
// For instance, an object might be attributed to the completion of another activity.
AttributedTo Item `jsonld:"attributedTo,omitempty"`
// Audience identifies one or more entities that represent the total population of entities
// for which the object can considered to be relevant.
Audience ItemCollection `jsonld:"audience,omitempty"`
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
// By default, the value of content is HTML.
// The mediaType property can be used in the object to indicate a different content type.
// (The content MAY be expressed using multiple language-tagged values.)
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
// Context identifies the context within which the object exists or an activity was performed.
// The notion of "context" used is intentionally vague.
// The intended function is to serve as a means of grouping objects and activities that share a
// common originating context or purpose. An example could be all activities relating to a common project or event.
Context Item `jsonld:"context,omitempty"`
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
// If not specified, the content property is assumed to contain text/html content.
MediaType MimeType `jsonld:"mediaType,omitempty"`
// EndTime the date and time describing the actual or expected ending time of the object.
// When used with an Activity object, for instance, the endTime property specifies the moment
// the activity concluded or is expected to conclude.
EndTime time.Time `jsonld:"endTime,omitempty"`
// Generator identifies the entity (e.g. an application) that generated the object.
Generator Item `jsonld:"generator,omitempty"`
// Icon indicates an entity that describes an icon for this object.
// The image should have an aspect ratio of one (horizontal) to one (vertical)
// and should be suitable for presentation at a small size.
Icon Item `jsonld:"icon,omitempty"`
// Image indicates an entity that describes an image for this object.
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
Image Item `jsonld:"image,omitempty"`
// InReplyTo indicates one or more entities for which this object is considered a response.
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
// Location indicates one or more physical or logical locations associated with the object.
Location Item `jsonld:"location,omitempty"`
// Preview identifies an entity that provides a preview of this object.
Preview Item `jsonld:"preview,omitempty"`
// Published the date and time at which the object was published
Published time.Time `jsonld:"published,omitempty"`
// Replies identifies a Collection containing objects considered to be responses to this object.
Replies Item `jsonld:"replies,omitempty"`
// StartTime the date and time describing the actual or expected starting time of the object.
// When used with an Activity object, for instance, the startTime property specifies
// the moment the activity began or is scheduled to begin.
StartTime time.Time `jsonld:"startTime,omitempty"`
// Summary a natural language summarization of the object encoded as HTML.
// *Multiple language tagged summaries may be provided.)
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
// The key difference between attachment and tag is that the former implies association by inclusion,
// while the latter implies associated by reference.
Tag ItemCollection `jsonld:"tag,omitempty"`
// Updated the date and time at which the object was updated
Updated time.Time `jsonld:"updated,omitempty"`
// URL identifies one or more links to representations of the object
URL Item `jsonld:"url,omitempty"`
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
To ItemCollection `jsonld:"to,omitempty"`
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
Bto ItemCollection `jsonld:"bto,omitempty"`
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
CC ItemCollection `jsonld:"cc,omitempty"`
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
BCC ItemCollection `jsonld:"bcc,omitempty"`
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
// the duration property indicates the object's approximate duration.
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
Duration time.Duration `jsonld:"duration,omitempty"`
// This is a list of all Like activities with this object as the object property, added as a side effect.
// The likes collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Likes Item `jsonld:"likes,omitempty"`
// This is a list of all Announce activities with this object as the object property, added as a side effect.
// The shares collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Shares Item `jsonld:"shares,omitempty"`
// Source property is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
// In general, clients do the conversion from source to content, not the other way around.
Source Source `jsonld:"source,omitempty"`
}
// ObjectNew initializes a new Object
func ObjectNew(typ ActivityVocabularyType) *Object {
if !(ObjectTypes.Contains(typ)) {
typ = ObjectType
}
o := Object{Type: typ}
o.Name = NaturalLanguageValuesNew()
o.Content = NaturalLanguageValuesNew()
return &o
}
// GetID returns the ID corresponding to the current Object
func (o Object) GetID() ID {
return o.ID
}
// GetLink returns the IRI corresponding to the current Object
func (o Object) GetLink() IRI {
return IRI(o.ID)
}
// GetType returns the type of the current Object
func (o Object) GetType() ActivityVocabularyType {
return o.Type
}
// IsLink validates if currentActivity Pub Object is a Link
func (o Object) IsLink() bool {
return false
}
// IsObject validates if currentActivity Pub Object is an Object
func (o Object) IsObject() bool {
return true
}
// IsCollection returns false for Object objects
func (o Object) IsCollection() bool {
return false
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (o *Object) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadObject(val, o)
}
// MarshalJSON encodes the receiver object to a JSON document.
func (o Object) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
JSONWrite(&b, '{')
if JSONWriteObjectValue(&b, o) {
JSONWrite(&b, '}')
return b, nil
}
return nil, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (o *Object) UnmarshalBinary(data []byte) error {
return o.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (o Object) MarshalBinary() ([]byte, error) {
return o.GobEncode()
}
// GobEncode
func (o Object) GobEncode() ([]byte, error) {
mm := make(map[string][]byte)
hasData, err := mapObjectProperties(mm, &o)
if err != nil {
return nil, err
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
// GobDecode
func (o *Object) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm, err := gobDecodeObjectAsMap(data)
if err != nil {
return err
}
return unmapObjectProperties(mm, o)
}
func fmtObjectProps(w io.Writer) func(*Object) error {
return func(o *Object) error {
if len(o.ID) > 0 {
if n, _ := fmt.Fprintf(w, "ID:%s", o.ID); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if len(o.Name) > 0 {
if n, _ := fmt.Fprintf(w, "%s: [%s]", "name", o.Name); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if len(o.Summary) > 0 {
if n, _ := fmt.Fprintf(w, "%s: [%s]", "summary", o.Summary); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if len(o.Content) > 0 {
if n, _ := fmt.Fprintf(w, "%s: [%s]", "content", o.Content); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.Attachment) {
if n, _ := fmt.Fprintf(w, "%s: %s", "attachment", o.Attachment); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.AttributedTo) {
if n, _ := fmt.Fprintf(w, "%s: %s", "attributedTo", o.AttributedTo); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.Audience) && o.Audience.Count() > 0 {
if n, _ := fmt.Fprintf(w, "%s: %s", "audience", o.Audience); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.Context) {
if n, _ := fmt.Fprintf(w, "%s: %s", "context", o.Context); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.Generator) {
if n, _ := fmt.Fprintf(w, "%s: %s", "generator", o.Generator); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.Icon) {
if n, _ := fmt.Fprintf(w, "%s: %s", "icon", o.Icon); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.Image) {
if n, _ := fmt.Fprintf(w, "%s: %s", "image", o.Image); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.InReplyTo) {
if n, _ := fmt.Fprintf(w, "%s: %s", "inReplyTo", o.InReplyTo); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.Location) {
if n, _ := fmt.Fprintf(w, "%s: %s", "location", o.Location); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.Preview) {
if n, _ := fmt.Fprintf(w, "%s: %s", "preview", o.Preview); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.Replies) {
if n, _ := fmt.Fprintf(w, "%s: %s", "replies", o.Replies); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.Tag) && o.Tag.Count() > 0 {
if n, _ := fmt.Fprintf(w, "%s: %s", "tag", o.Tag); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.URL) {
if n, _ := fmt.Fprintf(w, "%s: %s", "url", o.URL); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.To) && o.To.Count() > 0 {
if n, _ := fmt.Fprintf(w, "%s: %s", "to", o.To); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.Bto) && o.Bto.Count() > 0 {
if n, _ := fmt.Fprintf(w, "%s: %s", "bto", o.Bto); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.CC) && o.CC.Count() > 0 {
if n, _ := fmt.Fprintf(w, "%s: %s", "cc", o.CC); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.BCC) && o.BCC.Count() > 0 {
if n, _ := fmt.Fprintf(w, "%s: %s", "bcc", o.BCC); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !o.Published.IsZero() {
if n, _ := fmt.Fprintf(w, "%s: %s", "published", o.Published); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !o.Updated.IsZero() {
if n, _ := fmt.Fprintf(w, "%s: %s", "updated", o.Updated); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !o.StartTime.IsZero() {
if n, _ := fmt.Fprintf(w, "%s: %s", "startTime", o.StartTime); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !o.EndTime.IsZero() {
if n, _ := fmt.Fprintf(w, "%s: %s", "endTime", o.EndTime); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if o.Duration != 0 {
if n, _ := fmt.Fprintf(w, "%s: %s", "duration", o.Duration); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.Likes) {
if n, _ := fmt.Fprintf(w, "%s: %s", "likes", o.Likes); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
if !IsNil(o.Shares) {
if n, _ := fmt.Fprintf(w, "%s: %s", "shares", o.Shares); n > 0 {
_, _ = io.WriteString(w, ", ")
}
}
return nil
}
}
func (o Object) Format(s fmt.State, verb rune) {
switch verb {
case 's':
if o.Type != "" && o.ID != "" {
_, _ = fmt.Fprintf(s, "%T[%s]( %s )", o, o.Type, o.ID)
} else if o.ID != "" {
_, _ = fmt.Fprintf(s, "%T( %s )", o, o.ID)
} else {
_, _ = fmt.Fprintf(s, "%T[%p]", o, &o)
}
case 'v':
if o.Type != "" && o.ID != "" {
_, _ = fmt.Fprintf(s, "%T[%s] {", o, o.Type)
_ = fmtObjectProps(s)(&o)
_, _ = io.WriteString(s, " }")
} else if o.ID != "" {
_, _ = fmt.Fprintf(s, "%T { ", o)
_ = fmtObjectProps(s)(&o)
_, _ = io.WriteString(s, " }")
}
}
}
// Recipients performs recipient de-duplication on the Object's To, Bto, CC and BCC properties
func (o *Object) Recipients() ItemCollection {
var aud ItemCollection
return ItemCollectionDeduplication(&aud, &o.To, &o.Bto, &o.CC, &o.BCC, &o.Audience)
}
// Clean removes Bto and BCC properties
func (o *Object) Clean() {
o.BCC = o.BCC[:0]
o.Bto = o.Bto[:0]
CleanRecipients(o.Audience)
CleanRecipients(o.Attachment)
CleanRecipients(o.Icon)
CleanRecipients(o.Image)
CleanRecipients(o.Context)
CleanRecipients(o.Generator)
CleanRecipients(o.AttributedTo)
CleanRecipients(o.Preview)
CleanRecipients(o.Tag)
}
type (
// Article represents any kind of multi-paragraph written work.
Article = Object
// Audio represents an audio document of any kind.
Audio = Document
// Document represents a document of any kind.
Document = Object
// Event represents any kind of event.
Event = Object
// Image An image document of any kind
Image = Document
// Note represents a short written work typically less than a single paragraph in length.
Note = Object
// Page represents a Web Page.
Page = Document
// Video represents a video document of any kind
Video = Document
)
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (m *MimeType) UnmarshalJSON(data []byte) error {
*m = MimeType(strings.Trim(string(data), "\""))
return nil
}
// MarshalJSON encodes the receiver object to a JSON document.
func (m MimeType) MarshalJSON() ([]byte, error) {
if len(m) == 0 {
return nil, nil
}
b := make([]byte, 0)
JSONWriteStringValue(&b, string(m))
return b, nil
}
// GobEncode
func (m MimeType) GobEncode() ([]byte, error) {
if len(m) == 0 {
return []byte{}, nil
}
b := bytes.Buffer{}
gg := gob.NewEncoder(&b)
if err := gobEncodeStringLikeType(gg, []byte(m)); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// GobDecode
func (m *MimeType) GobDecode(data []byte) error {
if len(data) == 0 {
// NOTE(marius): this behaviour diverges from vanilla gob package
return nil
}
var bb []byte
if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&bb); err != nil {
return err
}
*m = MimeType(bb)
return nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (m *MimeType) UnmarshalBinary(data []byte) error {
return m.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (m MimeType) MarshalBinary() ([]byte, error) {
return m.GobEncode()
}
// ToLink returns a Link pointer to the data in the current Item
func ToLink(it LinkOrIRI) (*Link, error) {
switch i := it.(type) {
case *Link:
return i, nil
case Link:
return &i, nil
}
return nil, fmt.Errorf("unable to convert %T to %T", it, new(Link))
}
// ToObject returns an Object pointer to the data in the current Item
// It relies on the fact that all the types in this package have a data layout compatible with Object.
func ToObject(it Item) (*Object, error) {
switch i := it.(type) {
case *Object:
return i, nil
case Object:
return &i, nil
case *Place:
return (*Object)(unsafe.Pointer(i)), nil
case Place:
return (*Object)(unsafe.Pointer(&i)), nil
case *Profile:
return (*Object)(unsafe.Pointer(i)), nil
case Profile:
return (*Object)(unsafe.Pointer(&i)), nil
case *Relationship:
return (*Object)(unsafe.Pointer(i)), nil
case Relationship:
return (*Object)(unsafe.Pointer(&i)), nil
case *Tombstone:
return (*Object)(unsafe.Pointer(i)), nil
case Tombstone:
return (*Object)(unsafe.Pointer(&i)), nil
case *Actor:
return (*Object)(unsafe.Pointer(i)), nil
case Actor:
return (*Object)(unsafe.Pointer(&i)), nil
case *Activity:
return (*Object)(unsafe.Pointer(i)), nil
case Activity:
return (*Object)(unsafe.Pointer(&i)), nil
case *IntransitiveActivity:
return (*Object)(unsafe.Pointer(i)), nil
case IntransitiveActivity:
return (*Object)(unsafe.Pointer(&i)), nil
case *Question:
return (*Object)(unsafe.Pointer(i)), nil
case Question:
return (*Object)(unsafe.Pointer(&i)), nil
case *Collection:
return (*Object)(unsafe.Pointer(i)), nil
case Collection:
return (*Object)(unsafe.Pointer(&i)), nil
case *CollectionPage:
return (*Object)(unsafe.Pointer(i)), nil
case CollectionPage:
return (*Object)(unsafe.Pointer(&i)), nil
case *OrderedCollection:
return (*Object)(unsafe.Pointer(i)), nil
case OrderedCollection:
return (*Object)(unsafe.Pointer(&i)), nil
case *OrderedCollectionPage:
return (*Object)(unsafe.Pointer(i)), nil
case OrderedCollectionPage:
return (*Object)(unsafe.Pointer(&i)), nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(Object))
if reflect.TypeOf(it).ConvertibleTo(typ) {
if reflect.ValueOf(it).IsNil() {
return nil, nil
}
if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Object); ok {
return i, nil
}
}
}
return nil, ErrorInvalidType[Object](it)
}
// Source is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
type Source struct {
// Content
Content NaturalLanguageValues `jsonld:"content"`
// MediaType
MediaType MimeType `jsonld:"mediaType"`
}
// GetAPSource
func GetAPSource(val *fastjson.Value) Source {
s := Source{}
if val == nil {
return s
}
if contBytes := val.Get("source", "content").GetStringBytes(); len(contBytes) > 0 {
s.Content.UnmarshalJSON(contBytes)
}
if mimeBytes := val.Get("source", "mediaType").GetStringBytes(); len(mimeBytes) > 0 {
s.MediaType.UnmarshalJSON(mimeBytes)
}
return s
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (s *Source) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
*s = GetAPSource(val)
return nil
}
// MarshalJSON encodes the receiver object to a JSON document.
func (s Source) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
empty := true
JSONWrite(&b, '{')
if len(s.MediaType) > 0 {
if v, err := s.MediaType.MarshalJSON(); err == nil && len(v) > 0 {
empty = !JSONWriteProp(&b, "mediaType", v)
}
}
if len(s.Content) > 0 {
empty = !JSONWriteNaturalLanguageProp(&b, "content", s.Content)
}
if !empty {
JSONWrite(&b, '}')
return b, nil
}
return nil, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (s *Source) UnmarshalBinary(data []byte) error {
return s.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (s Source) MarshalBinary() ([]byte, error) {
return s.GobEncode()
}
// GobDecode
func (s *Source) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm := make(map[string][]byte)
g := gob.NewDecoder(bytes.NewReader(data))
if err := g.Decode(&mm); err != nil {
return err
}
if raw, ok := mm["mediaType"]; ok {
if err := s.MediaType.GobDecode(raw); err != nil {
return err
}
}
if raw, ok := mm["content"]; ok {
if err := s.Content.GobDecode(raw); err != nil {
return err
}
}
return nil
}
// GobEncode
func (s Source) GobEncode() ([]byte, error) {
var (
mm = make(map[string][]byte)
err error
hasData bool
)
if len(s.MediaType) > 0 {
if mm["mediaType"], err = s.MediaType.GobEncode(); err != nil {
return nil, err
}
hasData = true
}
if len(s.Content) > 0 {
if mm["content"], err = s.Content.GobEncode(); err != nil {
return nil, err
}
hasData = true
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
// Equals verifies if our receiver Object is equals with the "with" Object
func (o Object) Equals(with Item) bool {
if IsItemCollection(with) {
return false
}
if withID := with.GetID(); !o.ID.Equals(withID, true) {
return false
}
if withType := with.GetType(); !strings.EqualFold(string(o.Type), string(withType)) {
return false
}
if with.IsLink() && !with.GetLink().Equals(o.GetLink(), false) {
return false
}
result := true
err := OnObject(with, func(w *Object) error {
if len(w.Name) > 0 {
if !w.Name.Equals(o.Name) {
result = false
return nil
}
}
if len(w.Summary) > 0 {
if !w.Summary.Equals(o.Summary) {
result = false
return nil
}
}
if len(w.Content) > 0 {
if !w.Content.Equals(o.Content) {
result = false
return nil
}
}
if w.Attachment != nil {
if !ItemsEqual(o.Attachment, w.Attachment) {
result = false
return nil
}
}
if w.AttributedTo != nil {
if !ItemsEqual(o.AttributedTo, w.AttributedTo) {
result = false
return nil
}
}
if w.Audience != nil {
if !ItemsEqual(o.Audience, w.Audience) {
result = false
return nil
}
}
if w.Context != nil {
if !ItemsEqual(o.Context, w.Context) {
result = false
return nil
}
}
if w.Generator != nil {
if !ItemsEqual(o.Generator, w.Generator) {
result = false
return nil
}
}
if w.Icon != nil {
if !ItemsEqual(o.Icon, w.Icon) {
result = false
return nil
}
}
if w.Image != nil {
if !ItemsEqual(o.Image, w.Image) {
result = false
return nil
}
}
if w.InReplyTo != nil {
if !ItemsEqual(o.InReplyTo, w.InReplyTo) {
result = false
return nil
}
}
if w.Location != nil {
if !ItemsEqual(o.Location, w.Location) {
result = false
return nil
}
}
if w.Preview != nil {
if !ItemsEqual(o.Preview, w.Preview) {
result = false
return nil
}
}
if w.Replies != nil {
if !ItemsEqual(o.Replies, w.Replies) {
result = false
return nil
}
}
if w.Tag != nil {
if !ItemsEqual(o.Tag, w.Tag) {
result = false
return nil
}
}
if w.URL != nil {
if o.URL == nil {
result = false
return nil
}
if !w.URL.GetLink().Equals(o.URL.GetLink(), false) {
result = false
return nil
}
}
if w.To != nil {
if !ItemsEqual(o.To, w.To) {
result = false
return nil
}
}
if w.Bto != nil {
if !ItemsEqual(o.Bto, w.Bto) {
result = false
return nil
}
}
if w.CC != nil {
if !ItemsEqual(o.CC, w.CC) {
result = false
return nil
}
}
if w.BCC != nil {
if !ItemsEqual(o.BCC, w.BCC) {
result = false
return nil
}
}
if !w.Published.IsZero() {
if !w.Published.Equal(o.Published) {
result = false
return nil
}
}
if !w.Updated.IsZero() {
if !w.Updated.Equal(o.Updated) {
result = false
return nil
}
}
if !w.StartTime.IsZero() {
if !w.StartTime.Equal(o.StartTime) {
result = false
return nil
}
}
if !w.EndTime.IsZero() {
if !w.EndTime.Equal(o.EndTime) {
result = false
return nil
}
}
if w.Duration != 0 {
if w.Duration != o.Duration {
result = false
return nil
}
}
if w.Likes != nil {
if !ItemsEqual(o.Likes, w.Likes) {
result = false
return nil
}
}
if w.Shares != nil {
if !ItemsEqual(o.Shares, w.Shares) {
result = false
return nil
}
}
return nil
})
if err != nil {
result = false
}
return result
}

20
vendor/github.com/go-ap/activitypub/object_id.go generated vendored Normal file
View File

@ -0,0 +1,20 @@
package activitypub
// ID designates a unique global identifier.
// All Objects in [ActivityStreams] should have unique global identifiers.
// ActivityPub extends this requirement; all objects distributed by the ActivityPub protocol MUST
// have unique global identifiers, unless they are intentionally transient
// (short-lived activities that are not intended to be able to be looked up,
// such as some kinds of chat messages or game notifications).
// These identifiers must fall into one of the following groups:
//
// 1. Publicly de-referenceable URIs, such as HTTPS URIs, with their authority belonging
// to that of their originating server. (Publicly facing content SHOULD use HTTPS URIs).
// 2. An ID explicitly specified as the JSON null object, which implies an anonymous object
// (a part of its parent context)
type ID = IRI
// IsValid returns if the receiver pointer is not nil and if dereferenced it has a positive length.
func (i *ID) IsValid() bool {
return i != nil && len(*i) > 0
}

View File

@ -0,0 +1,427 @@
package activitypub
import (
"bytes"
"encoding/gob"
"fmt"
"reflect"
"time"
"unsafe"
"github.com/valyala/fastjson"
)
// OrderedCollection is a subtype of Collection in which members of the logical
// collection are assumed to always be strictly ordered.
type OrderedCollection struct {
// ID provides the globally unique identifier for an Activity Pub Object or Link.
ID ID `jsonld:"id,omitempty"`
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
Type ActivityVocabularyType `jsonld:"type,omitempty"`
// Name a simple, human-readable, plain-text name for the object.
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
// The intent is to provide a model that is at least semantically similar to attachments in email.
Attachment Item `jsonld:"attachment,omitempty"`
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
// For instance, an object might be attributed to the completion of another activity.
AttributedTo Item `jsonld:"attributedTo,omitempty"`
// Audience identifies one or more entities that represent the total population of entities
// for which the object can considered to be relevant.
Audience ItemCollection `jsonld:"audience,omitempty"`
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
// By default, the value of content is HTML.
// The mediaType property can be used in the object to indicate a different content type.
// (The content MAY be expressed using multiple language-tagged values.)
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
// Context identifies the context within which the object exists or an activity was performed.
// The notion of "context" used is intentionally vague.
// The intended function is to serve as a means of grouping objects and activities that share a
// common originating context or purpose. An example could be all activities relating to a common project or event.
Context Item `jsonld:"context,omitempty"`
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
// If not specified, the content property is assumed to contain text/html content.
MediaType MimeType `jsonld:"mediaType,omitempty"`
// EndTime the date and time describing the actual or expected ending time of the object.
// When used with an Activity object, for instance, the endTime property specifies the moment
// the activity concluded or is expected to conclude.
EndTime time.Time `jsonld:"endTime,omitempty"`
// Generator identifies the entity (e.g. an application) that generated the object.
Generator Item `jsonld:"generator,omitempty"`
// Icon indicates an entity that describes an icon for this object.
// The image should have an aspect ratio of one (horizontal) to one (vertical)
// and should be suitable for presentation at a small size.
Icon Item `jsonld:"icon,omitempty"`
// Image indicates an entity that describes an image for this object.
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
Image Item `jsonld:"image,omitempty"`
// InReplyTo indicates one or more entities for which this object is considered a response.
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
// Location indicates one or more physical or logical locations associated with the object.
Location Item `jsonld:"location,omitempty"`
// Preview identifies an entity that provides a preview of this object.
Preview Item `jsonld:"preview,omitempty"`
// Published the date and time at which the object was published
Published time.Time `jsonld:"published,omitempty"`
// Replies identifies a Collection containing objects considered to be responses to this object.
Replies Item `jsonld:"replies,omitempty"`
// StartTime the date and time describing the actual or expected starting time of the object.
// When used with an Activity object, for instance, the startTime property specifies
// the moment the activity began or is scheduled to begin.
StartTime time.Time `jsonld:"startTime,omitempty"`
// Summary a natural language summarization of the object encoded as HTML.
// *Multiple language tagged summaries may be provided.)
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
// The key difference between attachment and tag is that the former implies association by inclusion,
// while the latter implies associated by reference.
Tag ItemCollection `jsonld:"tag,omitempty"`
// Updated the date and time at which the object was updated
Updated time.Time `jsonld:"updated,omitempty"`
// URL identifies one or more links to representations of the object
URL Item `jsonld:"url,omitempty"`
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
To ItemCollection `jsonld:"to,omitempty"`
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
Bto ItemCollection `jsonld:"bto,omitempty"`
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
CC ItemCollection `jsonld:"cc,omitempty"`
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
BCC ItemCollection `jsonld:"bcc,omitempty"`
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
// the duration property indicates the object's approximate duration.
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
Duration time.Duration `jsonld:"duration,omitempty"`
// This is a list of all Like activities with this object as the object property, added as a side effect.
// The likes collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Likes Item `jsonld:"likes,omitempty"`
// This is a list of all Announce activities with this object as the object property, added as a side effect.
// The shares collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Shares Item `jsonld:"shares,omitempty"`
// Source property is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
// In general, clients do the conversion from source to content, not the other way around.
Source Source `jsonld:"source,omitempty"`
// In a paged Collection, indicates the page that contains the most recently updated member items.
Current ObjectOrLink `jsonld:"current,omitempty"`
// In a paged Collection, indicates the furthest preceding page of items in the collection.
First ObjectOrLink `jsonld:"first,omitempty"`
// In a paged Collection, indicates the furthest proceeding page of the collection.
Last ObjectOrLink `jsonld:"last,omitempty"`
// A non-negative integer specifying the total number of objects contained by the logical view of the collection.
// This number might not reflect the actual number of items serialized within the Collection object instance.
TotalItems uint `jsonld:"totalItems"`
// Identifies the items contained in a collection. The items might be ordered or unordered.
OrderedItems ItemCollection `jsonld:"orderedItems,omitempty"`
}
type (
// InboxStream contains all activities received by the actor.
// The server SHOULD filter content according to the requester's permission.
// In general, the owner of an inbox is likely to be able to access all of their inbox contents.
// Depending on access control, some other content may be public, whereas other content may
// require authentication for non-owner users, if they can access the inbox at all.
InboxStream = OrderedCollection
// LikedCollection is a list of every object from all of the actor's Like activities,
// added as a side effect. The liked collection MUST be either an OrderedCollection or
// a Collection and MAY be filtered on privileges of an authenticated user or as
// appropriate when no authentication is given.
LikedCollection = OrderedCollection
// LikesCollection is a list of all Like activities with this object as the object property,
// added as a side effect. The likes collection MUST be either an OrderedCollection or a Collection
// and MAY be filtered on privileges of an authenticated user or as appropriate when
// no authentication is given.
LikesCollection = OrderedCollection
// OutboxStream contains activities the user has published,
// subject to the ability of the requestor to retrieve the activity (that is,
// the contents of the outbox are filtered by the permissions of the person reading it).
OutboxStream = OrderedCollection
// SharesCollection is a list of all Announce activities with this object as the object property,
// added as a side effect. The shares collection MUST be either an OrderedCollection or a Collection
// and MAY be filtered on privileges of an authenticated user or as appropriate when no authentication
// is given.
SharesCollection = OrderedCollection
)
// GetType returns the OrderedCollection's type
func (o OrderedCollection) GetType() ActivityVocabularyType {
return o.Type
}
// IsLink returns false for an OrderedCollection object
func (o OrderedCollection) IsLink() bool {
return false
}
// GetID returns the ID corresponding to the OrderedCollection
func (o OrderedCollection) GetID() ID {
return o.ID
}
// GetLink returns the IRI corresponding to the OrderedCollection object
func (o OrderedCollection) GetLink() IRI {
return IRI(o.ID)
}
// IsObject returns true for am OrderedCollection object
func (o OrderedCollection) IsObject() bool {
return true
}
// Collection returns the underlying Collection type
func (o OrderedCollection) Collection() ItemCollection {
return o.OrderedItems
}
// IsCollection returns true for OrderedCollection objects.
func (o OrderedCollection) IsCollection() bool {
return true
}
// Contains verifies if OrderedCollection array contains the received item r.
func (o OrderedCollection) Contains(r Item) bool {
if len(o.OrderedItems) == 0 {
return false
}
for _, it := range o.OrderedItems {
if ItemsEqual(it, r) {
return true
}
}
return false
}
// Count returns the maximum between the length of Items in collection and its TotalItems property
func (o *OrderedCollection) Count() uint {
if o == nil {
return 0
}
return uint(len(o.OrderedItems))
}
// Append adds an element to an the receiver collection object.
func (o *OrderedCollection) Append(it ...Item) error {
for _, ob := range it {
if o.OrderedItems.Contains(ob) {
continue
}
o.OrderedItems = append(o.OrderedItems, ob)
}
return nil
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (o *OrderedCollection) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadOrderedCollection(val, o)
}
// MarshalJSON encodes the receiver object to a JSON document.
func (o OrderedCollection) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
notEmpty := false
JSONWrite(&b, '{')
OnObject(o, func(o *Object) error {
notEmpty = JSONWriteObjectValue(&b, *o)
return nil
})
if o.Current != nil {
notEmpty = JSONWriteItemProp(&b, "current", o.Current) || notEmpty
}
if o.First != nil {
notEmpty = JSONWriteItemProp(&b, "first", o.First) || notEmpty
}
if o.Last != nil {
notEmpty = JSONWriteItemProp(&b, "last", o.Last) || notEmpty
}
notEmpty = JSONWriteIntProp(&b, "totalItems", int64(o.TotalItems)) || notEmpty
if o.OrderedItems != nil {
notEmpty = JSONWriteItemCollectionProp(&b, "orderedItems", o.OrderedItems, false) || notEmpty
}
if notEmpty {
JSONWrite(&b, '}')
return b, nil
}
return nil, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (o *OrderedCollection) UnmarshalBinary(data []byte) error {
return o.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (o OrderedCollection) MarshalBinary() ([]byte, error) {
return o.GobEncode()
}
// GobEncode
func (o OrderedCollection) GobEncode() ([]byte, error) {
mm := make(map[string][]byte)
hasData, err := mapOrderedCollectionProperties(mm, o)
if err != nil {
return nil, err
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
// GobDecode
func (o *OrderedCollection) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm, err := gobDecodeObjectAsMap(data)
if err != nil {
return err
}
return unmapOrderedCollectionProperties(mm, o)
}
// OrderedCollectionPageNew initializes a new OrderedCollectionPage
func OrderedCollectionPageNew(parent CollectionInterface) *OrderedCollectionPage {
p := OrderedCollectionPage{
PartOf: parent.GetLink(),
}
if pc, ok := parent.(*OrderedCollection); ok {
copyOrderedCollectionToPage(pc, &p)
}
p.Type = OrderedCollectionPageType
return &p
}
// ToOrderedCollection
func ToOrderedCollection(it Item) (*OrderedCollection, error) {
switch i := it.(type) {
case *OrderedCollection:
return i, nil
case OrderedCollection:
return &i, nil
case *OrderedCollectionPage:
return (*OrderedCollection)(unsafe.Pointer(i)), nil
case OrderedCollectionPage:
return (*OrderedCollection)(unsafe.Pointer(&i)), nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(OrderedCollection))
val := reflect.ValueOf(it)
if val.IsValid() && typ.Elem().Name() == val.Type().Elem().Name() {
conv := val.Convert(typ)
if i, ok := conv.Interface().(*OrderedCollection); ok {
return i, nil
}
}
}
return nil, ErrorInvalidType[OrderedCollection](it)
}
func copyOrderedCollectionToPage(c *OrderedCollection, p *OrderedCollectionPage) error {
p.Type = OrderedCollectionPageType
p.Name = c.Name
p.Content = c.Content
p.Summary = c.Summary
p.Context = c.Context
p.URL = c.URL
p.MediaType = c.MediaType
p.Generator = c.Generator
p.AttributedTo = c.AttributedTo
p.Attachment = c.Attachment
p.Location = c.Location
p.Published = c.Published
p.StartTime = c.StartTime
p.EndTime = c.EndTime
p.Duration = c.Duration
p.Icon = c.Icon
p.Preview = c.Preview
p.Image = c.Image
p.Updated = c.Updated
p.InReplyTo = c.InReplyTo
p.To = c.To
p.Audience = c.Audience
p.Bto = c.Bto
p.CC = c.CC
p.BCC = c.BCC
p.Replies = c.Replies
p.Tag = c.Tag
p.TotalItems = c.TotalItems
p.OrderedItems = c.OrderedItems
p.Current = c.Current
p.First = c.First
p.PartOf = c.GetLink()
return nil
}
// ItemsMatch
func (o OrderedCollection) ItemsMatch(col ...Item) bool {
for _, it := range col {
if match := o.OrderedItems.Contains(it); !match {
return false
}
}
return true
}
// Equals
func (o OrderedCollection) Equals(with Item) bool {
if IsNil(with) {
return false
}
if !with.IsCollection() {
return false
}
result := true
OnOrderedCollection(with, func(w *OrderedCollection) error {
OnCollection(w, func(wo *Collection) error {
if !wo.Equals(o) {
result = false
return nil
}
return nil
})
if w.OrderedItems != nil {
if !o.OrderedItems.Equals(w.OrderedItems) {
result = false
return nil
}
}
return nil
})
return result
}
func (o OrderedCollection) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'v':
_, _ = fmt.Fprintf(s, "%T[%s] { totalItems: %d }", o, o.Type, o.TotalItems)
}
}
func (o *OrderedCollection) Recipients() ItemCollection {
return ItemCollectionDeduplication(&o.To, &o.Bto, &o.CC, &o.BCC, &o.Audience)
}
func (o *OrderedCollection) Clean() {
_ = OnObject(o, func(o *Object) error {
o.Clean()
return nil
})
}

View File

@ -0,0 +1,393 @@
package activitypub
import (
"bytes"
"encoding/gob"
"fmt"
"reflect"
"time"
"github.com/valyala/fastjson"
)
// OrderedCollectionPage type extends from both CollectionPage and OrderedCollection.
// In addition to the properties inherited from each of those, the OrderedCollectionPage
// may contain an additional startIndex property whose value indicates the relative index position
// of the first item contained by the page within the OrderedCollection to which the page belongs.
type OrderedCollectionPage struct {
// ID provides the globally unique identifier for anActivity Pub Object or Link.
ID ID `jsonld:"id,omitempty"`
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
Type ActivityVocabularyType `jsonld:"type,omitempty"`
// Name a simple, human-readable, plain-text name for the object.
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
// The intent is to provide a model that is at least semantically similar to attachments in email.
Attachment Item `jsonld:"attachment,omitempty"`
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
// For instance, an object might be attributed to the completion of another activity.
AttributedTo Item `jsonld:"attributedTo,omitempty"`
// Audience identifies one or more entities that represent the total population of entities
// for which the object can considered to be relevant.
Audience ItemCollection `jsonld:"audience,omitempty"`
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
// By default, the value of content is HTML.
// The mediaType property can be used in the object to indicate a different content type.
// (The content MAY be expressed using multiple language-tagged values.)
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
// Context identifies the context within which the object exists or an activity was performed.
// The notion of "context" used is intentionally vague.
// The intended function is to serve as a means of grouping objects and activities that share a
// common originating context or purpose. An example could be all activities relating to a common project or event.
Context Item `jsonld:"context,omitempty"`
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
// If not specified, the content property is assumed to contain text/html content.
MediaType MimeType `jsonld:"mediaType,omitempty"`
// EndTime the date and time describing the actual or expected ending time of the object.
// When used with an Activity object, for instance, the endTime property specifies the moment
// the activity concluded or is expected to conclude.
EndTime time.Time `jsonld:"endTime,omitempty"`
// Generator identifies the entity (e.g. an application) that generated the object.
Generator Item `jsonld:"generator,omitempty"`
// Icon indicates an entity that describes an icon for this object.
// The image should have an aspect ratio of one (horizontal) to one (vertical)
// and should be suitable for presentation at a small size.
Icon Item `jsonld:"icon,omitempty"`
// Image indicates an entity that describes an image for this object.
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
Image Item `jsonld:"image,omitempty"`
// InReplyTo indicates one or more entities for which this object is considered a response.
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
// Location indicates one or more physical or logical locations associated with the object.
Location Item `jsonld:"location,omitempty"`
// Preview identifies an entity that provides a preview of this object.
Preview Item `jsonld:"preview,omitempty"`
// Published the date and time at which the object was published
Published time.Time `jsonld:"published,omitempty"`
// Replies identifies a Collection containing objects considered to be responses to this object.
Replies Item `jsonld:"replies,omitempty"`
// StartTime the date and time describing the actual or expected starting time of the object.
// When used with an Activity object, for instance, the startTime property specifies
// the moment the activity began or is scheduled to begin.
StartTime time.Time `jsonld:"startTime,omitempty"`
// Summary a natural language summarization of the object encoded as HTML.
// *Multiple language tagged summaries may be provided.)
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
// The key difference between attachment and tag is that the former implies association by inclusion,
// while the latter implies associated by reference.
Tag ItemCollection `jsonld:"tag,omitempty"`
// Updated the date and time at which the object was updated
Updated time.Time `jsonld:"updated,omitempty"`
// URL identifies one or more links to representations of the object
URL Item `jsonld:"url,omitempty"`
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
To ItemCollection `jsonld:"to,omitempty"`
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
Bto ItemCollection `jsonld:"bto,omitempty"`
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
CC ItemCollection `jsonld:"cc,omitempty"`
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
BCC ItemCollection `jsonld:"bcc,omitempty"`
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
// the duration property indicates the object's approximate duration.
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
Duration time.Duration `jsonld:"duration,omitempty"`
// This is a list of all Like activities with this object as the object property, added as a side effect.
// The likes collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Likes Item `jsonld:"likes,omitempty"`
// This is a list of all Announce activities with this object as the object property, added as a side effect.
// The shares collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Shares Item `jsonld:"shares,omitempty"`
// Source property is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
// In general, clients do the conversion from source to content, not the other way around.
Source Source `jsonld:"source,omitempty"`
// In a paged Collection, indicates the page that contains the most recently updated member items.
Current ObjectOrLink `jsonld:"current,omitempty"`
// In a paged Collection, indicates the furthest preceding page of items in the collection.
First ObjectOrLink `jsonld:"first,omitempty"`
// In a paged Collection, indicates the furthest proceeding page of the collection.
Last ObjectOrLink `jsonld:"last,omitempty"`
// A non-negative integer specifying the total number of objects contained by the logical view of the collection.
// This number might not reflect the actual number of items serialized within the Collection object instance.
TotalItems uint `jsonld:"totalItems"`
// Identifies the items contained in a collection. The items might be ordered or unordered.
OrderedItems ItemCollection `jsonld:"orderedItems,omitempty"`
// Identifies the Collection to which a CollectionPage objects items belong.
PartOf Item `jsonld:"partOf,omitempty"`
// In a paged Collection, indicates the next page of items.
Next Item `jsonld:"next,omitempty"`
// In a paged Collection, identifies the previous page of items.
Prev Item `jsonld:"prev,omitempty"`
// A non-negative integer value identifying the relative position within the logical view of a strictly ordered collection.
StartIndex uint `jsonld:"startIndex,omitempty"`
}
// GetID returns the ID corresponding to the OrderedCollectionPage object
func (o OrderedCollectionPage) GetID() ID {
return o.ID
}
// GetType returns the OrderedCollectionPage's type
func (o OrderedCollectionPage) GetType() ActivityVocabularyType {
return o.Type
}
// IsLink returns false for a OrderedCollectionPage object
func (o OrderedCollectionPage) IsLink() bool {
return false
}
// IsObject returns true for a OrderedCollectionPage object
func (o OrderedCollectionPage) IsObject() bool {
return true
}
// IsCollection returns true for OrderedCollectionPage objects
func (o OrderedCollectionPage) IsCollection() bool {
return true
}
// GetLink returns the IRI corresponding to the OrderedCollectionPage object
func (o OrderedCollectionPage) GetLink() IRI {
return IRI(o.ID)
}
// Collection returns the underlying Collection type
func (o OrderedCollectionPage) Collection() ItemCollection {
return o.OrderedItems
}
// Count returns the maximum between the length of Items in the collection page and its TotalItems property
func (o *OrderedCollectionPage) Count() uint {
if o == nil {
return 0
}
return uint(len(o.OrderedItems))
}
// Append adds an element to an OrderedCollectionPage
func (o *OrderedCollectionPage) Append(it ...Item) error {
for _, ob := range it {
if o.OrderedItems.Contains(ob) {
continue
}
o.OrderedItems = append(o.OrderedItems, ob)
}
return nil
}
// Contains verifies if OrderedCollectionPage array contains the received one
func (o OrderedCollectionPage) Contains(r Item) bool {
if len(o.OrderedItems) == 0 {
return false
}
for _, it := range o.OrderedItems {
if ItemsEqual(it, r) {
return true
}
}
return false
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (o *OrderedCollectionPage) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadOrderedCollectionPage(val, o)
}
// MarshalJSON encodes the receiver object to a JSON document.
func (o OrderedCollectionPage) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
notEmpty := false
JSONWrite(&b, '{')
OnObject(o, func(o *Object) error {
notEmpty = JSONWriteObjectValue(&b, *o)
return nil
})
if o.PartOf != nil {
notEmpty = JSONWriteItemProp(&b, "partOf", o.PartOf) || notEmpty
}
if o.Current != nil {
notEmpty = JSONWriteItemProp(&b, "current", o.Current) || notEmpty
}
if o.First != nil {
notEmpty = JSONWriteItemProp(&b, "first", o.First) || notEmpty
}
if o.Last != nil {
notEmpty = JSONWriteItemProp(&b, "last", o.Last) || notEmpty
}
if o.Next != nil {
notEmpty = JSONWriteItemProp(&b, "next", o.Next) || notEmpty
}
if o.Prev != nil {
notEmpty = JSONWriteItemProp(&b, "prev", o.Prev) || notEmpty
}
notEmpty = JSONWriteIntProp(&b, "totalItems", int64(o.TotalItems)) || notEmpty
if o.OrderedItems != nil {
notEmpty = JSONWriteItemCollectionProp(&b, "orderedItems", o.OrderedItems, false) || notEmpty
}
if notEmpty {
JSONWrite(&b, '}')
return b, nil
}
return nil, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (o *OrderedCollectionPage) UnmarshalBinary(data []byte) error {
return o.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (o OrderedCollectionPage) MarshalBinary() ([]byte, error) {
return o.GobEncode()
}
// GobEncode
func (o OrderedCollectionPage) GobEncode() ([]byte, error) {
mm := make(map[string][]byte)
hasData, err := mapOrderedCollectionPageProperties(mm, o)
if err != nil {
return nil, err
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
// GobDecode
func (o *OrderedCollectionPage) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm, err := gobDecodeObjectAsMap(data)
if err != nil {
return err
}
return unmapOrderedCollectionPageProperties(mm, o)
}
// ToOrderedCollectionPage
func ToOrderedCollectionPage(it Item) (*OrderedCollectionPage, error) {
switch i := it.(type) {
case *OrderedCollectionPage:
return i, nil
case OrderedCollectionPage:
return &i, nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(OrderedCollectionPage))
val := reflect.ValueOf(it)
if val.IsValid() && typ.Elem().Name() == val.Type().Elem().Name() {
conv := val.Convert(typ)
if i, ok := conv.Interface().(*OrderedCollectionPage); ok {
return i, nil
}
}
}
return nil, ErrorInvalidType[OrderedCollectionPage](it)
}
// ItemsMatch
func (o OrderedCollectionPage) ItemsMatch(col ...Item) bool {
for _, it := range col {
if match := o.OrderedItems.Contains(it); !match {
return false
}
}
return true
}
// Equals
func (o OrderedCollectionPage) Equals(with Item) bool {
if IsNil(with) {
return false
}
if !with.IsCollection() {
return false
}
result := true
OnOrderedCollectionPage(with, func(w *OrderedCollectionPage) error {
OnOrderedCollection(w, func(wo *OrderedCollection) error {
if !wo.Equals(o) {
result = false
return nil
}
return nil
})
if w.PartOf != nil {
if !ItemsEqual(o.PartOf, w.PartOf) {
result = false
return nil
}
}
if w.Current != nil {
if !ItemsEqual(o.Current, w.Current) {
result = false
return nil
}
}
if w.First != nil {
if !ItemsEqual(o.First, w.First) {
result = false
return nil
}
}
if w.Last != nil {
if !ItemsEqual(o.Last, w.Last) {
result = false
return nil
}
}
if w.Next != nil {
if !ItemsEqual(o.Next, w.Next) {
result = false
return nil
}
}
if w.Prev != nil {
if !ItemsEqual(o.Prev, w.Prev) {
result = false
return nil
}
}
return nil
})
return result
}
func (o OrderedCollectionPage) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'v':
_, _ = fmt.Fprintf(s, "%T[%s] { totalItems: %d }", o, o.Type, o.TotalItems)
}
}
func (o *OrderedCollectionPage) Recipients() ItemCollection {
return ItemCollectionDeduplication(&o.To, &o.Bto, &o.CC, &o.BCC, &o.Audience)
}
func (o *OrderedCollectionPage) Clean() {
_ = OnObject(o, func(o *Object) error {
o.Clean()
return nil
})
}

310
vendor/github.com/go-ap/activitypub/place.go generated vendored Normal file
View File

@ -0,0 +1,310 @@
package activitypub
import (
"bytes"
"encoding/gob"
"fmt"
"reflect"
"time"
"github.com/valyala/fastjson"
)
// Place represents a logical or physical location. See 5.3 Representing Places for additional information.
type Place struct {
// ID provides the globally unique identifier for anActivity Pub Object or Link.
ID ID `jsonld:"id,omitempty"`
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
Type ActivityVocabularyType `jsonld:"type,omitempty"`
// Name a simple, human-readable, plain-text name for the object.
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
// The intent is to provide a model that is at least semantically similar to attachments in email.
Attachment Item `jsonld:"attachment,omitempty"`
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
// For instance, an object might be attributed to the completion of another activity.
AttributedTo Item `jsonld:"attributedTo,omitempty"`
// Audience identifies one or more entities that represent the total population of entities
// for which the object can considered to be relevant.
Audience ItemCollection `jsonld:"audience,omitempty"`
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
// By default, the value of content is HTML.
// The mediaType property can be used in the object to indicate a different content type.
// (The content MAY be expressed using multiple language-tagged values.)
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
// Context identifies the context within which the object exists or an activity was performed.
// The notion of "context" used is intentionally vague.
// The intended function is to serve as a means of grouping objects and activities that share a
// common originating context or purpose. An example could be all activities relating to a common project or event.
Context Item `jsonld:"context,omitempty"`
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
// If not specified, the content property is assumed to contain text/html content.
MediaType MimeType `jsonld:"mediaType,omitempty"`
// EndTime the date and time describing the actual or expected ending time of the object.
// When used with an Activity object, for instance, the endTime property specifies the moment
// the activity concluded or is expected to conclude.
EndTime time.Time `jsonld:"endTime,omitempty"`
// Generator identifies the entity (e.g. an application) that generated the object.
Generator Item `jsonld:"generator,omitempty"`
// Icon indicates an entity that describes an icon for this object.
// The image should have an aspect ratio of one (horizontal) to one (vertical)
// and should be suitable for presentation at a small size.
Icon Item `jsonld:"icon,omitempty"`
// Image indicates an entity that describes an image for this object.
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
Image Item `jsonld:"image,omitempty"`
// InReplyTo indicates one or more entities for which this object is considered a response.
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
// Location indicates one or more physical or logical locations associated with the object.
Location Item `jsonld:"location,omitempty"`
// Preview identifies an entity that provides a preview of this object.
Preview Item `jsonld:"preview,omitempty"`
// Published the date and time at which the object was published
Published time.Time `jsonld:"published,omitempty"`
// Replies identifies a Collection containing objects considered to be responses to this object.
Replies Item `jsonld:"replies,omitempty"`
// StartTime the date and time describing the actual or expected starting time of the object.
// When used with an Activity object, for instance, the startTime property specifies
// the moment the activity began or is scheduled to begin.
StartTime time.Time `jsonld:"startTime,omitempty"`
// Summary a natural language summarization of the object encoded as HTML.
// *Multiple language tagged summaries may be provided.)
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
// The key difference between attachment and tag is that the former implies association by inclusion,
// while the latter implies associated by reference.
Tag ItemCollection `jsonld:"tag,omitempty"`
// Updated the date and time at which the object was updated
Updated time.Time `jsonld:"updated,omitempty"`
// URL identifies one or more links to representations of the object
URL Item `jsonld:"url,omitempty"`
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
To ItemCollection `jsonld:"to,omitempty"`
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
Bto ItemCollection `jsonld:"bto,omitempty"`
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
CC ItemCollection `jsonld:"cc,omitempty"`
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
BCC ItemCollection `jsonld:"bcc,omitempty"`
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
// the duration property indicates the object's approximate duration.
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
Duration time.Duration `jsonld:"duration,omitempty"`
// This is a list of all Like activities with this object as the object property, added as a side effect.
// The likes collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Likes Item `jsonld:"likes,omitempty"`
// This is a list of all Announce activities with this object as the object property, added as a side effect.
// The shares collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Shares Item `jsonld:"shares,omitempty"`
// Source property is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
// In general, clients do the conversion from source to content, not the other way around.
Source Source `jsonld:"source,omitempty"`
// Accuracy indicates the accuracy of position coordinates on a Place objects.
// Expressed in properties of percentage. e.g. "94.0" means "94.0% accurate".
Accuracy float64 `jsonld:"accuracy,omitempty"`
// Altitude indicates the altitude of a place. The measurement units is indicated using the units property.
// If units is not specified, the default is assumed to be "m" indicating meters.
Altitude float64 `jsonld:"altitude,omitempty"`
// Latitude the latitude of a place
Latitude float64 `jsonld:"latitude,omitempty"`
// Longitude the longitude of a place
Longitude float64 `jsonld:"longitude,omitempty"`
// Radius the radius from the given latitude and longitude for a Place.
// The units is expressed by the units property. If units is not specified,
// the default is assumed to be "m" indicating "meters".
Radius int64 `jsonld:"radius,omitempty"`
// Specifies the measurement units for the radius and altitude properties on a Place object.
// If not specified, the default is assumed to be "m" for "meters".
// Values "cm" | " feet" | " inches" | " km" | " m" | " miles" | xsd:anyURI
Units string `jsonld:"units,omitempty"`
}
// IsLink returns false for Place objects
func (p Place) IsLink() bool {
return false
}
// IsObject returns true for Place objects
func (p Place) IsObject() bool {
return true
}
// IsCollection returns false for Place objects
func (p Place) IsCollection() bool {
return false
}
// GetLink returns the IRI corresponding to the current Place object
func (p Place) GetLink() IRI {
return IRI(p.ID)
}
// GetType returns the type of the current Place
func (p Place) GetType() ActivityVocabularyType {
return p.Type
}
// GetID returns the ID corresponding to the current Place
func (p Place) GetID() ID {
return p.ID
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (p *Place) UnmarshalJSON(data []byte) error {
par := fastjson.Parser{}
val, err := par.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadPlace(val, p)
}
// MarshalJSON encodes the receiver object to a JSON document.
func (p Place) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
notEmpty := false
JSONWrite(&b, '{')
OnObject(p, func(o *Object) error {
notEmpty = JSONWriteObjectValue(&b, *o)
return nil
})
if p.Accuracy > 0 {
notEmpty = JSONWriteFloatProp(&b, "accuracy", p.Accuracy) || notEmpty
}
if p.Altitude > 0 {
notEmpty = JSONWriteFloatProp(&b, "altitude", p.Altitude) || notEmpty
}
if p.Latitude > 0 {
notEmpty = JSONWriteFloatProp(&b, "latitude", p.Latitude) || notEmpty
}
if p.Longitude > 0 {
notEmpty = JSONWriteFloatProp(&b, "longitude", p.Longitude) || notEmpty
}
if p.Radius > 0 {
notEmpty = JSONWriteIntProp(&b, "radius", p.Radius) || notEmpty
}
if len(p.Units) > 0 {
notEmpty = JSONWriteStringProp(&b, "radius", p.Units) || notEmpty
}
if notEmpty {
JSONWrite(&b, '}')
return b, nil
}
return nil, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (p *Place) UnmarshalBinary(data []byte) error {
return p.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (p Place) MarshalBinary() ([]byte, error) {
return p.GobEncode()
}
// GobEncode
func (p Place) GobEncode() ([]byte, error) {
mm := make(map[string][]byte)
hasData, err := mapPlaceProperties(mm, p)
if err != nil {
return nil, err
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
// GobDecode
func (p *Place) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm, err := gobDecodeObjectAsMap(data)
if err != nil {
return err
}
return unmapPlaceProperties(mm, p)
}
// Recipients performs recipient de-duplication on the Place object's To, Bto, CC and BCC properties
func (p *Place) Recipients() ItemCollection {
return ItemCollectionDeduplication(&p.To, &p.Bto, &p.CC, &p.BCC, &p.Audience)
}
// Clean removes Bto and BCC properties
func (p *Place) Clean() {
_ = OnObject(p, func(o *Object) error {
o.Clean()
return nil
})
}
func (p Place) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'v':
_, _ = fmt.Fprintf(s, "%T[%s] { }", p, p.Type)
}
}
// ToPlace
func ToPlace(it Item) (*Place, error) {
switch i := it.(type) {
case *Place:
return i, nil
case Place:
return &i, nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(Place))
if reflect.TypeOf(it).ConvertibleTo(typ) {
if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Place); ok {
return i, nil
}
}
}
return nil, ErrorInvalidType[Place](it)
}
type withPlaceFn func(*Place) error
// OnPlace calls function fn on it Item if it can be asserted to type *Place
//
// This function should be called if trying to access the Place specific properties
// like "accuracy", "altitude", "latitude", "longitude", "radius", or "units".
// For the other properties OnObject should be used instead.
func OnPlace(it Item, fn withPlaceFn) error {
if it == nil {
return nil
}
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if IsLink(it) {
continue
}
if err := OnPlace(it, fn); err != nil {
return err
}
}
return nil
})
}
ob, err := ToPlace(it)
if err != nil {
return err
}
return fn(ob)
}

282
vendor/github.com/go-ap/activitypub/profile.go generated vendored Normal file
View File

@ -0,0 +1,282 @@
package activitypub
import (
"bytes"
"encoding/gob"
"fmt"
"reflect"
"time"
"github.com/valyala/fastjson"
)
// Profile a Profile is a content object that describes another Object,
// typically used to describe CanReceiveActivities Type objects.
// The describes property is used to reference the object being described by the profile.
type Profile struct {
// ID provides the globally unique identifier for anActivity Pub Object or Link.
ID ID `jsonld:"id,omitempty"`
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
Type ActivityVocabularyType `jsonld:"type,omitempty"`
// Name a simple, human-readable, plain-text name for the object.
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
// The intent is to provide a model that is at least semantically similar to attachments in email.
Attachment Item `jsonld:"attachment,omitempty"`
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
// For instance, an object might be attributed to the completion of another activity.
AttributedTo Item `jsonld:"attributedTo,omitempty"`
// Audience identifies one or more entities that represent the total population of entities
// for which the object can considered to be relevant.
Audience ItemCollection `jsonld:"audience,omitempty"`
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
// By default, the value of content is HTML.
// The mediaType property can be used in the object to indicate a different content type.
// (The content MAY be expressed using multiple language-tagged values.)
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
// Context identifies the context within which the object exists or an activity was performed.
// The notion of "context" used is intentionally vague.
// The intended function is to serve as a means of grouping objects and activities that share a
// common originating context or purpose. An example could be all activities relating to a common project or event.
Context Item `jsonld:"context,omitempty"`
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
// If not specified, the content property is assumed to contain text/html content.
MediaType MimeType `jsonld:"mediaType,omitempty"`
// EndTime the date and time describing the actual or expected ending time of the object.
// When used with an Activity object, for instance, the endTime property specifies the moment
// the activity concluded or is expected to conclude.
EndTime time.Time `jsonld:"endTime,omitempty"`
// Generator identifies the entity (e.g. an application) that generated the object.
Generator Item `jsonld:"generator,omitempty"`
// Icon indicates an entity that describes an icon for this object.
// The image should have an aspect ratio of one (horizontal) to one (vertical)
// and should be suitable for presentation at a small size.
Icon Item `jsonld:"icon,omitempty"`
// Image indicates an entity that describes an image for this object.
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
Image Item `jsonld:"image,omitempty"`
// InReplyTo indicates one or more entities for which this object is considered a response.
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
// Location indicates one or more physical or logical locations associated with the object.
Location Item `jsonld:"location,omitempty"`
// Preview identifies an entity that provides a preview of this object.
Preview Item `jsonld:"preview,omitempty"`
// Published the date and time at which the object was published
Published time.Time `jsonld:"published,omitempty"`
// Replies identifies a Collection containing objects considered to be responses to this object.
Replies Item `jsonld:"replies,omitempty"`
// StartTime the date and time describing the actual or expected starting time of the object.
// When used with an Activity object, for instance, the startTime property specifies
// the moment the activity began or is scheduled to begin.
StartTime time.Time `jsonld:"startTime,omitempty"`
// Summary a natural language summarization of the object encoded as HTML.
// *Multiple language tagged summaries may be provided.)
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
// The key difference between attachment and tag is that the former implies association by inclusion,
// while the latter implies associated by reference.
Tag ItemCollection `jsonld:"tag,omitempty"`
// Updated the date and time at which the object was updated
Updated time.Time `jsonld:"updated,omitempty"`
// URL identifies one or more links to representations of the object
URL Item `jsonld:"url,omitempty"`
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
To ItemCollection `jsonld:"to,omitempty"`
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
Bto ItemCollection `jsonld:"bto,omitempty"`
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
CC ItemCollection `jsonld:"cc,omitempty"`
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
BCC ItemCollection `jsonld:"bcc,omitempty"`
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
// the duration property indicates the object's approximate duration.
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
Duration time.Duration `jsonld:"duration,omitempty"`
// This is a list of all Like activities with this object as the object property, added as a side effect.
// The likes collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Likes Item `jsonld:"likes,omitempty"`
// This is a list of all Announce activities with this object as the object property, added as a side effect.
// The shares collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Shares Item `jsonld:"shares,omitempty"`
// Source property is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
// In general, clients do the conversion from source to content, not the other way around.
Source Source `jsonld:"source,omitempty"`
// Describes On a Profile object, the describes property identifies the object described by the Profile.
Describes Item `jsonld:"describes,omitempty"`
}
// IsLink returns false for Profile objects
func (p Profile) IsLink() bool {
return false
}
// IsObject returns true for Profile objects
func (p Profile) IsObject() bool {
return true
}
// IsCollection returns false for Profile objects
func (p Profile) IsCollection() bool {
return false
}
// GetLink returns the IRI corresponding to the current Profile object
func (p Profile) GetLink() IRI {
return IRI(p.ID)
}
// GetType returns the type of the current Profile
func (p Profile) GetType() ActivityVocabularyType {
return p.Type
}
// GetID returns the ID corresponding to the current Profile
func (p Profile) GetID() ID {
return p.ID
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (p *Profile) UnmarshalJSON(data []byte) error {
par := fastjson.Parser{}
val, err := par.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadProfile(val, p)
}
// MarshalJSON encodes the receiver object to a JSON document.
func (p Profile) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
notEmpty := false
JSONWrite(&b, '{')
OnObject(p, func(o *Object) error {
return nil
})
if p.Describes != nil {
notEmpty = JSONWriteItemProp(&b, "describes", p.Describes) || notEmpty
}
if notEmpty {
JSONWrite(&b, '}')
return b, nil
}
return nil, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (p *Profile) UnmarshalBinary(data []byte) error {
return p.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (p Profile) MarshalBinary() ([]byte, error) {
return p.GobEncode()
}
// GobEncode
func (p Profile) GobEncode() ([]byte, error) {
mm := make(map[string][]byte)
hasData, err := mapProfileProperties(mm, p)
if err != nil {
return nil, err
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
// GobDecode
func (p *Profile) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm, err := gobDecodeObjectAsMap(data)
if err != nil {
return err
}
return unmapProfileProperties(mm, p)
}
// Recipients performs recipient de-duplication on the Profile object's To, Bto, CC and BCC properties
func (p *Profile) Recipients() ItemCollection {
return ItemCollectionDeduplication(&p.To, &p.Bto, &p.CC, &p.BCC, &p.Audience)
}
// Clean removes Bto and BCC properties
func (p *Profile) Clean() {
_ = OnObject(p, func(o *Object) error {
o.Clean()
return nil
})
}
func (p Profile) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'v':
_, _ = fmt.Fprintf(s, "%T[%s] { }", p, p.Type)
}
}
// ToProfile tries to convert the "it" Item to a Profile object
func ToProfile(it Item) (*Profile, error) {
switch i := it.(type) {
case *Profile:
return i, nil
case Profile:
return &i, nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(Profile))
if reflect.TypeOf(it).ConvertibleTo(typ) {
if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Profile); ok {
return i, nil
}
}
}
return nil, ErrorInvalidType[Profile](it)
}
type withProfileFn func(*Profile) error
// OnProfile calls function fn on it Item if it can be asserted to type *Profile
//
// This function should be called if trying to access the Profile specific properties
// like "describes".
// For the other properties OnObject should be used instead.
func OnProfile(it Item, fn withProfileFn) error {
if it == nil {
return nil
}
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if IsLink(it) {
continue
}
if err := OnProfile(it, fn); err != nil {
return err
}
}
return nil
})
}
ob, err := ToProfile(it)
if err != nil {
return err
}
return fn(ob)
}

276
vendor/github.com/go-ap/activitypub/question.go generated vendored Normal file
View File

@ -0,0 +1,276 @@
package activitypub
import (
"bytes"
"encoding/gob"
"fmt"
"reflect"
"time"
"github.com/valyala/fastjson"
)
// Question represents a question being asked. Question objects are an extension of IntransitiveActivity.
// That is, the Question object is an Activity, but the direct object is the question
// itself and therefore it would not contain an object property.
// Either of the anyOf and oneOf properties may be used to express possible answers,
// but a Question object must not have both properties.
type Question struct {
// ID provides the globally unique identifier for anActivity Pub Object or Link.
ID ID `jsonld:"id,omitempty"`
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
Type ActivityVocabularyType `jsonld:"type,omitempty"`
// Name a simple, human-readable, plain-text name for the object.
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
// The intent is to provide a model that is at least semantically similar to attachments in email.
Attachment Item `jsonld:"attachment,omitempty"`
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
// For instance, an object might be attributed to the completion of another activity.
AttributedTo Item `jsonld:"attributedTo,omitempty"`
// Audience identifies one or more entities that represent the total population of entities
// for which the object can considered to be relevant.
Audience ItemCollection `jsonld:"audience,omitempty"`
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
// By default, the value of content is HTML.
// The mediaType property can be used in the object to indicate a different content type.
// (The content MAY be expressed using multiple language-tagged values.)
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
// Context identifies the context within which the object exists or an activity was performed.
// The notion of "context" used is intentionally vague.
// The intended function is to serve as a means of grouping objects and activities that share a
// common originating context or purpose. An example could be all activities relating to a common project or event.
Context Item `jsonld:"context,omitempty"`
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
// If not specified, the content property is assumed to contain text/html content.
MediaType MimeType `jsonld:"mediaType,omitempty"`
// EndTime the date and time describing the actual or expected ending time of the object.
// When used with an Activity object, for instance, the endTime property specifies the moment
// the activity concluded or is expected to conclude.
EndTime time.Time `jsonld:"endTime,omitempty"`
// Generator identifies the entity (e.g. an application) that generated the object.
Generator Item `jsonld:"generator,omitempty"`
// Icon indicates an entity that describes an icon for this object.
// The image should have an aspect ratio of one (horizontal) to one (vertical)
// and should be suitable for presentation at a small size.
Icon Item `jsonld:"icon,omitempty"`
// Image indicates an entity that describes an image for this object.
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
Image Item `jsonld:"image,omitempty"`
// InReplyTo indicates one or more entities for which this object is considered a response.
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
// Location indicates one or more physical or logical locations associated with the object.
Location Item `jsonld:"location,omitempty"`
// Preview identifies an entity that provides a preview of this object.
Preview Item `jsonld:"preview,omitempty"`
// Published the date and time at which the object was published
Published time.Time `jsonld:"published,omitempty"`
// Replies identifies a Collection containing objects considered to be responses to this object.
Replies Item `jsonld:"replies,omitempty"`
// StartTime the date and time describing the actual or expected starting time of the object.
// When used with an Activity object, for instance, the startTime property specifies
// the moment the activity began or is scheduled to begin.
StartTime time.Time `jsonld:"startTime,omitempty"`
// Summary a natural language summarization of the object encoded as HTML.
// *Multiple language tagged summaries may be provided.)
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
// The key difference between attachment and tag is that the former implies association by inclusion,
// while the latter implies associated by reference.
Tag ItemCollection `jsonld:"tag,omitempty"`
// Updated the date and time at which the object was updated
Updated time.Time `jsonld:"updated,omitempty"`
// URL identifies one or more links to representations of the object
URL Item `jsonld:"url,omitempty"`
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
To ItemCollection `jsonld:"to,omitempty"`
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
Bto ItemCollection `jsonld:"bto,omitempty"`
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
CC ItemCollection `jsonld:"cc,omitempty"`
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
BCC ItemCollection `jsonld:"bcc,omitempty"`
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
// the duration property indicates the object's approximate duration.
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
Duration time.Duration `jsonld:"duration,omitempty"`
// This is a list of all Like activities with this object as the object property, added as a side effect.
// The likes collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Likes Item `jsonld:"likes,omitempty"`
// This is a list of all Announce activities with this object as the object property, added as a side effect.
// The shares collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Shares Item `jsonld:"shares,omitempty"`
// Source property is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
// In general, clients do the conversion from source to content, not the other way around.
Source Source `jsonld:"source,omitempty"`
// CanReceiveActivities describes one or more entities that either performed or are expected to perform the activity.
// Any single activity can have multiple actors. The actor may be specified using an indirect Link.
Actor CanReceiveActivities `jsonld:"actor,omitempty"`
// Target describes the indirect object, or target, of the activity.
// The precise meaning of the target is largely dependent on the type of action being described
// but will often be the object of the English preposition "to".
// For instance, in the activity "John added a movie to his wishlist",
// the target of the activity is John's wishlist. An activity can have more than one target.
Target Item `jsonld:"target,omitempty"`
// Result describes the result of the activity. For instance, if a particular action results in the creation
// of a new resource, the result property can be used to describe that new resource.
Result Item `jsonld:"result,omitempty"`
// Origin describes an indirect object of the activity from which the activity is directed.
// The precise meaning of the origin is the object of the English preposition "from".
// For instance, in the activity "John moved an item to List B from List A", the origin of the activity is "List A".
Origin Item `jsonld:"origin,omitempty"`
// Instrument identifies one or more objects used (or to be used) in the completion of an Activity.
Instrument Item `jsonld:"instrument,omitempty"`
// OneOf identifies an exclusive option for a Question. Use of oneOf implies that the Question
// can have only a single answer. To indicate that a Question can have multiple answers, use anyOf.
OneOf Item `jsonld:"oneOf,omitempty"`
// AnyOf identifies an inclusive option for a Question. Use of anyOf implies that the Question can have multiple answers.
// To indicate that a Question can have only one answer, use oneOf.
AnyOf Item `jsonld:"anyOf,omitempty"`
// Closed indicates that a question has been closed, and answers are no longer accepted.
Closed bool `jsonld:"closed,omitempty"`
}
// GetID returns the ID corresponding to the Question object
func (q Question) GetID() ID {
return q.ID
}
// GetLink returns the IRI corresponding to the Question object
func (q Question) GetLink() IRI {
return IRI(q.ID)
}
// GetType returns the ActivityVocabulary type of the current Activity
func (q Question) GetType() ActivityVocabularyType {
return q.Type
}
// IsObject returns true for Question objects
func (q Question) IsObject() bool {
return true
}
// IsLink returns false for Question objects
func (q Question) IsLink() bool {
return false
}
// IsCollection returns false for Question objects
func (q Question) IsCollection() bool {
return false
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (q *Question) UnmarshalJSON(data []byte) error {
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadQuestion(val, q)
}
// MarshalJSON encodes the receiver object to a JSON document.
func (q Question) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
JSONWrite(&b, '{')
if !JSONWriteQuestionValue(&b, q) {
return nil, nil
}
JSONWrite(&b, '}')
return b, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (q *Question) UnmarshalBinary(data []byte) error {
return q.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (q Question) MarshalBinary() ([]byte, error) {
return q.GobEncode()
}
// GobEncode
func (q Question) GobEncode() ([]byte, error) {
mm := make(map[string][]byte)
hasData, err := mapQuestionProperties(mm, q)
if err != nil {
return nil, err
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
// GobDecode
func (q *Question) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm, err := gobDecodeObjectAsMap(data)
if err != nil {
return err
}
return unmapQuestionProperties(mm, q)
}
func (q Question) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'v':
_, _ = fmt.Fprintf(s, "%T[%s] { }", q, q.Type)
}
}
// QuestionNew initializes a Question activity
func QuestionNew(id ID) *Question {
q := Question{ID: id, Type: QuestionType}
q.Name = NaturalLanguageValuesNew()
q.Content = NaturalLanguageValuesNew()
return &q
}
// ToQuestion tries to convert the it Item to a Question object.
func ToQuestion(it Item) (*Question, error) {
switch i := it.(type) {
case *Question:
return i, nil
case Question:
return &i, nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(Question))
if reflect.TypeOf(it).ConvertibleTo(typ) {
if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Question); ok {
return i, nil
}
}
}
return nil, ErrorInvalidType[Question](it)
}
// Recipients performs recipient de-duplication on the Question's To, Bto, CC and BCC properties
func (q *Question) Recipients() ItemCollection {
return ItemCollectionDeduplication(&ItemCollection{q.Actor}, &q.To, &q.Bto, &q.CC, &q.BCC, &q.Audience)
}
// Clean removes Bto and BCC properties
func (q *Question) Clean() {
_ = OnObject(q, func(o *Object) error {
o.Clean()
return nil
})
}

290
vendor/github.com/go-ap/activitypub/relationship.go generated vendored Normal file
View File

@ -0,0 +1,290 @@
package activitypub
import (
"bytes"
"encoding/gob"
"fmt"
"reflect"
"time"
"unsafe"
"github.com/valyala/fastjson"
)
// Relationship describes a relationship between two individuals.
// The subject and object properties are used to identify the connected individuals.
// See 5.2 Representing Relationships Between Entities for additional information.
//
// 5.2: The relationship property specifies the kind of relationship that exists between the two individuals identified
// by the subject and object properties. Used together, these three properties form what is commonly known
// as a "reified statement" where subject identifies the subject, relationship identifies the predicate,
// and object identifies the object.
type Relationship struct {
// ID provides the globally unique identifier for anActivity Pub Object or Link.
ID ID `jsonld:"id,omitempty"`
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
Type ActivityVocabularyType `jsonld:"type,omitempty"`
// Name a simple, human-readable, plain-text name for the object.
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
// The intent is to provide a model that is at least semantically similar to attachments in email.
Attachment Item `jsonld:"attachment,omitempty"`
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
// For instance, an object might be attributed to the completion of another activity.
AttributedTo Item `jsonld:"attributedTo,omitempty"`
// Audience identifies one or more entities that represent the total population of entities
// for which the object can considered to be relevant.
Audience ItemCollection `jsonld:"audience,omitempty"`
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
// By default, the value of content is HTML.
// The mediaType property can be used in the object to indicate a different content type.
// (The content MAY be expressed using multiple language-tagged values.)
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
// Context identifies the context within which the object exists or an activity was performed.
// The notion of "context" used is intentionally vague.
// The intended function is to serve as a means of grouping objects and activities that share a
// common originating context or purpose. An example could be all activities relating to a common project or event.
Context Item `jsonld:"context,omitempty"`
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
// If not specified, the content property is assumed to contain text/html content.
MediaType MimeType `jsonld:"mediaType,omitempty"`
// EndTime the date and time describing the actual or expected ending time of the object.
// When used with an Activity object, for instance, the endTime property specifies the moment
// the activity concluded or is expected to conclude.
EndTime time.Time `jsonld:"endTime,omitempty"`
// Generator identifies the entity (e.g. an application) that generated the object.
Generator Item `jsonld:"generator,omitempty"`
// Icon indicates an entity that describes an icon for this object.
// The image should have an aspect ratio of one (horizontal) to one (vertical)
// and should be suitable for presentation at a small size.
Icon Item `jsonld:"icon,omitempty"`
// Image indicates an entity that describes an image for this object.
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
Image Item `jsonld:"image,omitempty"`
// InReplyTo indicates one or more entities for which this object is considered a response.
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
// Location indicates one or more physical or logical locations associated with the object.
Location Item `jsonld:"location,omitempty"`
// Preview identifies an entity that provides a preview of this object.
Preview Item `jsonld:"preview,omitempty"`
// Published the date and time at which the object was published
Published time.Time `jsonld:"published,omitempty"`
// Replies identifies a Collection containing objects considered to be responses to this object.
Replies Item `jsonld:"replies,omitempty"`
// StartTime the date and time describing the actual or expected starting time of the object.
// When used with an Activity object, for instance, the startTime property specifies
// the moment the activity began or is scheduled to begin.
StartTime time.Time `jsonld:"startTime,omitempty"`
// Summary a natural language summarization of the object encoded as HTML.
// *Multiple language tagged summaries may be provided.)
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
// The key difference between attachment and tag is that the former implies association by inclusion,
// while the latter implies associated by reference.
Tag ItemCollection `jsonld:"tag,omitempty"`
// Updated the date and time at which the object was updated
Updated time.Time `jsonld:"updated,omitempty"`
// URL identifies one or more links to representations of the object
URL Item `jsonld:"url,omitempty"`
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
To ItemCollection `jsonld:"to,omitempty"`
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
Bto ItemCollection `jsonld:"bto,omitempty"`
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
CC ItemCollection `jsonld:"cc,omitempty"`
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
BCC ItemCollection `jsonld:"bcc,omitempty"`
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
// the duration property indicates the object's approximate duration.
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
Duration time.Duration `jsonld:"duration,omitempty"`
// This is a list of all Like activities with this object as the object property, added as a side effect.
// The likes collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Likes Item `jsonld:"likes,omitempty"`
// This is a list of all Announce activities with this object as the object property, added as a side effect.
// The shares collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Shares Item `jsonld:"shares,omitempty"`
// Source property is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
// In general, clients do the conversion from source to content, not the other way around.
Source Source `jsonld:"source,omitempty"`
// Subject Subject On a Relationship object, the subject property identifies one of the connected individuals.
// For instance, for a Relationship object describing "John is related to Sally", subject would refer to John.
Subject Item `jsonld:"subject,omitempty"`
// Object
Object Item `jsonld:"object,omitempty"`
// Relationship On a Relationship object, the relationship property identifies the kind
// of relationship that exists between subject and object.
Relationship Item `jsonld:"relationship,omitempty"`
}
// IsLink returns false for Relationship objects
func (r Relationship) IsLink() bool {
return false
}
// IsObject returns true for Relationship objects
func (r Relationship) IsObject() bool {
return true
}
// IsCollection returns false for Relationship objects
func (r Relationship) IsCollection() bool {
return false
}
// GetLink returns the IRI corresponding to the current Relationship object
func (r Relationship) GetLink() IRI {
return IRI(r.ID)
}
// GetType returns the type of the current Relationship
func (r Relationship) GetType() ActivityVocabularyType {
return r.Type
}
// GetID returns the ID corresponding to the current Relationship
func (r Relationship) GetID() ID {
return r.ID
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (r *Relationship) UnmarshalJSON(data []byte) error {
par := fastjson.Parser{}
val, err := par.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadRelationship(val, r)
}
// MarshalJSON encodes the receiver object to a JSON document.
func (r Relationship) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
notEmpty := false
JSONWrite(&b, '{')
OnObject(r, func(o *Object) error {
notEmpty = JSONWriteObjectValue(&b, *o)
return nil
})
if r.Subject != nil {
notEmpty = JSONWriteItemProp(&b, "subject", r.Subject) || notEmpty
}
if r.Object != nil {
notEmpty = JSONWriteItemProp(&b, "object", r.Object) || notEmpty
}
if r.Relationship != nil {
notEmpty = JSONWriteItemProp(&b, "relationship", r.Relationship) || notEmpty
}
if notEmpty {
JSONWrite(&b, '}')
return b, nil
}
return nil, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (r *Relationship) UnmarshalBinary(data []byte) error {
return r.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (r Relationship) MarshalBinary() ([]byte, error) {
return r.GobEncode()
}
// GobEncode
func (r Relationship) GobEncode() ([]byte, error) {
mm := make(map[string][]byte)
hasData, err := mapRelationshipProperties(mm, r)
if err != nil {
return nil, err
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
// GobDecode
func (r *Relationship) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm, err := gobDecodeObjectAsMap(data)
if err != nil {
return err
}
return unmapRelationshipProperties(mm, r)
}
// Recipients performs recipient de-duplication on the Relationship object's To, Bto, CC and BCC properties
func (r *Relationship) Recipients() ItemCollection {
return ItemCollectionDeduplication(&r.To, &r.Bto, &r.CC, &r.BCC, &r.Audience)
}
// Clean removes Bto and BCC properties
func (r *Relationship) Clean() {
_ = OnObject(r, func(o *Object) error {
o.Clean()
return nil
})
}
func (r Relationship) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'v':
_, _ = fmt.Fprintf(s, "%T[%s] { }", r, r.Type)
}
}
// ToRelationship tries to convert the "it" Item to a Relationship object.
func ToRelationship(it Item) (*Relationship, error) {
switch i := it.(type) {
case *Relationship:
return i, nil
case Relationship:
return &i, nil
case *Object:
return (*Relationship)(unsafe.Pointer(i)), nil
case Object:
return (*Relationship)(unsafe.Pointer(&i)), nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(Relationship))
if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Relationship); ok {
return i, nil
}
}
return nil, ErrorInvalidType[Relationship](it)
}
type withRelationshipFn func(*Relationship) error
// OnRelationship calls function fn on it Item if it can be asserted to type *Relationship
//
// This function should be called if trying to access the Relationship specific properties
// like "subject", "object", or "relationship".
// For the other properties OnObject should be used instead.
func OnRelationship(it Item, fn withRelationshipFn) error {
if it == nil {
return nil
}
ob, err := ToRelationship(it)
if err != nil {
return err
}
return fn(ob)
}

290
vendor/github.com/go-ap/activitypub/tombstone.go generated vendored Normal file
View File

@ -0,0 +1,290 @@
package activitypub
import (
"bytes"
"encoding/gob"
"fmt"
"reflect"
"time"
"unsafe"
"github.com/valyala/fastjson"
)
// Tombstone a Tombstone represents a content object that has been deleted.
// It can be used in Collections to signify that there used to be an object at this position,
// but it has been deleted.
type Tombstone struct {
// ID provides the globally unique identifier for anActivity Pub Object or Link.
ID ID `jsonld:"id,omitempty"`
// Type identifies the Activity Pub Object or Link type. Multiple values may be specified.
Type ActivityVocabularyType `jsonld:"type,omitempty"`
// Name a simple, human-readable, plain-text name for the object.
// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.
Name NaturalLanguageValues `jsonld:"name,omitempty,collapsible"`
// Attachment identifies a resource attached or related to an object that potentially requires special handling.
// The intent is to provide a model that is at least semantically similar to attachments in email.
Attachment Item `jsonld:"attachment,omitempty"`
// AttributedTo identifies one or more entities to which this object is attributed. The attributed entities might not be Actors.
// For instance, an object might be attributed to the completion of another activity.
AttributedTo Item `jsonld:"attributedTo,omitempty"`
// Audience identifies one or more entities that represent the total population of entities
// for which the object can considered to be relevant.
Audience ItemCollection `jsonld:"audience,omitempty"`
// Content or textual representation of the Activity Pub Object encoded as a JSON string.
// By default, the value of content is HTML.
// The mediaType property can be used in the object to indicate a different content type.
// (The content MAY be expressed using multiple language-tagged values.)
Content NaturalLanguageValues `jsonld:"content,omitempty,collapsible"`
// Context identifies the context within which the object exists or an activity was performed.
// The notion of "context" used is intentionally vague.
// The intended function is to serve as a means of grouping objects and activities that share a
// common originating context or purpose. An example could be all activities relating to a common project or event.
Context Item `jsonld:"context,omitempty"`
// MediaType when used on an Object, identifies the MIME media type of the value of the content property.
// If not specified, the content property is assumed to contain text/html content.
MediaType MimeType `jsonld:"mediaType,omitempty"`
// EndTime the date and time describing the actual or expected ending time of the object.
// When used with an Activity object, for instance, the endTime property specifies the moment
// the activity concluded or is expected to conclude.
EndTime time.Time `jsonld:"endTime,omitempty"`
// Generator identifies the entity (e.g. an application) that generated the object.
Generator Item `jsonld:"generator,omitempty"`
// Icon indicates an entity that describes an icon for this object.
// The image should have an aspect ratio of one (horizontal) to one (vertical)
// and should be suitable for presentation at a small size.
Icon Item `jsonld:"icon,omitempty"`
// Image indicates an entity that describes an image for this object.
// Unlike the icon property, there are no aspect ratio or display size limitations assumed.
Image Item `jsonld:"image,omitempty"`
// InReplyTo indicates one or more entities for which this object is considered a response.
InReplyTo Item `jsonld:"inReplyTo,omitempty"`
// Location indicates one or more physical or logical locations associated with the object.
Location Item `jsonld:"location,omitempty"`
// Preview identifies an entity that provides a preview of this object.
Preview Item `jsonld:"preview,omitempty"`
// Published the date and time at which the object was published
Published time.Time `jsonld:"published,omitempty"`
// Replies identifies a Collection containing objects considered to be responses to this object.
Replies Item `jsonld:"replies,omitempty"`
// StartTime the date and time describing the actual or expected starting time of the object.
// When used with an Activity object, for instance, the startTime property specifies
// the moment the activity began or is scheduled to begin.
StartTime time.Time `jsonld:"startTime,omitempty"`
// Summary a natural language summarization of the object encoded as HTML.
// *Multiple language tagged summaries may be provided.)
Summary NaturalLanguageValues `jsonld:"summary,omitempty,collapsible"`
// Tag one or more "tags" that have been associated with an objects. A tag can be any kind of Activity Pub Object.
// The key difference between attachment and tag is that the former implies association by inclusion,
// while the latter implies associated by reference.
Tag ItemCollection `jsonld:"tag,omitempty"`
// Updated the date and time at which the object was updated
Updated time.Time `jsonld:"updated,omitempty"`
// URL identifies one or more links to representations of the object
URL Item `jsonld:"url,omitempty"`
// To identifies an entity considered to be part of the public primary audience of an Activity Pub Object
To ItemCollection `jsonld:"to,omitempty"`
// Bto identifies anActivity Pub Object that is part of the private primary audience of this Activity Pub Object.
Bto ItemCollection `jsonld:"bto,omitempty"`
// CC identifies anActivity Pub Object that is part of the public secondary audience of this Activity Pub Object.
CC ItemCollection `jsonld:"cc,omitempty"`
// BCC identifies one or more Objects that are part of the private secondary audience of this Activity Pub Object.
BCC ItemCollection `jsonld:"bcc,omitempty"`
// Duration when the object describes a time-bound resource, such as an audio or video, a meeting, etc,
// the duration property indicates the object's approximate duration.
// The value must be expressed as an xsd:duration as defined by [ xmlschema11-2],
// section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
Duration time.Duration `jsonld:"duration,omitempty"`
// This is a list of all Like activities with this object as the object property, added as a side effect.
// The likes collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Likes Item `jsonld:"likes,omitempty"`
// This is a list of all Announce activities with this object as the object property, added as a side effect.
// The shares collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges
// of an authenticated user or as appropriate when no authentication is given.
Shares Item `jsonld:"shares,omitempty"`
// Source property is intended to convey some sort of source from which the content markup was derived,
// as a form of provenance, or to support future editing by clients.
// In general, clients do the conversion from source to content, not the other way around.
Source Source `jsonld:"source,omitempty"`
// FormerType On a Tombstone object, the formerType property identifies the type of the object that was deleted.
FormerType ActivityVocabularyType `jsonld:"formerType,omitempty"`
// Deleted On a Tombstone object, the deleted property is a timestamp for when the object was deleted.
Deleted time.Time `jsonld:"deleted,omitempty"`
}
// IsLink returns false for Tombstone objects
func (t Tombstone) IsLink() bool {
return false
}
// IsObject returns true for Tombstone objects
func (t Tombstone) IsObject() bool {
return true
}
// IsCollection returns false for Tombstone objects
func (t Tombstone) IsCollection() bool {
return false
}
// GetLink returns the IRI corresponding to the current Tombstone object
func (t Tombstone) GetLink() IRI {
return IRI(t.ID)
}
// GetType returns the type of the current Tombstone
func (t Tombstone) GetType() ActivityVocabularyType {
return t.Type
}
// GetID returns the ID corresponding to the current Tombstone
func (t Tombstone) GetID() ID {
return t.ID
}
// UnmarshalJSON decodes an incoming JSON document into the receiver object.
func (t *Tombstone) UnmarshalJSON(data []byte) error {
par := fastjson.Parser{}
val, err := par.ParseBytes(data)
if err != nil {
return err
}
return JSONLoadTombstone(val, t)
}
// MarshalJSON encodes the receiver object to a JSON document.
func (t Tombstone) MarshalJSON() ([]byte, error) {
b := make([]byte, 0)
notEmpty := false
JSONWrite(&b, '{')
OnObject(t, func(o *Object) error {
notEmpty = JSONWriteObjectValue(&b, *o)
return nil
})
if len(t.FormerType) > 0 {
if v, err := t.FormerType.MarshalJSON(); err == nil && len(v) > 0 {
notEmpty = JSONWriteProp(&b, "formerType", v) || notEmpty
}
}
if !t.Deleted.IsZero() {
notEmpty = JSONWriteTimeProp(&b, "deleted", t.Deleted) || notEmpty
}
if notEmpty {
JSONWrite(&b, '}')
return b, nil
}
return nil, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (t *Tombstone) UnmarshalBinary(data []byte) error {
return t.GobDecode(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (t Tombstone) MarshalBinary() ([]byte, error) {
return t.GobEncode()
}
// GobEncode
func (t Tombstone) GobEncode() ([]byte, error) {
mm := make(map[string][]byte)
hasData, err := mapTombstoneProperties(mm, t)
if err != nil {
return nil, err
}
if !hasData {
return []byte{}, nil
}
bb := bytes.Buffer{}
g := gob.NewEncoder(&bb)
if err := g.Encode(mm); err != nil {
return nil, err
}
return bb.Bytes(), nil
}
// GobDecode
func (t *Tombstone) GobDecode(data []byte) error {
if len(data) == 0 {
return nil
}
mm, err := gobDecodeObjectAsMap(data)
if err != nil {
return err
}
return unmapTombstoneProperties(mm, t)
}
// Recipients performs recipient de-duplication on the Tombstone object's To, Bto, CC and BCC properties
func (t *Tombstone) Recipients() ItemCollection {
return ItemCollectionDeduplication(&t.To, &t.Bto, &t.CC, &t.BCC, &t.Audience)
}
// Clean removes Bto and BCC properties
func (t *Tombstone) Clean() {
_ = OnObject(t, func(o *Object) error {
o.Clean()
return nil
})
}
func (t Tombstone) Format(s fmt.State, verb rune) {
switch verb {
case 's', 'v':
_, _ = fmt.Fprintf(s, "%T[%s] { formerType: %q }", t, t.Type, t.FormerType)
}
}
// ToTombstone
func ToTombstone(it Item) (*Tombstone, error) {
switch i := it.(type) {
case *Tombstone:
return i, nil
case Tombstone:
return &i, nil
case *Object:
return (*Tombstone)(unsafe.Pointer(i)), nil
case Object:
return (*Tombstone)(unsafe.Pointer(&i)), nil
default:
// NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes
typ := reflect.TypeOf(new(Tombstone))
if reflect.TypeOf(it).ConvertibleTo(typ) {
if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Tombstone); ok {
return i, nil
}
}
}
return nil, ErrorInvalidType[Tombstone](it)
}
type withTombstoneFn func(*Tombstone) error
// OnTombstone calls function fn on it Item if it can be asserted to type *Tombstone
//
// This function should be called if trying to access the Tombstone specific properties
// like "formerType" or "deleted".
// For the other properties OnObject should be used instead.
func OnTombstone(it Item, fn withTombstoneFn) error {
if it == nil {
return nil
}
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if err := OnTombstone(it, fn); err != nil {
return err
}
}
return nil
})
}
ob, err := ToTombstone(it)
if err != nil {
return err
}
return fn(ob)
}

397
vendor/github.com/go-ap/activitypub/typer.go generated vendored Normal file
View File

@ -0,0 +1,397 @@
package activitypub
import (
"path/filepath"
"strings"
"github.com/go-ap/errors"
)
// CollectionPath
type CollectionPath string
// CollectionPaths
type CollectionPaths []CollectionPath
const (
Unknown = CollectionPath("")
// Outbox
//
// https://www.w3.org/TR/activitypub/#outbox
//
// The outbox is discovered through the outbox property of an actor's profile.
// The outbox MUST be an OrderedCollection.
//
// The outbox stream contains activities the user has published, subject to the ability of the requestor
// to retrieve the activity (that is, the contents of the outbox are filtered by the permissions
// of the person reading it). If a user submits a request without Authorization the server should respond
// with all of the Public posts. This could potentially be all relevant objects published by the user,
// though the number of available items is left to the discretion of those implementing and deploying the server.
//
// The outbox accepts HTTP POST requests, with behaviour described in Client to Server Interactions.
Outbox = CollectionPath("outbox")
// Inbox
//
// https://www.w3.org/TR/activitypub/#inbox
//
// The inbox is discovered through the inbox property of an actor's profile. The inbox MUST be an OrderedCollection.
//
// The inbox stream contains all activities received by the actor. The server SHOULD filter content according
// to the requester's permission. In general, the owner of an inbox is likely to be able to access
// all of their inbox contents. Depending on access control, some other content may be public,
// whereas other content may require authentication for non-owner users, if they can access the inbox at all.
//
// The server MUST perform de-duplication of activities returned by the inbox. Duplication can occur
// if an activity is addressed both to an actor's followers, and a specific actor who also follows
// the recipient actor, and the server has failed to de-duplicate the recipients list.
// Such deduplication MUST be performed by comparing the id of the activities and dropping any activities already seen.
//
// The inboxes of actors on federated servers accepts HTTP POST requests, with behaviour described in Delivery.
// Non-federated servers SHOULD return a 405 Method Not Allowed upon receipt of a POST request.
Inbox = CollectionPath("inbox")
// Followers
//
// https://www.w3.org/TR/activitypub/#followers
//
// Every actor SHOULD have a followers collection. This is a list of everyone who has sent a Follow activity
// for the actor, added as a side effect. This is where one would find a list of all the actors that are following
// the actor. The followers collection MUST be either an OrderedCollection or a Collection and MAY be filtered
// on privileges of an authenticated user or as appropriate when no authentication is given.
//
// NOTE: Default for notification targeting
// The follow activity generally is a request to see the objects an actor creates.
// This makes the Followers collection an appropriate default target for delivery of notifications.
Followers = CollectionPath("followers")
// Following
//
// https://www.w3.org/TR/activitypub/#following
//
// Every actor SHOULD have a following collection. This is a list of everybody that the actor has followed,
// added as a side effect. The following collection MUST be either an OrderedCollection or a Collection
// and MAY be filtered on privileges of an authenticated user or as appropriate when no authentication is given.
Following = CollectionPath("following")
// Liked
//
// https://www.w3.org/TR/activitypub/#liked
//
// Every actor MAY have a liked collection. This is a list of every object from all of the actor's Like activities,
// added as a side effect. The liked collection MUST be either an OrderedCollection or a Collection and
// MAY be filtered on privileges of an authenticated user or as appropriate when no authentication is given.
Liked = CollectionPath("liked")
// Likes
//
// https://www.w3.org/TR/activitypub/#likes
//
// Every object MAY have a likes collection. This is a list of all Like activities with this object as the object
// property, added as a side effect. The likes collection MUST be either an OrderedCollection or a Collection
// and MAY be filtered on privileges of an authenticated user or as appropriate when no authentication is given.
//
// NOTE
// Care should be taken to not confuse the the likes collection with the similarly named but different liked
// collection. In sum:
//
// * liked: Specifically a property of actors. This is a collection of Like activities performed by the actor,
// added to the collection as a side effect of delivery to the outbox.
// * likes: May be a property of any object. This is a collection of Like activities referencing this object,
// added to the collection as a side effect of delivery to the inbox.
Likes = CollectionPath("likes")
// Shares
//
// https://www.w3.org/TR/activitypub/#shares
//
// Every object MAY have a shares collection. This is a list of all Announce activities with this object
// as the object property, added as a side effect. The shares collection MUST be either an OrderedCollection
// or a Collection and MAY be filtered on privileges of an authenticated user or as appropriate when
// no authentication is given.
Shares = CollectionPath("shares")
Replies = CollectionPath("replies") // activitystreams
)
var (
validActivityCollection = CollectionPaths{
Outbox,
Inbox,
Likes,
Shares,
Replies, // activitystreams
}
OfObject = CollectionPaths{
Likes,
Shares,
Replies,
}
OfActor = CollectionPaths{
Outbox,
Inbox,
Liked,
Following,
Followers,
}
ActivityPubCollections = CollectionPaths{
Outbox,
Inbox,
Liked,
Following,
Followers,
Likes,
Shares,
Replies,
}
)
func (t CollectionPaths) Contains(typ CollectionPath) bool {
for _, tt := range t {
if strings.EqualFold(string(typ), string(tt)) {
return true
}
}
return false
}
// Split splits the IRI in an actor IRI and its CollectionPath
// if the CollectionPath is found in the elements in the t CollectionPaths slice
func (t CollectionPaths) Split(i IRI) (IRI, CollectionPath) {
if u, err := i.URL(); err == nil {
maybeActor, maybeCol := filepath.Split(u.Path)
if len(maybeActor) == 0 {
return i, Unknown
}
tt := CollectionPath(maybeCol)
if !t.Contains(tt) {
tt = ""
}
u.Path = strings.TrimRight(maybeActor, "/")
iri := IRI(u.String())
return iri, tt
}
maybeActor, maybeCol := filepath.Split(i.String())
if len(maybeActor) == 0 {
return i, Unknown
}
tt := CollectionPath(maybeCol)
if !t.Contains(tt) {
return i, Unknown
}
maybeActor = strings.TrimRight(maybeActor, "/")
return IRI(maybeActor), tt
}
// IRIf formats an IRI from an existing IRI and the CollectionPath type
func IRIf(i IRI, t CollectionPath) IRI {
si := i.String()
s := strings.Builder{}
_, _ = s.WriteString(si)
if l := len(si); l == 0 || si[l-1] != '/' {
_, _ = s.WriteRune('/')
}
_, _ = s.WriteString(string(t))
return IRI(s.String())
}
// IRI gives us the IRI of the t CollectionPath type corresponding to the i Item,
// or generates a new one if not found.
func (t CollectionPath) IRI(i Item) IRI {
if IsNil(i) {
return IRIf("", t)
}
if IsObject(i) {
if it := t.Of(i); !IsNil(it) {
return it.GetLink()
}
}
return IRIf(i.GetLink(), t)
}
func (t CollectionPath) ofItemCollection(col ItemCollection) Item {
iriCol := make(ItemCollection, len(col))
for i, it := range col {
iriCol[i] = t.Of(it)
}
return iriCol
}
func (t CollectionPath) ofObject(ob *Object) Item {
var it Item
switch t {
case Likes:
it = ob.Likes
case Shares:
it = ob.Shares
case Replies:
it = ob.Replies
}
if it == nil {
it = t.ofIRI(ob.ID)
}
return it
}
func (t CollectionPath) ofActor(a *Actor) Item {
var it Item
switch t {
case Inbox:
it = a.Inbox
case Outbox:
it = a.Outbox
case Liked:
it = a.Liked
case Following:
it = a.Following
case Followers:
it = a.Followers
}
if it == nil {
it = t.ofIRI(a.ID)
}
return it
}
func (t CollectionPath) ofIRI(iri IRI) Item {
if len(iri) == 0 {
return nil
}
return iri.AddPath(string(t))
}
func (t CollectionPath) ofItem(i Item) Item {
var it Item
return it
}
// Of gives us the property of the i Item that corresponds to the t CollectionPath type.
func (t CollectionPath) Of(i Item) Item {
if IsNil(i) {
return nil
}
it := t.ofIRI(i.GetLink())
if IsItemCollection(i) {
OnItemCollection(i, func(col *ItemCollection) error {
it = t.ofItemCollection(*col)
return nil
})
}
if OfActor.Contains(t) && ActorTypes.Contains(i.GetType()) {
OnActor(i, func(a *Actor) error {
it = t.ofActor(a)
return nil
})
}
OnObject(i, func(o *Object) error {
it = t.ofObject(o)
return nil
})
return it
}
// OfActor returns the base IRI of received i, if i represents an IRI matching CollectionPath type t
func (t CollectionPath) OfActor(i IRI) (IRI, error) {
maybeActor, maybeCol := filepath.Split(i.String())
if strings.EqualFold(maybeCol, string(t)) {
maybeActor = strings.TrimRight(maybeActor, "/")
return IRI(maybeActor), nil
}
return EmptyIRI, errors.Newf("IRI does not represent a valid %s CollectionPath", t)
}
// Split returns the base IRI of received i, if i represents an IRI matching CollectionPath type t
func Split(i IRI) (IRI, CollectionPath) {
return ActivityPubCollections.Split(i)
}
func getValidActivityCollection(t CollectionPath) CollectionPath {
if validActivityCollection.Contains(t) {
return t
}
return Unknown
}
// ValidActivityCollection shows if the current ActivityPub end-point type is a valid one for handling Activities
func ValidActivityCollection(typ CollectionPath) bool {
return getValidActivityCollection(typ) != Unknown
}
var validObjectCollection = []CollectionPath{
Following,
Followers,
Liked,
}
func getValidObjectCollection(typ CollectionPath) CollectionPath {
for _, t := range validObjectCollection {
if strings.EqualFold(string(typ), string(t)) {
return t
}
}
return Unknown
}
// ValidActivityCollection shows if the current ActivityPub end-point type is a valid one for handling Objects
func ValidObjectCollection(typ CollectionPath) bool {
return getValidObjectCollection(typ) != Unknown
}
func getValidCollection(typ CollectionPath) CollectionPath {
if typ := getValidActivityCollection(typ); typ != Unknown {
return typ
}
if typ := getValidObjectCollection(typ); typ != Unknown {
return typ
}
return Unknown
}
func ValidCollection(typ CollectionPath) bool {
return getValidCollection(typ) != Unknown
}
func ValidCollectionIRI(i IRI) bool {
_, t := Split(i)
return getValidCollection(t) != Unknown
}
// AddTo adds CollectionPath type IRI on the corresponding property of the i Item
func (t CollectionPath) AddTo(i Item) (IRI, bool) {
if IsNil(i) || !i.IsObject() {
return NilIRI, false
}
status := false
var iri IRI
if OfActor.Contains(t) {
OnActor(i, func(a *Actor) error {
if status = t == Inbox && IsNil(a.Inbox); status {
a.Inbox = IRIf(a.GetLink(), t)
iri = a.Inbox.GetLink()
} else if status = t == Outbox && IsNil(a.Outbox); status {
a.Outbox = IRIf(a.GetLink(), t)
iri = a.Outbox.GetLink()
} else if status = t == Liked && IsNil(a.Liked); status {
a.Liked = IRIf(a.GetLink(), t)
iri = a.Liked.GetLink()
} else if status = t == Following && IsNil(a.Following); status {
a.Following = IRIf(a.GetLink(), t)
iri = a.Following.GetLink()
} else if status = t == Followers && IsNil(a.Followers); status {
a.Followers = IRIf(a.GetLink(), t)
iri = a.Followers.GetLink()
}
return nil
})
} else if OfObject.Contains(t) {
OnObject(i, func(o *Object) error {
if status = t == Likes && IsNil(o.Likes); status {
o.Likes = IRIf(o.GetLink(), t)
iri = o.Likes.GetLink()
} else if status = t == Shares && IsNil(o.Shares); status {
o.Shares = IRIf(o.GetLink(), t)
iri = o.Shares.GetLink()
} else if status = t == Replies && IsNil(o.Replies); status {
o.Replies = IRIf(o.GetLink(), t)
iri = o.Replies.GetLink()
}
return nil
})
} else {
iri = IRIf(i.GetLink(), t)
}
return iri, status
}

66
vendor/github.com/go-ap/activitypub/types.go generated vendored Normal file
View File

@ -0,0 +1,66 @@
package activitypub
// ActivityVocabularyTypes is a type alias for a slice of ActivityVocabularyType elements
type ActivityVocabularyTypes []ActivityVocabularyType
// Types contains all valid types in the ActivityPub vocabulary
var Types = ActivityVocabularyTypes{
LinkType,
MentionType,
ArticleType,
AudioType,
DocumentType,
EventType,
ImageType,
NoteType,
PageType,
PlaceType,
ProfileType,
RelationshipType,
TombstoneType,
VideoType,
QuestionType,
CollectionType,
OrderedCollectionType,
CollectionPageType,
OrderedCollectionPageType,
ApplicationType,
GroupType,
OrganizationType,
PersonType,
ServiceType,
AcceptType,
AddType,
AnnounceType,
BlockType,
CreateType,
DeleteType,
DislikeType,
FlagType,
FollowType,
IgnoreType,
InviteType,
JoinType,
LeaveType,
LikeType,
ListenType,
MoveType,
OfferType,
RejectType,
ReadType,
RemoveType,
TentativeRejectType,
TentativeAcceptType,
UndoType,
UpdateType,
ViewType,
ArriveType,
TravelType,
QuestionType,
}

16
vendor/github.com/go-ap/activitypub/validation.go generated vendored Normal file
View File

@ -0,0 +1,16 @@
package activitypub
// ValidationErrors is an aggregated error interface that allows
// a Validator implementation to return all possible errors.
type ValidationErrors interface {
error
Errors() []error
Add(error)
}
// Validator is the interface that needs to be implemented by objects that
// provide a validation mechanism for incoming ActivityPub Objects or IRIs
// against an external set of rules.
type Validator interface {
Validate(receiver IRI, incoming Item) (bool, ValidationErrors)
}

16
vendor/github.com/go-ap/errors/.build.yml generated vendored Normal file
View File

@ -0,0 +1,16 @@
image: archlinux
packages:
- go
sources:
- https://github.com/go-ap/errors
environment:
GO111MODULE: 'on'
tasks:
- tests: |
cd errors
make test
- coverage: |
set -a +x
cd errors && make coverage
GIT_SHA=$(git rev-parse --verify HEAD)
GIT_BRANCH=$(git name-rev --name-only HEAD)

14
vendor/github.com/go-ap/errors/.gitignore generated vendored Normal file
View File

@ -0,0 +1,14 @@
# Gogland
.idea/
# Binaries for programs and plugins
*.so
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tools
*.out
*.coverprofile
*pkg

21
vendor/github.com/go-ap/errors/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Golang ActitvityPub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

22
vendor/github.com/go-ap/errors/Makefile generated vendored Normal file
View File

@ -0,0 +1,22 @@
GO ?= go
TEST := $(GO) test
TEST_FLAGS ?= -v
TEST_TARGET ?= ./...
GO111MODULE=on
PROJECT_NAME := $(shell basename $(PWD))
.PHONY: test coverage clean download
download:
$(GO) mod download all
test: download
$(TEST) $(TEST_FLAGS) $(TEST_TARGET)
coverage: TEST_TARGET := .
coverage: TEST_FLAGS += -covermode=count -coverprofile $(PROJECT_NAME).coverprofile
coverage: test
clean:
$(RM) -v *.coverprofile

6
vendor/github.com/go-ap/errors/README.md generated vendored Normal file
View File

@ -0,0 +1,6 @@
[![MIT Licensed](https://img.shields.io/github/license/go-ap/errors.svg)](https://raw.githubusercontent.com/go-ap/errors/master/LICENSE)
[![Build Status](https://builds.sr.ht/~mariusor/errors.svg)](https://builds.sr.ht/~mariusor/errors)
[![Test Coverage](https://img.shields.io/codecov/c/github/go-ap/errors.svg)](https://codecov.io/gh/go-ap/errors)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-ap/errors)](https://goreportcard.com/report/github.com/go-ap/errors)
<!--[![Codacy Badge](https://api.codacy.com/project/badge/Grade/29664f7ae6c643bca76700143e912cd3)](https://www.codacy.com/app/go-ap/errors/dashboard)-->

82
vendor/github.com/go-ap/errors/decoding.go generated vendored Normal file
View File

@ -0,0 +1,82 @@
package errors
import (
"github.com/valyala/fastjson"
)
func UnmarshalJSON(data []byte) ([]Error, error) {
if len(data) == 0 {
return nil, nil
}
p := fastjson.Parser{}
val, err := p.ParseBytes(data)
if err != nil {
return nil, err
}
v := val.Get("errors")
if v == nil {
return nil, wrap(nil, "invalid errors array")
}
items := make([]Error, 0)
switch v.Type() {
case fastjson.TypeArray:
for _, v := range v.GetArray() {
status := v.GetInt("status")
localErr := errorFromStatus(status)
if err := localErr.UnmarshalJSON([]byte(v.String())); err == nil {
items = append(items, localErr)
}
}
return items, err
case fastjson.TypeObject:
status := v.GetInt("status")
localErr := errorFromStatus(status)
if err := localErr.UnmarshalJSON(data); err == nil {
items = append(items, localErr)
}
case fastjson.TypeString:
it := new(Err)
it.m = string(data)
items = append(items, it)
}
return items, nil
}
func (e *Err) UnmarshalJSON(data []byte) error {
if m := fastjson.GetString(data, "message"); len(m) > 0 {
e.m = m
}
return nil
}
func (n *notFound) UnmarshalJSON(data []byte) error {
return n.Err.UnmarshalJSON(data)
}
func (m *methodNotAllowed) UnmarshalJSON(data []byte) error {
return m.Err.UnmarshalJSON(data)
}
func (n *notValid) UnmarshalJSON(data []byte) error {
return n.Err.UnmarshalJSON(data)
}
func (f *forbidden) UnmarshalJSON(data []byte) error {
return f.Err.UnmarshalJSON(data)
}
func (n *notImplemented) UnmarshalJSON(data []byte) error {
return n.Err.UnmarshalJSON(data)
}
func (b *badRequest) UnmarshalJSON(data []byte) error {
return b.Err.UnmarshalJSON(data)
}
func (u *unauthorized) UnmarshalJSON(data []byte) error {
return u.Err.UnmarshalJSON(data)
}
func (n *notSupported) UnmarshalJSON(data []byte) error {
return n.Err.UnmarshalJSON(data)
}
func (t *timeout) UnmarshalJSON(data []byte) error {
return t.Err.UnmarshalJSON(data)
}
func (b *badGateway) UnmarshalJSON(data []byte) error {
return b.Err.UnmarshalJSON(data)
}

165
vendor/github.com/go-ap/errors/errors.go generated vendored Normal file
View File

@ -0,0 +1,165 @@
package errors
import (
"errors"
"fmt"
"io"
"strings"
)
// Export a number of functions or variables from package errors.
var (
As = errors.As
Is = errors.Is
Unwrap = errors.Unwrap
)
// IncludeBacktrace is a static variable that decides if when creating an error we store the backtrace with it.
var IncludeBacktrace = true
// Err is our custom error type that can store backtrace, file and line number
type Err struct {
m string
c error
t stack
}
func (e Err) Format(s fmt.State, verb rune) {
switch verb {
case 's':
io.WriteString(s, e.m)
switch {
case s.Flag('+'):
if e.c != nil {
io.WriteString(s, ": ")
io.WriteString(s, fmt.Sprintf("%+s", e.c))
}
}
case 'v':
e.Format(s, 's')
switch {
case s.Flag('+'):
if e.t != nil {
io.WriteString(s, "\n\t")
e.t.Format(s, 'v')
}
}
}
}
// Error implements the error interface
func (e Err) Error() string {
if IncludeBacktrace {
return e.m
}
s := strings.Builder{}
s.WriteString(e.m)
if ch := errors.Unwrap(e); ch != nil {
s.WriteString(": ")
s.WriteString(ch.Error())
}
return s.String()
}
// Unwrap implements the errors.Wrapper interface
func (e Err) Unwrap() error {
return e.c
}
// StackTrace returns the stack trace as returned by the debug.Stack function
func (e Err) StackTrace() StackTrace {
return e.t.StackTrace()
}
// Annotatef wraps an error with new message
func Annotatef(e error, s string, args ...interface{}) *Err {
err := wrap(e, s, args...)
return &err
}
// Newf creaates a new error
func Newf(s string, args ...interface{}) *Err {
err := wrap(nil, s, args...)
return &err
}
// Errorf is an alias for Newf
func Errorf(s string, args ...interface{}) error {
err := wrap(nil, s, args...)
return &err
}
// As implements support for errors.As
func (e *Err) As(err interface{}) bool {
switch x := err.(type) {
case **Err:
*x = e
case *Err:
*x = *e
default:
return false
}
return true
}
type StackTracer interface {
StackTrace() StackTrace
}
// ancestorOfCause returns true if the caller looks to be an ancestor of the given stack
// trace. We check this by seeing whether our stack prefix-matches the cause stack, which
// should imply the error was generated directly from our goroutine.
func ancestorOfCause(ourStack stack, causeStack StackTrace) bool {
// Stack traces are ordered such that the deepest frame is first. We'll want to check
// for prefix matching in reverse.
//
// As an example, imagine we have a prefix-matching stack for ourselves:
// [
// "github.com/go-ap/processing/processing.Validate",
// "testing.tRunner",
// "runtime.goexit"
// ]
//
// We'll want to compare this against an error cause that will have happened further
// down the stack. An example stack trace from such an error might be:
// [
// "github.com/go-ap/errors/errors.New",
// "testing.tRunner",
// "runtime.goexit"
// ]
//
// Their prefix matches, but we'll have to handle the match carefully as we need to match
// from back to forward.
// We can't possibly prefix match if our stack is larger than the cause stack.
if len(ourStack) > len(causeStack) {
return false
}
// We know the sizes are compatible, so compare program counters from back to front.
for idx := 0; idx < len(ourStack); idx++ {
if ourStack[len(ourStack)-1] != (uintptr)(causeStack[len(causeStack)-1]) {
return false
}
}
return true
}
func wrap(e error, s string, args ...interface{}) Err {
err := Err{
c: e,
m: fmt.Sprintf(s, args...),
}
if IncludeBacktrace {
causeStackTracer := new(StackTracer)
// If our cause has set a stack trace, and that trace is a child of our own function
// as inferred by prefix matching our current program counter stack, then we only want
// to decorate the error message rather than add a redundant stack trace.
stack := callers(2)
if !(As(e, causeStackTracer) && ancestorOfCause(*stack, (*causeStackTracer).StackTrace())) {
err.t = *stack
}
}
return err
}

944
vendor/github.com/go-ap/errors/http.go generated vendored Normal file
View File

@ -0,0 +1,944 @@
package errors
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/go-ap/jsonld"
)
const errorsPackageName = "github.com/go-ap/errors"
const runtimeDebugPackageName = "runtime/debug"
type Error interface {
error
json.Unmarshaler
}
type notFound struct {
Err
s int
}
type methodNotAllowed struct {
Err
s int
}
type notValid struct {
Err
s int
}
type forbidden struct {
Err
s int
}
type notImplemented struct {
Err
s int
}
type conflict struct {
Err
s int
}
type gone struct {
Err
s int
}
type badRequest struct {
Err
s int
}
type unauthorized struct {
Err
s int
challenge string
}
type notSupported struct {
Err
s int
}
type timeout struct {
Err
s int
}
type badGateway struct {
Err
s int
}
func wrapErr(err error, s string, args ...interface{}) Err {
e := Annotatef(err, s, args...)
asErr := Err{}
As(e, &asErr)
return asErr
}
func FromResponse(resp *http.Response) error {
if resp.StatusCode < http.StatusBadRequest {
return nil
}
body := make([]byte, 0)
defer resp.Body.Close()
body, _ = ioutil.ReadAll(resp.Body)
var withStatus error
errors, err := UnmarshalJSON(body)
if err != nil {
return AnnotateFromStatus(nil, resp.StatusCode, string(body))
}
for _, err := range errors {
if err == nil {
withStatus = err
}
withStatus = Annotatef(err, err.Error())
}
return AnnotateFromStatus(withStatus, resp.StatusCode, resp.Status)
}
func AnnotateFromStatus(err error, status int, s string, args ...interface{}) error {
switch status {
case http.StatusBadRequest:
return NewBadRequest(err, s, args...)
case http.StatusUnauthorized:
return NewUnauthorized(err, s, args...)
// http.StatusPaymentRequired
case http.StatusForbidden:
return NewForbidden(err, s, args...)
case http.StatusNotFound:
return NewNotFound(err, s, args...)
case http.StatusMethodNotAllowed:
return NewMethodNotAllowed(err, s, args...)
case http.StatusNotAcceptable:
return NewNotValid(err, s, args...)
// case http.StatusProxyAuthRequired
// case http.StatusRequestTimeout
case http.StatusConflict:
return NewConflict(err, s, args...)
case http.StatusGone:
return NewGone(err, s, args...)
// case http.StatusLengthRequres
// case http.StatusPreconditionFailed
// case http.StatusRequestEntityTooLarge
// case http.StatusRequestURITooLong
// TODO(marius): http.StatusUnsupportedMediaType
// case http.StatusRequestedRangeNotSatisfiable
// case http.StatusExpectationFailed
// case http.StatusTeapot
// case http.StatusMisdirectedRequest
// case http.StatusUnprocessableEntity
// case http.StatusLocked
// case http.StatusFailedDependency
// case http.StatusTooEarly
// case http.StatusTooManyRequests
// case http.StatusRequestHeaderFieldsTooLarge
// case http.StatusUnavailableForLegalReason
// case http.StatusInternalServerError
case http.StatusNotImplemented:
return NewNotImplemented(err, s, args...)
case http.StatusBadGateway:
return NewBadGateway(err, s, args...)
// case http.StatusServiceUnavailable
// case http.StatusGatewayTimeout
case http.StatusHTTPVersionNotSupported:
return NewNotSupported(err, s, args...)
case http.StatusGatewayTimeout:
return NewTimeout(err, s, args...)
}
return Annotatef(err, s, args...)
}
func NewFromStatus(status int, s string, args ...interface{}) error {
switch status {
case http.StatusBadRequest:
return BadRequestf(s, args...)
case http.StatusUnauthorized:
return Unauthorizedf(s, args...)
// http.StatusPaymentRequired
case http.StatusForbidden:
return Forbiddenf(s, args...)
case http.StatusNotFound:
return NotFoundf(s, args...)
case http.StatusMethodNotAllowed:
return MethodNotAllowedf(s, args...)
case http.StatusNotAcceptable:
return NotValidf(s, args...)
// case http.StatusProxyAuthRequired
// case http.StatusRequestTimeout
case http.StatusConflict:
return Conflictf(s, args...)
case http.StatusGone:
return Gonef(s, args...)
// case http.StatusLengthRequres
// case http.StatusPreconditionFailed
// case http.StatusRequestEntityTooLarge
// case http.StatusRequestURITooLong
// TODO(marius): http.StatusUnsupportedMediaType
// case http.StatusRequestedRangeNotSatisfiable
// case http.StatusExpectationFailed
// case http.StatusTeapot
// case http.StatusMisdirectedRequest
// case http.StatusUnprocessableEntity
// case http.StatusLocked
// case http.StatusFailedDependency
// case http.StatusTooEarly
// case http.StatusTooManyRequests
// case http.StatusRequestHeaderFieldsTooLarge
// case http.StatusUnavailableForLegalReason
// case http.StatusInternalServerError
case http.StatusNotImplemented:
return NotImplementedf(s, args...)
case http.StatusBadGateway:
return BadGatewayf(s, args...)
// case http.StatusServiceUnavailable
// case http.StatusGatewayTimeout
case http.StatusHTTPVersionNotSupported:
return NotSupportedf(s, args...)
case http.StatusGatewayTimeout:
return Timeoutf(s, args...)
}
return Newf(s, args...)
}
func WrapWithStatus(status int, err error, s string, args ...interface{}) error {
switch status {
case http.StatusBadRequest:
return NewBadRequest(err, s, args...)
case http.StatusUnauthorized:
return NewUnauthorized(err, s, args...)
// http.StatusPaymentRequired
case http.StatusForbidden:
return NewForbidden(err, s, args...)
case http.StatusNotFound:
return NewNotFound(err, s, args...)
case http.StatusMethodNotAllowed:
return NewMethodNotAllowed(err, s, args...)
case http.StatusNotAcceptable:
return NewNotValid(err, s, args...)
// case http.StatusProxyAuthRequired
// case http.StatusRequestTimeout
case http.StatusConflict:
return NewConflict(err, s, args...)
case http.StatusGone:
return NewGone(err, s, args...)
// case http.StatusLengthRequres
// case http.StatusPreconditionFailed
// case http.StatusRequestEntityTooLarge
// case http.StatusRequestURITooLong
// TODO(marius): http.StatusUnsupportedMediaType
// case http.StatusRequestedRangeNotSatisfiable
// case http.StatusExpectationFailed
// case http.StatusTeapot
// case http.StatusMisdirectedRequest
// case http.StatusUnprocessableEntity
// case http.StatusLocked
// case http.StatusFailedDependency
// case http.StatusTooEarly
// case http.StatusTooManyRequests
// case http.StatusRequestHeaderFieldsTooLarge
// case http.StatusUnavailableForLegalReason
// case http.StatusInternalServerError
case http.StatusNotImplemented:
return NewNotImplemented(err, s, args...)
case http.StatusBadGateway:
return NewBadGateway(err, s, args...)
// case http.StatusServiceUnavailable
// case http.StatusGatewayTimeout
case http.StatusHTTPVersionNotSupported:
return NewNotSupported(err, s, args...)
case http.StatusGatewayTimeout:
return NewTimeout(err, s, args...)
}
return wrapErr(err, s, args...)
}
func NotFoundf(s string, args ...interface{}) *notFound {
return &notFound{Err: wrapErr(nil, s, args...), s: http.StatusNotFound}
}
func NewNotFound(e error, s string, args ...interface{}) *notFound {
return &notFound{Err: wrapErr(e, s, args...), s: http.StatusNotFound}
}
func MethodNotAllowedf(s string, args ...interface{}) *methodNotAllowed {
return &methodNotAllowed{Err: wrapErr(nil, s, args...), s: http.StatusMethodNotAllowed}
}
func NewMethodNotAllowed(e error, s string, args ...interface{}) *methodNotAllowed {
return &methodNotAllowed{Err: wrapErr(e, s, args...), s: http.StatusMethodNotAllowed}
}
func NotValidf(s string, args ...interface{}) *notValid {
return &notValid{Err: wrapErr(nil, s, args...)}
}
func NewNotValid(e error, s string, args ...interface{}) *notValid {
return &notValid{Err: wrapErr(e, s, args...)}
}
func Conflictf(s string, args ...interface{}) *conflict {
return &conflict{Err: wrapErr(nil, s, args...), s: http.StatusConflict}
}
func NewConflict(e error, s string, args ...interface{}) *conflict {
return &conflict{Err: wrapErr(e, s, args...), s: http.StatusConflict}
}
func Gonef(s string, args ...interface{}) *gone {
return &gone{Err: wrapErr(nil, s, args...), s: http.StatusGone}
}
func NewGone(e error, s string, args ...interface{}) *gone {
return &gone{Err: wrapErr(e, s, args...), s: http.StatusGone}
}
func Forbiddenf(s string, args ...interface{}) *forbidden {
return &forbidden{Err: wrapErr(nil, s, args...), s: http.StatusForbidden}
}
func NewForbidden(e error, s string, args ...interface{}) *forbidden {
return &forbidden{Err: wrapErr(e, s, args...), s: http.StatusForbidden}
}
func NotImplementedf(s string, args ...interface{}) *notImplemented {
return &notImplemented{Err: wrapErr(nil, s, args...), s: http.StatusNotImplemented}
}
func NewNotImplemented(e error, s string, args ...interface{}) *notImplemented {
return &notImplemented{Err: wrapErr(e, s, args...), s: http.StatusNotImplemented}
}
func BadRequestf(s string, args ...interface{}) *badRequest {
return &badRequest{Err: wrapErr(nil, s, args...), s: http.StatusBadRequest}
}
func NewBadRequest(e error, s string, args ...interface{}) *badRequest {
return &badRequest{Err: wrapErr(e, s, args...), s: http.StatusBadRequest}
}
func Unauthorizedf(s string, args ...interface{}) *unauthorized {
return &unauthorized{Err: wrapErr(nil, s, args...), s: http.StatusUnauthorized}
}
func NewUnauthorized(e error, s string, args ...interface{}) *unauthorized {
return &unauthorized{Err: wrapErr(e, s, args...), s: http.StatusUnauthorized}
}
func NotSupportedf(s string, args ...interface{}) *notSupported {
return &notSupported{Err: wrapErr(nil, s, args...), s: http.StatusHTTPVersionNotSupported}
}
func NewNotSupported(e error, s string, args ...interface{}) *notSupported {
return &notSupported{Err: wrapErr(e, s, args...), s: http.StatusHTTPVersionNotSupported}
}
func Timeoutf(s string, args ...interface{}) *timeout {
return &timeout{Err: wrapErr(nil, s, args...), s: http.StatusRequestTimeout}
}
func NewTimeout(e error, s string, args ...interface{}) *timeout {
return &timeout{Err: wrapErr(e, s, args...), s: http.StatusRequestTimeout}
}
func BadGatewayf(s string, args ...interface{}) *badGateway {
return &badGateway{Err: wrapErr(nil, s, args...), s: http.StatusBadGateway}
}
func NewBadGateway(e error, s string, args ...interface{}) *badGateway {
return &badGateway{Err: wrapErr(e, s, args...), s: http.StatusBadGateway}
}
func IsBadRequest(e error) bool {
_, okp := e.(*badRequest)
_, oks := e.(badRequest)
return okp || oks || As(e, &badRequest{})
}
func IsForbidden(e error) bool {
_, okp := e.(*forbidden)
_, oks := e.(forbidden)
return okp || oks || As(e, &forbidden{})
}
func IsNotSupported(e error) bool {
_, okp := e.(*notSupported)
_, oks := e.(notSupported)
return okp || oks
}
func IsConflict(e error) bool {
_, okp := e.(*conflict)
_, oks := e.(conflict)
return okp || oks || As(e, &conflict{})
}
func IsGone(e error) bool {
_, okp := e.(*gone)
_, oks := e.(gone)
return okp || oks || As(e, &gone{})
}
func IsMethodNotAllowed(e error) bool {
_, okp := e.(*methodNotAllowed)
_, oks := e.(methodNotAllowed)
return okp || oks || As(e, &methodNotAllowed{})
}
func IsNotFound(e error) bool {
_, okp := e.(*notFound)
_, oks := e.(notFound)
return okp || oks || As(e, &notFound{})
}
func IsNotImplemented(e error) bool {
_, okp := e.(*notImplemented)
_, oks := e.(notImplemented)
return okp || oks || As(e, &notImplemented{})
}
func IsUnauthorized(e error) bool {
_, okp := e.(*unauthorized)
_, oks := e.(unauthorized)
return okp || oks || As(e, &unauthorized{})
}
func IsTimeout(e error) bool {
_, okp := e.(*timeout)
_, oks := e.(timeout)
return okp || oks || As(e, &timeout{})
}
func IsNotValid(e error) bool {
_, okp := e.(*notValid)
_, oks := e.(notValid)
return okp || oks || As(e, &notValid{})
}
func IsBadGateway(e error) bool {
_, okp := e.(*badGateway)
_, oks := e.(badGateway)
return okp || oks || As(e, &badGateway{})
}
func (n notFound) Is(e error) bool {
return IsNotFound(e)
}
func (n notValid) Is(e error) bool {
return IsNotValid(e)
}
func (n notImplemented) Is(e error) bool {
return IsNotImplemented(e)
}
func (n notSupported) Is(e error) bool {
return IsNotSupported(e)
}
func (b badRequest) Is(e error) bool {
return IsBadRequest(e)
}
func (t timeout) Is(e error) bool {
return IsTimeout(e)
}
func (u unauthorized) Is(e error) bool {
return IsUnauthorized(e)
}
func (m methodNotAllowed) Is(e error) bool {
return IsMethodNotAllowed(e)
}
func (f forbidden) Is(e error) bool {
return IsForbidden(e)
}
func (b badGateway) Is(e error) bool {
return IsBadGateway(e)
}
func (g gone) Is(e error) bool {
return IsGone(e)
}
func (c conflict) Is(e error) bool {
return IsConflict(e)
}
func (n notFound) Unwrap() error {
return n.Err.Unwrap()
}
func (n notValid) Unwrap() error {
return n.Err.Unwrap()
}
func (n notImplemented) Unwrap() error {
return n.Err.Unwrap()
}
func (n notSupported) Unwrap() error {
return n.Err.Unwrap()
}
func (b badRequest) Unwrap() error {
return b.Err.Unwrap()
}
func (t timeout) Unwrap() error {
return t.Err.Unwrap()
}
func (u unauthorized) Unwrap() error {
return u.Err.Unwrap()
}
func (m methodNotAllowed) Unwrap() error {
return m.Err.Unwrap()
}
func (f forbidden) Unwrap() error {
return f.Err.Unwrap()
}
func (b badGateway) Unwrap() error {
return b.Err.Unwrap()
}
func (g gone) Unwrap() error {
return g.Err.Unwrap()
}
func (c conflict) Unwrap() error {
return c.Err.Unwrap()
}
// As is used by the errors.As() function to coerce the method's parameter to the one of the receiver
//
// if the underlying logic of the receiver's type can understand it.
//
// In this case we're converting a notFound to its underlying type Err.
func (n *notFound) As(err interface{}) bool {
switch x := err.(type) {
case **notFound:
*x = n
case *notFound:
*x = *n
case *Err:
return n.Err.As(x)
default:
return false
}
return true
}
// As is used by the errors.As() function to coerce the method's parameter to the one of the receiver
//
// if the underlying logic of the receiver's type can understand it.
//
// In this case we're converting a notValid to its underlying type Err.
func (n *notValid) As(err interface{}) bool {
switch x := err.(type) {
case **notValid:
*x = n
case *notValid:
*x = *n
case *Err:
return n.Err.As(x)
default:
return false
}
return true
}
// As is used by the errors.As() function to coerce the method's parameter to the one of the receiver
//
// if the underlying logic of the receiver's type can understand it.
//
// In this case we're converting a notImplemented to its underlying type Err.
func (n *notImplemented) As(err interface{}) bool {
switch x := err.(type) {
case **notImplemented:
*x = n
case *notImplemented:
*x = *n
case *Err:
return n.Err.As(x)
default:
return false
}
return true
}
// As is used by the errors.As() function to coerce the method's parameter to the one of the receiver
//
// if the underlying logic of the receiver's type can understand it.
//
// In this case we're converting a notSupported to its underlying type Err.
func (n *notSupported) As(err interface{}) bool {
switch x := err.(type) {
case **notSupported:
*x = n
case *notSupported:
*x = *n
case *Err:
return n.Err.As(x)
default:
return false
}
return true
}
// As is used by the errors.As() function to coerce the method's parameter to the one of the receiver
//
// if the underlying logic of the receiver's type can understand it.
//
// In this case we're converting a badRequest to its underlying type Err.
func (b *badRequest) As(err interface{}) bool {
switch x := err.(type) {
case **badRequest:
*x = b
case *badRequest:
*x = *b
case *Err:
return b.Err.As(x)
default:
return false
}
return true
}
// As is used by the errors.As() function to coerce the method's parameter to the one of the receiver
//
// if the underlying logic of the receiver's type can understand it.
//
// In this case we're converting a timeout to its underlying type Err.
func (t *timeout) As(err interface{}) bool {
switch x := err.(type) {
case **timeout:
*x = t
case *timeout:
*x = *t
case *Err:
return t.Err.As(x)
default:
return false
}
return true
}
// As is used by the errors.As() function to coerce the method's parameter to the one of the receiver
//
// if the underlying logic of the receiver's type can understand it.
//
// In this case we're converting a unauthorized to its underlying type Err.
func (u *unauthorized) As(err interface{}) bool {
switch x := err.(type) {
case **unauthorized:
*x = u
case *unauthorized:
*x = *u
case *Err:
return u.Err.As(x)
default:
return false
}
return true
}
// As is used by the errors.As() function to coerce the method's parameter to the one of the receiver
//
// if the underlying logic of the receiver's type can understand it.
//
// In this case we're converting a methodNotAllowed to its underlying type Err.
func (m *methodNotAllowed) As(err interface{}) bool {
switch x := err.(type) {
case **methodNotAllowed:
*x = m
case *methodNotAllowed:
*x = *m
case *Err:
return m.Err.As(x)
default:
return false
}
return true
}
// As is used by the errors.As() function to coerce the method's parameter to the one of the receiver
//
// if the underlying logic of the receiver's type can understand it.
//
// In this case we're converting a forbidden to its underlying type Err.
func (f *forbidden) As(err interface{}) bool {
switch x := err.(type) {
case **forbidden:
*x = f
case *forbidden:
*x = *f
case *Err:
return f.Err.As(x)
default:
return false
}
return true
}
// As is used by the errors.As() function to coerce the method's parameter to the one of the receiver
//
// if the underlying logic of the receiver's type can understand it.
//
// In this case we're converting a badGateway to its underlying type Err.
func (b *badGateway) As(err interface{}) bool {
switch x := err.(type) {
case **badGateway:
*x = b
case *badGateway:
*x = *b
case *Err:
return b.Err.As(x)
default:
return false
}
return true
}
// As is used by the errors.As() function to coerce the method's parameter to the one of the receiver
//
// if the underlying logic of the receiver's type can understand it.
//
// In this case we're converting a gone error to its underlying type Err.
func (g *gone) As(err interface{}) bool {
switch x := err.(type) {
case **gone:
*x = g
case *gone:
*x = *g
case *Err:
return g.Err.As(x)
default:
return false
}
return true
}
// As is used by the errors.As() function to coerce the method's parameter to the one of the receiver
//
// if the underlying logic of the receiver's type can understand it.
//
// In this case we're converting a conflict error to its underlying type Err.
func (c *conflict) As(err interface{}) bool {
switch x := err.(type) {
case **conflict:
*x = c
case *conflict:
*x = *c
case *Err:
return c.Err.As(x)
default:
return false
}
return true
}
// Challenge adds a challenge token to be added to the HTTP response
func (u *unauthorized) Challenge(c string) *unauthorized {
u.challenge = c
return u
}
// Challenge returns the challenge of the err parameter if it's an unauthorized type error
func Challenge(err error) string {
un := unauthorized{}
if ok := As(err, &un); ok {
return un.challenge
}
return ""
}
// ErrorHandlerFn
type ErrorHandlerFn func(http.ResponseWriter, *http.Request) error
// ServeHTTP implements the http.Handler interface for the ItemHandlerFn type
func (h ErrorHandlerFn) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var dat []byte
var status int
if err := h(w, r); err != nil {
if status, dat = RenderErrors(r, err); status == 0 {
status = http.StatusInternalServerError
}
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
w.Write(dat)
}
// HandleError is a generic method to return an HTTP handler that passes an error up the chain
func HandleError(e error) ErrorHandlerFn {
return func(w http.ResponseWriter, r *http.Request) error {
return e
}
}
// NotFound is a generic method to return an 404 error HTTP handler that
var NotFound = ErrorHandlerFn(func(w http.ResponseWriter, r *http.Request) error {
return NotFoundf("%s not found", r.URL.Path)
})
type Http struct {
Code int `jsonld:"status,omitempty"`
Message string `jsonld:"message"`
Trace StackTrace `jsonld:"trace,omitempty"`
}
func HttpErrors(err error) []Http {
https := make([]Http, 0)
load := func(err error) Http {
var trace StackTrace
var msg string
switch e := err.(type) {
case *Err:
msg = e.Error()
if IncludeBacktrace {
trace = e.StackTrace()
}
default:
local := new(Err)
if ok := As(err, local); ok {
if IncludeBacktrace {
trace = local.StackTrace()
}
}
msg = err.Error()
}
return Http{
Message: msg,
Trace: trace,
Code: HttpStatus(err),
}
}
https = append(https, load(err))
for {
if err = Unwrap(err); err != nil {
https = append(https, load(err))
} else {
break
}
}
return https
}
func HttpStatus(e error) int {
if IsBadRequest(e) {
return http.StatusBadRequest
}
if IsUnauthorized(e) {
return http.StatusUnauthorized
}
// http.StatusPaymentRequired
if IsForbidden(e) {
return http.StatusForbidden
}
if IsNotFound(e) {
return http.StatusNotFound
}
if IsMethodNotAllowed(e) {
return http.StatusMethodNotAllowed
}
if IsNotValid(e) {
return http.StatusNotAcceptable
}
// http.StatusProxyAuthRequired
// http.StatusRequestTimeout
if IsConflict(e) {
return http.StatusConflict
}
if IsGone(e) {
return http.StatusGone
}
// TODO(marius): http.StatusGone
// http.StatusLengthRequires
// http.StatusPreconditionFailed
// http.StatusRequestEntityTooLarge
// http.StatusRequestURITooLong
// TODO(marius): http.StatusUnsupportedMediaType
// http.StatusRequestedRangeNotSatisfiable
// http.StatusExpectationFailed
// http.StatusTeapot
// http.StatusMisdirectedRequest
// http.StatusUnprocessableEntity
// http.StatusLocked
// http.StatusFailedDependency
// http.StatusTooEarly
// http.StatusTooManyRequests
// http.StatusRequestHeaderFieldsTooLarge
// http.StatusUnavailableForLegalReason
// http.StatusInternalServerError
if IsNotImplemented(e) {
return http.StatusNotImplemented
}
if IsBadGateway(e) {
return http.StatusBadGateway
}
// http.StatusServiceUnavailable
// http.StatusGatewayTimeout
if IsNotSupported(e) {
return http.StatusHTTPVersionNotSupported
}
if IsTimeout(e) {
return http.StatusGatewayTimeout
}
return 0
}
func errorFromStatus(status int) Error {
switch status {
case http.StatusBadRequest:
return new(badRequest)
case http.StatusUnauthorized:
return new(unauthorized)
// case http.StatusPaymentRequired:
case http.StatusForbidden:
return new(forbidden)
case http.StatusNotFound:
return new(notFound)
case http.StatusMethodNotAllowed:
return new(methodNotAllowed)
case http.StatusNotAcceptable:
return new(notValid)
// case http.StatusProxyAuthRequired:
// case http.StatusRequestTimeout:
case http.StatusConflict:
return new(conflict)
// case http.StatusGone: // TODO(marius):
// case http.StatusLengthRequres:
// case http.StatusPreconditionFailed:
// case http.StatusRequestEntityTooLarge:
// case http.StatusRequestURITooLong:
// case http.StatusUnsupportedMediaType: // TODO(marius):
// case http.StatusRequestedRangeNotSatisfiable:
// case http.StatusExpectationFailed:
// case http.StatusTeapot:
// case http.StatusMisdirectedRequest:
// case http.StatusUnprocessableEntity:
// case http.StatusLocked:
// case http.StatusFailedDependency:
// case http.StatusTooEarly:
// case http.StatusTooManyRequests:
// case http.StatusRequestHeaderFieldsTooLarge:
// case http.StatusUnavailableForLegalReason:
// case http.StatusInternalServerError:
case http.StatusNotImplemented:
return new(notImplemented)
case http.StatusBadGateway:
return new(badGateway)
// case http.StatusServiceUnavailable:
case http.StatusHTTPVersionNotSupported:
return new(notSupported)
case http.StatusGatewayTimeout:
return new(badGateway)
case http.StatusInternalServerError:
fallthrough
default:
return new(Err)
}
}
// TODO(marius): get a proper ctxt
func ctxt(r *http.Request) jsonld.Context {
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
return jsonld.Context{
jsonld.ContextElement{
Term: "errors",
IRI: jsonld.IRI(fmt.Sprintf("%s://%s/ns#errors", scheme, r.Host)),
},
}
}
// RenderErrors outputs the json encoded errors, with the JsonLD ctxt for current
func RenderErrors(r *http.Request, errs ...error) (int, []byte) {
errMap := make([]Http, 0)
var status int
for _, err := range errs {
more := HttpErrors(err)
errMap = append(errMap, more...)
status = HttpStatus(err)
}
var dat []byte
var err error
m := struct {
Errors []Http `jsonld:"errors"`
}{Errors: errMap}
if dat, err = jsonld.WithContext(ctxt(r)).Marshal(m); err != nil {
return http.StatusInternalServerError, dat
}
return status, dat
}

219
vendor/github.com/go-ap/errors/stack.go generated vendored Normal file
View File

@ -0,0 +1,219 @@
package errors
import (
"bytes"
"fmt"
"io"
"path"
"runtime"
"strconv"
"strings"
)
// Frame represents a program counter inside a stack frame.
// For historical reasons if Frame is interpreted as a uintptr
// its value represents the program counter + 1.
type Frame uintptr
// pc returns the program counter for this frame;
// multiple frames may have the same PC value.
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
// file returns the full path to the file that contains the
// function for this Frame's pc.
func (f Frame) file() string {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return "unknown"
}
file, _ := fn.FileLine(f.pc())
return file
}
// line returns the line number of source code of the
// function for this Frame's pc.
func (f Frame) line() int {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return 0
}
_, line := fn.FileLine(f.pc())
return line
}
// name returns the name of this function, if known.
func (f Frame) name() string {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return "unknown"
}
return fn.Name()
}
// Format formats the frame according to the fmt.Formatter interface.
//
// %s source file
// %d source line
// %n function name
// %v equivalent to %s:%d
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+s function name and path of source file relative to the compile time
// GOPATH separated by \n\t (<funcname>\n\t<path>)
// %+v equivalent to %+s:%d
func (f Frame) Format(s fmt.State, verb rune) {
switch verb {
case 's':
switch {
case s.Flag('+'):
io.WriteString(s, f.name())
io.WriteString(s, "\n\t")
io.WriteString(s, f.file())
default:
io.WriteString(s, path.Base(f.file()))
}
case 'd':
io.WriteString(s, strconv.Itoa(f.line()))
case 'n':
io.WriteString(s, funcname(f.name()))
case 'v':
f.Format(s, 's')
io.WriteString(s, ":")
f.Format(s, 'd')
}
}
// MarshalText formats a stacktrace Frame as a text string. The output is the
// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs.
func (f Frame) MarshalText() ([]byte, error) {
name := f.name()
if name == "unknown" {
return []byte(name), nil
}
return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil
}
func (f Frame) MarshalJSON() ([]byte, error) {
name := f.name()
w := bytes.NewBuffer(nil)
if name == "unknown" {
w.WriteByte('"')
w.WriteString(name)
w.WriteByte('"')
return w.Bytes(), nil
}
w.WriteByte('{')
w.WriteString("\"function\": ")
w.WriteByte('"')
w.WriteString(funcname(name))
w.WriteByte('"')
w.WriteByte(',')
w.WriteString("\"file\": ")
w.WriteByte('"')
w.WriteString(f.file())
w.WriteByte('"')
w.WriteByte(',')
w.WriteString("\"line\": ")
w.WriteString(fmt.Sprintf("%d", f.line()))
w.WriteByte('}')
return w.Bytes(), nil
}
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
type StackTrace []Frame
// Format formats the stack of Frames according to the fmt.Formatter interface.
//
// %s lists source files for each Frame in the stack
// %v lists the source file and line number for each Frame in the stack
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+v Prints filename, function, and line number for each Frame in the stack.
func (st StackTrace) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case s.Flag('+'):
for _, f := range st {
io.WriteString(s, "\n")
f.Format(s, verb)
}
case s.Flag('#'):
fmt.Fprintf(s, "%#v", []Frame(st))
default:
st.formatSlice(s, verb)
}
case 's':
st.formatSlice(s, verb)
}
}
func (st StackTrace) MarshalJSON() ([]byte, error) {
w := bytes.NewBuffer(nil)
w.WriteByte('[')
for i, f := range st {
b, _ := f.MarshalJSON()
w.Write(b)
if i == len(st)-1 {
break
}
w.WriteByte(',')
}
w.WriteByte(']')
return w.Bytes(), nil
}
// formatSlice will format this StackTrace into the given buffer as a slice of
// Frame, only valid when called with '%s' or '%v'.
func (st StackTrace) formatSlice(s fmt.State, verb rune) {
io.WriteString(s, "[")
for i, f := range st {
if i > 0 {
io.WriteString(s, " ")
}
f.Format(s, verb)
}
io.WriteString(s, "]")
}
// stack represents a stack of program counters.
type stack []uintptr
func (s *stack) Format(st fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case st.Flag('+'):
for _, pc := range *s {
f := Frame(pc)
fmt.Fprintf(st, "\n%+v", f)
}
}
}
}
func (s *stack) StackTrace() StackTrace {
f := make([]Frame, len(*s))
for i := 0; i < len(f); i++ {
f[i] = Frame((*s)[i])
}
return f
}
func callers(skip int) *stack {
const depth = 32
var pcs [depth]uintptr
n := runtime.Callers(skip+3, pcs[:])
var st stack = pcs[0:n]
return &st
}
// funcname removes the path prefix component of a function's name reported by func.Name().
func funcname(name string) string {
i := strings.LastIndex(name, "/")
name = name[i+1:]
i = strings.Index(name, ".")
return name[i+1:]
}

18
vendor/github.com/go-ap/jsonld/.build.yml generated vendored Normal file
View File

@ -0,0 +1,18 @@
image: archlinux
packages:
- go
- postgresql
sources:
- https://github.com/go-ap/jsonld
environment:
GO111MODULE: 'on'
tasks:
- setup: |
cd jsonld && go mod download
- tests: |
cd jsonld && make test
- coverage: |
set -a +x
cd jsonld && make coverage
GIT_SHA=$(git rev-parse --verify HEAD)
GIT_BRANCH=$(git name-rev --name-only HEAD)

14
vendor/github.com/go-ap/jsonld/.gitignore generated vendored Normal file
View File

@ -0,0 +1,14 @@
# Gogland
.idea/
# Binaries for programs and plugins
*.so
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tools
*.out
*.coverprofile
*pkg

21
vendor/github.com/go-ap/jsonld/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Marius Orcsik
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

18
vendor/github.com/go-ap/jsonld/Makefile generated vendored Normal file
View File

@ -0,0 +1,18 @@
TEST := go test
TEST_FLAGS ?= -v
TEST_TARGET ?= ./...
GO111MODULE=on
PROJECT_NAME := $(shell basename $(PWD))
.PHONY: test coverage clean
test:
$(TEST) $(TEST_FLAGS) $(TEST_TARGET)
coverage: TEST_TARGET := .
coverage: TEST_FLAGS += -covermode=count -coverprofile $(PROJECT_NAME).coverprofile
coverage: test
clean:
$(RM) -v *.coverprofile

15
vendor/github.com/go-ap/jsonld/README.md generated vendored Normal file
View File

@ -0,0 +1,15 @@
# JSON-ld for Go
[![MIT Licensed](https://img.shields.io/github/license/go-ap/jsonld.svg)](https://raw.githubusercontent.com/go-ap/jsonld/master/LICENSE)
[![Build Status](https://builds.sr.ht/~mariusor/jsonld.svg)](https://builds.sr.ht/~mariusor/jsonld)
[![Test Coverage](https://img.shields.io/codecov/c/github/go-ap/jsonld.svg)](https://codecov.io/gh/go-ap/jsonld)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-ap/jsonld)](https://goreportcard.com/report/github.com/go-ap/jsonld)
<!--[![Codacy Badge](https://api.codacy.com/project/badge/Grade/29664f7ae6c643bca76700143e912cd3)](https://www.codacy.com/app/go-ap/jsonld/dashboard)-->
Basic lib for using [activity pub](https://www.w3.org/TR/activitypub/#Overview) API in Go.
## Usage
```go
import "github.com/go-ap/jsonld"
```

219
vendor/github.com/go-ap/jsonld/context.go generated vendored Normal file
View File

@ -0,0 +1,219 @@
package jsonld
import (
"encoding/json"
"strings"
)
// From the JSON-LD spec 3.3
// https://www.w3.org/TR/json-ld/#dfn-keyword
const (
// @context
// Used to define the short-hand names that are used throughout a JSON-LD document.
// These short-hand names are called terms and help developers to express specific identifiers in a compact manner.
// The @context keyword is described in detail in section 5.1 The Context.
ContextKw Term = "@context"
// @id
//Used to uniquely identify things that are being described in the document with IRIs or blank node identifiers.
// This keyword is described in section 5.3 Node Identifiers.
IdKw Term = "@id"
// @value
// Used to specify the data that is associated with a particular property in the graph.
// This keyword is described in section 6.9 String Internationalization and section 6.4 Typed Values.
ValueKw Term = "@value"
// @language
// Used to specify the language for a particular string value or the default language of a JSON-LD document.
// This keyword is described in section 6.9 String Internationalization.
LanguageKw Term = "@language"
//@type
//Used to set the data type of a node or typed value. This keyword is described in section 6.4 Typed Values.
TypeKw Term = "@type"
// @container
// Used to set the default container type for a term. This keyword is described in section 6.11 Sets and Lists.
ContainerKw Term = "@container"
//@list
//Used to express an ordered set of data. This keyword is described in section 6.11 Sets and Lists.
ListKw Term = "@list"
// @set
// Used to express an unordered set of data and to ensure that values are always represented as arrays.
// This keyword is described in section 6.11 Sets and Lists.
SetKw Term = "@set"
// @reverse
// Used to express reverse properties. This keyword is described in section 6.12 Reverse Properties.
ReverseKw Term = "@reverse"
// @index
// Used to specify that a container is used to index information and that processing should continue deeper
// into a JSON data structure. This keyword is described in section 6.16 Data Indexing.
IndexKw Term = "@index"
// @base
// Used to set the base IRI against which relative IRIs are resolved. T
// his keyword is described in section 6.1 Base IRI.
BaseKw Term = "@base"
// @vocab
// Used to expand properties and values in @type with a common prefix IRI.
// This keyword is described in section 6.2 Default Vocabulary.
VocabKw Term = "@vocab"
// @graph
// Used to express a graph. This keyword is described in section 6.13 Named Graphs.
GraphKw Term = "@graph"
)
// ContentType is the content type of JsonLD documents
const ContentType = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
type (
// Ref basic type
LangRef string
// Term represents the JSON-LD term for @context maps
Term string
// IRI is a International Resource Identificator
IRI string
// Terms is an array of Term values
Terms []Term
)
// Nillable
type Nillable interface {
IsNil() bool
}
type IRILike interface {
IsCompact() bool
IsAbsolute() bool
IsRelative() bool
}
func (i IRI) IsCompact() bool {
return !i.IsAbsolute() && strings.Contains(string(i), ":")
}
func (i IRI) IsAbsolute() bool {
return strings.Contains(string(i), "https://")
}
func (i IRI) IsRelative() bool {
return !i.IsAbsolute()
}
var keywords = Terms{
BaseKw,
ContextKw,
ContainerKw,
GraphKw,
IdKw,
IndexKw,
LanguageKw,
ListKw,
ReverseKw,
SetKw,
TypeKw,
ValueKw,
VocabKw,
}
const NilTerm Term = "-"
const NilLangRef LangRef = "-"
type ContextObject struct {
ID interface{} `jsonld:"@id,omitempty,collapsible"`
Type interface{} `jsonld:"@type,omitempty,collapsible"`
}
// Context is of of the basic JSON-LD elements.
// It represents an array of ContextElements
type Context []ContextElement
// ContextElement is used to map terms to IRIs or JSON objects.
// Terms are case sensitive and any valid string that is not a reserved JSON-LD
// keyword can be used as a term.
type ContextElement struct {
Term Term
IRI IRI
}
func GetContext() Context {
return Context{}
}
//type Context Collapsible
// Collapsible is an interface used by the JSON-LD marshaller to collapse a struct to one single value
type Collapsible interface {
Collapse() interface{}
}
// Collapse returns the plain text collapsed value of the current Context object
func (c Context) Collapse() interface{} {
if len(c) == 1 && len(c[0].IRI) > 0 {
return c[0].IRI
}
for _, el := range c {
if el.Term == NilTerm {
}
}
return c
}
// Collapse returns the plain text collapsed value of the current IRI string
func (i IRI) Collapse() interface{} {
return i
}
// MarshalText basic stringify function
func (i IRI) MarshalText() ([]byte, error) {
return []byte(i), nil
}
// MarshalJSON returns the JSON document represented by the current Context
// This should return :
// If only one element in the context and the element has no Term -> json marshaled string
// If multiple elements in the context without Term -> json marshaled array of strings
// If multiple elements where at least one doesn't have a Term and one has a Term -> json marshaled array
// If multiple elements where all have Terms -> json marshaled object
func (c Context) MarshalJSON() ([]byte, error) {
mapIRI := make(map[Term]IRI, 0)
arr := make([]interface{}, 0)
i := 0
if len(c) == 1 && len(c[0].IRI) > 0 {
return json.Marshal(c[0].IRI)
}
for _, el := range c {
t := el.Term
iri := el.IRI
if t.IsNil() {
arr = append(arr, iri)
i += 1
} else {
if len(iri) > 0 {
mapIRI[t] = iri
}
}
}
if len(mapIRI) > 0 {
if len(arr) == 0 {
return json.Marshal(mapIRI)
}
arr = append(arr, mapIRI)
}
return json.Marshal(arr)
}
// UnmarshalJSON tries to load the Context from the incoming json value
func (c *Context) UnmarshalJSON(data []byte) error {
return nil
}
// IsNil returns if current LangRef is equal to empty string or to its nil value
func (l LangRef) IsNil() bool {
return len(l) == 0 || l == NilLangRef
}
// IsNil returns if current IRI is equal to empty string
func (i IRI) IsNil() bool {
return len(i) == 0
}
// IsNil returns if current Term is equal to empty string or to its nil value
func (i Term) IsNil() bool {
return len(i) == 0 || i == NilTerm
}

1253
vendor/github.com/go-ap/jsonld/decode.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

1377
vendor/github.com/go-ap/jsonld/encode.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

143
vendor/github.com/go-ap/jsonld/fold.go generated vendored Normal file
View File

@ -0,0 +1,143 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonld
import (
"bytes"
"unicode/utf8"
)
const (
caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
kelvin = '\u212a'
smallLongEss = '\u017f'
)
// foldFunc returns one of four different case folding equivalence
// functions, from most general (and slow) to fastest:
//
// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8
// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S')
// 3) asciiEqualFold, no special, but includes non-letters (including _)
// 4) simpleLetterEqualFold, no specials, no non-letters.
//
// The letters S and K are special because they map to 3 runes, not just 2:
// * S maps to s and to U+017F 'ſ' Latin small letter long s
// * k maps to K and to U+212A '' Kelvin sign
// See https://play.golang.org/p/tTxjOc0OGo
//
// The returned function is specialized for matching against s and
// should only be given s. It's not curried for performance reasons.
func foldFunc(s []byte) func(s, t []byte) bool {
nonLetter := false
special := false // special letter
for _, b := range s {
if b >= utf8.RuneSelf {
return bytes.EqualFold
}
upper := b & caseMask
if upper < 'A' || upper > 'Z' {
nonLetter = true
} else if upper == 'K' || upper == 'S' {
// See above for why these letters are special.
special = true
}
}
if special {
return equalFoldRight
}
if nonLetter {
return asciiEqualFold
}
return simpleLetterEqualFold
}
// equalFoldRight is a specialization of bytes.EqualFold when s is
// known to be all ASCII (including punctuation), but contains an 's',
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
// See comments on foldFunc.
func equalFoldRight(s, t []byte) bool {
for _, sb := range s {
if len(t) == 0 {
return false
}
tb := t[0]
if tb < utf8.RuneSelf {
if sb != tb {
sbUpper := sb & caseMask
if 'A' <= sbUpper && sbUpper <= 'Z' {
if sbUpper != tb&caseMask {
return false
}
} else {
return false
}
}
t = t[1:]
continue
}
// sb is ASCII and t is not. t must be either kelvin
// sign or long s; sb must be s, S, k, or K.
tr, size := utf8.DecodeRune(t)
switch sb {
case 's', 'S':
if tr != smallLongEss {
return false
}
case 'k', 'K':
if tr != kelvin {
return false
}
default:
return false
}
t = t[size:]
}
if len(t) > 0 {
return false
}
return true
}
// asciiEqualFold is a specialization of bytes.EqualFold for use when
// s is all ASCII (but may contain non-letters) and contains no
// special-folding letters.
// See comments on foldFunc.
func asciiEqualFold(s, t []byte) bool {
if len(s) != len(t) {
return false
}
for i, sb := range s {
tb := t[i]
if sb == tb {
continue
}
if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
if sb&caseMask != tb&caseMask {
return false
}
} else {
return false
}
}
return true
}
// simpleLetterEqualFold is a specialization of bytes.EqualFold for
// use when s is all ASCII letters (no underscores, etc) and also
// doesn't contain 'k', 'K', 's', or 'S'.
// See comments on foldFunc.
func simpleLetterEqualFold(s, t []byte) bool {
if len(s) != len(t) {
return false
}
for i, b := range s {
if b&caseMask != t[i]&caseMask {
return false
}
}
return true
}

632
vendor/github.com/go-ap/jsonld/scanner.go generated vendored Normal file
View File

@ -0,0 +1,632 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonld
// JSON value parser state machine.
// Just about at the limit of what is reasonable to write by hand.
// Some parts are a bit tedious, but overall it nicely factors out the
// otherwise common code from the multiple scanning functions
// in this package (Compact, Indent, checkValid, nextValue, etc).
//
// This file starts with two simple examples using the scanner
// before diving into the scanner itself.
import (
"strconv"
)
// Valid reports whether data is a valid JSON encoding.
func Valid(data []byte) bool {
return checkValid(data, &scanner{}) == nil
}
// checkValid verifies that data is valid JSON-encoded data.
// scan is passed in for use by checkValid to avoid an allocation.
func checkValid(data []byte, scan *scanner) error {
scan.reset()
for _, c := range data {
scan.bytes++
if scan.step(scan, c) == scanError {
return scan.err
}
}
if scan.eof() == scanError {
return scan.err
}
return nil
}
// nextValue splits data after the next whole JSON value,
// returning that value and the bytes that follow it as separate slices.
// scan is passed in for use by nextValue to avoid an allocation.
func nextValue(data []byte, scan *scanner) (value, rest []byte, err error) {
scan.reset()
for i, c := range data {
v := scan.step(scan, c)
if v >= scanEndObject {
switch v {
// probe the scanner with a space to determine whether we will
// get scanEnd on the next character. Otherwise, if the next character
// is not a space, scanEndTop allocates a needless error.
case scanEndObject, scanEndArray:
if scan.step(scan, ' ') == scanEnd {
return data[:i+1], data[i+1:], nil
}
case scanError:
return nil, nil, scan.err
case scanEnd:
return data[:i], data[i:], nil
}
}
}
if scan.eof() == scanError {
return nil, nil, scan.err
}
return data, nil, nil
}
// A SyntaxError is a description of a JSON syntax error.
type SyntaxError struct {
msg string // description of error
Offset int64 // error occurred after reading Offset bytes
}
func (e *SyntaxError) Error() string { return e.msg }
// A scanner is a JSON scanning state machine.
// Callers call scan.reset() and then pass bytes in one at a time
// by calling scan.step(&scan, c) for each byte.
// The return value, referred to as an opcode, tells the
// caller about significant parsing events like beginning
// and ending literals, objects, and arrays, so that the
// caller can follow along if it wishes.
// The return value scanEnd indicates that a single top-level
// JSON value has been completed, *before* the byte that
// just got passed in. (The indication must be delayed in order
// to recognize the end of numbers: is 123 a whole value or
// the beginning of 12345e+6?).
type scanner struct {
// The step is a func to be called to execute the next transition.
// Also tried using an integer constant and a single func
// with a switch, but using the func directly was 10% faster
// on a 64-bit Mac Mini, and it's nicer to read.
step func(*scanner, byte) int
// Reached end of top-level value.
endTop bool
// Stack of what we're in the middle of - array values, object keys, object values.
parseState []int
// Error that happened, if any.
err error
// 1-byte redo (see undo method)
redo bool
redoCode int
redoState func(*scanner, byte) int
// total bytes consumed, updated by decoder.Decode
bytes int64
}
// These values are returned by the state transition functions
// assigned to scanner.state and the method scanner.eof.
// They give details about the current state of the scan that
// callers might be interested to know about.
// It is okay to Ignore the return value of any particular
// call to scanner.state: if one call returns scanError,
// every subsequent call will return scanError too.
const (
// Continue.
scanContinue = iota // uninteresting byte
scanBeginLiteral // end implied by next result != scanContinue
scanBeginObject // begin object
scanObjectKey // just finished object key (string)
scanObjectValue // just finished non-last object value
scanEndObject // end object (implies scanObjectValue if possible)
scanBeginArray // begin array
scanArrayValue // just finished array value
scanEndArray // end array (implies scanArrayValue if possible)
scanSkipSpace // space byte; can skip; known to be last "continue" result
// Stop.
scanEnd // top-level value ended *before* this byte; known to be first "stop" result
scanError // hit an error, scanner.err.
scanFindType // need to find the "jsonld:type" element of the object.
)
// These values are stored in the parseState stack.
// They give the current state of a composite value
// being scanned. If the parser is inside a nested value
// the parseState describes the nested state, outermost at entry 0.
const (
parseObjectKey = iota // parsing object key (before colon)
parseObjectValue // parsing object value (after colon)
parseArrayValue // parsing array value
)
// reset prepares the scanner for use.
// It must be called before calling s.step.
func (s *scanner) reset() {
s.step = stateBeginValue
s.parseState = s.parseState[0:0]
s.err = nil
s.redo = false
s.endTop = false
}
// eof tells the scanner that the end of input has been reached.
// It returns a scan status just as s.step does.
func (s *scanner) eof() int {
if s.err != nil {
return scanError
}
if s.endTop {
return scanEnd
}
s.step(s, ' ')
if s.endTop {
return scanEnd
}
if s.err == nil {
s.err = &SyntaxError{"unexpected end of JSON input", s.bytes}
}
return scanError
}
// pushParseState pushes a new parse state p onto the parse stack.
func (s *scanner) pushParseState(p int) {
s.parseState = append(s.parseState, p)
}
// popParseState pops a parse state (already obtained) off the stack
// and updates s.step accordingly.
func (s *scanner) popParseState() {
n := len(s.parseState) - 1
s.parseState = s.parseState[0:n]
s.redo = false
if n == 0 {
s.step = stateEndTop
s.endTop = true
} else {
s.step = stateEndValue
}
}
func isSpace(c byte) bool {
return c == ' ' || c == '\t' || c == '\r' || c == '\n'
}
// stateBeginValueOrEmpty is the state after reading `[`.
func stateBeginValueOrEmpty(s *scanner, c byte) int {
if c <= ' ' && isSpace(c) {
return scanSkipSpace
}
if c == ']' {
return stateEndValue(s, c)
}
return stateBeginValue(s, c)
}
// stateBeginValue is the state at the beginning of the input.
func stateBeginValue(s *scanner, c byte) int {
if c <= ' ' && isSpace(c) {
return scanSkipSpace
}
switch c {
case '{':
s.step = stateBeginStringOrEmpty
s.pushParseState(parseObjectKey)
return scanBeginObject
case '[':
s.step = stateBeginValueOrEmpty
s.pushParseState(parseArrayValue)
return scanBeginArray
case '"':
s.step = stateInString
return scanBeginLiteral
case '-':
s.step = stateNeg
return scanBeginLiteral
case '0': // beginning of 0.123
s.step = state0
return scanBeginLiteral
case 't': // beginning of true
s.step = stateT
return scanBeginLiteral
case 'f': // beginning of false
s.step = stateF
return scanBeginLiteral
case 'n': // beginning of null
s.step = stateN
return scanBeginLiteral
}
if '1' <= c && c <= '9' { // beginning of 1234.5
s.step = state1
return scanBeginLiteral
}
return s.error(c, "looking for beginning of value")
}
// stateBeginStringOrEmpty is the state after reading `{`.
func stateBeginStringOrEmpty(s *scanner, c byte) int {
if c <= ' ' && isSpace(c) {
return scanSkipSpace
}
if c == '}' {
n := len(s.parseState)
s.parseState[n-1] = parseObjectValue
return stateEndValue(s, c)
}
return stateBeginString(s, c)
}
// stateBeginString is the state after reading `{"key": value,`.
func stateBeginString(s *scanner, c byte) int {
if c <= ' ' && isSpace(c) {
return scanSkipSpace
}
if c == '"' {
s.step = stateInString
return scanBeginLiteral
}
return s.error(c, "looking for beginning of object key string")
}
// stateEndValue is the state after completing a value,
// such as after reading `{}` or `true` or `["x"`.
func stateEndValue(s *scanner, c byte) int {
n := len(s.parseState)
if n == 0 {
// Completed top-level before the current byte.
s.step = stateEndTop
s.endTop = true
return stateEndTop(s, c)
}
if c <= ' ' && isSpace(c) {
s.step = stateEndValue
return scanSkipSpace
}
ps := s.parseState[n-1]
switch ps {
case parseObjectKey:
if c == ':' {
s.parseState[n-1] = parseObjectValue
s.step = stateBeginValue
return scanObjectKey
}
return s.error(c, "after object key")
case parseObjectValue:
if c == ',' {
s.parseState[n-1] = parseObjectKey
s.step = stateBeginString
return scanObjectValue
}
if c == '}' {
s.popParseState()
return scanEndObject
}
return s.error(c, "after object key:value pair")
case parseArrayValue:
if c == ',' {
s.step = stateBeginValue
return scanArrayValue
}
if c == ']' {
s.popParseState()
return scanEndArray
}
return s.error(c, "after array element")
}
return s.error(c, "")
}
// stateEndTop is the state after finishing the top-level value,
// such as after reading `{}` or `[1,2,3]`.
// Only space characters should be seen now.
func stateEndTop(s *scanner, c byte) int {
if c != ' ' && c != '\t' && c != '\r' && c != '\n' {
// Complain about non-space byte on next call.
s.error(c, "after top-level value")
}
return scanEnd
}
// stateInString is the state after reading `"`.
func stateInString(s *scanner, c byte) int {
if c == '"' {
s.step = stateEndValue
return scanContinue
}
if c == '\\' {
s.step = stateInStringEsc
return scanContinue
}
if c < 0x20 {
return s.error(c, "in string literal")
}
return scanContinue
}
// stateInStringEsc is the state after reading `"\` during a quoted string.
func stateInStringEsc(s *scanner, c byte) int {
switch c {
case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
s.step = stateInString
return scanContinue
case 'u':
s.step = stateInStringEscU
return scanContinue
}
return s.error(c, "in string escape code")
}
// stateInStringEscU is the state after reading `"\u` during a quoted string.
func stateInStringEscU(s *scanner, c byte) int {
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
s.step = stateInStringEscU1
return scanContinue
}
// numbers
return s.error(c, "in \\u hexadecimal character escape")
}
// stateInStringEscU1 is the state after reading `"\u1` during a quoted string.
func stateInStringEscU1(s *scanner, c byte) int {
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
s.step = stateInStringEscU12
return scanContinue
}
// numbers
return s.error(c, "in \\u hexadecimal character escape")
}
// stateInStringEscU12 is the state after reading `"\u12` during a quoted string.
func stateInStringEscU12(s *scanner, c byte) int {
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
s.step = stateInStringEscU123
return scanContinue
}
// numbers
return s.error(c, "in \\u hexadecimal character escape")
}
// stateInStringEscU123 is the state after reading `"\u123` during a quoted string.
func stateInStringEscU123(s *scanner, c byte) int {
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
s.step = stateInString
return scanContinue
}
// numbers
return s.error(c, "in \\u hexadecimal character escape")
}
// stateNeg is the state after reading `-` during a number.
func stateNeg(s *scanner, c byte) int {
if c == '0' {
s.step = state0
return scanContinue
}
if '1' <= c && c <= '9' {
s.step = state1
return scanContinue
}
return s.error(c, "in numeric literal")
}
// state1 is the state after reading a non-zero integer during a number,
// such as after reading `1` or `100` but not `0`.
func state1(s *scanner, c byte) int {
if '0' <= c && c <= '9' {
s.step = state1
return scanContinue
}
return state0(s, c)
}
// state0 is the state after reading `0` during a number.
func state0(s *scanner, c byte) int {
if c == '.' {
s.step = stateDot
return scanContinue
}
if c == 'e' || c == 'E' {
s.step = stateE
return scanContinue
}
return stateEndValue(s, c)
}
// stateDot is the state after reading the integer and decimal point in a number,
// such as after reading `1.`.
func stateDot(s *scanner, c byte) int {
if '0' <= c && c <= '9' {
s.step = stateDot0
return scanContinue
}
return s.error(c, "after decimal point in numeric literal")
}
// stateDot0 is the state after reading the integer, decimal point, and subsequent
// digits of a number, such as after reading `3.14`.
func stateDot0(s *scanner, c byte) int {
if '0' <= c && c <= '9' {
return scanContinue
}
if c == 'e' || c == 'E' {
s.step = stateE
return scanContinue
}
return stateEndValue(s, c)
}
// stateE is the state after reading the mantissa and e in a number,
// such as after reading `314e` or `0.314e`.
func stateE(s *scanner, c byte) int {
if c == '+' || c == '-' {
s.step = stateESign
return scanContinue
}
return stateESign(s, c)
}
// stateESign is the state after reading the mantissa, e, and sign in a number,
// such as after reading `314e-` or `0.314e+`.
func stateESign(s *scanner, c byte) int {
if '0' <= c && c <= '9' {
s.step = stateE0
return scanContinue
}
return s.error(c, "in exponent of numeric literal")
}
// stateE0 is the state after reading the mantissa, e, optional sign,
// and at least one digit of the exponent in a number,
// such as after reading `314e-2` or `0.314e+1` or `3.14e0`.
func stateE0(s *scanner, c byte) int {
if '0' <= c && c <= '9' {
return scanContinue
}
return stateEndValue(s, c)
}
// stateT is the state after reading `t`.
func stateT(s *scanner, c byte) int {
if c == 'r' {
s.step = stateTr
return scanContinue
}
return s.error(c, "in literal true (expecting 'r')")
}
// stateTr is the state after reading `tr`.
func stateTr(s *scanner, c byte) int {
if c == 'u' {
s.step = stateTru
return scanContinue
}
return s.error(c, "in literal true (expecting 'u')")
}
// stateTru is the state after reading `tru`.
func stateTru(s *scanner, c byte) int {
if c == 'e' {
s.step = stateEndValue
return scanContinue
}
return s.error(c, "in literal true (expecting 'e')")
}
// stateF is the state after reading `f`.
func stateF(s *scanner, c byte) int {
if c == 'a' {
s.step = stateFa
return scanContinue
}
return s.error(c, "in literal false (expecting 'a')")
}
// stateFa is the state after reading `fa`.
func stateFa(s *scanner, c byte) int {
if c == 'l' {
s.step = stateFal
return scanContinue
}
return s.error(c, "in literal false (expecting 'l')")
}
// stateFal is the state after reading `fal`.
func stateFal(s *scanner, c byte) int {
if c == 's' {
s.step = stateFals
return scanContinue
}
return s.error(c, "in literal false (expecting 's')")
}
// stateFals is the state after reading `fals`.
func stateFals(s *scanner, c byte) int {
if c == 'e' {
s.step = stateEndValue
return scanContinue
}
return s.error(c, "in literal false (expecting 'e')")
}
// stateN is the state after reading `n`.
func stateN(s *scanner, c byte) int {
if c == 'u' {
s.step = stateNu
return scanContinue
}
return s.error(c, "in literal null (expecting 'u')")
}
// stateNu is the state after reading `nu`.
func stateNu(s *scanner, c byte) int {
if c == 'l' {
s.step = stateNul
return scanContinue
}
return s.error(c, "in literal null (expecting 'l')")
}
// stateNul is the state after reading `nul`.
func stateNul(s *scanner, c byte) int {
if c == 'l' {
s.step = stateEndValue
return scanContinue
}
return s.error(c, "in literal null (expecting 'l')")
}
// stateError is the state after reaching a syntax error,
// such as after reading `[1}` or `5.1.2`.
func stateError(s *scanner, c byte) int {
return scanError
}
// error records an error and switches to the error state.
func (s *scanner) error(c byte, context string) int {
s.step = stateError
s.err = &SyntaxError{"invalid character " + quoteChar(c) + " " + context, s.bytes}
return scanError
}
// quoteChar formats c as a quoted character literal
func quoteChar(c byte) string {
// special cases - different from quoted strings
if c == '\'' {
return `'\''`
}
if c == '"' {
return `'"'`
}
// use quoted string with different quotation marks
s := strconv.Quote(string(c))
return "'" + s[1:len(s)-1] + "'"
}
// undo causes the scanner to return scanCode from the next state transition.
// This gives callers a simple 1-byte undo mechanism.
func (s *scanner) undo(scanCode int) {
if s.redo {
panic("json: invalid use of scanner")
}
s.redoCode = scanCode
s.redoState = s.step
s.step = stateRedo
s.redo = true
}
// stateRedo helps implement the scanner's 1-byte undo.
func stateRedo(s *scanner, c byte) int {
s.redo = false
s.step = s.redoState
return s.redoCode
}

218
vendor/github.com/go-ap/jsonld/tables.go generated vendored Normal file
View File

@ -0,0 +1,218 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonld
import "unicode/utf8"
// safeSet holds the value true if the ASCII character with the given array
// position can be represented inside a JSON string without any further
// escaping.
//
// All values are true except for the ASCII control characters (0-31), the
// double quote ("), and the backslash character ("\").
var safeSet = [utf8.RuneSelf]bool{
' ': true,
'!': true,
'"': false,
'#': true,
'$': true,
'%': true,
'&': true,
'\'': true,
'(': true,
')': true,
'*': true,
'+': true,
',': true,
'-': true,
'.': true,
'/': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
':': true,
';': true,
'<': true,
'=': true,
'>': true,
'?': true,
'@': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'V': true,
'W': true,
'X': true,
'Y': true,
'Z': true,
'[': true,
'\\': false,
']': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'{': true,
'|': true,
'}': true,
'~': true,
'\u007f': true,
}
// htmlSafeSet holds the value true if the ASCII character with the given
// array position can be safely represented inside a JSON string, embedded
// inside of HTML <script> tags, without any additional escaping.
//
// All values are true except for the ASCII control characters (0-31), the
// double quote ("), the backslash character ("\"), HTML opening and closing
// tags ("<" and ">"), and the ampersand ("&").
var htmlSafeSet = [utf8.RuneSelf]bool{
' ': true,
'!': true,
'"': false,
'#': true,
'$': true,
'%': true,
'&': false,
'\'': true,
'(': true,
')': true,
'*': true,
'+': true,
',': true,
'-': true,
'.': true,
'/': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
':': true,
';': true,
'<': false,
'=': true,
'>': false,
'?': true,
'@': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'V': true,
'W': true,
'X': true,
'Y': true,
'Z': true,
'[': true,
'\\': false,
']': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'{': true,
'|': true,
'}': true,
'~': true,
'\u007f': true,
}

44
vendor/github.com/go-ap/jsonld/tags.go generated vendored Normal file
View File

@ -0,0 +1,44 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonld
import (
"strings"
)
// tagOptions is the string following a comma in a struct field's "json"
// tag, or the empty string. It does not include the leading comma.
type tagOptions string
// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:])
}
return tag, tagOptions("")
}
// Contains reports whether a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 {
return false
}
s := string(o)
for s != "" {
var next string
i := strings.Index(s, ",")
if i >= 0 {
s, next = s[:i], s[i+1:]
}
if s == optionName {
return true
}
s = next
}
return false
}

1
vendor/github.com/valyala/fastjson/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
tags

19
vendor/github.com/valyala/fastjson/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,19 @@
language: go
go:
- 1.10.x
script:
# build test for supported platforms
- GOOS=linux go build
- GOOS=darwin go build
- GOOS=freebsd go build
- GOOS=windows go build
# run tests on a standard platform
- go test -v ./... -coverprofile=coverage.txt -covermode=atomic
- go test -v ./... -race
after_success:
# Upload coverage results to codecov.io
- bash <(curl -s https://codecov.io/bash)

22
vendor/github.com/valyala/fastjson/LICENSE generated vendored Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2018 Aliaksandr Valialkin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

227
vendor/github.com/valyala/fastjson/README.md generated vendored Normal file
View File

@ -0,0 +1,227 @@
[![Build Status](https://travis-ci.org/valyala/fastjson.svg)](https://travis-ci.org/valyala/fastjson)
[![GoDoc](https://godoc.org/github.com/valyala/fastjson?status.svg)](http://godoc.org/github.com/valyala/fastjson)
[![Go Report](https://goreportcard.com/badge/github.com/valyala/fastjson)](https://goreportcard.com/report/github.com/valyala/fastjson)
[![codecov](https://codecov.io/gh/valyala/fastjson/branch/master/graph/badge.svg)](https://codecov.io/gh/valyala/fastjson)
# fastjson - fast JSON parser and validator for Go
## Features
* Fast. As usual, up to 15x faster than the standard [encoding/json](https://golang.org/pkg/encoding/json/).
See [benchmarks](#benchmarks).
* Parses arbitrary JSON without schema, reflection, struct magic and code generation
contrary to [easyjson](https://github.com/mailru/easyjson).
* Provides simple [API](http://godoc.org/github.com/valyala/fastjson).
* Outperforms [jsonparser](https://github.com/buger/jsonparser) and [gjson](https://github.com/tidwall/gjson)
when accessing multiple unrelated fields, since `fastjson` parses the input JSON only once.
* Validates the parsed JSON unlike [jsonparser](https://github.com/buger/jsonparser)
and [gjson](https://github.com/tidwall/gjson).
* May quickly extract a part of the original JSON with `Value.Get(...).MarshalTo` and modify it
with [Del](https://godoc.org/github.com/valyala/fastjson#Value.Del)
and [Set](https://godoc.org/github.com/valyala/fastjson#Value.Set) functions.
* May parse array containing values with distinct types (aka non-homogenous types).
For instance, `fastjson` easily parses the following JSON array `[123, "foo", [456], {"k": "v"}, null]`.
* `fastjson` preserves the original order of object items when calling
[Object.Visit](https://godoc.org/github.com/valyala/fastjson#Object.Visit).
## Known limitations
* Requies extra care to work with - references to certain objects recursively
returned by [Parser](https://godoc.org/github.com/valyala/fastjson#Parser)
must be released before the next call to [Parse](https://godoc.org/github.com/valyala/fastjson#Parser.Parse).
Otherwise the program may work improperly. The same applies to objects returned by [Arena](https://godoc.org/github.com/valyala/fastjson#Arena).
Adhere recommendations from [docs](https://godoc.org/github.com/valyala/fastjson).
* Cannot parse JSON from `io.Reader`. There is [Scanner](https://godoc.org/github.com/valyala/fastjson#Scanner)
for parsing stream of JSON values from a string.
## Usage
One-liner accessing a single field:
```go
s := []byte(`{"foo": [123, "bar"]}`)
fmt.Printf("foo.0=%d\n", fastjson.GetInt(s, "foo", "0"))
// Output:
// foo.0=123
```
Accessing multiple fields with error handling:
```go
var p fastjson.Parser
v, err := p.Parse(`{
"str": "bar",
"int": 123,
"float": 1.23,
"bool": true,
"arr": [1, "foo", {}]
}`)
if err != nil {
log.Fatal(err)
}
fmt.Printf("foo=%s\n", v.GetStringBytes("str"))
fmt.Printf("int=%d\n", v.GetInt("int"))
fmt.Printf("float=%f\n", v.GetFloat64("float"))
fmt.Printf("bool=%v\n", v.GetBool("bool"))
fmt.Printf("arr.1=%s\n", v.GetStringBytes("arr", "1"))
// Output:
// foo=bar
// int=123
// float=1.230000
// bool=true
// arr.1=foo
```
See also [examples](https://godoc.org/github.com/valyala/fastjson#pkg-examples).
## Security
* `fastjson` shouldn't crash or panic when parsing input strings specially crafted
by an attacker. It must return error on invalid input JSON.
* `fastjson` requires up to `sizeof(Value) * len(inputJSON)` bytes of memory
for parsing `inputJSON` string. Limit the maximum size of the `inputJSON`
before parsing it in order to limit the maximum memory usage.
## Performance optimization tips
* Re-use [Parser](https://godoc.org/github.com/valyala/fastjson#Parser) and [Scanner](https://godoc.org/github.com/valyala/fastjson#Scanner)
for parsing many JSONs. This reduces memory allocations overhead.
[ParserPool](https://godoc.org/github.com/valyala/fastjson#ParserPool) may be useful in this case.
* Prefer calling `Value.Get*` on the value returned from [Parser](https://godoc.org/github.com/valyala/fastjson#Parser)
instead of calling `Get*` one-liners when multiple fields
must be obtained from JSON, since each `Get*` one-liner re-parses
the input JSON again.
* Prefer calling once [Value.Get](https://godoc.org/github.com/valyala/fastjson#Value.Get)
for common prefix paths and then calling `Value.Get*` on the returned value
for distinct suffix paths.
* Prefer iterating over array returned from [Value.GetArray](https://godoc.org/github.com/valyala/fastjson#Object.Visit)
with a range loop instead of calling `Value.Get*` for each array item.
## Fuzzing
Install [go-fuzz](https://github.com/dvyukov/go-fuzz) & optionally the go-fuzz-corpus.
```bash
go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build
```
Build using `go-fuzz-build` and run `go-fuzz` with an optional corpus.
```bash
mkdir -p workdir/corpus
cp $GOPATH/src/github.com/dvyukov/go-fuzz-corpus/json/corpus/* workdir/corpus
go-fuzz-build github.com/valyala/fastjson
go-fuzz -bin=fastjson-fuzz.zip -workdir=workdir
```
## Benchmarks
Go 1.12 has been used for benchmarking.
Legend:
* `small` - parse [small.json](testdata/small.json) (190 bytes).
* `medium` - parse [medium.json](testdata/medium.json) (2.3KB).
* `large` - parse [large.json](testdata/large.json) (28KB).
* `canada` - parse [canada.json](testdata/canada.json) (2.2MB).
* `citm` - parse [citm_catalog.json](testdata/citm_catalog.json) (1.7MB).
* `twitter` - parse [twitter.json](testdata/twitter.json) (617KB).
* `stdjson-map` - parse into a `map[string]interface{}` using `encoding/json`.
* `stdjson-struct` - parse into a struct containing
a subset of fields of the parsed JSON, using `encoding/json`.
* `stdjson-empty-struct` - parse into an empty struct using `encoding/json`.
This is the fastest possible solution for `encoding/json`, may be used
for json validation. See also benchmark results for json validation.
* `fastjson` - parse using `fastjson` without fields access.
* `fastjson-get` - parse using `fastjson` with fields access similar to `stdjson-struct`.
```
$ GOMAXPROCS=1 go test github.com/valyala/fastjson -bench='Parse$'
goos: linux
goarch: amd64
pkg: github.com/valyala/fastjson
BenchmarkParse/small/stdjson-map 200000 7305 ns/op 26.01 MB/s 960 B/op 51 allocs/op
BenchmarkParse/small/stdjson-struct 500000 3431 ns/op 55.37 MB/s 224 B/op 4 allocs/op
BenchmarkParse/small/stdjson-empty-struct 500000 2273 ns/op 83.58 MB/s 168 B/op 2 allocs/op
BenchmarkParse/small/fastjson 5000000 347 ns/op 547.53 MB/s 0 B/op 0 allocs/op
BenchmarkParse/small/fastjson-get 2000000 620 ns/op 306.39 MB/s 0 B/op 0 allocs/op
BenchmarkParse/medium/stdjson-map 30000 40672 ns/op 57.26 MB/s 10196 B/op 208 allocs/op
BenchmarkParse/medium/stdjson-struct 30000 47792 ns/op 48.73 MB/s 9174 B/op 258 allocs/op
BenchmarkParse/medium/stdjson-empty-struct 100000 22096 ns/op 105.40 MB/s 280 B/op 5 allocs/op
BenchmarkParse/medium/fastjson 500000 3025 ns/op 769.90 MB/s 0 B/op 0 allocs/op
BenchmarkParse/medium/fastjson-get 500000 3211 ns/op 725.20 MB/s 0 B/op 0 allocs/op
BenchmarkParse/large/stdjson-map 2000 614079 ns/op 45.79 MB/s 210734 B/op 2785 allocs/op
BenchmarkParse/large/stdjson-struct 5000 298554 ns/op 94.18 MB/s 15616 B/op 353 allocs/op
BenchmarkParse/large/stdjson-empty-struct 5000 268577 ns/op 104.69 MB/s 280 B/op 5 allocs/op
BenchmarkParse/large/fastjson 50000 35210 ns/op 798.56 MB/s 5 B/op 0 allocs/op
BenchmarkParse/large/fastjson-get 50000 35171 ns/op 799.46 MB/s 5 B/op 0 allocs/op
BenchmarkParse/canada/stdjson-map 20 68147307 ns/op 33.03 MB/s 12260502 B/op 392539 allocs/op
BenchmarkParse/canada/stdjson-struct 20 68044518 ns/op 33.08 MB/s 12260123 B/op 392534 allocs/op
BenchmarkParse/canada/stdjson-empty-struct 100 17709250 ns/op 127.11 MB/s 280 B/op 5 allocs/op
BenchmarkParse/canada/fastjson 300 4182404 ns/op 538.22 MB/s 254902 B/op 381 allocs/op
BenchmarkParse/canada/fastjson-get 300 4274744 ns/op 526.60 MB/s 254902 B/op 381 allocs/op
BenchmarkParse/citm/stdjson-map 50 27772612 ns/op 62.19 MB/s 5214163 B/op 95402 allocs/op
BenchmarkParse/citm/stdjson-struct 100 14936191 ns/op 115.64 MB/s 1989 B/op 75 allocs/op
BenchmarkParse/citm/stdjson-empty-struct 100 14946034 ns/op 115.56 MB/s 280 B/op 5 allocs/op
BenchmarkParse/citm/fastjson 1000 1879714 ns/op 918.87 MB/s 17628 B/op 30 allocs/op
BenchmarkParse/citm/fastjson-get 1000 1881598 ns/op 917.94 MB/s 17628 B/op 30 allocs/op
BenchmarkParse/twitter/stdjson-map 100 11289146 ns/op 55.94 MB/s 2187878 B/op 31266 allocs/op
BenchmarkParse/twitter/stdjson-struct 300 5779442 ns/op 109.27 MB/s 408 B/op 6 allocs/op
BenchmarkParse/twitter/stdjson-empty-struct 300 5738504 ns/op 110.05 MB/s 408 B/op 6 allocs/op
BenchmarkParse/twitter/fastjson 2000 774042 ns/op 815.86 MB/s 2541 B/op 2 allocs/op
BenchmarkParse/twitter/fastjson-get 2000 777833 ns/op 811.89 MB/s 2541 B/op 2 allocs/op
```
Benchmark results for json validation:
```
$ GOMAXPROCS=1 go test github.com/valyala/fastjson -bench='Validate$'
goos: linux
goarch: amd64
pkg: github.com/valyala/fastjson
BenchmarkValidate/small/stdjson 2000000 955 ns/op 198.83 MB/s 72 B/op 2 allocs/op
BenchmarkValidate/small/fastjson 5000000 384 ns/op 493.60 MB/s 0 B/op 0 allocs/op
BenchmarkValidate/medium/stdjson 200000 10799 ns/op 215.66 MB/s 184 B/op 5 allocs/op
BenchmarkValidate/medium/fastjson 300000 3809 ns/op 611.30 MB/s 0 B/op 0 allocs/op
BenchmarkValidate/large/stdjson 10000 133064 ns/op 211.31 MB/s 184 B/op 5 allocs/op
BenchmarkValidate/large/fastjson 30000 45268 ns/op 621.14 MB/s 0 B/op 0 allocs/op
BenchmarkValidate/canada/stdjson 200 8470904 ns/op 265.74 MB/s 184 B/op 5 allocs/op
BenchmarkValidate/canada/fastjson 500 2973377 ns/op 757.07 MB/s 0 B/op 0 allocs/op
BenchmarkValidate/citm/stdjson 200 7273172 ns/op 237.48 MB/s 184 B/op 5 allocs/op
BenchmarkValidate/citm/fastjson 1000 1684430 ns/op 1025.39 MB/s 0 B/op 0 allocs/op
BenchmarkValidate/twitter/stdjson 500 2849439 ns/op 221.63 MB/s 312 B/op 6 allocs/op
BenchmarkValidate/twitter/fastjson 2000 1036796 ns/op 609.10 MB/s 0 B/op 0 allocs/op
```
## FAQ
* Q: _There are a ton of other high-perf packages for JSON parsing in Go. Why creating yet another package?_
A: Because other packages require either rigid JSON schema via struct magic
and code generation or perform poorly when multiple unrelated fields
must be obtained from the parsed JSON.
Additionally, `fastjson` provides nicer [API](http://godoc.org/github.com/valyala/fastjson).
* Q: _What is the main purpose for `fastjson`?_
A: High-perf JSON parsing for [RTB](https://www.iab.com/wp-content/uploads/2015/05/OpenRTB_API_Specification_Version_2_3_1.pdf)
and other [JSON-RPC](https://en.wikipedia.org/wiki/JSON-RPC) services.
* Q: _Why fastjson doesn't provide fast marshaling (serialization)?_
A: Actually it provides some sort of marshaling - see [Value.MarshalTo](https://godoc.org/github.com/valyala/fastjson#Value.MarshalTo).
But I'd recommend using [quicktemplate](https://github.com/valyala/quicktemplate#use-cases)
for high-performance JSON marshaling :)
* Q: _`fastjson` crashes my program!_
A: There is high probability of improper use.
* Make sure you don't hold references to objects recursively returned by `Parser` / `Scanner`
beyond the next `Parser.Parse` / `Scanner.Next` call
if such restriction is mentioned in [docs](https://github.com/valyala/fastjson/issues/new).
* Make sure you don't access `fastjson` objects from concurrently running goroutines
if such restriction is mentioned in [docs](https://github.com/valyala/fastjson/issues/new).
* Build and run your program with [-race](https://golang.org/doc/articles/race_detector.html) flag.
Make sure the race detector detects zero races.
* If your program continue crashing after fixing issues mentioned above, [file a bug](https://github.com/valyala/fastjson/issues/new).

126
vendor/github.com/valyala/fastjson/arena.go generated vendored Normal file
View File

@ -0,0 +1,126 @@
package fastjson
import (
"strconv"
)
// Arena may be used for fast creation and re-use of Values.
//
// Typical Arena lifecycle:
//
// 1) Construct Values via the Arena and Value.Set* calls.
// 2) Marshal the constructed Values with Value.MarshalTo call.
// 3) Reset all the constructed Values at once by Arena.Reset call.
// 4) Go to 1 and re-use the Arena.
//
// It is unsafe calling Arena methods from concurrent goroutines.
// Use per-goroutine Arenas or ArenaPool instead.
type Arena struct {
b []byte
c cache
}
// Reset resets all the Values allocated by a.
//
// Values previously allocated by a cannot be used after the Reset call.
func (a *Arena) Reset() {
a.b = a.b[:0]
a.c.reset()
}
// NewObject returns new empty object value.
//
// New entries may be added to the returned object via Set call.
//
// The returned object is valid until Reset is called on a.
func (a *Arena) NewObject() *Value {
v := a.c.getValue()
v.t = TypeObject
v.o.reset()
return v
}
// NewArray returns new empty array value.
//
// New entries may be added to the returned array via Set* calls.
//
// The returned array is valid until Reset is called on a.
func (a *Arena) NewArray() *Value {
v := a.c.getValue()
v.t = TypeArray
v.a = v.a[:0]
return v
}
// NewString returns new string value containing s.
//
// The returned string is valid until Reset is called on a.
func (a *Arena) NewString(s string) *Value {
v := a.c.getValue()
v.t = typeRawString
bLen := len(a.b)
a.b = escapeString(a.b, s)
v.s = b2s(a.b[bLen+1 : len(a.b)-1])
return v
}
// NewStringBytes returns new string value containing b.
//
// The returned string is valid until Reset is called on a.
func (a *Arena) NewStringBytes(b []byte) *Value {
v := a.c.getValue()
v.t = typeRawString
bLen := len(a.b)
a.b = escapeString(a.b, b2s(b))
v.s = b2s(a.b[bLen+1 : len(a.b)-1])
return v
}
// NewNumberFloat64 returns new number value containing f.
//
// The returned number is valid until Reset is called on a.
func (a *Arena) NewNumberFloat64(f float64) *Value {
v := a.c.getValue()
v.t = TypeNumber
bLen := len(a.b)
a.b = strconv.AppendFloat(a.b, f, 'g', -1, 64)
v.s = b2s(a.b[bLen:])
return v
}
// NewNumberInt returns new number value containing n.
//
// The returned number is valid until Reset is called on a.
func (a *Arena) NewNumberInt(n int) *Value {
v := a.c.getValue()
v.t = TypeNumber
bLen := len(a.b)
a.b = strconv.AppendInt(a.b, int64(n), 10)
v.s = b2s(a.b[bLen:])
return v
}
// NewNumberString returns new number value containing s.
//
// The returned number is valid until Reset is called on a.
func (a *Arena) NewNumberString(s string) *Value {
v := a.c.getValue()
v.t = TypeNumber
v.s = s
return v
}
// NewNull returns null value.
func (a *Arena) NewNull() *Value {
return valueNull
}
// NewTrue returns true value.
func (a *Arena) NewTrue() *Value {
return valueTrue
}
// NewFalse return false value.
func (a *Arena) NewFalse() *Value {
return valueFalse
}

9
vendor/github.com/valyala/fastjson/doc.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
/*
Package fastjson provides fast JSON parsing.
Arbitrary JSON may be parsed by fastjson without the need for creating structs
or for generating go code. Just parse JSON and get the required fields with
Get* functions.
*/
package fastjson

515
vendor/github.com/valyala/fastjson/fastfloat/parse.go generated vendored Normal file
View File

@ -0,0 +1,515 @@
package fastfloat
import (
"fmt"
"math"
"strconv"
"strings"
)
// ParseUint64BestEffort parses uint64 number s.
//
// It is equivalent to strconv.ParseUint(s, 10, 64), but is faster.
//
// 0 is returned if the number cannot be parsed.
// See also ParseUint64, which returns parse error if the number cannot be parsed.
func ParseUint64BestEffort(s string) uint64 {
if len(s) == 0 {
return 0
}
i := uint(0)
d := uint64(0)
j := i
for i < uint(len(s)) {
if s[i] >= '0' && s[i] <= '9' {
d = d*10 + uint64(s[i]-'0')
i++
if i > 18 {
// The integer part may be out of range for uint64.
// Fall back to slow parsing.
dd, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return 0
}
return dd
}
continue
}
break
}
if i <= j {
return 0
}
if i < uint(len(s)) {
// Unparsed tail left.
return 0
}
return d
}
// ParseUint64 parses uint64 from s.
//
// It is equivalent to strconv.ParseUint(s, 10, 64), but is faster.
//
// See also ParseUint64BestEffort.
func ParseUint64(s string) (uint64, error) {
if len(s) == 0 {
return 0, fmt.Errorf("cannot parse uint64 from empty string")
}
i := uint(0)
d := uint64(0)
j := i
for i < uint(len(s)) {
if s[i] >= '0' && s[i] <= '9' {
d = d*10 + uint64(s[i]-'0')
i++
if i > 18 {
// The integer part may be out of range for uint64.
// Fall back to slow parsing.
dd, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, err
}
return dd, nil
}
continue
}
break
}
if i <= j {
return 0, fmt.Errorf("cannot parse uint64 from %q", s)
}
if i < uint(len(s)) {
// Unparsed tail left.
return 0, fmt.Errorf("unparsed tail left after parsing uint64 from %q: %q", s, s[i:])
}
return d, nil
}
// ParseInt64BestEffort parses int64 number s.
//
// It is equivalent to strconv.ParseInt(s, 10, 64), but is faster.
//
// 0 is returned if the number cannot be parsed.
// See also ParseInt64, which returns parse error if the number cannot be parsed.
func ParseInt64BestEffort(s string) int64 {
if len(s) == 0 {
return 0
}
i := uint(0)
minus := s[0] == '-'
if minus {
i++
if i >= uint(len(s)) {
return 0
}
}
d := int64(0)
j := i
for i < uint(len(s)) {
if s[i] >= '0' && s[i] <= '9' {
d = d*10 + int64(s[i]-'0')
i++
if i > 18 {
// The integer part may be out of range for int64.
// Fall back to slow parsing.
dd, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return 0
}
return dd
}
continue
}
break
}
if i <= j {
return 0
}
if i < uint(len(s)) {
// Unparsed tail left.
return 0
}
if minus {
d = -d
}
return d
}
// ParseInt64 parses int64 number s.
//
// It is equivalent to strconv.ParseInt(s, 10, 64), but is faster.
//
// See also ParseInt64BestEffort.
func ParseInt64(s string) (int64, error) {
if len(s) == 0 {
return 0, fmt.Errorf("cannot parse int64 from empty string")
}
i := uint(0)
minus := s[0] == '-'
if minus {
i++
if i >= uint(len(s)) {
return 0, fmt.Errorf("cannot parse int64 from %q", s)
}
}
d := int64(0)
j := i
for i < uint(len(s)) {
if s[i] >= '0' && s[i] <= '9' {
d = d*10 + int64(s[i]-'0')
i++
if i > 18 {
// The integer part may be out of range for int64.
// Fall back to slow parsing.
dd, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return 0, err
}
return dd, nil
}
continue
}
break
}
if i <= j {
return 0, fmt.Errorf("cannot parse int64 from %q", s)
}
if i < uint(len(s)) {
// Unparsed tail left.
return 0, fmt.Errorf("unparsed tail left after parsing int64 form %q: %q", s, s[i:])
}
if minus {
d = -d
}
return d, nil
}
// Exact powers of 10.
//
// This works faster than math.Pow10, since it avoids additional multiplication.
var float64pow10 = [...]float64{
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16,
}
// ParseBestEffort parses floating-point number s.
//
// It is equivalent to strconv.ParseFloat(s, 64), but is faster.
//
// 0 is returned if the number cannot be parsed.
// See also Parse, which returns parse error if the number cannot be parsed.
func ParseBestEffort(s string) float64 {
if len(s) == 0 {
return 0
}
i := uint(0)
minus := s[0] == '-'
if minus {
i++
if i >= uint(len(s)) {
return 0
}
}
// the integer part might be elided to remain compliant
// with https://go.dev/ref/spec#Floating-point_literals
if s[i] == '.' && (i+1 >= uint(len(s)) || s[i+1] < '0' || s[i+1] > '9') {
return 0
}
d := uint64(0)
j := i
for i < uint(len(s)) {
if s[i] >= '0' && s[i] <= '9' {
d = d*10 + uint64(s[i]-'0')
i++
if i > 18 {
// The integer part may be out of range for uint64.
// Fall back to slow parsing.
f, err := strconv.ParseFloat(s, 64)
if err != nil && !math.IsInf(f, 0) {
return 0
}
return f
}
continue
}
break
}
if i <= j && s[i] != '.' {
s = s[i:]
if strings.HasPrefix(s, "+") {
s = s[1:]
}
// "infinity" is needed for OpenMetrics support.
// See https://github.com/OpenObservability/OpenMetrics/blob/master/OpenMetrics.md
if strings.EqualFold(s, "inf") || strings.EqualFold(s, "infinity") {
if minus {
return -inf
}
return inf
}
if strings.EqualFold(s, "nan") {
return nan
}
return 0
}
f := float64(d)
if i >= uint(len(s)) {
// Fast path - just integer.
if minus {
f = -f
}
return f
}
if s[i] == '.' {
// Parse fractional part.
i++
if i >= uint(len(s)) {
// the fractional part may be elided to remain compliant
// with https://go.dev/ref/spec#Floating-point_literals
return f
}
k := i
for i < uint(len(s)) {
if s[i] >= '0' && s[i] <= '9' {
d = d*10 + uint64(s[i]-'0')
i++
if i-j >= uint(len(float64pow10)) {
// The mantissa is out of range. Fall back to standard parsing.
f, err := strconv.ParseFloat(s, 64)
if err != nil && !math.IsInf(f, 0) {
return 0
}
return f
}
continue
}
break
}
if i < k {
return 0
}
// Convert the entire mantissa to a float at once to avoid rounding errors.
f = float64(d) / float64pow10[i-k]
if i >= uint(len(s)) {
// Fast path - parsed fractional number.
if minus {
f = -f
}
return f
}
}
if s[i] == 'e' || s[i] == 'E' {
// Parse exponent part.
i++
if i >= uint(len(s)) {
return 0
}
expMinus := false
if s[i] == '+' || s[i] == '-' {
expMinus = s[i] == '-'
i++
if i >= uint(len(s)) {
return 0
}
}
exp := int16(0)
j := i
for i < uint(len(s)) {
if s[i] >= '0' && s[i] <= '9' {
exp = exp*10 + int16(s[i]-'0')
i++
if exp > 300 {
// The exponent may be too big for float64.
// Fall back to standard parsing.
f, err := strconv.ParseFloat(s, 64)
if err != nil && !math.IsInf(f, 0) {
return 0
}
return f
}
continue
}
break
}
if i <= j {
return 0
}
if expMinus {
exp = -exp
}
f *= math.Pow10(int(exp))
if i >= uint(len(s)) {
if minus {
f = -f
}
return f
}
}
return 0
}
// Parse parses floating-point number s.
//
// It is equivalent to strconv.ParseFloat(s, 64), but is faster.
//
// See also ParseBestEffort.
func Parse(s string) (float64, error) {
if len(s) == 0 {
return 0, fmt.Errorf("cannot parse float64 from empty string")
}
i := uint(0)
minus := s[0] == '-'
if minus {
i++
if i >= uint(len(s)) {
return 0, fmt.Errorf("cannot parse float64 from %q", s)
}
}
// the integer part might be elided to remain compliant
// with https://go.dev/ref/spec#Floating-point_literals
if s[i] == '.' && (i+1 >= uint(len(s)) || s[i+1] < '0' || s[i+1] > '9') {
return 0, fmt.Errorf("missing integer and fractional part in %q", s)
}
d := uint64(0)
j := i
for i < uint(len(s)) {
if s[i] >= '0' && s[i] <= '9' {
d = d*10 + uint64(s[i]-'0')
i++
if i > 18 {
// The integer part may be out of range for uint64.
// Fall back to slow parsing.
f, err := strconv.ParseFloat(s, 64)
if err != nil && !math.IsInf(f, 0) {
return 0, err
}
return f, nil
}
continue
}
break
}
if i <= j && s[i] != '.' {
ss := s[i:]
if strings.HasPrefix(ss, "+") {
ss = ss[1:]
}
// "infinity" is needed for OpenMetrics support.
// See https://github.com/OpenObservability/OpenMetrics/blob/master/OpenMetrics.md
if strings.EqualFold(ss, "inf") || strings.EqualFold(ss, "infinity") {
if minus {
return -inf, nil
}
return inf, nil
}
if strings.EqualFold(ss, "nan") {
return nan, nil
}
return 0, fmt.Errorf("unparsed tail left after parsing float64 from %q: %q", s, ss)
}
f := float64(d)
if i >= uint(len(s)) {
// Fast path - just integer.
if minus {
f = -f
}
return f, nil
}
if s[i] == '.' {
// Parse fractional part.
i++
if i >= uint(len(s)) {
// the fractional part might be elided to remain compliant
// with https://go.dev/ref/spec#Floating-point_literals
return f, nil
}
k := i
for i < uint(len(s)) {
if s[i] >= '0' && s[i] <= '9' {
d = d*10 + uint64(s[i]-'0')
i++
if i-j >= uint(len(float64pow10)) {
// The mantissa is out of range. Fall back to standard parsing.
f, err := strconv.ParseFloat(s, 64)
if err != nil && !math.IsInf(f, 0) {
return 0, fmt.Errorf("cannot parse mantissa in %q: %s", s, err)
}
return f, nil
}
continue
}
break
}
if i < k {
return 0, fmt.Errorf("cannot find mantissa in %q", s)
}
// Convert the entire mantissa to a float at once to avoid rounding errors.
f = float64(d) / float64pow10[i-k]
if i >= uint(len(s)) {
// Fast path - parsed fractional number.
if minus {
f = -f
}
return f, nil
}
}
if s[i] == 'e' || s[i] == 'E' {
// Parse exponent part.
i++
if i >= uint(len(s)) {
return 0, fmt.Errorf("cannot parse exponent in %q", s)
}
expMinus := false
if s[i] == '+' || s[i] == '-' {
expMinus = s[i] == '-'
i++
if i >= uint(len(s)) {
return 0, fmt.Errorf("cannot parse exponent in %q", s)
}
}
exp := int16(0)
j := i
for i < uint(len(s)) {
if s[i] >= '0' && s[i] <= '9' {
exp = exp*10 + int16(s[i]-'0')
i++
if exp > 300 {
// The exponent may be too big for float64.
// Fall back to standard parsing.
f, err := strconv.ParseFloat(s, 64)
if err != nil && !math.IsInf(f, 0) {
return 0, fmt.Errorf("cannot parse exponent in %q: %s", s, err)
}
return f, nil
}
continue
}
break
}
if i <= j {
return 0, fmt.Errorf("cannot parse exponent in %q", s)
}
if expMinus {
exp = -exp
}
f *= math.Pow10(int(exp))
if i >= uint(len(s)) {
if minus {
f = -f
}
return f, nil
}
}
return 0, fmt.Errorf("cannot parse float64 from %q", s)
}
var inf = math.Inf(1)
var nan = math.NaN()

22
vendor/github.com/valyala/fastjson/fuzz.go generated vendored Normal file
View File

@ -0,0 +1,22 @@
// +build gofuzz
package fastjson
func Fuzz(data []byte) int {
err := ValidateBytes(data)
if err != nil {
return 0
}
v := MustParseBytes(data)
dst := make([]byte, 0)
dst = v.MarshalTo(dst)
err = ValidateBytes(dst)
if err != nil {
panic(err)
}
return 1
}

170
vendor/github.com/valyala/fastjson/handy.go generated vendored Normal file
View File

@ -0,0 +1,170 @@
package fastjson
var handyPool ParserPool
// GetString returns string value for the field identified by keys path
// in JSON data.
//
// Array indexes may be represented as decimal numbers in keys.
//
// An empty string is returned on error. Use Parser for proper error handling.
//
// Parser is faster for obtaining multiple fields from JSON.
func GetString(data []byte, keys ...string) string {
p := handyPool.Get()
v, err := p.ParseBytes(data)
if err != nil {
handyPool.Put(p)
return ""
}
sb := v.GetStringBytes(keys...)
str := string(sb)
handyPool.Put(p)
return str
}
// GetBytes returns string value for the field identified by keys path
// in JSON data.
//
// Array indexes may be represented as decimal numbers in keys.
//
// nil is returned on error. Use Parser for proper error handling.
//
// Parser is faster for obtaining multiple fields from JSON.
func GetBytes(data []byte, keys ...string) []byte {
p := handyPool.Get()
v, err := p.ParseBytes(data)
if err != nil {
handyPool.Put(p)
return nil
}
sb := v.GetStringBytes(keys...)
// Make a copy of sb, since sb belongs to p.
var b []byte
if sb != nil {
b = append(b, sb...)
}
handyPool.Put(p)
return b
}
// GetInt returns int value for the field identified by keys path
// in JSON data.
//
// Array indexes may be represented as decimal numbers in keys.
//
// 0 is returned on error. Use Parser for proper error handling.
//
// Parser is faster for obtaining multiple fields from JSON.
func GetInt(data []byte, keys ...string) int {
p := handyPool.Get()
v, err := p.ParseBytes(data)
if err != nil {
handyPool.Put(p)
return 0
}
n := v.GetInt(keys...)
handyPool.Put(p)
return n
}
// GetFloat64 returns float64 value for the field identified by keys path
// in JSON data.
//
// Array indexes may be represented as decimal numbers in keys.
//
// 0 is returned on error. Use Parser for proper error handling.
//
// Parser is faster for obtaining multiple fields from JSON.
func GetFloat64(data []byte, keys ...string) float64 {
p := handyPool.Get()
v, err := p.ParseBytes(data)
if err != nil {
handyPool.Put(p)
return 0
}
f := v.GetFloat64(keys...)
handyPool.Put(p)
return f
}
// GetBool returns boolean value for the field identified by keys path
// in JSON data.
//
// Array indexes may be represented as decimal numbers in keys.
//
// False is returned on error. Use Parser for proper error handling.
//
// Parser is faster for obtaining multiple fields from JSON.
func GetBool(data []byte, keys ...string) bool {
p := handyPool.Get()
v, err := p.ParseBytes(data)
if err != nil {
handyPool.Put(p)
return false
}
b := v.GetBool(keys...)
handyPool.Put(p)
return b
}
// Exists returns true if the field identified by keys path exists in JSON data.
//
// Array indexes may be represented as decimal numbers in keys.
//
// False is returned on error. Use Parser for proper error handling.
//
// Parser is faster when multiple fields must be checked in the JSON.
func Exists(data []byte, keys ...string) bool {
p := handyPool.Get()
v, err := p.ParseBytes(data)
if err != nil {
handyPool.Put(p)
return false
}
ok := v.Exists(keys...)
handyPool.Put(p)
return ok
}
// Parse parses json string s.
//
// The function is slower than the Parser.Parse for re-used Parser.
func Parse(s string) (*Value, error) {
var p Parser
return p.Parse(s)
}
// MustParse parses json string s.
//
// The function panics if s cannot be parsed.
// The function is slower than the Parser.Parse for re-used Parser.
func MustParse(s string) *Value {
v, err := Parse(s)
if err != nil {
panic(err)
}
return v
}
// ParseBytes parses b containing json.
//
// The function is slower than the Parser.ParseBytes for re-used Parser.
func ParseBytes(b []byte) (*Value, error) {
var p Parser
return p.ParseBytes(b)
}
// MustParseBytes parses b containing json.
//
// The function panics if b cannot be parsed.
// The function is slower than the Parser.ParseBytes for re-used Parser.
func MustParseBytes(b []byte) *Value {
v, err := ParseBytes(b)
if err != nil {
panic(err)
}
return v
}

976
vendor/github.com/valyala/fastjson/parser.go generated vendored Normal file
View File

@ -0,0 +1,976 @@
package fastjson
import (
"fmt"
"github.com/valyala/fastjson/fastfloat"
"strconv"
"strings"
"unicode/utf16"
)
// Parser parses JSON.
//
// Parser may be re-used for subsequent parsing.
//
// Parser cannot be used from concurrent goroutines.
// Use per-goroutine parsers or ParserPool instead.
type Parser struct {
// b contains working copy of the string to be parsed.
b []byte
// c is a cache for json values.
c cache
}
// Parse parses s containing JSON.
//
// The returned value is valid until the next call to Parse*.
//
// Use Scanner if a stream of JSON values must be parsed.
func (p *Parser) Parse(s string) (*Value, error) {
s = skipWS(s)
p.b = append(p.b[:0], s...)
p.c.reset()
v, tail, err := parseValue(b2s(p.b), &p.c, 0)
if err != nil {
return nil, fmt.Errorf("cannot parse JSON: %s; unparsed tail: %q", err, startEndString(tail))
}
tail = skipWS(tail)
if len(tail) > 0 {
return nil, fmt.Errorf("unexpected tail: %q", startEndString(tail))
}
return v, nil
}
// ParseBytes parses b containing JSON.
//
// The returned Value is valid until the next call to Parse*.
//
// Use Scanner if a stream of JSON values must be parsed.
func (p *Parser) ParseBytes(b []byte) (*Value, error) {
return p.Parse(b2s(b))
}
type cache struct {
vs []Value
}
func (c *cache) reset() {
c.vs = c.vs[:0]
}
func (c *cache) getValue() *Value {
if cap(c.vs) > len(c.vs) {
c.vs = c.vs[:len(c.vs)+1]
} else {
c.vs = append(c.vs, Value{})
}
// Do not reset the value, since the caller must properly init it.
return &c.vs[len(c.vs)-1]
}
func skipWS(s string) string {
if len(s) == 0 || s[0] > 0x20 {
// Fast path.
return s
}
return skipWSSlow(s)
}
func skipWSSlow(s string) string {
if len(s) == 0 || s[0] != 0x20 && s[0] != 0x0A && s[0] != 0x09 && s[0] != 0x0D {
return s
}
for i := 1; i < len(s); i++ {
if s[i] != 0x20 && s[i] != 0x0A && s[i] != 0x09 && s[i] != 0x0D {
return s[i:]
}
}
return ""
}
type kv struct {
k string
v *Value
}
// MaxDepth is the maximum depth for nested JSON.
const MaxDepth = 300
func parseValue(s string, c *cache, depth int) (*Value, string, error) {
if len(s) == 0 {
return nil, s, fmt.Errorf("cannot parse empty string")
}
depth++
if depth > MaxDepth {
return nil, s, fmt.Errorf("too big depth for the nested JSON; it exceeds %d", MaxDepth)
}
if s[0] == '{' {
v, tail, err := parseObject(s[1:], c, depth)
if err != nil {
return nil, tail, fmt.Errorf("cannot parse object: %s", err)
}
return v, tail, nil
}
if s[0] == '[' {
v, tail, err := parseArray(s[1:], c, depth)
if err != nil {
return nil, tail, fmt.Errorf("cannot parse array: %s", err)
}
return v, tail, nil
}
if s[0] == '"' {
ss, tail, err := parseRawString(s[1:])
if err != nil {
return nil, tail, fmt.Errorf("cannot parse string: %s", err)
}
v := c.getValue()
v.t = typeRawString
v.s = ss
return v, tail, nil
}
if s[0] == 't' {
if len(s) < len("true") || s[:len("true")] != "true" {
return nil, s, fmt.Errorf("unexpected value found: %q", s)
}
return valueTrue, s[len("true"):], nil
}
if s[0] == 'f' {
if len(s) < len("false") || s[:len("false")] != "false" {
return nil, s, fmt.Errorf("unexpected value found: %q", s)
}
return valueFalse, s[len("false"):], nil
}
if s[0] == 'n' {
if len(s) < len("null") || s[:len("null")] != "null" {
// Try parsing NaN
if len(s) >= 3 && strings.EqualFold(s[:3], "nan") {
v := c.getValue()
v.t = TypeNumber
v.s = s[:3]
return v, s[3:], nil
}
return nil, s, fmt.Errorf("unexpected value found: %q", s)
}
return valueNull, s[len("null"):], nil
}
ns, tail, err := parseRawNumber(s)
if err != nil {
return nil, tail, fmt.Errorf("cannot parse number: %s", err)
}
v := c.getValue()
v.t = TypeNumber
v.s = ns
return v, tail, nil
}
func parseArray(s string, c *cache, depth int) (*Value, string, error) {
s = skipWS(s)
if len(s) == 0 {
return nil, s, fmt.Errorf("missing ']'")
}
if s[0] == ']' {
v := c.getValue()
v.t = TypeArray
v.a = v.a[:0]
return v, s[1:], nil
}
a := c.getValue()
a.t = TypeArray
a.a = a.a[:0]
for {
var v *Value
var err error
s = skipWS(s)
v, s, err = parseValue(s, c, depth)
if err != nil {
return nil, s, fmt.Errorf("cannot parse array value: %s", err)
}
a.a = append(a.a, v)
s = skipWS(s)
if len(s) == 0 {
return nil, s, fmt.Errorf("unexpected end of array")
}
if s[0] == ',' {
s = s[1:]
continue
}
if s[0] == ']' {
s = s[1:]
return a, s, nil
}
return nil, s, fmt.Errorf("missing ',' after array value")
}
}
func parseObject(s string, c *cache, depth int) (*Value, string, error) {
s = skipWS(s)
if len(s) == 0 {
return nil, s, fmt.Errorf("missing '}'")
}
if s[0] == '}' {
v := c.getValue()
v.t = TypeObject
v.o.reset()
return v, s[1:], nil
}
o := c.getValue()
o.t = TypeObject
o.o.reset()
for {
var err error
kv := o.o.getKV()
// Parse key.
s = skipWS(s)
if len(s) == 0 || s[0] != '"' {
return nil, s, fmt.Errorf(`cannot find opening '"" for object key`)
}
kv.k, s, err = parseRawKey(s[1:])
if err != nil {
return nil, s, fmt.Errorf("cannot parse object key: %s", err)
}
s = skipWS(s)
if len(s) == 0 || s[0] != ':' {
return nil, s, fmt.Errorf("missing ':' after object key")
}
s = s[1:]
// Parse value
s = skipWS(s)
kv.v, s, err = parseValue(s, c, depth)
if err != nil {
return nil, s, fmt.Errorf("cannot parse object value: %s", err)
}
s = skipWS(s)
if len(s) == 0 {
return nil, s, fmt.Errorf("unexpected end of object")
}
if s[0] == ',' {
s = s[1:]
continue
}
if s[0] == '}' {
return o, s[1:], nil
}
return nil, s, fmt.Errorf("missing ',' after object value")
}
}
func escapeString(dst []byte, s string) []byte {
if !hasSpecialChars(s) {
// Fast path - nothing to escape.
dst = append(dst, '"')
dst = append(dst, s...)
dst = append(dst, '"')
return dst
}
// Slow path.
return strconv.AppendQuote(dst, s)
}
func hasSpecialChars(s string) bool {
if strings.IndexByte(s, '"') >= 0 || strings.IndexByte(s, '\\') >= 0 {
return true
}
for i := 0; i < len(s); i++ {
if s[i] < 0x20 {
return true
}
}
return false
}
func unescapeStringBestEffort(s string) string {
n := strings.IndexByte(s, '\\')
if n < 0 {
// Fast path - nothing to unescape.
return s
}
// Slow path - unescape string.
b := s2b(s) // It is safe to do, since s points to a byte slice in Parser.b.
b = b[:n]
s = s[n+1:]
for len(s) > 0 {
ch := s[0]
s = s[1:]
switch ch {
case '"':
b = append(b, '"')
case '\\':
b = append(b, '\\')
case '/':
b = append(b, '/')
case 'b':
b = append(b, '\b')
case 'f':
b = append(b, '\f')
case 'n':
b = append(b, '\n')
case 'r':
b = append(b, '\r')
case 't':
b = append(b, '\t')
case 'u':
if len(s) < 4 {
// Too short escape sequence. Just store it unchanged.
b = append(b, "\\u"...)
break
}
xs := s[:4]
x, err := strconv.ParseUint(xs, 16, 16)
if err != nil {
// Invalid escape sequence. Just store it unchanged.
b = append(b, "\\u"...)
break
}
s = s[4:]
if !utf16.IsSurrogate(rune(x)) {
b = append(b, string(rune(x))...)
break
}
// Surrogate.
// See https://en.wikipedia.org/wiki/Universal_Character_Set_characters#Surrogates
if len(s) < 6 || s[0] != '\\' || s[1] != 'u' {
b = append(b, "\\u"...)
b = append(b, xs...)
break
}
x1, err := strconv.ParseUint(s[2:6], 16, 16)
if err != nil {
b = append(b, "\\u"...)
b = append(b, xs...)
break
}
r := utf16.DecodeRune(rune(x), rune(x1))
b = append(b, string(r)...)
s = s[6:]
default:
// Unknown escape sequence. Just store it unchanged.
b = append(b, '\\', ch)
}
n = strings.IndexByte(s, '\\')
if n < 0 {
b = append(b, s...)
break
}
b = append(b, s[:n]...)
s = s[n+1:]
}
return b2s(b)
}
// parseRawKey is similar to parseRawString, but is optimized
// for small-sized keys without escape sequences.
func parseRawKey(s string) (string, string, error) {
for i := 0; i < len(s); i++ {
if s[i] == '"' {
// Fast path.
return s[:i], s[i+1:], nil
}
if s[i] == '\\' {
// Slow path.
return parseRawString(s)
}
}
return s, "", fmt.Errorf(`missing closing '"'`)
}
func parseRawString(s string) (string, string, error) {
n := strings.IndexByte(s, '"')
if n < 0 {
return s, "", fmt.Errorf(`missing closing '"'`)
}
if n == 0 || s[n-1] != '\\' {
// Fast path. No escaped ".
return s[:n], s[n+1:], nil
}
// Slow path - possible escaped " found.
ss := s
for {
i := n - 1
for i > 0 && s[i-1] == '\\' {
i--
}
if uint(n-i)%2 == 0 {
return ss[:len(ss)-len(s)+n], s[n+1:], nil
}
s = s[n+1:]
n = strings.IndexByte(s, '"')
if n < 0 {
return ss, "", fmt.Errorf(`missing closing '"'`)
}
if n == 0 || s[n-1] != '\\' {
return ss[:len(ss)-len(s)+n], s[n+1:], nil
}
}
}
func parseRawNumber(s string) (string, string, error) {
// The caller must ensure len(s) > 0
// Find the end of the number.
for i := 0; i < len(s); i++ {
ch := s[i]
if (ch >= '0' && ch <= '9') || ch == '.' || ch == '-' || ch == 'e' || ch == 'E' || ch == '+' {
continue
}
if i == 0 || i == 1 && (s[0] == '-' || s[0] == '+') {
if len(s[i:]) >= 3 {
xs := s[i : i+3]
if strings.EqualFold(xs, "inf") || strings.EqualFold(xs, "nan") {
return s[:i+3], s[i+3:], nil
}
}
return "", s, fmt.Errorf("unexpected char: %q", s[:1])
}
ns := s[:i]
s = s[i:]
return ns, s, nil
}
return s, "", nil
}
// Object represents JSON object.
//
// Object cannot be used from concurrent goroutines.
// Use per-goroutine parsers or ParserPool instead.
type Object struct {
kvs []kv
keysUnescaped bool
}
func (o *Object) reset() {
o.kvs = o.kvs[:0]
o.keysUnescaped = false
}
// MarshalTo appends marshaled o to dst and returns the result.
func (o *Object) MarshalTo(dst []byte) []byte {
dst = append(dst, '{')
for i, kv := range o.kvs {
if o.keysUnescaped {
dst = escapeString(dst, kv.k)
} else {
dst = append(dst, '"')
dst = append(dst, kv.k...)
dst = append(dst, '"')
}
dst = append(dst, ':')
dst = kv.v.MarshalTo(dst)
if i != len(o.kvs)-1 {
dst = append(dst, ',')
}
}
dst = append(dst, '}')
return dst
}
// String returns string representation for the o.
//
// This function is for debugging purposes only. It isn't optimized for speed.
// See MarshalTo instead.
func (o *Object) String() string {
b := o.MarshalTo(nil)
// It is safe converting b to string without allocation, since b is no longer
// reachable after this line.
return b2s(b)
}
func (o *Object) getKV() *kv {
if cap(o.kvs) > len(o.kvs) {
o.kvs = o.kvs[:len(o.kvs)+1]
} else {
o.kvs = append(o.kvs, kv{})
}
return &o.kvs[len(o.kvs)-1]
}
func (o *Object) unescapeKeys() {
if o.keysUnescaped {
return
}
kvs := o.kvs
for i := range kvs {
kv := &kvs[i]
kv.k = unescapeStringBestEffort(kv.k)
}
o.keysUnescaped = true
}
// Len returns the number of items in the o.
func (o *Object) Len() int {
return len(o.kvs)
}
// Get returns the value for the given key in the o.
//
// Returns nil if the value for the given key isn't found.
//
// The returned value is valid until Parse is called on the Parser returned o.
func (o *Object) Get(key string) *Value {
if !o.keysUnescaped && strings.IndexByte(key, '\\') < 0 {
// Fast path - try searching for the key without object keys unescaping.
for _, kv := range o.kvs {
if kv.k == key {
return kv.v
}
}
}
// Slow path - unescape object keys.
o.unescapeKeys()
for _, kv := range o.kvs {
if kv.k == key {
return kv.v
}
}
return nil
}
// Visit calls f for each item in the o in the original order
// of the parsed JSON.
//
// f cannot hold key and/or v after returning.
func (o *Object) Visit(f func(key []byte, v *Value)) {
if o == nil {
return
}
o.unescapeKeys()
for _, kv := range o.kvs {
f(s2b(kv.k), kv.v)
}
}
// Value represents any JSON value.
//
// Call Type in order to determine the actual type of the JSON value.
//
// Value cannot be used from concurrent goroutines.
// Use per-goroutine parsers or ParserPool instead.
type Value struct {
o Object
a []*Value
s string
t Type
}
// MarshalTo appends marshaled v to dst and returns the result.
func (v *Value) MarshalTo(dst []byte) []byte {
switch v.t {
case typeRawString:
dst = append(dst, '"')
dst = append(dst, v.s...)
dst = append(dst, '"')
return dst
case TypeObject:
return v.o.MarshalTo(dst)
case TypeArray:
dst = append(dst, '[')
for i, vv := range v.a {
dst = vv.MarshalTo(dst)
if i != len(v.a)-1 {
dst = append(dst, ',')
}
}
dst = append(dst, ']')
return dst
case TypeString:
return escapeString(dst, v.s)
case TypeNumber:
return append(dst, v.s...)
case TypeTrue:
return append(dst, "true"...)
case TypeFalse:
return append(dst, "false"...)
case TypeNull:
return append(dst, "null"...)
default:
panic(fmt.Errorf("BUG: unexpected Value type: %d", v.t))
}
}
// String returns string representation of the v.
//
// The function is for debugging purposes only. It isn't optimized for speed.
// See MarshalTo instead.
//
// Don't confuse this function with StringBytes, which must be called
// for obtaining the underlying JSON string for the v.
func (v *Value) String() string {
b := v.MarshalTo(nil)
// It is safe converting b to string without allocation, since b is no longer
// reachable after this line.
return b2s(b)
}
// Type represents JSON type.
type Type int
const (
// TypeNull is JSON null.
TypeNull Type = 0
// TypeObject is JSON object type.
TypeObject Type = 1
// TypeArray is JSON array type.
TypeArray Type = 2
// TypeString is JSON string type.
TypeString Type = 3
// TypeNumber is JSON number type.
TypeNumber Type = 4
// TypeTrue is JSON true.
TypeTrue Type = 5
// TypeFalse is JSON false.
TypeFalse Type = 6
typeRawString Type = 7
)
// String returns string representation of t.
func (t Type) String() string {
switch t {
case TypeObject:
return "object"
case TypeArray:
return "array"
case TypeString:
return "string"
case TypeNumber:
return "number"
case TypeTrue:
return "true"
case TypeFalse:
return "false"
case TypeNull:
return "null"
// typeRawString is skipped intentionally,
// since it shouldn't be visible to user.
default:
panic(fmt.Errorf("BUG: unknown Value type: %d", t))
}
}
// Type returns the type of the v.
func (v *Value) Type() Type {
if v.t == typeRawString {
v.s = unescapeStringBestEffort(v.s)
v.t = TypeString
}
return v.t
}
// Exists returns true if the field exists for the given keys path.
//
// Array indexes may be represented as decimal numbers in keys.
func (v *Value) Exists(keys ...string) bool {
v = v.Get(keys...)
return v != nil
}
// Get returns value by the given keys path.
//
// Array indexes may be represented as decimal numbers in keys.
//
// nil is returned for non-existing keys path.
//
// The returned value is valid until Parse is called on the Parser returned v.
func (v *Value) Get(keys ...string) *Value {
if v == nil {
return nil
}
for _, key := range keys {
if v.t == TypeObject {
v = v.o.Get(key)
if v == nil {
return nil
}
} else if v.t == TypeArray {
n, err := strconv.Atoi(key)
if err != nil || n < 0 || n >= len(v.a) {
return nil
}
v = v.a[n]
} else {
return nil
}
}
return v
}
// GetObject returns object value by the given keys path.
//
// Array indexes may be represented as decimal numbers in keys.
//
// nil is returned for non-existing keys path or for invalid value type.
//
// The returned object is valid until Parse is called on the Parser returned v.
func (v *Value) GetObject(keys ...string) *Object {
v = v.Get(keys...)
if v == nil || v.t != TypeObject {
return nil
}
return &v.o
}
// GetArray returns array value by the given keys path.
//
// Array indexes may be represented as decimal numbers in keys.
//
// nil is returned for non-existing keys path or for invalid value type.
//
// The returned array is valid until Parse is called on the Parser returned v.
func (v *Value) GetArray(keys ...string) []*Value {
v = v.Get(keys...)
if v == nil || v.t != TypeArray {
return nil
}
return v.a
}
// GetFloat64 returns float64 value by the given keys path.
//
// Array indexes may be represented as decimal numbers in keys.
//
// 0 is returned for non-existing keys path or for invalid value type.
func (v *Value) GetFloat64(keys ...string) float64 {
v = v.Get(keys...)
if v == nil || v.Type() != TypeNumber {
return 0
}
return fastfloat.ParseBestEffort(v.s)
}
// GetInt returns int value by the given keys path.
//
// Array indexes may be represented as decimal numbers in keys.
//
// 0 is returned for non-existing keys path or for invalid value type.
func (v *Value) GetInt(keys ...string) int {
v = v.Get(keys...)
if v == nil || v.Type() != TypeNumber {
return 0
}
n := fastfloat.ParseInt64BestEffort(v.s)
nn := int(n)
if int64(nn) != n {
return 0
}
return nn
}
// GetUint returns uint value by the given keys path.
//
// Array indexes may be represented as decimal numbers in keys.
//
// 0 is returned for non-existing keys path or for invalid value type.
func (v *Value) GetUint(keys ...string) uint {
v = v.Get(keys...)
if v == nil || v.Type() != TypeNumber {
return 0
}
n := fastfloat.ParseUint64BestEffort(v.s)
nn := uint(n)
if uint64(nn) != n {
return 0
}
return nn
}
// GetInt64 returns int64 value by the given keys path.
//
// Array indexes may be represented as decimal numbers in keys.
//
// 0 is returned for non-existing keys path or for invalid value type.
func (v *Value) GetInt64(keys ...string) int64 {
v = v.Get(keys...)
if v == nil || v.Type() != TypeNumber {
return 0
}
return fastfloat.ParseInt64BestEffort(v.s)
}
// GetUint64 returns uint64 value by the given keys path.
//
// Array indexes may be represented as decimal numbers in keys.
//
// 0 is returned for non-existing keys path or for invalid value type.
func (v *Value) GetUint64(keys ...string) uint64 {
v = v.Get(keys...)
if v == nil || v.Type() != TypeNumber {
return 0
}
return fastfloat.ParseUint64BestEffort(v.s)
}
// GetStringBytes returns string value by the given keys path.
//
// Array indexes may be represented as decimal numbers in keys.
//
// nil is returned for non-existing keys path or for invalid value type.
//
// The returned string is valid until Parse is called on the Parser returned v.
func (v *Value) GetStringBytes(keys ...string) []byte {
v = v.Get(keys...)
if v == nil || v.Type() != TypeString {
return nil
}
return s2b(v.s)
}
// GetBool returns bool value by the given keys path.
//
// Array indexes may be represented as decimal numbers in keys.
//
// false is returned for non-existing keys path or for invalid value type.
func (v *Value) GetBool(keys ...string) bool {
v = v.Get(keys...)
if v != nil && v.t == TypeTrue {
return true
}
return false
}
// Object returns the underlying JSON object for the v.
//
// The returned object is valid until Parse is called on the Parser returned v.
//
// Use GetObject if you don't need error handling.
func (v *Value) Object() (*Object, error) {
if v.t != TypeObject {
return nil, fmt.Errorf("value doesn't contain object; it contains %s", v.Type())
}
return &v.o, nil
}
// Array returns the underlying JSON array for the v.
//
// The returned array is valid until Parse is called on the Parser returned v.
//
// Use GetArray if you don't need error handling.
func (v *Value) Array() ([]*Value, error) {
if v.t != TypeArray {
return nil, fmt.Errorf("value doesn't contain array; it contains %s", v.Type())
}
return v.a, nil
}
// StringBytes returns the underlying JSON string for the v.
//
// The returned string is valid until Parse is called on the Parser returned v.
//
// Use GetStringBytes if you don't need error handling.
func (v *Value) StringBytes() ([]byte, error) {
if v.Type() != TypeString {
return nil, fmt.Errorf("value doesn't contain string; it contains %s", v.Type())
}
return s2b(v.s), nil
}
// Float64 returns the underlying JSON number for the v.
//
// Use GetFloat64 if you don't need error handling.
func (v *Value) Float64() (float64, error) {
if v.Type() != TypeNumber {
return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type())
}
return fastfloat.Parse(v.s)
}
// Int returns the underlying JSON int for the v.
//
// Use GetInt if you don't need error handling.
func (v *Value) Int() (int, error) {
if v.Type() != TypeNumber {
return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type())
}
n, err := fastfloat.ParseInt64(v.s)
if err != nil {
return 0, err
}
nn := int(n)
if int64(nn) != n {
return 0, fmt.Errorf("number %q doesn't fit int", v.s)
}
return nn, nil
}
// Uint returns the underlying JSON uint for the v.
//
// Use GetInt if you don't need error handling.
func (v *Value) Uint() (uint, error) {
if v.Type() != TypeNumber {
return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type())
}
n, err := fastfloat.ParseUint64(v.s)
if err != nil {
return 0, err
}
nn := uint(n)
if uint64(nn) != n {
return 0, fmt.Errorf("number %q doesn't fit uint", v.s)
}
return nn, nil
}
// Int64 returns the underlying JSON int64 for the v.
//
// Use GetInt64 if you don't need error handling.
func (v *Value) Int64() (int64, error) {
if v.Type() != TypeNumber {
return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type())
}
return fastfloat.ParseInt64(v.s)
}
// Uint64 returns the underlying JSON uint64 for the v.
//
// Use GetInt64 if you don't need error handling.
func (v *Value) Uint64() (uint64, error) {
if v.Type() != TypeNumber {
return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type())
}
return fastfloat.ParseUint64(v.s)
}
// Bool returns the underlying JSON bool for the v.
//
// Use GetBool if you don't need error handling.
func (v *Value) Bool() (bool, error) {
if v.t == TypeTrue {
return true, nil
}
if v.t == TypeFalse {
return false, nil
}
return false, fmt.Errorf("value doesn't contain bool; it contains %s", v.Type())
}
var (
valueTrue = &Value{t: TypeTrue}
valueFalse = &Value{t: TypeFalse}
valueNull = &Value{t: TypeNull}
)

52
vendor/github.com/valyala/fastjson/pool.go generated vendored Normal file
View File

@ -0,0 +1,52 @@
package fastjson
import (
"sync"
)
// ParserPool may be used for pooling Parsers for similarly typed JSONs.
type ParserPool struct {
pool sync.Pool
}
// Get returns a Parser from pp.
//
// The Parser must be Put to pp after use.
func (pp *ParserPool) Get() *Parser {
v := pp.pool.Get()
if v == nil {
return &Parser{}
}
return v.(*Parser)
}
// Put returns p to pp.
//
// p and objects recursively returned from p cannot be used after p
// is put into pp.
func (pp *ParserPool) Put(p *Parser) {
pp.pool.Put(p)
}
// ArenaPool may be used for pooling Arenas for similarly typed JSONs.
type ArenaPool struct {
pool sync.Pool
}
// Get returns an Arena from ap.
//
// The Arena must be Put to ap after use.
func (ap *ArenaPool) Get() *Arena {
v := ap.pool.Get()
if v == nil {
return &Arena{}
}
return v.(*Arena)
}
// Put returns a to ap.
//
// a and objects created by a cannot be used after a is put into ap.
func (ap *ArenaPool) Put(a *Arena) {
ap.pool.Put(a)
}

94
vendor/github.com/valyala/fastjson/scanner.go generated vendored Normal file
View File

@ -0,0 +1,94 @@
package fastjson
import (
"errors"
)
// Scanner scans a series of JSON values. Values may be delimited by whitespace.
//
// Scanner may parse JSON lines ( http://jsonlines.org/ ).
//
// Scanner may be re-used for subsequent parsing.
//
// Scanner cannot be used from concurrent goroutines.
//
// Use Parser for parsing only a single JSON value.
type Scanner struct {
// b contains a working copy of json value passed to Init.
b []byte
// s points to the next JSON value to parse.
s string
// err contains the last error.
err error
// v contains the last parsed JSON value.
v *Value
// c is used for caching JSON values.
c cache
}
// Init initializes sc with the given s.
//
// s may contain multiple JSON values, which may be delimited by whitespace.
func (sc *Scanner) Init(s string) {
sc.b = append(sc.b[:0], s...)
sc.s = b2s(sc.b)
sc.err = nil
sc.v = nil
}
// InitBytes initializes sc with the given b.
//
// b may contain multiple JSON values, which may be delimited by whitespace.
func (sc *Scanner) InitBytes(b []byte) {
sc.Init(b2s(b))
}
// Next parses the next JSON value from s passed to Init.
//
// Returns true on success. The parsed value is available via Value call.
//
// Returns false either on error or on the end of s.
// Call Error in order to determine the cause of the returned false.
func (sc *Scanner) Next() bool {
if sc.err != nil {
return false
}
sc.s = skipWS(sc.s)
if len(sc.s) == 0 {
sc.err = errEOF
return false
}
sc.c.reset()
v, tail, err := parseValue(sc.s, &sc.c, 0)
if err != nil {
sc.err = err
return false
}
sc.s = tail
sc.v = v
return true
}
// Error returns the last error.
func (sc *Scanner) Error() error {
if sc.err == errEOF {
return nil
}
return sc.err
}
// Value returns the last parsed value.
//
// The value is valid until the Next call.
func (sc *Scanner) Value() *Value {
return sc.v
}
var errEOF = errors.New("end of s")

110
vendor/github.com/valyala/fastjson/update.go generated vendored Normal file
View File

@ -0,0 +1,110 @@
package fastjson
import (
"strconv"
"strings"
)
// Del deletes the entry with the given key from o.
func (o *Object) Del(key string) {
if o == nil {
return
}
if !o.keysUnescaped && strings.IndexByte(key, '\\') < 0 {
// Fast path - try searching for the key without object keys unescaping.
for i, kv := range o.kvs {
if kv.k == key {
o.kvs = append(o.kvs[:i], o.kvs[i+1:]...)
return
}
}
}
// Slow path - unescape object keys before item search.
o.unescapeKeys()
for i, kv := range o.kvs {
if kv.k == key {
o.kvs = append(o.kvs[:i], o.kvs[i+1:]...)
return
}
}
}
// Del deletes the entry with the given key from array or object v.
func (v *Value) Del(key string) {
if v == nil {
return
}
if v.t == TypeObject {
v.o.Del(key)
return
}
if v.t == TypeArray {
n, err := strconv.Atoi(key)
if err != nil || n < 0 || n >= len(v.a) {
return
}
v.a = append(v.a[:n], v.a[n+1:]...)
}
}
// Set sets (key, value) entry in the o.
//
// The value must be unchanged during o lifetime.
func (o *Object) Set(key string, value *Value) {
if o == nil {
return
}
if value == nil {
value = valueNull
}
o.unescapeKeys()
// Try substituting already existing entry with the given key.
for i := range o.kvs {
kv := &o.kvs[i]
if kv.k == key {
kv.v = value
return
}
}
// Add new entry.
kv := o.getKV()
kv.k = key
kv.v = value
}
// Set sets (key, value) entry in the array or object v.
//
// The value must be unchanged during v lifetime.
func (v *Value) Set(key string, value *Value) {
if v == nil {
return
}
if v.t == TypeObject {
v.o.Set(key, value)
return
}
if v.t == TypeArray {
idx, err := strconv.Atoi(key)
if err != nil || idx < 0 {
return
}
v.SetArrayItem(idx, value)
}
}
// SetArrayItem sets the value in the array v at idx position.
//
// The value must be unchanged during v lifetime.
func (v *Value) SetArrayItem(idx int, value *Value) {
if v == nil || v.t != TypeArray {
return
}
for idx >= len(v.a) {
v.a = append(v.a, valueNull)
}
v.a[idx] = value
}

30
vendor/github.com/valyala/fastjson/util.go generated vendored Normal file
View File

@ -0,0 +1,30 @@
package fastjson
import (
"reflect"
"unsafe"
)
func b2s(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
func s2b(s string) (b []byte) {
strh := (*reflect.StringHeader)(unsafe.Pointer(&s))
sh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
sh.Data = strh.Data
sh.Len = strh.Len
sh.Cap = strh.Len
return b
}
const maxStartEndStringLen = 80
func startEndString(s string) string {
if len(s) <= maxStartEndStringLen {
return s
}
start := s[:40]
end := s[len(s)-40:]
return start + "..." + end
}

308
vendor/github.com/valyala/fastjson/validate.go generated vendored Normal file
View File

@ -0,0 +1,308 @@
package fastjson
import (
"fmt"
"strconv"
"strings"
)
// Validate validates JSON s.
func Validate(s string) error {
s = skipWS(s)
tail, err := validateValue(s)
if err != nil {
return fmt.Errorf("cannot parse JSON: %s; unparsed tail: %q", err, startEndString(tail))
}
tail = skipWS(tail)
if len(tail) > 0 {
return fmt.Errorf("unexpected tail: %q", startEndString(tail))
}
return nil
}
// ValidateBytes validates JSON b.
func ValidateBytes(b []byte) error {
return Validate(b2s(b))
}
func validateValue(s string) (string, error) {
if len(s) == 0 {
return s, fmt.Errorf("cannot parse empty string")
}
if s[0] == '{' {
tail, err := validateObject(s[1:])
if err != nil {
return tail, fmt.Errorf("cannot parse object: %s", err)
}
return tail, nil
}
if s[0] == '[' {
tail, err := validateArray(s[1:])
if err != nil {
return tail, fmt.Errorf("cannot parse array: %s", err)
}
return tail, nil
}
if s[0] == '"' {
sv, tail, err := validateString(s[1:])
if err != nil {
return tail, fmt.Errorf("cannot parse string: %s", err)
}
// Scan the string for control chars.
for i := 0; i < len(sv); i++ {
if sv[i] < 0x20 {
return tail, fmt.Errorf("string cannot contain control char 0x%02X", sv[i])
}
}
return tail, nil
}
if s[0] == 't' {
if len(s) < len("true") || s[:len("true")] != "true" {
return s, fmt.Errorf("unexpected value found: %q", s)
}
return s[len("true"):], nil
}
if s[0] == 'f' {
if len(s) < len("false") || s[:len("false")] != "false" {
return s, fmt.Errorf("unexpected value found: %q", s)
}
return s[len("false"):], nil
}
if s[0] == 'n' {
if len(s) < len("null") || s[:len("null")] != "null" {
return s, fmt.Errorf("unexpected value found: %q", s)
}
return s[len("null"):], nil
}
tail, err := validateNumber(s)
if err != nil {
return tail, fmt.Errorf("cannot parse number: %s", err)
}
return tail, nil
}
func validateArray(s string) (string, error) {
s = skipWS(s)
if len(s) == 0 {
return s, fmt.Errorf("missing ']'")
}
if s[0] == ']' {
return s[1:], nil
}
for {
var err error
s = skipWS(s)
s, err = validateValue(s)
if err != nil {
return s, fmt.Errorf("cannot parse array value: %s", err)
}
s = skipWS(s)
if len(s) == 0 {
return s, fmt.Errorf("unexpected end of array")
}
if s[0] == ',' {
s = s[1:]
continue
}
if s[0] == ']' {
s = s[1:]
return s, nil
}
return s, fmt.Errorf("missing ',' after array value")
}
}
func validateObject(s string) (string, error) {
s = skipWS(s)
if len(s) == 0 {
return s, fmt.Errorf("missing '}'")
}
if s[0] == '}' {
return s[1:], nil
}
for {
var err error
// Parse key.
s = skipWS(s)
if len(s) == 0 || s[0] != '"' {
return s, fmt.Errorf(`cannot find opening '"" for object key`)
}
var key string
key, s, err = validateKey(s[1:])
if err != nil {
return s, fmt.Errorf("cannot parse object key: %s", err)
}
// Scan the key for control chars.
for i := 0; i < len(key); i++ {
if key[i] < 0x20 {
return s, fmt.Errorf("object key cannot contain control char 0x%02X", key[i])
}
}
s = skipWS(s)
if len(s) == 0 || s[0] != ':' {
return s, fmt.Errorf("missing ':' after object key")
}
s = s[1:]
// Parse value
s = skipWS(s)
s, err = validateValue(s)
if err != nil {
return s, fmt.Errorf("cannot parse object value: %s", err)
}
s = skipWS(s)
if len(s) == 0 {
return s, fmt.Errorf("unexpected end of object")
}
if s[0] == ',' {
s = s[1:]
continue
}
if s[0] == '}' {
return s[1:], nil
}
return s, fmt.Errorf("missing ',' after object value")
}
}
// validateKey is similar to validateString, but is optimized
// for typical object keys, which are quite small and have no escape sequences.
func validateKey(s string) (string, string, error) {
for i := 0; i < len(s); i++ {
if s[i] == '"' {
// Fast path - the key doesn't contain escape sequences.
return s[:i], s[i+1:], nil
}
if s[i] == '\\' {
// Slow path - the key contains escape sequences.
return validateString(s)
}
}
return "", s, fmt.Errorf(`missing closing '"'`)
}
func validateString(s string) (string, string, error) {
// Try fast path - a string without escape sequences.
if n := strings.IndexByte(s, '"'); n >= 0 && strings.IndexByte(s[:n], '\\') < 0 {
return s[:n], s[n+1:], nil
}
// Slow path - escape sequences are present.
rs, tail, err := parseRawString(s)
if err != nil {
return rs, tail, err
}
for {
n := strings.IndexByte(rs, '\\')
if n < 0 {
return rs, tail, nil
}
n++
if n >= len(rs) {
return rs, tail, fmt.Errorf("BUG: parseRawString returned invalid string with trailing backslash: %q", rs)
}
ch := rs[n]
rs = rs[n+1:]
switch ch {
case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
// Valid escape sequences - see http://json.org/
break
case 'u':
if len(rs) < 4 {
return rs, tail, fmt.Errorf(`too short escape sequence: \u%s`, rs)
}
xs := rs[:4]
_, err := strconv.ParseUint(xs, 16, 16)
if err != nil {
return rs, tail, fmt.Errorf(`invalid escape sequence \u%s: %s`, xs, err)
}
rs = rs[4:]
default:
return rs, tail, fmt.Errorf(`unknown escape sequence \%c`, ch)
}
}
}
func validateNumber(s string) (string, error) {
if len(s) == 0 {
return s, fmt.Errorf("zero-length number")
}
if s[0] == '-' {
s = s[1:]
if len(s) == 0 {
return s, fmt.Errorf("missing number after minus")
}
}
i := 0
for i < len(s) {
if s[i] < '0' || s[i] > '9' {
break
}
i++
}
if i <= 0 {
return s, fmt.Errorf("expecting 0..9 digit, got %c", s[0])
}
if s[0] == '0' && i != 1 {
return s, fmt.Errorf("unexpected number starting from 0")
}
if i >= len(s) {
return "", nil
}
if s[i] == '.' {
// Validate fractional part
s = s[i+1:]
if len(s) == 0 {
return s, fmt.Errorf("missing fractional part")
}
i = 0
for i < len(s) {
if s[i] < '0' || s[i] > '9' {
break
}
i++
}
if i == 0 {
return s, fmt.Errorf("expecting 0..9 digit in fractional part, got %c", s[0])
}
if i >= len(s) {
return "", nil
}
}
if s[i] == 'e' || s[i] == 'E' {
// Validate exponent part
s = s[i+1:]
if len(s) == 0 {
return s, fmt.Errorf("missing exponent part")
}
if s[0] == '-' || s[0] == '+' {
s = s[1:]
if len(s) == 0 {
return s, fmt.Errorf("missing exponent part")
}
}
i = 0
for i < len(s) {
if s[i] < '0' || s[i] > '9' {
break
}
i++
}
if i == 0 {
return s, fmt.Errorf("expecting 0..9 digit in exponent part, got %c", s[0])
}
if i >= len(s) {
return "", nil
}
}
return s[i:], nil
}

16
vendor/modules.txt vendored
View File

@ -1,3 +1,6 @@
# git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078
## explicit; go 1.14
git.sr.ht/~mariusor/go-xsd-duration
# github.com/BurntSushi/toml v1.3.2
## explicit; go 1.16
github.com/BurntSushi/toml
@ -8,6 +11,15 @@ github.com/adrg/frontmatter
# github.com/caarlos0/env/v10 v10.0.0
## explicit; go 1.17
github.com/caarlos0/env/v10
# github.com/go-ap/activitypub v0.0.0-20240211124657-820024a66b78
## explicit; go 1.18
github.com/go-ap/activitypub
# github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7
## explicit; go 1.13
github.com/go-ap/errors
# github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
## explicit; go 1.13
github.com/go-ap/jsonld
# github.com/google/go-cmp v0.6.0
## explicit; go 1.13
github.com/google/go-cmp/cmp
@ -18,6 +30,10 @@ github.com/google/go-cmp/cmp/internal/value
# github.com/valyala/bytebufferpool v1.0.0
## explicit
github.com/valyala/bytebufferpool
# github.com/valyala/fastjson v1.6.4
## explicit; go 1.12
github.com/valyala/fastjson
github.com/valyala/fastjson/fastfloat
# github.com/valyala/quicktemplate v1.7.0
## explicit; go 1.11
github.com/valyala/quicktemplate

View File

@ -43,7 +43,7 @@ func NewBaseOf(site *domain.Site) BaseOf {
{% endfunc %}
{% func (b BaseOf) Dir() %}
{%s b.site.Language.Dir() %}
{%s b.site.Language.Dir().String() %}
{% endfunc %}
{% func Template(p Pager) %}

View File

@ -206,7 +206,7 @@ func (b BaseOf) Lang() string {
//line web/template/baseof.qtpl:45
func (b BaseOf) StreamDir(qw422016 *qt422016.Writer) {
//line web/template/baseof.qtpl:46
qw422016.E().S(b.site.Language.Dir())
qw422016.E().S(b.site.Language.Dir().String())
//line web/template/baseof.qtpl:47
}

View File

@ -5,10 +5,10 @@
{% code
type Page struct {
BaseOf
page *domain.Page
page *domain.Entry
}
func NewPage(base BaseOf, page *domain.Page) Page {
func NewPage(base BaseOf, page *domain.Entry) Page {
return Page{
BaseOf: base,
page: page,
@ -35,7 +35,7 @@ func NewPage(base BaseOf, page *domain.Page) Page {
{% func (p Page) Dir() %}
{% if p.page.Language != domain.LanguageUnd %}
{%s p.page.Language.Dir() %}
{%s p.page.Language.Dir().String() %}
{% else %}
{%= p.BaseOf.Lang() %}
{% endif %}

View File

@ -25,10 +25,10 @@ var (
//line web/template/page.qtpl:6
type Page struct {
BaseOf
page *domain.Page
page *domain.Entry
}
func NewPage(base BaseOf, page *domain.Page) Page {
func NewPage(base BaseOf, page *domain.Entry) Page {
return Page{
BaseOf: base,
page: page,
@ -122,7 +122,7 @@ func (p Page) StreamDir(qw422016 *qt422016.Writer) {
//line web/template/page.qtpl:37
if p.page.Language != domain.LanguageUnd {
//line web/template/page.qtpl:38
qw422016.E().S(p.page.Language.Dir())
qw422016.E().S(p.page.Language.Dir().String())
//line web/template/page.qtpl:39
} else {
//line web/template/page.qtpl:40