Browse Source

Fragment validation rules;

bjorn 9 years ago
parent
commit
45879a535d
3 changed files with 90 additions and 28 deletions
  1. 43 22
      rules.lua
  2. 2 2
      schema.lua
  3. 45 4
      validate.lua

+ 43 - 22
rules.lua

@@ -58,20 +58,6 @@ function rules.compositeFieldsAreNotLeaves(node, context)
   end
 end
 
-function rules.inlineFragmentValidTypeCondition(node, context)
-  if not node.typeCondition then return end
-
-  local kind = context.objects[#context.objects]
-
-  if kind == false then
-    error('Inline fragment type condition refers to non-existent type')
-  end
-
-  if kind.__type ~= 'Object' and kind.__type ~= 'Interface' and kind.__type ~= 'Union' then
-    error('Inline fragment type condition was not an Object, Interface, or Union')
-  end
-end
-
 function rules.unambiguousSelections(node, context)
   local selectionMap = {}
 
@@ -148,16 +134,12 @@ function rules.unambiguousSelections(node, context)
 
         validateField(key, fieldEntry)
       elseif selection.kind == 'inlineFragment' then
-        local parentType = parentType
-
-        if selection.typeCondition then
-          parentType = context.schema:getType(selection.typeCondition.name.value) or parentType
-        end
-
+        local parentType = selection.typeCondition and context.schema:getType(selection.typeCondition.name.value) or parentType
         validateSelectionSet(selection.selectionSet, parentType)
       elseif selection.kind == 'fragmentSpread' then
-        -- FIXME find fragment by name, get type condition to compute parentType
-        validateSelectionSet(selection.selectionSet, parentType)
+        local fragmentDefinition = context.fragmentMap[selection.name.value]
+        local parentType = context.schema:getType(fragmentDefinition.typeCondition.name.value)
+        validateSelectionSet(fragmentDefinition.selectionSet, parentType)
       end
     end
   end
@@ -255,4 +237,43 @@ function rules.requiredArgumentsPresent(node, context)
   end
 end
 
+function rules.uniqueFragmentNames(node, context)
+  local fragments = {}
+  for _, definition in ipairs(node.definitions) do
+    if definition.kind == 'fragmentDefinition' then
+      local name = definition.name.value
+      if fragments[name] then
+        error('Encountered multiple fragments named "' .. name .. '"')
+      end
+      fragments[name] = true
+    end
+  end
+end
+
+function rules.fragmentHasValidType(node, context)
+  if not node.typeCondition then return end
+
+  local name = node.typeCondition.name.value
+  local kind = context.schema:getType(name)
+
+  if not kind then
+    error('Fragment refers to non-existent type "' .. name .. '"')
+  end
+
+  if kind.__type ~= 'Object' and kind.__type ~= 'Interface' and kind.__type ~= 'Union' then
+    error('Fragment type must be an Object, Interface, or Union, got ' .. kind.__type)
+  end
+end
+
+function rules.noUnusedFragments(node, context)
+  for _, definition in ipairs(node.definitions) do
+    if definition.kind == 'fragmentDefinition' then
+      local name = definition.name.value
+      if not context.usedFragments[name] then
+        error('Fragment "' .. name .. '" was not used.')
+      end
+    end
+  end
+end
+
 return rules

+ 2 - 2
schema.lua

@@ -9,6 +9,8 @@ function schema.create(config)
     self[k] = v
   end
 
+  self.typeMap = {}
+
   local function generateTypeMap(node)
     if node.__type == 'NonNull' or node.__type == 'List' then
       return generateTypeMap(node.ofType)
@@ -39,8 +41,6 @@ function schema.create(config)
     end
   end
 
-  self.typeMap = {}
-
   generateTypeMap(self.query)
 
   return setmetatable(self, schema)

+ 45 - 4
validate.lua

@@ -2,9 +2,19 @@ local rules = require 'rules'
 
 local visitors = {
   document = {
+    enter = function(node, context)
+      for _, definition in ipairs(node.definitions) do
+        if definition.kind == 'fragmentDefinition' then
+          context.fragmentMap[definition.name.value] = definition
+        end
+      end
+    end,
+
     children = function(node, context)
       return node.definitions
-    end
+    end,
+
+    rules = { rules.uniqueFragmentNames, exit = { rules.noUnusedFragments } }
   },
 
   operation = {
@@ -87,16 +97,41 @@ local visitors = {
       end
     end,
 
-    rules = { rules.inlineFragmentValidTypeCondition }
+    rules = { rules.fragmentHasValidType }
+  },
+
+  fragmentSpread = {
+    enter = function(node, context)
+      context.usedFragments[node.name.value] = true
+    end
+  },
+
+  fragmentDefinition = {
+    enter = function(node, context)
+      kind = context.schema:getType(node.typeCondition.name.value) or false
+      table.insert(context.objects, kind)
+    end,
+
+    exit = function(node, context)
+      table.remove(context.objects)
+    end,
+
+    children = function(node)
+      return { node.selectionSet }
+    end,
+
+    rules = { rules.fragmentHasValidType }
   }
 }
 
 return function(schema, tree)
   local context = {
+    schema = schema,
+    fragmentMap = {},
     operationNames = {},
     hasAnonymousOperation = false,
-    objects = {},
-    schema = schema
+    usedFragments = {},
+    objects = {}
   }
 
   local function visit(node)
@@ -123,6 +158,12 @@ return function(schema, tree)
       end
     end
 
+    if visitor.rules and visitor.rules.exit then
+      for i = 1, #visitor.rules.exit do
+        visitor.rules.exit[i](node, context)
+      end
+    end
+
     if visitor.exit then
       visitor.exit(node, context)
     end