Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion docs/protocols/http_client.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,35 @@ const response = await postPingPostRequest({
});
```

## Error Handling

Non-OK HTTP responses **throw** a typed `HttpError` instead of returning. `HttpError` extends the built-in `Error` and carries the HTTP `status`, `statusText`, and the parsed response `body`:

```typescript
export class HttpError extends Error {
status: number;
statusText: string;
body?: unknown; // the parsed JSON error body, when present
}
```

Consume it with an `instanceof` check:

```typescript
import { getGetUser, HttpError } from './__gen__/channels/http_client';

try {
const response = await getGetUser({ baseUrl: 'https://api.example.com' });
// response.data is the typed, unmarshalled success payload
} catch (error) {
if (error instanceof HttpError) {
console.error(error.status); // e.g. 404
console.error(error.statusText); // e.g. 'Not Found'
console.error(error.body); // parsed error body (unknown)
}
}
```

## Request/Response Hooks

Customize request behavior with hooks:
Expand Down Expand Up @@ -451,4 +480,4 @@ if (response.status === 200) {
} else if (response.status === 404) {
console.log('Not found:', response.data); // NotFoundError
}
```
```
32 changes: 26 additions & 6 deletions examples/openapi-http-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,42 @@ import {GetV2ConnectReferenceIdParameters} from './generated/parameter/GetV2Conn

// Body: build it with the generated request model, pass as `payload`.
const body = new PostV2ConnectRequest({returnUrl: 'https://shop.example/return'});
const created = await http_client.postV2Connect({server, payload: body});
const created = await http_client.postV2Connect({baseUrl: server, payload: body});
created.data.connectUrl; // typed

// Path parameters: supply them through the generated parameter model.
const params = new GetV2ConnectReferenceIdParameters({referenceId: 'ref_123'});
const connect = await http_client.getV2ConnectReferenceId({server, parameters: params});
const connect = await http_client.getV2ConnectReferenceId({baseUrl: server, parameters: params});
connect.data.safepayAccountId; // typed

// ...or, more ergonomically, pass a plain object — every parameter model also
// exports a companion interface, and channels accept `Interface | Class` and
// normalize internally. No need to construct the model yourself:
await http_client.getV2ConnectReferenceId({server, parameters: {referenceId: 'ref_123'}});
await http_client.getV2ConnectReferenceId({baseUrl: server, parameters: {referenceId: 'ref_123'}});
```

Every function returns `HttpClientResponse<T>` where `data` is the unmarshalled, typed response model, alongside `status`, `headers`, and pagination helpers.

## Error handling

Non-OK responses **throw** a typed `HttpError` (exported from `generated/http_client.ts`) carrying the `status`, `statusText`, and the parsed response `body`:

```ts
import {http_client} from './generated';

try {
await http_client.getV2ConnectReferenceId({baseUrl: server, parameters: {referenceId: 'missing'}});
} catch (error) {
if (error instanceof http_client.HttpError) {
error.status; // e.g. 404
error.statusText; // e.g. 'Not Found'
error.body; // parsed JSON error body (unknown)
}
}
```

The explicit `handleHttpError` cases are generated from the **error status codes the OpenAPI document declares** — `safepay-nordic-sample.json` declares `400` and `404`, so those throw with the standard reason-phrase message (`'Bad Request'`, `'Not Found'`). Any status code the document does not declare falls through to a generic `HTTP Error: <status> <statusText>` message. `HttpError` still flows through the `onError` hook and the retry logic unchanged.

## About the generated function names

The operations in this document have **no `operationId`**, so names are synthesized from the HTTP method and the path, preserving word boundaries:
Expand Down Expand Up @@ -84,13 +104,13 @@ Call sites, before and after:

```ts
// organization: 'flat' (default)
await http_client.postV2Connect({server, payload});
await http_client.postV2Connect({baseUrl: server, payload});

// organization: 'tag' — grouped under the operation's OpenAPI tag (tag & leaf verbatim; here the tag is "Connect")
await http_client.Connect.postV2Connect({server, payload});
await http_client.Connect.postV2Connect({baseUrl: server, payload});

// organization: 'path' — nested by URL path segments, HTTP method as the leaf
await http_client.v2.connect.post({server, payload});
await http_client.v2.connect.post({baseUrl: server, payload});
```

