diff --git a/tools/exp/props/funcprop.go b/tools/exp/props/funcprop.go index 0422918..61493b8 100644 --- a/tools/exp/props/funcprop.go +++ b/tools/exp/props/funcprop.go @@ -7,6 +7,10 @@ import ( "sync" ) +const ( + iriMember = "iri" +) + // FunctionalPropertyGenerator produces Go code for properties that can have // only one value. The resulting property is a struct type that can have one // value that could be from multiple Kinds of values. If there is only one @@ -306,22 +310,32 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, *co jen.Nil(), )}, jen.Commentf("%s converts this into an interface representation suitable for marshalling into a text or binary format.", p.serializeFnName())) - deserializeFns := jen.Empty() + valueDeserializeFns := jen.Empty() + typeDeserializeFns := jen.Empty() + foundValue := false + foundType := false for i, kind := range p.Kinds { - if i > 0 { - deserializeFns = deserializeFns.Else() - } values := jen.Dict{ jen.Id(p.memberName(i)): jen.Id("v"), } if !kind.Nilable { values[jen.Id(p.hasMemberName(i))] = jen.True() } - deserializeFns = deserializeFns.If( + tmp := jen.Empty() + if kind.isValue() && foundValue { + tmp = tmp.Else() + } else if !kind.isValue() && foundType { + tmp = tmp.Else() + } + variable := jen.Id("i") + if !kind.isValue() { + variable = jen.Id("m") + } + tmp = tmp.If( jen.List( jen.Id("v"), jen.Err(), - ).Op(":=").Add(kind.deserializeFnCode(jen.Id("i"))), + ).Op(":=").Add(kind.deserializeFnCode(variable)), jen.Err().Op("!=").Nil(), ).Block( jen.Id(codegen.This()).Op(":=").Op("&").Id(p.StructName()).Values( @@ -332,9 +346,15 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, *co jen.Nil(), ), ) + if kind.isValue() { + foundValue = true + valueDeserializeFns = valueDeserializeFns.Add(tmp) + } else { + foundType = true + typeDeserializeFns = typeDeserializeFns.Add(tmp) + } } var deserialize *codegen.Function - // TODO: IRI Value if p.asIterator { deserialize = codegen.NewCommentedFunction( p.GetPrivatePackage().Path(), @@ -342,19 +362,13 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, *co []jen.Code{jen.Id("i").Interface()}, []jen.Code{jen.Op("*").Id(p.StructName()), jen.Error()}, []jen.Code{ - p.addUnknownDeserializeCode(deserializeFns).Else().Block( - jen.Return( - jen.Nil(), - jen.Qual("fmt", "Errorf").Call( - jen.Lit("could not deserialize %q property"), - jen.Lit(p.PropertyName()), - ), + p.wrapDeserializeCode(valueDeserializeFns, typeDeserializeFns, false).Line().Return( + jen.Nil(), + jen.Qual("fmt", "Errorf").Call( + jen.Lit("could not deserialize %q property"), + jen.Lit(p.PropertyName()), ), ), - jen.Return( - jen.Nil(), - jen.Nil(), - ), }, jen.Commentf("%s creates an iterator from an element that has been unmarshalled from a text or binary format.", p.DeserializeFnName())) } else { @@ -373,7 +387,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, *co ), jen.Id("ok"), ).Block( - p.addUnknownDeserializeCode(deserializeFns), + p.wrapDeserializeCode(valueDeserializeFns, typeDeserializeFns, true), ), jen.Return( jen.Nil(), @@ -409,6 +423,7 @@ func (p *FunctionalPropertyGenerator) singleTypeDef() *codegen.Struct { } } kindMembers = append(kindMembers, p.unknownMemberDef()) + kindMembers = append(kindMembers, p.iriMemberDef()) if p.HasNaturalLanguageMap { kindMembers = append(kindMembers, jen.Id(langMapMember).Map(jen.String()).String()) } @@ -596,6 +611,7 @@ func (p *FunctionalPropertyGenerator) multiTypeDef() *codegen.Struct { } } kindMembers = append(kindMembers, p.unknownMemberDef()) + kindMembers = append(kindMembers, p.iriMemberDef()) if p.HasNaturalLanguageMap { kindMembers = append(kindMembers, jen.Id(langMapMember).Map(jen.String()).String()) } @@ -820,13 +836,58 @@ func (p *FunctionalPropertyGenerator) unknownMemberDef() jen.Code { return jen.Id(unknownMemberName).Index().Byte() } -// addUnknownDeserializeCode generates the "else if it's a []byte" code used for -// deserializing unknown values. -func (p *FunctionalPropertyGenerator) addUnknownDeserializeCode(existing jen.Code) *jen.Statement { - if len(p.Kinds) > 0 { - existing = jen.Add(existing, jen.Else()) +// iriMemberDef returns the definition of a struct member that handles +// a property whose type is an IRI. +func (p *FunctionalPropertyGenerator) iriMemberDef() jen.Code { + return jen.Id(iriMember).Op("*").Qual("net/url", "URL") +} + +// wrapDeserializeCode generates the "else if it's a []byte" code and IRI code +// used for deserializing unknown values. +func (p *FunctionalPropertyGenerator) wrapDeserializeCode(valueExisting, typeExisting jen.Code, errorAtEnd bool) *jen.Statement { + iriCode := jen.Empty() + if !p.hasURIKind() { + iriCode = jen.If( + jen.List( + jen.Id("s"), + jen.Id("ok"), + ).Op(":=").Id("i").Assert(jen.String()), + jen.Id("ok"), + ).Block( + // IRI + jen.List( + jen.Id("u"), + jen.Err(), + ).Op(":=").Qual("net/url", "Parse").Call(jen.Id("s")), + jen.Commentf("If error exists, don't error out -- skip this and treat as unknown string ([]byte) at worst"), + jen.If(jen.Err().Op("==").Nil()).Block( + jen.Id(codegen.This()).Op(":=").Op("&").Id(p.StructName()).Values( + jen.Dict{ + jen.Id(iriMember): jen.Id("u"), + }, + ), + jen.Return( + jen.Id(codegen.This()), + jen.Nil(), + ), + ), + ).Line() } - return jen.Add(existing, + if p.hasTypeKind() { + iriCode = iriCode.If( + jen.List( + jen.Id("m"), + jen.Id("ok"), + ).Op(":=").Id("i").Assert(jen.Map(jen.String()).Interface()), + jen.Id("ok"), + ).Block( + typeExisting, + ).Else() + } + if p.hasValueKind() { + iriCode = iriCode.Add(valueExisting).Else() + } + iriCode = iriCode.Add( jen.If( jen.List( jen.Id("v"), @@ -847,4 +908,46 @@ func (p *FunctionalPropertyGenerator) addUnknownDeserializeCode(existing jen.Cod ), ), ) + if errorAtEnd { + iriCode = iriCode.Else().Block( + jen.Return( + jen.Nil(), + jen.Qual("fmt", "Errorf").Call( + jen.Lit("could not deserialize %q property"), + jen.Lit(p.PropertyName()), + ), + ), + ) + } + return iriCode +} + +// hasURIKind returns true if this property already has a Kind that is a URI. +func (p *FunctionalPropertyGenerator) hasURIKind() bool { + for _, k := range p.Kinds { + if k.IsURI { + return true + } + } + return false +} + +// hasTypeKind returns true if this property has a Kind that is a type. +func (p *FunctionalPropertyGenerator) hasTypeKind() bool { + for _, k := range p.Kinds { + if !k.isValue() { + return true + } + } + return false +} + +// hasValueKind returns true if this property has a Kind that is a Value. +func (p *FunctionalPropertyGenerator) hasValueKind() bool { + for _, k := range p.Kinds { + if k.isValue() { + return true + } + } + return false } diff --git a/tools/exp/props/nonfuncprop.go b/tools/exp/props/nonfuncprop.go index 51112c2..57a4742 100644 --- a/tools/exp/props/nonfuncprop.go +++ b/tools/exp/props/nonfuncprop.go @@ -386,8 +386,9 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, ), jen.Err().Op("!=").Nil(), ).Block( + jen.Id("ret").Op(":=").Id(p.StructName()).Call(jen.Id(codegen.This())), jen.Return( - jen.Id(codegen.This()), + jen.Op("&").Id("ret"), jen.Err(), ), ).Else().If( @@ -403,7 +404,7 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, p.GetPrivatePackage().Path(), p.DeserializeFnName(), []jen.Code{jen.Id("m").Map(jen.String()).Interface()}, - []jen.Code{jen.Id(p.StructName()), jen.Error()}, + []jen.Code{jen.Qual(p.GetPublicPackage().Path(), p.InterfaceName()), jen.Error()}, []jen.Code{ jen.Var().Id(codegen.This()).Index().Op("*").Id(p.iteratorTypeName().CamelName), jen.If( @@ -436,8 +437,9 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, deserializeFn("i"), ), ), + jen.Id("ret").Op(":=").Id(p.StructName()).Call(jen.Id(codegen.This())), jen.Return( - jen.Id(codegen.This()), + jen.Op("&").Id("ret"), jen.Nil(), ), }, diff --git a/tools/exp/props/property.go b/tools/exp/props/property.go index ce75cdb..0cf6000 100644 --- a/tools/exp/props/property.go +++ b/tools/exp/props/property.go @@ -87,7 +87,7 @@ type Kind struct { func (k Kind) lessFnCode(this, other *jen.Statement) *jen.Statement { // LessFn is nil case -- call comparison Less method directly on the LHS lessCall := this.Clone().Dot(compareLessMethod).Call(other.Clone()) - if k.LessFn != nil { + if k.isValue() { // LessFn is indeed a function -- call this function lessCall = k.LessFn.Clone().Call( this.Clone(), @@ -98,8 +98,7 @@ func (k Kind) lessFnCode(this, other *jen.Statement) *jen.Statement { } func (k Kind) deserializeFnCode(this *jen.Statement) *jen.Statement { - // LessFn is not nil, this means it is a value. - if k.LessFn != nil { + if k.isValue() { return k.DeserializeFn.Clone().Call(this) } else { // If LessFn is nil, this means it is a type. Which requires an @@ -108,6 +107,13 @@ func (k Kind) deserializeFnCode(this *jen.Statement) *jen.Statement { } } +func (k Kind) isValue() bool { + // LessFn is not nil, this means it is a value. + // If LessFn is nil, this means it is a type. Types will have their + // LessThan method called directly on the type. + return k.LessFn != nil +} + // PropertyGenerator is a common base struct used in both Functional and // NonFunctional ActivityStreams properties. It provides common naming patterns, // logic, and common Go code to be generated. diff --git a/tools/exp/props/type.go b/tools/exp/props/type.go index b1051e8..eb53e2c 100644 --- a/tools/exp/props/type.go +++ b/tools/exp/props/type.go @@ -21,6 +21,7 @@ const ( compareLessMethod = "LessThan" getUnknownMethod = "GetUnknownProperties" unknownMember = "unknown" + getMethodFormat = "Get%s" ) // TypeInterface returns the Type Interface that is needed for ActivityStream @@ -559,12 +560,16 @@ func (t *TypeGenerator) lessMethod() (less *codegen.Method) { jen.Commentf("Compare property %q", prop.PropertyName()).Line(), jen.If( jen.Id(codegen.This()).Dot(t.memberName(prop)).Dot(compareLessMethod).Call( - jen.Id("o").Dot(t.memberName(prop)), + jen.Id("o").Dot( + fmt.Sprintf(getMethodFormat, t.memberName(prop)), + ).Call(), ), ).Block( jen.Return(jen.True()), ).Else().If( - jen.Id("o").Dot(t.memberName(prop)).Dot(compareLessMethod).Call( + jen.Id("o").Dot( + fmt.Sprintf(getMethodFormat, t.memberName(prop)), + ).Call().Dot(compareLessMethod).Call( jen.Id(codegen.This()).Dot(t.memberName(prop)), ), ).Block( @@ -693,7 +698,7 @@ func (t *TypeGenerator) allGetters() (m []*codegen.Method) { for _, property := range t.allProperties() { m = append(m, codegen.NewCommentedValueMethod( t.PrivatePackage().Path(), - fmt.Sprintf("Get%s", t.memberName(property)), + fmt.Sprintf(getMethodFormat, t.memberName(property)), t.TypeName(), /*params=*/ nil, []jen.Code{jen.Qual(property.GetPublicPackage().Path(), property.InterfaceName())}, @@ -702,7 +707,7 @@ func (t *TypeGenerator) allGetters() (m []*codegen.Method) { jen.Id(codegen.This()).Dot(t.memberName(property)), ), }, - jen.Commentf("Get%s returns the %q property if it exists, and nil otherwise.", t.memberName(property), property.PropertyName()))) + jen.Commentf(getMethodFormat+" returns the %q property if it exists, and nil otherwise.", t.memberName(property), property.PropertyName()))) } return } @@ -719,7 +724,7 @@ func (t *TypeGenerator) allSetters() (m []*codegen.Method) { []jen.Code{ jen.Id(codegen.This()).Dot(t.memberName(property)).Op("=").Id("i"), }, - jen.Commentf("Get%s returns the %q property if it exists, and nil otherwise.", t.memberName(property), property.PropertyName()))) + jen.Commentf("Set%s returns the %q property if it exists, and nil otherwise.", t.memberName(property), property.PropertyName()))) } return }