Skip to content

Commit 4beef2b

Browse files
authored
fix(gen/go-client): pass protojson.UnmarshalOptions through custom unmarshalers (#163)
* fix(gen/go-client): pass protojson.UnmarshalOptions through custom unmarshalers The previous DiscardUnknownFields implementation was bypassed by custom unmarshalers (int64, nullable, flatten, etc.) because json.Unmarshaler has no way to receive options. Introduce sebufUnmarshaler interface with UnmarshalJSONSebuf(data, opts) so the discard flag propagates through all annotation types, SSE streams, and nested message composition. Closes #153 * fix: resolve lint issues in forward-compat generators and tests Split TestForwardCompatGoldenPatterns into individual test functions to reduce cyclomatic complexity, fix golines violations, and remove unused nolint directives.
1 parent 8045798 commit 4beef2b

32 files changed

Lines changed: 2219 additions & 423 deletions

internal/clientgen/bytes_encoding.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,9 @@ func (g *Generator) generateBytesUnmarshalJSON(gf *protogen.GeneratedFile, ctx *
237237
fieldNames = append(fieldNames, string(f.Field.Desc.Name()))
238238
}
239239

240-
gf.P("// UnmarshalJSON implements json.Unmarshaler for ", msgName, ".")
240+
gf.P("// UnmarshalJSONSebuf implements sebufUnmarshaler for ", msgName, ".")
241241
gf.P("// This method handles bytes_encoding fields: ", strings.Join(fieldNames, ", "))
242-
gf.P("func (x *", msgName, ") UnmarshalJSON(data []byte) error {")
242+
gf.P("func (x *", msgName, ") UnmarshalJSONSebuf(data []byte, opts protojson.UnmarshalOptions) error {")
243243
gf.P("// Parse the raw JSON to extract bytes-encoded fields")
244244
gf.P("var raw map[string]json.RawMessage")
245245
gf.P("if err := json.Unmarshal(data, &raw); err != nil {")
@@ -258,7 +258,14 @@ func (g *Generator) generateBytesUnmarshalJSON(gf *protogen.GeneratedFile, ctx *
258258
gf.P("}")
259259
gf.P()
260260
gf.P("// Use protojson to unmarshal the rest")
261-
gf.P("return protojson.Unmarshal(modified, x)")
261+
gf.P("return opts.Unmarshal(modified, x)")
262+
gf.P("}")
263+
gf.P()
264+
265+
// Backward-compatible UnmarshalJSON wrapper for stdlib encoding/json
266+
gf.P("// UnmarshalJSON implements json.Unmarshaler for ", msgName, ".")
267+
gf.P("func (x *", msgName, ") UnmarshalJSON(data []byte) error {")
268+
gf.P("return x.UnmarshalJSONSebuf(data, protojson.UnmarshalOptions{})")
262269
gf.P("}")
263270
gf.P()
264271
}

internal/clientgen/empty_behavior.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,9 @@ func (g *Generator) generateEmptyBehaviorUnmarshalJSON(gf *protogen.GeneratedFil
221221
fieldNames = append(fieldNames, string(f.Field.Desc.Name()))
222222
}
223223

224-
gf.P("// UnmarshalJSON implements json.Unmarshaler for ", msgName, ".")
224+
gf.P("// UnmarshalJSONSebuf implements sebufUnmarshaler for ", msgName, ".")
225225
gf.P("// This method handles empty_behavior fields: ", strings.Join(fieldNames, ", "))
226-
gf.P("func (x *", msgName, ") UnmarshalJSON(data []byte) error {")
226+
gf.P("func (x *", msgName, ") UnmarshalJSONSebuf(data []byte, opts protojson.UnmarshalOptions) error {")
227227
gf.P("// Parse to check for explicit null values on empty_behavior=NULL fields")
228228
gf.P("var raw map[string]json.RawMessage")
229229
gf.P("if err := json.Unmarshal(data, &raw); err != nil {")
@@ -251,7 +251,14 @@ func (g *Generator) generateEmptyBehaviorUnmarshalJSON(gf *protogen.GeneratedFil
251251
gf.P("return err")
252252
gf.P("}")
253253
gf.P()
254-
gf.P("return protojson.Unmarshal(modified, x)")
254+
gf.P("return opts.Unmarshal(modified, x)")
255+
gf.P("}")
256+
gf.P()
257+
258+
// Backward-compatible UnmarshalJSON wrapper for stdlib encoding/json
259+
gf.P("// UnmarshalJSON implements json.Unmarshaler for ", msgName, ".")
260+
gf.P("func (x *", msgName, ") UnmarshalJSON(data []byte) error {")
261+
gf.P("return x.UnmarshalJSONSebuf(data, protojson.UnmarshalOptions{})")
255262
gf.P("}")
256263
gf.P()
257264
}

internal/clientgen/encoding.go

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,9 @@ func (g *Generator) generateInt64UnmarshalJSON(gf *protogen.GeneratedFile, ctx *
301301
numberFieldNames = append(numberFieldNames, string(f.Desc.Name()))
302302
}
303303

304-
gf.P("// UnmarshalJSON implements json.Unmarshaler for ", msgName, ".")
304+
gf.P("// UnmarshalJSONSebuf implements sebufUnmarshaler for ", msgName, ".")
305305
gf.P("// This method handles int64_encoding=NUMBER fields: ", strings.Join(numberFieldNames, ", "))
306-
gf.P("func (x *", msgName, ") UnmarshalJSON(data []byte) error {")
306+
gf.P("func (x *", msgName, ") UnmarshalJSONSebuf(data []byte, opts protojson.UnmarshalOptions) error {")
307307
gf.P("// First, parse the raw JSON to extract NUMBER-encoded fields")
308308
gf.P("var raw map[string]json.RawMessage")
309309
gf.P("if err := json.Unmarshal(data, &raw); err != nil {")
@@ -323,7 +323,14 @@ func (g *Generator) generateInt64UnmarshalJSON(gf *protogen.GeneratedFile, ctx *
323323
gf.P("}")
324324
gf.P()
325325
gf.P("// Use protojson to unmarshal the rest")
326-
gf.P("return protojson.Unmarshal(modified, x)")
326+
gf.P("return opts.Unmarshal(modified, x)")
327+
gf.P("}")
328+
gf.P()
329+
330+
// Backward-compatible UnmarshalJSON wrapper for stdlib encoding/json
331+
gf.P("// UnmarshalJSON implements json.Unmarshaler for ", msgName, ".")
332+
gf.P("func (x *", msgName, ") UnmarshalJSON(data []byte) error {")
333+
gf.P("return x.UnmarshalJSONSebuf(data, protojson.UnmarshalOptions{})")
327334
gf.P("}")
328335
gf.P()
329336
}
@@ -463,12 +470,12 @@ func (g *Generator) generateWrapperUnmarshalJSON(gf *protogen.GeneratedFile, ctx
463470
nestedFieldNames = append(nestedFieldNames, string(f.Desc.Name()))
464471
}
465472

466-
gf.P("// UnmarshalJSON implements json.Unmarshaler for ", msgName, ".")
473+
gf.P("// UnmarshalJSONSebuf implements sebufUnmarshaler for ", msgName, ".")
467474
gf.P(
468475
"// This method handles nested messages that have int64_encoding=NUMBER fields: ",
469476
strings.Join(nestedFieldNames, ", "),
470477
)
471-
gf.P("func (x *", msgName, ") UnmarshalJSON(data []byte) error {")
478+
gf.P("func (x *", msgName, ") UnmarshalJSONSebuf(data []byte, opts protojson.UnmarshalOptions) error {")
472479
gf.P("var raw map[string]json.RawMessage")
473480
gf.P("if err := json.Unmarshal(data, &raw); err != nil {")
474481
gf.P("return err")
@@ -477,10 +484,14 @@ func (g *Generator) generateWrapperUnmarshalJSON(gf *protogen.GeneratedFile, ctx
477484

478485
for _, field := range ctx.NestedFields {
479486
jsonName := field.Desc.JSONName()
480-
gf.P("// Handle \"", jsonName, "\" using its custom UnmarshalJSON")
487+
gf.P("// Handle \"", jsonName, "\" using its custom unmarshaler")
481488
gf.P("if rawVal, ok := raw[\"", jsonName, "\"]; ok {")
482489
gf.P("inner := &", gf.QualifiedGoIdent(field.Message.GoIdent), "{}")
483-
gf.P("if err := json.Unmarshal(rawVal, inner); err != nil {")
490+
gf.P("if u, ok := any(inner).(interface{ UnmarshalJSONSebuf([]byte, protojson.UnmarshalOptions) error }); ok {")
491+
gf.P("if err := u.UnmarshalJSONSebuf(rawVal, opts); err != nil {")
492+
gf.P("return err")
493+
gf.P("}")
494+
gf.P("} else if err := json.Unmarshal(rawVal, inner); err != nil {")
484495
gf.P("return err")
485496
gf.P("}")
486497
gf.P("innerJSON, err := protojson.Marshal(inner)")
@@ -497,7 +508,14 @@ func (g *Generator) generateWrapperUnmarshalJSON(gf *protogen.GeneratedFile, ctx
497508
gf.P("return err")
498509
gf.P("}")
499510
gf.P()
500-
gf.P("return protojson.Unmarshal(modified, x)")
511+
gf.P("return opts.Unmarshal(modified, x)")
512+
gf.P("}")
513+
gf.P()
514+
515+
// Backward-compatible UnmarshalJSON wrapper for stdlib encoding/json
516+
gf.P("// UnmarshalJSON implements json.Unmarshaler for ", msgName, ".")
517+
gf.P("func (x *", msgName, ") UnmarshalJSON(data []byte) error {")
518+
gf.P("return x.UnmarshalJSONSebuf(data, protojson.UnmarshalOptions{})")
501519
gf.P("}")
502520
gf.P()
503521
}

internal/clientgen/flatten.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,9 @@ func (g *Generator) generateFlattenUnmarshalJSON(gf *protogen.GeneratedFile, ctx
260260
fieldNames = append(fieldNames, string(info.Field.Desc.Name()))
261261
}
262262

263-
gf.P("// UnmarshalJSON implements json.Unmarshaler for ", msgName, ".")
263+
gf.P("// UnmarshalJSONSebuf implements sebufUnmarshaler for ", msgName, ".")
264264
gf.P("// This method handles flatten fields: ", strings.Join(fieldNames, ", "))
265-
gf.P("func (x *", msgName, ") UnmarshalJSON(data []byte) error {")
265+
gf.P("func (x *", msgName, ") UnmarshalJSONSebuf(data []byte, opts protojson.UnmarshalOptions) error {")
266266
gf.P("var raw map[string]json.RawMessage")
267267
gf.P("if err := json.Unmarshal(data, &raw); err != nil {")
268268
gf.P("return err")
@@ -279,7 +279,14 @@ func (g *Generator) generateFlattenUnmarshalJSON(gf *protogen.GeneratedFile, ctx
279279
gf.P("return err")
280280
gf.P("}")
281281
gf.P()
282-
gf.P("return protojson.Unmarshal(remaining, x)")
282+
gf.P("return opts.Unmarshal(remaining, x)")
283+
gf.P("}")
284+
gf.P()
285+
286+
// Backward-compatible UnmarshalJSON wrapper for stdlib encoding/json
287+
gf.P("// UnmarshalJSON implements json.Unmarshaler for ", msgName, ".")
288+
gf.P("func (x *", msgName, ") UnmarshalJSON(data []byte) error {")
289+
gf.P("return x.UnmarshalJSONSebuf(data, protojson.UnmarshalOptions{})")
283290
gf.P("}")
284291
gf.P()
285292
}
@@ -319,8 +326,15 @@ func (g *Generator) generateFlattenFieldUnmarshal(gf *protogen.GeneratedFile, in
319326
gf.P("return childErr")
320327
gf.P("}")
321328
gf.P("x.", goName, " = &", childTypeName, "{}")
322-
gf.P("// Use json.Unmarshal to invoke child's UnmarshalJSON (annotation composability)")
323-
gf.P("if childErr = json.Unmarshal(childData, x.", goName, "); childErr != nil {")
329+
gf.P("// Forward opts to child's UnmarshalJSONSebuf if available (annotation composability)")
330+
gf.P(
331+
"if u, ok := any(x.", goName,
332+
`).(interface{ UnmarshalJSONSebuf([]byte, protojson.UnmarshalOptions) error }); ok {`,
333+
)
334+
gf.P("if childErr = u.UnmarshalJSONSebuf(childData, opts); childErr != nil {")
335+
gf.P("return childErr")
336+
gf.P("}")
337+
gf.P("} else if childErr = json.Unmarshal(childData, x.", goName, "); childErr != nil {")
324338
gf.P("return childErr")
325339
gf.P("}")
326340
gf.P("}")

0 commit comments

Comments
 (0)