Skip to content

Commit f4c1163

Browse files
committed
Added ability to create Apollo zip using an input graphQL schema file instead of discovering the schema by querying neptune. For consistency with App Sync pipeline and CDK workflows, this workflow requires setting a new endpoint argument --create-update-apollo-server-neptune-endpoint in addition to the existing --input-schema-file argument.
Other changes: -fixed bugs resolving gremlin queries and added resolver tests for these scenarios -changed schema file in zip to have output. prefix for consistency with the resolver file -bumped the version from 1.1.1 to 1.2.0-beta.1
1 parent c9d6a39 commit f4c1163

32 files changed

+466
-179
lines changed

README.md

+11-2
Original file line numberDiff line numberDiff line change
@@ -275,15 +275,24 @@ See CDK end to end example [here](https://github.com/aws/amazon-neptune-for-grap
275275
# Generate Apollo Server Artifacts
276276
If you prefer to use Apollo Server instead of AWS App Sync, the utility can generate a ZIP file of Apollo Server artifacts with the CLI option `--create-update-apollo-server` for a standalone server or `--create-update-apollo-server-subgraph` for federated systems.
277277

278-
For example:
278+
Example of starting with a neptune database with data from which to determine the graphQL schema:
279279
```
280280
neptune-for-graphql \
281281
--input-graphdb-schema-neptune-endpoint abc.us-west-2.neptune.amazonaws.com:8182 \
282282
--create-update-apollo-server \
283283
--output-resolver-query-https
284284
```
285285

286-
The command above will generate an `apollo-server-<identifier>-<timestamp>.zip` file which can then be deployed locally by following these steps:
286+
Example of starting with a graphQL schema file:
287+
```
288+
neptune-for-graphql \
289+
--input-schema-file airports.source.schema.graphql \
290+
--create-update-apollo-server-neptune-endpoint abc.us-west-2.neptune.amazonaws.com:8182 \
291+
--create-update-apollo-server \
292+
--output-resolver-query-https
293+
```
294+
295+
The example commands above will generate the file `apollo-server-<identifier>-<timestamp>.zip`, which can then be deployed locally by following these steps:
287296
1. unzip `apollo-server-<identifier>-<timestamp>.zip`
288297
2. change directory into the unzipped folder
289298
3. execute `npm install` to install required dependencies

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@aws/neptune-for-graphql",
3-
"version": "1.1.1",
3+
"version": "1.2.0-beta.1",
44
"description": "CLI utility to create and maintain a GraphQL API for Amazon Neptune",
55
"keywords": [
66
"Amazon Neptune",

src/help.js

+2
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ Parameters
146146
147147
[--create-update-apollo-server -asvr]
148148
[--create-update-apollo-server-subgraph -asub]
149+
[--create-update-apollo-server-neptune-endpoint -ase ] default: --input-graphdb-schema-neptune-endpoint if exists
150+
[ ... more apollo options to come in future roadmap ]
149151
150152
[--output-aws-pipeline-cdk -c ]
151153
[--output-aws-pipeline-cdk-name <value> -cn ] default: Neptune DB name from --input-graphdb-schema-neptune-endpoint if exists

src/main.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ let queryClient = 'sdk'; // or 'http'
5555
let isNeptuneIAMAuth = false;
5656
let createUpdateApolloServer = false;
5757
let createUpdateApolloServerSubgraph = false;
58+
let createUpdateApolloServerEndpoint = '';
5859
let createUpdatePipeline = false;
5960
let createUpdatePipelineName = '';
6061
let createUpdatePipelineEndpoint = '';
@@ -197,6 +198,10 @@ function processArgs() {
197198
createUpdatePipeline = false;
198199
inputCDKpipeline = false;
199200
break;
201+
case '-ase':
202+
case '--create-update-apollo-server-neptune-endpoint':
203+
createUpdateApolloServerEndpoint = array[index + 1];
204+
break;
200205
case '-p':
201206
case '--create-update-aws-pipeline':
202207
createUpdatePipeline = true;
@@ -314,10 +319,15 @@ function createOutputFolder() {
314319

315320
function validateArgs() {
316321
// TODO more argument validation
317-
if (queryClient !== 'http' && (createUpdateApolloServerSubgraph || createUpdateApolloServer)) {
322+
const createApollo = createUpdateApolloServerSubgraph || createUpdateApolloServer;
323+
if (queryClient !== 'http' && createApollo) {
318324
console.error(`Neptune querying using ${queryClient} is not yet supported for Apollo Server. Please use option --output-resolver-query-https.`);
319325
process.exit(1);
320326
}
327+
if (createApollo && !inputGraphDBSchemaNeptuneEndpoint && !createUpdateApolloServerEndpoint) {
328+
console.error(`Apollo artifact creation requires a neptune endpoint. Please specify option --input-graphdb-schema-neptune-endpoint or --create-update-apollo-server-neptune-endpoint.`);
329+
process.exit(1);
330+
}
321331
}
322332

323333
async function main() {
@@ -351,7 +361,12 @@ async function main() {
351361
let neptuneInfo;
352362
// Check if any of the Neptune endpoints are a neptune analytic endpoint and if so, set the neptuneType and IAM to required
353363
// only one of these endpoints are expected to be non-empty at the same time
354-
const nonEmptyEndpoints = [inputGraphDBSchemaNeptuneEndpoint, createUpdatePipelineEndpoint, inputCDKpipelineEndpoint].filter(endpoint => endpoint !== '');
364+
const nonEmptyEndpoints = [
365+
inputGraphDBSchemaNeptuneEndpoint,
366+
createUpdatePipelineEndpoint,
367+
inputCDKpipelineEndpoint,
368+
createUpdateApolloServerEndpoint
369+
].filter(endpoint => endpoint !== '');
355370
if (nonEmptyEndpoints.length > 0) {
356371
neptuneInfo = parseNeptuneEndpoint(nonEmptyEndpoints[0]);
357372
neptuneType = neptuneInfo.neptuneType;

src/zipPackage.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export async function createApolloDeploymentPackage({zipFilePath, resolverFilePa
8181
includePaths: [
8282
{source: path.join(modulePath, '/../templates/ApolloServer')},
8383
{source: resolverFilePath, target: 'output.resolver.graphql.js'},
84-
{source: schemaFilePath, target: 'schema.graphql'},
84+
{source: schemaFilePath, target: 'output.schema.graphql'},
8585
// querying neptune using SDK not yet supported
8686
{source: path.join(modulePath, '/../templates/queryHttpNeptune.mjs')}
8787
],

templates/ApolloServer/index.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import dotenv from 'dotenv';
88

99
dotenv.config();
1010

11-
const typeDefs = gql(readFileSync('./schema.graphql', 'utf-8'));
11+
const typeDefs = gql(readFileSync('./output.schema.graphql', 'utf-8'));
1212
const queryDefinition = typeDefs.definitions.find(
1313
definition => definition.kind === 'ObjectTypeDefinition' && definition.name.value === 'Query'
1414
);

templates/ApolloServer/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "neptune-apollo-server",
3-
"version": "1.0.0",
3+
"version": "1.2.0-beta.1",
44
"description": "",
55
"license": "Apache-2.0",
66
"author": "",

templates/JSResolverOCHTTPSTemplate.js

+13-11
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ express or implied. See the License for the specific language governing
1010
permissions and limitations under the License.
1111
*/
1212

13-
import { astFromValue, buildASTSchema, typeFromAST, GraphQLID } from 'graphql';
13+
import { astFromValue, buildASTSchema, typeFromAST, GraphQLID, GraphQLInputObjectType } from 'graphql';
1414
import { gql } from 'graphql-tag'; // GraphQL library to parse the GraphQL query
1515

1616
const useCallSubquery = false;
@@ -36,7 +36,7 @@ export function resolveGraphDBQueryFromAppSyncEvent(event) {
3636
return resolveGraphDBQueryFromEvent({
3737
field: event.field,
3838
arguments: event.arguments,
39-
selectionSet: gql`${event.selectionSetGraphQL}`.definitions[0].selectionSet
39+
selectionSet: event.selectionSetGraphQL ? gql`${event.selectionSetGraphQL}`.definitions[0].selectionSet : {}
4040
});
4141
}
4242

@@ -58,15 +58,17 @@ export function resolveGraphDBQueryFromEvent(event) {
5858
if (value) {
5959
const inputType = typeFromAST(schema, inputDef.type);
6060
const astValue = astFromValue(value, inputType);
61-
// retrieve an ID field which may not necessarily be named 'id'
62-
const idField = Object.values(inputType.getFields()).find(field => field.type.name === GraphQLID.name);
63-
if (idField) {
64-
// check if id was an input arg
65-
const idValue = astValue.fields.find(f => f.name.value === idField.name);
66-
if (idValue?.value?.kind === 'IntValue') {
67-
// graphql astFromValue function can convert ID integer strings into integer type
68-
// if input args contain an id and the graphql library has interpreted the value as an int, change it to treat the value as a string
69-
idValue.value.kind = 'StringValue';
61+
if (inputType instanceof GraphQLInputObjectType) {
62+
// retrieve an ID field which may not necessarily be named 'id'
63+
const idField = Object.values(inputType.getFields()).find(field => field.type.name === GraphQLID.name);
64+
if (idField) {
65+
// check if id was an input arg
66+
const idValue = astValue.fields.find(f => f.name.value === idField.name);
67+
if (idValue?.value?.kind === 'IntValue') {
68+
// graphql astFromValue function can convert ID integer strings into integer type
69+
// if input args contain an id and the graphql library has interpreted the value as an int, change it to treat the value as a string
70+
idValue.value.kind = 'StringValue';
71+
}
7072
}
7173
}
7274
args.push({

templates/Lambda4AppSyncGraphSDK/package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

templates/Lambda4AppSyncGraphSDK/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "lambda4appsyncgraphsdk",
3-
"version": "1.1.1",
3+
"version": "1.2.0-beta.1",
44
"description": "AWS Lambda function to bridge AppSync to Neptune Analytics",
55
"main": "index.mjs",
66
"type": "module",

templates/Lambda4AppSyncHTTP/package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

templates/Lambda4AppSyncHTTP/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "lambda4appsynchttp",
3-
"version": "1.1.1",
3+
"version": "1.2.0-beta.1",
44
"description": "AWS Lambda function to bridge AppSync to Neptune or Neptune Analytics",
55
"main": "index.mjs",
66
"type": "module",

templates/Lambda4AppSyncSDK/package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

templates/Lambda4AppSyncSDK/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "lambda4appsyncsdk",
3-
"version": "1.1.1",
3+
"version": "1.2.0-beta.1",
44
"description": "AWS Lambda function to bridge AppSync to Neptune",
55
"main": "index.mjs",
66
"type": "module",

test/TestCases/Case01/Case01.05.test.js

+30
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,36 @@ describe('AppSync resolver', () => {
105105
});
106106
});
107107

108+
test('should resolve gremlin query with argument', () => {
109+
const result = resolve({
110+
field: 'getAirportWithGremlin',
111+
arguments: { code: 'YVR' },
112+
selectionSetGraphQL: '{ city }'
113+
});
114+
115+
expect(result).toMatchObject({
116+
query: "g.V().has('airport', 'code', 'YVR').elementMap()",
117+
parameters: {},
118+
language: 'gremlin',
119+
refactorOutput: null
120+
});
121+
});
122+
123+
test('should resolve gremlin query without arguments or selection set', () => {
124+
const result = resolve({
125+
field: 'getCountriesCount',
126+
arguments: { },
127+
selectionSetGraphQL: ''
128+
});
129+
130+
expect(result).toMatchObject({
131+
query: "g.V().hasLabel('country').count()",
132+
parameters: {},
133+
language: 'gremlin',
134+
refactorOutput: null
135+
});
136+
});
137+
108138
function resolve(event) {
109139
return resolverModule.resolveGraphDBQueryFromAppSyncEvent(event);
110140
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
[
22
{ "type": "Airport", "field": "outboundRoutesCountAdd", "action": "add", "value":"outboundRoutesCountAdd: Int @graphQuery(statement: \"MATCH (this)-[r:route]->(a) RETURN count(r)\")"},
33
{ "type": "Mutation", "field": "deleteNodeVersion", "action": "remove", "value": "" },
4-
{ "type": "Mutation", "field": "createNodeVersion", "action": "remove", "value": "" }
4+
{ "type": "Mutation", "field": "createNodeVersion", "action": "remove", "value": "" },
5+
{ "type": "Query", "field": "getAirportWithGremlin", "action": "add", "value":"getAirportWithGremlin(code:String): Airport @graphQuery(statement: \"g.V().has('airport', 'code', '$code').elementMap()\")"},
6+
{ "type": "Query", "field": "getCountriesCount", "action": "add", "value":"getCountriesCount: Int @graphQuery(statement: \"g.V().hasLabel('country').count()\")"}
57
]

0 commit comments

Comments
 (0)