Skip to content

Conversation

vlboiko
Copy link
Collaborator

@vlboiko vlboiko commented Sep 28, 2025

Description

Brief description of what this PR does.

Type of Change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring (no functional changes)
  • Performance improvement
  • Test coverage improvement

Changes Made

Testing

  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have tested the examples affected by my changes

Documentation

  • I have updated the documentation accordingly
  • I have updated the CHANGELOG.md (if applicable)
  • My changes generate no new warnings in the documentation build

Code Quality

  • My code follows the Go style guidelines
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new lint warnings
  • I have run make fmt and make lint locally

Breaking Changes

If this PR contains breaking changes, please describe them here and how users should adapt their code:

Additional Notes

Any additional information that reviewers should know about this PR.

Related Issues

Fixes #(issue number)
Closes #(issue number)
Relates to #(issue number)

- Create separate TypeRegistry instances for search, request, and response body generation
- Prevents type name conflicts when the same nested objects appear in different schemas
- Ensures consistent naming: ViewsRequestBody_* for request body, ViewResponseBody_* for response body
- Fixes issue where SCHEMA=View and POST=views approaches were producing inconsistent results
- Change usePointers from true to false in generateRequestBodyFromSchema and generateResponseBodyFromSchema
- Now both POST=views and SCHEMA=View approaches generate fields without pointers
- Ensures consistent behavior between URL-based and schema-based generation
- Add toSingularCamelCase function to convert plural resource paths to singular
- Update POST-based generation to use singular names (quotas -> Quota, views -> View)
- Now both POST=quotas and SCHEMA=Quota generate QuotaResponseBody_* (consistent singular)
- Fixes discrepancy where POST generated QuotasResponseBody_* and SCHEMA generated QuotaResponseBody_*
- Added formatGeneratedFiles function that runs go fmt on all generated Go files
- Improved code readability with proper field alignment and consistent spacing
- Added os/exec import for running go fmt command
- Generator now automatically formats all generated files after creation
- Added 'Code generated by generate-typed-resources. DO NOT EDIT.' header to both templates
- All generated files now clearly indicate they are auto-generated
- Follows Go community convention for marking generated code
- Helps prevent accidental manual editing of generated files
Major Changes:
- Add +apibuilder:readOnly marker support for read-only resources
- Rename requestBody → createBody and responseBody → model for clarity
- Add validation to ensure resources have required markers
- Create separate read-only template with only Get*/List*/Exists* methods

Implementation Details:
- Added ReadOnly field to VastResource struct
- Updated apibuilder registry with new marker names (createBody:*, model:*)
- Updated parser to handle both old and new marker names for compatibility
- Added Section field to NestedType for proper template organization
- Updated all generation functions to pass section parameter
- Created readonly-resource.tpl template for read-only resources
- Updated main resource.tpl to use new field names (CreateBody, Model)

Validation:
- Resources must have searchQuery and model markers
- Non-read-only resources must also have createBody marker
- Read-only resources skip createBody validation

Example Usage:
// +apibuilder:readOnly
// +apibuilder:searchQuery:GET=versions
// +apibuilder:model:SCHEMA=Version
type Version struct {
    *VastResource
}

Generated Files:
- version.go: Read-only resource with only Get*/List*/Exists* methods
- quota.go, view.go, vippool.go: Full CRUD resources with CreateBody/Model types
- rest.go: Updated to include all typed resources including Version

Backward Compatibility:
- Old marker names (requestBody, responseBody) still supported
- Existing resources continue to work unchanged
Major Changes:
- Reorganized examples into untyped/ and typed/ directories
- Moved all existing examples to untyped/ directory
- Created comprehensive typed examples for resources with apibuilder markers

Untyped Examples (examples/untyped/):
- All existing examples moved here
- Traditional API using client.Params and Record/RecordSet
- Manual type conversion with Fill() method
- String-based field access

Typed Examples (examples/typed/):
- basic-usage/: Overview of typed client features
- quota/: Full CRUD operations with type safety
- view/: Type-safe view management with NFS protocols
- vippool/: Type-safe VIP pool configuration
- version/: Read-only resource demonstrating compile-time protection
- rest-demo/: General typed client demonstration

Updated Examples:
- Updated existing typed examples to use new naming (CreateBody, Model)
- Enhanced examples with comprehensive CRUD operations
- Added proper error handling and context usage
- Demonstrated read-only resource protection

Documentation:
- Main README.md: Overview of both approaches with migration guide
- untyped/README.md: Detailed guide for traditional API
- typed/README.md: Comprehensive guide for typed API with benefits

Key Features Demonstrated:
- Type safety and compile-time validation
- IDE auto-completion and IntelliSense
- Read-only resource protection
- Automatic field validation
- Context support for all operations
- Migration path from untyped to typed API

Benefits of Typed API:
- Compile-time type checking prevents runtime errors
- Better IDE support with auto-completion
- Self-documenting code with typed structs
- Automatic validation of required fields
- Read-only protection prevents accidental modifications
- Improved maintainability and refactoring support
Major Changes:
- Added SetCtx convenience method to typed VMSRest client
- Updated rest.tpl template to include the new method
- Regenerated typed resources with the new method

Implementation:
- Added context import to rest.tpl template
- Added SetCtx method that delegates to Untyped.SetCtx(ctx)
- Method provides convenient shorthand for setting context

Template Changes:
// SetCtx sets the context for the underlying untyped client
// This is a convenience method to avoid having to call r.Untyped.SetCtx(ctx)
func (r *VMSRest) SetCtx(ctx context.Context) {
    r.Untyped.SetCtx(ctx)
}

Updated Examples:
- Updated all typed examples to use new shorthand method
- Changed from: typedClient.Untyped.SetCtx(ctx)
- Changed to: typedClient.SetCtx(ctx)
- Fixed compilation error in basic-usage example (RecordSet access)
- Updated rest-demo example to use correct function name

