Skip to content

Conversation

@jmcarp
Copy link
Contributor

@jmcarp jmcarp commented Dec 3, 2025

Currently, we define field descriptions and serialization behavior in several places, with repetitive but also sometime different logic. To make the field generation logic easier to understand and modify, this patch consolidates field descriptions and serialization logic into the TypeFields struct, and derives them from the schema dynamically. We also add tests for these new methods.

Part of #350.
Part of SSE-123.

@jmcarp jmcarp requested a review from a team as a code owner December 3, 2025 18:17
@jmcarp jmcarp force-pushed the jmcarp/field-cleanup branch from 9b465a5 to 4c1b525 Compare December 4, 2025 17:24
Currently, we define field descriptions and serialization behavior in several
places, with repetitive but also sometime different logic. To make the field
generation logic easier to understand and modify, this patch consolidates field
descriptions and serialization logic into the TypeFields struct, and derives
them from the schema dynamically. We also add tests for these new methods.
@jmcarp jmcarp force-pushed the jmcarp/field-cleanup branch from 4c1b525 to 1dafa11 Compare December 9, 2025 17:33
Rather than deciding which generated fields are represented as pointers in
different places in the code, this patch adds an IsPointer() method to
TypeFields to consolidate that logic.
@jmcarp jmcarp force-pushed the jmcarp/field-cleanup branch from 1dafa11 to 96f0b5c Compare December 9, 2025 17:45
@jmcarp jmcarp requested a review from lgfa29 December 9, 2025 18:12
assert.Equal(t, tt.want, got)
})

if diff := cmp.Diff(want, got, cmpIgnoreSchema); diff != "" {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an example of an assertion we could replace with a comparison of want/got generated code, rather than comparing the intermediate representations. In this case, I don't have a strong preference about which style is better. WDYT @lgfa29?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Yeah, this looks good to me.

My general thought was more towards end-to-end testing, like, have a set of OpenAPI JSON spec files and a corresponding set of expected Go generated files and just compare them:

//go:embed test-fixtures/*.json
var jsonSpecs embed.FS

//go:embed test-fixtures/*.go
var goFiles embed.FS

func TestCodeGeneration(t *testing.t) {
    for  range <each file in jsonSpec> {
        spec := <parse JSON file into *openapi3.T>
        generateTypes(tmpFile, spec)
        expect := <read corresponding goFiles>
        assert.Equal(t, <tmpFile content>, expect)
    }
}

But I don't have enough experience with the codebase yet to know how feasible/useful this would be.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think something more like an end-to-end test sounds good, but maybe we'd have a smaller number of bigger tests there. Most of the tests here are making assertions about a small surface area, like whether we're generating a description or type correctly for given inputs, and I don't know that we'd want to generate a large number of those tests as openapi specs and desired output files. So maybe I'll leave these tests alone for now and think about adding end-to-end tests later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, maybe we already have the beginnings of this, although just for a single spec, which is defined in go. There are a few golden files like https://github.com/oxidecomputer/oxide.go/blob/main/internal/generate/test_utils/types_output_expected that define expectations for different parts of the generated code. So maybe the approach should be to clean up and expand that kind of testing, in addition to the unit tests we're already writing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nice! Yeah, that looks like what I was thinking about. And you're right, we probably only need a handful of these E2E tests. Potentially even just one big file with all different scenarios we expect to encounter 😄

Name: paramName,
Type: paramType,
MarshalKey: p.Value.Name,
Schema: nil, // nil so StructTag always uses omitempty
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would setting OmitDirective: "omitempty" work as well here? instead of Schema: nil? It seems a bit more clear what the intention is without relying on the comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that works. Changing it.

Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{Description: "A foo field"}},
}
assert.Equal(t, "// Foo is a foo field", f.Description())
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth testing FallbackDescription, even though there's a TODO to remove it. It can at least help describe the intended behaviour.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding.

assert.Equal(t, tt.want, got)
})

if diff := cmp.Diff(want, got, cmpIgnoreSchema); diff != "" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Yeah, this looks good to me.

My general thought was more towards end-to-end testing, like, have a set of OpenAPI JSON spec files and a corresponding set of expected Go generated files and just compare them:

//go:embed test-fixtures/*.json
var jsonSpecs embed.FS

//go:embed test-fixtures/*.go
var goFiles embed.FS

func TestCodeGeneration(t *testing.t) {
    for  range <each file in jsonSpec> {
        spec := <parse JSON file into *openapi3.T>
        generateTypes(tmpFile, spec)
        expect := <read corresponding goFiles>
        assert.Equal(t, <tmpFile content>, expect)
    }
}

But I don't have enough experience with the codebase yet to know how feasible/useful this would be.

@jmcarp jmcarp force-pushed the jmcarp/field-cleanup branch from d2be59a to c4a9a4f Compare December 10, 2025 19:47
@jmcarp jmcarp merged commit d418991 into main Dec 10, 2025
2 checks passed
@jmcarp jmcarp deleted the jmcarp/field-cleanup branch December 10, 2025 19:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants