Skip to content

Commit 40c90f0

Browse files
authored
docs: data validation (NangoHQ#2468)
## Describe your changes Fixes https://linear.app/nango/issue/NAN-1351/official-documentation Fixes https://linear.app/nango/issue/NAN-1201/option-to-generate-zod-models-in-the-nango-integrations-folder - Documentation about data validation I tried to provide some examples. I have tested the custom validations generation but not really the JSON schema usage in other languages. Feel free to propose rewrite suggestion, documentation is not my strongest skill :D
1 parent 1ebac87 commit 40c90f0

File tree

4 files changed

+179
-5
lines changed

4 files changed

+179
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
---
2+
title: 'Validate input and output'
3+
sidebarTitle: 'Validate input and output'
4+
description: 'How to automatically validate your input and ouput with JSON schema'
5+
---
6+
7+
Nango automatically validates your integration inputs & outputs. It also offers ways to further customize data validation in code. The guide will walk you through each approach.
8+
9+
## Automatic validation
10+
11+
The validation is available during development and production, and do not require any configuration from you:
12+
13+
- **CLI**: Dry Run validation errors are logged and will halt execution when using `--validation` command option
14+
- **Production**: Validation errors are logged but do not halt execution
15+
16+
17+
## Available schema files
18+
19+
When you use Nango CLI, it automatically generates two schema files in the `.nango` folder:
20+
21+
- `schema.ts` a TypeScript file that contains all your models.
22+
- `schema.json` a JSON Schema file that is used for automatic data validation.
23+
24+
These files can be versioned and integrated into your own codebase, ensuring consistency and reliability across different environments.
25+
26+
27+
28+
## Custom Validation
29+
30+
For more advanced use cases, you can generate your own validation schemas using the available files with the tool of your choice.
31+
32+
33+
<Tabs>
34+
35+
<Tab title="Typescript `zod`">
36+
37+
To create `zod` schemas you can use [`ts-to-zod` npm package](https://www.npmjs.com/package/ts-to-zod). This tool converts TypeScript definitions into Zod schemas, which can be used for runtime validation in your own Typescript codebase.
38+
39+
40+
```bash
41+
npx ts-to-zod .nango/schema.ts schema.zod.ts
42+
```
43+
<Tip>
44+
You can use zod models in your scripts too
45+
</Tip>
46+
```typescript
47+
import { myZodModel } from '../../schema.zod.ts';
48+
49+
export default async fetchData(nango: Nango) {
50+
const response = await nango.get({ endpoint: '/tickets' });
51+
const isValid = myZodModel.parse(response.json);
52+
if (isValid) {
53+
[...]
54+
}
55+
}
56+
```
57+
</Tab>
58+
59+
60+
<Tab title="Golang">
61+
You can use [`go-jsonschema` golang package](https://github.com/omissis/go-jsonschema). This tool converts JSON Schema definitions into Golang struct. Note that some syntax are not supported by this package.
62+
63+
```bash
64+
go-jsonschema -p main .nango/schema.json > test.go
65+
```
66+
67+
</Tab>
68+
69+
70+
<Tab title="Rust">
71+
72+
You can use [`typify` rust package](https://github.com/oxidecomputer/typify). This tool converts JSON Schema definitions into Rust types. Note that some syntax are not supported by this package.
73+
74+
```bash
75+
cargo typify .nango/schema.json
76+
```
77+
</Tab>
78+
</Tabs>
79+
80+
81+
## Using `schema.json` in your codebase
82+
83+
JSON Schema is supported in most of the main software languages, here is a non-exhaustive list of how you can directly use this file to validate the records you receive from Nango.
84+
85+
<Tabs>
86+
87+
<Tab title="Typescript">
88+
89+
```ts
90+
import { Ajv } from 'ajv';
91+
import addFormats from 'ajv-formats';
92+
import jsonSchema from '.nango/schema.json';
93+
94+
// Initiate AJV
95+
const ajv = new Ajv({ allErrors: true, discriminator: true });
96+
addFormats(ajv);
97+
98+
const modelToValidate = 'MyModelName';
99+
const myData = {"id": "hello-word"};
100+
101+
// Compile the JSON schema
102+
const validate = ajv.compile({
103+
...jsonSchema,
104+
...jsonSchema['definitions'][modelToValidate]
105+
});
106+
107+
// Validate your data
108+
validate(mydata);
109+
```
110+
</Tab>
111+
112+
113+
<Tab title="Golang">
114+
```go
115+
package main
116+
117+
import (
118+
"fmt"
119+
"log"
120+
"github.com/santhosh-tekuri/jsonschema/v5"
121+
)
122+
123+
func main() {
124+
sch, err := jsonschema.Compile(".nango/schema.json")
125+
if err != nil {
126+
log.Fatalf("%#v", err)
127+
}
128+
129+
myData := map[string]interface{}{
130+
"id": "hello-word",
131+
}
132+
if err = sch.Validate(v); err != nil {
133+
log.Fatalf("%#v", err)
134+
}
135+
}
136+
```
137+
</Tab>
138+
139+
<Tab title="Rust">
140+
```rust
141+
use jsonschema::{Draft, JSONSchema};
142+
use serde_json::json;
143+
144+
let data = fs::read_to_string(".nango/schema.json").expect("Unable to read file");
145+
let schema: serde_json::Value = serde_json::from_str(&data).expect("Unable to parse");
146+
147+
let instance = json!("{'id': 'hello-word'}");
148+
let compiled = JSONSchema::compile(&schema).expect("A valid schema");
149+
let result = compiled.validate(&instance);
150+
```
151+
</Tab>
152+
153+
154+
<Tab title="Python">
155+
```py
156+
import json
157+
from jsonschema import validate
158+
159+
with open('.nango/schema.json') as file:
160+
schema = json.load(file)
161+
print(schema)
162+
163+
validate(instance={"id": "hello-word"}, schema=schema)
164+
```
165+
</Tab>
166+
</Tabs>
167+
168+
169+

docs-v2/mint.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@
169169
"pages": [
170170
"customize/guides/advanced/handle-rate-limits",
171171
"customize/guides/advanced/handle-large-datasets",
172-
"customize/guides/advanced/migrate-integration-configuration"
172+
"customize/guides/advanced/migrate-integration-configuration",
173+
"customize/guides/advanced/validate-input-and-output"
173174
]
174175
}
175176
]

packages/cli/lib/services/local-integration.service.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class IntegrationService implements IntegrationServiceInterface {
6969
jsonSchema: nangoProps.syncConfig.models_json_schema
7070
});
7171
if (Array.isArray(valInput)) {
72-
await nango.log('Invalid action input', { data: input, validation: valInput, model: nangoProps.syncConfig.input }, { level: 'error' });
72+
await nango.log('Invalid action input. Use `--validation` option to see the details', { level: 'warn' });
7373
if (nangoProps.runnerFlags.validateActionInput) {
7474
return {
7575
success: false,
@@ -89,7 +89,7 @@ class IntegrationService implements IntegrationServiceInterface {
8989
jsonSchema: nangoProps.syncConfig.models_json_schema
9090
});
9191
if (Array.isArray(valOutput)) {
92-
await nango.log('Invalid action output', { data: output, validation: valOutput, model: modelNameOutput }, { level: 'error' });
92+
await nango.log('Invalid action output. Use `--validation` option to see the details', { level: 'warn' });
9393
if (nangoProps.runnerFlags.validateActionOutput) {
9494
return {
9595
success: false,

packages/shared/lib/sdk/sync.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -836,14 +836,18 @@ export class NangoSync extends NangoAction {
836836

837837
// Validate records
838838
for (const record of results) {
839-
const validation = validateData({ input: record, jsonSchema: this.syncConfig!.models_json_schema, modelName: model });
839+
const validation = validateData({ input: JSON.parse(JSON.stringify(record)), jsonSchema: this.syncConfig!.models_json_schema, modelName: model });
840840
if (validation === true) {
841841
continue;
842842
}
843843

844844
metrics.increment(metrics.Types.RUNNER_INVALID_SYNCS_RECORDS);
845845

846-
await this.log('Invalid record payload', { data: record, validation, model }, { level: 'warn' });
846+
if (this.dryRun) {
847+
await this.log('Invalid action input. Use `--validation` option to see the details', { level: 'warn' });
848+
} else {
849+
await this.log('Invalid record payload', { data: record, validation, model }, { level: 'warn' });
850+
}
847851
if (this.runnerFlags?.validateSyncRecords) {
848852
throw new NangoError(`invalid_sync_record`, { data: record, validation, model });
849853
}

0 commit comments

Comments
 (0)