200 行
7.9 KiB
Markdown
200 行
7.9 KiB
Markdown
|
# streams
|
||
|
|
||
|
The `streams` package provides static types for Core and Extended types to the
|
||
|
[ActivityStream Vocabulary](https://www.w3.org/TR/activitystreams-vocabulary).
|
||
|
The library is battle-tested against the `vocabulary-ex*-jsonld.json`
|
||
|
[test documents](https://github.com/w3c-social/activitystreams-test-documents)
|
||
|
in addition to usual unit tests.
|
||
|
|
||
|
Its mission is simple: Provide meaningful static types for the ActivityStream
|
||
|
Vocabulary in golang. This library is a convenience layer on top of the
|
||
|
`activity/vocab` library, which gives unfettered access to the data types.
|
||
|
|
||
|
This library is entirely code-generated by the `activity/tools/streams/gen`
|
||
|
library and `activity/tools/streams` tool. Run `go generate` to refresh the
|
||
|
library, which requires `$GOPATH/bin` to be on your `$PATH`.
|
||
|
|
||
|
**Consider using this library and falling back to `activity/vocab` only when
|
||
|
necessary.**
|
||
|
|
||
|
## This library's API is huge!
|
||
|
|
||
|
**The W3C does not require client applications to support all of these
|
||
|
use cases.** The W3C only requires that *"all implementations must at least be
|
||
|
capable of serializing and deserializing the Extended properties in accordance
|
||
|
with the Activity Streams 2.0 Core Syntax,"* which what this library and the
|
||
|
`activity/vocab` libraries do for clients. This library's API is large to
|
||
|
permit clients to use as much or as little as desired.
|
||
|
|
||
|
## What it does
|
||
|
|
||
|
This library provides a `Resolver`, which is simply a collection of callbacks
|
||
|
that clients can specify to handle specific ActivtyStream data types. The
|
||
|
`Resolver.Deserialize` method turns a JSON-decoded `map[string]interface{}`
|
||
|
into its proper type, passed to the corresponding callback.
|
||
|
|
||
|
For example, given the data:
|
||
|
|
||
|
```
|
||
|
{
|
||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||
|
"type": "Note",
|
||
|
"name": "Equivalent Exchange",
|
||
|
"content": "I'll give half of my life to you and you give half of yours to me!",
|
||
|
"attachment": "https://example.com/attachment"
|
||
|
}
|
||
|
```
|
||
|
|
||
|
in `b []byte` one can do the following:
|
||
|
|
||
|
```
|
||
|
var m map[string]interface{}
|
||
|
if err := json.Unmarshal(b, &m); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
r := &Resolver {
|
||
|
NoteCallback: func(n *Note) error {
|
||
|
// 1) Use the Note concrete type here
|
||
|
// 2) Errors are propagated transparently
|
||
|
},
|
||
|
}
|
||
|
if handled, err := r.Deserialize(m); err != nil {
|
||
|
// 3) Any errors from #2 can be handled, or the payload is an unknown type.
|
||
|
return err
|
||
|
} else if !handled {
|
||
|
// 4) The callback to handle the concrete type was not set.
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Only set the callbacks that are interesting. There is no need to set every
|
||
|
callback, unless your application requires it.
|
||
|
|
||
|
## Using concrete types
|
||
|
|
||
|
The convenience layer provides easy access to properties with specific types.
|
||
|
However, because ActivityStreams is fundamentally built off of JSON-LD and
|
||
|
still permits large degree of freedom when it comes to obtaining a concrete type
|
||
|
for a property, the convenience API is built to give clients the freedom to
|
||
|
choose how best to federate.
|
||
|
|
||
|
For every type in this package (except `Resolver`), there is an equivalent type
|
||
|
in the `activity/vocab` package. It takes only a call to `Raw` to go from this
|
||
|
convenience API to the full API:
|
||
|
|
||
|
```
|
||
|
r := &Resolver {
|
||
|
NoteCallback: func(n *Note) error {
|
||
|
// Raw is available for all ActivityStream types
|
||
|
vocabNote := n.Raw()
|
||
|
},
|
||
|
}
|
||
|
```
|
||
|
|
||
|
To determine whether the call to `Raw` is needed, the "get" and "has" methods
|
||
|
use `Resolution` and `Presence` types to inform client code. The client is free
|
||
|
to support as many types as is feasible within the specific application.
|
||
|
|
||
|
Reusing the `Note` example above that has an `attachment`, the following is
|
||
|
client code that tries to handle every possible type that `attachment` can
|
||
|
take. **The W3C does not require client applications to support all of these
|
||
|
use cases.**
|
||
|
|
||
|
```
|
||
|
r := &Resolver {}
|
||
|
r.NoteCallback = func(n *Note) error {
|
||
|
if n.LenAttachment() == 1 {
|
||
|
if presence := n.HasAttachment(0); p == ConvenientPresence {
|
||
|
// A new or existing Resolver can be used. This is the convenient getter.
|
||
|
if resolution, err := n.ResolveAttachment(r, 0); err != nil {
|
||
|
return err
|
||
|
} else if resolution == RawResolutionNeeded {
|
||
|
vocabNote := n.Raw()
|
||
|
// Use the full API
|
||
|
if vocabNote.IsAttachmentIRI(0) {
|
||
|
...
|
||
|
} else ...
|
||
|
}
|
||
|
} else if p == RawPresence {
|
||
|
vocabNote := n.Raw()
|
||
|
// Use the full API
|
||
|
if vocabNote.IsAttachmentIRI(0) {
|
||
|
...
|
||
|
} else ...
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Serializing data
|
||
|
|
||
|
Creating a raw type and serializing it is straightforward:
|
||
|
|
||
|
```
|
||
|
n := &Note{}
|
||
|
n.AddName("I'll see you again")
|
||
|
n.AddContent("You don't have to be alone when I leave")
|
||
|
// The "type" property is automatically handled...
|
||
|
m, err := n.Serialize()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// ...but "@context" is not.
|
||
|
m["@context"] = "https://www.w3.org/ns/activitystreams"
|
||
|
b, err := json.Marshal(m)
|
||
|
```
|
||
|
|
||
|
The only caveat is that clients must set `"@context"` manually at this time.
|
||
|
|
||
|
## What it doesn't do
|
||
|
|
||
|
This library does not use the `reflect` package at all. It prioritizes
|
||
|
minimizing dependencies and speed over binary size.
|
||
|
|
||
|
The ActivityStream specification is built on top of JSON-LD, which uses JSON.
|
||
|
This library should be used with `encoding/json` in order to transform a raw
|
||
|
string into a `map[string]interface{}` to static, semantically meaningful
|
||
|
types.
|
||
|
|
||
|
This library does not set the `"@context"` property required when sending
|
||
|
serialized data. Clients are in charge of setting it to
|
||
|
`"https://www.w3.org/ns/activitystreams"`.
|
||
|
|
||
|
This implementation is heavily opinionated against understanding JSON-LD due to
|
||
|
its sacrifice of semantic meaning, significant increase of complexity, even
|
||
|
weaker typing, and increased exposure to partially-understood messages. These
|
||
|
costs earn a degree of flexibility that is not needed for the ActivityStream
|
||
|
Vocabulary.
|
||
|
|
||
|
This library is *not* a [JSON-LD](https://json-ld.org/) parser, and by design
|
||
|
does not implement any further understanding of JSON-LD that may be outlined in
|
||
|
the [W3C's JSON-LD](https://www.w3.org/TR/json-ld/) specification. Furthermore,
|
||
|
it does *not* implement any of the JSON-LD
|
||
|
[processing algorithms](https://www.w3.org/TR/json-ld-api/). If this
|
||
|
functionality is strictly needed, or this library is not suitable, please see
|
||
|
[piprate/json-gold/ld](https://github.com/piprate/json-gold) and its
|
||
|
[documentation](https://godoc.org/github.com/piprate/json-gold/ld).
|
||
|
|
||
|
## Other considerations
|
||
|
|
||
|
This library is entirely code-generated. Determined clients can add their own
|
||
|
custom extended types to the `activity/tools/defs` library and generate a
|
||
|
useful type. However, this process is purposefully painful to force clients to
|
||
|
seriously consider whether they need their own
|
||
|
[custom type](https://xkcd.com/927).
|
||
|
|
||
|
The code-generation aspect also allows the specification to be translated into
|
||
|
declarative data, which permits certain kinds of validation and verification.
|
||
|
This has led to giving the following feedback to the specification:
|
||
|
|
||
|
* [Inconsistencies between property domains and type properties](https://github.com/w3c/activitystreams/issues/436)
|
||
|
* [Please clarify "items" and "orderedItems" properties](https://github.com/w3c/activitystreams/issues/437)
|
||
|
* [Expectations around IRI resolution](https://github.com/w3c/activitystreams/issues/438)
|
||
|
* [Examples Contradict Specification](https://github.com/w3c/activitystreams/issues/439)
|
||
|
* [Tombstone "formerType" Property Range Needs Clarification](https://github.com/w3c/activitystreams/issues/440)
|
||
|
* [Example 60 Missing `@context` Property](https://github.com/w3c/activitystreams/issues/441)
|
||
|
* [Stylistic Consistency For Non-Functional Single-Value JSON in Examples](https://github.com/w3c/activitystreams/issues/442)
|
||
|
* [Spec does not clarify non-functional natural language values when mapped](https://github.com/w3c/activitystreams/issues/443)
|
||
|
* [Example 102: `url` property missing `type: "Link"`](https://github.com/w3c/activitystreams/issues/444)
|
||
|
* [Example 146: Missing `Z` in startTime](https://github.com/w3c/activitystreams/issues/445)
|
||
|
* [Example 150: Latitude/Longitude are not xsd:float](https://github.com/w3c/activitystreams/issues/446)
|