Graceful Handling of Unimplemented Types

Overview

The Atlassian Document Format (ADF) is a rich specification with many node and mark types. As a library that evolves incrementally, atlas_doc_parser may not implement all ADF types at any given time. This document explains how the library gracefully handles unimplemented types during parsing.

Design Philosophy

The library follows a progressive implementation approach:

  1. Parse what we can: When encountering an ADF document, the parser deserializes all implemented node and mark types into Python objects.

  2. Skip what we can’t: Unimplemented types are silently skipped rather than causing the entire parsing operation to fail.

  3. Inform the user: When an unimplemented type is encountered, a warning is logged to inform users which types are missing. This helps users identify gaps and submit feature requests.

  4. Fail on real errors: Actual parsing errors (malformed data, type mismatches, etc.) are still raised as exceptions. Only missing type registrations are skipped.

This design ensures that:

  • Users can work with partial ADF documents even if the library doesn’t support all types yet

  • The library can evolve incrementally without breaking existing functionality

  • Users are informed about which types need implementation

Error Handling Behavior

When to Skip Errors

Errors are skipped only when:

  • A node type is not registered in NODE_TYPE_TO_CLASS_MAPPING

  • A mark type is not registered in MARK_TYPE_TO_CLASS_MAPPING

In these cases, the unimplemented element is simply omitted from the parsed result.

When to Raise Errors

Errors are raised normally when:

  • The from_dict() method of a dataclass fails (malformed attributes, type errors, etc.)

  • Any other exception occurs during parsing (network errors, JSON decode errors, etc.)

  • The ADF structure is invalid (missing required fields, wrong types, etc.)

This distinction ensures that genuine bugs and data issues are surfaced while allowing the library to handle incomplete implementations gracefully.

Implementation Details

Key Components

The graceful handling mechanism involves several components:

  1. Exception Class: atlas_doc_parser.exc.UnimplementedTypeError

    A custom exception raised when a type is not found in the registry. It carries the type name and category (node or mark) for informative error messages.

  2. Parse Functions: parse_node() and parse_mark()

    Located in atlas_doc_parser.nodes.parse_node and atlas_doc_parser.marks.parse_mark respectively. These functions look up the type in their respective mapping dictionaries. If the type is not found, they raise UnimplementedTypeError instead of letting the KeyError propagate.

  3. Base Class Handler: BaseNode.from_dict()

    Located in atlas_doc_parser.mark_or_node. This method catches UnimplementedTypeError specifically when parsing content (child nodes) and marks. When caught, it skips the unimplemented element and continues parsing the remaining elements.

  4. Warning Control: settings.WARN_UNIMPLEMENTED_TYPE

    A module-level boolean flag in atlas_doc_parser.settings that controls whether warnings are logged for unimplemented types. Default is True.

Code Flow

When parsing a document:

NodeDoc.from_dict(data)
    |
    +-- For each item in content:
    |       |
    |       +-- parse_node(item_data)
    |               |
    |               +-- Type not in NODE_TYPE_TO_CLASS_MAPPING?
    |               |       |
    |               |       +-- Raise UnimplementedTypeError
    |               |
    |               +-- Type found?
    |                       |
    |                       +-- Call NodeClass.from_dict(item_data)
    |
    +-- Catch UnimplementedTypeError?
    |       |
    |       +-- Log warning (if WARN_UNIMPLEMENTED_TYPE is True)
    |       +-- Skip this item, continue with next
    |
    +-- Catch other exceptions?
            |
            +-- Re-raise (fail normally)

Usage Examples

Default Behavior (Warnings Enabled)

from atlas_doc_parser.nodes.node_doc import NodeDoc

# Parse a document that contains an unimplemented node type
data = {
    "version": 1,
    "type": "doc",
    "content": [
        {"type": "bodiedExtension", "attrs": {...}},  # Not implemented
        {"type": "paragraph", "content": [{"type": "text", "text": "Hello"}]}
    ]
}

doc = NodeDoc.from_dict(data)
# WARNING logged: Node type 'bodiedExtension' is not yet implemented...
# doc.content contains only the paragraph (bodiedExtension was skipped)

Disabling Warnings

import atlas_doc_parser.settings as settings

# Disable warnings for unimplemented types
settings.WARN_UNIMPLEMENTED_TYPE = False

# Now parse silently
doc = NodeDoc.from_dict(data)
# No warning logged, unimplemented types still skipped

Testing

The graceful handling mechanism is tested in tests/nodes/test_nodes_node_doc.py with the test_node_doc_with_unimplemented_model test case. This test uses a Confluence page that intentionally contains an unimplemented node type (bodiedExtension) to verify that:

  1. Parsing completes without raising an exception

  2. The unimplemented node is skipped

  3. Other nodes in the document are parsed correctly

Contributing New Types

When a user encounters an unimplemented type, the warning message directs them to submit an issue at: https://github.com/MacHu-GWU/atlas_doc_parser-project/issues

To implement a new type:

  1. Create the dataclass in the appropriate module (nodes/ or marks/)

  2. Register the type in the mapping dictionary (NODE_TYPE_TO_CLASS_MAPPING or MARK_TYPE_TO_CLASS_MAPPING)

  3. Add tests for the new type