# jg-api-response

> Build and recognise the success, data, and error envelope that services exchange over HTTP, with errors as a code, a message, and data.

- **Version:** `1.1.1`
- **Runtimes:** Universal
- **Install:** `npm install jg-api-response`
- **On npm:** [jg-api-response](https://www.npmjs.com/package/jg-api-response)
- **Source:** [packages/jg-api-response](https://github.com/jesusgraterol/jg-packages/tree/main/packages/jg-api-response)
- **Recipes:** [jg-api-response recipes](https://jg-packages.jesusgraterol.dev/packages/jg-api-response/recipes/index.md)

## Usage

Build the body a service responds with. A response is successful when no error is passed:

```ts
import { buildResponse } from 'jg-api-response';

buildResponse({ id: 1 });
// { success: true, data: { id: 1 }, error: null }
```

A failed response carries the error as `{ code, message, data }`, so the client gets everything it needs to handle or debug it. An `Exception` from `jg-exception` keeps its code, message, and data:

```ts
import { Exception } from 'jg-exception';

buildResponse(
  undefined,
  new Exception('The user was not found.', { code: 'USER_NOT_FOUND', data: { id: 1 } }),
);
// {
//   success: false,
//   data: undefined,
//   error: { code: 'USER_NOT_FOUND', message: 'The user was not found.', data: { id: 1 } },
// }
```

Any other error keeps its string `code`, such as a Node.js `ENOENT`, or gets `UNKNOWN_ERROR`, with its message and `null` data:

```ts
buildResponse(undefined, new Error('Request failed'));
// { success: false, data: undefined, error: { code: 'UNKNOWN_ERROR', message: 'Request failed', data: null } }
```

The envelope can always be serialized when its data can: Exception data that JSON can't hold becomes `null`.

Check a body you received, after `JSON.parse` dropped its undefined fields, or from a service that leaves out `error` or its `data`:

```ts
import { isResponse } from 'jg-api-response';

const body: unknown = await response.json();
if (isResponse<IUser>(body)) {
  body.success; // boolean
  body.data; // IUser | undefined
  body.error; // { code: string; message: string; data?: unknown } | null | undefined
}
```

`isResponse` checks the error's `code` and `message`, but not its `data`, which is `unknown` until you check it.

## <a id="api-reference"></a>jg-api-response API reference

Every export of `jg-api-response@1.1.1`, 6 in all, with its signature, its errors, and an example. It's generated from the package's TSDoc.

- Functions: [`buildResponse`](#api-buildResponse), [`isResponse`](#api-isResponse)
- Types: [`IApiError`](#api-IApiError), [`IApiResponse`](#api-IApiResponse), [`IParsedApiError`](#api-IParsedApiError), [`IParsedApiResponse`](#api-IParsedApiResponse)

### <a id="api-functions"></a>Functions

#### <a id="api-buildResponse"></a>`buildResponse`

*Function*

Builds the envelope for an HTTP body. The response is successful when no error is passed. Any error that is passed, even a falsy one, becomes `{ code, message, data }`: an Exception keeps all three, and any other error keeps its string `code`, or gets `UNKNOWN_ERROR`, and its message. A failed response can carry data too.

```ts
buildResponse<T = undefined>(data?: T, error?: unknown): IApiResponse<T>
```

**Parameters**

- `data?` `T`: The data to send.
- `error?` `unknown`: The error the request failed with, of any shape.

**Returns** `IApiResponse<T>`: The envelope, which `JSON.stringify` can always serialize when the data can.

**Example**

```ts
import { buildResponse } from 'jg-api-response';
import { Exception } from 'jg-exception';

buildResponse({ id: 1 }); // => { success: true, data: { id: 1 }, error: null }
buildResponse(undefined, new Exception('Not found', { code: 'NOT_FOUND', data: { id: 1 } }));
// { success: false, data: undefined, error: { code: 'NOT_FOUND', message: 'Not found', data: { id: 1 } } }
```

#### <a id="api-isResponse"></a>`isResponse`

*Function*

Checks whether a value is an envelope, including one parsed from JSON or sent by another service: an object with a boolean `success`, an `error` that is `null`, missing, or an object with a string `code` and `message`, and any `data` or none. The error's `data` isn't checked.

```ts
isResponse<T>(value: unknown): value is IParsedApiResponse<T>
```

**Parameters**

- `value` `unknown`: The value to check, such as a parsed response body.

**Returns** `value is IParsedApiResponse<T>`: Whether the value is an envelope.

**Example**

```ts
import { isResponse } from 'jg-api-response';

type IUser = { id: number; name: string };

const body: unknown = JSON.parse('{"success":true,"data":{"id":1,"name":"Ada"},"error":null}');
isResponse<IUser>(body); // => true
if (isResponse<IUser>(body) && body.success) {
  body.data; // IUser | undefined
}
```

### <a id="api-types"></a>Types

#### <a id="api-IApiError"></a>`IApiError`

*Type*

an error as an envelope carries it to the client

```ts
type IApiError = {
    code: string;
    message: string;
    data: IJsonValue;
};
```

**Members**

- `code: string` (property): the stable code the client handles the error by, or `UNKNOWN_ERROR` when the error had none
- `message: string` (property): what failed
- `data: IJsonValue` (property): the context the error carried, or `null` when it carried none

#### <a id="api-IApiResponse"></a>`IApiResponse`

*Type*

The envelope a service sends in an HTTP body. A response is successful when it has no error, and it carries data either way.

```ts
type IApiResponse<T> = {
    success: boolean;
    data: T;
    error: IApiError | null;
};
```

**Members**

- `success: boolean` (property): whether the request succeeded, which is when `error` is null
- `data: T` (property): the data sent to the client, whatever the outcome
- `error: IApiError | null` (property): the error the request failed with, or null when it succeeded

#### <a id="api-IParsedApiError"></a>`IParsedApiError`

*Type*

An envelope error as it arrives from any service. `data` may be missing, and it's `unknown`, because `isResponse` doesn't check it: whoever reads it checks it first.

```ts
type IParsedApiError = {
    code: string;
    message: string;
    data?: unknown;
};
```

**Members**

- `code: string` (property)
- `message: string` (property)
- `data: unknown` (property)

#### <a id="api-IParsedApiResponse"></a>`IParsedApiResponse`

*Type*

An envelope as it arrives after `JSON.parse`: `undefined` fields are gone, and services may send `error: null` on success or leave `error` out.

```ts
type IParsedApiResponse<T> = {
    success: boolean;
    data?: T;
    error?: IParsedApiError | null;
};
```

**Members**

- `success: boolean` (property)
- `data: T` (property)
- `error: IParsedApiError | null` (property)
