1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
# 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.
|