Documentation:
- Added convenience methods section to typed README
- Documented the new SetCtx shorthand method
- Provided before/after comparison

Benefits:
- Cleaner, more intuitive API
- Reduces need to access Untyped client directly
- Maintains consistency with typed client design
- Better developer experience

Usage:
// Before (still works)
typedClient.Untyped.SetCtx(ctx)

// After (recommended)
typedClient.SetCtx(ctx)
Major Changes:
- Added GetSession convenience method to typed VMSRest client
- Updated rest.tpl template to include the new method
- Regenerated typed resources with the new method

Implementation:
- Added GetSession method that returns r.Untyped.Session
- Method provides convenient shorthand for accessing REST session

Template Changes:
// GetSession returns the REST session from the underlying untyped client
// This is a convenience method to avoid having to call r.Untyped.Session
func (r *VMSRest) GetSession() vast_client.RESTSession {
    return r.Untyped.Session
}

Updated Examples:
- Enhanced basic-usage example to demonstrate GetSession usage
- Shows how to access session configuration through the shorthand
- Fixed variable naming conflicts in the example

Documentation:
- Added GetSession to convenience methods section in typed README
- Provided before/after comparison for the new method
- Documented proper usage with session.GetConfig()

Benefits:
- Cleaner API for accessing session information
- Reduces need to access Untyped client directly
- Consistent with other convenience methods
- Better developer experience

Usage:
// Before (still works)
session := typedClient.Untyped.Session

// After (recommended)
session := typedClient.GetSession()

// Access session configuration
config := session.GetConfig()
fmt.Printf("Host: %s, Username: %s", config.Host, config.Username)
- Restored accidentally deleted README files from examples/
- Added GetSession method documentation to typed README
- Updated convenience methods section with both SetCtx and GetSession
- Provided usage examples for the new shorthand method
Root Cause:
- NewParamsFromStruct panicked when passed nil due to reflect.Value.Field()
  being called on a zero reflect.Value
- User passed nil to versionClient.List(nil) causing the panic

Fixes Applied:

1. Fixed structToMap function (utils.go):
   - Prevents panic when calling Field() on zero reflect.Value
   - Returns empty map for nil inputs gracefully

2. Enhanced typed resource methods:
   - Added nil checks in all SearchParams methods
   - Automatically provides empty struct when nil is passed
   - Applied to both regular and read-only resource templates

3. Updated templates:
   - resource.tpl: Added nil checks to Get, List, Delete, Exists, Ensure, MustExists methods
   - readonly-resource.tpl: Added nil checks to Get, List, Exists, MustExists methods
   - All methods now handle: if req == nil { req = &{{.Name}}SearchParams{} }

4. Regenerated all typed resources with the fixes

Benefits:
- No more panics when passing nil to typed resource methods
- More user-friendly API - nil parameters work as expected
- Backward compatible - existing code continues to work
- Consistent behavior across all typed resource methods

Usage Examples:
// These all work now without panics:
versions, err := versionClient.List(nil)                    // ✅ Works
versions, err := versionClient.List(&typed.VersionSearchParams{}) // ✅ Works
quotas, err := quotaClient.List(nil)                        // ✅ Works

The fix ensures that typed resources are more robust and user-friendly
while maintaining type safety and the existing API contract.
Instead of adding nil checks in every typed resource method,
the fix is now centralized in NewParamsFromStruct() function.

Changes:
- Modified NewParamsFromStruct() in serde.go to return empty Params when obj is nil
- Removed all nil checks from resource templates (resource.tpl, readonly-resource.tpl)
- Regenerated all typed resources with clean templates
- The centralized approach is much cleaner and more maintainable

Benefits:
- Single point of nil handling instead of scattered checks
- Cleaner generated code without repetitive nil checks
- Same functionality - nil parameters work without panics
- More maintainable - changes to nil handling logic only need to be made in one place

Usage:
// These all work without panics:
versions, err := versionClient.List(nil)                    // ✅ Works
quotas, err := quotaClient.List(nil)                        // ✅ Works
Added production-ready interceptor examples based on working implementation:

New Examples:
1. examples/untyped/request-interceptors-with-id/main.go
   - Advanced interceptors with unique request ID generation
   - Atomic counter-based ID generation for thread safety
   - Context-based request ID propagation
   - Smart body logging (skips null/empty bodies)
   - Concurrent request demonstration

2. examples/typed/request-interceptors-with-id/main.go
   - Same features as untyped but with typed client
   - Demonstrates SetCtx() method usage
   - Type-safe request/response structures
   - Typed search parameters and create bodies

3. examples/INTERCEPTORS.md
   - Comprehensive documentation for all interceptor patterns
   - Usage examples and best practices
   - Integration guidance for logging frameworks

Improvements to Existing Examples:
- Updated examples/untyped/request-interceptors/main.go
- Fixed null body logging issue that was printing 'null'
- Added smart body detection and JSON compacting
- Better error handling and logging format

Key Features:
✓ Unique request ID generation using atomic counters
✓ Thread-safe concurrent request tracking
✓ Context-based ID propagation
✓ Smart body logging (skips null/empty)
✓ JSON compacting for readability
✓ Before/After request correlation
✓ Production-ready error handling
✓ Both typed and untyped client examples

Usage Pattern:
// Create context with request ID
ctx := ContextWithRequestID(context.Background())

// For typed client
typedClient.SetCtx(ctx)
result, err := typedClient.Resource.Method(params)

// For untyped client
result, err := client.Resource.MethodWithContext(ctx, params)

The interceptors automatically log with request ID correlation,
making it easy to track request/response pairs in production logs.
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.

1 participant