diff --git a/admin/client.go b/admin/client.go index af7ccc0a..374c94fd 100644 --- a/admin/client.go +++ b/admin/client.go @@ -480,6 +480,14 @@ func (c *APIClient) prepareRequest( return nil, err } + // Handle request ID from context (OPTIONAL - only if present) + if requestID, ok := ctx.Value("X-Request-ID").(string); ok && requestID != "" { + if headerParams == nil { + headerParams = make(map[string]string) + } + headerParams["X-Request-ID"] = requestID + } + // add header parameters, if any if len(headerParams) > 0 { headers := http.Header{} @@ -719,3 +727,7 @@ func (u *UntypedClient) CallAPI(request *http.Request) (*http.Response, error) { func (u *UntypedClient) MakeApiError(res *http.Response, httpMethod, httpPath string) error { return u.client.makeApiError(res, httpMethod, httpPath) } + +func WithRequestID(ctx context.Context, requestID string) context.Context { + return context.WithValue(ctx, "X-Request-ID", requestID) +} diff --git a/admin/client_test.go b/admin/client_test.go new file mode 100644 index 00000000..ededf0fa --- /dev/null +++ b/admin/client_test.go @@ -0,0 +1,43 @@ +package admin + +import ( + "context" + "net/http" + "testing" +) + +func TestPrepareRequest_WithRequestID(t *testing.T) { + client := NewAPIClient(NewConfiguration()) + + // Test with request ID in context + ctx := context.WithValue(context.Background(), "X-Request-ID", "test-request-123") + req, err := client.prepareRequest(ctx, "/api/test", "GET", nil, nil, nil, nil, nil) + + if err != nil { + t.Fatalf("PrepareRequest failed: %v", err) + } + + // Check that request ID header was added + requestID := req.Header.Get("X-Request-ID") + if requestID != "test-request-123" { + t.Errorf("Expected X-Request-ID header 'test-request-123', got '%s'", requestID) + } +} + +func TestPrepareRequest_WithoutRequestID(t *testing.T) { + client := NewAPIClient(NewConfiguration()) + + // Test without request ID in context (should work as before) + ctx := context.Background() + req, err := client.prepareRequest(ctx, "/api/test", "GET", nil, nil, nil, nil, nil) + + if err != nil { + t.Fatalf("PrepareRequest failed: %v", err) + } + + // Check that no request ID header was added + requestID := req.Header.Get("X-Request-ID") + if requestID != "" { + t.Errorf("Expected no X-Request-ID header, got '%s'", requestID) + } +} \ No newline at end of file diff --git a/admin/model_api_error.go b/admin/model_api_error.go index 1bb0b947..c887b1b4 100644 --- a/admin/model_api_error.go +++ b/admin/model_api_error.go @@ -219,3 +219,14 @@ func (o *ApiError) HasReason() bool { func (o *ApiError) SetReason(v string) { o.Reason = &v } + +func (a *ApiError) GetContextualError(ctx context.Context) string { + baseError := a.Error() + + // Check if context has request ID + if requestID, ok := ctx.Value("X-Request-ID").(string); ok && requestID != "" { + return fmt.Sprintf("%s (RequestID: %s)", baseError, requestID) + } + + return baseError +} diff --git a/admin/model_api_error_test.go b/admin/model_api_error_test.go new file mode 100644 index 00000000..83939737 --- /dev/null +++ b/admin/model_api_error_test.go @@ -0,0 +1,36 @@ +package admin + +import ( + "context" + "testing" +) + +func TestApiError_GetContextualError(t *testing.T) { + // Create a sample API error + detail := "Project not found" + reason := "Resource not found" + apiError := &ApiError{ + Detail: &detail, + Error: 404, + ErrorCode: "RESOURCE_NOT_FOUND", + Reason: &reason, + } + + // Test with request ID in context + ctx := context.WithValue(context.Background(), "X-Request-ID", "debug-456") + contextualError := apiError.GetContextualError(ctx) + + expected := "404 RESOURCE_NOT_FOUND Resource not found: Project not found (RequestID: debug-456)" + if contextualError != expected { + t.Errorf("Expected: %s\nGot: %s", expected, contextualError) + } + + // Test without request ID in context + ctx = context.Background() + contextualError = apiError.GetContextualError(ctx) + + expected = "404 RESOURCE_NOT_FOUND Resource not found: Project not found" + if contextualError != expected { + t.Errorf("Expected: %s\nGot: %s", expected, contextualError) + } +} \ No newline at end of file diff --git a/openapi/atlas-api.yaml b/openapi/atlas-api.yaml index 515c74d5..daa829a1 100644 --- a/openapi/atlas-api.yaml +++ b/openapi/atlas-api.yaml @@ -2287,6 +2287,7 @@ components: readOnly: true type: object ApiError: + x-is-api-error: true properties: badRequestDetail: $ref: '#/components/schemas/BadRequestDetail' diff --git a/tools/config/go-templates/client.mustache b/tools/config/go-templates/client.mustache index 9ba2d32b..414b9211 100644 --- a/tools/config/go-templates/client.mustache +++ b/tools/config/go-templates/client.mustache @@ -348,6 +348,14 @@ func (c *APIClient) prepareRequest( return nil, err } + // Handle request ID from context (OPTIONAL - only if present) + if requestID, ok := ctx.Value("X-Request-ID").(string); ok && requestID != "" { + if headerParams == nil { + headerParams = make(map[string]string) + } + headerParams["X-Request-ID"] = requestID + } + // add header parameters, if any if len(headerParams) > 0 { headers := http.Header{} @@ -606,3 +614,7 @@ func (u *UntypedClient) CallAPI(request *http.Request) (*http.Response, error) { func (u *UntypedClient) MakeApiError(res *http.Response, httpMethod, httpPath string) error { return u.client.makeApiError(res, httpMethod, httpPath) } + +func WithRequestID(ctx context.Context, requestID string) context.Context { + return context.WithValue(ctx, "X-Request-ID", requestID) +} diff --git a/tools/config/go-templates/model_simple.mustache b/tools/config/go-templates/model_simple.mustache index db76a614..f796c2dd 100644 --- a/tools/config/go-templates/model_simple.mustache +++ b/tools/config/go-templates/model_simple.mustache @@ -1,3 +1,13 @@ +import ( +"encoding/json" +{{#vendorExtensions.x-is-api-error}} + "context" + "fmt" +{{/vendorExtensions.x-is-api-error}} +{{#imports}} + "{{import}}" +{{/imports}} +) // {{classname}} {{{description}}}{{^description}}struct for {{{classname}}}{{/description}} type {{classname}} struct { @@ -178,3 +188,13 @@ func (o *{{{classname}}}) UnmarshalJSON(bytes []byte) (err error) { } {{/isArray}} +{{#vendorExtensions.x-is-api-error}} +// GetContextualError returns the error message with request ID context if available +func (o *{{classname}}) GetContextualError(ctx context.Context) string { + baseError := o.Error() + if requestID, ok := ctx.Value("X-Request-ID").(string); ok && requestID != "" { + return fmt.Sprintf("%s (RequestID: %s)", baseError, requestID) + } + return baseError +} +{{/vendorExtensions.x-is-api-error}}