See the [channels generator docs](https://the-codegen-project.org/docs/generators/channels) for the full description of each style.
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import {PostV2ConnectRequest} from './../payload/PostV2ConnectRequest';
import {PostV2ConnectResponse_200} from './../payload/PostV2ConnectResponse_200';
import {GetV2ConnectReferenceIdResponse_200} from './../payload/GetV2ConnectReferenceIdResponse_200';
import {GetV2UsersSafepayAccountIdBankAccountsResponse_200} from './../payload/GetV2UsersSafepayAccountIdBankAccountsResponse_200';
import {PostV2ConnectRequest, PostV2ConnectRequestInterface} from './../payload/PostV2ConnectRequest';
import {PostV2ConnectResponse_200, PostV2ConnectResponse_200Interface} from './../payload/PostV2ConnectResponse_200';
import {GetV2ConnectReferenceIdResponse_200, GetV2ConnectReferenceIdResponse_200Interface} from './../payload/GetV2ConnectReferenceIdResponse_200';
import {GetV2UsersSafepayAccountIdBankAccountsResponse_200, GetV2UsersSafepayAccountIdBankAccountsResponse_200Interface} from './../payload/GetV2UsersSafepayAccountIdBankAccountsResponse_200';
import {Status} from './../payload/Status';
import {BankAccountsItem} from './../payload/BankAccountsItem';
import {InitializeRequest} from './../payload/InitializeRequest';
import {InitializeModel} from './../payload/InitializeModel';
import {GetConnectModel} from './../payload/GetConnectModel';
import {BankAccount} from './../payload/BankAccount';
import {BankAccount, BankAccountInterface} from './../payload/BankAccount';
import {InitializeRequest, InitializeRequestInterface} from './../payload/InitializeRequest';
import {InitializeModel, InitializeModelInterface} from './../payload/InitializeModel';
import {GetConnectModel, GetConnectModelInterface} from './../payload/GetConnectModel';
export {PostV2ConnectRequest};
export {PostV2ConnectResponse_200};
export {GetV2ConnectReferenceIdResponse_200};
export {GetV2UsersSafepayAccountIdBankAccountsResponse_200};
export {Status};
export {BankAccountsItem};
export {BankAccount};
export {InitializeRequest};
export {InitializeModel};
export {GetConnectModel};
export {BankAccount};
import {GetV2ConnectReferenceIdParameters, GetV2ConnectReferenceIdParametersInterface} from './../parameter/GetV2ConnectReferenceIdParameters';
import {GetV2UsersSafepayAccountIdBankAccountsParameters, GetV2UsersSafepayAccountIdBankAccountsParametersInterface} from './../parameter/GetV2UsersSafepayAccountIdBankAccountsParameters';
export {GetV2ConnectReferenceIdParameters};
Expand All @@ -30,7 +28,7 @@ import * as http_client from './../http_client';
* @class SafepayApiV2SampleClient
*
* A fully-typed HTTP client for the Safepay API V2 (sample). Construct it once with the shared request configuration
* (server, auth, hooks, ...) and call the operation methods; every method
* (baseUrl, auth, hooks, ...) and call the operation methods; every method
* forwards to the underlying channel function with that configuration applied.
*/
export class SafepayApiV2SampleClient {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,61 +1,14 @@
import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv';
import {default as addFormats} from 'ajv-formats';
class PostV2ConnectHeaders {
private _xCorrelationId?: string;

constructor(input: {
xCorrelationId?: string,
}) {
this._xCorrelationId = input.xCorrelationId;
}

interface PostV2ConnectHeaders {
/**
* Correlation ID used for logging.
*/
get xCorrelationId(): string | undefined { return this._xCorrelationId; }
set xCorrelationId(xCorrelationId: string | undefined) { this._xCorrelationId = xCorrelationId; }

public marshal() : string {
let json = '{'
if(this.xCorrelationId !== undefined) {
json += `"X-Correlation-Id": ${typeof this.xCorrelationId === 'number' || typeof this.xCorrelationId === 'boolean' ? this.xCorrelationId : JSON.stringify(this.xCorrelationId)},`;
}

//Remove potential last comma
return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`;
}

public static unmarshal(json: string | object): PostV2ConnectHeaders {
const obj = typeof json === "object" ? json : JSON.parse(json);
const instance = new PostV2ConnectHeaders({} as any);

if (obj["X-Correlation-Id"] !== undefined) {
instance.xCorrelationId = obj["X-Correlation-Id"];
}


return instance;
}
public static theCodeGenSchema = {"type":"object","additionalProperties":false,"properties":{"X-Correlation-Id":{"type":"string","minLength":20,"maxLength":64,"description":"Correlation ID used for logging."}},"$id":"PostV2ConnectHeaders","$schema":"http://json-schema.org/draft-07/schema"};
public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } {
const {data, ajvValidatorFunction} = context ?? {};
// Intentionally parse JSON strings to support validation of marshalled output.
// Example: validate({data: marshal(obj)}) works because marshal returns JSON string.
// Note: String 'true' will be coerced to boolean true due to JSON.parse.
const parsedData = typeof data === 'string' ? JSON.parse(data) : data;
const validate = ajvValidatorFunction ?? this.createValidator(context)
return {
valid: validate(parsedData),
errors: validate.errors ?? undefined,
};
}
public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction {
const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})};
addFormats(ajvInstance);
ajvInstance.addVocabulary(["xml", "example"])
const validate = ajvInstance.compile(this.theCodeGenSchema);
return validate;
}

xMinusCorrelationMinusId?: string;
}
export { PostV2ConnectHeaders };
export { PostV2ConnectHeaders };

export function serializePostV2ConnectHeadersHeaders(headers: PostV2ConnectHeaders): Record<string, string> {
const result: Record<string, string> = {};
if (headers.xMinusCorrelationMinusId !== undefined) { result['X-Correlation-Id'] = String(headers.xMinusCorrelationMinusId); }
return result;
}
Loading
Loading