go-fed-activity/streams/README.md
2020-07-09 17:34:37 +02:00

153 lines
5 KiB
Markdown

# streams
ActivityStreams vocabularies automatically code-generated with `astool`.
## Reference & Tutorial
The [go-fed website](https://go-fed.org/) contains tutorials and reference
materials, in addition to the rest of this README.
## How To Use
```
go get github.com/go-fed/activity
```
All generated types and properties are interfaces in
`github.com/go-fed/streams/vocab`, but note that the constructors and supporting
functions live in `github.com/go-fed/streams`.
To create a type and set properties:
```golang
var actorURL *url.URL = // ...
// A new "Create" Activity.
create := streams.NewActivityStreamsCreate()
// A new "actor" property.
actor := streams.NewActivityStreamsActorProperty()
actor.AppendIRI(actorURL)
// Set the "actor" property on the "Create" Activity.
create.SetActivityStreamsActor(actor)
```
To process properties on a type:
```golang
// Returns true if the "Update" has at least one "object" with an IRI value.
func hasObjectWithIRIValue(update vocab.ActivityStreamsUpdate) bool {
objectProperty := update.GetActivityStreamsObject()
// Any property may be nil if it was either empty in the original JSON or
// never set on the golang type.
if objectProperty == nil {
return false
}
// The "object" property is non-functional: it could have multiple values. The
// generated code has slightly different methods for a functional property
// versus a non-functional one.
//
// While it may be easy to ignore multiple values in other languages
// (accidentally or purposefully), go-fed is designed to make it hard to do
// so.
for iter := objectProperty.Begin(); iter != objectProperty.End(); iter = iter.Next() {
// If this particular value is an IRI, return true.
if iter.IsIRI() {
return true
}
}
// All values are literal embedded values and not IRIs.
return false
}
```
The ActivityStreams type hierarchy of "extends" and "disjoint" is not the same
as the Object Oriented definition of inheritance. It is also not the same as
golang's interface duck-typing. Helper functions are provided to guarantee that
an application's logic can correctly apply the type hierarchy.
```golang
thing := // Pick a type from streams.NewActivityStreams<Type>()
if streams.ActivityStreamsObjectIsDisjointWith(thing) {
fmt.Printf("The \"Object\" type is Disjoint with the %T type.\n", thing)
}
if streams.ActivityStreamsLinkIsExtendedBy(thing) {
fmt.Printf("The %T type Extends from the \"Link\" type.\n", thing)
}
if streams.ActivityStreamsActivityExtends(thing) {
fmt.Printf("The \"Activity\" type extends from the %T type.\n", thing)
}
```
When given a generic JSON payload, it can be resolved to a concrete type by
creating a `streams.JSONResolver` and giving it a callback function that accepts
the interesting concrete type:
```golang
// Callbacks must be in the form:
// func(context.Context, <TypeInterface>) error
createCallback := func(c context.Context, create vocab.ActivityStreamsCreate) error {
// Do something with 'create'
fmt.Printf("createCallback called: %T\n", create)
return nil
}
updateCallback := func(c context.Context, update vocab.ActivityStreamsUpdate) error {
// Do something with 'update'
fmt.Printf("updateCallback called: %T\n", update)
return nil
}
jsonResolver, err := streams.NewJSONResolver(createCallback, updateCallback)
if err != nil {
// Something in the setup was wrong. For example, a callback has an
// unsupported signature and would never be called
panic(err)
}
// Create a context, which allows you to pass data opaquely through the
// JSONResolver.
c := context.Background()
// Example 15 of the ActivityStreams specification.
b := []byte(`{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally created a note",
"type": "Create",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": {
"type": "Note",
"name": "A Simple Note",
"content": "This is a simple note"
}
}`)
var jsonMap map[string]interface{}
if err = json.Unmarshal(b, &jsonMap); err != nil {
panic(err)
}
// The createCallback function will be called.
err = jsonResolver.Resolve(c, jsonMap)
if err != nil && !streams.IsUnmatchedErr(err) {
// Something went wrong
panic(err)
} else if streams.IsUnmatchedErr(err) {
// Everything went right but the callback didn't match or the ActivityStreams
// type is one that wasn't code generated.
fmt.Println("No match: ", err)
}
```
A `streams.TypeResolver` is similar but uses the golang types instead. It
accepts the generic `vocab.Type`. This is the abstraction when needing to handle
any ActivityStreams type. The function `ToType` can convert a JSON-decoded-map
into this kind of value if needed.
A `streams.PredicatedTypeResolver` lets you apply a boolean predicate function
that acts as a check whether a callback is allowed to be invoked.
## FAQ
### Why Are Empty Properties Nil And Not Zero-Valued?
Due to implementation design decisions, it would require a lot of plumbing to
ensure this would work properly. It would also require allocation of a
non-trivial amount of memory.