collet 0.1.1

Relentless agentic coding orchestrator with zero-drop agent loops
Documentation
use serde::{Deserialize, Serialize};
use serde_json::Value;

// ---------------------------------------------------------------------------
// JSON-RPC base types
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LspRequest {
    pub jsonrpc: String,
    pub id: u64,
    pub method: String,
    pub params: Value,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LspResponse {
    pub jsonrpc: String,
    pub id: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub result: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<LspError>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LspNotification {
    pub jsonrpc: String,
    pub method: String,
    pub params: Value,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LspError {
    pub code: i32,
    pub message: String,
}

// ---------------------------------------------------------------------------
// LSP-specific parameter types
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InitializeParams {
    pub process_id: u32,
    pub root_uri: String,
    pub capabilities: ClientCapabilities,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientCapabilities {}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DocumentSymbolParams {
    pub text_document: TextDocumentIdentifier,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TextDocumentIdentifier {
    pub uri: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TextDocumentItem {
    pub uri: String,
    pub language_id: String,
    pub version: i32,
    pub text: String,
}

// ---------------------------------------------------------------------------
// Symbol types
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DocumentSymbol {
    pub name: String,
    pub kind: SymbolKind,
    pub range: LspRange,
    pub selection_range: LspRange,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub children: Option<Vec<DocumentSymbol>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub detail: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LspRange {
    pub start: LspPosition,
    pub end: LspPosition,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LspPosition {
    pub line: u32,
    pub character: u32,
}

/// LSP symbol kind — serialises as its numeric value (u8).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum SymbolKind {
    File = 1,
    Module = 2,
    Namespace = 3,
    Package = 4,
    Class = 5,
    Method = 6,
    Property = 7,
    Field = 8,
    Constructor = 9,
    Enum = 10,
    Interface = 11,
    Function = 12,
    Variable = 13,
    Constant = 14,
    String = 15,
    Number = 16,
    Boolean = 17,
    Array = 18,
    Object = 19,
    Key = 20,
    Null = 21,
    EnumMember = 22,
    Struct = 23,
    Event = 24,
    Operator = 25,
    TypeParameter = 26,
}

impl Serialize for SymbolKind {
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_u8(*self as u8)
    }
}

impl<'de> Deserialize<'de> for SymbolKind {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let value = u8::deserialize(deserializer)?;
        match value {
            1 => Ok(Self::File),
            2 => Ok(Self::Module),
            3 => Ok(Self::Namespace),
            4 => Ok(Self::Package),
            5 => Ok(Self::Class),
            6 => Ok(Self::Method),
            7 => Ok(Self::Property),
            8 => Ok(Self::Field),
            9 => Ok(Self::Constructor),
            10 => Ok(Self::Enum),
            11 => Ok(Self::Interface),
            12 => Ok(Self::Function),
            13 => Ok(Self::Variable),
            14 => Ok(Self::Constant),
            15 => Ok(Self::String),
            16 => Ok(Self::Number),
            17 => Ok(Self::Boolean),
            18 => Ok(Self::Array),
            19 => Ok(Self::Object),
            20 => Ok(Self::Key),
            21 => Ok(Self::Null),
            22 => Ok(Self::EnumMember),
            23 => Ok(Self::Struct),
            24 => Ok(Self::Event),
            25 => Ok(Self::Operator),
            26 => Ok(Self::TypeParameter),
            other => Err(serde::de::Error::custom(format!(
                "unknown SymbolKind value: {other}"
            ))),
        }
    }
}

// ---------------------------------------------------------------------------
// Diagnostic types
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Diagnostic {
    pub range: LspRange,
    pub severity: Option<DiagnosticSeverity>,
    pub code: Option<Value>,
    pub source: Option<String>,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub related_information: Option<Vec<DiagnosticRelatedInformation>>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[repr(u8)]
pub enum DiagnosticSeverity {
    Error = 1,
    Warning = 2,
    Information = 3,
    Hint = 4,
}

impl<'de> serde::Deserialize<'de> for DiagnosticSeverity {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let n = u8::deserialize(deserializer)?;
        match n {
            1 => Ok(Self::Error),
            2 => Ok(Self::Warning),
            3 => Ok(Self::Information),
            4 => Ok(Self::Hint),
            other => Err(serde::de::Error::custom(format!(
                "unknown DiagnosticSeverity value: {other}"
            ))),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiagnosticRelatedInformation {
    pub location: Location,
    pub message: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Location {
    pub uri: String,
    pub range: LspRange,
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn test_serialize_initialize_request() {
        let req = LspRequest {
            jsonrpc: "2.0".into(),
            id: 1,
            method: "initialize".into(),
            params: serde_json::to_value(InitializeParams {
                process_id: 1234,
                root_uri: "file:///tmp/project".into(),
                capabilities: ClientCapabilities {},
            })
            .unwrap(),
        };

        let json = serde_json::to_value(&req).unwrap();
        assert_eq!(json["jsonrpc"], "2.0");
        assert_eq!(json["id"], 1);
        assert_eq!(json["method"], "initialize");
        assert_eq!(json["params"]["processId"], 1234);
        assert_eq!(json["params"]["rootUri"], "file:///tmp/project");
    }

    #[test]
    fn test_deserialize_document_symbol() {
        let json = json!({
            "name": "main",
            "kind": 12,
            "range": {
                "start": { "line": 0, "character": 0 },
                "end": { "line": 10, "character": 1 }
            },
            "selectionRange": {
                "start": { "line": 0, "character": 3 },
                "end": { "line": 0, "character": 7 }
            },
            "children": [
                {
                    "name": "inner",
                    "kind": 12,
                    "range": {
                        "start": { "line": 2, "character": 4 },
                        "end": { "line": 5, "character": 5 }
                    },
                    "selectionRange": {
                        "start": { "line": 2, "character": 7 },
                        "end": { "line": 2, "character": 12 }
                    }
                }
            ],
            "detail": "fn main()"
        });

        let sym: DocumentSymbol = serde_json::from_value(json).unwrap();
        assert_eq!(sym.name, "main");
        assert_eq!(sym.kind, SymbolKind::Function);
        assert_eq!(sym.range.start.line, 0);
        assert_eq!(sym.range.end.line, 10);
        assert_eq!(sym.detail.as_deref(), Some("fn main()"));

        let children = sym.children.unwrap();
        assert_eq!(children.len(), 1);
        assert_eq!(children[0].name, "inner");
    }

    #[test]
    fn test_symbol_kind_values() {
        // Verify numeric serialization round-trips correctly.
        let kinds = [
            (SymbolKind::File, 1u8),
            (SymbolKind::Module, 2),
            (SymbolKind::Class, 5),
            (SymbolKind::Method, 6),
            (SymbolKind::Function, 12),
            (SymbolKind::Variable, 13),
            (SymbolKind::Struct, 23),
            (SymbolKind::TypeParameter, 26),
        ];

        for (kind, expected_num) in kinds {
            let serialized = serde_json::to_value(kind).unwrap();
            assert_eq!(serialized, json!(expected_num), "SymbolKind::{kind:?}");

            let deserialized: SymbolKind = serde_json::from_value(json!(expected_num)).unwrap();
            assert_eq!(deserialized, kind);
        }
    }

    #[test]
    fn test_document_symbol_params_round_trip() {
        // Verifies that DocumentSymbolParams and TextDocumentIdentifier
        // can be constructed and serialized (both are used by LspClient::document_symbols).
        let params = DocumentSymbolParams {
            text_document: TextDocumentIdentifier {
                uri: "file:///src/main.rs".into(),
            },
        };
        let serialized = serde_json::to_value(&params).unwrap();
        assert_eq!(serialized["textDocument"]["uri"], "file:///src/main.rs");

        let rt: DocumentSymbolParams = serde_json::from_value(serialized).unwrap();
        assert_eq!(rt.text_document.uri, "file:///src/main.rs");
    }

    #[test]
    fn test_lsp_error_deserialize() {
        let json = json!({
            "code": -32601,
            "message": "Method not found"
        });

        let err: LspError = serde_json::from_value(json).unwrap();
        assert_eq!(err.code, -32601);
        assert_eq!(err.message, "Method not found");

        // Round-trip
        let serialized = serde_json::to_value(&err).unwrap();
        assert_eq!(serialized["code"], -32601);
        assert_eq!(serialized["message"], "Method not found");
    }
}