fix(goctl): resolve multi-level struct embedding in TypeScript generation#5407
fix(goctl): resolve multi-level struct embedding in TypeScript generation#5407kevwan wants to merge 3 commits intozeromicro:masterfrom
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
This PR updates the API parser’s type-resolution step so that embedded structs are resolved across multiple levels, preventing TypeScript generation from losing fields coming from deeply embedded structs (as reported in #5402).
Changes:
- Reworks
Analyzer.fillTypes()to iteratively resolve embeddedspec.DefineStructreferences until resolution stabilizes. - Adds a helper
findTypeInList()to prefer types already resolved earlier in the same iteration.
| // Check if the type changed (was resolved further) | ||
| if resolvedType, ok := tp.(spec.DefineStruct); ok { | ||
| if len(resolvedType.Members) != len(v.Members) { | ||
| changed = true | ||
| } | ||
| } |
There was a problem hiding this comment.
The loop break condition can stop iterations too early: changed is only set when the embedded struct’s member count changes. For deeper embedding chains (e.g., A embeds B embeds C embeds D), B/C can become “more resolved” (inner embedded members replaced) without changing len(Members), so changed stays false and earlier structs may never pick up the newly-resolved versions. Consider setting changed when the resolved type differs from the current member.Type (e.g., !reflect.DeepEqual(member.Type, tp)), or alternatively always run up to maxIterations and break only when no member.Type replacements occur in an iteration.
| // Resolve embedded struct types iteratively until all references are fully resolved. | ||
| // This handles multi-level struct embedding where a struct embeds another struct | ||
| // that itself embeds yet another struct. | ||
| maxIterations := len(a.spec.Types) |
There was a problem hiding this comment.
This change is a regression fix for multi-level struct embedding resolution, but there isn’t an automated test covering it in this package. Adding a parser/analyzer test that asserts embedded fields are fully flattened (including a chain deeper than 2 levels and with types defined out-of-order) would prevent future regressions.
…tion Fixes zeromicro#5402 When using multi-level nesting in type definitions (e.g., AgeStruct embeds NameStruct which embeds IdStruct), the TypeScript code generation was losing fields from the deepest embedded structs. Root cause: The type resolution in fillTypes() did a single pass through types, looking up embedded struct references from the original unresolved type list. This meant multi-level embeddings couldn't resolve properly. Solution: Implement iterative type resolution that: 1. Resolves types in multiple passes until no changes occur 2. Looks up types from both the currently-resolved types and original list 3. Prioritizes recently-resolved types to handle multi-level embeddings This ensures all embedded struct fields are properly flattened in the generated TypeScript interfaces.
Add comprehensive test cases to verify that multi-level struct embedding is correctly resolved by the parser: 1. Test 2-level embedding (AgeStruct -> NameStruct -> IdStruct) 2. Test 3-level embedding (FullProfile -> ExtendedProfile -> UserProfile -> BaseUser) The tests verify that embedded struct references are fully resolved with all their members, ensuring the fix in analyzer.go works correctly for multi-level type resolution.
f70818f to
d8fed9d
Compare
Description
Fixes #5402
When using multi-level nesting in type definitions (e.g.,
AgeStructembedsNameStructwhich embedsIdStruct), the TypeScript code generation was losing fields from the deepest embedded structs.Problem
Given the following API definition:
The generated TypeScript was incorrectly missing the
idfield:Root Cause
The type resolution in
fillTypes()did a single pass through types, looking up embedded struct references from the original unresolved type list. This meant multi-level embeddings couldn't resolve properly because nested embedded types were not yet fully resolved when they were referenced.Solution
Implemented iterative type resolution that:
Result
After the fix, the generated TypeScript correctly includes all fields:
Testing
This ensures all embedded struct fields are properly flattened in the generated TypeScript interfaces.