Source code for oas2mcp.utils.refs
"""Reference and JSON pointer helpers.
Purpose:
Resolve JSON pointers and schema references against normalized OpenAPI raw
spec data.
Design:
- Support local ``#/...`` references first.
- Keep helpers read-only and deterministic.
- Return ``None`` instead of raising for missing refs in normal lookup
flows.
- Provide separate request and response schema ref collection so viewers
and agent-context builders can distinguish input from output structure.
Examples:
.. code-block:: python
schema = dereference_schema_ref(catalog.raw_spec, "#/components/schemas/Pet")
"""
from __future__ import annotations
from typing import Any
from oas2mcp.models.normalized import ApiOperation
[docs]
def resolve_json_pointer(document: dict[str, Any], pointer: str) -> Any | None:
"""Resolve a local JSON pointer within a document.
Args:
document: The target document.
pointer: The JSON pointer, such as ``#/components/schemas/Pet``.
Returns:
The resolved value when found, otherwise ``None``.
Raises:
None.
Examples:
.. code-block:: python
value = resolve_json_pointer(
{"components": {"schemas": {"Pet": {"type": "object"}}}},
"#/components/schemas/Pet",
)
"""
if not pointer.startswith("#/"):
return None
current: Any = document
parts = pointer[2:].split("/")
for raw_part in parts:
part = raw_part.replace("~1", "/").replace("~0", "~")
if isinstance(current, dict) and part in current:
current = current[part]
continue
if isinstance(current, list):
try:
index = int(part)
except ValueError:
return None
if index < 0 or index >= len(current):
return None
current = current[index]
continue
return None
return current
[docs]
def dereference_schema_ref(
document: dict[str, Any],
schema_ref: str,
) -> dict[str, Any] | None:
"""Dereference a local schema reference.
Args:
document: The raw OpenAPI specification dictionary.
schema_ref: A local schema reference.
Returns:
The resolved schema mapping when found, otherwise ``None``.
Raises:
None.
Examples:
.. code-block:: python
schema = dereference_schema_ref(
{"components": {"schemas": {"Pet": {"type": "object"}}}},
"#/components/schemas/Pet",
)
"""
value = resolve_json_pointer(document, schema_ref)
if isinstance(value, dict):
return value
return None
[docs]
def collect_request_schema_refs(operation: ApiOperation) -> list[str]:
"""Collect request-body schema refs mentioned by an operation.
Args:
operation: The normalized API operation.
Returns:
A de-duplicated list of request schema refs.
Raises:
None.
Examples:
.. code-block:: python
refs = collect_request_schema_refs(operation)
"""
collected: list[str] = []
if operation.request_body is not None:
for media_type in operation.request_body.media_types:
if media_type.schema_ref:
collected.append(media_type.schema_ref)
return _deduplicate_refs(collected)
[docs]
def collect_response_schema_refs(operation: ApiOperation) -> list[str]:
"""Collect response schema refs mentioned by an operation.
Args:
operation: The normalized API operation.
Returns:
A de-duplicated list of response schema refs.
Raises:
None.
Examples:
.. code-block:: python
refs = collect_response_schema_refs(operation)
"""
collected: list[str] = []
for response in operation.responses:
for media_type in response.media_types:
if media_type.schema_ref:
collected.append(media_type.schema_ref)
return _deduplicate_refs(collected)
[docs]
def collect_operation_schema_refs(operation: ApiOperation) -> list[str]:
"""Collect all schema refs mentioned by an operation.
Args:
operation: The normalized API operation.
Returns:
A de-duplicated list of request and response schema refs.
Raises:
None.
Examples:
.. code-block:: python
refs = collect_operation_schema_refs(operation)
"""
return _deduplicate_refs(
[
*collect_request_schema_refs(operation),
*collect_response_schema_refs(operation),
]
)
def _deduplicate_refs(schema_refs: list[str]) -> list[str]:
"""Deduplicate schema refs while preserving order.
Args:
schema_refs: The candidate schema refs.
Returns:
A de-duplicated list of refs.
Raises:
None.
Examples:
.. code-block:: python
refs = _deduplicate_refs(["#/A", "#/A", "#/B"])
assert refs == ["#/A", "#/B"]
"""
seen: set[str] = set()
ordered: list[str] = []
for item in schema_refs:
if item in seen:
continue
seen.add(item)
ordered.append(item)
return ordered