# gen-lsp-types

<!-- vim: set spell: -->

Programmatically generated Rust type definitions for the
[Language Server Protocol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/).

## Features

### 100% Accurate to the protocol*

**All** types are generated by the LSP Metamodel, and will be up-to-date on the
latest LSP changes.

\* If you notice that this is not the case, please
[file an issue](https://github.com/ribru17/gen-lsp-types/issues/new)!

### Comprehensive `derive` usage

Every type implements `serde`'s `Serialize` and `Deserialize` traits adhering to
the protocol, as well as `Debug`, `Clone`, and `PartialEq`. Additionally, the
generator will derive all of the following traits on every `enum`/`struct` that
supports them:

- `Copy`
- `Default`
- `Eq`
- `Hash`

Special types `Position` and `Range` also derive `Ord` and `PartialOrd`, and
string enumerations derive `From<String>` and `Display`.

All structures generate `::new()` constructors for convenience. For the same
reason, string enumerations supporting custom values will also provide `::new()`
constructors, taking a `&'static str`.

All "or" types in the spec (e.g. `bar: string | integer | null`) are represented
as untagged `enum`s, with intuitive naming based on the context that the "or"
type is defined in. Every "or" type `enum` implements `From` so it can be
seamlessly converted from its member types. E.g., for the above example, you can
do:

```rust
// This...
let bar: Bar = 123.into();

// ...or this...
let bar: Bar = String::from("baz").into();

// ...or this.
let bar: Bar = ().into();
```

### Distinguishes between `null` and "not present" properties

Unlike the original [`lsp-types`](https://github.com/gluon-lang/lsp-types)
crate, this one is able to encode properties as `null` **or** `undefined` (not
present). This is important because the spec **does differentiate the two from
each other**.

For example, for the following property which can be `null` **or** `undefined`:

```ts
interface InitializeParams extends WorkDoneProgressParams {
  // ...

  /**
   * The workspace folders configured in the client when the server starts.
   * This property is only available if the client supports workspace folders.
   * It can be `null` if the client supports workspace folders but none are
   * configured.
   *
   * @since 3.6.0
   */
  workspaceFolders?: WorkspaceFolder[] | null;
}
```

The generated Rust struct will be:

```rust
pub struct InitializeParams {
    // ...

    /// The workspace folders configured in the client when the server starts.
    ///
    /// This property is only available if the client supports workspace folders.
    /// It can be `null` if the client supports workspace folders but none are
    /// configured.
    ///
    /// @since 3.6.0
    #[serde(default, deserialize_with = "deserialize_some")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub workspace_folders: Option<WorkspaceFolders>,
}
```

...where `WorkspaceFolders` is an `enum`:

```rust
#[serde(untagged)]
pub enum WorkspaceFolders {
    WorkspaceFolderList(Vec<WorkspaceFolder>),
    Null,
}
```

So `undefined` is represented as `workspace_folders = None`, and `null` is
represented as `workspace_folders = Some(WorkspaceFolders::Null)`.

> [!NOTE]
> If a property can only be one of `undefined` or `null`, not both, `None` will
> be used in both cases to denote those values, for convenience.

### Support for string literal properties

Structures with string literal properties will have those properties omitted
from their generated Rust types. E.g., for the following `DeleteFile`
definition:

```ts
/**
 * Delete file operation
 */
export interface DeleteFile {
  /**
   * This is a delete operation.
   */
  kind: 'delete';

  /**
   * The file to delete.
   */
  uri: DocumentUri;

  /**
   * Delete options.
   */
  options?: DeleteFileOptions;

  /**
   * An optional annotation identifier describing the operation.
   *
   * @since 3.16.0
   */
  annotationId?: ChangeAnnotationIdentifier;
}
```

The generated Rust struct looks like the following:

```rust
/// Delete file operation
pub struct DeleteFile {
    /// The file to delete.
    pub uri: Uri,
    /// Delete options.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub options: Option<DeleteFileOptions>,
    /// An optional annotation identifier describing the operation.
    ///
    /// @since 3.16.0
    #[serde(skip_serializing_if = "Option::is_none")]
    pub annotation_id: Option<ChangeAnnotationIdentifier>,
}
```

Note the `kind` property is not present. However, when serializing, it will
always be present with a value of `"delete"`. Deserializing only succeeds when
the field is present and has a `"delete"` value.

### Stronger typing

This library's `Request` and `Notification` traits define not only the
associated `Params` and `Result` objects, but also their message directions
(i.e. `ClientToServer`, `ServerToClient`, `Both`). It also defines the method
string as an `enum`, rather than a `&'static str`.

Additionally, the library defines a `RequestWithPartialResults` trait for
requests which support streaming partial results. The trait exposes a
`PartialResult` type for this use case.

## Notable changes from `lsp-types`

[`lsp-types` controversially
switched to `fluent-uri`](https://github.com/gluon-lang/lsp-types/issues/284)
for better spec correctness, at the cost of decreased interoperability and
poorer DX. Previously, the `url` crate was used, which is significantly more
battle-tested, but isn't spec-compliant in all cases.

This library deliberately puts the URI encoding decision in the hands of the
downstream consumer. Developers migrating from the original `lsp-types` crate
will notice that the URI type is just a newtype wrapper over a `String`,
allowing them to internally represent URIs however they wish.

This crate also uses feature flags to allow downstream consumers to tell it to
prefer a specific URI representation. The `url` feature instructs the library to
deserialize URIs as `url::Url` (matching `lsp-types <=0.95.0`), and the
`fluent-uri` feature instructs it to deserialize them as `fluent_uri::Uri`. Note
that the two features are mutually exclusive, and by default just the `String`
wrapper will be used.

## Stability

Changes that are backwards compatible from a protocol perspective (e.g.
`foo: integer | string` -> `foo: integer | string | boolean`) are still breaking
from the point of view of this library. In the above example, an extra `enum`
variant would be added to the `Foo` type, which is a breaking change. Thus the
library will be perpetually kept at [zerover](https://0ver.org/) versioning,
like the original `lsp-types` crate.
