Skip to content

Commit 5462dfc

Browse files
committed
⚠️ Add support for encoding.TextMarshaler
Whenever a type is encountered that implements encoding.TextMarshaler but not encoding/json.Marshaler, assume that it will be encoded as a string. This is a breaking change, as types that implement TextMarshaler are now handled differently. On the other hand, this probably restores reality for all but the most customized setups, as Go's JSON package will also check for the presence of this interface, and output a JSON string. Signed-off-by: Tom Wieczorek <twieczorek@mirantis.com>
1 parent 942927c commit 5462dfc

File tree

3 files changed

+72
-8
lines changed

3 files changed

+72
-8
lines changed

pkg/crd/schema.go

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,26 @@ func (c *schemaContext) requestSchema(pkgPath, typeName string) {
113113

114114
// infoToSchema creates a schema for the type in the given set of type information.
115115
func infoToSchema(ctx *schemaContext) *apiext.JSONSchemaProps {
116-
// If the obj implements a JSON marshaler and has a marker, use the markers value and do not traverse as
117-
// the marshaler could be doing anything. If there is no marker, fall back to traversing.
118-
if obj := ctx.pkg.Types.Scope().Lookup(ctx.info.Name); obj != nil && implementsJSONMarshaler(obj.Type()) {
119-
schema := &apiext.JSONSchemaProps{}
120-
applyMarkers(ctx, ctx.info.Markers, schema, ctx.info.RawSpec.Type)
121-
if schema.Type != "" {
116+
if obj := ctx.pkg.Types.Scope().Lookup(ctx.info.Name); obj != nil {
117+
switch {
118+
// If the obj implements a JSON marshaler and has a marker, use the
119+
// markers value and do not traverse as the marshaler could be doing
120+
// anything. If there is no marker, fall back to traversing.
121+
case implements(obj.Type(), jsonMarshaler):
122+
schema := &apiext.JSONSchemaProps{}
123+
applyMarkers(ctx, ctx.info.Markers, schema, ctx.info.RawSpec.Type)
124+
if schema.Type != "" {
125+
return schema
126+
}
127+
128+
// If the obj implements a text marshaler, encode it as a string.
129+
case implements(obj.Type(), textMarshaler):
130+
schema := &apiext.JSONSchemaProps{Type: "string"}
131+
applyMarkers(ctx, ctx.info.Markers, schema, ctx.info.RawSpec.Type)
132+
if schema.Type != "string" {
133+
err := fmt.Errorf("%q implements encoding.TextMarshaler but schema type is not string: %q", ctx.info.RawSpec.Name, schema.Type)
134+
ctx.pkg.AddError(loader.ErrFromNode(err, ctx.info.RawSpec.Type))
135+
}
122136
return schema
123137
}
124138
}
@@ -493,6 +507,15 @@ var jsonMarshaler = types.NewInterfaceType([]*types.Func{
493507
types.NewVar(token.NoPos, nil, "", types.Universe.Lookup("error").Type())), false)),
494508
}, nil).Complete()
495509

496-
func implementsJSONMarshaler(typ types.Type) bool {
497-
return types.Implements(typ, jsonMarshaler) || types.Implements(types.NewPointer(typ), jsonMarshaler)
510+
// Open coded go/types representation of encoding.TextMarshaler
511+
var textMarshaler = types.NewInterfaceType([]*types.Func{
512+
types.NewFunc(token.NoPos, nil, "MarshalText",
513+
types.NewSignatureType(nil, nil, nil, nil,
514+
types.NewTuple(
515+
types.NewVar(token.NoPos, nil, "text", types.NewSlice(types.Universe.Lookup("byte").Type())),
516+
types.NewVar(token.NoPos, nil, "err", types.Universe.Lookup("error").Type())), false)),
517+
}, nil).Complete()
518+
519+
func implements(typ types.Type, iface *types.Interface) bool {
520+
return types.Implements(typ, iface) || types.Implements(types.NewPointer(typ), iface)
498521
}

pkg/crd/testdata/cronjob_types.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ limitations under the License.
2323
package cronjob
2424

2525
import (
26+
"encoding"
2627
"encoding/json"
2728
"fmt"
2829
"net/url"
@@ -552,6 +553,30 @@ func (u *URL2) String() string {
552553
return (*url.URL)(u).String()
553554
}
554555

556+
// URL3 wraps [net/url.URL]. It implements [encoding.TextMarshaler] so that it
557+
// can be used in K8s CRDs such that the CRD resource will have the URL but
558+
// operator code can can work with the URL struct.
559+
type URL3 struct{ url.URL }
560+
561+
var _ encoding.TextMarshaler = (*URL3)(nil)
562+
563+
// MarshalText implements [encoding.TextMarshaler].
564+
func (u *URL3) MarshalText() (text []byte, err error) {
565+
return u.MarshalBinary()
566+
}
567+
568+
// URL4 is an alias of [net/url.URL]. It implements [encoding.TextMarshaler] so
569+
// that it can be used in K8s CRDs such that the CRD resource will have the URL
570+
// but operator code can can work with the URL struct.
571+
type URL4 url.URL
572+
573+
var _ encoding.TextMarshaler = (*URL4)(nil)
574+
575+
// MarshalText implements [encoding.TextMarshaler].
576+
func (u *URL4) MarshalText() (text []byte, err error) {
577+
return (*url.URL)(u).MarshalBinary()
578+
}
579+
555580
// Duration has a custom Marshaler but no markers.
556581
// We want the CRD generation to infer type information
557582
// from the go types and ignore the presense of the Marshaler.
@@ -611,6 +636,14 @@ type CronJobStatus struct {
611636
// +optional
612637
LastActiveLogURL2 *URL2 `json:"lastActiveLogURL2,omitempty"`
613638

639+
// LastActiveLogURL3 specifies the logging url for the last started job
640+
// +optional
641+
LastActiveLogURL3 *URL3 `json:"lastActiveLogURL3,omitempty"`
642+
643+
// LastActiveLogURL4 specifies the logging url for the last started job
644+
// +optional
645+
LastActiveLogURL4 *URL4 `json:"lastActiveLogURL4,omitempty"`
646+
614647
Runtime *Duration `json:"duration,omitempty"`
615648
}
616649

pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7018,6 +7018,14 @@ spec:
70187018
description: LastActiveLogURL2 specifies the logging url for the last
70197019
started job
70207020
type: string
7021+
lastActiveLogURL3:
7022+
description: LastActiveLogURL3 specifies the logging url for the last
7023+
started job
7024+
type: string
7025+
lastActiveLogURL4:
7026+
description: LastActiveLogURL4 specifies the logging url for the last
7027+
started job
7028+
type: string
70217029
lastScheduleMicroTime:
70227030
description: |-
70237031
Information about the last time the job was successfully scheduled,

0 commit comments

Comments
 (0)