-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): introduce GraphQL importer
closes #232
- Loading branch information
Showing
13 changed files
with
1,594 additions
and
30 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { BaseImporter } from './BaseImporter'; | ||
import { ImporterType } from './ImporterType'; | ||
import { isArrayOfStrings } from '../utils'; | ||
import { type DocFormat, type Spec } from './Spec'; | ||
import { GraphQL, introspectionFromSchema } from '../types'; | ||
import { loadSchema } from '@graphql-tools/load'; | ||
import { URL } from 'url'; | ||
import { type BinaryLike, createHash } from 'crypto'; | ||
|
||
export class GraphQLImporter extends BaseImporter<ImporterType.GRAPHQL> { | ||
get type(): ImporterType.GRAPHQL { | ||
return ImporterType.GRAPHQL; | ||
} | ||
|
||
constructor() { | ||
super(); | ||
} | ||
|
||
public async import( | ||
content: string, | ||
expectedFormat?: DocFormat | ||
): Promise<Spec<ImporterType.GRAPHQL, GraphQL.Document> | undefined> { | ||
try { | ||
const spec = await super.import(content, expectedFormat); | ||
|
||
return spec | ||
? { | ||
...spec, | ||
doc: await this.tryConvertSDL(spec.doc) | ||
} | ||
: spec; | ||
} catch { | ||
// noop | ||
} | ||
|
||
return Promise.resolve(undefined); | ||
} | ||
|
||
public isSupported( | ||
spec: unknown | ||
): spec is GraphQL.Document { | ||
return ( | ||
this.isGraphQLSDLEnvelope(spec) || | ||
this.isGraphQlIntrospectionEnvelope(spec) | ||
); | ||
} | ||
|
||
protected fileName({ | ||
doc | ||
}: { | ||
doc: GraphQL.Document; | ||
format: DocFormat; | ||
}): string | undefined { | ||
const url = new URL(doc.url); | ||
const checkSum = this.generateCheckSum(url.toString()); | ||
|
||
return `${url.hostname}-${checkSum}`.toLowerCase(); | ||
} | ||
|
||
private async tryConvertSDL( | ||
obj: GraphQL.Document | ||
): Promise<GraphQL.Document> { | ||
if (this.isGraphQLSDLEnvelope(obj)) { | ||
const schema = await loadSchema(obj.data, { | ||
loaders: [] | ||
}); | ||
|
||
return { | ||
...obj, | ||
data: introspectionFromSchema(schema) | ||
}; | ||
} | ||
|
||
return obj; | ||
} | ||
|
||
private isGraphQLSDLEnvelope( | ||
obj: unknown | ||
): obj is GraphQL.GraphQLEnvelope<string | string[]> { | ||
return ( | ||
typeof obj === 'object' && | ||
'url' in obj && | ||
typeof (obj as GraphQL.GraphQLEnvelope<string>).url === 'string' && | ||
'data' in obj && | ||
(typeof (obj as GraphQL.GraphQLEnvelope<string>).data === 'string' || | ||
isArrayOfStrings((obj as GraphQL.GraphQLEnvelope<string[]>).data)) | ||
); | ||
} | ||
|
||
private isGraphQlIntrospectionEnvelope( | ||
obj: unknown | ||
): obj is GraphQL.Document { | ||
return ( | ||
typeof obj === 'object' && | ||
'url' in obj && | ||
typeof (obj as GraphQL.Document).url === 'string' && | ||
'data' in obj && | ||
'__schema' in (obj as GraphQL.Document).data && | ||
typeof (obj as GraphQL.Document).data.__schema === | ||
'object' | ||
); | ||
} | ||
|
||
private generateCheckSum(value: BinaryLike): string { | ||
return createHash('md5').update(value).digest('hex'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { IntrospectionQuery } from 'graphql'; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-namespace | ||
export namespace GraphQL { | ||
export interface GraphQLEnvelope< | ||
T extends IntrospectionQuery | string | string[] | ||
> { | ||
url: string; | ||
data: T; | ||
} | ||
|
||
export type Document = GraphQLEnvelope<IntrospectionQuery>; | ||
} | ||
|
||
export * from 'graphql'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './har'; | ||
export * from './openapi'; | ||
export * from './postman'; | ||
export * from './openapi'; | ||
export * from './graphql'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from './first'; | ||
export * from './url'; | ||
export * from './is-array-of-strings' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export const isArrayOfStrings = (data: unknown) => !!data && Array.isArray(data) | ||
? data.every(item => typeof item === 'string') | ||
: false; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { GraphQLImporter } from '../src/importers/GraphQLImporter'; | ||
import { readFile } from 'fs'; | ||
import { resolve } from 'path'; | ||
import { promisify } from 'util'; | ||
|
||
describe('GraphQLImporter', () => { | ||
let sut!: GraphQLImporter; | ||
|
||
beforeEach(() => { | ||
sut = new GraphQLImporter(); | ||
}); | ||
|
||
describe('type', () => { | ||
it(`should return graphql`, () => { | ||
// act | ||
const result = sut.type; | ||
|
||
// assert | ||
expect(result).toStrictEqual('graphql'); | ||
}); | ||
}); | ||
|
||
describe('import', () => { | ||
it('should not import unparsable content ', async () => { | ||
// arrange | ||
const content = '{'; | ||
|
||
// act | ||
const spec = await sut.import(content); | ||
|
||
// assert | ||
expect(spec).toBeUndefined(); | ||
}); | ||
|
||
it('should import introspection envelope', async () => { | ||
// arrange | ||
const expected = JSON.parse( | ||
await promisify(readFile)( | ||
resolve(__dirname, './fixtures/graphql-introspection.json'), | ||
'utf8' | ||
) | ||
); | ||
|
||
const content = JSON.stringify(expected); | ||
|
||
// act | ||
const spec = await sut.import(content); | ||
|
||
// assert | ||
expect(spec).toMatchObject({ | ||
doc: expected, | ||
format: 'json', | ||
type: 'graphql', | ||
name: 'example.com-c00f7d6a02b8e2fb143fd737b7302c15' | ||
}); | ||
}); | ||
|
||
it.each([ | ||
{ | ||
test: 'single', | ||
input: { | ||
url: 'https://example.com/graphql', | ||
data: 'type Foo { bar : String!} type Query { foo: Foo }' | ||
} | ||
}, | ||
{ | ||
test: 'multiple', | ||
input: { | ||
url: 'https://example.com/graphql', | ||
data: ['type Foo { bar : String!}', 'type Query { foo: Foo }'] | ||
} | ||
} | ||
])('should import $test SDL envelope', async ({ input }) => { | ||
// arrange | ||
|
||
const expected = JSON.parse( | ||
await promisify(readFile)( | ||
resolve(__dirname, './fixtures/graphql-introspection.json'), | ||
'utf8' | ||
) | ||
); | ||
|
||
const content = JSON.stringify(input); | ||
|
||
// act | ||
const spec = await sut.import(content); | ||
|
||
// assert | ||
expect(spec).toMatchObject({ | ||
doc: expected, | ||
format: 'json', | ||
type: 'graphql', | ||
name: 'example.com-c00f7d6a02b8e2fb143fd737b7302c15' | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.