{"record":{"id":"b0cd54977d1bac42","repo":"usestrix/strix","slug":"p-does-not-contain-a-mapping-at-the-top-level","errorCode":null,"errorMessage":"{p} does not contain a mapping at the top level","messagePattern":"(.+?) does not contain a mapping at the top level","errorType":"exception","errorClass":"SpecParseError","httpStatus":null,"severity":"error","filePath":"strix/utils/api_spec.py","lineNumber":61,"sourceCode":"    Raises :class:`SpecParseError` if the file cannot be read or is not a\n    JSON/YAML mapping.\n    \"\"\"\n    p = Path(path)\n    try:\n        text = p.read_text(encoding=\"utf-8\")\n    except OSError as exc:\n        raise SpecParseError(f\"Cannot read spec {p}: {exc}\") from exc\n    # JSON is a subset of YAML, so safe_load parses both; try JSON first for a\n    # clearer error and to keep the fast path fast.\n    try:\n        data: Any = json.loads(text)\n    except json.JSONDecodeError:\n        try:\n            data = yaml.safe_load(text)\n        except yaml.YAMLError as exc:\n            raise SpecParseError(f\"{p} is not valid JSON or YAML: {exc}\") from exc\n    if not isinstance(data, dict):\n        raise SpecParseError(f\"{p} does not contain a mapping at the top level\")\n    return data\n\n\ndef classify_spec(raw: dict[str, Any]) -> str | None:\n    \"\"\"Return ``openapi`` / ``swagger`` / ``postman``, or ``None`` if unrecognized.\"\"\"\n    if isinstance(raw.get(\"openapi\"), str):\n        return \"openapi\"\n    if str(raw.get(\"swagger\", \"\")).startswith(\"2\"):\n        return \"swagger\"\n    info = raw.get(\"info\")\n    if isinstance(info, dict) and (\"_postman_id\" in info or \"item\" in raw):\n        return \"postman\"\n    return None\n\n\ndef detect_spec_format(path: Path) -> str | None:\n    \"\"\"Return the spec format of *path*, or ``None`` if it is not a spec.\n","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/utils/api_spec.py#L43-L79","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Wrap the content in an object: the root must be a mapping such as {'openapi': '3.0.0', ...}","If you extracted a fragment, re-export the full spec instead","For an empty file, regenerate the spec from the source (Postman export, swagger gen)"],"exampleFix":"# before\n[{\"path\": \"/users\", \"method\": \"get\"}]\n\n# after\n{\"openapi\": \"3.0.0\", \"paths\": {\"/users\": {\"get\": {\"responses\": {\"200\": {\"description\": \"ok\"}}}}}}","handlingStrategy":"type-guard","validationCode":"data = yaml.safe_load(Path(spec).read_text())\nif not isinstance(data, dict):\n    raise ValueError(f\"spec root is {type(data).__name__}, expected mapping\")","typeGuard":"def is_spec_mapping(data) -> bool:\n    return isinstance(data, dict) and len(data) > 0","tryCatchPattern":"try:\n    spec = load_spec(path)\nexcept SpecParseError as e:\n    if \"does not contain a mapping\" in str(e):\n        stop and fix the source file — retrying with the same content cannot succeed","preventionTips":["When extracting spec fragments, keep the full document instead","Assert the first non-space character of the file is '{' before parsing"],"tags":["parsing","api-spec","schema-shape"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}