activity/streams
Cory Slep 56e7e842d3 Add AnyObject/Link/Activity callbacks to Deserialize 2018-04-01 16:52:54 +02:00
..
README.md Migrate streams library to go-fed. 2018-01-24 00:31:50 +01:00
streams.go Add AnyObject/Link/Activity callbacks to Deserialize 2018-04-01 16:52:54 +02:00
streams_data_test.go Make substantial progress on ActivityPub client & server implmentation 2018-02-04 17:42:22 +01:00
streams_test.go Make substantial progress on ActivityPub client & server implmentation 2018-02-04 17:42:22 +01:00

README.md

streams

The streams package provides static types for Core and Extended types to the ActivityStream Vocabulary. The library is battle-tested against the vocabulary-ex*-jsonld.json 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 parser, and by design does not implement any further understanding of JSON-LD that may be outlined in the W3C's JSON-LD specification. Furthermore, it does not implement any of the JSON-LD processing algorithms. If this functionality is strictly needed, or this library is not suitable, please see piprate/json-gold/ld and its documentation.

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.

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: