usestrix/strix · error · SpecParseError

{p} does not contain a mapping at the top level

Error message

{p} does not contain a mapping at the top level

What it means

SpecParseError raised by load_spec when the file parses successfully but the top-level node is not a mapping — e.g. a YAML list, a bare scalar, or a JSON array. API specs (OpenAPI, Swagger, Postman) must be objects at the root.

Source

Thrown at strix/utils/api_spec.py:61

    Raises :class:`SpecParseError` if the file cannot be read or is not a
    JSON/YAML mapping.
    """
    p = Path(path)
    try:
        text = p.read_text(encoding="utf-8")
    except OSError as exc:
        raise SpecParseError(f"Cannot read spec {p}: {exc}") from exc
    # JSON is a subset of YAML, so safe_load parses both; try JSON first for a
    # clearer error and to keep the fast path fast.
    try:
        data: Any = json.loads(text)
    except json.JSONDecodeError:
        try:
            data = yaml.safe_load(text)
        except yaml.YAMLError as exc:
            raise SpecParseError(f"{p} is not valid JSON or YAML: {exc}") from exc
    if not isinstance(data, dict):
        raise SpecParseError(f"{p} does not contain a mapping at the top level")
    return data


def classify_spec(raw: dict[str, Any]) -> str | None:
    """Return ``openapi`` / ``swagger`` / ``postman``, or ``None`` if unrecognized."""
    if isinstance(raw.get("openapi"), str):
        return "openapi"
    if str(raw.get("swagger", "")).startswith("2"):
        return "swagger"
    info = raw.get("info")
    if isinstance(info, dict) and ("_postman_id" in info or "item" in raw):
        return "postman"
    return None


def detect_spec_format(path: Path) -> str | None:
    """Return the spec format of *path*, or ``None`` if it is not a spec.

View on GitHub (pinned to 8551339130)

Solutions

  1. Wrap the content in an object: the root must be a mapping such as {'openapi': '3.0.0', ...}
  2. If you extracted a fragment, re-export the full spec instead
  3. For an empty file, regenerate the spec from the source (Postman export, swagger gen)

Example fix

# before
[{"path": "/users", "method": "get"}]

# after
{"openapi": "3.0.0", "paths": {"/users": {"get": {"responses": {"200": {"description": "ok"}}}}}}
Defensive patterns

Strategy: type-guard

Validate before calling

data = yaml.safe_load(Path(spec).read_text())
if not isinstance(data, dict):
    raise ValueError(f"spec root is {type(data).__name__}, expected mapping")

Type guard

def is_spec_mapping(data) -> bool:
    return isinstance(data, dict) and len(data) > 0

Try / catch

try:
    spec = load_spec(path)
except SpecParseError as e:
    if "does not contain a mapping" in str(e):
        stop and fix the source file — retrying with the same content cannot succeed

Prevention

When it happens

Trigger: A YAML file that is just '- item\n- item'; a JSON file containing a top-level array like '[{"path": "/users"}]'; a YAML file of just a string or number; an effectively empty file that safe_load parses as None.

Common situations: Extracting only the 'paths' array from a spec and saving that; hand-authored spec starting with a list; empty file left by a failed pipeline step.

Related errors


AI-assisted analysis of usestrix/strix@8551339130 (2026-08-15). Data as JSON: /api/errors/b0cd54977d1bac42. Report an issue: GitHub.