Skip to content

Commit

Permalink
added getFieldsWithDirectives to utils package
Browse files Browse the repository at this point in the history
  • Loading branch information
dotansimha committed Nov 20, 2018
1 parent 37bba0b commit 5078fef
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"printWidth": 140
}
6 changes: 6 additions & 0 deletions packages/utils/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
.idea
dist
build
out
temp
5 changes: 5 additions & 0 deletions packages/utils/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
src
node_modules
tests
!dist
tsconfig.json
1 change: 1 addition & 0 deletions packages/utils/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
save-exact = true
49 changes: 49 additions & 0 deletions packages/utils/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@graphql-modules/utils",
"version": "0.2.4",
"main": "dist/index.js",
"license": "MIT",
"scripts": {
"test": "jest",
"build": "tsc",
"lint": "tslint -c ../../tslint.json 'src/**/*.ts' --format stylish"
},
"jest": {
"globals": {
"ts-jest": {
"diagnostics": false
}
},
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testPathIgnorePatterns": [
"/node_modules/",
"/test-assets"
],
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
]
},
"devDependencies": {
"jest": "23.6.0",
"tslint": "5.11.0",
"typescript": "3.1.6"
},
"dependencies": {
"tslib": "1.9.3"
},
"typings": "./dist/index.d.ts",
"typescript": {
"definition": "./dist/index.d.ts"
},
"publishConfig": {
"access": "public"
}
}
57 changes: 57 additions & 0 deletions packages/utils/src/get-fields-with-directives.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { DocumentNode, ObjectTypeDefinitionNode, ValueNode, Kind } from 'graphql';

export type DirectiveArgs = { [name: string]: any };
export type DirectiveUsage = { name: string; args: DirectiveArgs };
export type TypeAndFieldToDirectives = {
[typeAndField: string]: DirectiveUsage[];
};

function isObjectTypeDefinition(obj: any): obj is ObjectTypeDefinitionNode {
return obj && obj.kind === 'ObjectTypeDefinition';
}

function parseDirectiveValue(value: ValueNode): any {
switch (value.kind) {
case Kind.INT:
return parseInt(value.value);
case Kind.FLOAT:
return parseFloat(value.value);
case Kind.BOOLEAN:
return Boolean(value.value);
case Kind.STRING:
case Kind.ENUM:
return value.value;
case Kind.LIST:
return value.values.map(v => parseDirectiveValue(v));
case Kind.OBJECT:
return value.fields.reduce((prev, v) => ({ ...prev, [v.name.value]: parseDirectiveValue(v.value) }), {});
case Kind.NULL:
return null;
default:
return null;
}
}

export function getFieldsWithDirectives(documentNode: DocumentNode): TypeAndFieldToDirectives {
const result: TypeAndFieldToDirectives = {};
const allTypes: ObjectTypeDefinitionNode[] = documentNode.definitions.filter<ObjectTypeDefinitionNode>(isObjectTypeDefinition);

for (const type of allTypes) {
const typeName = type.name.value;

for (const field of type.fields) {
if (field.directives && field.directives.length > 0) {
const fieldName = field.name.value;
const key = `${typeName}.${fieldName}`;
const directives: DirectiveUsage[] = field.directives.map(d => ({
name: d.name.value,
args: (d.arguments || []).reduce((prev, arg) => ({ ...prev, [arg.name.value]: parseDirectiveValue(arg.value) }), {}),
}));

result[key] = directives;
}
}
}

return result;
}
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './get-fields-with-directives';
128 changes: 128 additions & 0 deletions packages/utils/tests/get-fields-with-directives.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { parse } from 'graphql';
import { getFieldsWithDirectives } from '../src/get-fields-with-directives';

describe('getFieldsWithDirectives', () => {
it('Should detect single basic directive', () => {
const node = parse(`
type A {
f1: String @a
}
`);

const result = getFieldsWithDirectives(node);
expect(result['A.f1']).toEqual([{ name: 'a', args: {} }]);
});

it('Should parse string argument correctly', () => {
const node = parse(`
type A {
f1: String @a(f: "1")
}
`);

const result = getFieldsWithDirectives(node);
expect(result['A.f1']).toEqual([{ name: 'a', args: { f: '1' } }]);
});

it('Should parse multiple arguments correctly', () => {
const node = parse(`
type A {
f1: String @a(a1: "1", a2: 10)
}
`);

const result = getFieldsWithDirectives(node);
expect(result['A.f1']).toEqual([{ name: 'a', args: { a1: '1', a2: 10 } }]);
});

it('Should parse object arg correctly', () => {
const node = parse(`
type A {
f1: String @a(a1: { foo: "bar" })
}
`);

const result = getFieldsWithDirectives(node);
expect(result['A.f1']).toEqual([{ name: 'a', args: { a1: { foo: 'bar' } } }]);
});

it('Should parse array arg correctly', () => {
const node = parse(`
type A {
f1: String @a(a1: [1,2,3])
}
`);

const result = getFieldsWithDirectives(node);
expect(result['A.f1']).toEqual([{ name: 'a', args: { a1: [1, 2, 3] } }]);
});

it('Should parse complex array arg correctly', () => {
const node = parse(`
type A {
f1: String @a(a1: ["a", 1, {c: 3, d: true }])
}
`);

const result = getFieldsWithDirectives(node);
expect(result['A.f1']).toEqual([{ name: 'a', args: { a1: ['a', 1, { c: 3, d: true }] } }]);
});

it('Should detect multiple directives', () => {
const node = parse(`
type A {
f1: String @a @b
}
`);

const result = getFieldsWithDirectives(node);
expect(result['A.f1']).toEqual([{ name: 'a', args: {} }, { name: 'b', args: {} }]);
});

it('Should detect multiple directives and multiple fields', () => {
const node = parse(`
type A {
f1: String @a @b
f2: String @c
}
`);

const result = getFieldsWithDirectives(node);
expect(result['A.f1']).toEqual([{ name: 'a', args: {} }, { name: 'b', args: {} }]);
expect(result['A.f2']).toEqual([{ name: 'c', args: {} }]);
});

it('Should detect multiple types', () => {
const node = parse(`
type A {
f1: String @a
}
type B {
f2: String @a
}
`);

const result = getFieldsWithDirectives(node);
expect(result['A.f1']).toEqual([{ name: 'a', args: {} }]);
expect(result['B.f2']).toEqual([{ name: 'a', args: {} }]);
});

it('Should include only fields with directives', () => {
const node = parse(`
type A {
f1: String @a
f2: Int
f3: String
}
type B {
f4: ID!
f2: String @a
}
`);

const result = getFieldsWithDirectives(node);
expect(Object.keys(result).length).toBe(2);
});
});
24 changes: 24 additions & 0 deletions packages/utils/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"module": "commonjs",
"target": "es5",
"lib": ["es6", "esnext", "es2015"],
"suppressImplicitAnyIndexErrors": true,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"sourceMap": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src",
"importHelpers": true,
"noImplicitAny": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"files": ["src/index.ts"],
"exclude": ["node_modules"]
}

0 comments on commit 5078fef

Please sign in to comment.