[object Object]

← back to Cli Printing Press

fix(generator): add PATCH support + skip unexported fields in types

588c694e908b978f5446f618ae60748c2bb53e6c · 2026-03-23 17:12:51 -0700 · Matt Van Horn

Add PATCH case to command template and Patch method to client template.
Skip schema properties starting with _ in type generation since they
produce unexported Go fields with JSON tags that fail go vet.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files touched

Diff

commit 588c694e908b978f5446f618ae60748c2bb53e6c
Author: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date:   Mon Mar 23 17:12:51 2026 -0700

    fix(generator): add PATCH support + skip unexported fields in types
    
    Add PATCH case to command template and Patch method to client template.
    Skip schema properties starting with _ in type generation since they
    produce unexported Go fields with JSON tags that fail go vet.
    
    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
 internal/generator/templates/client.go.tmpl  |  4 +++
 internal/generator/templates/command.go.tmpl |  8 +++++
 internal/openapi/parser.go                   |  6 ++++
 internal/openapi/parser_test.go              | 46 ++++++++++++++++++++++++++++
 4 files changed, 64 insertions(+)

diff --git a/internal/generator/templates/client.go.tmpl b/internal/generator/templates/client.go.tmpl
index 5a2391f7..8c1f921c 100644
--- a/internal/generator/templates/client.go.tmpl
+++ b/internal/generator/templates/client.go.tmpl
@@ -39,6 +39,10 @@ func (c *Client) Put(path string, body any) (json.RawMessage, error) {
 	return c.do("PUT", path, nil, body)
 }
 
+func (c *Client) Patch(path string, body any) (json.RawMessage, error) {
+	return c.do("PATCH", path, nil, body)
+}
+
 func (c *Client) do(method, path string, params map[string]string, body any) (json.RawMessage, error) {
 	url := c.BaseURL + path
 
diff --git a/internal/generator/templates/command.go.tmpl b/internal/generator/templates/command.go.tmpl
index 422c1a42..b4ef5270 100644
--- a/internal/generator/templates/command.go.tmpl
+++ b/internal/generator/templates/command.go.tmpl
@@ -78,6 +78,14 @@ func new{{title $.ResourceName}}{{title $eName}}Cmd(flags *rootFlags) *cobra.Com
 			}
 {{- end}}
 			data, err := c.Put(path, body)
+{{- else if eq $endpoint.Method "PATCH"}}
+			body := map[string]any{}
+{{- range $endpoint.Body}}
+			if body{{camel .Name}} != {{zeroVal .Type}} {
+				body["{{.Name}}"] = body{{camel .Name}}
+			}
+{{- end}}
+			data, err := c.Patch(path, body)
 {{- end}}
 			if err != nil {
 				return apiErr(err)
diff --git a/internal/openapi/parser.go b/internal/openapi/parser.go
index 4883c550..e7bcee9a 100644
--- a/internal/openapi/parser.go
+++ b/internal/openapi/parser.go
@@ -643,6 +643,9 @@ func mapTypes(doc *openapi3.T, out *spec.APISpec) {
 
 		fields := make([]spec.TypeField, 0, len(fieldNames))
 		for _, fieldName := range fieldNames {
+			if strings.HasPrefix(fieldName, "_") {
+				continue
+			}
 			fields = append(fields, spec.TypeField{
 				Name: fieldName,
 				Type: mapSchemaType(schemaRefValue(properties[fieldName])),
@@ -668,6 +671,9 @@ func collectTypeProperties(schemaRef *openapi3.SchemaRef, properties map[string]
 		if prop == nil {
 			continue
 		}
+		if strings.HasPrefix(name, "_") {
+			continue
+		}
 		properties[name] = prop
 	}
 	for _, sub := range schema.AllOf {
diff --git a/internal/openapi/parser_test.go b/internal/openapi/parser_test.go
index c5cef31b..18b10300 100644
--- a/internal/openapi/parser_test.go
+++ b/internal/openapi/parser_test.go
@@ -58,6 +58,52 @@ func TestParseStytchOpenAPI(t *testing.T) {
 	assert.Greater(t, totalEndpoints, 10)
 }
 
+func TestSkipUnderscoreFields(t *testing.T) {
+	spec := []byte(`
+openapi: "3.0.0"
+info:
+  title: Test
+  version: "1.0"
+servers:
+  - url: https://api.example.com
+paths:
+  /items:
+    get:
+      operationId: listItems
+      responses:
+        "200":
+          description: OK
+components:
+  schemas:
+    Item:
+      type: object
+      properties:
+        id:
+          type: string
+        name:
+          type: string
+        _errors:
+          type: object
+        _internal:
+          type: string
+`)
+	parsed, err := Parse(spec)
+	require.NoError(t, err)
+
+	item, ok := parsed.Types["Item"]
+	require.True(t, ok)
+
+	// Should have id and name but NOT _errors or _internal
+	fieldNames := make([]string, 0)
+	for _, f := range item.Fields {
+		fieldNames = append(fieldNames, f.Name)
+	}
+	assert.Contains(t, fieldNames, "id")
+	assert.Contains(t, fieldNames, "name")
+	assert.NotContains(t, fieldNames, "_errors")
+	assert.NotContains(t, fieldNames, "_internal")
+}
+
 func TestIsOpenAPI(t *testing.T) {
 	t.Parallel()
 

← d3393f88 docs: add Phase 4 plan  ·  back to Cli Printing Press  ·  fix(openapi): smart operationId cleaning for clean command n 84823431 →