diff --git a/examples/circular/mutual/gen.go b/examples/circular/mutual/gen.go index a4c52d96..fa223b3f 100644 --- a/examples/circular/mutual/gen.go +++ b/examples/circular/mutual/gen.go @@ -60,8 +60,14 @@ func (c *Client) GetFiles(ctx context.Context, reqEditors ...runtime.RequestEdit return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFilesResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/client/example1/example1/client.go b/examples/client/example1/example1/client.go index 6c1ce9db..0f9c500c 100644 --- a/examples/client/example1/example1/client.go +++ b/examples/client/example1/example1/client.go @@ -56,7 +56,14 @@ func (c *Client) GetClient(ctx context.Context, options *GetClientRequestOptions // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { - return nil, fmt.Errorf("error decoding response: %w", err) + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetClientErrorResponse", + Body: bodyBytes, + Err: err, + } } } // Return error with (possibly empty) target @@ -72,8 +79,14 @@ func (c *Client) GetClient(ctx context.Context, options *GetClientRequestOptions return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetClientResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } @@ -106,7 +119,14 @@ func (c *Client) UpdateClient(ctx context.Context, options *UpdateClientRequestO // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { - return nil, fmt.Errorf("error decoding response: %w", err) + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "UpdateClientErrorResponseJSON", + Body: bodyBytes, + Err: err, + } } } // Return error with (possibly empty) target diff --git a/examples/client/example2-with-payload-encoding/example2/client.go b/examples/client/example2-with-payload-encoding/example2/client.go index 9ed3de83..917a64ef 100644 --- a/examples/client/example2-with-payload-encoding/example2/client.go +++ b/examples/client/example2-with-payload-encoding/example2/client.go @@ -67,8 +67,14 @@ func (c *Client) CreateOrder(ctx context.Context, options *CreateOrderRequestOpt return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateOrderResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/client/example3-with-response-encoded/example3/client.go b/examples/client/example3-with-response-encoded/example3/client.go index 87ca7de6..15d750f5 100644 --- a/examples/client/example3-with-response-encoded/example3/client.go +++ b/examples/client/example3-with-response-encoded/example3/client.go @@ -66,8 +66,14 @@ func (c *Client) GetUserSingle(ctx context.Context, reqEditors ...runtime.Reques return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetUserSingleResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } @@ -105,8 +111,14 @@ func (c *Client) GetUserUnion1(ctx context.Context, reqEditors ...runtime.Reques return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetUserUnion1Response", + Body: bodyBytes, + Err: err, + } } return target, nil } @@ -144,8 +156,14 @@ func (c *Client) GetUserUnion2(ctx context.Context, reqEditors ...runtime.Reques return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetUserUnion2Response", + Body: bodyBytes, + Err: err, + } } return target, nil } @@ -183,8 +201,14 @@ func (c *Client) GetUserUnion3(ctx context.Context, reqEditors ...runtime.Reques return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetUserUnion3Response", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/client/example4-with-params/gen.go b/examples/client/example4-with-params/gen.go index 1bdeedb0..d2d4ff05 100644 --- a/examples/client/example4-with-params/gen.go +++ b/examples/client/example4-with-params/gen.go @@ -65,8 +65,14 @@ func (c *Client) GetOrder(ctx context.Context, options *GetOrderRequestOptions, return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetOrderResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/client/example5-query-explode-false/gen.go b/examples/client/example5-query-explode-false/gen.go index 99475f1f..74cfbb07 100644 --- a/examples/client/example5-query-explode-false/gen.go +++ b/examples/client/example5-query-explode-false/gen.go @@ -65,8 +65,14 @@ func (c *Client) GetCharge(ctx context.Context, options *GetChargeRequestOptions return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetChargeResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/client/example6-json-payloads/example6/client.go b/examples/client/example6-json-payloads/example6/client.go index da2829e9..c8e48a0e 100644 --- a/examples/client/example6-json-payloads/example6/client.go +++ b/examples/client/example6-json-payloads/example6/client.go @@ -55,7 +55,14 @@ func (c *Client) UpdateUser(ctx context.Context, options *UpdateUserRequestOptio // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { - return nil, fmt.Errorf("error decoding response: %w", err) + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "UpdateUserErrorResponse", + Body: bodyBytes, + Err: err, + } } } // Return error with (possibly empty) target @@ -71,8 +78,14 @@ func (c *Client) UpdateUser(ctx context.Context, options *UpdateUserRequestOptio return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "UpdateUserResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/client/request-options-collision/gen.go b/examples/client/request-options-collision/gen.go index ab2f0bc9..832d66ef 100644 --- a/examples/client/request-options-collision/gen.go +++ b/examples/client/request-options-collision/gen.go @@ -60,8 +60,14 @@ func (c *Client) GetTest1(ctx context.Context, options *GetTest1RequestOptions, return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetTestResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/custom-client-type/customclienttype/client.go b/examples/custom-client-type/customclienttype/client.go index 45d12a55..678c7958 100644 --- a/examples/custom-client-type/customclienttype/client.go +++ b/examples/custom-client-type/customclienttype/client.go @@ -58,8 +58,14 @@ func (c *CustomClientType) GetClient(ctx context.Context, reqEditors ...runtime. return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetClientResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/deep-path-refs/gen.go b/examples/deep-path-refs/gen.go index 55918bfe..1093f3bc 100644 --- a/examples/deep-path-refs/gen.go +++ b/examples/deep-path-refs/gen.go @@ -67,8 +67,14 @@ func (c *Client) GetUser(ctx context.Context, options *GetUserRequestOptions, re return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetUserResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } @@ -105,8 +111,14 @@ func (c *Client) GetPost(ctx context.Context, options *GetPostRequestOptions, re return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPostResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } @@ -142,8 +154,14 @@ func (c *Client) ListComments(ctx context.Context, reqEditors ...runtime.Request return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListCommentsResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } @@ -181,8 +199,14 @@ func (c *Client) CreateEvent(ctx context.Context, options *CreateEventRequestOpt return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateEventResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/extensions/xgoname/gen.go b/examples/extensions/xgoname/gen.go index b8672532..61f694fa 100644 --- a/examples/extensions/xgoname/gen.go +++ b/examples/extensions/xgoname/gen.go @@ -61,8 +61,14 @@ func (c *CustomClientName) CreateClient(ctx context.Context, options *CreateClie return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateClientResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/filtering/by-components/gen.go b/examples/filtering/by-components/gen.go index fb866001..779398b0 100644 --- a/examples/filtering/by-components/gen.go +++ b/examples/filtering/by-components/gen.go @@ -56,7 +56,14 @@ func (c *Client) CreateOrder(ctx context.Context, options *CreateOrderRequestOpt // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { - return nil, fmt.Errorf("error decoding response: %w", err) + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateOrderErrorResponse", + Body: bodyBytes, + Err: err, + } } } // Return error with (possibly empty) target @@ -72,8 +79,14 @@ func (c *Client) CreateOrder(ctx context.Context, options *CreateOrderRequestOpt return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateOrderResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/filtering/by-extension/gen.go b/examples/filtering/by-extension/gen.go index c24c31a0..3ba3e3fb 100644 --- a/examples/filtering/by-extension/gen.go +++ b/examples/filtering/by-extension/gen.go @@ -59,8 +59,14 @@ func (c *Client) GetClient(ctx context.Context, reqEditors ...runtime.RequestEdi return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetClientResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/filtering/by-path/gen.go b/examples/filtering/by-path/gen.go index 3250f18f..c593242b 100644 --- a/examples/filtering/by-path/gen.go +++ b/examples/filtering/by-path/gen.go @@ -59,8 +59,14 @@ func (c *Client) GetClient(ctx context.Context, reqEditors ...runtime.RequestEdi return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetClientResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/filtering/by-property/gen.go b/examples/filtering/by-property/gen.go index 9415fcf9..570523ac 100644 --- a/examples/filtering/by-property/gen.go +++ b/examples/filtering/by-property/gen.go @@ -67,8 +67,14 @@ func (c *Client) GetUsers(ctx context.Context, reqEditors ...runtime.RequestEdit return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetUsersResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } @@ -102,7 +108,14 @@ func (c *Client) CreateUser(ctx context.Context, options *CreateUserRequestOptio // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { - return nil, fmt.Errorf("error decoding response: %w", err) + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateUserErrorResponse", + Body: bodyBytes, + Err: err, + } } } // Return error with (possibly empty) target @@ -118,8 +131,14 @@ func (c *Client) CreateUser(ctx context.Context, options *CreateUserRequestOptio return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateUserResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } @@ -157,8 +176,14 @@ func (c *Client) GetUser(ctx context.Context, options *GetUserRequestOptions, re return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetUserResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/filtering/by-tag/gen.go b/examples/filtering/by-tag/gen.go index f641ee8b..731bc229 100644 --- a/examples/filtering/by-tag/gen.go +++ b/examples/filtering/by-tag/gen.go @@ -61,8 +61,14 @@ func (c *Client) GetPurchases(ctx context.Context, reqEditors ...runtime.Request return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPurchasesResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } @@ -98,8 +104,14 @@ func (c *Client) GetPurchase(ctx context.Context, reqEditors ...runtime.RequestE return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPurchaseResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/mcp/gen/client.go b/examples/mcp/gen/client.go index 1d422f35..1e7e1c79 100644 --- a/examples/mcp/gen/client.go +++ b/examples/mcp/gen/client.go @@ -75,8 +75,14 @@ func (c *Client) HealthCheck(ctx context.Context, reqEditors ...runtime.RequestE return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "HealthCheckResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } @@ -114,8 +120,14 @@ func (c *Client) ListUsers(ctx context.Context, options *ListUsersRequestOptions return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListUsersResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } @@ -149,7 +161,14 @@ func (c *Client) CreateUser(ctx context.Context, options *CreateUserRequestOptio // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { - return nil, fmt.Errorf("error decoding response: %w", err) + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateUserErrorResponse", + Body: bodyBytes, + Err: err, + } } } // Return error with (possibly empty) target @@ -165,8 +184,14 @@ func (c *Client) CreateUser(ctx context.Context, options *CreateUserRequestOptio return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateUserResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } @@ -199,7 +224,14 @@ func (c *Client) GetUser(ctx context.Context, options *GetUserRequestOptions, re // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { - return nil, fmt.Errorf("error decoding response: %w", err) + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetUserErrorResponse", + Body: bodyBytes, + Err: err, + } } } // Return error with (possibly empty) target @@ -215,8 +247,14 @@ func (c *Client) GetUser(ctx context.Context, options *GetUserRequestOptions, re return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetUserResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } @@ -282,8 +320,14 @@ func (c *Client) GetMetrics(ctx context.Context, reqEditors ...runtime.RequestEd return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetMetricsResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/optional-properties/gen.go b/examples/optional-properties/gen.go index 20d752eb..ae6fc4f0 100644 --- a/examples/optional-properties/gen.go +++ b/examples/optional-properties/gen.go @@ -63,8 +63,14 @@ func (c *Client) PostPayments(ctx context.Context, options *PostPaymentsRequestO return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "PostPaymentsResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/overlays/basic/gen.go b/examples/overlays/basic/gen.go index e4bca5a6..bac5bec9 100644 --- a/examples/overlays/basic/gen.go +++ b/examples/overlays/basic/gen.go @@ -66,8 +66,14 @@ func (c *Client) GetUsers(ctx context.Context, reqEditors ...runtime.RequestEdit return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetUsersResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } @@ -106,8 +112,14 @@ func (c *Client) CreateUser(ctx context.Context, options *CreateUserRequestOptio return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateUserResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/responses/array-same-name/gen.go b/examples/responses/array-same-name/gen.go index 4ea202be..270bb369 100644 --- a/examples/responses/array-same-name/gen.go +++ b/examples/responses/array-same-name/gen.go @@ -59,8 +59,14 @@ func (c *Client) GetBusinessGroups(ctx context.Context, reqEditors ...runtime.Re return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetBusinessGroupsResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/responses/array/gen.go b/examples/responses/array/gen.go index dd095884..646951de 100644 --- a/examples/responses/array/gen.go +++ b/examples/responses/array/gen.go @@ -60,8 +60,14 @@ func (c *Client) GetFiles(ctx context.Context, reqEditors ...runtime.RequestEdit return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFilesResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/responses/arrayarray/gen.go b/examples/responses/arrayarray/gen.go index 7be191b4..98eea004 100644 --- a/examples/responses/arrayarray/gen.go +++ b/examples/responses/arrayarray/gen.go @@ -59,8 +59,14 @@ func (c *Client) GetTest(ctx context.Context, reqEditors ...runtime.RequestEdito return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetTestResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/responses/conflicting/gen.go b/examples/responses/conflicting/gen.go index e3e39134..1cd16469 100644 --- a/examples/responses/conflicting/gen.go +++ b/examples/responses/conflicting/gen.go @@ -63,8 +63,14 @@ func (c *Client) CreatePayment(ctx context.Context, options *CreatePaymentReques return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreatePaymentResponse1", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/responses/empty-error/gen.go b/examples/responses/empty-error/gen.go index ff2803d2..fb770305 100644 --- a/examples/responses/empty-error/gen.go +++ b/examples/responses/empty-error/gen.go @@ -58,7 +58,14 @@ func (c *Client) CreateUser(ctx context.Context, options *CreateUserRequestOptio // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { - return nil, fmt.Errorf("error decoding response: %w", err) + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateUserErrorResponse", + Body: bodyBytes, + Err: err, + } } } // Return error with (possibly empty) target @@ -74,8 +81,14 @@ func (c *Client) CreateUser(ctx context.Context, options *CreateUserRequestOptio return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateUserResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/responses/error-mapping/ex1/gen.go b/examples/responses/error-mapping/ex1/gen.go index fa012560..0959fe16 100644 --- a/examples/responses/error-mapping/ex1/gen.go +++ b/examples/responses/error-mapping/ex1/gen.go @@ -54,7 +54,14 @@ func (c *Client) GetFiles(ctx context.Context, reqEditors ...runtime.RequestEdit // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { - return nil, fmt.Errorf("error decoding response: %w", err) + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFilesErrorResponse", + Body: bodyBytes, + Err: err, + } } } // Return error with (possibly empty) target @@ -70,8 +77,14 @@ func (c *Client) GetFiles(ctx context.Context, reqEditors ...runtime.RequestEdit return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFilesResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/responses/error-mapping/from-aliased/gen.go b/examples/responses/error-mapping/from-aliased/gen.go index 61e436ea..82bcecb3 100644 --- a/examples/responses/error-mapping/from-aliased/gen.go +++ b/examples/responses/error-mapping/from-aliased/gen.go @@ -54,7 +54,14 @@ func (c *Client) GetFiles(ctx context.Context, reqEditors ...runtime.RequestEdit // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { - return nil, fmt.Errorf("error decoding response: %w", err) + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFilesErrorResponse", + Body: bodyBytes, + Err: err, + } } } // Return error with (possibly empty) target @@ -70,8 +77,14 @@ func (c *Client) GetFiles(ctx context.Context, reqEditors ...runtime.RequestEdit return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFilesResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/responses/error-mapping/from-array/gen.go b/examples/responses/error-mapping/from-array/gen.go index e8c21f12..5a5bd0ca 100644 --- a/examples/responses/error-mapping/from-array/gen.go +++ b/examples/responses/error-mapping/from-array/gen.go @@ -54,7 +54,14 @@ func (c *Client) GetFiles(ctx context.Context, reqEditors ...runtime.RequestEdit // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { - return nil, fmt.Errorf("error decoding response: %w", err) + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFilesErrorResponse", + Body: bodyBytes, + Err: err, + } } } // Return error with (possibly empty) target @@ -70,8 +77,14 @@ func (c *Client) GetFiles(ctx context.Context, reqEditors ...runtime.RequestEdit return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFilesResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/responses/error-mapping/name-replacement/gen.go b/examples/responses/error-mapping/name-replacement/gen.go index 9423f288..c1081443 100644 --- a/examples/responses/error-mapping/name-replacement/gen.go +++ b/examples/responses/error-mapping/name-replacement/gen.go @@ -54,7 +54,14 @@ func (c *Client) GetFiles(ctx context.Context, reqEditors ...runtime.RequestEdit // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { - return nil, fmt.Errorf("error decoding response: %w", err) + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFilesErrorResponse", + Body: bodyBytes, + Err: err, + } } } // Return error with (possibly empty) target @@ -70,8 +77,14 @@ func (c *Client) GetFiles(ctx context.Context, reqEditors ...runtime.RequestEdit return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFilesResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/examples/responses/multiple/multiple/client.go b/examples/responses/multiple/multiple/client.go index d0fbe33a..7562d1fd 100644 --- a/examples/responses/multiple/multiple/client.go +++ b/examples/responses/multiple/multiple/client.go @@ -55,7 +55,14 @@ func (c *Client) CreateBooking(ctx context.Context, options *CreateBookingReques // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { - return nil, fmt.Errorf("error decoding response: %w", err) + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateBookingErrorResponse", + Body: bodyBytes, + Err: err, + } } } // Return error with (possibly empty) target @@ -71,8 +78,14 @@ func (c *Client) CreateBooking(ctx context.Context, options *CreateBookingReques return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateBookingResponse", + Body: bodyBytes, + Err: err, + } } return target, nil } diff --git a/pkg/codegen/templates/client.tmpl b/pkg/codegen/templates/client.tmpl index c5b3fb63..025baa2b 100644 --- a/pkg/codegen/templates/client.tmpl +++ b/pkg/codegen/templates/client.tmpl @@ -138,7 +138,14 @@ responseParser := func(ctx context.Context, resp *runtime.Response) (*{{$op.Resp // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { - return nil, fmt.Errorf("error decoding response: %w", err) + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "{{ .ResponseName }}", + Body: bodyBytes, + Err: err, + } } } // Return error with (possibly empty) target @@ -172,8 +179,14 @@ responseParser := func(ctx context.Context, resp *runtime.Response) (*{{$op.Resp return target, nil } if err = json.Unmarshal(bodyBytes, target); err != nil { - err = fmt.Errorf("error decoding response: %w", err) - return nil, err + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "{{ $respName }}", + Body: bodyBytes, + Err: err, + } } return target, nil {{ end -}} diff --git a/pkg/runtime/errors.go b/pkg/runtime/errors.go index f4c2cf3f..aad62ea9 100644 --- a/pkg/runtime/errors.go +++ b/pkg/runtime/errors.go @@ -26,6 +26,28 @@ var ( ErrRequestBodyEmpty = errors.New("request body is empty") ) +// ResponseDecodeError is returned when the client fails to decode (unmarshal) +// an HTTP response body. It provides structured context for debugging. +// Note: Body is available for programmatic access but is intentionally excluded +// from Error() to avoid leaking sensitive data into logs. +type ResponseDecodeError struct { + StatusCode int // HTTP status code of the response + ContentType string // Content-Type header of the response + ContentLength int // size of the response body in bytes + TargetType string // name of the Go type we attempted to unmarshal into + Body []byte // raw response body - excluded from Error() to avoid PII leakage + Err error // underlying unmarshal error +} + +func (e *ResponseDecodeError) Error() string { + return fmt.Sprintf("error decoding response: status=%d, content-type=%s, content-length=%d, target-type=%s: %s", + e.StatusCode, e.ContentType, e.ContentLength, e.TargetType, e.Err) +} + +func (e *ResponseDecodeError) Unwrap() error { + return e.Err +} + type ClientAPIErrorOption func(*ClientAPIError) // ClientAPIError represents type for client API errors. diff --git a/pkg/runtime/errors_test.go b/pkg/runtime/errors_test.go index fff59885..7950a9b3 100644 --- a/pkg/runtime/errors_test.go +++ b/pkg/runtime/errors_test.go @@ -12,6 +12,7 @@ package runtime import ( "errors" + "fmt" "testing" "github.com/go-playground/validator/v10" @@ -19,6 +20,51 @@ import ( "github.com/stretchr/testify/require" ) +func TestResponseDecodeError(t *testing.T) { + underlyingErr := errors.New("json: cannot unmarshal string into Go struct field Foo.bar of type int") + body := []byte(`{"secret":"hunter2","ssn":"123-45-6789"}`) + + decodeErr := &ResponseDecodeError{ + StatusCode: 200, + ContentType: "application/json", + ContentLength: len(body), + TargetType: "MyResponse", + Body: body, + Err: underlyingErr, + } + + t.Run("Error includes metadata but not body", func(t *testing.T) { + errMsg := decodeErr.Error() + + assert.Contains(t, errMsg, "status=200") + assert.Contains(t, errMsg, "content-type=application/json") + assert.Contains(t, errMsg, fmt.Sprintf("content-length=%d", len(body))) + assert.Contains(t, errMsg, "target-type=MyResponse") + assert.Contains(t, errMsg, underlyingErr.Error()) + + // Body must not leak into Error() output + assert.NotContains(t, errMsg, "hunter2") + assert.NotContains(t, errMsg, "123-45-6789") + }) + + t.Run("Unwrap returns underlying error", func(t *testing.T) { + assert.Equal(t, underlyingErr, decodeErr.Unwrap()) + assert.True(t, errors.Is(decodeErr, underlyingErr)) + }) + + t.Run("errors.As exposes all fields including body", func(t *testing.T) { + wrapped := fmt.Errorf("operation failed: %w", decodeErr) + + var target *ResponseDecodeError + require.True(t, errors.As(wrapped, &target)) + assert.Equal(t, 200, target.StatusCode) + assert.Equal(t, "application/json", target.ContentType) + assert.Equal(t, len(body), target.ContentLength) + assert.Equal(t, "MyResponse", target.TargetType) + assert.Equal(t, body, target.Body) + }) +} + func TestNewClientAPIError(t *testing.T) { t.Run("nil error", func(t *testing.T) { err := NewClientAPIError(nil)