Skip to content

ribru17/gen-lsp-types

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gen-lsp-types

Programmatically generated Rust type definitions for the Language Server Protocol.

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!

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 enums, 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:

// 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 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:

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:

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:

pub enum WorkspaceFolders {
    WorkspaceFolderList(Vec<WorkspaceFolder>),
    #[serde(rename = "null")]
    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:

/**
 * 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:

/// 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.

Notable changes from lsp-types

lsp-types controversially switched to fluent-uri 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 versioning, like the original lsp-types crate.

About

Library of Rust LSP types generated from the official metamodel

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages