Skip to content

Commit 8e28858

Browse files
committed
Added annotations (edge fields) and implemented these in creation and updating of objects
1 parent 2ebf233 commit 8e28858

File tree

2 files changed

+91
-33
lines changed

2 files changed

+91
-33
lines changed

graphql-api-generator/utils/utils.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,19 @@ def extend_connect(schema: GraphQLSchema, _type: GraphQLType, field_type: GraphQ
354354
make += f'{create_field} : {create_implementing_type} '
355355
else:
356356
make += f'create: {create_name} '
357+
358+
# Get annotations
359+
edge_from = f'{capitalize(field_name)}EdgeFrom{_type.name}'
360+
annotate_input = f'_InputToAnnotate{edge_from}'
361+
annotations = get_field_annotations(_type.fields[field_name])
362+
363+
if len(annotations) > 0:
364+
# Make sure the annotation type actually exists first, and prevent us from defining it multiple times
365+
if annotate_input not in schema.type_map:
366+
schema = add_to_schema(schema, f'input {annotate_input}{{{annotations}}}')
367+
368+
make += f'annotations: {annotate_input}'
369+
357370
make += '}'
358371
schema = add_to_schema(schema, make)
359372
return schema
@@ -653,7 +666,10 @@ def add_input_to_create_edge_objects(schema: GraphQLSchema):
653666

654667
if len(annotations) > 0:
655668
make += f'input {edge_input} {{sourceID: ID! targetID: ID! annotations: {annotate_input} }}\n'
656-
make += f'input {annotate_input}{{{annotations}}}\n'
669+
670+
# Make sure the annotation type actually exists first, and prevent us from defining it multiple times
671+
if annotate_input not in schema.type_map:
672+
make += f'input {annotate_input}{{{annotations}}}\n'
657673
else:
658674
make += f'input {edge_input} {{sourceID: ID! targetID: ID!}}\n'
659675

graphql-server/drivers/arangodb/driver.js

Lines changed: 74 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,28 @@ function getObjectOrInterfaceFields(type) {
240240
return keys;
241241
}
242242

243+
/**
244+
* Get the annotations of an edge and convert them to string to be used in insert operations
245+
* Would have prefered using createEdge, but we seem unable to pass a properly accesible "context variable"._id as parameter
246+
* @param annotations
247+
* @returns String
248+
*/
249+
function getAnnotations(annotations) {
250+
// Remeber that annotations should already have passed through getScalarsAndEnums
251+
// So injections on field should not be possible
252+
// The values need to be checked separatly.
253+
let ret = '';
254+
for (let field in annotations) {
255+
if (typeof annotations[field] === 'string') {
256+
// Treat strings as strings
257+
ret += `, ${field}: "${annotations[field]}"`;
258+
}
259+
else
260+
ret += `, ${field}: ${annotations[field]}`;
261+
}
262+
return ret;
263+
}
264+
243265
// ----------------------------------------------------------
244266

245267
async function getEdge(parent, args, info){
@@ -301,15 +323,15 @@ async function getEdge(parent, args, info){
301323
/*
302324
TODO: We should probably call the createEdge function here (when we've defined it).
303325
*/
304-
async function create(isRoot, ctxt, data, returnType, info){
326+
async function create(isRoot, ctxt, data, returnType, info) {
305327
// define transaction object
306-
if(ctxt.trans === undefined) ctxt.trans = initTransaction();
328+
if (ctxt.trans === undefined) ctxt.trans = initTransaction();
307329

308330
// is root op and mutatation is already queued
309-
if(isRoot && ctxt.trans.queue[info.path.key]){
310-
if(ctxt.trans.open) await executeTransaction(ctxt);
311-
if(ctxt.trans.error){
312-
if(ctxt.trans.errorReported) return null;
331+
if (isRoot && ctxt.trans.queue[info.path.key]) {
332+
if (ctxt.trans.open) await executeTransaction(ctxt);
333+
if (ctxt.trans.error) {
334+
if (ctxt.trans.errorReported) return null;
313335
ctxt.trans.errorReported = true;
314336
throw ctxt.trans.error;
315337
}
@@ -338,18 +360,26 @@ async function create(isRoot, ctxt, data, returnType, info){
338360

339361
// for edges
340362
let ob = getTypesAndInterfaces(data, returnType);
341-
for(let fieldName in ob){
363+
for (let fieldName in ob) {
342364
let innerFieldType = graphql.getNamedType(returnType.getFields()[fieldName].type);
343365
let edge = getEdgeCollectionName(returnType.name, fieldName);
344366
ctxt.trans.write.add(edge);
345367
let edgeCollection = asAQLVar(`db.${edge}`);
346368
let values = Array.isArray(ob[fieldName]) ? ob[fieldName] : [ob[fieldName]]; // treat as list even if only one value is present
347369

348-
for(let i in values){
370+
for (let i in values) {
349371
let value = values[i];
350372
console.log(value);
351-
if(graphql.isInterfaceType(innerFieldType)){ // interface
352-
if(value['connect']){
373+
374+
// Prepare annotations
375+
let annotations = null;
376+
if (value['annotations']) {
377+
annotations = getScalarsAndEnums(value['annotations'], info.schema.getType("_InputToAnnotate" + edge));
378+
annotations['_creationDate'] = date.valueOf();
379+
}
380+
381+
if (graphql.isInterfaceType(innerFieldType)) { // interface
382+
if (value['connect']) {
353383
validateType(ctxt, value['connect'], innerFieldType, info.schema);
354384
let typeToConnect = value['connect'].split('/')[0];
355385
// add edge
@@ -361,10 +391,14 @@ async function create(isRoot, ctxt, data, returnType, info){
361391
} else {
362392
// create
363393
let key = Object.keys(value)[0];
394+
if (key == "annotations") {
395+
// In case the user actually specifies the annotations before the edge
396+
key = Object.keys(value)[1];
397+
}
364398
let typeToCreate = key.replace(/^create(.+)$/, '$1');
365-
let to = asAQLVar(getVar(ctxt)); // reference to the object to be added
399+
let to = asAQLVar(getVar(ctxt)); // reference to the object to be addedd
366400
await create(false, ctxt, value[key], info.schema.getType(typeToCreate), info);
367-
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: ${from}._id, _to: ${to}._id } IN ${edgeCollection} RETURN NEW\`);`);
401+
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: ${from}._id, _to: ${to}._id ${getAnnotations(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
368402
}
369403
} else { // type
370404
if (value['connect']) {
@@ -376,10 +410,11 @@ async function create(isRoot, ctxt, data, returnType, info){
376410
ctxt.trans.code.push(`} else { `);
377411
ctxt.trans.code.push(` throw "${value['connect']} does not exist in ${typeToConnect}";`);
378412
ctxt.trans.code.push(`}`);
379-
} else {// create
413+
} else {//
380414
let to = asAQLVar(getVar(ctxt)); // reference to the object to be added
381415
await create(false, ctxt, value['create'], innerFieldType, info);
382-
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: ${from}._id, _to: ${to}._id } IN ${edgeCollection} RETURN NEW\`);`);
416+
console.log(innerFieldType.name);
417+
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: ${from}._id, _to: ${to}._id ${getAnnotations(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
383418
}
384419
}
385420
}
@@ -389,7 +424,7 @@ async function create(isRoot, ctxt, data, returnType, info){
389424
addFinalDirectiveChecksForType(ctxt, returnType, aql`${asAQLVar(resVar)}._id`, info.schema);
390425

391426
// overwrite the current action
392-
if(isRoot) {
427+
if (isRoot) {
393428
ctxt.trans.code.push(`result['${info.path.key}'] = ${resVar};`); // add root result
394429
ctxt.trans.queue[info.path.key] = true; // indicate that this mutation op has been added to the transaction
395430
getVar(ctxt); // increment varCounter
@@ -549,15 +584,15 @@ function validateType(ctxt, id, type, schema){
549584
}
550585
}
551586

552-
async function update(isRoot, ctxt, id, data, returnType, info){
587+
async function update(isRoot, ctxt, id, data, returnType, info) {
553588
// define transaction object
554-
if(ctxt.trans === undefined) ctxt.trans = initTransaction();
589+
if (ctxt.trans === undefined) ctxt.trans = initTransaction();
555590

556591
// is root op and mutation is already queued
557-
if(isRoot && ctxt.trans.queue[info.path.key]){
558-
if(ctxt.trans.open) await executeTransaction(ctxt);
559-
if(ctxt.trans.error){
560-
if(ctxt.trans.errorReported) return null;
592+
if (isRoot && ctxt.trans.queue[info.path.key]) {
593+
if (ctxt.trans.open) await executeTransaction(ctxt);
594+
if (ctxt.trans.error) {
595+
if (ctxt.trans.errorReported) return null;
561596
ctxt.trans.errorReported = true;
562597
throw ctxt.trans.error;
563598
}
@@ -569,24 +604,24 @@ async function update(isRoot, ctxt, id, data, returnType, info){
569604
// 3) Add key check to transaction
570605
let keyName = getKeyName(returnType.name);
571606
let keyType = info.schema["_typeMap"][keyName];
572-
if(keyType){
607+
if (keyType) {
573608
try {
574609
let collection = db.collection(returnType);
575610
const cursor = await db.query(aql`FOR i IN ${collection} FILTER(i._id == ${id}) RETURN i`);
576611
let doc = await cursor.next();
577-
if(doc == undefined){
612+
if (doc == undefined) {
578613
throw new ApolloError(`ID ${id} is not a document in the type ${returnType}`);
579614
}
580615

581616
let key = {};
582-
for(let f in keyType._fields){
617+
for (let f in keyType._fields) {
583618
key[f] = doc[f];
584-
if(data[f] !== undefined){
619+
if (data[f] !== undefined) {
585620
key[f] = data[f];
586621
}
587622
}
588623
validateKey(ctxt, key, returnType, info.schema, id);
589-
} catch(err) {
624+
} catch (err) {
590625
throw new ApolloError(err);
591626
}
592627
}
@@ -619,6 +654,13 @@ async function update(isRoot, ctxt, id, data, returnType, info){
619654
for (let i in values) {
620655
let value = values[i];
621656

657+
// Prepare annotations
658+
let annotations = null;
659+
if (value['annotations']) {
660+
annotations = getScalarsAndEnums(value['annotations'], info.schema.getType("_InputToAnnotate" + edge));
661+
annotations['_creationDate'] = date.valueOf();
662+
}
663+
622664
if (graphql.isInterfaceType(nestedReturnType)) {
623665
// interface field
624666
if (value['connect']) {
@@ -630,11 +672,11 @@ async function update(isRoot, ctxt, id, data, returnType, info){
630672
// add edge
631673
if (!disableEdgeValidation) { // check the database
632674
ctxt.trans.code.push(`if(db._collection('${typeToConnect}').exists('${value['connect']}')){`);
633-
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" } IN ${edgeCollection} RETURN NEW\`);`);
675+
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" ${getAnnotations(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
634676
ctxt.trans.code.push(`} else { throw "${value['connect']} does not exist in ${typeToConnect}"; }`);
635677
} else {
636678
console.warn(`Adding connection to ${value['connect']} in ${edge} without validating ID`);
637-
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" } IN ${edgeCollection} RETURN NEW\`);`);
679+
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" ${getAnnotations(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
638680
}
639681
} else {
640682
// create
@@ -658,17 +700,17 @@ async function update(isRoot, ctxt, id, data, returnType, info){
658700
// add edge
659701
if (!disableEdgeValidation) { // check the database
660702
ctxt.trans.code.push(`if(db._collection('${typeToConnect}').exists('${value['connect']}')){`);
661-
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" } IN ${edgeCollection} RETURN NEW\`);`);
703+
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" ${getAnnotations(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
662704
ctxt.trans.code.push(`} else { throw "${value['connect']} does not exist in ${typeToConnect}"; }`);
663705
} else {
664706
console.warn(`Adding connection to ${value['connect']} in ${edge} without validating ID`);
665-
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" } IN ${edgeCollection} RETURN NEW\`);`);
707+
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" ${getAnnotations(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
666708
}
667709
} else {
668710
// create
669711
let to = asAQLVar(getVar(ctxt)); // reference to the object to be added
670712
await create(false, ctxt, value['create'], nestedReturnType, info);
671-
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: ${to}._id } IN ${edgeCollection} RETURN NEW\`);`);
713+
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: ${to}._id ${getAnnotations(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
672714
}
673715
}
674716
}
@@ -678,7 +720,7 @@ async function update(isRoot, ctxt, id, data, returnType, info){
678720
addFinalDirectiveChecksForType(ctxt, returnType, aql`${asAQLVar(resVar)}._id`, info.schema);
679721

680722
// overwrite the current action
681-
if(isRoot) {
723+
if (isRoot) {
682724
ctxt.trans.code.push(`result['${info.path.key}'] = ${resVar}.new;`); // add root result
683725
ctxt.trans.queue[info.path.key] = true; // indicate that this mutation op has been added to the transaction
684726
getVar(ctxt); // increment varCounter

0 commit comments

Comments
 (0)