Skip to content

Commit

Permalink
feat(nango-yaml): endpoint explicit definition (#2940)
Browse files Browse the repository at this point in the history
## Context

Contributes to
https://linear.app/nango/issue/NAN-1934/specify-script-endpoint-section-in-nangoyaml

We want to introduce an `entry` props that will define the endpoint
category. That means we can no longer store this in a string (or we can
but it would be bad). We still need to support the old way but this PR
introduce a more explicit mode for endpoint.

> [!NOTE]
> This PR is changing the internal format but should not introduce any
blocking changes for customers. If something is no longer working it's a
bug

### Major Changes

- Change default format of endpoint in parser and internally
It's just the string split in an object. That will allow us to add
`entry` later.
```ts
{ method: HTTP_METHOD, path: string } 
```

- Introduce a way to enforce CLI version at the API level
I ended up not enforcing a very recent CLI version because we want to
keep retro-compat for a few months, but at some we will be able to
increase the minimum CLI version.

### Minor

- Rename HTTP_VERB to HTTP_METHOD
- Remove a lot of dead code


## 🧪 How to test?

- Run the dashboard, go to integrations and check that nothing crashes
- Deploy stuff to your local dashboard using the old CLI `nango deploy
dev`
- Deploy stuff to your local dashboard using the new CLI `node
nango/packages/cli/dist/index.js deploy dev`
  • Loading branch information
bodinsamuel authored Nov 6, 2024
1 parent 41c6b1d commit 0b45b98
Show file tree
Hide file tree
Showing 45 changed files with 356 additions and 698 deletions.
19 changes: 4 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ exports[`load > should parse a nango.yaml file that is version 2 as expected 1`]
{
"description": "Get a GitHub issue.",
"endpoint": {
"GET": "/ticketing/tickets/{GithubCreateIssueInput:id}",
"method": "GET",
"path": "/ticketing/tickets/{GithubCreateIssueInput:id}",
},
"input": null,
"name": "github-get-issue",
Expand All @@ -175,7 +176,8 @@ exports[`load > should parse a nango.yaml file that is version 2 as expected 1`]
{
"description": "Creates a GitHub issue.",
"endpoint": {
"POST": "/ticketing/tickets",
"method": "POST",
"path": "/ticketing/tickets",
},
"input": "GithubCreateIssueInput",
"name": "github-create-issue",
Expand All @@ -196,7 +198,8 @@ exports[`load > should parse a nango.yaml file that is version 2 as expected 1`]
{
"description": "Deletes a GitHub issue.",
"endpoint": {
"DELETE": "/ticketing/tickets/{GithubIssue:id}",
"method": "DELETE",
"path": "/ticketing/tickets/{GithubIssue:id}",
},
"input": null,
"name": "github-delete-issue",
Expand All @@ -221,7 +224,8 @@ exports[`load > should parse a nango.yaml file that is version 2 as expected 1`]
"description": "Sync github issues continuously from public repos",
"endpoints": [
{
"GET": "/ticketing/tickets",
"method": "GET",
"path": "/ticketing/tickets",
},
],
"input": null,
Expand Down Expand Up @@ -249,7 +253,8 @@ exports[`load > should parse a nango.yaml file that is version 2 as expected 1`]
"description": "Sync github issues continuously from public repos example two",
"endpoints": [
{
"GET": "/ticketing/tickets-two",
"method": "GET",
"path": "/ticketing/tickets-two",
},
],
"input": null,
Expand All @@ -275,10 +280,12 @@ exports[`load > should parse a nango.yaml file that is version 2 as expected 1`]
"description": "Sync github issues to multiple models",
"endpoints": [
{
"GET": "/ticketing/ticket",
"method": "GET",
"path": "/ticketing/ticket",
},
{
"GET": "/ticketing/pr",
"method": "GET",
"path": "/ticketing/pr",
},
],
"input": null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ exports[`package > should package correctly 2`] = `
"auto_start": true,
"endpoints": [
{
"GET": "/hubspot/contacts",
"method": "GET",
"path": "/hubspot/contacts",
},
],
"fileBody": {
Expand Down Expand Up @@ -113,7 +114,8 @@ export default async function fetchData(nango: NangoSync): Promise<void> {
{
"endpoints": [
{
"POST": "/hubspot/contact",
"method": "POST",
"path": "/hubspot/contact",
},
],
"fileBody": {
Expand Down Expand Up @@ -171,7 +173,8 @@ export default async function runAction(nango: NangoAction): Promise<void> {
"auto_start": true,
"endpoints": [
{
"GET": "/github/issues",
"method": "GET",
"path": "/github/issues",
},
],
"fileBody": {
Expand Down Expand Up @@ -244,7 +247,8 @@ export default async function fetchData(nango: NangoSync): Promise<void> {
{
"endpoints": [
{
"POST": "/github/issue",
"method": "POST",
"path": "/github/issue",
},
],
"fileBody": {
Expand Down
15 changes: 14 additions & 1 deletion packages/nango-yaml/lib/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'node:path';
import ms from 'ms';
import type { StringValue } from 'ms';
import type { NangoYaml, NangoYamlParsed, NangoYamlParsedIntegration } from '@nangohq/types';
import type { HTTP_METHOD, NangoSyncEndpointV2, NangoYaml, NangoYamlParsed, NangoYamlParsedIntegration } from '@nangohq/types';

interface IntervalResponse {
interval: StringValue;
Expand Down Expand Up @@ -194,3 +194,16 @@ export function getProviderConfigurationFromPath({ filePath, parsed }: { filePat

return providerConfiguration;
}

export function parseEndpoint(rawEndpoint: string | NangoSyncEndpointV2, defaultMethod: HTTP_METHOD): NangoSyncEndpointV2 {
if (typeof rawEndpoint === 'string') {
const endpoint = rawEndpoint.split(' ');
if (endpoint.length > 1) {
return { method: endpoint[0] as HTTP_METHOD, path: endpoint[1] as string };
}

return { method: defaultMethod, path: endpoint[0] as string };
}

return rawEndpoint;
}
64 changes: 24 additions & 40 deletions packages/nango-yaml/lib/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,27 +120,19 @@ export abstract class NangoYamlParser {
const find = getRecursiveModelNames(this.modelsParser, sync.input);
find.forEach((name) => usedModelsSync.add(name));
}
for (const endpointByVerb of sync.endpoints) {
for (const [verb, endpoint] of Object.entries(endpointByVerb)) {
if (!endpoint) {
continue; // TS pleasing
}

const str = `${verb} ${endpoint}`;
if (endpoints.has(str)) {
this.errors.push(new ParserErrorDuplicateEndpoint({ endpoint: str, path: [integrationName, 'syncs', sync.name, '[endpoints]'] }));
continue;
}
for (const endpoint of sync.endpoints) {
const str = `${endpoint.method} ${endpoint.path}`;
if (endpoints.has(str)) {
this.errors.push(new ParserErrorDuplicateEndpoint({ endpoint: str, path: [integrationName, 'syncs', sync.name, '[endpoints]'] }));
continue;
}

endpoints.add(str);
const modelInUrl = endpoint.match(/{([^}]+)}/);
if (modelInUrl) {
const modelName = modelInUrl[1]!;
if (!this.modelsParser.get(modelName)) {
this.errors.push(
new ParserErrorModelNotFound({ model: modelName, path: [integrationName, 'syncs', sync.name, '[endpoints]'] })
);
}
endpoints.add(str);
const modelInUrl = endpoint.path.match(/{([^}]+)}/);
if (modelInUrl) {
const modelName = modelInUrl[1]!;
if (!this.modelsParser.get(modelName)) {
this.errors.push(new ParserErrorModelNotFound({ model: modelName, path: [integrationName, 'syncs', sync.name, '[endpoints]'] }));
}
}
}
Expand Down Expand Up @@ -195,27 +187,19 @@ export abstract class NangoYamlParser {
find.forEach((name) => usedModelsAction.add(name));
}
if (action.endpoint) {
for (const [verb, endpoint] of Object.entries(action.endpoint)) {
if (!endpoint) {
continue; // TS pleasing
}
const endpoint = action.endpoint;

const str = `${verb} ${endpoint}`;
if (endpoints.has(str)) {
this.errors.push(
new ParserErrorDuplicateEndpoint({ endpoint: str, path: [integrationName, 'actions', action.name, '[endpoint]'] })
);
continue;
}
endpoints.add(str);
const modelInUrl = endpoint.match(/{([^}]+)}/);
if (modelInUrl) {
const modelName = modelInUrl[1]!.split(':')[0]!;
if (!this.modelsParser.get(modelName)) {
this.errors.push(
new ParserErrorModelNotFound({ model: modelName, path: [integrationName, 'syncs', action.name, '[endpoint]'] })
);
}
const str = `${endpoint.method} ${endpoint.path}`;
if (endpoints.has(str)) {
this.errors.push(new ParserErrorDuplicateEndpoint({ endpoint: str, path: [integrationName, 'actions', action.name, '[endpoint]'] }));
continue;
}
endpoints.add(str);
const modelInUrl = endpoint.path.match(/{([^}]+)}/);
if (modelInUrl) {
const modelName = modelInUrl[1]!.split(':')[0]!;
if (!this.modelsParser.get(modelName)) {
this.errors.push(new ParserErrorModelNotFound({ model: modelName, path: [integrationName, 'syncs', action.name, '[endpoint]'] }));
}
}
}
Expand Down
23 changes: 6 additions & 17 deletions packages/nango-yaml/lib/parser.v2.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type {
HTTP_VERB,
NangoModel,
NangoSyncEndpoint,
NangoSyncEndpointV2,
NangoYamlParsedIntegration,
NangoYamlV2,
NangoYamlV2Integration,
Expand All @@ -13,7 +12,7 @@ import type {
} from '@nangohq/types';
import { NangoYamlParser } from './parser.js';
import { ParserErrorEndpointsMismatch, ParserErrorInvalidRuns } from './errors.js';
import { getInterval } from './helpers.js';
import { getInterval, parseEndpoint } from './helpers.js';

export class NangoYamlParserV2 extends NangoYamlParser {
parse(): boolean {
Expand Down Expand Up @@ -85,7 +84,7 @@ export class NangoYamlParserV2 extends NangoYamlParser {
modelNames.add(modelInput.name);
}

const endpoints: NangoSyncEndpoint[] = [];
const endpoints: NangoSyncEndpointV2[] = [];
if (sync.endpoint) {
const tmp = Array.isArray(sync.endpoint) ? sync.endpoint : [sync.endpoint];

Expand All @@ -95,12 +94,7 @@ export class NangoYamlParserV2 extends NangoYamlParser {
}

for (const endpoint of tmp) {
const split = endpoint.split(' ');
if (split.length === 2) {
endpoints.push({ [split[0] as HTTP_VERB]: split[1] });
} else {
endpoints.push({ GET: split[0]! });
}
endpoints.push(parseEndpoint(endpoint, 'GET'));
}
}

Expand Down Expand Up @@ -163,14 +157,9 @@ export class NangoYamlParserV2 extends NangoYamlParser {
modelNames.add(modelInput.name);
}

const endpoint: NangoSyncEndpoint = {};
let endpoint: NangoSyncEndpointV2 | null = null;
if (action.endpoint) {
const split = action.endpoint.split(' ');
if (split.length === 2) {
endpoint[split[0]! as HTTP_VERB] = split[1]!;
} else {
endpoint['POST'] = split[0]!;
}
endpoint = parseEndpoint(action.endpoint, 'POST');
}

const parsedAction: ParsedNangoAction = {
Expand Down
10 changes: 5 additions & 5 deletions packages/nango-yaml/lib/parser.v2.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('parse', () => {
{
auto_start: true,
description: '',
endpoints: [{ GET: '/provider/top' }],
endpoints: [{ method: 'GET', path: '/provider/top' }],
input: 'GithubIssue',
name: 'top',
output: ['GithubIssue'],
Expand All @@ -44,7 +44,7 @@ describe('parse', () => {
{
description: '',
input: 'Anonymous_provider_action_createIssue_input',
endpoint: { POST: '/test' },
endpoint: { method: 'POST', path: '/test' },
name: 'createIssue',
output: ['GithubIssue'],
scopes: [],
Expand Down Expand Up @@ -92,7 +92,7 @@ describe('parse', () => {
{
description: '',
input: null,
endpoint: { POST: '/test' },
endpoint: { method: 'POST', path: '/test' },
name: 'createIssue',
output: ['Start'],
scopes: [],
Expand Down Expand Up @@ -131,7 +131,7 @@ describe('parse', () => {
{
auto_start: true,
description: '',
endpoints: [{ GET: '/provider/top' }],
endpoints: [{ method: 'GET', path: '/provider/top' }],
input: 'Anonymous_provider_sync_top_input',
name: 'top',
output: ['Anonymous_provider_sync_top_output'],
Expand Down Expand Up @@ -235,7 +235,7 @@ describe('parse', () => {
expect(parser.warnings).toStrictEqual([]);
expect(parser.parsed?.integrations[0]?.actions).toMatchObject([
{
endpoint: { GET: '/ticketing/tickets/{Found:id}' }
endpoint: { method: 'GET', path: '/ticketing/tickets/{Found:id}' }
}
]);
});
Expand Down
8 changes: 4 additions & 4 deletions packages/node-client/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import type {
AuthOperationType,
AuthModeType,
AuthModes,
HTTP_VERB,
NangoSyncEndpoint,
HTTP_METHOD,
NangoSyncEndpointV2,
AllAuthCredentials,
OAuth1Credentials,
OAuth2Credentials,
Expand Down Expand Up @@ -72,7 +72,7 @@ export type {
JwtCredentials,
TwoStepCredentials
};
export type { HTTP_VERB, NangoSyncEndpoint };
export type { HTTP_METHOD, NangoSyncEndpointV2 };
export type { RecordMetadata, RecordLastAction, NangoRecord };

export type {
Expand Down Expand Up @@ -269,7 +269,7 @@ export interface NangoSyncConfig {
track_deletes?: boolean;
returns: string[];
models: NangoSyncModel[];
endpoints: NangoSyncEndpoint[];
endpoints: NangoSyncEndpointV2[];
is_public?: boolean;
pre_built?: boolean;
version?: string | null;
Expand Down
Loading

0 comments on commit 0b45b98

Please sign in to comment.