From 8f3240f62af0eefa330bbf9f775decfabcb31c4b Mon Sep 17 00:00:00 2001 From: leier318 Date: Thu, 14 May 2020 17:23:01 +0200 Subject: [PATCH 01/14] Think we have a working @noloops directive now --- graphql-server/drivers/arangodb/driver.js | 411 +++++++++++++--------- 1 file changed, 235 insertions(+), 176 deletions(-) diff --git a/graphql-server/drivers/arangodb/driver.js b/graphql-server/drivers/arangodb/driver.js index 39c4370..1189e18 100644 --- a/graphql-server/drivers/arangodb/driver.js +++ b/graphql-server/drivers/arangodb/driver.js @@ -8,18 +8,19 @@ let db; let disableEdgeValidation; module.exports = { - init: async function(schema){ - let db_name = process.env.db ? process.env.db: 'dev-db'; + init: async function (schema) { + let db_name = process.env.db ? process.env.db : 'dev-db'; let url = process.env.URL ? process.env.URL : 'http://localhost:8529'; let drop = process.env.DROP === 'true'; + drop = true; // TODO: Remove disableEdgeValidation = process.env.DISABLE_EDGE_VALIDATION === 'true'; db = new arangojs.Database({ url: url }); // wait for ArangoDB console.log(`Waiting for ArangoDB to become available at ${url}`); - let urlGet = url.replace(/^http(s?)(.+$)/,'http$1-get$2'); + let urlGet = url.replace(/^http(s?)(.+$)/, 'http$1-get$2'); const opts = { - resources: [ urlGet ], + resources: [urlGet], delay: 1000, // initial delay in ms interval: 1000, // poll interval in ms followRedirect: true @@ -28,9 +29,9 @@ module.exports = { console.log(`ArangoDB is now available at ${url}`); // if drop is set - if(drop) { + if (drop) { await db.dropDatabase(db_name).then( - (msg) => console.info(`Database ${db_name} deleted: ${! msg['error']}`), + (msg) => console.info(`Database ${db_name} deleted: ${!msg['error']}`), () => console.log() ); } @@ -39,25 +40,25 @@ module.exports = { await createEdgeCollections(db, schema); }, getConnection: () => db, - get: function(id, returnType, schema){ + get: function (id, returnType, schema) { return get(id, returnType, schema); }, - getByKey: function(key, info){ + getByKey: function (key, info) { return getByKey(key, info); }, - create: function(isRoot, context, data, returnType, info) { + create: function (isRoot, context, data, returnType, info) { return create(isRoot, context, data, returnType, info); }, - createEdge: async function(isRoot, ctxt, source, sourceType, sourceField, target, targetType, annotations, info) { + createEdge: async function (isRoot, ctxt, source, sourceType, sourceField, target, targetType, annotations, info) { return await createEdge(isRoot, ctxt, source, sourceType, sourceField, target, targetType, annotations, info); }, - update: async function(isRoot, ctxt, id, data, returnType, info){ + update: async function (isRoot, ctxt, id, data, returnType, info) { return await update(isRoot, ctxt, id, data, returnType, info); }, - getEdge: async function(parent, args, info){ + getEdge: async function (parent, args, info) { return await getEdge(parent, args, info) }, - getList: async function(args, info){ + getList: async function (args, info) { return await getList(args, info); }, getTotalCount: async function (parent, args, info) { @@ -66,20 +67,20 @@ module.exports = { isEndOfList: async function (parent, args, info) { return await isEndOfList(parent, args, info); }, - addPossibleEdgeTypes: function(query, schema, type_name, field_name){ + addPossibleEdgeTypes: function (query, schema, type_name, field_name) { return addPossibleEdgeTypes(query, schema, type_name, field_name); }, - getEdgeCollectionName: function(type, field){ + getEdgeCollectionName: function (type, field) { return getEdgeCollectionName(type, field); }, hello: () => hello() // TODO: Remove after testing }; -async function hello(){ +async function hello() { return "This is the arangodb.tools saying hello!" } -async function createAndUseDatabase(db, db_name){ +async function createAndUseDatabase(db, db_name) { await db.createDatabase(db_name).then( () => { console.info(`Database '${db_name}' created`); }, err => { console.warn(`Database '${db_name}' not created:`, err.response.body.errorMessage); } @@ -88,9 +89,9 @@ async function createAndUseDatabase(db, db_name){ } async function createTypeCollections(db, schema) { - const type_definitions = getTypeDefinitions(schema, kind='GraphQLObjectType'); + const type_definitions = getTypeDefinitions(schema, kind = 'GraphQLObjectType'); for (let collection_name in type_definitions) { - if(collection_name.startsWith('_') || collection_name.includes('EdgeFrom')){ + if (collection_name.startsWith('_') || collection_name.includes('EdgeFrom')) { continue; // skip } let collection = await db.collection(collection_name); @@ -103,11 +104,11 @@ async function createTypeCollections(db, schema) { } } -async function createEdgeCollections(db, schema){ +async function createEdgeCollections(db, schema) { let collections = []; - const type_definitions = getTypeDefinitions(schema, kind='GraphQLObjectType'); + const type_definitions = getTypeDefinitions(schema, kind = 'GraphQLObjectType'); for (let type_name in type_definitions) { - if(type_name.startsWith('_') || type_name.includes('EdgeFrom')){ + if (type_name.startsWith('_') || type_name.includes('EdgeFrom')) { continue; } let type = type_definitions[type_name]; @@ -115,9 +116,9 @@ async function createEdgeCollections(db, schema){ // collections for type and interface fields fields = getObjectOrInterfaceFields(type); - for(let i in fields){ + for (let i in fields) { let field_name = fields[i]; - if(field_name.startsWith('_')) { + if (field_name.startsWith('_')) { continue; } let collection_name = getEdgeCollectionName(type_name, field_name); @@ -126,7 +127,7 @@ async function createEdgeCollections(db, schema){ } // create collections - for(let i in collections) { + for (let i in collections) { let collection_name = collections[i]; let collection = await db.edgeCollection(collection_name); await collection.create().then( @@ -140,11 +141,11 @@ async function createEdgeCollections(db, schema){ } } -function getKeyName(type){ +function getKeyName(type) { return `_KeyFor${type}`; } -function getEdgeCollectionName(type, field){ +function getEdgeCollectionName(type, field) { let f = capitalizeFirstLetter(field); let t = capitalizeFirstLetter(type); // return `EdgeToConnect${f}Of${t}`; @@ -155,12 +156,12 @@ function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); } -function getTypeDefinitions(schema, kind=null) { +function getTypeDefinitions(schema, kind = null) { let types = {}; - for(let i in schema.getTypeMap()){ + for (let i in schema.getTypeMap()) { let type = schema.getType(i); let name = type.name; - if(name == 'Query' || name == 'Mutation'){ + if (name == 'Query' || name == 'Mutation') { continue; } if (kind == null || type.constructor.name == kind) { @@ -180,7 +181,7 @@ function getScalarOrEnumFields(type) { for (let i in type.getFields()) { let value = type.getFields()[i]; let t = graphql.getNamedType(value.type); - if(graphql.isEnumType(t) || graphql.isScalarType(t)){ + if (graphql.isEnumType(t) || graphql.isScalarType(t)) { keys.push(value.name); } } @@ -192,7 +193,7 @@ function getScalarOrEnumFields(type) { * scalars, enums, lists of scalars, and lists of enums. * @param object */ -function getScalarsAndEnums(object, type){ +function getScalarsAndEnums(object, type) { let doc = {}; return formatFixInput(doc, object, type); } @@ -202,13 +203,13 @@ function getScalarsAndEnums(object, type){ * GraphQL types, GraphQL interfaces, lists of GraphQL types, and lists of GraphQL interfaces. * @param object */ -function getTypesAndInterfaces(object, type){ +function getTypesAndInterfaces(object, type) { let doc = {}; for (let i in type.getFields()) { let field = type.getFields()[i]; let t = graphql.getNamedType(field.type); - if(graphql.isObjectType(t) || graphql.isInterfaceType(t)){ - if(object[field.name] !== undefined) { + if (graphql.isObjectType(t) || graphql.isInterfaceType(t)) { + if (object[field.name] !== undefined) { doc[field.name] = object[field.name]; } } @@ -227,7 +228,7 @@ function getObjectOrInterfaceFields(type) { for (let i in type.getFields()) { let value = type.getFields()[i]; let t = graphql.getNamedType(value.type); - if(graphql.isObjectType(t) || graphql.isInterfaceType(t)){ + if (graphql.isObjectType(t) || graphql.isInterfaceType(t)) { keys.push(value.name); } } @@ -236,12 +237,12 @@ function getObjectOrInterfaceFields(type) { // ---------------------------------------------------------- -async function getEdge(parent, args, info){ +async function getEdge(parent, args, info) { let parent_type = graphql.getNamedType(info.parentType); let return_type = graphql.getNamedType(info.returnType); let field_name = info.fieldName; - if(info.fieldName.startsWith('_')){ // reverse edge + if (info.fieldName.startsWith('_')) { // reverse edge let pattern_string = `^_(.+?)From${return_type.name}$`; // get the non-reversed edge name let re = new RegExp(pattern_string); field_name = re.exec(info.fieldName)[1]; @@ -249,19 +250,19 @@ async function getEdge(parent, args, info){ // Create query let query = [aql`FOR x IN`]; - if(info.fieldName.startsWith('_')) { + if (info.fieldName.startsWith('_')) { // If the type that is the origin of the edge is an interface, then we need to check all the edge collections // corresponding to its implementing types. Note: This is only necessary when traversing some edges that are // defined in in the API schema for interfaces. The parent type will never be an interface type at this stage. - if(graphql.isInterfaceType(return_type)){ + if (graphql.isInterfaceType(return_type)) { let possible_types = info.schema.getPossibleTypes(return_type); - if(possible_types.length > 1) query.push(aql`UNION(`); - for(let i in possible_types) { - if(i != 0) query.push(aql`,`); + if (possible_types.length > 1) query.push(aql`UNION(`); + for (let i in possible_types) { + if (i != 0) query.push(aql`,`); let collection = db.collection(getEdgeCollectionName(possible_types[i].name, field_name)); query.push(aql`(FOR i IN 1..1 INBOUND ${parent._id} ${collection} RETURN i)`); } - if(possible_types.length > 1) query.push(aql`)`); + if (possible_types.length > 1) query.push(aql`)`); } else { let collection = db.collection(getEdgeCollectionName(return_type.name, field_name)); @@ -274,9 +275,9 @@ async function getEdge(parent, args, info){ // add filters let query_filters = []; - if(args.filter != undefined && !isEmptyObject(args.filter)){ + if (args.filter != undefined && !isEmptyObject(args.filter)) { let filters = getFilters(args.filter, info); - for(let i in filters){ + for (let i in filters) { i == 0 ? query_filters.push(aql`FILTER`) : query_filters.push(aql`AND`); query_filters = query_filters.concat(filters[i]); } @@ -295,15 +296,27 @@ async function getEdge(parent, args, info){ /* TODO: We should probably call the createEdge function here (when we've defined it). */ -async function create(isRoot, ctxt, data, returnType, info){ +async function create(isRoot, ctxt, data, returnType, info) { // define transaction object - if(ctxt.trans === undefined) ctxt.trans = initTransaction(); + if (ctxt.trans === undefined) ctxt.trans = initTransaction(); + + // define postcode + // postcode will enforce all directives by adding checks, running after the mutations have been completed + if (ctxt.trans.postCode === undefined) ctxt.trans.postCode = []; // is root op and mutatation is already queued - if(isRoot && ctxt.trans.queue[info.path.key]){ - if(ctxt.trans.open) await executeTransaction(ctxt); - if(ctxt.trans.error){ - if(ctxt.trans.errorReported) return null; + if (isRoot && ctxt.trans.queue[info.path.key]) { + + if (ctxt.trans.open) { + // add all postcode to code before executing + //ctxt.trans.code.concat(ctxt.trans.postCode); // concat is not working? + for (let i in ctxt.trans.postCode) { + ctxt.trans.code.push(ctxt.trans.postCode[i]); + } + await executeTransaction(ctxt); + } + if (ctxt.trans.error) { + if (ctxt.trans.errorReported) return null; ctxt.trans.errorReported = true; throw ctxt.trans.error; } @@ -313,7 +326,6 @@ async function create(isRoot, ctxt, data, returnType, info){ // check key validateKey(ctxt, data, returnType, info.schema); - // Do not increment the var counter let resVar = getVar(ctxt, false); let from = asAQLVar(resVar); @@ -330,20 +342,21 @@ async function create(isRoot, ctxt, data, returnType, info){ ctxt.trans.params[docVar] = doc; ctxt.trans.code.push(`let ${resVar} = db._query(aql\`INSERT ${aqlDocVar} IN ${collection} RETURN NEW\`).next();`); + // for edges let ob = getTypesAndInterfaces(data, returnType); - for(let fieldName in ob){ + for (let fieldName in ob) { let innerFieldType = graphql.getNamedType(returnType.getFields()[fieldName].type); let edge = getEdgeCollectionName(returnType.name, fieldName); ctxt.trans.write.add(edge); let edgeCollection = asAQLVar(`db.${edge}`); let values = Array.isArray(ob[fieldName]) ? ob[fieldName] : [ob[fieldName]]; // treat as list even if only one value is present - for(let i in values){ + for (let i in values) { let value = values[i]; console.log(value); - if(graphql.isInterfaceType(innerFieldType)){ // interface - if(value['connect']){ + if (graphql.isInterfaceType(innerFieldType)) { // interface + if (value['connect']) { validateType(ctxt, value['connect'], innerFieldType, info.schema); let typeToConnect = value['connect'].split('/')[0]; // add edge @@ -379,8 +392,11 @@ async function create(isRoot, ctxt, data, returnType, info){ } } + // directives handling + addPostCodeDirectivesForTypes(ctxt, returnType, resVar); + // overwrite the current action - if(isRoot) { + if (isRoot) { ctxt.trans.code.push(`result['${info.path.key}'] = ${resVar};`); // add root result ctxt.trans.queue[info.path.key] = true; // indicate that this mutation op has been added to the transaction getVar(ctxt); // increment varCounter @@ -390,15 +406,15 @@ async function create(isRoot, ctxt, data, returnType, info){ return null; } -async function createEdge(isRoot, ctxt, source, sourceType, sourceField, target, targetType, annotations, info){ +async function createEdge(isRoot, ctxt, source, sourceType, sourceField, target, targetType, annotations, info) { // define transaction object - if(ctxt.trans === undefined) ctxt.trans = initTransaction(); + if (ctxt.trans === undefined) ctxt.trans = initTransaction(); // is root op and mutation is already queued - if(isRoot && ctxt.trans.queue[info.path.key]){ - if(ctxt.trans.open) await executeTransaction(ctxt); - if(ctxt.trans.error){ - if(ctxt.trans.errorReported) return null; + if (isRoot && ctxt.trans.queue[info.path.key]) { + if (ctxt.trans.open) await executeTransaction(ctxt); + if (ctxt.trans.error) { + if (ctxt.trans.errorReported) return null; ctxt.trans.errorReported = true; throw ctxt.trans.error; } @@ -409,11 +425,11 @@ async function createEdge(isRoot, ctxt, source, sourceType, sourceField, target, await validateEdge(ctxt, source, sourceType, sourceField, target, targetType, info); // variable reference to object that will be created - let resVar = getVar(ctxt,false); // Note: This should not increment the var counter, but use the previously allocated var name. + let resVar = getVar(ctxt, false); // Note: This should not increment the var counter, but use the previously allocated var name. // add doc let doc = {}; - if(annotations !== undefined) { + if (annotations !== undefined) { doc = annotations; } doc = formatFixInput(doc, doc, info.returnType); @@ -428,7 +444,7 @@ async function createEdge(isRoot, ctxt, source, sourceType, sourceField, target, ctxt.trans.code.push(`let ${resVar} = db._query(aql\`INSERT ${aqlDocVar} IN ${collection} RETURN NEW\`).next();`); // overwrite the current action - if(isRoot) { + if (isRoot) { ctxt.trans.code.push(`result['${info.path.key}'] = ${resVar};`); // add root result ctxt.trans.queue[info.path.key] = true; // indicate that this mutation op has been added to the transaction getVar(ctxt); // increment varCounter @@ -441,22 +457,22 @@ async function createEdge(isRoot, ctxt, source, sourceType, sourceField, target, async function validateEdge(ctxt, source, sourceType, sourceField, target, targetType, info) { let schema = info.schema; - if(!isOfType(source, sourceType, schema)) { + if (!isOfType(source, sourceType, schema)) { ctxt.trans.code.push(`throw \`Source object ${source} is not of type ${sourceType}\``); return } let sourceObject = await get(source, sourceType, schema); - if(sourceObject === undefined) { + if (sourceObject === undefined) { ctxt.trans.code.push(`throw \`Source object ${source} does not exist in collection ${sourceType}\``); return } - if(!isOfType(target, targetType, schema)) { + if (!isOfType(target, targetType, schema)) { ctxt.trans.code.push(`throw \`Target object ${target} is not of type ${targetType}\``); return } let targetObject = await get(target, targetType, schema); - if(targetObject === undefined) { + if (targetObject === undefined) { ctxt.trans.code.push(`throw \`Target object ${target} does not exist in collection ${targetType}\``); return } @@ -465,22 +481,22 @@ async function validateEdge(ctxt, source, sourceType, sourceField, target, targe let fieldType = schema.getType(sourceType).getFields()[sourceField].type; let collection = db.edgeCollection(getEdgeCollectionName(sourceType.name, sourceField)); let query = [aql`FOR x IN 1..1 OUTBOUND ${source} ${collection}`]; - if(graphql.isListType(fieldType)){ + if (graphql.isListType(fieldType)) { query.push(aql`FILTER(x._id == ${target})`); } query.push(aql`RETURN x`); const cursor = await db.query(aql.join(query)); let otherEdge = await cursor.next(); - if(otherEdge !== undefined) { + if (otherEdge !== undefined) { ctxt.trans.code.push(`throw \`Edge already exists for ${sourceField} from ${source}.\``); } } -function asAQLVar(varName){ +function asAQLVar(varName) { return '${' + varName + '}'; } -function initTransaction(){ +function initTransaction() { return { write: new Set(), params: {}, open: true, queue: {}, code: [ @@ -488,22 +504,23 @@ function initTransaction(){ 'const {aql} = require("@arangodb");', 'let result = Object.create(null);' ], + postCode: [], error: false }; } -async function executeTransaction(ctxt){ +async function executeTransaction(ctxt) { try { let action = `function(params){\n\t${ctxt.trans.code.join('\n\t')}\n\treturn result;\n}`; console.log(action); - ctxt.trans.results = await db.transaction({write: Array.from(ctxt.trans.write), read: []}, action, ctxt.trans.params); + ctxt.trans.results = await db.transaction({ write: Array.from(ctxt.trans.write), read: [] }, action, ctxt.trans.params); } catch (e) { ctxt.trans.error = new ApolloError(e.message); } ctxt.trans.open = false; } -function validateKey(ctxt, data, type, schema, id=undefined){ +function validateKey(ctxt, data, type, schema, id = undefined) { let collection = asAQLVar(`db.${type.name}`); let keyType = schema["_typeMap"][getKeyName(type.name)]; if (keyType) { @@ -522,25 +539,36 @@ function validateKey(ctxt, data, type, schema, id=undefined){ } } -function validateType(ctxt, id, type, schema){ - if(graphql.isInterfaceType(type)) { - if(!isImplementingType(id.split('/')[0], type, schema)) { +function validateType(ctxt, id, type, schema) { + if (graphql.isInterfaceType(type)) { + if (!isImplementingType(id.split('/')[0], type, schema)) { ctxt.trans.code.push(`throw "ID ${id} is not a document of the interface ${type}";`); } - } else if(id.split('/')[0] != type.name){ + } else if (id.split('/')[0] != type.name) { ctxt.trans.code.push(`throw "ID ${id} is not a document of the type ${type}";`); } } -async function update(isRoot, ctxt, id, data, returnType, info){ +async function update(isRoot, ctxt, id, data, returnType, info) { // define transaction object - if(ctxt.trans === undefined) ctxt.trans = initTransaction(); + if (ctxt.trans === undefined) ctxt.trans = initTransaction(); + + // define postcode + // postcode will enforce all directives by adding checks, running after the mutations have been completed + if (ctxt.trans.postCode === undefined) ctxt.trans.postCode = []; // is root op and mutation is already queued - if(isRoot && ctxt.trans.queue[info.path.key]){ - if(ctxt.trans.open) await executeTransaction(ctxt); - if(ctxt.trans.error){ - if(ctxt.trans.errorReported) return null; + if (isRoot && ctxt.trans.queue[info.path.key]) { + if (ctxt.trans.open) { + // add all postcode to code before executing + //ctxt.trans.code.concat(ctxt.trans.postCode); // concat is not working? + for (let i in ctxt.trans.postCode) { + ctxt.trans.code.push(ctxt.trans.postCode[i]); + } + await executeTransaction(ctxt); + } + if (ctxt.trans.error) { + if (ctxt.trans.errorReported) return null; ctxt.trans.errorReported = true; throw ctxt.trans.error; } @@ -552,24 +580,24 @@ async function update(isRoot, ctxt, id, data, returnType, info){ // 3) Add key check to transaction let keyName = getKeyName(returnType.name); let keyType = info.schema["_typeMap"][keyName]; - if(keyType){ + if (keyType) { try { let collection = db.collection(returnType); const cursor = await db.query(aql`FOR i IN ${collection} FILTER(i._id == ${id}) RETURN i`); let doc = await cursor.next(); - if(doc == undefined){ + if (doc == undefined) { throw new ApolloError(`ID ${id} is not a document in the type ${returnType}`); } let key = {}; - for(let f in keyType._fields){ + for (let f in keyType._fields) { key[f] = doc[f]; - if(data[f] !== undefined){ + if (data[f] !== undefined) { key[f] = data[f]; } } validateKey(ctxt, key, returnType, info.schema, id); - } catch(err) { + } catch (err) { throw new ApolloError(err); } } @@ -657,8 +685,11 @@ async function update(isRoot, ctxt, id, data, returnType, info){ } } + // directives handling + addPostCodeDirectivesForTypes(ctxt, returnType, resVar); + // overwrite the current action - if(isRoot) { + if (isRoot) { ctxt.trans.code.push(`result['${info.path.key}'] = ${resVar}.new;`); // add root result ctxt.trans.queue[info.path.key] = true; // indicate that this mutation op has been added to the transaction getVar(ctxt); // increment varCounter @@ -668,10 +699,10 @@ async function update(isRoot, ctxt, id, data, returnType, info){ return null; } -function asAqlArray(array){ +function asAqlArray(array) { let q = [aql`[`]; - for(let i in array){ - if(i != 0){ + for (let i in array) { + if (i != 0) { q.push(aql`,`); } q.push(aql`${array[i]}`); @@ -683,16 +714,18 @@ function asAqlArray(array){ /** * Converts all values of enum or scalar type in inputDoc to mach format used for storage in the database, * and return result in output map - * @param map to be changed and returned, map to be used as input, type - * @returns map given as output + * @param {map} outputDoc + * @param {map} inputDoc + * @param type + * @returns {map} outputDoc */ function formatFixInput(outputDoc, inputDoc, type) { // Adds scalar/enum values to outputDoc for (let i in type.getFields()) { let field = type.getFields()[i]; let t = graphql.getNamedType(field.type); - if(graphql.isEnumType(t) || graphql.isScalarType(t)){ - if(inputDoc[field.name] !== undefined) { + if (graphql.isEnumType(t) || graphql.isScalarType(t)) { + if (inputDoc[field.name] !== undefined) { outputDoc[field.name] = formatFixVariable(t, inputDoc[field.name]); } } @@ -702,16 +735,17 @@ function formatFixInput(outputDoc, inputDoc, type) { /** * Convert input data (value) to match format used for storage in the database - * @param type (of field), value + * @param type (of field) + * @param value * @returns value (in database ready format) */ function formatFixVariable(_type, v) { // DateTime has to be handled separately, which is currently the only reason for this function to exist if (_type == 'DateTime') // Arrays of DateTime needs special, special care. - if(Array.isArray(v)){ + if (Array.isArray(v)) { let newV = [] - for(let i in v) + for (let i in v) newV.push(aql`DATE_TIMESTAMP(${v})`); return newV; } @@ -728,7 +762,7 @@ function formatFixVariable(_type, v) { */ function formatFixVariableWrapper(field, info, v) { // no need to even try when we have _id as field - if(field == '_id') + if (field == '_id') return v; let namedReturnType = graphql.getNamedType(info.returnType.getFields()['content'].type) @@ -737,23 +771,23 @@ function formatFixVariableWrapper(field, info, v) { return formatFixVariable(_type, v); } -function getFilters(filter_arg, info){ +function getFilters(filter_arg, info) { let filters = []; - for(let i in filter_arg){ + for (let i in filter_arg) { let filter = filter_arg[i]; /// Rewrite id field - if(i == 'id'){ i = '_id'; } + if (i == 'id') { i = '_id'; } // AND expression - if(i == '_and'){ + if (i == '_and') { let f = []; f.push(aql`(`); - for(let x in filter) { - if(x != 0){ + for (let x in filter) { + if (x != 0) { f.push(aql`AND`); } let arr = getFilters(filter[x], info); - for(let j in arr){ + for (let j in arr) { f = f.concat(arr[j]); } } @@ -762,15 +796,15 @@ function getFilters(filter_arg, info){ } // OR expression - if(i == '_or'){ + if (i == '_or') { let f = []; f.push(aql`(`); - for(let x in filter) { - if(x != 0){ + for (let x in filter) { + if (x != 0) { f.push(aql`OR`); } let arr = getFilters(filter[x], info); - for(let j in arr){ + for (let j in arr) { f = f.concat(arr[j]); } } @@ -779,49 +813,49 @@ function getFilters(filter_arg, info){ } // NOT expression - if(i == '_not'){ + if (i == '_not') { let f = []; f.push(aql`NOT (`); let arr = getFilters(filter, info); - for(let j in arr){ + for (let j in arr) { f = f.concat(arr[j]); } f.push(aql`)`); filters.push(f); } - if(filter._eq != null){ + if (filter._eq != null) { let preparedArg = formatFixVariableWrapper(i, info, filter._eq); filters.push([aql`x.${i} == ${preparedArg}`]); } - if(filter._neq != null){ + if (filter._neq != null) { let preparedArgs = formatFixVariableWrapper(i, info, filter._neq); filters.push([aql`x.${i} != ${preparedArgs}`]); } - if(filter._gt != null){ + if (filter._gt != null) { let preparedArgs = formatFixVariableWrapper(i, info, filter._gt); filters.push([aql`x.${i} > ${preparedArgs}`]); } - if(filter._egt != null){ + if (filter._egt != null) { let preparedArgs = formatFixVariableWrapper(i, info, filter._egt); filters.push([aql`x.${i} >= ${preparedArgs}`]); } - if(filter._lt != null){ + if (filter._lt != null) { let preparedArgs = formatFixVariableWrapper(i, info, filter._lt); filters.push([aql`x.${i} < ${preparedArgs}`]); } - if(filter._elt != null){ + if (filter._elt != null) { let preparedArgs = formatFixVariableWrapper(i, info, filter._elt); filters.push([aql`x.${i} <= ${preparedArgs}`]); } - if(filter._in != null){ + if (filter._in != null) { let preparedArgs = formatFixVariableWrapper(i, info, filter._in) let q = []; q = q.concat([aql`x.${i} IN `]); q = q.concat(asAqlArray(preparedArgs)); filters.push(q); } - if(filter._nin != null){ + if (filter._nin != null) { let preparedArgs = formatFixVariableWrapper(i, info, filter._nin); let q = []; q = q.concat([aql`x.${i} NOT IN `]); @@ -829,19 +863,19 @@ function getFilters(filter_arg, info){ filters.push(q); } - if(filter._like != null){ + if (filter._like != null) { let preparedArgs = formatFixVariableWrapper(i, info, filter._like); filters.push([aql`LIKE(x.${i}, ${preparedArgs}, false)`]); } - if(filter._ilike != null){ + if (filter._ilike != null) { let preparedArgs = formatFixVariableWrapper(i, info, filter._ilike); filters.push([aql`LIKE(x.${i}, ${preparedArgs}, true)`]); } - if(filter._nlike != null){ + if (filter._nlike != null) { let preparedArgs = formatFixVariableWrapper(i, info, filter._nlike); filters.push([aql`NOT LIKE(x.${i}, ${preparedArgs}, false)`]); } - if(filter._nilike != null){ + if (filter._nilike != null) { let preparedArgs = formatFixVariableWrapper(i, info, filter._nilike); filters.push([aql`NOT LIKE(x.${i}, ${preparedArgs}, true)`]); } @@ -851,7 +885,7 @@ function getFilters(filter_arg, info){ return filters; } -async function getByKey(key, returnType){ +async function getByKey(key, returnType) { let type = graphql.getNamedType(returnType); let query = [aql`FOR x IN`]; let collection = db.collection(type.name); @@ -866,21 +900,21 @@ async function getByKey(key, returnType){ try { const cursor = await db.query(aql.join(query)); return await cursor.next(); - } catch(err) { + } catch (err) { //console.error(err); throw new ApolloError(err); } } -async function getList(args, info){ +async function getList(args, info) { let type = graphql.getNamedType(info.returnType.getFields()['content'].type); let first = args.first; let after = args.after; let query = [aql`FOR x IN FLATTEN(FOR i IN [`]; - if(graphql.isInterfaceType(type)){ + if (graphql.isInterfaceType(type)) { let possible_types = info.schema.getPossibleTypes(type); - for(let i in possible_types) { - if(i != 0){ + for (let i in possible_types) { + if (i != 0) { query.push(aql`,`); } let collection = db.collection(possible_types[i].name); @@ -894,9 +928,9 @@ async function getList(args, info){ // add filters let query_filters = []; - if(args.filter != undefined && !isEmptyObject(args.filter)){ + if (args.filter != undefined && !isEmptyObject(args.filter)) { let filters = getFilters(args.filter, info); - if(filters.length > 0) { + if (filters.length > 0) { query_filters.push(aql`FILTER`); for (let i in filters) { if (i != 0) { @@ -916,19 +950,19 @@ async function getList(args, info){ 'content': result }; return list; - } catch(err) { + } catch (err) { //console.error(err); throw new ApolloError(err); } } -async function isEndOfList(parent, args, info){ +async function isEndOfList(parent, args, info) { let type = graphql.getNamedType(info.parentType.getFields()['content'].type); let query = [aql`FOR x IN FLATTEN(FOR i IN [`]; - if(graphql.isInterfaceType(type)){ + if (graphql.isInterfaceType(type)) { let possible_types = info.schema.getPossibleTypes(type); - for(let i in possible_types) { - if(i != 0){ + for (let i in possible_types) { + if (i != 0) { query.push(aql`,`); } let collection = db.collection(possible_types[i].name); @@ -941,12 +975,12 @@ async function isEndOfList(parent, args, info){ query.push(aql`] RETURN i)`); // add filters - if(parent._filter){ + if (parent._filter) { query = query.concat(parent._filter); } // get last ID in parent content - if(parent.content.length != 0){ - const last = parent.content[parent.content.length-1]; + if (parent.content.length != 0) { + const last = parent.content[parent.content.length - 1]; query.push(aql`FILTER(x._id > ${last._id})`); } @@ -955,19 +989,19 @@ async function isEndOfList(parent, args, info){ const cursor = await db.query(aql.join(query)); const result = await cursor.next(); return result == 0; - } catch(err) { + } catch (err) { console.error(err); throw new ApolloError(err); } } -async function getTotalCount(parent, args, info){ +async function getTotalCount(parent, args, info) { let type = graphql.getNamedType(info.parentType.getFields()['content'].type); let query = [aql`FOR x IN FLATTEN(FOR i IN [`]; - if(graphql.isInterfaceType(type)){ + if (graphql.isInterfaceType(type)) { let possible_types = info.schema.getPossibleTypes(type); - for(let i in possible_types) { - if(i != 0){ + for (let i in possible_types) { + if (i != 0) { query.push(aql`,`); } let collection = db.collection(possible_types[i].name); @@ -980,7 +1014,7 @@ async function getTotalCount(parent, args, info){ query.push(aql`] RETURN i)`); // add filters - if(parent._filter){ + if (parent._filter) { query = query.concat(parent._filter); } @@ -988,7 +1022,7 @@ async function getTotalCount(parent, args, info){ try { const cursor = await db.query(aql.join(query)); return await cursor.next(); - } catch(err) { + } catch (err) { console.error(err); throw new ApolloError(err); } @@ -1001,22 +1035,22 @@ async function getTotalCount(parent, args, info){ * @param schema * @returns {Promise<*>} */ -async function get(id, returnType, schema){ +async function get(id, returnType, schema) { let type = returnType; let query = [aql`FOR i IN`]; - if(graphql.isInterfaceType(type)){ + if (graphql.isInterfaceType(type)) { let possible_types = schema.getPossibleTypes(type); - if(possible_types.length > 1){ + if (possible_types.length > 1) { query.push(aql`UNION(`); } - for(let i in possible_types) { - if(i != 0){ + for (let i in possible_types) { + if (i != 0) { query.push(aql`,`); } let collection = db.collection(possible_types[i].name); query.push(aql`(FOR x IN ${collection} FILTER(x._id == ${id}) RETURN x)`); } - if(possible_types.length > 1){ + if (possible_types.length > 1) { query.push(aql`)`); } } else { @@ -1028,7 +1062,7 @@ async function get(id, returnType, schema){ try { const cursor = await db.query(aql.join(query)); return await cursor.next(); - } catch(err) { + } catch (err) { console.error(err); throw new ApolloError(err); } @@ -1043,27 +1077,27 @@ function isEmptyObject(obj) { * @param ob * @param props */ -function pick(ob, props){ +function pick(ob, props) { let sub = {}; - for(let i in props) { + for (let i in props) { let prop = props[i]; - if(ob[prop] !== undefined) { + if (ob[prop] !== undefined) { sub[prop] = ob[prop]; } } return sub; } -function getVar(context, increment=true){ - if(context.varCounter === undefined){ +function getVar(context, increment = true) { + if (context.varCounter === undefined) { context.varCounter = 0; } - if(increment) context.varCounter++; + if (increment) context.varCounter++; return `x${context.varCounter}`; } -function getParamVar(context){ - if(context.paramCounter === undefined){ +function getParamVar(context) { + if (context.paramCounter === undefined) { context.paramCounter = 0; } context.paramCounter++; @@ -1076,13 +1110,13 @@ function getTypeNameFromId(id) { function isOfType(id, type, schema) { let idType = getTypeNameFromId(id); - if(type.name == idType || isImplementingType(idType, type, schema)){ - return true; + if (type.name == idType || isImplementingType(idType, type, schema)) { + return true; } return false; } -function isImplementingType(name, type_interface, schema){ +function isImplementingType(name, type_interface, schema) { let possible_types = schema.getPossibleTypes(type_interface); for (let i in possible_types) { if (possible_types[i].name == name) { @@ -1092,20 +1126,24 @@ function isImplementingType(name, type_interface, schema){ return false; } -function conditionalThrow(msg){ +function conditionalThrow(msg) { //console.warn(msg); - if(!disableEdgeValidation){ + if (!disableEdgeValidation) { throw msg; } } /** * Add all possible edge-collections for the given type and field to query - * @param query, schema, type_name, field_name, directionString (Optional) + * @param query (modifies) + * @param schema + * @param type_name + * @param field_name + * @param directionString (optional) */ -function addPossibleEdgeTypes(query, schema, type_name, field_name, directionString = ""){ +function addPossibleEdgeTypes(query, schema, type_name, field_name, directionString = "") { let type = schema._typeMap[type_name]; - if(graphql.isInterfaceType(type)){ + if (graphql.isInterfaceType(type)) { let possible_types = schema.getPossibleTypes(type); for (let i in possible_types) { if (i != 0) { @@ -1119,3 +1157,24 @@ function addPossibleEdgeTypes(query, schema, type_name, field_name, directionStr query.push(aql`${collection}`); } } + +/** + * Append postcode to ctxt for all directives of all fields in input type + * @param ctxt (modifies) + * @param type + * @param resVar + */ +function addPostCodeDirectivesForTypes(ctxt, type, resVar) { + for (let f in type.getFields()) { + let field = type.getFields()[f] + for (let d in field.astNode.directives) { + let dir = field.astNode.directives[d]; + if (dir.name.value == 'noloops') { + let collection = asAQLVar(`db.${getEdgeCollectionName(type.name, field.name)}`); + ctxt.trans.postCode.push(`if(db._query(aql\`FOR v, e, p IN 1..1 OUTBOUND ${asAQLVar(resVar)}._id ${collection} FILTER ${asAQLVar(resVar)}._id == v._id RETURN v\`).next()){`); + ctxt.trans.postCode.push(` throw "Field ${f} in ${type.name} is breaking a @noloops directive! ${resVar}";`); + ctxt.trans.postCode.push(`}`); + } + } + } +} \ No newline at end of file From fa019c32c2c320ce52e56977cd4c53644b9cbced Mon Sep 17 00:00:00 2001 From: leier318 Date: Thu, 28 May 2020 14:04:56 +0200 Subject: [PATCH 02/14] Working on the new printing for API schemas --- graphql-api-generator/generator.py | 8 +- graphql-api-generator/utils/utils.py | 213 ++++++++++++++++++++++++++- 2 files changed, 214 insertions(+), 7 deletions(-) diff --git a/graphql-api-generator/generator.py b/graphql-api-generator/generator.py index 9480fd3..19340aa 100644 --- a/graphql-api-generator/generator.py +++ b/graphql-api-generator/generator.py @@ -36,16 +36,16 @@ def cmd(args): with open(file, 'r') as f: schema_string += f.read() + '\n' schema = build_schema(schema_string) - + # run schema = run(schema, config) # write to file or stdout if args.output: with open(args.output, 'w') as out: - out.write(print_schema(schema)) + out.write(printSchemaWithDirectives(schema)) else: - print(print_schema(schema)) + print(printSchemaWithDirectives(schema)) def run(schema: GraphQLSchema, config: dict): @@ -138,7 +138,7 @@ def run(schema: GraphQLSchema, config: dict): raise UnsupportedOperation('{0} is currently not supported'.format('delete_edge_objects')) # remove field arguments for edges (should not be in the API schema) - schema = remove_field_arguments_for_types(schema) + #schema = remove_field_arguments_for_types(schema) return schema diff --git a/graphql-api-generator/utils/utils.py b/graphql-api-generator/utils/utils.py index 3b75de0..18f7854 100644 --- a/graphql-api-generator/utils/utils.py +++ b/graphql-api-generator/utils/utils.py @@ -202,14 +202,27 @@ def add_reverse_edges(schema: GraphQLSchema): # Reverse edge edge_from = get_named_type(field_type.type) edge_name = f'_{field_name}From{_type.name}' - edge_to = GraphQLList(_type) + + directives = {} + directive_to_add = '' + + if hasattr(field_type, 'ast_node') and field_type.ast_node is not None: + directives = {directive.name.value: directive for directive in field_type.ast_node.directives} + + if 'requiredForTarget' in directives: + directive_to_add = '@required' + + if 'uniqueForTarget' in directives: + edge_to = _type + else: + edge_to = GraphQLList(_type) if is_interface_type(edge_from): - make += 'extend interface {0} {{ {1}: {2} }}\n'.format(edge_from, edge_name, edge_to) + make += 'extend interface {0} {{ {1}: {2} {3} }}\n'.format(edge_from, edge_name, edge_to, directive_to_add) for implementing_type in schema.get_possible_types(edge_from): make += 'extend type {0} {{ {1}: {2} }}\n'.format(implementing_type, edge_name, edge_to) else: - make += 'extend type {0} {{ {1}: {2} }}\n'.format(edge_from, edge_name, edge_to) + make += 'extend type {0} {{ {1}: {2} {3} }}\n'.format(edge_from, edge_name, edge_to, directive_to_add) schema = add_to_schema(schema, make) return schema @@ -751,3 +764,197 @@ def add_delete_mutations(schema: GraphQLSchema): make += f'extend type Mutation {{ {delete}(id: ID!): {_type.name} }} ' schema = add_to_schema(schema, make) return schema + + +def ast_type_to_string(_type: GraphQLType): + """ + Print the ast_type properly + :param _type: + :return: + """ + + # ast_nodes types behavies differnetly than for other types (as they are NodeTypes) + # So we can't use the normal functions + + + _post_str = '' + _pre_str = '' + # A, A!, [A!], [A]!, [A!]! + wrappers = [] + if isinstance(_type, NonNullTypeNode): + _post_str = '!' + _type = _type.type + if isinstance(_type, ListTypeNode): + _post_str = ']' + _post_str + _pre_str = '[' + _type = _type.type + if isinstance(_type, NonNullTypeNode): + _post_str = '!' + _post_str + _type = _type.type + + # Dig down to find the actual named node, should be the first one actually + name = _type + while not isinstance(name, NamedTypeNode): + name = name.type + name = name.name.value + + return _pre_str + name + _post_str + + +def directive_from_interface(directive, interface_name): + """ + Return the correct directive string fro directives inhertied from interfaces + :param directive: + :param interface_name: + :return string: + """ + directive_string = directive.name.value + + # The only two cases who needs special attention is @requiredForTarget and @uniqueForTarget + if directive_string == 'requiredForTarget': + directive_string = '_requiredForTarget_AccordingToInterface(interface: "' + interface_name + '")' + elif directive_string == 'uniqueForTarget': + directive_string = '_uniqueForTarget_AccordingToInterface(interface: "' + interface_name + '")' + else: + directive_string += get_directive_arguments(directive) + + return directive_string + + +def get_directive_arguments(directive): + """ + Get the arguments of the given directive as string + :param directive: + :return string: + """ + + output = '' + if len(directive.arguments) > 0: + output+= '(' + for arg in directive.arguments: + output+= arg.name.value + ':' + if isinstance(arg.value, ListValueNode): + output+= '[' + for V in arg.value.values: + if isinstance(V, StringValueNode): + output+='"' + V.value + '", ' + else: + output+= V.value + ', ' + + output = output[:-2] + ']' + + else: + if isinstance(arg.value, StringValueNode): + output+='"' + arg.value.value + '", ' + else: + output+= arg.value.value + ', ' + + output += ', ' + + output = output[:-2] + ')' + + return output + + +def printSchemaWithDirectives(schema): + + output = '' + + for _dir in schema.directives: + if _dir.ast_node is not None: + # If the directive does not have a proper ast_node + # Then it is an non-user defined directive, and can hence, be skipped + output+= 'directive @' + _dir.name + + if len(_dir.ast_node.arguments) > 0: + output+= '(' + for arg in _dir.ast_node.arguments: + output+= arg.name.value + ': ' + ast_type_to_string(arg.type) + ', ' + output = output[:-2] + ')' + + output+= ' on ' + for _location in _dir.locations: + output+= _location._name_ + ', ' + + output = output[:-2] + '\n\n' + + + output += 'directive @_requiredForTarget_AccordingToInterface(interface: String!) on FIELD_DEFINITION\n\n' + output += 'directive @_uniqueForTarget_AccordingToInterface(interface: String!) on FIELD_DEFINITION\n\n' + + + for _type in sorted(schema.type_map.values(), key=lambda x : x.name): + if _type.name[:2] == '__': + continue + + if is_interface_type(_type): + output += 'interface ' + _type.name + elif is_enum_type(_type): + output += 'enum ' + _type.name + elif is_scalar_type(_type): + if _type.ast_node is not None: + # If the scalar does not have a proper ast_node + # Then it is an non-user defined scalar, and can hence, be skipped + output += 'scalar ' + _type.name + elif is_input_type(_type): + output += 'input ' + _type.name + else: # type, hopefully + output += 'type ' + _type.name + if hasattr(_type, 'interfaces') and len(_type.interfaces) > 0: + output += ' implements ' + for interface in _type.interfaces: + output += interface.name + ', ' + output = output[:-2] + + if is_enum_type(_type): + output += ' {\n' + for value in _type.values: + output += ' ' + value + '\n' + output += '}' + + elif not is_enum_or_scalar(_type): + if _type.ast_node is not None: + for directive in _type.ast_node.directives: + output+= ' @' + directive.name.value + output += get_directive_arguments(directive) + + output += ' {\n' + + for field_name, field in _type.fields.items(): + output += ' ' + field_name + + if hasattr(field, 'args') and field.args: + output += '(' + for arg_name, arg in field.args.items(): + output += arg_name + ': ' + str(arg.type) + ', ' + output = output[:-2] + ')' + + output += ': ' + str(field.type) + + directives_set = set() + + for directive in field.ast_node.directives: + if not directive.name.value in directives_set: + output+= ' @' + directive.name.value + directives_set.add(directive.name.value) + output += get_directive_arguments(directive) + + + if hasattr(_type, 'interfaces'): + for interface in _type.interfaces: + if field_name in interface.fields: + for directive in interface.fields[field_name].ast_node.directives: + directive_str = directive_from_interface(directive, interface.name) + if not directive_str in directives_set: + output+= ' @' + directive_str + directives_set.add(directive_str) + + + output += '\n' + + output += '}' + + if _type.ast_node is not None: + output += '\n\n' + + return output From afb10cfaea7c2ca5a7bba0a2f956fa839bca8c64 Mon Sep 17 00:00:00 2001 From: leier318 Date: Thu, 28 May 2020 16:08:10 +0200 Subject: [PATCH 03/14] Updated drivers.js with full directives checks --- graphql-api-generator/utils/utils.py | 6 +- graphql-server/drivers/arangodb/driver.js | 547 ++++++++++++---------- 2 files changed, 314 insertions(+), 239 deletions(-) diff --git a/graphql-api-generator/utils/utils.py b/graphql-api-generator/utils/utils.py index 18f7854..7afd950 100644 --- a/graphql-api-generator/utils/utils.py +++ b/graphql-api-generator/utils/utils.py @@ -773,7 +773,7 @@ def ast_type_to_string(_type: GraphQLType): :return: """ - # ast_nodes types behavies differnetly than for other types (as they are NodeTypes) + # ast_nodes types behavies differently than other types (as they are NodeTypes) # So we can't use the normal functions @@ -803,7 +803,7 @@ def ast_type_to_string(_type: GraphQLType): def directive_from_interface(directive, interface_name): """ - Return the correct directive string fro directives inhertied from interfaces + Return the correct directive string from directives inhertied from interfaces :param directive: :param interface_name: :return string: @@ -834,6 +834,7 @@ def get_directive_arguments(directive): for arg in directive.arguments: output+= arg.name.value + ':' if isinstance(arg.value, ListValueNode): + # List output+= '[' for V in arg.value.values: if isinstance(V, StringValueNode): @@ -844,6 +845,7 @@ def get_directive_arguments(directive): output = output[:-2] + ']' else: + # Non-list if isinstance(arg.value, StringValueNode): output+='"' + arg.value.value + '", ' else: diff --git a/graphql-server/drivers/arangodb/driver.js b/graphql-server/drivers/arangodb/driver.js index 1189e18..839e8cd 100644 --- a/graphql-server/drivers/arangodb/driver.js +++ b/graphql-server/drivers/arangodb/driver.js @@ -8,19 +8,18 @@ let db; let disableEdgeValidation; module.exports = { - init: async function (schema) { + init: async function(schema){ let db_name = process.env.db ? process.env.db : 'dev-db'; - let url = process.env.URL ? process.env.URL : 'http://localhost:8529'; + let url = process.env.URL ? process.env.URL: 'http://localhost:8529'; let drop = process.env.DROP === 'true'; - drop = true; // TODO: Remove disableEdgeValidation = process.env.DISABLE_EDGE_VALIDATION === 'true'; db = new arangojs.Database({ url: url }); // wait for ArangoDB console.log(`Waiting for ArangoDB to become available at ${url}`); - let urlGet = url.replace(/^http(s?)(.+$)/, 'http$1-get$2'); + let urlGet = url.replace(/^http(s?)(.+$)/,'http$1-get$2'); const opts = { - resources: [urlGet], + resources:[urlGet], delay: 1000, // initial delay in ms interval: 1000, // poll interval in ms followRedirect: true @@ -29,9 +28,8 @@ module.exports = { console.log(`ArangoDB is now available at ${url}`); // if drop is set - if (drop) { + if(drop) { await db.dropDatabase(db_name).then( - (msg) => console.info(`Database ${db_name} deleted: ${!msg['error']}`), () => console.log() ); } @@ -40,43 +38,46 @@ module.exports = { await createEdgeCollections(db, schema); }, getConnection: () => db, - get: function (id, returnType, schema) { + get: function(id, returnType, schema){ return get(id, returnType, schema); }, - getByKey: function (key, info) { + getByKey: function(key, info){ return getByKey(key, info); }, - create: function (isRoot, context, data, returnType, info) { + create: function(isRoot, context, data, returnType, info){ return create(isRoot, context, data, returnType, info); }, - createEdge: async function (isRoot, ctxt, source, sourceType, sourceField, target, targetType, annotations, info) { + createEdge: async function(isRoot, ctxt, source, sourceType, sourceField, target, targetType, annotations, info){ return await createEdge(isRoot, ctxt, source, sourceType, sourceField, target, targetType, annotations, info); }, - update: async function (isRoot, ctxt, id, data, returnType, info) { + update: async function(isRoot, ctxt, id, data, returnType, info){ return await update(isRoot, ctxt, id, data, returnType, info); }, - getEdge: async function (parent, args, info) { + getEdge: async function(parent, args, info){ return await getEdge(parent, args, info) }, - getList: async function (args, info) { + getList: async function(args, info){ return await getList(args, info); }, - getTotalCount: async function (parent, args, info) { + getTotalCount: async function(parent, args, info){ return await getTotalCount(parent, args, info); }, - isEndOfList: async function (parent, args, info) { + isEndOfList: async function(parent, args, info){ return await isEndOfList(parent, args, info); }, - addPossibleEdgeTypes: function (query, schema, type_name, field_name) { + addPossibleTypes: function(query, schema, type_name){ + return addPossibleTypes(query, schema, type_name); + }, + addPossibleEdgeTypes: function(query, schema, type_name, field_name){ return addPossibleEdgeTypes(query, schema, type_name, field_name); }, - getEdgeCollectionName: function (type, field) { + getEdgeCollectionName: function(type, field){ return getEdgeCollectionName(type, field); }, hello: () => hello() // TODO: Remove after testing }; -async function hello() { +async function hello(){ return "This is the arangodb.tools saying hello!" } @@ -89,9 +90,9 @@ async function createAndUseDatabase(db, db_name) { } async function createTypeCollections(db, schema) { - const type_definitions = getTypeDefinitions(schema, kind = 'GraphQLObjectType'); + const type_definitions = getTypeDefinitions(schema, kind='GraphQLObjectType'); for (let collection_name in type_definitions) { - if (collection_name.startsWith('_') || collection_name.includes('EdgeFrom')) { + if(collection_name.startsWith('_') || collection_name.includes('EdgeFrom')){ continue; // skip } let collection = await db.collection(collection_name); @@ -104,11 +105,11 @@ async function createTypeCollections(db, schema) { } } -async function createEdgeCollections(db, schema) { +async function createEdgeCollections(db, schema){ let collections = []; - const type_definitions = getTypeDefinitions(schema, kind = 'GraphQLObjectType'); + const type_definitions = getTypeDefinitions(schema, kind='GraphQLObjectType'); for (let type_name in type_definitions) { - if (type_name.startsWith('_') || type_name.includes('EdgeFrom')) { + if(type_name.startsWith('_') || type_name.includes('EdgeFrom')){ continue; } let type = type_definitions[type_name]; @@ -116,9 +117,9 @@ async function createEdgeCollections(db, schema) { // collections for type and interface fields fields = getObjectOrInterfaceFields(type); - for (let i in fields) { + for(let i in fields){ let field_name = fields[i]; - if (field_name.startsWith('_')) { + if(field_name.startsWith('_')) { continue; } let collection_name = getEdgeCollectionName(type_name, field_name); @@ -127,7 +128,7 @@ async function createEdgeCollections(db, schema) { } // create collections - for (let i in collections) { + for(let i in collections) { let collection_name = collections[i]; let collection = await db.edgeCollection(collection_name); await collection.create().then( @@ -141,11 +142,11 @@ async function createEdgeCollections(db, schema) { } } -function getKeyName(type) { +function getKeyName(type){ return `_KeyFor${type}`; } -function getEdgeCollectionName(type, field) { +function getEdgeCollectionName(type, field){ let f = capitalizeFirstLetter(field); let t = capitalizeFirstLetter(type); // return `EdgeToConnect${f}Of${t}`; @@ -156,12 +157,12 @@ function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); } -function getTypeDefinitions(schema, kind = null) { +function getTypeDefinitions(schema, kind=null) { let types = {}; - for (let i in schema.getTypeMap()) { + for(let i in schema.getTypeMap()){ let type = schema.getType(i); let name = type.name; - if (name == 'Query' || name == 'Mutation') { + if(name == 'Query' || name == 'Mutation'){ continue; } if (kind == null || type.constructor.name == kind) { @@ -181,7 +182,7 @@ function getScalarOrEnumFields(type) { for (let i in type.getFields()) { let value = type.getFields()[i]; let t = graphql.getNamedType(value.type); - if (graphql.isEnumType(t) || graphql.isScalarType(t)) { + if(graphql.isEnumType(t) || graphql.isScalarType(t)){ keys.push(value.name); } } @@ -193,7 +194,7 @@ function getScalarOrEnumFields(type) { * scalars, enums, lists of scalars, and lists of enums. * @param object */ -function getScalarsAndEnums(object, type) { +function getScalarsAndEnums(object, type){ let doc = {}; return formatFixInput(doc, object, type); } @@ -203,13 +204,13 @@ function getScalarsAndEnums(object, type) { * GraphQL types, GraphQL interfaces, lists of GraphQL types, and lists of GraphQL interfaces. * @param object */ -function getTypesAndInterfaces(object, type) { +function getTypesAndInterfaces(object, type){ let doc = {}; for (let i in type.getFields()) { let field = type.getFields()[i]; let t = graphql.getNamedType(field.type); - if (graphql.isObjectType(t) || graphql.isInterfaceType(t)) { - if (object[field.name] !== undefined) { + if(graphql.isObjectType(t) || graphql.isInterfaceType(t)){ + if(object[field.name] !== undefined) { doc[field.name] = object[field.name]; } } @@ -228,7 +229,7 @@ function getObjectOrInterfaceFields(type) { for (let i in type.getFields()) { let value = type.getFields()[i]; let t = graphql.getNamedType(value.type); - if (graphql.isObjectType(t) || graphql.isInterfaceType(t)) { + if(graphql.isObjectType(t) || graphql.isInterfaceType(t)){ keys.push(value.name); } } @@ -237,12 +238,12 @@ function getObjectOrInterfaceFields(type) { // ---------------------------------------------------------- -async function getEdge(parent, args, info) { +async function getEdge(parent, args, info){ let parent_type = graphql.getNamedType(info.parentType); let return_type = graphql.getNamedType(info.returnType); let field_name = info.fieldName; - if (info.fieldName.startsWith('_')) { // reverse edge + if(info.fieldName.startsWith('_')){ // reverse edge let pattern_string = `^_(.+?)From${return_type.name}$`; // get the non-reversed edge name let re = new RegExp(pattern_string); field_name = re.exec(info.fieldName)[1]; @@ -254,15 +255,15 @@ async function getEdge(parent, args, info) { // If the type that is the origin of the edge is an interface, then we need to check all the edge collections // corresponding to its implementing types. Note: This is only necessary when traversing some edges that are // defined in in the API schema for interfaces. The parent type will never be an interface type at this stage. - if (graphql.isInterfaceType(return_type)) { + if(graphql.isInterfaceType(return_type)){ let possible_types = info.schema.getPossibleTypes(return_type); if (possible_types.length > 1) query.push(aql`UNION(`); - for (let i in possible_types) { - if (i != 0) query.push(aql`,`); + for(let i in possible_types) { + if(i != 0) query.push(aql`,`); let collection = db.collection(getEdgeCollectionName(possible_types[i].name, field_name)); query.push(aql`(FOR i IN 1..1 INBOUND ${parent._id} ${collection} RETURN i)`); } - if (possible_types.length > 1) query.push(aql`)`); + if(possible_types.length > 1) query.push(aql`)`); } else { let collection = db.collection(getEdgeCollectionName(return_type.name, field_name)); @@ -275,9 +276,9 @@ async function getEdge(parent, args, info) { // add filters let query_filters = []; - if (args.filter != undefined && !isEmptyObject(args.filter)) { + if(args.filter != undefined && !isEmptyObject(args.filter)){ let filters = getFilters(args.filter, info); - for (let i in filters) { + for(let i in filters){ i == 0 ? query_filters.push(aql`FILTER`) : query_filters.push(aql`AND`); query_filters = query_filters.concat(filters[i]); } @@ -296,27 +297,16 @@ async function getEdge(parent, args, info) { /* TODO: We should probably call the createEdge function here (when we've defined it). */ -async function create(isRoot, ctxt, data, returnType, info) { +async function create(isRoot, ctxt, data, returnType, info){ // define transaction object - if (ctxt.trans === undefined) ctxt.trans = initTransaction(); - - // define postcode - // postcode will enforce all directives by adding checks, running after the mutations have been completed - if (ctxt.trans.postCode === undefined) ctxt.trans.postCode = []; + if(ctxt.trans === undefined) ctxt.trans = initTransaction(); // is root op and mutatation is already queued - if (isRoot && ctxt.trans.queue[info.path.key]) { + if(isRoot && ctxt.trans.queue[info.path.key]){ - if (ctxt.trans.open) { - // add all postcode to code before executing - //ctxt.trans.code.concat(ctxt.trans.postCode); // concat is not working? - for (let i in ctxt.trans.postCode) { - ctxt.trans.code.push(ctxt.trans.postCode[i]); - } - await executeTransaction(ctxt); - } - if (ctxt.trans.error) { - if (ctxt.trans.errorReported) return null; + if(ctxt.trans.open) await executeTransaction(ctxt); + if(ctxt.trans.error){ + if(ctxt.trans.errorReported) return null; ctxt.trans.errorReported = true; throw ctxt.trans.error; } @@ -326,6 +316,7 @@ async function create(isRoot, ctxt, data, returnType, info) { // check key validateKey(ctxt, data, returnType, info.schema); + // Do not increment the var counter let resVar = getVar(ctxt, false); let from = asAQLVar(resVar); @@ -342,21 +333,20 @@ async function create(isRoot, ctxt, data, returnType, info) { ctxt.trans.params[docVar] = doc; ctxt.trans.code.push(`let ${resVar} = db._query(aql\`INSERT ${aqlDocVar} IN ${collection} RETURN NEW\`).next();`); - // for edges let ob = getTypesAndInterfaces(data, returnType); - for (let fieldName in ob) { + for(let fieldName in ob){ let innerFieldType = graphql.getNamedType(returnType.getFields()[fieldName].type); let edge = getEdgeCollectionName(returnType.name, fieldName); ctxt.trans.write.add(edge); let edgeCollection = asAQLVar(`db.${edge}`); let values = Array.isArray(ob[fieldName]) ? ob[fieldName] : [ob[fieldName]]; // treat as list even if only one value is present - for (let i in values) { + for(let i in values){ let value = values[i]; console.log(value); - if (graphql.isInterfaceType(innerFieldType)) { // interface - if (value['connect']) { + if(graphql.isInterfaceType(innerFieldType)){ // interface + if(value['connect']){ validateType(ctxt, value['connect'], innerFieldType, info.schema); let typeToConnect = value['connect'].split('/')[0]; // add edge @@ -393,10 +383,10 @@ async function create(isRoot, ctxt, data, returnType, info) { } // directives handling - addPostCodeDirectivesForTypes(ctxt, returnType, resVar); + addfinalDirectiveChecksForType(ctxt, returnType, aql`${asAQLVar(resVar)}._id`, info.schema); // overwrite the current action - if (isRoot) { + if(isRoot){ ctxt.trans.code.push(`result['${info.path.key}'] = ${resVar};`); // add root result ctxt.trans.queue[info.path.key] = true; // indicate that this mutation op has been added to the transaction getVar(ctxt); // increment varCounter @@ -406,15 +396,15 @@ async function create(isRoot, ctxt, data, returnType, info) { return null; } -async function createEdge(isRoot, ctxt, source, sourceType, sourceField, target, targetType, annotations, info) { +async function createEdge(isRoot, ctxt, source, sourceType, sourceField, target, targetType, annotations, info){ // define transaction object - if (ctxt.trans === undefined) ctxt.trans = initTransaction(); + if(ctxt.trans === undefined) ctxt.trans = initTransaction(); // is root op and mutation is already queued - if (isRoot && ctxt.trans.queue[info.path.key]) { - if (ctxt.trans.open) await executeTransaction(ctxt); - if (ctxt.trans.error) { - if (ctxt.trans.errorReported) return null; + if(isRoot && ctxt.trans.queue[info.path.key]){ + if(ctxt.trans.open) await executeTransaction(ctxt); + if(ctxt.trans.error){ + if(ctxt.trans.errorReported) return null; ctxt.trans.errorReported = true; throw ctxt.trans.error; } @@ -425,11 +415,11 @@ async function createEdge(isRoot, ctxt, source, sourceType, sourceField, target, await validateEdge(ctxt, source, sourceType, sourceField, target, targetType, info); // variable reference to object that will be created - let resVar = getVar(ctxt, false); // Note: This should not increment the var counter, but use the previously allocated var name. + let resVar = getVar(ctxt,false); // Note: This should not increment the var counter, but use the previously allocated var name. // add doc let doc = {}; - if (annotations !== undefined) { + if(annotations !== undefined) { doc = annotations; } doc = formatFixInput(doc, doc, info.returnType); @@ -443,8 +433,10 @@ async function createEdge(isRoot, ctxt, source, sourceType, sourceField, target, ctxt.trans.params[docVar] = doc; ctxt.trans.code.push(`let ${resVar} = db._query(aql\`INSERT ${aqlDocVar} IN ${collection} RETURN NEW\`).next();`); + addfinalDirectiveChecksForType(ctxt, sourceType, source, info.schema); + // overwrite the current action - if (isRoot) { + if(isRoot) { ctxt.trans.code.push(`result['${info.path.key}'] = ${resVar};`); // add root result ctxt.trans.queue[info.path.key] = true; // indicate that this mutation op has been added to the transaction getVar(ctxt); // increment varCounter @@ -457,22 +449,22 @@ async function createEdge(isRoot, ctxt, source, sourceType, sourceField, target, async function validateEdge(ctxt, source, sourceType, sourceField, target, targetType, info) { let schema = info.schema; - if (!isOfType(source, sourceType, schema)) { + if(!isOfType(source, sourceType, schema)) { ctxt.trans.code.push(`throw \`Source object ${source} is not of type ${sourceType}\``); return } let sourceObject = await get(source, sourceType, schema); - if (sourceObject === undefined) { + if(sourceObject === undefined) { ctxt.trans.code.push(`throw \`Source object ${source} does not exist in collection ${sourceType}\``); return } - if (!isOfType(target, targetType, schema)) { + if(!isOfType(target, targetType, schema)) { ctxt.trans.code.push(`throw \`Target object ${target} is not of type ${targetType}\``); return } let targetObject = await get(target, targetType, schema); - if (targetObject === undefined) { + if(targetObject === undefined) { ctxt.trans.code.push(`throw \`Target object ${target} does not exist in collection ${targetType}\``); return } @@ -481,22 +473,22 @@ async function validateEdge(ctxt, source, sourceType, sourceField, target, targe let fieldType = schema.getType(sourceType).getFields()[sourceField].type; let collection = db.edgeCollection(getEdgeCollectionName(sourceType.name, sourceField)); let query = [aql`FOR x IN 1..1 OUTBOUND ${source} ${collection}`]; - if (graphql.isListType(fieldType)) { + if(graphql.isListType(fieldType)){ query.push(aql`FILTER(x._id == ${target})`); } query.push(aql`RETURN x`); const cursor = await db.query(aql.join(query)); let otherEdge = await cursor.next(); - if (otherEdge !== undefined) { + if(otherEdge !== undefined) { ctxt.trans.code.push(`throw \`Edge already exists for ${sourceField} from ${source}.\``); } } -function asAQLVar(varName) { +function asAQLVar(varName){ return '${' + varName + '}'; } -function initTransaction() { +function initTransaction(){ return { write: new Set(), params: {}, open: true, queue: {}, code: [ @@ -504,23 +496,28 @@ function initTransaction() { 'const {aql} = require("@arangodb");', 'let result = Object.create(null);' ], - postCode: [], + finalConstraintChecks: [], error: false }; } -async function executeTransaction(ctxt) { +async function executeTransaction(ctxt){ + // add all finalConstraintChecks to code before executing + //ctxt.trans.code.concat(ctxt.trans.finalConstraintChecks); // concat is not working? + for (const row of ctxt.trans.finalConstraintChecks) { + ctxt.trans.code.push(row); + } try { let action = `function(params){\n\t${ctxt.trans.code.join('\n\t')}\n\treturn result;\n}`; console.log(action); - ctxt.trans.results = await db.transaction({ write: Array.from(ctxt.trans.write), read: [] }, action, ctxt.trans.params); + ctxt.trans.results = await db.transaction({write: Array.from(ctxt.trans.write), read: []}, action, ctxt.trans.params); } catch (e) { ctxt.trans.error = new ApolloError(e.message); } ctxt.trans.open = false; } -function validateKey(ctxt, data, type, schema, id = undefined) { +function validateKey(ctxt, data, type, schema, id=undefined){ let collection = asAQLVar(`db.${type.name}`); let keyType = schema["_typeMap"][getKeyName(type.name)]; if (keyType) { @@ -539,36 +536,25 @@ function validateKey(ctxt, data, type, schema, id = undefined) { } } -function validateType(ctxt, id, type, schema) { - if (graphql.isInterfaceType(type)) { - if (!isImplementingType(id.split('/')[0], type, schema)) { +function validateType(ctxt, id, type, schema){ + if(graphql.isInterfaceType(type)) { + if(!isImplementingType(id.split('/')[0], type, schema)) { ctxt.trans.code.push(`throw "ID ${id} is not a document of the interface ${type}";`); } - } else if (id.split('/')[0] != type.name) { + } else if(id.split('/')[0] != type.name){ ctxt.trans.code.push(`throw "ID ${id} is not a document of the type ${type}";`); } } -async function update(isRoot, ctxt, id, data, returnType, info) { +async function update(isRoot, ctxt, id, data, returnType, info){ // define transaction object - if (ctxt.trans === undefined) ctxt.trans = initTransaction(); - - // define postcode - // postcode will enforce all directives by adding checks, running after the mutations have been completed - if (ctxt.trans.postCode === undefined) ctxt.trans.postCode = []; + if(ctxt.trans === undefined) ctxt.trans = initTransaction(); // is root op and mutation is already queued - if (isRoot && ctxt.trans.queue[info.path.key]) { - if (ctxt.trans.open) { - // add all postcode to code before executing - //ctxt.trans.code.concat(ctxt.trans.postCode); // concat is not working? - for (let i in ctxt.trans.postCode) { - ctxt.trans.code.push(ctxt.trans.postCode[i]); - } - await executeTransaction(ctxt); - } - if (ctxt.trans.error) { - if (ctxt.trans.errorReported) return null; + if(isRoot && ctxt.trans.queue[info.path.key]){ + if(ctxt.trans.open) await executeTransaction(ctxt); + if(ctxt.trans.error){ + if(ctxt.trans.errorReported) return null; ctxt.trans.errorReported = true; throw ctxt.trans.error; } @@ -580,24 +566,24 @@ async function update(isRoot, ctxt, id, data, returnType, info) { // 3) Add key check to transaction let keyName = getKeyName(returnType.name); let keyType = info.schema["_typeMap"][keyName]; - if (keyType) { + if(keyType){ try { let collection = db.collection(returnType); const cursor = await db.query(aql`FOR i IN ${collection} FILTER(i._id == ${id}) RETURN i`); let doc = await cursor.next(); - if (doc == undefined) { + if(doc == undefined){ throw new ApolloError(`ID ${id} is not a document in the type ${returnType}`); } let key = {}; - for (let f in keyType._fields) { + for(let f in keyType._fields){ key[f] = doc[f]; - if (data[f] !== undefined) { + if(data[f] !== undefined){ key[f] = data[f]; } } validateKey(ctxt, key, returnType, info.schema, id); - } catch (err) { + } catch(err) { throw new ApolloError(err); } } @@ -686,10 +672,10 @@ async function update(isRoot, ctxt, id, data, returnType, info) { } // directives handling - addPostCodeDirectivesForTypes(ctxt, returnType, resVar); + addfinalDirectiveChecksForType(ctxt, returnType, aql`${asAQLVar(resVar)}._id`, info.schema); // overwrite the current action - if (isRoot) { + if(isRoot) { ctxt.trans.code.push(`result['${info.path.key}'] = ${resVar}.new;`); // add root result ctxt.trans.queue[info.path.key] = true; // indicate that this mutation op has been added to the transaction getVar(ctxt); // increment varCounter @@ -699,10 +685,10 @@ async function update(isRoot, ctxt, id, data, returnType, info) { return null; } -function asAqlArray(array) { +function asAqlArray(array){ let q = [aql`[`]; - for (let i in array) { - if (i != 0) { + for(let i in array){ + if(i != 0){ q.push(aql`,`); } q.push(aql`${array[i]}`); @@ -721,8 +707,9 @@ function asAqlArray(array) { */ function formatFixInput(outputDoc, inputDoc, type) { // Adds scalar/enum values to outputDoc - for (let i in type.getFields()) { - let field = type.getFields()[i]; + for (let f in type.getFields()) { + let field = type.getFields()[f]; + //let field = type.getFields()[i]; let t = graphql.getNamedType(field.type); if (graphql.isEnumType(t) || graphql.isScalarType(t)) { if (inputDoc[field.name] !== undefined) { @@ -745,8 +732,8 @@ function formatFixVariable(_type, v) { // Arrays of DateTime needs special, special care. if (Array.isArray(v)) { let newV = [] - for (let i in v) - newV.push(aql`DATE_TIMESTAMP(${v})`); + for (date of v) + newV.push(aql`DATE_TIMESTAMP(${date})`); return newV; } else @@ -771,23 +758,23 @@ function formatFixVariableWrapper(field, info, v) { return formatFixVariable(_type, v); } -function getFilters(filter_arg, info) { +function getFilters(filter_arg, info){ let filters = []; - for (let i in filter_arg) { + for(let i in filter_arg){ let filter = filter_arg[i]; /// Rewrite id field - if (i == 'id') { i = '_id'; } + if(i == 'id'){ i = '_id'; } // AND expression - if (i == '_and') { + if(i == '_and'){ let f = []; f.push(aql`(`); - for (let x in filter) { - if (x != 0) { + for(let x in filter) { + if(x != 0){ f.push(aql`AND`); } let arr = getFilters(filter[x], info); - for (let j in arr) { + for(let j in arr){ f = f.concat(arr[j]); } } @@ -796,15 +783,15 @@ function getFilters(filter_arg, info) { } // OR expression - if (i == '_or') { + if(i == '_or'){ let f = []; f.push(aql`(`); - for (let x in filter) { - if (x != 0) { + for(let x in filter) { + if(x != 0){ f.push(aql`OR`); } let arr = getFilters(filter[x], info); - for (let j in arr) { + for(let j in arr){ f = f.concat(arr[j]); } } @@ -813,49 +800,49 @@ function getFilters(filter_arg, info) { } // NOT expression - if (i == '_not') { + if(i == '_not'){ let f = []; f.push(aql`NOT (`); let arr = getFilters(filter, info); - for (let j in arr) { + for(let j in arr){ f = f.concat(arr[j]); } f.push(aql`)`); filters.push(f); } - if (filter._eq != null) { + if(filter._eq != null){ let preparedArg = formatFixVariableWrapper(i, info, filter._eq); filters.push([aql`x.${i} == ${preparedArg}`]); } - if (filter._neq != null) { + if(filter._neq != null){ let preparedArgs = formatFixVariableWrapper(i, info, filter._neq); filters.push([aql`x.${i} != ${preparedArgs}`]); } - if (filter._gt != null) { + if(filter._gt != null){ let preparedArgs = formatFixVariableWrapper(i, info, filter._gt); filters.push([aql`x.${i} > ${preparedArgs}`]); } - if (filter._egt != null) { + if(filter._egt != null){ let preparedArgs = formatFixVariableWrapper(i, info, filter._egt); filters.push([aql`x.${i} >= ${preparedArgs}`]); } - if (filter._lt != null) { + if(filter._lt != null){ let preparedArgs = formatFixVariableWrapper(i, info, filter._lt); filters.push([aql`x.${i} < ${preparedArgs}`]); } - if (filter._elt != null) { + if(filter._elt != null){ let preparedArgs = formatFixVariableWrapper(i, info, filter._elt); filters.push([aql`x.${i} <= ${preparedArgs}`]); } - if (filter._in != null) { + if(filter._in != null){ let preparedArgs = formatFixVariableWrapper(i, info, filter._in) let q = []; q = q.concat([aql`x.${i} IN `]); q = q.concat(asAqlArray(preparedArgs)); filters.push(q); } - if (filter._nin != null) { + if(filter._nin != null){ let preparedArgs = formatFixVariableWrapper(i, info, filter._nin); let q = []; q = q.concat([aql`x.${i} NOT IN `]); @@ -863,19 +850,19 @@ function getFilters(filter_arg, info) { filters.push(q); } - if (filter._like != null) { + if(filter._like != null){ let preparedArgs = formatFixVariableWrapper(i, info, filter._like); filters.push([aql`LIKE(x.${i}, ${preparedArgs}, false)`]); } - if (filter._ilike != null) { + if(filter._ilike != null){ let preparedArgs = formatFixVariableWrapper(i, info, filter._ilike); filters.push([aql`LIKE(x.${i}, ${preparedArgs}, true)`]); } - if (filter._nlike != null) { + if(filter._nlike != null){ let preparedArgs = formatFixVariableWrapper(i, info, filter._nlike); filters.push([aql`NOT LIKE(x.${i}, ${preparedArgs}, false)`]); } - if (filter._nilike != null) { + if(filter._nilike != null){ let preparedArgs = formatFixVariableWrapper(i, info, filter._nilike); filters.push([aql`NOT LIKE(x.${i}, ${preparedArgs}, true)`]); } @@ -885,7 +872,7 @@ function getFilters(filter_arg, info) { return filters; } -async function getByKey(key, returnType) { +async function getByKey(key, returnType){ let type = graphql.getNamedType(returnType); let query = [aql`FOR x IN`]; let collection = db.collection(type.name); @@ -900,37 +887,25 @@ async function getByKey(key, returnType) { try { const cursor = await db.query(aql.join(query)); return await cursor.next(); - } catch (err) { + } catch(err) { //console.error(err); throw new ApolloError(err); } } -async function getList(args, info) { +async function getList(args, info){ let type = graphql.getNamedType(info.returnType.getFields()['content'].type); let first = args.first; let after = args.after; let query = [aql`FOR x IN FLATTEN(FOR i IN [`]; - if (graphql.isInterfaceType(type)) { - let possible_types = info.schema.getPossibleTypes(type); - for (let i in possible_types) { - if (i != 0) { - query.push(aql`,`); - } - let collection = db.collection(possible_types[i].name); - query.push(aql`${collection}`); - } - } else { - let collection = db.collection(type.name); - query.push(aql`${collection}`); - } + addPossibleTypes(query, info.schema, type); query.push(aql`] RETURN i)`); // add filters let query_filters = []; - if (args.filter != undefined && !isEmptyObject(args.filter)) { + if(args.filter != undefined && !isEmptyObject(args.filter)){ let filters = getFilters(args.filter, info); - if (filters.length > 0) { + if(filters.length > 0) { query_filters.push(aql`FILTER`); for (let i in filters) { if (i != 0) { @@ -950,37 +925,25 @@ async function getList(args, info) { 'content': result }; return list; - } catch (err) { + } catch(err) { //console.error(err); throw new ApolloError(err); } } -async function isEndOfList(parent, args, info) { +async function isEndOfList(parent, args, info){ let type = graphql.getNamedType(info.parentType.getFields()['content'].type); let query = [aql`FOR x IN FLATTEN(FOR i IN [`]; - if (graphql.isInterfaceType(type)) { - let possible_types = info.schema.getPossibleTypes(type); - for (let i in possible_types) { - if (i != 0) { - query.push(aql`,`); - } - let collection = db.collection(possible_types[i].name); - query.push(aql`${collection}`); - } - } else { - let collection = db.collection(type.name); - query.push(aql`${collection}`); - } + addPossibleTypes(query, info.schema, type); query.push(aql`] RETURN i)`); // add filters - if (parent._filter) { + if(parent._filter){ query = query.concat(parent._filter); } // get last ID in parent content - if (parent.content.length != 0) { - const last = parent.content[parent.content.length - 1]; + if(parent.content.length != 0){ + const last = parent.content[parent.content.length-1]; query.push(aql`FILTER(x._id > ${last._id})`); } @@ -989,32 +952,20 @@ async function isEndOfList(parent, args, info) { const cursor = await db.query(aql.join(query)); const result = await cursor.next(); return result == 0; - } catch (err) { + } catch(err) { console.error(err); throw new ApolloError(err); } } -async function getTotalCount(parent, args, info) { +async function getTotalCount(parent, args, info){ let type = graphql.getNamedType(info.parentType.getFields()['content'].type); let query = [aql`FOR x IN FLATTEN(FOR i IN [`]; - if (graphql.isInterfaceType(type)) { - let possible_types = info.schema.getPossibleTypes(type); - for (let i in possible_types) { - if (i != 0) { - query.push(aql`,`); - } - let collection = db.collection(possible_types[i].name); - query.push(aql`${collection}`); - } - } else { - let collection = db.collection(type.name); - query.push(aql`${collection}`); - } + addPossibleTypes(query, info.schema, type); query.push(aql`] RETURN i)`); // add filters - if (parent._filter) { + if(parent._filter){ query = query.concat(parent._filter); } @@ -1022,7 +973,7 @@ async function getTotalCount(parent, args, info) { try { const cursor = await db.query(aql.join(query)); return await cursor.next(); - } catch (err) { + } catch(err) { console.error(err); throw new ApolloError(err); } @@ -1035,22 +986,22 @@ async function getTotalCount(parent, args, info) { * @param schema * @returns {Promise<*>} */ -async function get(id, returnType, schema) { +async function get(id, returnType, schema){ let type = returnType; let query = [aql`FOR i IN`]; - if (graphql.isInterfaceType(type)) { + if(graphql.isInterfaceType(type)){ let possible_types = schema.getPossibleTypes(type); - if (possible_types.length > 1) { + if(possible_types.length > 1){ query.push(aql`UNION(`); } - for (let i in possible_types) { - if (i != 0) { + for(let i in possible_types) { + if(i != 0){ query.push(aql`,`); } let collection = db.collection(possible_types[i].name); query.push(aql`(FOR x IN ${collection} FILTER(x._id == ${id}) RETURN x)`); } - if (possible_types.length > 1) { + if(possible_types.length > 1){ query.push(aql`)`); } } else { @@ -1062,7 +1013,7 @@ async function get(id, returnType, schema) { try { const cursor = await db.query(aql.join(query)); return await cursor.next(); - } catch (err) { + } catch(err) { console.error(err); throw new ApolloError(err); } @@ -1077,27 +1028,27 @@ function isEmptyObject(obj) { * @param ob * @param props */ -function pick(ob, props) { +function pick(ob, props){ let sub = {}; - for (let i in props) { + for(let i in props) { let prop = props[i]; - if (ob[prop] !== undefined) { + if(ob[prop] !== undefined) { sub[prop] = ob[prop]; } } return sub; } -function getVar(context, increment = true) { - if (context.varCounter === undefined) { +function getVar(context, increment=true){ + if(context.varCounter === undefined){ context.varCounter = 0; } - if (increment) context.varCounter++; + if(increment) context.varCounter++; return `x${context.varCounter}`; } -function getParamVar(context) { - if (context.paramCounter === undefined) { +function getParamVar(context){ + if(context.paramCounter === undefined){ context.paramCounter = 0; } context.paramCounter++; @@ -1110,13 +1061,13 @@ function getTypeNameFromId(id) { function isOfType(id, type, schema) { let idType = getTypeNameFromId(id); - if (type.name == idType || isImplementingType(idType, type, schema)) { + if(type.name == idType || isImplementingType(idType, type, schema)){ return true; } return false; } -function isImplementingType(name, type_interface, schema) { +function isImplementingType(name, type_interface, schema){ let possible_types = schema.getPossibleTypes(type_interface); for (let i in possible_types) { if (possible_types[i].name == name) { @@ -1126,54 +1077,176 @@ function isImplementingType(name, type_interface, schema) { return false; } -function conditionalThrow(msg) { +function conditionalThrow(msg){ //console.warn(msg); - if (!disableEdgeValidation) { + if(!disableEdgeValidation){ throw msg; } } +/** + * Add all possible collections for the given type to query + * @param query (modifies) + * @param schema + * @param type + * @param {bool} use_aql = true (optional) + */ +function addPossibleTypes(query, schema, type, use_aql = true) { + if (graphql.isInterfaceType(type)) { + let possible_types = schema.getPossibleTypes(type); + for (let i in possible_types) { + if (i != 0) { + if (use_aql) query.push(aql`,`); + else query[query.length - 1] += `,`; + } + if (use_aql) query.push(aql`${db.collection(possible_types[i].name)}`); + else { + let collection = asAQLVar(`db.${possible_types[i].name}`) + query.push(`${collection}`); + } + } + } else { + if (use_aql) query.push(aql`${db.collection(type.name)}`); + else { + let collection = asAQLVar(`db.${type.name}`); + query.push(`${collection}`); + } + } +} + /** * Add all possible edge-collections for the given type and field to query * @param query (modifies) * @param schema * @param type_name * @param field_name + * @param {bool} use_aql = true (optional) * @param directionString (optional) */ -function addPossibleEdgeTypes(query, schema, type_name, field_name, directionString = "") { +function addPossibleEdgeTypes(query, schema, type_name, field_name, use_aql = true, directionString = "") { let type = schema._typeMap[type_name]; if (graphql.isInterfaceType(type)) { let possible_types = schema.getPossibleTypes(type); for (let i in possible_types) { if (i != 0) { - query.push(aql`,`); + if (use_aql) query.push(aql`,`); + else query[query.length - 1] += `,`; + } + let collectionName = getEdgeCollectionName(possible_types[i].name, field_name); + + if (use_aql) query.push(aql`${db.collection(collectionName)}`); + else { + let collection = asAQLVar(`db.${collectionName}`); + query.push(`${collection}`); } - let collection = db.collection(getEdgeCollectionName(possible_types[i].name, field_name)); - query.push(aql`${collection}`); } } else { - let collection = db.collection(getEdgeCollectionName(type.name, field_name)); - query.push(aql`${collection}`); + let collectionName = getEdgeCollectionName(type.name, field_name); + + if (use_aql) query.push(aql`${db.collection(collectionName)}`); + else { + let collection = asAQLVar(`db.${type.name}`); + query.push(`${collection}`); + } } } /** - * Append postcode to ctxt for all directives of all fields in input type + * Append finalConstraintChecks to ctxt for all directives of all fields in input type * @param ctxt (modifies) * @param type * @param resVar + * @param schema */ -function addPostCodeDirectivesForTypes(ctxt, type, resVar) { +function addfinalDirectiveChecksForType(ctxt, type, id, schema) { for (let f in type.getFields()) { - let field = type.getFields()[f] - for (let d in field.astNode.directives) { - let dir = field.astNode.directives[d]; + let field = type.getFields()[f]; + for (dir of field.astNode.directives) { if (dir.name.value == 'noloops') { let collection = asAQLVar(`db.${getEdgeCollectionName(type.name, field.name)}`); - ctxt.trans.postCode.push(`if(db._query(aql\`FOR v, e, p IN 1..1 OUTBOUND ${asAQLVar(resVar)}._id ${collection} FILTER ${asAQLVar(resVar)}._id == v._id RETURN v\`).next()){`); - ctxt.trans.postCode.push(` throw "Field ${f} in ${type.name} is breaking a @noloops directive! ${resVar}";`); - ctxt.trans.postCode.push(`}`); + ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR v IN 1..1 OUTBOUND ${id} ${collection} FILTER ${id} == v._id RETURN v\`).next()){`); + ctxt.trans.finalConstraintChecks.push(` throw "Field ${f} in ${type.name} is breaking a @noloops directive!";`); + ctxt.trans.finalConstraintChecks.push(`}`); + } + else if (dir.name.value == 'distinct') { + let collection = asAQLVar(`db.${getEdgeCollectionName(type.name, field.name)}`); + ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR v, e IN 1..1 OUTBOUND ${id} ${collection} FOR v2, e2 IN 1..1 OUTBOUND ${id} ${collection} FILTER v._id == v2._id AND e._id != e2._id RETURN v\`).next()){`); + ctxt.trans.finalConstraintChecks.push(` throw "Field ${f} in ${type.name} is breaking a @distinct directive!";`); + ctxt.trans.finalConstraintChecks.push(`}`); + } + else if (dir.name.value == 'uniqueForTarget') { + // The direct variant of @uniqueForTarget + // edge is named after current type etc. + let collection = asAQLVar(`db.${getEdgeCollectionName(type.name, field.name)}`); + ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR v, e IN 1..1 OUTBOUND ${id} ${collection} FOR v2, e2 IN 1..1 INBOUND v._id ${collection} FILTER e._id != e2._id RETURN v\`).next()){`); + ctxt.trans.finalConstraintChecks.push(` throw "Field ${f} in ${type.name} is breaking a @uniqueForTarget directive!";`); + ctxt.trans.finalConstraintChecks.push(`}`); + } + else if (dir.name.value == '_uniqueForTarget_AccordingToInterface') { + // The inherited/implemented variant of @uniqueForTarget + // The target does not only require at most one edge of this type, but at most one of any type implementing the interface + // Thankfully we got the name of the interface as a mandatory argument and can hence use this to get all types implementing it + + let interfaceName = dir.arguments[0].value.value; // If we add more arguments to the directive this will fail horrible. + // But that should not happen (and it is quite easy to fix) + + ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR v, e IN 1..1 OUTBOUND ${id}`); + addPossibleEdgeTypes(ctxt.trans.finalConstraintChecks, schema, interfaceName, field.name, false); + ctxt.trans.finalConstraintChecks.push(`FOR v2, e2 IN 1..1 INBOUND v._id`); + addPossibleEdgeTypes(ctxt.trans.finalConstraintChecks, schema, interfaceName, field.name, false); + ctxt.trans.finalConstraintChecks.push(`FILTER e._id != e2._id RETURN v\`).next()){`); + ctxt.trans.finalConstraintChecks.push(` throw "Field ${f} in ${type.name} is breaking a @_uniqueForTarget_AccordingToInterface directive!";`); + ctxt.trans.finalConstraintChecks.push(`}`); + } + else if (dir.name.value == 'requiredForTarget') { + // The direct variant of @requiredForTarget + // edge is named after current type etc. + let edgeCollection = asAQLVar(`db.${getEdgeCollectionName(type.name, field.name)}`); + + // The target type might be an interface, giving us slightly more to keep track of + // First, find the right collections to check + ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR x IN FLATTEN(FOR i IN [`); + addPossibleTypes(ctxt.trans.finalConstraintChecks, schema, graphql.getNamedType(field.type), false); + // Second, count all edges ending at objects in these collections + ctxt.trans.finalConstraintChecks.push(`] RETURN i) LET endpoints = ( FOR v IN 1..1 INBOUND x ${edgeCollection} RETURN v)`); + // If the count returns 0, we have an object breaking the directive + ctxt.trans.finalConstraintChecks.push(`FILTER LENGTH(endpoints) == 0 RETURN x\`).next()){`); + ctxt.trans.finalConstraintChecks.push(` throw "There are object(s) breaking the @requiredForTarget directive of Field ${f} in ${type.name}!";`); + ctxt.trans.finalConstraintChecks.push(`}`); + } + else if (dir.name.value == '_requiredForTarget_AccordingToInterface') { + // The inherited/implemented variant of @requiredForTarget + // The target does not directly require an edge of this type, but at least one of any type implementing the interface + // Thankfully we got the name of the interface as a mandatory argument and can hence use this to get all types implementing it + + let interfaceName = dir.arguments[0].value.value; // If we add more arguments to the directive this will fail horrible. + // But that should not happen (and it is quite easy to fix) + + // The target type might be an interface, giving us slightly more to keep track of + // First, find the right collections to check + ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR x IN FLATTEN(FOR i IN [`); + addPossibleTypes(ctxt.trans.finalConstraintChecks, schema, graphql.getNamedType(field.type), false); + // Second, count all edges ending at objects in these collections + ctxt.trans.finalConstraintChecks.push(`] RETURN i) LET endpoints = ( FOR v IN 1..1 INBOUND x `); + addPossibleEdgeTypes(ctxt.trans.finalConstraintChecks, schema, interfaceName, field.name, false); + ctxt.trans.finalConstraintChecks.push(` RETURN v)`); + // If the count returns 0, we have an object breaking the directive + ctxt.trans.finalConstraintChecks.push(`FILTER LENGTH(endpoints) == 0 RETURN x\`).next()){`); + ctxt.trans.finalConstraintChecks.push(` throw "There are object(s) breaking the inherited @_requiredForTarget_AccordingToInterface directive of Field ${f} in ${type.name}!";`); + ctxt.trans.finalConstraintChecks.push(`}`); + } + else if (dir.name.value == 'required' && field.name[0] == '_') { + // This is actually the reverse edge of a @requiredForTarget directive + + let pattern_string = `^_(.+?)From${type.name}$`; // get the non-reversed edge name + let re = new RegExp(pattern_string); + let field_name = re.exec(field.name)[1]; + + ctxt.trans.finalConstraintChecks.push(`if(!db._query(aql\`FOR v IN 1..1 INBOUND ${id}`); + addPossibleEdgeTypes(ctxt.trans.finalConstraintChecks, schema, graphql.getNamedType(field.type), field_name, false); + ctxt.trans.finalConstraintChecks.push(`RETURN x\`).next()){`); + ctxt.trans.finalConstraintChecks.push(` throw "Field ${f} in ${type.name} is breaking a @requiredForTarget directive (in reverse)!";`); + ctxt.trans.finalConstraintChecks.push(`}`); } } } From e0a0dbf11b30bcbac72aa5a90fdda3a36b202f3a Mon Sep 17 00:00:00 2001 From: leier318 Date: Thu, 28 May 2020 16:26:02 +0200 Subject: [PATCH 04/14] Minor fixes --- graphql-api-generator/generator.py | 2 +- graphql-server/drivers/arangodb/driver.js | 25 +++++++++++------------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/graphql-api-generator/generator.py b/graphql-api-generator/generator.py index 19340aa..63b3cf9 100644 --- a/graphql-api-generator/generator.py +++ b/graphql-api-generator/generator.py @@ -138,7 +138,7 @@ def run(schema: GraphQLSchema, config: dict): raise UnsupportedOperation('{0} is currently not supported'.format('delete_edge_objects')) # remove field arguments for edges (should not be in the API schema) - #schema = remove_field_arguments_for_types(schema) + schema = remove_field_arguments_for_types(schema) return schema diff --git a/graphql-server/drivers/arangodb/driver.js b/graphql-server/drivers/arangodb/driver.js index 839e8cd..171238d 100644 --- a/graphql-server/drivers/arangodb/driver.js +++ b/graphql-server/drivers/arangodb/driver.js @@ -10,7 +10,7 @@ let disableEdgeValidation; module.exports = { init: async function(schema){ let db_name = process.env.db ? process.env.db : 'dev-db'; - let url = process.env.URL ? process.env.URL: 'http://localhost:8529'; + let url = process.env.URL ? process.env.URL : 'http://localhost:8529'; let drop = process.env.DROP === 'true'; disableEdgeValidation = process.env.DISABLE_EDGE_VALIDATION === 'true'; db = new arangojs.Database({ url: url }); @@ -44,10 +44,10 @@ module.exports = { getByKey: function(key, info){ return getByKey(key, info); }, - create: function(isRoot, context, data, returnType, info){ + create: function(isRoot, context, data, returnType, info) { return create(isRoot, context, data, returnType, info); }, - createEdge: async function(isRoot, ctxt, source, sourceType, sourceField, target, targetType, annotations, info){ + createEdge: async function(isRoot, ctxt, source, sourceType, sourceField, target, targetType, annotations, info) { return await createEdge(isRoot, ctxt, source, sourceType, sourceField, target, targetType, annotations, info); }, update: async function(isRoot, ctxt, id, data, returnType, info){ @@ -59,19 +59,19 @@ module.exports = { getList: async function(args, info){ return await getList(args, info); }, - getTotalCount: async function(parent, args, info){ + getTotalCount: async function (parent, args, info) { return await getTotalCount(parent, args, info); }, - isEndOfList: async function(parent, args, info){ + isEndOfList: async function (parent, args, info) { return await isEndOfList(parent, args, info); }, - addPossibleTypes: function(query, schema, type_name){ + addPossibleTypes: function (query, schema, type_name) { return addPossibleTypes(query, schema, type_name); }, - addPossibleEdgeTypes: function(query, schema, type_name, field_name){ + addPossibleEdgeTypes: function (query, schema, type_name, field_name) { return addPossibleEdgeTypes(query, schema, type_name, field_name); }, - getEdgeCollectionName: function(type, field){ + getEdgeCollectionName: function (type, field) { return getEdgeCollectionName(type, field); }, hello: () => hello() // TODO: Remove after testing @@ -81,7 +81,7 @@ async function hello(){ return "This is the arangodb.tools saying hello!" } -async function createAndUseDatabase(db, db_name) { +async function createAndUseDatabase(db, db_name){ await db.createDatabase(db_name).then( () => { console.info(`Database '${db_name}' created`); }, err => { console.warn(`Database '${db_name}' not created:`, err.response.body.errorMessage); } @@ -251,13 +251,13 @@ async function getEdge(parent, args, info){ // Create query let query = [aql`FOR x IN`]; - if (info.fieldName.startsWith('_')) { + if(info.fieldName.startsWith('_')) { // If the type that is the origin of the edge is an interface, then we need to check all the edge collections // corresponding to its implementing types. Note: This is only necessary when traversing some edges that are // defined in in the API schema for interfaces. The parent type will never be an interface type at this stage. if(graphql.isInterfaceType(return_type)){ let possible_types = info.schema.getPossibleTypes(return_type); - if (possible_types.length > 1) query.push(aql`UNION(`); + if(possible_types.length > 1) query.push(aql`UNION(`); for(let i in possible_types) { if(i != 0) query.push(aql`,`); let collection = db.collection(getEdgeCollectionName(possible_types[i].name, field_name)); @@ -303,7 +303,6 @@ async function create(isRoot, ctxt, data, returnType, info){ // is root op and mutatation is already queued if(isRoot && ctxt.trans.queue[info.path.key]){ - if(ctxt.trans.open) await executeTransaction(ctxt); if(ctxt.trans.error){ if(ctxt.trans.errorReported) return null; @@ -386,7 +385,7 @@ async function create(isRoot, ctxt, data, returnType, info){ addfinalDirectiveChecksForType(ctxt, returnType, aql`${asAQLVar(resVar)}._id`, info.schema); // overwrite the current action - if(isRoot){ + if(isRoot) { ctxt.trans.code.push(`result['${info.path.key}'] = ${resVar};`); // add root result ctxt.trans.queue[info.path.key] = true; // indicate that this mutation op has been added to the transaction getVar(ctxt); // increment varCounter From 4d217dd5817f2ab19089170834f24ab9ffdde9c6 Mon Sep 17 00:00:00 2001 From: leier318 Date: Thu, 28 May 2020 16:40:15 +0200 Subject: [PATCH 05/14] Added some comments and fixed some phytonic errors --- graphql-api-generator/utils/utils.py | 30 ++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/graphql-api-generator/utils/utils.py b/graphql-api-generator/utils/utils.py index 7afd950..65bfb0a 100644 --- a/graphql-api-generator/utils/utils.py +++ b/graphql-api-generator/utils/utils.py @@ -829,7 +829,7 @@ def get_directive_arguments(directive): """ output = '' - if len(directive.arguments) > 0: + if directive.arguments: output+= '(' for arg in directive.arguments: output+= arg.name.value + ':' @@ -859,16 +859,23 @@ def get_directive_arguments(directive): def printSchemaWithDirectives(schema): + """ + Ouputs the given schema as string, in the format we want it. + Types and fields will all contain directives + :param schema: + :return string: + """ output = '' + # Start by adding directives for _dir in schema.directives: if _dir.ast_node is not None: # If the directive does not have a proper ast_node # Then it is an non-user defined directive, and can hence, be skipped output+= 'directive @' + _dir.name - if len(_dir.ast_node.arguments) > 0: + if _dir.ast_node.arguments: output+= '(' for arg in _dir.ast_node.arguments: output+= arg.name.value + ': ' + ast_type_to_string(arg.type) + ', ' @@ -880,12 +887,14 @@ def printSchemaWithDirectives(schema): output = output[:-2] + '\n\n' - + # Two special directives that should not exists in the db schema output += 'directive @_requiredForTarget_AccordingToInterface(interface: String!) on FIELD_DEFINITION\n\n' output += 'directive @_uniqueForTarget_AccordingToInterface(interface: String!) on FIELD_DEFINITION\n\n' - + # For each type, and output the types sortad after name for _type in sorted(schema.type_map.values(), key=lambda x : x.name): + + # Internal type if _type.name[:2] == '__': continue @@ -902,29 +911,35 @@ def printSchemaWithDirectives(schema): output += 'input ' + _type.name else: # type, hopefully output += 'type ' + _type.name - if hasattr(_type, 'interfaces') and len(_type.interfaces) > 0: + if hasattr(_type, 'interfaces') and _type.interfaces: output += ' implements ' for interface in _type.interfaces: output += interface.name + ', ' output = output[:-2] if is_enum_type(_type): + # For enums we can get the values directly and add them output += ' {\n' for value in _type.values: output += ' ' + value + '\n' output += '}' elif not is_enum_or_scalar(_type): + # This should be a type, or an interface + if _type.ast_node is not None: + # Get directives on type for directive in _type.ast_node.directives: output+= ' @' + directive.name.value output += get_directive_arguments(directive) output += ' {\n' + # Get fields for field_name, field in _type.fields.items(): output += ' ' + field_name + # Get arguments for field if hasattr(field, 'args') and field.args: output += '(' for arg_name, arg in field.args.items(): @@ -933,8 +948,10 @@ def printSchemaWithDirectives(schema): output += ': ' + str(field.type) + # Used to make sure we don't add the same directive multiple times to the same field directives_set = set() + # Get all directives directly on field for directive in field.ast_node.directives: if not directive.name.value in directives_set: output+= ' @' + directive.name.value @@ -943,6 +960,7 @@ def printSchemaWithDirectives(schema): if hasattr(_type, 'interfaces'): + # Get all inherited directives for interface in _type.interfaces: if field_name in interface.fields: for directive in interface.fields[field_name].ast_node.directives: @@ -955,7 +973,7 @@ def printSchemaWithDirectives(schema): output += '\n' output += '}' - + if _type.ast_node is not None: output += '\n\n' From da5f223e27910fef0bc6c78d092f1fab287be5f8 Mon Sep 17 00:00:00 2001 From: leier318 Date: Fri, 29 May 2020 11:31:10 +0200 Subject: [PATCH 06/14] Corrected some minor stuff and added a new helper function --- graphql-api-generator/utils/utils.py | 71 +++++++++++++++++----------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/graphql-api-generator/utils/utils.py b/graphql-api-generator/utils/utils.py index 65bfb0a..d5f636e 100644 --- a/graphql-api-generator/utils/utils.py +++ b/graphql-api-generator/utils/utils.py @@ -247,19 +247,20 @@ def add_input_to_create(schema: GraphQLSchema): for _type in schema.type_map.values(): if not is_db_schema_defined_type(_type) or is_interface_type(_type): continue - make += f'\nextend input _InputToCreate{_type.name} {{ ' + make += f'\nextend input _InputToCreate{_type.name} {{' for field_name, field in _type.fields.items(): if field_name == 'id' or field_name[0] == '_': continue inner_field_type = get_named_type(field.type) + if is_enum_or_scalar(inner_field_type): - make += f'{field_name}: {field.type} ' + make += f' {field_name}: {field.type} ' else: schema = extend_connect(schema, _type, inner_field_type, field_name) connect_name = f'_InputToConnect{capitalize(field_name)}Of{_type.name}' connect = copy_wrapper_structure(schema.type_map[connect_name], field.type) make += f' {field_name}: {connect} ' - make += '} ' + make += '}' schema = add_to_schema(schema, make) return schema @@ -858,6 +859,39 @@ def get_directive_arguments(directive): return output +def get_field_directives(field, field_name, _type): + """ + Get the directives of given field, and return them as string + :param schema: + :return string: + """ + + output = '' + + # Used to make sure we don't add the same directive multiple times to the same field + directives_set = set() + + # Get all directives directly on field + for directive in field.ast_node.directives: + if not directive.name.value in directives_set: + output+= ' @' + directive.name.value + directives_set.add(directive.name.value) + output += get_directive_arguments(directive) + + + if hasattr(_type, 'interfaces'): + # Get all inherited directives + for interface in _type.interfaces: + if field_name in interface.fields: + for directive in interface.fields[field_name].ast_node.directives: + directive_str = directive_from_interface(directive, interface.name) + if not directive_str in directives_set: + output+= ' @' + directive_str + directives_set.add(directive_str) + + return output + + def printSchemaWithDirectives(schema): """ Ouputs the given schema as string, in the format we want it. @@ -883,9 +917,9 @@ def printSchemaWithDirectives(schema): output+= ' on ' for _location in _dir.locations: - output+= _location._name_ + ', ' + output+= _location._name_ + ' | ' - output = output[:-2] + '\n\n' + output = output[:-3] + '\n\n' # Two special directives that should not exists in the db schema output += 'directive @_requiredForTarget_AccordingToInterface(interface: String!) on FIELD_DEFINITION\n\n' @@ -914,8 +948,8 @@ def printSchemaWithDirectives(schema): if hasattr(_type, 'interfaces') and _type.interfaces: output += ' implements ' for interface in _type.interfaces: - output += interface.name + ', ' - output = output[:-2] + output += interface.name + ' & ' + output = output[:-3] if is_enum_type(_type): # For enums we can get the values directly and add them @@ -948,27 +982,8 @@ def printSchemaWithDirectives(schema): output += ': ' + str(field.type) - # Used to make sure we don't add the same directive multiple times to the same field - directives_set = set() - - # Get all directives directly on field - for directive in field.ast_node.directives: - if not directive.name.value in directives_set: - output+= ' @' + directive.name.value - directives_set.add(directive.name.value) - output += get_directive_arguments(directive) - - - if hasattr(_type, 'interfaces'): - # Get all inherited directives - for interface in _type.interfaces: - if field_name in interface.fields: - for directive in interface.fields[field_name].ast_node.directives: - directive_str = directive_from_interface(directive, interface.name) - if not directive_str in directives_set: - output+= ' @' + directive_str - directives_set.add(directive_str) - + # Add directives + output += get_field_directives(field, field_name, _type) output += '\n' From 7c5414a149d2900a5b9e44d2aa49b7a89bc3b479 Mon Sep 17 00:00:00 2001 From: leier318 Date: Mon, 1 Jun 2020 14:26:27 +0200 Subject: [PATCH 07/14] Small changes before pulling master --- graphql-api-generator/utils/utils.py | 1 + graphql-server/drivers/arangodb/driver.js | 179 +++++++++++----------- 2 files changed, 92 insertions(+), 88 deletions(-) diff --git a/graphql-api-generator/utils/utils.py b/graphql-api-generator/utils/utils.py index d5f636e..49dc6d6 100644 --- a/graphql-api-generator/utils/utils.py +++ b/graphql-api-generator/utils/utils.py @@ -920,6 +920,7 @@ def printSchemaWithDirectives(schema): output+= _location._name_ + ' | ' output = output[:-3] + '\n\n' + print(_dir.name) # Two special directives that should not exists in the db schema output += 'directive @_requiredForTarget_AccordingToInterface(interface: String!) on FIELD_DEFINITION\n\n' diff --git a/graphql-server/drivers/arangodb/driver.js b/graphql-server/drivers/arangodb/driver.js index 171238d..c6bb059 100644 --- a/graphql-server/drivers/arangodb/driver.js +++ b/graphql-server/drivers/arangodb/driver.js @@ -12,6 +12,7 @@ module.exports = { let db_name = process.env.db ? process.env.db : 'dev-db'; let url = process.env.URL ? process.env.URL : 'http://localhost:8529'; let drop = process.env.DROP === 'true'; + let diasableDiractivesChecking = process.env.DISABLE_DIRECTIVES_CHECKING === 'true'; disableEdgeValidation = process.env.DISABLE_EDGE_VALIDATION === 'true'; db = new arangojs.Database({ url: url }); @@ -1158,94 +1159,96 @@ function addPossibleEdgeTypes(query, schema, type_name, field_name, use_aql = tr * @param schema */ function addfinalDirectiveChecksForType(ctxt, type, id, schema) { - for (let f in type.getFields()) { - let field = type.getFields()[f]; - for (dir of field.astNode.directives) { - if (dir.name.value == 'noloops') { - let collection = asAQLVar(`db.${getEdgeCollectionName(type.name, field.name)}`); - ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR v IN 1..1 OUTBOUND ${id} ${collection} FILTER ${id} == v._id RETURN v\`).next()){`); - ctxt.trans.finalConstraintChecks.push(` throw "Field ${f} in ${type.name} is breaking a @noloops directive!";`); - ctxt.trans.finalConstraintChecks.push(`}`); - } - else if (dir.name.value == 'distinct') { - let collection = asAQLVar(`db.${getEdgeCollectionName(type.name, field.name)}`); - ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR v, e IN 1..1 OUTBOUND ${id} ${collection} FOR v2, e2 IN 1..1 OUTBOUND ${id} ${collection} FILTER v._id == v2._id AND e._id != e2._id RETURN v\`).next()){`); - ctxt.trans.finalConstraintChecks.push(` throw "Field ${f} in ${type.name} is breaking a @distinct directive!";`); - ctxt.trans.finalConstraintChecks.push(`}`); - } - else if (dir.name.value == 'uniqueForTarget') { - // The direct variant of @uniqueForTarget - // edge is named after current type etc. - let collection = asAQLVar(`db.${getEdgeCollectionName(type.name, field.name)}`); - ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR v, e IN 1..1 OUTBOUND ${id} ${collection} FOR v2, e2 IN 1..1 INBOUND v._id ${collection} FILTER e._id != e2._id RETURN v\`).next()){`); - ctxt.trans.finalConstraintChecks.push(` throw "Field ${f} in ${type.name} is breaking a @uniqueForTarget directive!";`); - ctxt.trans.finalConstraintChecks.push(`}`); - } - else if (dir.name.value == '_uniqueForTarget_AccordingToInterface') { - // The inherited/implemented variant of @uniqueForTarget - // The target does not only require at most one edge of this type, but at most one of any type implementing the interface - // Thankfully we got the name of the interface as a mandatory argument and can hence use this to get all types implementing it - - let interfaceName = dir.arguments[0].value.value; // If we add more arguments to the directive this will fail horrible. - // But that should not happen (and it is quite easy to fix) - - ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR v, e IN 1..1 OUTBOUND ${id}`); - addPossibleEdgeTypes(ctxt.trans.finalConstraintChecks, schema, interfaceName, field.name, false); - ctxt.trans.finalConstraintChecks.push(`FOR v2, e2 IN 1..1 INBOUND v._id`); - addPossibleEdgeTypes(ctxt.trans.finalConstraintChecks, schema, interfaceName, field.name, false); - ctxt.trans.finalConstraintChecks.push(`FILTER e._id != e2._id RETURN v\`).next()){`); - ctxt.trans.finalConstraintChecks.push(` throw "Field ${f} in ${type.name} is breaking a @_uniqueForTarget_AccordingToInterface directive!";`); - ctxt.trans.finalConstraintChecks.push(`}`); - } - else if (dir.name.value == 'requiredForTarget') { - // The direct variant of @requiredForTarget - // edge is named after current type etc. - let edgeCollection = asAQLVar(`db.${getEdgeCollectionName(type.name, field.name)}`); - - // The target type might be an interface, giving us slightly more to keep track of - // First, find the right collections to check - ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR x IN FLATTEN(FOR i IN [`); - addPossibleTypes(ctxt.trans.finalConstraintChecks, schema, graphql.getNamedType(field.type), false); - // Second, count all edges ending at objects in these collections - ctxt.trans.finalConstraintChecks.push(`] RETURN i) LET endpoints = ( FOR v IN 1..1 INBOUND x ${edgeCollection} RETURN v)`); - // If the count returns 0, we have an object breaking the directive - ctxt.trans.finalConstraintChecks.push(`FILTER LENGTH(endpoints) == 0 RETURN x\`).next()){`); - ctxt.trans.finalConstraintChecks.push(` throw "There are object(s) breaking the @requiredForTarget directive of Field ${f} in ${type.name}!";`); - ctxt.trans.finalConstraintChecks.push(`}`); - } - else if (dir.name.value == '_requiredForTarget_AccordingToInterface') { - // The inherited/implemented variant of @requiredForTarget - // The target does not directly require an edge of this type, but at least one of any type implementing the interface - // Thankfully we got the name of the interface as a mandatory argument and can hence use this to get all types implementing it - - let interfaceName = dir.arguments[0].value.value; // If we add more arguments to the directive this will fail horrible. - // But that should not happen (and it is quite easy to fix) - - // The target type might be an interface, giving us slightly more to keep track of - // First, find the right collections to check - ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR x IN FLATTEN(FOR i IN [`); - addPossibleTypes(ctxt.trans.finalConstraintChecks, schema, graphql.getNamedType(field.type), false); - // Second, count all edges ending at objects in these collections - ctxt.trans.finalConstraintChecks.push(`] RETURN i) LET endpoints = ( FOR v IN 1..1 INBOUND x `); - addPossibleEdgeTypes(ctxt.trans.finalConstraintChecks, schema, interfaceName, field.name, false); - ctxt.trans.finalConstraintChecks.push(` RETURN v)`); - // If the count returns 0, we have an object breaking the directive - ctxt.trans.finalConstraintChecks.push(`FILTER LENGTH(endpoints) == 0 RETURN x\`).next()){`); - ctxt.trans.finalConstraintChecks.push(` throw "There are object(s) breaking the inherited @_requiredForTarget_AccordingToInterface directive of Field ${f} in ${type.name}!";`); - ctxt.trans.finalConstraintChecks.push(`}`); - } - else if (dir.name.value == 'required' && field.name[0] == '_') { - // This is actually the reverse edge of a @requiredForTarget directive - - let pattern_string = `^_(.+?)From${type.name}$`; // get the non-reversed edge name - let re = new RegExp(pattern_string); - let field_name = re.exec(field.name)[1]; - - ctxt.trans.finalConstraintChecks.push(`if(!db._query(aql\`FOR v IN 1..1 INBOUND ${id}`); - addPossibleEdgeTypes(ctxt.trans.finalConstraintChecks, schema, graphql.getNamedType(field.type), field_name, false); - ctxt.trans.finalConstraintChecks.push(`RETURN x\`).next()){`); - ctxt.trans.finalConstraintChecks.push(` throw "Field ${f} in ${type.name} is breaking a @requiredForTarget directive (in reverse)!";`); - ctxt.trans.finalConstraintChecks.push(`}`); + if (!diasableDiractivesChecking) { + for (let f in type.getFields()) { + let field = type.getFields()[f]; + for (dir of field.astNode.directives) { + if (dir.name.value == 'noloops') { + let collection = asAQLVar(`db.${getEdgeCollectionName(type.name, field.name)}`); + ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR v IN 1..1 OUTBOUND ${id} ${collection} FILTER ${id} == v._id RETURN v\`).next()){`); + ctxt.trans.finalConstraintChecks.push(` throw "Field ${f} in ${type.name} is breaking a @noloops directive!";`); + ctxt.trans.finalConstraintChecks.push(`}`); + } + else if (dir.name.value == 'distinct') { + let collection = asAQLVar(`db.${getEdgeCollectionName(type.name, field.name)}`); + ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR v, e IN 1..1 OUTBOUND ${id} ${collection} FOR v2, e2 IN 1..1 OUTBOUND ${id} ${collection} FILTER v._id == v2._id AND e._id != e2._id RETURN v\`).next()){`); + ctxt.trans.finalConstraintChecks.push(` throw "Field ${f} in ${type.name} is breaking a @distinct directive!";`); + ctxt.trans.finalConstraintChecks.push(`}`); + } + else if (dir.name.value == 'uniqueForTarget') { + // The direct variant of @uniqueForTarget + // edge is named after current type etc. + let collection = asAQLVar(`db.${getEdgeCollectionName(type.name, field.name)}`); + ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR v, e IN 1..1 OUTBOUND ${id} ${collection} FOR v2, e2 IN 1..1 INBOUND v._id ${collection} FILTER e._id != e2._id RETURN v\`).next()){`); + ctxt.trans.finalConstraintChecks.push(` throw "Field ${f} in ${type.name} is breaking a @uniqueForTarget directive!";`); + ctxt.trans.finalConstraintChecks.push(`}`); + } + else if (dir.name.value == '_uniqueForTarget_AccordingToInterface') { + // The inherited/implemented variant of @uniqueForTarget + // The target does not only require at most one edge of this type, but at most one of any type implementing the interface + // Thankfully we got the name of the interface as a mandatory argument and can hence use this to get all types implementing it + + let interfaceName = dir.arguments[0].value.value; // If we add more arguments to the directive this will fail horrible. + // But that should not happen (and it is quite easy to fix) + + ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR v, e IN 1..1 OUTBOUND ${id}`); + addPossibleEdgeTypes(ctxt.trans.finalConstraintChecks, schema, interfaceName, field.name, false); + ctxt.trans.finalConstraintChecks.push(`FOR v2, e2 IN 1..1 INBOUND v._id`); + addPossibleEdgeTypes(ctxt.trans.finalConstraintChecks, schema, interfaceName, field.name, false); + ctxt.trans.finalConstraintChecks.push(`FILTER e._id != e2._id RETURN v\`).next()){`); + ctxt.trans.finalConstraintChecks.push(` throw "Field ${f} in ${type.name} is breaking a @_uniqueForTarget_AccordingToInterface directive!";`); + ctxt.trans.finalConstraintChecks.push(`}`); + } + else if (dir.name.value == 'requiredForTarget') { + // The direct variant of @requiredForTarget + // edge is named after current type etc. + let edgeCollection = asAQLVar(`db.${getEdgeCollectionName(type.name, field.name)}`); + + // The target type might be an interface, giving us slightly more to keep track of + // First, find the right collections to check + ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR x IN FLATTEN(FOR i IN [`); + addPossibleTypes(ctxt.trans.finalConstraintChecks, schema, graphql.getNamedType(field.type), false); + // Second, count all edges ending at objects in these collections + ctxt.trans.finalConstraintChecks.push(`] RETURN i) LET endpoints = ( FOR v IN 1..1 INBOUND x ${edgeCollection} RETURN v)`); + // If the count returns 0, we have an object breaking the directive + ctxt.trans.finalConstraintChecks.push(`FILTER LENGTH(endpoints) == 0 RETURN x\`).next()){`); + ctxt.trans.finalConstraintChecks.push(` throw "There are object(s) breaking the @requiredForTarget directive of Field ${f} in ${type.name}!";`); + ctxt.trans.finalConstraintChecks.push(`}`); + } + else if (dir.name.value == '_requiredForTarget_AccordingToInterface') { + // The inherited/implemented variant of @requiredForTarget + // The target does not directly require an edge of this type, but at least one of any type implementing the interface + // Thankfully we got the name of the interface as a mandatory argument and can hence use this to get all types implementing it + + let interfaceName = dir.arguments[0].value.value; // If we add more arguments to the directive this will fail horrible. + // But that should not happen (and it is quite easy to fix) + + // The target type might be an interface, giving us slightly more to keep track of + // First, find the right collections to check + ctxt.trans.finalConstraintChecks.push(`if(db._query(aql\`FOR x IN FLATTEN(FOR i IN [`); + addPossibleTypes(ctxt.trans.finalConstraintChecks, schema, graphql.getNamedType(field.type), false); + // Second, count all edges ending at objects in these collections + ctxt.trans.finalConstraintChecks.push(`] RETURN i) LET endpoints = ( FOR v IN 1..1 INBOUND x `); + addPossibleEdgeTypes(ctxt.trans.finalConstraintChecks, schema, interfaceName, field.name, false); + ctxt.trans.finalConstraintChecks.push(` RETURN v)`); + // If the count returns 0, we have an object breaking the directive + ctxt.trans.finalConstraintChecks.push(`FILTER LENGTH(endpoints) == 0 RETURN x\`).next()){`); + ctxt.trans.finalConstraintChecks.push(` throw "There are object(s) breaking the inherited @_requiredForTarget_AccordingToInterface directive of Field ${f} in ${type.name}!";`); + ctxt.trans.finalConstraintChecks.push(`}`); + } + else if (dir.name.value == 'required' && field.name[0] == '_') { + // This is actually the reverse edge of a @requiredForTarget directive + + let pattern_string = `^_(.+?)From${type.name}$`; // get the non-reversed edge name + let re = new RegExp(pattern_string); + let field_name = re.exec(field.name)[1]; + + ctxt.trans.finalConstraintChecks.push(`if(!db._query(aql\`FOR v IN 1..1 INBOUND ${id}`); + addPossibleEdgeTypes(ctxt.trans.finalConstraintChecks, schema, graphql.getNamedType(field.type), field_name, false); + ctxt.trans.finalConstraintChecks.push(`RETURN x\`).next()){`); + ctxt.trans.finalConstraintChecks.push(` throw "Field ${f} in ${type.name} is breaking a @requiredForTarget directive (in reverse)!";`); + ctxt.trans.finalConstraintChecks.push(`}`); + } } } } From 55ec3f045c320ec7bf88055deff457d3092a4e06 Mon Sep 17 00:00:00 2001 From: leier318 Date: Wed, 3 Jun 2020 15:52:59 +0200 Subject: [PATCH 08/14] Added comment that client_tests does not handle directives --- graphql-server/tests/client-tests.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/graphql-server/tests/client-tests.js b/graphql-server/tests/client-tests.js index c7a8fea..c180293 100644 --- a/graphql-server/tests/client-tests.js +++ b/graphql-server/tests/client-tests.js @@ -10,10 +10,11 @@ * possible subfields down to a configurable level. A simple equality filter is added for the ID of the type. * * - * What id does NOT do: + * What it does NOT do: * - Does not verify that the returned object matches the expected result. (TODO) * - Executes no queries over annotated edges (TODO) * - Executes no mutations to annotate edges (TODO). + * - Care about / check directives (Requires process.env.DISABLE_DIRECTIVES_CHECKING to be set to true if directives are used) (TODO). * - ... a lot of other things probably. */ From 6cc89c643a977dcff937a4b8fa01e7adec5d978b Mon Sep 17 00:00:00 2001 From: leier318 Date: Wed, 3 Jun 2020 15:55:41 +0200 Subject: [PATCH 09/14] Corrected a misstake in driver --- graphql-server/drivers/arangodb/driver.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/graphql-server/drivers/arangodb/driver.js b/graphql-server/drivers/arangodb/driver.js index c6bb059..82c2eb0 100644 --- a/graphql-server/drivers/arangodb/driver.js +++ b/graphql-server/drivers/arangodb/driver.js @@ -6,13 +6,14 @@ const waitOn = require('wait-on'); let db; let disableEdgeValidation; +let diasableDiractivesChecking module.exports = { init: async function(schema){ let db_name = process.env.db ? process.env.db : 'dev-db'; let url = process.env.URL ? process.env.URL : 'http://localhost:8529'; let drop = process.env.DROP === 'true'; - let diasableDiractivesChecking = process.env.DISABLE_DIRECTIVES_CHECKING === 'true'; + diasableDiractivesChecking = process.env.DISABLE_DIRECTIVES_CHECKING === 'true'; disableEdgeValidation = process.env.DISABLE_EDGE_VALIDATION === 'true'; db = new arangojs.Database({ url: url }); From 79c862c1b25f060c7f51f340c4272d61a6f44acd Mon Sep 17 00:00:00 2001 From: leier318 Date: Thu, 4 Jun 2020 12:19:01 +0200 Subject: [PATCH 10/14] Corrected @_unique. being skipped, corrected directives for field handling for input types --- graphql-api-generator/utils/utils.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/graphql-api-generator/utils/utils.py b/graphql-api-generator/utils/utils.py index 8f67a60..87106ae 100644 --- a/graphql-api-generator/utils/utils.py +++ b/graphql-api-generator/utils/utils.py @@ -815,8 +815,8 @@ def directive_from_interface(directive, interface_name): # The only two cases who needs special attention is @requiredForTarget and @uniqueForTarget if directive_string == 'requiredForTarget': directive_string = '_requiredForTarget_AccordingToInterface(interface: "' + interface_name + '")' - #elif directive_string == 'uniqueForTarget': - # directive_string = '_uniqueForTarget_AccordingToInterface(interface: "' + interface_name + '")' + elif directive_string == 'uniqueForTarget': + directive_string = '_uniqueForTarget_AccordingToInterface(interface: "' + interface_name + '")' else: directive_string += get_directive_arguments(directive) @@ -860,7 +860,7 @@ def get_directive_arguments(directive): return output -def get_field_directives(field, field_name, _type, schema): +def get_field_directives(field_name, _type, schema): """ Get the directives of given field, and return them as string :param field: @@ -889,6 +889,13 @@ def get_field_directives(field, field_name, _type, schema): else: return '' + # We got type without fields, just return empty + if not hasattr(_type, 'fields'): + return '' + + # Get the field from the correct type + field = _type.fields[field_name] + # Get all directives directly on field for directive in field.ast_node.directives: if not directive.name.value in directives_set: @@ -1036,7 +1043,7 @@ def print_schema_with_directives(schema): output += ': ' + str(field.type) # Add directives - output += get_field_directives(field, field_name, _type, schema) + output += get_field_directives(field_name, _type, schema) output += '\n' From 53c4da49dd3e51044a8d817465b1b9267b277709 Mon Sep 17 00:00:00 2001 From: leier318 Date: Thu, 4 Jun 2020 12:31:34 +0200 Subject: [PATCH 11/14] Got rid of the on argument_definition for directives. have no clue why they where needed for a while --- graphql-api-generator/utils/utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/graphql-api-generator/utils/utils.py b/graphql-api-generator/utils/utils.py index 87106ae..93f5261 100644 --- a/graphql-api-generator/utils/utils.py +++ b/graphql-api-generator/utils/utils.py @@ -955,12 +955,12 @@ def print_schema_with_directives(schema): manual_directives = {'key':'directive @key(fields: [String!]!) on OBJECT | INPUT_OBJECT',\ - 'distinct':'directive @distinct on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION',\ - 'noloops':'directive @noloops on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION',\ - 'requiredForTarget':'directive @requiredForTarget on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION',\ - 'uniqueForTarget':'directive @uniqueForTarget on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION',\ - '_requiredForTarget_AccordingToInterface':'directive @_requiredForTarget_AccordingToInterface(interface: String!) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION',\ - '_uniqueForTarget_AccordingToInterface':'directive @_uniqueForTarget_AccordingToInterface(interface: String!) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION'\ + 'distinct':'directive @distinct on FIELD_DEFINITION | INPUT_FIELD_DEFINITION',\ + 'noloops':'directive @noloops on FIELD_DEFINITION | INPUT_FIELD_DEFINITION',\ + 'requiredForTarget':'directive @requiredForTarget on FIELD_DEFINITION | INPUT_FIELD_DEFINITION',\ + 'uniqueForTarget':'directive @uniqueForTarget on FIELD_DEFINITION | INPUT_FIELD_DEFINITION',\ + '_requiredForTarget_AccordingToInterface':'directive @_requiredForTarget_AccordingToInterface(interface: String!) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION',\ + '_uniqueForTarget_AccordingToInterface':'directive @_uniqueForTarget_AccordingToInterface(interface: String!) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION'\ } output = '' From 3db6b3abccd0a87a748b51fa25172a9f5710e517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Keskisa=CC=88rkka=CC=88?= Date: Fri, 5 Jun 2020 13:31:11 +0200 Subject: [PATCH 12/14] make generated DateTime appear as a user-defined scalar during output --- graphql-api-generator/generator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/graphql-api-generator/generator.py b/graphql-api-generator/generator.py index f6f33d3..9625fe9 100644 --- a/graphql-api-generator/generator.py +++ b/graphql-api-generator/generator.py @@ -269,7 +269,8 @@ def datetime_control(schema): if not is_scalar_type(schema.type_map['DateTime']): raise Exception('DateTime exists but is not scalar type: ' + schema.type_map['DateTime']) else: - schema.type_map['DateTime'] = GraphQLScalarType('DateTime') + # ast_node definition ensures that DateTime appears as a user-defined scalar + schema.type_map['DateTime'] = GraphQLScalarType('DateTime', ast_node=ScalarTypeDefinitionNode()) if not is_scalar_type(schema.type_map['DateTime']): raise Exception('DateTime could not be added as scalar!') From 7ef87a4f0fe9cb961426bce3267eeb5eb7a677a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Keskisa=CC=88rkka=CC=88?= Date: Fri, 5 Jun 2020 13:34:25 +0200 Subject: [PATCH 13/14] add directive @required to manually added drirectives, minor optimizations to schema printer --- graphql-api-generator/utils/utils.py | 82 +++++++++++----------------- 1 file changed, 33 insertions(+), 49 deletions(-) diff --git a/graphql-api-generator/utils/utils.py b/graphql-api-generator/utils/utils.py index 93f5261..0de4721 100644 --- a/graphql-api-generator/utils/utils.py +++ b/graphql-api-generator/utils/utils.py @@ -947,52 +947,44 @@ def get_type_directives(_type, schema): def print_schema_with_directives(schema): """ - Ouputs the given schema as string, in the format we want it. + Outputs the given schema as string, in the format we want it. Types and fields will all contain directives :param schema: :return string: """ - - - manual_directives = {'key':'directive @key(fields: [String!]!) on OBJECT | INPUT_OBJECT',\ - 'distinct':'directive @distinct on FIELD_DEFINITION | INPUT_FIELD_DEFINITION',\ - 'noloops':'directive @noloops on FIELD_DEFINITION | INPUT_FIELD_DEFINITION',\ - 'requiredForTarget':'directive @requiredForTarget on FIELD_DEFINITION | INPUT_FIELD_DEFINITION',\ - 'uniqueForTarget':'directive @uniqueForTarget on FIELD_DEFINITION | INPUT_FIELD_DEFINITION',\ - '_requiredForTarget_AccordingToInterface':'directive @_requiredForTarget_AccordingToInterface(interface: String!) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION',\ - '_uniqueForTarget_AccordingToInterface':'directive @_uniqueForTarget_AccordingToInterface(interface: String!) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION'\ - } - + manual_directives = { + 'required': 'directive @required on FIELD_DEFINITION', + 'key': 'directive @key(fields: [String!]!) on OBJECT | INPUT_OBJECT', + 'distinct': 'directive @distinct on FIELD_DEFINITION | INPUT_FIELD_DEFINITION', + 'noloops': 'directive @noloops on FIELD_DEFINITION | INPUT_FIELD_DEFINITION', + 'requiredForTarget': 'directive @requiredForTarget on FIELD_DEFINITION | INPUT_FIELD_DEFINITION', + 'uniqueForTarget': 'directive @uniqueForTarget on FIELD_DEFINITION | INPUT_FIELD_DEFINITION', + '_requiredForTarget_AccordingToInterface': 'directive @_requiredForTarget_AccordingToInterface(interface: String!) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION', + '_uniqueForTarget_AccordingToInterface': 'directive @_uniqueForTarget_AccordingToInterface(interface: String!) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION' + } output = '' - # Add directives for _dir in schema.directives: - if _dir.ast_node is not None and _dir.name not in manual_directives.keys(): - # If the directive does not have a proper ast_node - # Then it is an non-user defined directive, and can hence, be skipped - output+= 'directive @' + _dir.name - - if _dir.ast_node.arguments: - output+= '(' - for arg in _dir.ast_node.arguments: - output+= arg.name.value + ': ' + ast_type_to_string(arg.type) + ', ' - output = output[:-2] + ')' - - output+= ' on ' - for _location in _dir.locations: - output+= _location._name_ + ' | ' - - output = output[:-3] + '\n\n' + # Skip non-user defined directives + if _dir.ast_node is None or _dir.name in manual_directives.keys(): + continue + + output += f'directive @{_dir.name}' + if _dir.ast_node.arguments: + args = ', '.join([f'{arg.name.value}: {ast_type_to_string(arg.type)}' for arg in _dir.ast_node.arguments]) + output += f'({args})' + + output += ' on ' + ' | '.join([loc.name for loc in _dir.locations]) + output += '\n\n' - # Manualy handled directives + # Manually handled directives for _dir in manual_directives.values(): - output+= _dir + '\n\n' - - # For each type, and output the types sortad after name - for _type in sorted(schema.type_map.values(), key=lambda x : x.name): + output += _dir + '\n\n' + # For each type, and output the types sorted by name + for _type in sorted(schema.type_map.values(), key=lambda x: x.name): # Internal type - if _type.name[:2] == '__': + if _type.name.startswith('__'): continue if is_interface_type(_type): @@ -1000,19 +992,16 @@ def print_schema_with_directives(schema): elif is_enum_type(_type): output += 'enum ' + _type.name elif is_scalar_type(_type): + # Skip non-user defined directives if _type.ast_node is not None: - # If the scalar does not have a proper ast_node - # Then it is an non-user defined scalar, and can hence, be skipped output += 'scalar ' + _type.name elif is_input_type(_type): output += 'input ' + _type.name - else: # type, hopefully + else: output += 'type ' + _type.name if hasattr(_type, 'interfaces') and _type.interfaces: output += ' implements ' - for interface in _type.interfaces: - output += interface.name + ' & ' - output = output[:-3] + output += ' & '.join([interface.name for interface in _type.interfaces]) if is_enum_type(_type): # For enums we can get the values directly and add them @@ -1023,10 +1012,8 @@ def print_schema_with_directives(schema): elif not is_enum_or_scalar(_type): # This should be a type, or an interface - # Get directives on type output += get_type_directives(_type, schema) - output += ' {\n' # Get fields @@ -1035,16 +1022,13 @@ def print_schema_with_directives(schema): # Get arguments for field if hasattr(field, 'args') and field.args: - output += '(' - for arg_name, arg in field.args.items(): - output += arg_name + ': ' + str(arg.type) + ', ' - output = output[:-2] + ')' + args = ', '.join([f'{arg_name}: {arg.type}' for arg_name, arg in field.args.items()]) + output += f'({args})' output += ': ' + str(field.type) # Add directives output += get_field_directives(field_name, _type, schema) - output += '\n' output += '}' @@ -1052,4 +1036,4 @@ def print_schema_with_directives(schema): if _type.ast_node is not None: output += '\n\n' - return output \ No newline at end of file + return output From 4fc9707729e887cfbabec55b8c4d24fee7008350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20Keskisa=CC=88rkka=CC=88?= Date: Mon, 8 Jun 2020 09:17:04 +0200 Subject: [PATCH 14/14] set directives checking by default --- graphql-server/drivers/arangodb/driver.js | 2 +- graphql-server/server.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/graphql-server/drivers/arangodb/driver.js b/graphql-server/drivers/arangodb/driver.js index 0f865cd..2c59ae9 100644 --- a/graphql-server/drivers/arangodb/driver.js +++ b/graphql-server/drivers/arangodb/driver.js @@ -15,7 +15,7 @@ module.exports = { let db_name = args.db_name || 'dev-db'; let url = args.url || 'http://localhost:8529'; let drop = args.drop || false; - disableDirectivesChecking = args.disableDirectivesChecking || false; + disableDirectivesChecking = args.disableDirectivesChecking || true; disableEdgeValidation = args.disableEdgeValidation || false; db = new arangojs.Database({ url: url }); diff --git a/graphql-server/server.js b/graphql-server/server.js index 8f74384..8a772ab 100644 --- a/graphql-server/server.js +++ b/graphql-server/server.js @@ -22,7 +22,7 @@ async function run(){ 'db_name': process.env.DB_NAME || 'spirit-db', 'url': process.env.URL || 'http://localhost:8529', 'drop': process.env.DROP === 'true', - 'disableDirectivesChecking': process.env.DISABLE_DIRECTIVES_CHECKING === 'true', + //'disableDirectivesChecking': process.env.DISABLE_DIRECTIVES_CHECKING === 'false', 'disableEdgeValidation': process.env.DISABLE_EDGE_VALIDATION === 'true' }; await driver.init(args);