package gen import ( "bytes" "fmt" "github.com/go-fed/activity/tools/defs" ) func generateDefinitions(t *defs.Type, m map[*defs.PropertyType]*intermedDef) (fd []*defs.FunctionDef, sd []*defs.StructDef, x []*defs.InterfaceDef, imports map[string]bool) { imports = make(map[string]bool) this := &defs.StructDef{ Typename: t.Name, Comment: t.Notes, M: []*defs.StructMember{{"unknown_", "map[string]interface{}", "An unknown value."}}, } sd = append(sd, this) thisInterface := &defs.InterfaceDef{ Typename: InterfaceName(t), Comment: fmt.Sprintf("%s is an interface for accepting types that extend from '%s'.", InterfaceName(t), t.Name), O: []string{"Serializer", "Deserializer"}, } x = append(x, thisInterface) var serializeFragments []string var deserializeFragments []string for _, p := range t.GetProperties() { pf, pd, s, d := generatePropertyDefinitions(p, this, thisInterface, m) serializeFragments = append(serializeFragments, s) deserializeFragments = append(deserializeFragments, d) fd = append(fd, pf...) sd = append(sd, pd...) for _, r := range p.Range { if r.V != nil { for _, i := range r.V.Imports { imports[i] = true } } } } generateWithoutProperties(t, this, thisInterface, m) generateAddUnknownFunction(t, this) generateHasUnknownFunction(t, this) generateRemoveUnknownFunction(t, this) generateGetUnknownFunction(t, this) generateSerializeFunction(t, this, serializeFragments) generateDeserializeFunction(t, this, deserializeFragments) generateMetadataFunctions(t, this, thisInterface) return } func Name(r defs.RangeReference) string { if r.T != nil { return r.T.Name } else if r.V != nil { return r.V.Name } else if r.Any { return "any" } else { panic("RangeReference must have exactly one member set") } } func Type(r defs.RangeReference) string { if r.T != nil { return InterfaceName(r.T) } else if r.V != nil { return r.V.DefinitionType } else if r.Any { return "interface{}" } else { panic("RangeReference must have exactly one member set") } } func Deserialize(r defs.RangeReference, mapName, field string, slice bool) string { if r.T != nil { var parseCode bytes.Buffer parseCode.WriteString(fmt.Sprintf("tmp := &%s{}\n", r.T.Name)) parseCode.WriteString("err = tmp.Deserialize(v)\n") parseCode.WriteString("if err != nil {\nreturn err\n}\n") var b bytes.Buffer b.WriteString("// Begin generation by RangeReference.Deserialize for Type\n") deserializeCode(&b, parseCode.String(), mapName, "map[string]interface{}", field, slice, false) b.WriteString("// End generation by RangeReference.Deserialize for Type\n") return b.String() } else if r.V != nil { var b bytes.Buffer b.WriteString("// Begin generation by RangeReference.Deserialize for Value\n") deserializeCode(&b, fmt.Sprintf("tmp, err := %s(v)\n", r.V.DeserializeFn.Name), mapName, "interface{}", field, slice, false) b.WriteString("// End generation by RangeReference.Deserialize for Value\n") return b.String() } else { panic("Bad call to Deserialize for Any") } } func Serialize(r defs.RangeReference, mapName, field string, slice bool) string { if r.T != nil { var sCode bytes.Buffer sCode.WriteString("tmp, err := v.Serialize()\n") sCode.WriteString("if err != nil {\nreturn m, err\n}\n") var b bytes.Buffer b.WriteString("// Begin generation by RangeReference.Serialize for Type\n") serializeCode(&b, sCode.String(), mapName, field, slice) b.WriteString("// End generation by RangeReference.Serialize for Type\n") return b.String() } else if r.V != nil { deref := "*" if slice || isIRIType(Type(r)) { deref = "" } var b bytes.Buffer b.WriteString("// Begin generation by RangeReference.Serialize for Value\n") serializeCode(&b, fmt.Sprintf("tmp := %s(%sv)\n", r.V.SerializeFn.Name, deref), mapName, field, slice) b.WriteString("// End generation by RangeReference.Serialize for Value\n") return b.String() } else { panic("Bad call to Serialize for Any") } } func generateRemoveUnknownFunction(t *defs.Type, this *defs.StructDef) { m := &defs.MemberFunctionDef{ Name: "RemoveUnknown", Comment: "RemoveUnknown removes a raw extension from this object with the specified key", P: this, Args: []*defs.FunctionVarDef{{"k", "string"}}, Return: []*defs.FunctionVarDef{{"this", "*" + t.Name}}, Body: func() string { var b bytes.Buffer b.WriteString("delete(t.unknown_, k)\n") b.WriteString("return t\n") return b.String() }, } this.F = append(this.F, m) } func generateMetadataFunctions(t *defs.Type, this *defs.StructDef, it *defs.InterfaceDef) { if t.GetTypeMetadata().HasIsPublicMethod { m := &defs.MemberFunctionDef{ Name: "IsPublic", Comment: "IsPublic returns true if the 'to', 'bto', 'cc', or 'bcc' properties address the special Public ActivityPub collection", P: this, Return: []*defs.FunctionVarDef{{"b", "bool"}}, Body: func() string { var b bytes.Buffer b.WriteString("for i := 0; i < t.ToLen(); i++ {\n") b.WriteString(fmt.Sprintf("if t.IsToIRI(i) && t.GetToIRI(i).String() == \"%s\" {\n", defs.PublicActivityPub)) b.WriteString("return true\n") b.WriteString("}\n") b.WriteString("}\n") b.WriteString("for i := 0; i < t.BtoLen(); i++ {\n") b.WriteString(fmt.Sprintf("if t.IsBtoIRI(i) && t.GetBtoIRI(i).String() == \"%s\" {\n", defs.PublicActivityPub)) b.WriteString("return true\n") b.WriteString("}\n") b.WriteString("}\n") b.WriteString("for i := 0; i < t.CcLen(); i++ {\n") b.WriteString(fmt.Sprintf("if t.IsCcIRI(i) && t.GetCcIRI(i).String() == \"%s\" {\n", defs.PublicActivityPub)) b.WriteString("return true\n") b.WriteString("}\n") b.WriteString("}\n") b.WriteString("for i := 0; i < t.BccLen(); i++ {\n") b.WriteString(fmt.Sprintf("if t.IsBccIRI(i) && t.GetBccIRI(i).String() == \"%s\" {\n", defs.PublicActivityPub)) b.WriteString("return true\n") b.WriteString("}\n") b.WriteString("}\n") b.WriteString("return false\n") return b.String() }, } this.F = append(this.F, m) it.F = append(it.F, &defs.FunctionDef{ Name: "IsPublic", Comment: "IsPublic returns true if the 'to', 'bto', 'cc', or 'bcc' properties address the special Public ActivityPub collection", Return: []*defs.FunctionVarDef{{"b", "bool"}}, }) } } func generateHasUnknownFunction(t *defs.Type, this *defs.StructDef) { m := &defs.MemberFunctionDef{ Name: "HasUnknown", Comment: "HasUnknown returns true if there is an unknown property with the specified key", P: this, Args: []*defs.FunctionVarDef{{"k", "string"}}, Return: []*defs.FunctionVarDef{{"b", "bool"}}, Body: func() string { var b bytes.Buffer b.WriteString("if t.unknown_ == nil {\n") b.WriteString("return false") b.WriteString("}\n") b.WriteString("_, ok := t.unknown_[k]\n") b.WriteString("return ok\n") return b.String() }, } this.F = append(this.F, m) } func generateAddUnknownFunction(t *defs.Type, this *defs.StructDef) { m := &defs.MemberFunctionDef{ Name: "AddUnknown", Comment: "AddUnknown adds an unknown property to this object with the specified key", P: this, Args: []*defs.FunctionVarDef{{"k", "string"}, {"i", "interface{}"}}, Return: []*defs.FunctionVarDef{{"this", "*" + t.Name}}, Body: func() string { var b bytes.Buffer b.WriteString("if t.unknown_ == nil {\n") b.WriteString("t.unknown_ = make(map[string]interface{})\n") b.WriteString("}\n") b.WriteString("t.unknown_[k] = i\n") b.WriteString("return t\n") return b.String() }, } this.F = append(this.F, m) } func generateGetUnknownFunction(t *defs.Type, this *defs.StructDef) { m := &defs.MemberFunctionDef{ Name: "GetUnknown", Comment: "GetUnknown fetches an unknown property from this object with the specified key. Note that this will panic if HasUnknown would return false.", P: this, Args: []*defs.FunctionVarDef{{"k", "string"}}, Return: []*defs.FunctionVarDef{{"i", "interface{}"}}, Body: func() string { var b bytes.Buffer b.WriteString("return t.unknown_[k]\n") return b.String() }, } this.F = append(this.F, m) } func generateDeserializeFunction(t *defs.Type, this *defs.StructDef, fragments []string) { d := &defs.MemberFunctionDef{ Name: "Deserialize", Comment: "Deserialize populates this object from a map[string]interface{}", P: this, Args: []*defs.FunctionVarDef{{"m", "map[string]interface{}"}}, Return: []*defs.FunctionVarDef{{"err", "error"}}, Body: func() string { var b bytes.Buffer b.WriteString("for k, v := range m {\n") b.WriteString("handled := false\n") for _, s := range fragments { b.WriteString("if !handled {\n") b.WriteString(s) b.WriteString("}\n") } b.WriteString("if !handled && k != \"@context\"{\n") b.WriteString("if t.unknown_ == nil {\n") b.WriteString("t.unknown_ = make(map[string]interface{})\n") b.WriteString("}\n") b.WriteString(fmt.Sprintf("t.unknown_[k] = %s(v)\n", unknownValueDeserializeFnName)) b.WriteString("}\n") b.WriteString("}\n") b.WriteString("return\n") return b.String() }, } this.F = append(this.F, d) } func generateSerializeFunction(t *defs.Type, this *defs.StructDef, fragments []string) { d := &defs.MemberFunctionDef{ Name: "Serialize", Comment: fmt.Sprintf("Serialize turns this object into a map[string]interface{}. Note that the \"type\" property will automatically be populated with \"%s\" if not manually set by the caller", t.Name), P: this, Return: []*defs.FunctionVarDef{{"m", "map[string]interface{}"}, {"err", "error"}}, Body: func() string { var b bytes.Buffer b.WriteString("m = make(map[string]interface{})\n") b.WriteString("for k, v := range t.unknown_ {\n") b.WriteString(fmt.Sprintf("m[k] = %s(v)\n", unknownValueSerializeFnName)) b.WriteString("}\n") b.WriteString("var typeAlreadySet bool\n") b.WriteString(fmt.Sprintf("for _, k := range t.%s {\n", cleanName(typePropertyName))) b.WriteString("if ks, ok := k.(string); ok {\n") b.WriteString(fmt.Sprintf("if ks == \"%s\" {\n", t.Name)) b.WriteString("typeAlreadySet = true\n") b.WriteString("break\n") b.WriteString("}\n") b.WriteString("}\n") b.WriteString("}\n") b.WriteString("if !typeAlreadySet {\n") b.WriteString(fmt.Sprintf("t.%s = append(t.%s, \"%s\")\n", cleanName(typePropertyName), cleanName(typePropertyName), t.Name)) b.WriteString("}\n") for _, s := range fragments { b.WriteString(s) } b.WriteString("return\n") return b.String() }, } this.F = append(this.F, d) } func generateWithoutProperties(d *defs.Type, this *defs.StructDef, it *defs.InterfaceDef, m map[*defs.PropertyType]*intermedDef) { hasNamed := make(map[string]bool, 0) for _, p := range d.GetProperties() { hasNamed[p.Name] = true } for _, t := range d.GetWithoutProperties() { if hasNamed[t.Name] { // No need to stub -- already has a replacement. continue } clonedThis := &defs.StructDef{ Typename: this.Typename, Comment: this.Comment, } _, _, _, _ = generatePropertyDefinitions(t, clonedThis, it, m) for _, f := range clonedThis.F { if len(f.Return) > 1 { panic("generateWithoutProperties can only handle replacing functions with zero or one return types") } f.P = this f.Comment = fmt.Sprintf("%s is NOT a valid property for this type; calling its associated methods will always yield an empty-equivalent value such as false, nil, or empty string. This includes instances where it should return itself. This ugliness is a symptom of the fundamental design of the ActivityStream vocabulary as instead of 'W-is-a-X' relationships it contains the notion of 'W-is-a-X-except-for-Y'.", f.Name) if len(f.Return) > 0 { returnType := f.Return[0].Type f.Body = func() string { var b bytes.Buffer if returnType == "url.URL" { b.WriteString("return url.URL{}\n") } else if isSlice(returnType) || isPtrType(returnType) { b.WriteString("return nil\n") } else if returnType == "bool" { b.WriteString("return false\n") } else if returnType == "int" { b.WriteString("return 0\n") } else if returnType == "interface{}" { b.WriteString("return nil\n") } else { found := false for _, v := range t.Range { if v.V == nil { continue } if returnType == deref(Type(v)) { found = true b.WriteString(fmt.Sprintf("return %s\n", v.V.Zero)) } } if !found { b.WriteString("return nil\n") } } return b.String() } this.F = append(this.F, f) } else { f.Body = func() string { return "\n" } this.F = append(this.F, f) } } } } func InterfaceName(t *defs.Type) string { return fmt.Sprintf("%sType", t.Name) }