# @marblejs/middleware-io

A data validation middleware based on awesome [io-ts](https://github.com/gcanti/io-ts) library authored by [*gcanti*](https://github.com/gcanti).

### Installation

```bash
yarn add @marblejs/middleware-io
```

Requires `@marblejs/core` to be installed.

### Importing

```typescript
// HTTP
import { requestValidator$ } from '@marblejs/middleware-io';

// Events, eg. WebSockets
import { eventValidator$ } from '@marblejs/middleware-io';
```

### Type declaration <a href="#type-declaration" id="type-declaration"></a>

```haskell
requestValidator$ :: (RequestSchema, ValidatorOptions) -> Observable<HttpRequest> -> Observable<HttpRequest>
eventValidator$ :: (Schema, ValidatorOptions) -> Observable<Event> -> Observable<Event>
```

### Parameters

***requestValidator$***

| parameter | definition                                                                  |
| --------- | --------------------------------------------------------------------------- |
| *schema*  | `Partial<RequestSchema>`(see [io-ts](https://github.com/gcanti/io-ts) docs) |
| *options* | \<optional> `ValidatorOptions`                                              |

***eventValidator$***

| parameter | definition                                                   |
| --------- | ------------------------------------------------------------ |
| *schema*  | `Schema` (see [io-ts](https://github.com/gcanti/io-ts) docs) |
| *options* | \<optional> `ValidatorOptions`                               |

***ValidatorOptions***

| parameter  | definition             |
| ---------- | ---------------------- |
| *reporter* | \<optional> `Reporter` |
| *context*  | \<optional> `string`   |

### Usage

Let's define a user schema that will be used for I/O validation.

{% code title="user.schema.ts" %}

```typescript
export const userSchema = t.type({
  id: t.string,
  firstName: t.string,
  lastName: t.string,
  roles: t.array(t.union([
    t.literal('ADMIN'),
    t.literal('GUEST'),
  ])),
});

export type User = t.TypeOf<typeof userSchema>;
```

{% endcode %}

```typescript
import { use, r } from '@marblejs/core';
import { requestValidator$, t } from '@marblejs/middleware-io';
import { userSchema } from './user.schema.ts';

const effect$ = r.pipe(
  r.matchPath('/'),
  r.matchType('POST'),
  r.useEffect(req$ => req$.pipe(
    requestValidator$({ body: userSchema }),
    // ..
  )));
```

{% hint style="info" %}
For more validation use cases and recipes, visit [Validation](https://marblejs.gitbook.io/docs/other/api-reference/broken-reference) chapter.
{% endhint %}

You can also reuse the same schema for Events validation if you want.

```typescript
import { matchEvent, act } from '@marblejs/core';
import { WsEffect } from '@marblejs/websockets';
import { eventValidator$, t } from '@marblejs/middleware-io';
import { userSchema } from './user.schema.ts';

const postUser$: WsEffect = event$ =>
  event$.pipe(
    matchEvent('CREATE_USER'),
    act(eventValidator$(userSchema)),
    act(event => { ... }),
  );
```

The inferred `req.body` / `event.payload` type of provided schema, will be of the following form:

```typescript
type User = {
  id: string;
  firstName: string;
  lastName: string;
  roles: ('ADMIN' | 'GUEST')[];
};
```

{% hint style="warning" %}
Please note that **each** `eventValidator$` **must be** applied inside `act` operator to prevent closing of the main observable stream.
{% endhint %}

### Validation errors

Lets take a look at the default reported validation error thrown by `eventValidator$` . Let's assume that client passed wrong values for `firstName` and `roles`  fields.

```javascript
payload.lastName = false;
payload.roles = ['TEST'];
```

The reported error intercepted via default error effect will look like follows.

```javascript
{
  type: 'CREATE_USER',
  error: {
    message: 'Validation error',
    data: [
      {
        path: 'lastName',
        expected: 'string',
        got: 'false'
      },
      {
        path: 'roles.0.0',
        got: '"TEST"',
        expected: '"ADMIN"'
      },
      {
        path: 'roles.0.1',
        got: '"TEST"',
        expected: '"GUEST"'
      }
    ]
  }
}
```

### Reporters

You can create custom reporters by conforming to [io-ts](https://github.com/gcanti/io-ts#error-reporters) `Reporter` interface.

```typescript
interface Reporter<A> {
  report: (validation: Validation<any>) => A
}
```

In order to use custom reporter you have to pass it with `options` object as a second argument.

```typescript
requestValidator$(schema, { reporter: customReporter });
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://marblejs.gitbook.io/docs/other/api-reference/middleware-io.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
