Skip to content

Commit

Permalink
[E4E-30]: Port of existing code from private repo
Browse files Browse the repository at this point in the history
  • Loading branch information
TheCleric committed Nov 21, 2021
1 parent 5c3b975 commit 7691b31
Show file tree
Hide file tree
Showing 24 changed files with 47,413 additions and 132 deletions.
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
build
dist
26 changes: 26 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const baseConfig = {
extends: ['@encoura/eslint-config'],
settings: {
'import/resolver': {
'babel-plugin-root-import': {},
typescript: {
project: ['tsconfig.json', 'test/tsconfig.json'],
},
},
},
};

const tsConfig = {
extends: ['@encoura/eslint-config'],
files: ['**/*.ts'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: ['tsconfig.json', 'test/tsconfig.json'],
},
plugins: ['@typescript-eslint', 'prettier'],
rules: {
...baseConfig.rules,
},
};

module.exports = { ...baseConfig, overrides: [tsConfig] };
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
build
67 changes: 58 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,75 @@
# Apollo Rest utils

Encoura's solution to integrating rest APIs with [Apollo](https://www.apollographql.com/docs/) and [Apollo Client](https://www.apollographql.com/docs/react/).
Encoura's solution to integrating rest APIs with
[Apollo](https://www.apollographql.com/docs/) and
[Apollo Client](https://www.apollographql.com/docs/react/).

These utils build on top of the great work of [apollo-link-rest](https://www.apollographql.com/docs/react/api/link/apollo-link-rest/).
These utils build on top of the great work of
[apollo-link-rest](https://www.apollographql.com/docs/react/api/link/apollo-link-rest/).

This library provides helper functions and generators for making integration with REST apis and Apollo in a TypeScript application.
This library provides helper functions and generators for making integration
with REST apis and Apollo in a TypeScript application.

## Features

* A command line utiltity that takes a swagger file/url, and automatically generates input and output types, and endpoint definitions that can be used to make integration with `apollo-link-rest` much easier.
* Wrapper functions for common GraphQL operations that allow you to pass in pure GraphQL, and enables the input variables and the result to be strongly typed based on the swagger definition.
* Automatically checks your GraphQL at runtime and will throw exceptions if your GraphQL fields do not match the endpoint definition.
* A command line utiltity that takes a swagger file/url, and automatically
generates input and output types, and endpoint definitions that can be used
to make integration with `apollo-link-rest` much easier.
* Wrapper functions for common GraphQL operations that allow you to pass in
pure GraphQL, and enables the input variables and the result to be strongly
typed based on the swagger definition.
* Automatically checks your GraphQL at runtime and will throw exceptions if
your GraphQL fields do not match the endpoint definition.

## Usage

TODO
From the command line you can generate definitions for endpoints:

`npx apollo-rest-utils <path_to_swagger_file_or_url> <output_directory_where_you_want_the_files>`

Then you can use those definitions to make GraphQL calls within an Apollo context:

```TypeScript
import { wrapRestQuery } from 'apollo-rest-utils';

import ROUTES from 'path/to/directory_specified_in_cli_call/__generatedRestEndpoints';

const userSearch = (searchPattern: string) => {
const wrappedRestQuery = wrapRestQuery<'users'>();

const { data, loading } = wrappedRestQuery(
gql`
query RenderUserSearchQuery($search: String!) {
user(search: $search) {
id
name
city
state
}
}
`,
{
endpoint: ROUTES.GET.USER_SEARCH,
skip: !searchPattern,
variables: {
search: searchPattern,
},
},
);

return data?.users ?? [];
}
```

## Releasing

After making any changes and merging them to main, please do the following:

* Create a new branch from main and run `npm run update:version`
* Verify the `CHANGELOG.md` generated changes
* Commit, push, and merge to main.
* Create a new [release](https://github.com/nrccua/apollo-rest-utils/releases/new) using the tag generated in the previous steps
* Use the `Auto-generate release notes` button to generate the release notes, and add any context you may deem necessary.
* Create a new
[release](https://github.com/nrccua/apollo-rest-utils/releases/new) using
the tag generated in the previous steps
* Use the `Auto-generate release notes` button to generate the release notes,
and add any context you may deem necessary.
27 changes: 27 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const defaultConfig = require('@encoura/eslint-config/jest.config');
const os = require('os');

module.exports = {
...defaultConfig,
collectCoverageFrom: ['<rootDir>/lib/**/*.ts', '!<rootDir>/lib/index.ts'],
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
globals: {
'ts-jest': {
diagnostics: false,
tsconfig: './tsconfig.json',
},
},
maxWorkers: os.cpus().length / 2,
preset: 'ts-jest',
roots: ['<rootDir>/lib'],
transform: {
'^.+\\.ts$': 'ts-jest',
},
};
78 changes: 78 additions & 0 deletions lib/generateRoutes/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { randomUUID } from 'crypto';
import fs from 'fs';
import path from 'path';

import SwaggerParser from '@apidevtools/swagger-parser';

import { generateTypes, generateTypescript, normalizeName, pathToType } from '.';

const buildFolder = path.join('.', 'build', 'tests');

function createBuildFolder(): void {
if (!fs.existsSync(buildFolder)) {
fs.mkdirSync(buildFolder, { recursive: true });
}
}
function deleteBuildFolder(): void {
if (fs.existsSync(buildFolder)) {
fs.rmdirSync(buildFolder, { recursive: true });
}
}

function doTestImport(tsData: string): unknown {
const filename = `${randomUUID()}.ts`;

fs.writeFileSync(path.join(buildFolder, filename), tsData);

// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, security/detect-non-literal-require, import/no-dynamic-require, global-require, @typescript-eslint/no-var-requires
const endpoints = require(`../../build/tests/${filename}`);
expect(endpoints).toBeTruthy(); // Probably a better test here, but I don't know what it would be
return endpoints;
}

describe('generateTypescript', () => {
const testSwaggerPath = './test/test_data/generateRoutes/';

beforeAll(() => {
createBuildFolder();
});

afterAll(() => {
deleteBuildFolder();
});

it.each(fs.readdirSync(testSwaggerPath).map(d => path.join(testSwaggerPath, d)))('can parse the swagger document %s', async apiPath => {
const api = await SwaggerParser.validate(apiPath);

const typeImport = await generateTypes(apiPath, path.join(buildFolder, `${randomUUID()}_types.ts`));

const tsData = generateTypescript(api, typeImport).replace('apollo-rest-utils', '../../lib');

expect(tsData).toBeTruthy(); // Not empty, null, or undefined

doTestImport(tsData);
});
});

describe('normalizeName', () => {
it.each([
['/login', 'LOGIN'],
['login', 'LOGIN'],
['/high-schools/search', 'HIGH_SCHOOLS_SEARCH'],
['/invitations/{studentKey}/users/{mentorEmail}', 'INVITATIONS_BY_STUDENTKEY_USERS_BY_MENTOREMAIL'],
['/{userId}/{studentKey}/{email}', 'BY_USERID_BY_STUDENTKEY_BY_EMAIL'],
])('path %s produces name %s', (pathName, expectedEndpointName) => {
expect(normalizeName(pathName)).toEqual(expectedEndpointName);
});
});

describe('pathToType', () => {
it.each([
['/login', 'LoginResponse'],
['/high-schools/search', 'SearchResponse'],
['/invitations/{studentKey}/users/{mentorEmail}', 'UsersResponse'],
])('path %s produces type name %s', (pathName, expectedTypeName) => {
expect(pathToType(pathName)).toEqual(expectedTypeName);
});
});
Loading

0 comments on commit 7691b31

Please sign in to comment.