1// Package gen contains the libraries and algorithms used to generate the
2// code for the vocab package.
3package gen
4
5import (
6	"bytes"
7	"fmt"
8	"github.com/writeas/activity/tools/defs"
9	"go/format"
10	"strings"
11)
12
13const (
14	objectName                    = "Object"
15	linkName                      = "Link"
16	ObjectTypeName                = "ObjectType"
17	LinkTypeName                  = "LinkType"
18	typePropertyName              = "type"
19	resolveLinkName               = "resolveLink"
20	resolveObjectName             = "resolveObject"
21	unknownValueDeserializeFnName = "unknownValueDeserialize"
22	unknownValueSerializeFnName   = "unknownValueSerialize"
23)
24
25type File struct {
26	Name    string
27	Content []byte
28}
29
30func GenerateImplementations(types []*defs.Type, properties []*defs.PropertyType, values []*defs.ValueType) (f []*File, err error) {
31	// Validate inputs
32	err = validateDomains(properties)
33	if err != nil {
34		return
35	}
36	err = validateProperties(types)
37	if err != nil {
38		return
39	}
40	err = validateValues(values, properties)
41	if err != nil {
42		return
43	}
44
45	p := generatePackageDefinition()
46	p.Defs = append(p.Defs, generateUnknownType())
47
48	// Add ValueType serialize & deserialize functions
49	for _, v := range values {
50		p.F = append(p.F, v.DeserializeFn, v.SerializeFn)
51	}
52	p.F = append(p.F, defs.IRIFuncs()...)
53	p.F = append(p.F, generateHasTypeFuncs(types)...)
54	p.I = append(p.I, generateTyperInterface())
55
56	// Add functions to resolve string 'name' into concrete types
57	p.F = append(p.F, generateResolveObjectFunction(types))
58	p.F = append(p.F, generateResolveLinkFunction(types))
59	unknown := generateUnknownValueType()
60	p.F = append(p.F, unknown.DeserializeFn, unknown.SerializeFn)
61
62	var b []byte
63	b, err = format.Source([]byte(p.Generate()))
64	if err != nil {
65		return
66	}
67	f = append(f, &File{
68		Name:    "gen_vocab.go",
69		Content: b,
70	})
71
72	// ActivityStream Types
73	m := make(map[*defs.PropertyType]*intermedDef)
74	for _, t := range types {
75		p := &defs.PackageDef{
76			Name: "vocab",
77		}
78		funcs, defs, interfaces, imports := generateDefinitions(t, m)
79		for i, _ := range imports {
80			p.Imports = append(p.Imports, i)
81		}
82		p.F = append(p.F, funcs...)
83		p.Defs = append(p.Defs, defs...)
84		p.I = append(p.I, interfaces...)
85
86		var b []byte
87		b, err = format.Source([]byte(p.Generate()))
88		if err != nil {
89			return
90		}
91		f = append(f, &File{
92			Name:    fmt.Sprintf("gen_%s.go", strings.ToLower(t.Name)),
93			Content: b,
94		})
95	}
96
97	// Intermediate definitions
98	p = &defs.PackageDef{
99		Name:    "vocab",
100		Imports: []string{"fmt", "net/url", "time"},
101	}
102	for _, v := range m {
103		p.F = append(p.F, v.F...)
104		p.Defs = append(p.Defs, v.S)
105	}
106	b, err = format.Source([]byte(p.Generate()))
107	if err != nil {
108		return
109	}
110	f = append(f, &File{
111		Name:    fmt.Sprintf("gen_intermediate.go"),
112		Content: b,
113	})
114	return
115}
116
117func generateTyperInterface() *defs.InterfaceDef {
118	return &defs.InterfaceDef{
119		Typename: "Typer",
120		Comment:  "Typer supports common functions for determining an ActivityStream type",
121		F: []*defs.FunctionDef{
122			{
123				Name:   "TypeLen",
124				Return: []*defs.FunctionVarDef{{Name: "l", Type: "int"}},
125			},
126			{
127				Name:   "GetType",
128				Args:   []*defs.FunctionVarDef{{Name: "index", Type: "int"}},
129				Return: []*defs.FunctionVarDef{{Name: "v", Type: "interface{}"}},
130			},
131		},
132	}
133}
134
135func generateHasTypeFuncs(types []*defs.Type) (f []*defs.FunctionDef) {
136	var activityTypes []string
137	for _, t := range types {
138		t := t
139		if defs.IsActivity(t) {
140			activityTypes = append(activityTypes, t.Name)
141		}
142		f = append(f, &defs.FunctionDef{
143			Name:    fmt.Sprintf("HasType%s", t.Name),
144			Comment: fmt.Sprintf("HasType%s returns true if the Typer has a type of %s.", t.Name, t.Name),
145			Args:    []*defs.FunctionVarDef{{Name: "t", Type: "Typer"}},
146			Return:  []*defs.FunctionVarDef{{Name: "b", Type: "bool"}},
147			Body: func() string {
148				var b bytes.Buffer
149				b.WriteString("for i := 0; i < t.TypeLen(); i++ {\n")
150				b.WriteString("v := t.GetType(i)\n")
151				b.WriteString("if s, ok := v.(string); ok {\n")
152				b.WriteString(fmt.Sprintf("if s == \"%s\" {\n", t.Name))
153				b.WriteString("return true\n")
154				b.WriteString("}\n")
155				b.WriteString("}\n")
156				b.WriteString("}\n")
157				b.WriteString("return false\n")
158				return b.String()
159			},
160		})
161	}
162	f = append(f, &defs.FunctionDef{
163		Name:    "IsActivityType",
164		Comment: "Returns true if the provided Typer is an Activity.",
165		Args:    []*defs.FunctionVarDef{{Name: "t", Type: "Typer"}},
166		Return:  []*defs.FunctionVarDef{{Name: "b", Type: "bool"}},
167		Body: func() string {
168			var b bytes.Buffer
169			b.WriteString(fmt.Sprintf("var activityTypes = []string{\"%s\"}\n", strings.Join(activityTypes, "\", \"")))
170			b.WriteString("hasType := make(map[string]bool, 1)\n")
171			b.WriteString("for i := 0; i < t.TypeLen(); i++ {\n")
172			b.WriteString("v := t.GetType(i)\n")
173			b.WriteString("if s, ok := v.(string); ok {\n")
174			b.WriteString("hasType[s] = true\n")
175			b.WriteString("}\n")
176			b.WriteString("}\n")
177			b.WriteString("for _, t := range activityTypes {\n")
178			b.WriteString("if hasType[t] {\n")
179			b.WriteString("return true\n")
180			b.WriteString("}\n")
181			b.WriteString("}\n")
182			b.WriteString("return false\n")
183			return b.String()
184		},
185	})
186	return
187}
188
189func generatePackageDefinition() *defs.PackageDef {
190	return &defs.PackageDef{
191		Name:    "vocab",
192		Comment: "Package vocab provides an implementation of serializing and deserializing activity streams into native golang structs without relying on reflection. This package is code-generated from the vocabulary specification available at https://www.w3.org/TR/activitystreams-vocabulary and by design forgoes full resolution of raw JSON-LD data. However, custom extensions of the vocabulary are supported by modifying the data definitions in the generation tool and rerunning it. Do not modify this package directly.",
193		Imports: []string{"fmt", "time", "net/url", "regexp", "strconv", "math"},
194		I: []*defs.InterfaceDef{
195			{
196				Typename: "Serializer",
197				Comment:  "Serializer implementations can serialize themselves to a generic map form.",
198				F: []*defs.FunctionDef{{
199					Name:   "Serialize",
200					Return: []*defs.FunctionVarDef{{"m", "map[string]interface{}"}, {"e", "error"}},
201				}},
202			},
203			{
204				Typename: "Deserializer",
205				Comment:  "Deserializer implementations can deserialize themselves from a generic map form.",
206				F: []*defs.FunctionDef{{
207					Name:   "Deserialize",
208					Args:   []*defs.FunctionVarDef{{"m", "map[string]interface{}"}},
209					Return: []*defs.FunctionVarDef{{"e", "error"}},
210				}},
211			},
212		},
213	}
214}
215
216func generateUnknownValueType() *defs.ValueType {
217	return &defs.ValueType{
218		Name:           "Unknown Value",
219		DefinitionType: "interface{}",
220		Zero:           "nil",
221		DeserializeFn: &defs.FunctionDef{
222			Name:    unknownValueDeserializeFnName,
223			Comment: "unknownValueDeserialize transparently stores the object.",
224			Args:    []*defs.FunctionVarDef{{"v", "interface{}"}},
225			Return:  []*defs.FunctionVarDef{{"o", "interface{}"}},
226			Body: func() string {
227				var b bytes.Buffer
228				b.WriteString("o = v\n")
229				b.WriteString("return\n")
230				return b.String()
231			},
232		},
233		SerializeFn: &defs.FunctionDef{
234			Name:    unknownValueSerializeFnName,
235			Comment: "unknownValueSerialize transparently returns the object.",
236			Args:    []*defs.FunctionVarDef{{"v", "interface{}"}},
237			Return:  []*defs.FunctionVarDef{{"o", "interface{}"}},
238			Body: func() string {
239				var b bytes.Buffer
240				b.WriteString("o = v\n")
241				b.WriteString("return\n")
242				return b.String()
243			},
244		},
245	}
246}
247
248func generateUnknownType() *defs.StructDef {
249	u := &defs.StructDef{
250		Typename: "Unknown",
251		Comment:  "Unknown is an entry whose root type is unknown.",
252		M: []*defs.StructMember{{
253			Name:    "u",
254			Type:    "map[string]interface{}",
255			Comment: "Raw unknown, untyped values",
256		}},
257	}
258	u.F = append(u.F, []*defs.MemberFunctionDef{
259		{
260			Name:    "Serialize",
261			Comment: "Serialize turns this object into a map[string]interface{}. Note that for the Unknown type, the \"type\" property is NOT populated with anything special during this process.",
262			P:       u,
263			Return:  []*defs.FunctionVarDef{{"m", "map[string]interface{}"}, {"err", "error"}},
264			Body: func() string {
265				var b bytes.Buffer
266				b.WriteString("m = t.u\n")
267				b.WriteString("return\n")
268				return b.String()
269			},
270		},
271		{
272			Name:    "Deserialize",
273			Comment: "Deserialize populates this object from a map[string]interface{}",
274			P:       u,
275			Args:    []*defs.FunctionVarDef{{"m", "map[string]interface{}"}},
276			Return:  []*defs.FunctionVarDef{{"err", "error"}},
277			Body: func() string {
278				var b bytes.Buffer
279				b.WriteString("t.u = m\n")
280				b.WriteString("return\n")
281				return b.String()
282			},
283		},
284		{
285			Name:    "HasField",
286			Comment: "HasField determines whether the call to GetField is safe with the specified field",
287			P:       u,
288			Args:    []*defs.FunctionVarDef{{"f", "string"}},
289			Return:  []*defs.FunctionVarDef{{"ok", "bool"}},
290			Body: func() string {
291				return "return t.u != nil && t.u[f] != nil\n"
292			},
293		},
294		{
295			Name:    "GetField",
296			Comment: "GetField returns the unknown field value",
297			P:       u,
298			Args:    []*defs.FunctionVarDef{{"f", "string"}},
299			Return:  []*defs.FunctionVarDef{{"v", "interface{}"}},
300			Body: func() string {
301				return "return t.u[f]"
302			},
303		},
304		{
305			Name:    "SetField",
306			Comment: "SetField sets the unknown field value",
307			P:       u,
308			Args:    []*defs.FunctionVarDef{{"f", "string"}, {"i", "interface{}"}},
309			Return:  []*defs.FunctionVarDef{{"this", "*" + u.Typename}},
310			Body: func() string {
311				var b bytes.Buffer
312				b.WriteString("if t.u == nil {\n")
313				b.WriteString("t.u = make(map[string]interface{})\n")
314				b.WriteString("}\n")
315				b.WriteString("t.u[f] = i\n")
316				b.WriteString("return t\n")
317				return b.String()
318			},
319		},
320	}...)
321	return u
322}
323
324func generateResolveObjectFunction(types []*defs.Type) *defs.FunctionDef {
325	return &defs.FunctionDef{
326		Name:    resolveObjectName,
327		Comment: fmt.Sprintf("%s turns a string type that extends Object into a concrete type.", resolveObjectName),
328		Args:    []*defs.FunctionVarDef{{"s", "string"}},
329		Return:  []*defs.FunctionVarDef{{"i", "interface{}"}},
330		Body: func() string {
331			var b bytes.Buffer
332			for _, r := range types {
333				if isAnObjectType(r) {
334					b.WriteString(fmt.Sprintf("if s == \"%s\" {\n", r.Name))
335					b.WriteString(fmt.Sprintf("return &%s{}\n", r.Name))
336					b.WriteString("}\n")
337				}
338			}
339			b.WriteString("return nil\n")
340			return b.String()
341		},
342	}
343}
344
345func generateResolveLinkFunction(types []*defs.Type) *defs.FunctionDef {
346	return &defs.FunctionDef{
347		Name:    resolveLinkName,
348		Comment: fmt.Sprintf("%s turns a string type that extends Link into a concrete type.", resolveLinkName),
349		Args:    []*defs.FunctionVarDef{{"s", "string"}},
350		Return:  []*defs.FunctionVarDef{{"i", "interface{}"}},
351		Body: func() string {
352			var b bytes.Buffer
353			for _, r := range types {
354				if isALinkType(r) {
355					b.WriteString(fmt.Sprintf("if s == \"%s\" {\n", r.Name))
356					b.WriteString(fmt.Sprintf("return &%s{}\n", r.Name))
357					b.WriteString("}\n")
358				}
359			}
360			b.WriteString("return nil\n")
361			return b.String()
362		},
363	}
364}
365
366func isAnObjectType(t *defs.Type) bool {
367	obj, _ := getIsAType(t)
368	return obj
369}
370
371func isALinkType(t *defs.Type) bool {
372	_, link := getIsAType(t)
373	return link
374}
375
376func getIsAType(t *defs.Type) (obj, link bool) {
377	if t.Name == objectName {
378		return true, false
379	} else if t.Name == linkName {
380		return false, true
381	}
382	for _, e := range t.Extends {
383		if obj, link = getIsAType(e); obj || link {
384			return
385		}
386	}
387	panic("Unknown root is-a-type")
388}
389