makeplane/plane · error · DRFValidationError

invalid_leaf

invalid_leaf

Error message

Leaf filter must be a non-empty JSON object

What it means

Raised by _validate_leaf when the leaf node (an object with no or/and/not keys, reached at the end of _validate_structure) is not a dict or is an empty dict. Leaves must be non-empty JSON objects whose keys are field names; everything else is a structural error.

Source

Thrown at apps/api/plane/utils/filters/filter_backend.py:414

            if op == "not":
                if not isinstance(value, dict):
                    raise DRFValidationError(
                        {
                            "message": "'not' must be a single JSON object",
                            "code": "invalid_not_child",
                        }
                    )
                self._validate_structure(value, max_depth=max_depth, current_depth=current_depth + 1)
                return

        # Leaf node: validate fields and values
        self._validate_leaf(node)

    def _validate_leaf(self, leaf):
        """Validate a leaf dict containing field lookups and values."""
        if not isinstance(leaf, dict) or not leaf:
            raise DRFValidationError(
                {
                    "message": "Leaf filter must be a non-empty JSON object",
                    "code": "invalid_leaf",
                }
            )

        for key, value in leaf.items():
            if isinstance(key, str) and key.lower() in ("or", "and", "not"):
                raise DRFValidationError(
                    {
                        "message": "Logical operators cannot appear in a leaf filter object",
                        "code": "operator_in_leaf",
                    }
                )

            # Lists/Tuples must contain only scalar values
            if isinstance(value, (list, tuple)):
                if len(value) == 0:

View on GitHub (pinned to 1c8a60f858)

Solutions

  1. Route all nodes through _validate_structure rather than calling _validate_leaf directly.
  2. If you must call _validate_leaf, ensure the argument is a non-empty dict first.
  3. Keep the structural pre-checks intact when subclassing.

Example fix

# before
self._validate_leaf(None)

# after
if isinstance(node, dict) and node and not _is_operator_node(node):
    self._validate_leaf(node)
Defensive patterns

Strategy: validation

Validate before calling

def assert_leaf_ok(leaf):
    if not isinstance(leaf, dict) or not leaf:
        raise ValueError('leaf filter must be a non-empty object')

Type guard

function isNonEmptyObject(v) {
  return typeof v === 'object' && v !== null && !Array.isArray(v) && Object.keys(v).length > 0;
}

Prevention

When it happens

Trigger: Structurally unreachable in the default pipeline because _validate_structure already rejects non-dict nodes (invalid_filter_node) and empty nodes (empty_filter_object) before dispatching to _validate_leaf. Can fire only in a subclass that calls _validate_leaf directly with bad input, or if _validate_structure is overridden to skip those checks.

Common situations: Subclassing ComplexFilterBackend and invoking _validate_leaf from custom code; unit tests that exercise _validate_leaf in isolation without preparing a proper dict.

Related errors


AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12). Data as JSON: /api/errors/5bc3f8218ae09893. Report an issue: GitHub.