diff --git a/astool/convert/convert.go b/astool/convert/convert.go index 04c0c18..4687b17 100644 --- a/astool/convert/convert.go +++ b/astool/convert/convert.go @@ -364,7 +364,7 @@ func (c Converter) convertToFiles(v vocabulary) (f []*File, e error) { Directory: pub.WriteDir(), }) // Resolvers - files, e = c.resolverFiles(c.GenRoot.Sub(resolverPkg).PublicPackage(), v) + files, e = c.resolverFiles(c.GenRoot.PublicPackage(), v.Manager, v) if e != nil { return } @@ -1087,9 +1087,9 @@ func (c Converter) propertyPackageFiles(pg *gen.PropertyGenerator, vocabName str } // resolverFiles creates the files necessary for the resolvers. -func (c Converter) resolverFiles(pkg gen.Package, root vocabulary) (files []*File, e error) { - rg := gen.NewResolverGenerator(root.allTypeArray(), pkg) - typeRes, intRes, typePredRes, intPredRes, errDefs, isUnFn, iFaces := rg.Definition() +func (c Converter) resolverFiles(pkg gen.Package, manGen *gen.ManagerGenerator, root vocabulary) (files []*File, e error) { + rg := gen.NewResolverGenerator(root.allTypeArray(), manGen, pkg) + jsonRes, typeRes, intRes, typePredRes, intPredRes, errDefs, isUnFn, iFaces := rg.Definition() // Utils file := jen.NewFilePath(pkg.Path()) for _, errDef := range errDefs { @@ -1104,6 +1104,14 @@ func (c Converter) resolverFiles(pkg gen.Package, root vocabulary) (files []*Fil FileName: "gen_resolver_utils.go", Directory: pkg.WriteDir(), }) + // JSON resolver + file = jen.NewFilePath(pkg.Path()) + file.Add(jsonRes.Definition()) + files = append(files, &File{ + F: file, + FileName: "gen_json_resolver.go", + Directory: pkg.WriteDir(), + }) // Type, not predicated file = jen.NewFilePath(pkg.Path()) file.Add(typeRes.Definition()) diff --git a/astool/gen/funcprop.go b/astool/gen/funcprop.go index 703902e..ba62ec8 100644 --- a/astool/gen/funcprop.go +++ b/astool/gen/funcprop.go @@ -338,7 +338,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, *co jen.List( jen.Id("v"), jen.Err(), - ).Op(":=").Add(kind.deserializeFnCode(variable, jen.Id("context"))), + ).Op(":=").Add(kind.deserializeFnCode(variable, jen.Id("aliasMap"))), jen.Err().Op("!=").Nil(), ).Block( jen.Id(codegen.This()).Op(":=").Op("&").Id(p.StructName()).Values( @@ -362,7 +362,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, *co deserialize = codegen.NewCommentedFunction( p.GetPrivatePackage().Path(), p.DeserializeFnName(), - []jen.Code{jen.Id("i").Interface(), jen.Id("context").Map(jen.String()).String()}, + []jen.Code{jen.Id("i").Interface(), jen.Id("aliasMap").Map(jen.String()).String()}, []jen.Code{jen.Op("*").Id(p.StructName()), jen.Error()}, []jen.Code{ jen.Id("alias").Op(":=").Lit(""), @@ -370,7 +370,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, *co jen.List( jen.Id("a"), jen.Id("ok"), - ).Op(":=").Id("context").Index(jen.Lit(p.vocabURI.String())), + ).Op(":=").Id("aliasMap").Index(jen.Lit(p.vocabURI.String())), jen.Id("ok"), ).Block( jen.Id("alias").Op("=").Id("a"), @@ -388,7 +388,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, *co deserialize = codegen.NewCommentedFunction( p.GetPrivatePackage().Path(), p.DeserializeFnName(), - []jen.Code{jen.Id("m").Map(jen.String()).Interface(), jen.Id("context").Map(jen.String()).String()}, + []jen.Code{jen.Id("m").Map(jen.String()).Interface(), jen.Id("aliasMap").Map(jen.String()).String()}, []jen.Code{jen.Op("*").Id(p.StructName()), jen.Error()}, []jen.Code{ jen.Id("alias").Op(":=").Lit(""), @@ -396,7 +396,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, *co jen.List( jen.Id("a"), jen.Id("ok"), - ).Op(":=").Id("context").Index(jen.Lit(p.vocabURI.String())), + ).Op(":=").Id("aliasMap").Index(jen.Lit(p.vocabURI.String())), jen.Id("ok"), ).Block( jen.Id("alias").Op("=").Id("a"), diff --git a/astool/gen/manager.go b/astool/gen/manager.go index cd311c0..140cccf 100644 --- a/astool/gen/manager.go +++ b/astool/gen/manager.go @@ -212,7 +212,7 @@ func (m *ManagerGenerator) createDeserializationMethod(deserName string, pubPkg, jen.Return( jen.Func().Params( jen.Id("m").Map(jen.String()).Interface(), - jen.Id("context").Map(jen.String()).String(), + jen.Id("aliasMap").Map(jen.String()).String(), ).Params( jen.Qual(pubPkg.Path(), interfaceName), jen.Error(), @@ -221,7 +221,7 @@ func (m *ManagerGenerator) createDeserializationMethod(deserName string, pubPkg, // Note: this "i" must be the same as the "i" in the deserialization definition. jen.Id("i"), jen.Err(), - ).Op(":=").Qual(privPkg.Path(), deserName).Call(jen.Id("m"), jen.Id("context")), + ).Op(":=").Qual(privPkg.Path(), deserName).Call(jen.Id("m"), jen.Id("aliasMap")), jen.Return(jen.List( jen.Id("i"), jen.Err(), diff --git a/astool/gen/nonfuncprop.go b/astool/gen/nonfuncprop.go index 2c4eb1f..5111999 100644 --- a/astool/gen/nonfuncprop.go +++ b/astool/gen/nonfuncprop.go @@ -592,7 +592,7 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, jen.Err(), ).Op(":=").Id(p.elementTypeGenerator().DeserializeFnName()).Call( jen.Id(variable), - jen.Id("context"), + jen.Id("aliasMap"), ), jen.Err().Op("!=").Nil(), ).Block( @@ -612,7 +612,7 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, deserialize := codegen.NewCommentedFunction( p.GetPrivatePackage().Path(), p.DeserializeFnName(), - []jen.Code{jen.Id("m").Map(jen.String()).Interface(), jen.Id("context").Map(jen.String()).String()}, + []jen.Code{jen.Id("m").Map(jen.String()).Interface(), jen.Id("aliasMap").Map(jen.String()).String()}, []jen.Code{jen.Qual(p.GetPublicPackage().Path(), p.InterfaceName()), jen.Error()}, []jen.Code{ jen.Id("alias").Op(":=").Lit(""), @@ -620,7 +620,7 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, jen.List( jen.Id("a"), jen.Id("ok"), - ).Op(":=").Id("context").Index(jen.Lit(p.vocabURI.String())), + ).Op(":=").Id("aliasMap").Index(jen.Lit(p.vocabURI.String())), jen.Id("ok"), ).Block( jen.Id("alias").Op("=").Id("a"), diff --git a/astool/gen/resolver.go b/astool/gen/resolver.go index ecaed1b..e60a895 100644 --- a/astool/gen/resolver.go +++ b/astool/gen/resolver.go @@ -8,6 +8,9 @@ import ( ) const ( + contextJSONLDName = "@context" + typePropertyName = "type" + jsonResolverStructName = "JSONResolver" typeResolverStructName = "TypeResolver" interfaceResolverStructName = "InterfaceResolver" typePredicatedResolverStructName = "TypePredicatedResolver" @@ -25,6 +28,7 @@ const ( errorCannotTypeAssert = "errCannotTypeAssertType" errorCannotTypeAssertPredicate = "errCannotTypeAssertPredicate" isUnFnName = "IsUnmatchedErr" + toAliasMapFnName = "toAliasMap" ) // ResolverGenerator generates the code required for the TypeResolver and the @@ -32,7 +36,9 @@ const ( type ResolverGenerator struct { pkg Package types []*TypeGenerator + manGen *ManagerGenerator cacheOnce sync.Once + cachedJSON *codegen.Struct cachedTypePredicate *codegen.Struct cachedInterfacePredicate *codegen.Struct cachedType *codegen.Struct @@ -53,16 +59,41 @@ type ResolverGenerator struct { // Must be constructed after all TypeGenerators. func NewResolverGenerator( tgs []*TypeGenerator, + m *ManagerGenerator, pkg Package) *ResolverGenerator { return &ResolverGenerator{ - pkg: pkg, - types: tgs, + pkg: pkg, + types: tgs, + manGen: m, } } // Definition returns the TypeResolver and PredicateTypeResolver. -func (r *ResolverGenerator) Definition() (typeRes, interfaceRes, typePredRes, interfacePredRes *codegen.Struct, errs []jen.Code, isUnFn *codegen.Function, iFaces []*codegen.Interface) { +// +// This function signature is pure garbage and yet I keep heaping it on. +func (r *ResolverGenerator) Definition() (jsonRes, typeRes, interfaceRes, typePredRes, interfacePredRes *codegen.Struct, errs []jen.Code, isUnFn *codegen.Function, iFaces []*codegen.Interface) { r.cacheOnce.Do(func() { + r.cachedJSON = codegen.NewStruct( + fmt.Sprintf("%s resolves a JSON-deserialized map into "+ + "its concrete ActivityStreams type", jsonResolverStructName), + jsonResolverStructName, + r.jsonResolverMethods(), + append(r.resolverFunctions(jsonResolverStructName, + "creates a new Resolver that takes a "+ + "JSON-deserialized generic map and determines "+ + "the correct concrete Go type. The callback "+ + "function is guaranteed to receive a value "+ + "whose underlying ActivityStreams type "+ + "matches the concrete interface name in its "+ + "signature. The callback functions must be of "+ + "the form:\n\n"+ + " func(context.Context, ) error\n\n"+ + "where TypeInterface is the code-generated "+ + "interface for an ActivityStream type. An "+ + "error is returned if a callback function "+ + "does not match this signature."), + r.toAliasFunction()), + r.resolverMembers()) r.cachedType = codegen.NewStruct( fmt.Sprintf("%s resolves ActivityStreams values based "+ "on their type name.", typeResolverStructName), @@ -165,7 +196,7 @@ func (r *ResolverGenerator) Definition() (typeRes, interfaceRes, typePredRes, in r.cachedASInterface = r.asInterface() r.cachedResolverInterface = r.resolverInterface() }) - return r.cachedType, r.cachedInterface, r.cachedTypePredicate, r.cachedInterfacePredicate, []jen.Code{ + return r.cachedJSON, r.cachedType, r.cachedInterface, r.cachedTypePredicate, r.cachedInterfacePredicate, []jen.Code{ r.cachedErrNoMatch, r.cachedErrUnhandled, r.cachedErrPredicateUnmatched, @@ -251,6 +282,106 @@ func (r *ResolverGenerator) isUnFn() *codegen.Function { fmt.Sprintf("%s is true when the error indicates that a Resolver was unsuccessful due to the ActivityStreams value not matching its callbacks or predicates.", isUnFnName)) } +// jsonResolverMethods returns the methods for the TypeResolver. +func (r *ResolverGenerator) jsonResolverMethods() (m []*codegen.Method) { + impl := jen.Empty() + for _, t := range r.types { + impl = impl.Case( + jen.Lit(t.TypeName()), + ).Block( + jen.List( + jen.Id("v"), + jen.Err(), + ).Op(":=").Add(r.manGen.getDeserializationMethodForType(t).On(managerInitVarName).Call().Call( + jen.Id("m"), + jen.Id("aliasMap"), + )), + jen.If( + jen.Err().Op("!=").Nil(), + ).Block( + jen.Return(jen.Err()), + ), + jen.For( + jen.List( + jen.Id("_"), + jen.Id("i"), + ).Op(":=").Range().Id(codegen.This()).Dot(callbackMember), + ).Block( + jen.If( + jen.List( + jen.Id("fn"), + jen.Id("ok"), + ).Op(":=").Id("i").Assert( + jen.Func().Parens( + jen.List( + jen.Qual("context", "Context"), + jen.Qual(t.PublicPackage().Path(), t.InterfaceName()), + ), + ).Error(), + ), + jen.Id("ok"), + ).Block( + jen.Return( + jen.Id("fn").Call(jen.Id("ctx"), jen.Id("v")), + ), + ), + ), + jen.Return( + jen.Id(errorNoMatch), + ), + ).Line() + } + m = append(m, codegen.NewCommentedValueMethod( + r.pkg.Path(), + resolveMethod, + jsonResolverStructName, + []jen.Code{ + jen.Id("ctx").Qual("context", "Context"), + jen.Id("m").Map(jen.String()).Interface(), + }, + []jen.Code{ + jen.Error(), + }, + []jen.Code{ + jen.List( + jen.Id("typeValue"), + jen.Id("ok"), + ).Op(":=").Id("m").Index(jen.Lit(typePropertyName)), + jen.If( + jen.Op("!").Id("ok"), + ).Block( + jen.Return( + jen.Qual("fmt", "Errorf").Call( + jen.Lit("cannot determine ActivityStreams type: 'type' property is missing"), + ), + ), + ), + jen.List( + jen.Id("rawContext"), + jen.Id("ok"), + ).Op(":=").Id("m").Index(jen.Lit(contextJSONLDName)), + jen.If( + jen.Op("!").Id("ok"), + ).Block( + jen.Return( + jen.Qual("fmt", "Errorf").Call( + jen.Lit("cannot determine ActivityStreams type: '@context' is missing"), + ), + ), + ), + jen.Id("aliasMap").Op(":=").Id(toAliasMapFnName).Call(jen.Id("rawContext")), + jen.Switch(jen.Id("typeValue")).Block( + impl.Default().Block( + jen.Return( + jen.Id(errorUnhandled), + ), + ), + ), + }, + fmt.Sprintf("%s determines the ActivityStreams type of the payload, then applies the first callback function whose signature accepts the ActivityStreams value's type. This strictly assures that the callback function will only be passed ActivityStream objects whose type matches its interface. Returns an error if the ActivityStreams type does not match callbackers or is not a type handled by the generated code.", resolveMethod))) + return +} + // typeResolverMethods returns the methods for the TypeResolver. func (r *ResolverGenerator) typeResolverMethods() (m []*codegen.Method) { impl := jen.Empty() @@ -764,3 +895,121 @@ func (r *ResolverGenerator) resolverInterface() *codegen.Interface { }, fmt.Sprintf("%s represents any %s or %s.", resolverInterface, typeResolverStructName, interfaceResolverStructName)) } + +// toAliasFunction returns the toAliasMap function +func (r *ResolverGenerator) toAliasFunction() *codegen.Function { + return codegen.NewCommentedFunction( + r.pkg.Path(), + toAliasMapFnName, + []jen.Code{ + jen.Id("i").Interface(), + }, + []jen.Code{ + jen.Id("m").Map(jen.String()).String(), + }, + []jen.Code{ + jen.Id("toHttpHttpsFn").Op(":=").Func().Parens( + jen.Id("s").String(), + ).Parens( + jen.List( + jen.Id("ok").Bool(), + jen.Id("http"), + jen.Id("https").String(), + ), + ).Block( + jen.If( + jen.Qual("strings", "HasPrefix").Call( + jen.Id("s"), + jen.Lit("http://"), + ), + ).Block( + jen.Id("ok").Op("=").True(), + jen.Id("http").Op("=").Id("s"), + jen.Id("https").Op("=").Lit("https").Op("+").Qual("strings", "TrimPrefix").Call( + jen.Id("s"), + jen.Lit("http"), + ), + ).Else().If( + jen.Qual("strings", "HasPrefix").Call( + jen.Id("s"), + jen.Lit("https://"), + ), + ).Block( + jen.Id("ok").Op("=").True(), + jen.Id("https").Op("=").Id("s"), + jen.Id("http").Op("=").Lit("http").Op("+").Qual("strings", "TrimPrefix").Call( + jen.Id("s"), + jen.Lit("https"), + ), + ), + jen.Return(), + ), + jen.Switch(jen.Id("v").Op(":=").Id("i").Assert(jen.Type())).Block( + jen.Case(jen.String()).Block( + jen.Commentf("Single entry, no alias."), + jen.If( + jen.List( + jen.Id("ok"), + jen.Id("http"), + jen.Id("https"), + ).Op(":=").Id("toHttpHttpsFn").Call(jen.Id("v")), + jen.Id("ok"), + ).Block( + jen.Id("m").Index( + jen.Id("http"), + ).Op("=").Lit(""), + jen.Id("m").Index( + jen.Id("https"), + ).Op("=").Lit(""), + ).Else().Block( + jen.Id("m").Index( + jen.Id("v"), + ).Op("=").Lit(""), + ), + ), + jen.Case(jen.Index().Interface()).Block( + jen.Commentf("Recursively apply."), + jen.For( + jen.List( + jen.Id("_"), + jen.Id("elem"), + ).Op(":=").Range().Id("v"), + ).Block( + jen.Id("r").Op(":=").Id(toAliasMapFnName).Call( + jen.Id("elem"), + ), + jen.For( + jen.List( + jen.Id("k"), + jen.Id("val"), + ).Op(":=").Range().Id("r"), + ).Block( + jen.Id("m").Index( + jen.Id("k"), + ).Op("=").Id("val"), + ), + ), + ), + jen.Case(jen.Map(jen.String()).Interface()).Block( + jen.Commentf("Map any aliases."), + jen.For( + jen.List( + jen.Id("k"), + jen.Id("val"), + ).Op(":=").Range().Id("v"), + ).Block( + jen.Commentf("Only handle string aliases."), + jen.Switch(jen.Id("conc").Op(":=").Id("val").Assert(jen.Type())).Block( + jen.Case(jen.String()).Block( + jen.Id("m").Index( + jen.Id("k"), + ).Op("=").Id("conc"), + ), + ), + ), + ), + ), + jen.Return(), + }, + fmt.Sprintf("%s converts a JSONLD context into a map of vocabulary name to alias.", toAliasMapFnName)) +} diff --git a/astool/gen/type.go b/astool/gen/type.go index bccd06f..f5845f1 100644 --- a/astool/gen/type.go +++ b/astool/gen/type.go @@ -684,7 +684,7 @@ func (t *TypeGenerator) deserializationFn() (deser *codegen.Function) { jen.List( jen.Id("p"), jen.Err(), - ).Op(":=").Add(deserMethod.On(managerInitName()).Call().Call(jen.Id("m"), jen.Id("context"))), + ).Op(":=").Add(deserMethod.On(managerInitName()).Call().Call(jen.Id("m"), jen.Id("aliasMap"))), jen.Err().Op("!=").Nil(), ).Block( jen.Return(jen.Nil(), jen.Err()), @@ -717,7 +717,7 @@ func (t *TypeGenerator) deserializationFn() (deser *codegen.Function) { deser = codegen.NewCommentedFunction( t.PrivatePackage().Path(), t.deserializationFnName(), - []jen.Code{jen.Id("m").Map(jen.String()).Interface(), jen.Id("context").Map(jen.String()).String()}, + []jen.Code{jen.Id("m").Map(jen.String()).Interface(), jen.Id("aliasMap").Map(jen.String()).String()}, []jen.Code{jen.Op("*").Id(t.TypeName()), jen.Error()}, []jen.Code{ jen.Id("alias").Op(":=").Lit(""), @@ -725,7 +725,7 @@ func (t *TypeGenerator) deserializationFn() (deser *codegen.Function) { jen.List( jen.Id("a"), jen.Id("ok"), - ).Op(":=").Id("context").Index(jen.Lit(t.vocabURI.String())), + ).Op(":=").Id("aliasMap").Index(jen.Lit(t.vocabURI.String())), jen.Id("ok"), ).Block( jen.Id("alias").Op("=").Id("a"),