From c295be211c3131e1d035ae5720fc481ac151cb3f Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Thu, 21 Dec 2023 15:27:42 +0000 Subject: [PATCH] docs: add guide on handling invalid data --- docs/source/howto/handle-invalid-data.rst | 155 ++++++++++++++++++++++ docs/source/index.rst | 1 + 2 files changed, 156 insertions(+) create mode 100644 docs/source/howto/handle-invalid-data.rst diff --git a/docs/source/howto/handle-invalid-data.rst b/docs/source/howto/handle-invalid-data.rst new file mode 100644 index 00000000..a1d89a50 --- /dev/null +++ b/docs/source/howto/handle-invalid-data.rst @@ -0,0 +1,155 @@ +How To Handle Invalid Data +========================== + +.. highlight:: python + +By default, servers written with *pygls* are quite pedantic and will complain loudly when given data they consider to be invalid + +.. dropdown:: Example Data + :open: + + .. code-block:: json + + { + "jsonrpc": "2.0", + "id": 10, + "method": "textDocument/codeAction", + "params": { + "textDocument": { + "uri": "file:///path/to/file.txt" + }, + "range": { + "start": { "line": 7, "character": 14 }, + "end": { "line": 7, "character": 14 } + }, + "context": { + "diagnostics": [ + { + "range": { + "start": { + "line": null, // Invalid! + "character": 0 + }, + "end":{ + "line": 1, + "character": 65535 + } + }, + "message": "an example message", + "severity": 2, + "source": "example" + } + ] + } + } + } + +.. dropdown:: Example Error + + :: + + ERROR:pygls.protocol:Error receiving data + + Exception Group Traceback (most recent call last): + | ... + | cattrs.errors.ClassValidationError: While structuring TextDocumentCodeActionRequest (1 sub-exception) + +-+---------------- 1 ---------------- + | Exception Group Traceback (most recent call last): + | ... + | cattrs.errors.ClassValidationError: While structuring CodeActionParams (1 sub-exception) + | Structuring class TextDocumentCodeActionRequest @ attribute params + +-+---------------- 1 ---------------- + | Exception Group Traceback (most recent call last): + | ... + | cattrs.errors.ClassValidationError: While structuring CodeActionContext (1 sub-exception) + | Structuring class CodeActionParams @ attribute context + +-+---------------- 1 ---------------- + | Exception Group Traceback (most recent call last): + | ... + | cattrs.errors.IterableValidationError: While structuring typing.List[lsprotocol.types.Diagnostic] (2 sub-exceptions) + | Structuring class CodeActionContext @ attribute diagnostics + +-+---------------- 1 ---------------- + | Exception Group Traceback (most recent call last): + | ... + | cattrs.errors.ClassValidationError: While structuring Diagnostic (1 sub-exception) + | Structuring typing.List[lsprotocol.types.Diagnostic] @ index 0 + +-+---------------- 1 ---------------- + | Exception Group Traceback (most recent call last): + | ... + | cattrs.errors.ClassValidationError: While structuring Range (2 sub-exceptions) + | Structuring class Diagnostic @ attribute range + +-+---------------- 1 ---------------- + | Exception Group Traceback (most recent call last): + | ... + | cattrs.errors.ClassValidationError: While structuring Position (1 sub-exception) + | Structuring class Range @ attribute start + +-+---------------- 1 ---------------- + | Traceback (most recent call last): + | ... + | TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType' + | Structuring class Position @ attribute line + +------------------------------------ + + The above exception was the direct cause of the following exception: + + Traceback (most recent call last): + ... + pygls.exceptions.JsonRpcInvalidParams: Invalid Params + +This is due to the fact ``pygls`` relies on `lsprotocol `__ for all of its type definitions. +``lsprotocol`` in turn builds on `attrs `__ and `cattrs `__ to provide the serialisation and deserialisation of types to/from JSON. + +This is done through a ``converter`` object:: + + >>> from lsprotocol.types import Position + >>> from pygls.protocol import default_converter + + >>> converter = default_converter() + >>> p = converter.structure({"line": 1, "character": 2}, Position) + >>> p.line + 1 + >>> p.character + 2 + +Each language server receives a :func:`~pygls.protocol.default_converter` which is derived from the converter provided by ``lsprotocol``. +This means it will follow the Language Server Protocol exactly. + +Structure Hooks +--------------- + +By registering your own `structure hooks `__ you can take control over how malformed types should be handled. + +Using the example data above, let's define a custom converter which includes a hook to silently ignore any diagnostics that are rejected when parsing the ``context`` field of a :lsp:`textDocument/codeAction` request. + +.. code-block:: python + + from lsprotocol import types + from pygls.protocol import default_converter + + def my_converter_factory(): + converter = default_converter() + + def code_action_context_hook(obj, type_): + diagnostics = [] + raw_diagnostics = obj.get("diagnostics", []) or [] + + for d in raw_diagnostics: + try: + diagnostics.append(converter.structure(d, Diagnostic)) + except Exception: + pass + + return CodeActionContext(diagnostics=diagnostics) + + converter.register_structure_hook(CodeActionContext, code_action_context_hook) + + return converter + +To use this custom converter with a language server set ``my_converter_factory`` as the server's ``converter_factory``. + +.. code-block:: python + + server = LanguageServer( + name="my-language-server", + version="v1.0", + converter_factory=my_converter_factory, + ) diff --git a/docs/source/index.rst b/docs/source/index.rst index ac862d11..66260e78 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -48,6 +48,7 @@ User Guide :hidden: :caption: How To + Handle Invalid Data Migrate to v1 .. _Language Server Protocol: https://microsoft.github.io/language-server-protocol/specification