def validate(self):
        """Check whether the encoded recipe is valid. It must describe a directed
        acyclical graph, all connections must be defined, etc."""
        if not self.recipe:
            raise workflows.Error("Invalid recipe: No recipe defined")

        # Without a 'start' node nothing would happen
        if "start" not in self.recipe:
            raise workflows.Error('Invalid recipe: "start" node missing')
        if not self.recipe["start"]:
            raise workflows.Error('Invalid recipe: "start" node empty')
        if not all(
            isinstance(x, (list, tuple)) and len(x) == 2 for x in self.recipe["start"]
        ):
            raise workflows.Error('Invalid recipe: "start" node invalid')
        if any(x[0] == "start" for x in self.recipe["start"]):
            raise workflows.Error('Invalid recipe: "start" node points to itself')

        # Check that 'error' node points to regular nodes only
        if "error" in self.recipe and isinstance(
            self.recipe["error"], (list, tuple, basestring)
        ):
            if "start" in self.recipe["error"]:
                raise workflows.Error(
                    'Invalid recipe: "error" node points to "start" node'
                )
            if "error" in self.recipe["error"]:
                raise workflows.Error('Invalid recipe: "error" node points to itself')

        # All other nodes must be numeric
        nodes = list(
            filter(
                lambda x: not isinstance(x, int) and x not in ("start", "error"),
                self.recipe,
            )
        )
        if nodes:
            raise workflows.Error('Invalid recipe: Node "%s" is not numeric' % nodes[0])

        # Detect cycles
        touched_nodes = set(["start", "error"])

        def flatten_links(struct):
            """Take an output/error link object, list or dictionary and return flat list of linked nodes."""
            if struct is None:
                return []
            if isinstance(struct, int):
                return [struct]
            if isinstance(struct, list):
                if not all(isinstance(x, int) for x in struct):
                    raise workflows.Error(
                        "Invalid recipe: Invalid link in recipe (%s)" % str(struct)
                    )
                return struct
            if isinstance(struct, dict):
                joined_list = []
                for sub_list in struct.values():
                    joined_list += flatten_links(sub_list)
                return joined_list
            raise workflows.Error(
                "Invalid recipe: Invalid link in recipe (%s)" % str(struct)
            )

        def find_cycles(path):
            """Depth-First-Search helper function to identify cycles."""
            if path[-1] not in self.recipe:
                raise workflows.Error(
                    'Invalid recipe: Node "%s" is referenced via "%s" but missing'
                    % (str(path[-1]), str(path[:-1]))
                )
            touched_nodes.add(path[-1])
            node = self.recipe[path[-1]]
            for outgoing in ("output", "error"):
                if outgoing in node:
                    references = flatten_links(node[outgoing])
                    for n in references:
                        if n in path:
                            raise workflows.Error(
                                "Invalid recipe: Recipe contains cycle (%s -> %s)"
                                % (str(path), str(n))
                            )
                        find_cycles(path + [n])

        for link in self.recipe["start"]:
            find_cycles(["start", link[0]])
        if "error" in self.recipe:
            if isinstance(self.recipe["error"], (list, tuple)):
                for link in self.recipe["error"]:
                    find_cycles(["error", link])
            else:
                find_cycles(["error", self.recipe["error"]])

        # Test recipe for unreferenced nodes
        for node in self.recipe:
            if node not in touched_nodes:
                raise workflows.Error(
                    'Invalid recipe: Recipe contains unreferenced node "%s"' % str(node)
                )
Check whether the encoded recipe is valid. It must describe a directed
        acyclical graph, all connections must be defined, etc.