Skip to content

v0.18.0 regression: anyOf with null variant results in incorrect codegen #427

@woodruffw

Description

@woodruffw

Hi there! Thanks a ton for maintaining go-jsonschema; I've been using it as part of x509-limbo to share types across a common schema.

I'm reporting what I believe to be a regression with v0.18.0, which appears to be caused by improvements made with that release (namely, better handling of anyOf variants).

In particular, I have a type schema that has a member like this:

        "expected_peer_name": {
          "anyOf": [
            {
              "$ref": "#/$defs/PeerName"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "For server (i.e. client-side) validation: the expected peer name, if any"
        },

Full source here: https://github.com/C2SP/x509-limbo/blob/main/limbo-schema.json

go-jsonschema generates the following for this:

// Represents a peer (i.e., end entity) certificate's name (Subject or SAN).
type TestcaseExpectedPeerName struct {
	// The kind of peer name
	Kind PeerKind `json:"kind" yaml:"kind" mapstructure:"kind"`

	// The peer's name
	Value string `json:"value" yaml:"value" mapstructure:"value"`
}

// UnmarshalJSON implements json.Unmarshaler.
func (j *TestcaseExpectedPeerName) UnmarshalJSON(value []byte) error {
	var raw map[string]interface{}
	if err := json.Unmarshal(value, &raw); err != nil {
		return err
	}
	var testcaseExpectedPeerName_0 TestcaseExpectedPeerName_0
	var testcaseExpectedPeerName_1 TestcaseExpectedPeerName_1
	var errs []error
	if err := testcaseExpectedPeerName_0.UnmarshalJSON(value); err != nil {
		errs = append(errs, err)
	}
	if err := testcaseExpectedPeerName_1.UnmarshalJSON(value); err != nil {
		errs = append(errs, err)
	}
	if len(errs) == 2 {
		return fmt.Errorf("all validators failed: %s", errors.Join(errs...))
	}
	type Plain TestcaseExpectedPeerName
	var plain Plain
	if err := json.Unmarshal(value, &plain); err != nil {
		return err
	}
	*j = TestcaseExpectedPeerName(plain)
	return nil
}

In plain English: TestcaseExpectedPeerName is a duplicate of the PeerName type, and then the UnmarshalJSON function attempts to deserialize two variants of it (TestcaseExpectedPeerName_0 and TestcaseExpectedPeerName_1). However, go-jsonschema does not generate the TestcaseExpectedPeerName_1 variant, presumably because it has type: null in the schema:

./schema.go:467:33: undefined: TestcaseExpectedPeerName_1
make: *** [gocryptox509] Error 1

This can be seen in the source as well, where only TestcaseExpectedPeerName_0 is present:

type TestcaseExpectedPeerName_0 = PeerName

Expected behavior

I expected go-jsonschema to generate well-formed Go code.

In particular, I expected it to generate either an interface{} type for ExpectedPeerName (like it did in v0.17.0 and earlier) or generate an appropriate non-variant type (no _0 or _1 at all), since the null variant should be handled via the omitempty marker.

Workarounds

Downgrading to v0.17.0 results in correct codegen, although with slightly suboptimal typing (interface{} instead of a stronger type definition).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions