/ src / source / remote / protocol.rs
protocol.rs
   1  //! JSON-RPC 2.0 protocol types for external provider communication.
   2  //!
   3  //! This module defines all message types used to communicate with out-of-process
   4  //! provider binaries over stdio. The protocol follows JSON-RPC 2.0 specification
   5  //! with provider-specific methods for authentication, scope navigation, and secret
   6  //! management.
   7  //!
   8  //! ## Protocol Version
   9  //!
  10  //! The protocol version uses `YYYY-MM-DD` format (e.g., "2025-01-01").
  11  //! Providers should support current + previous version for backward compatibility.
  12  //!
  13  //! ## Method Categories
  14  //!
  15  //! - **Lifecycle**: `initialize`, `initialized`, `shutdown`, `exit`, `ping`
  16  //! - **Authentication**: `auth/status`, `auth/fields`, `auth/authenticate`, `auth/revoke`
  17  //! - **Scope**: `scope/levels`, `scope/options`
  18  //! - **Secrets**: `secrets/fetch`, `secrets/get`, `secrets/set`, `secrets/delete`
  19  //!
  20  //! ## Notifications
  21  //!
  22  //! - `secrets/changed`: Provider notifies when secrets change (push-based invalidation)
  23  //! - `log`: Provider sends log messages to LSP for debugging
  24  
  25  use compact_str::CompactString;
  26  use serde::{Deserialize, Serialize};
  27  use std::collections::HashMap;
  28  
  29  /// Current protocol version.
  30  pub const PROTOCOL_VERSION: &str = "2025-01-01";
  31  
  32  // =============================================================================
  33  // JSON-RPC 2.0 Base Types
  34  // =============================================================================
  35  
  36  /// JSON-RPC 2.0 request.
  37  #[derive(Debug, Clone, Serialize, Deserialize)]
  38  pub struct JsonRpcRequest {
  39      pub jsonrpc: String,
  40      pub id: JsonRpcId,
  41      pub method: String,
  42      #[serde(skip_serializing_if = "Option::is_none")]
  43      pub params: Option<serde_json::Value>,
  44  }
  45  
  46  impl JsonRpcRequest {
  47      pub fn new(id: impl Into<JsonRpcId>, method: impl Into<String>) -> Self {
  48          Self {
  49              jsonrpc: "2.0".into(),
  50              id: id.into(),
  51              method: method.into(),
  52              params: None,
  53          }
  54      }
  55  
  56      pub fn with_params<T: Serialize>(mut self, params: T) -> Self {
  57          self.params = Some(serde_json::to_value(params).expect("params must be serializable"));
  58          self
  59      }
  60  }
  61  
  62  /// JSON-RPC 2.0 response.
  63  #[derive(Debug, Clone, Serialize, Deserialize)]
  64  pub struct JsonRpcResponse {
  65      pub jsonrpc: String,
  66      pub id: JsonRpcId,
  67      #[serde(skip_serializing_if = "Option::is_none")]
  68      pub result: Option<serde_json::Value>,
  69      #[serde(skip_serializing_if = "Option::is_none")]
  70      pub error: Option<JsonRpcError>,
  71  }
  72  
  73  impl JsonRpcResponse {
  74      pub fn success(id: JsonRpcId, result: impl Serialize) -> Self {
  75          Self {
  76              jsonrpc: "2.0".into(),
  77              id,
  78              result: Some(serde_json::to_value(result).expect("result must be serializable")),
  79              error: None,
  80          }
  81      }
  82  
  83      pub fn error(id: JsonRpcId, error: JsonRpcError) -> Self {
  84          Self {
  85              jsonrpc: "2.0".into(),
  86              id,
  87              result: None,
  88              error: Some(error),
  89          }
  90      }
  91  
  92      pub fn is_success(&self) -> bool {
  93          self.error.is_none()
  94      }
  95  
  96      /// Extract the result, returning an error if the response is an error.
  97      pub fn into_result<T: for<'de> Deserialize<'de>>(self) -> Result<T, JsonRpcError> {
  98          if let Some(error) = self.error {
  99              return Err(error);
 100          }
 101          let value = self.result.ok_or_else(|| JsonRpcError {
 102              code: ErrorCode::InternalError as i32,
 103              message: "No result in response".into(),
 104              data: None,
 105          })?;
 106          serde_json::from_value(value).map_err(|e| JsonRpcError {
 107              code: ErrorCode::ParseError as i32,
 108              message: format!("Failed to deserialize result: {}", e),
 109              data: None,
 110          })
 111      }
 112  }
 113  
 114  /// JSON-RPC 2.0 notification (no response expected).
 115  #[derive(Debug, Clone, Serialize, Deserialize)]
 116  pub struct JsonRpcNotification {
 117      pub jsonrpc: String,
 118      pub method: String,
 119      #[serde(skip_serializing_if = "Option::is_none")]
 120      pub params: Option<serde_json::Value>,
 121  }
 122  
 123  impl JsonRpcNotification {
 124      pub fn new(method: impl Into<String>) -> Self {
 125          Self {
 126              jsonrpc: "2.0".into(),
 127              method: method.into(),
 128              params: None,
 129          }
 130      }
 131  
 132      pub fn with_params<T: Serialize>(mut self, params: T) -> Self {
 133          self.params = Some(serde_json::to_value(params).expect("params must be serializable"));
 134          self
 135      }
 136  }
 137  
 138  /// JSON-RPC ID (can be number, string, or null).
 139  #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 140  #[serde(untagged)]
 141  pub enum JsonRpcId {
 142      Number(i64),
 143      String(String),
 144      Null,
 145  }
 146  
 147  impl From<i64> for JsonRpcId {
 148      fn from(n: i64) -> Self {
 149          JsonRpcId::Number(n)
 150      }
 151  }
 152  
 153  impl From<String> for JsonRpcId {
 154      fn from(s: String) -> Self {
 155          JsonRpcId::String(s)
 156      }
 157  }
 158  
 159  impl From<&str> for JsonRpcId {
 160      fn from(s: &str) -> Self {
 161          JsonRpcId::String(s.to_string())
 162      }
 163  }
 164  
 165  /// JSON-RPC 2.0 error object.
 166  #[derive(Debug, Clone, Serialize, Deserialize)]
 167  pub struct JsonRpcError {
 168      pub code: i32,
 169      pub message: String,
 170      #[serde(skip_serializing_if = "Option::is_none")]
 171      pub data: Option<serde_json::Value>,
 172  }
 173  
 174  impl std::fmt::Display for JsonRpcError {
 175      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 176          write!(f, "[{}] {}", self.code, self.message)
 177      }
 178  }
 179  
 180  impl std::error::Error for JsonRpcError {}
 181  
 182  /// Standard and provider-specific error codes.
 183  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 184  #[repr(i32)]
 185  pub enum ErrorCode {
 186      // Standard JSON-RPC errors
 187      ParseError = -32700,
 188      InvalidRequest = -32600,
 189      MethodNotFound = -32601,
 190      InvalidParams = -32602,
 191      InternalError = -32603,
 192  
 193      // Provider-specific errors (-32000 to -32099)
 194      NotAuthenticated = -32000,
 195      AuthenticationFailed = -32001,
 196      InvalidScope = -32002,
 197      RateLimited = -32003,
 198      NetworkError = -32004,
 199      PermissionDenied = -32005,
 200      SecretNotFound = -32006,
 201      UnsupportedOperation = -32007,
 202  }
 203  
 204  impl ErrorCode {
 205      pub fn from_i32(code: i32) -> Option<ErrorCode> {
 206          match code {
 207              -32700 => Some(ErrorCode::ParseError),
 208              -32600 => Some(ErrorCode::InvalidRequest),
 209              -32601 => Some(ErrorCode::MethodNotFound),
 210              -32602 => Some(ErrorCode::InvalidParams),
 211              -32603 => Some(ErrorCode::InternalError),
 212              -32000 => Some(ErrorCode::NotAuthenticated),
 213              -32001 => Some(ErrorCode::AuthenticationFailed),
 214              -32002 => Some(ErrorCode::InvalidScope),
 215              -32003 => Some(ErrorCode::RateLimited),
 216              -32004 => Some(ErrorCode::NetworkError),
 217              -32005 => Some(ErrorCode::PermissionDenied),
 218              -32006 => Some(ErrorCode::SecretNotFound),
 219              -32007 => Some(ErrorCode::UnsupportedOperation),
 220              _ => None,
 221          }
 222      }
 223  }
 224  
 225  impl JsonRpcError {
 226      pub fn not_authenticated(message: impl Into<String>) -> Self {
 227          Self {
 228              code: ErrorCode::NotAuthenticated as i32,
 229              message: message.into(),
 230              data: None,
 231          }
 232      }
 233  
 234      pub fn authentication_failed(message: impl Into<String>) -> Self {
 235          Self {
 236              code: ErrorCode::AuthenticationFailed as i32,
 237              message: message.into(),
 238              data: None,
 239          }
 240      }
 241  
 242      pub fn invalid_scope(message: impl Into<String>) -> Self {
 243          Self {
 244              code: ErrorCode::InvalidScope as i32,
 245              message: message.into(),
 246              data: None,
 247          }
 248      }
 249  
 250      pub fn rate_limited(retry_after_secs: u64) -> Self {
 251          Self {
 252              code: ErrorCode::RateLimited as i32,
 253              message: format!("Rate limited. Retry after {} seconds", retry_after_secs),
 254              data: Some(serde_json::json!({ "retry_after_secs": retry_after_secs })),
 255          }
 256      }
 257  
 258      pub fn network_error(message: impl Into<String>) -> Self {
 259          Self {
 260              code: ErrorCode::NetworkError as i32,
 261              message: message.into(),
 262              data: None,
 263          }
 264      }
 265  
 266      pub fn permission_denied(message: impl Into<String>) -> Self {
 267          Self {
 268              code: ErrorCode::PermissionDenied as i32,
 269              message: message.into(),
 270              data: None,
 271          }
 272      }
 273  
 274      pub fn method_not_found(method: &str) -> Self {
 275          Self {
 276              code: ErrorCode::MethodNotFound as i32,
 277              message: format!("Method not found: {}", method),
 278              data: None,
 279          }
 280      }
 281  
 282      pub fn invalid_params(message: impl Into<String>) -> Self {
 283          Self {
 284              code: ErrorCode::InvalidParams as i32,
 285              message: message.into(),
 286              data: None,
 287          }
 288      }
 289  
 290      pub fn internal(message: impl Into<String>) -> Self {
 291          Self {
 292              code: ErrorCode::InternalError as i32,
 293              message: message.into(),
 294              data: None,
 295          }
 296      }
 297  }
 298  
 299  // =============================================================================
 300  // Message Wrapper (for parsing incoming messages)
 301  // =============================================================================
 302  
 303  /// Wrapper for incoming JSON-RPC messages.
 304  #[derive(Debug, Clone, Serialize, Deserialize)]
 305  #[serde(untagged)]
 306  pub enum JsonRpcMessage {
 307      Request(JsonRpcRequest),
 308      Response(JsonRpcResponse),
 309      Notification(JsonRpcNotification),
 310  }
 311  
 312  impl JsonRpcMessage {
 313      /// Try to parse a JSON-RPC message from a string.
 314      pub fn parse(s: &str) -> Result<Self, serde_json::Error> {
 315          serde_json::from_str(s)
 316      }
 317  }
 318  
 319  // =============================================================================
 320  // Lifecycle Methods
 321  // =============================================================================
 322  
 323  /// Client info sent during initialization.
 324  #[derive(Debug, Clone, Serialize, Deserialize)]
 325  #[serde(rename_all = "camelCase")]
 326  pub struct ClientInfo {
 327      pub name: String,
 328      pub version: String,
 329  }
 330  
 331  /// Client capabilities sent during initialization.
 332  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 333  #[serde(rename_all = "camelCase")]
 334  pub struct ClientCapabilities {
 335      /// Whether client supports secrets/changed notifications.
 336      #[serde(default)]
 337      pub notifications: NotificationCapabilities,
 338      /// Whether client can store credentials securely.
 339      #[serde(default)]
 340      pub credential_storage: bool,
 341  }
 342  
 343  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 344  #[serde(rename_all = "camelCase")]
 345  pub struct NotificationCapabilities {
 346      #[serde(default)]
 347      pub secrets_changed: bool,
 348  }
 349  
 350  /// Initialize request parameters.
 351  #[derive(Debug, Clone, Serialize, Deserialize)]
 352  #[serde(rename_all = "camelCase")]
 353  pub struct InitializeParams {
 354      /// Protocol version the client speaks.
 355      pub protocol_version: String,
 356      /// Information about the client (LSP).
 357      pub client_info: ClientInfo,
 358      /// Client capabilities.
 359      #[serde(default)]
 360      pub capabilities: ClientCapabilities,
 361      /// Provider-specific configuration from ecolog.toml.
 362      #[serde(default)]
 363      pub config: HashMap<String, serde_json::Value>,
 364  }
 365  
 366  /// Provider info returned during initialization.
 367  #[derive(Debug, Clone, Serialize, Deserialize)]
 368  #[serde(rename_all = "camelCase")]
 369  pub struct ProviderInfo {
 370      /// Unique provider identifier (e.g., "doppler", "aws").
 371      pub id: String,
 372      /// Human-readable name (e.g., "Doppler", "AWS Secrets Manager").
 373      pub name: String,
 374      /// Provider version.
 375      pub version: String,
 376      /// Short name for UI display (e.g., "DPL", "AWS").
 377      #[serde(skip_serializing_if = "Option::is_none")]
 378      pub short_name: Option<String>,
 379      /// Provider description.
 380      #[serde(skip_serializing_if = "Option::is_none")]
 381      pub description: Option<String>,
 382      /// Documentation URL.
 383      #[serde(skip_serializing_if = "Option::is_none")]
 384      pub docs_url: Option<String>,
 385  }
 386  
 387  /// Provider capabilities returned during initialization.
 388  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 389  #[serde(rename_all = "camelCase")]
 390  pub struct ProviderCapabilities {
 391      /// Secret operation capabilities.
 392      pub secrets: SecretsCapability,
 393      /// Supported authentication methods.
 394      #[serde(default)]
 395      pub authentication: AuthenticationCapability,
 396  }
 397  
 398  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 399  #[serde(rename_all = "camelCase")]
 400  pub struct SecretsCapability {
 401      /// Provider can read secrets.
 402      #[serde(default)]
 403      pub read: bool,
 404      /// Provider can write secrets.
 405      #[serde(default)]
 406      pub write: bool,
 407      /// Provider supports push notifications for changes.
 408      #[serde(default)]
 409      pub watch: bool,
 410  }
 411  
 412  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 413  #[serde(rename_all = "camelCase")]
 414  pub struct AuthenticationCapability {
 415      /// Supported auth methods (e.g., ["token", "oauth"]).
 416      #[serde(default)]
 417      pub methods: Vec<String>,
 418  }
 419  
 420  /// Initialize response result.
 421  #[derive(Debug, Clone, Serialize, Deserialize)]
 422  #[serde(rename_all = "camelCase")]
 423  pub struct InitializeResult {
 424      /// Protocol version the provider will use.
 425      pub protocol_version: String,
 426      /// Provider information.
 427      pub provider_info: ProviderInfo,
 428      /// Provider capabilities.
 429      pub capabilities: ProviderCapabilities,
 430  }
 431  
 432  /// Ping request/response for health checks.
 433  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 434  pub struct PingParams {}
 435  
 436  #[derive(Debug, Clone, Serialize, Deserialize)]
 437  pub struct PingResult {
 438      /// Optional status message.
 439      #[serde(skip_serializing_if = "Option::is_none")]
 440      pub status: Option<String>,
 441  }
 442  
 443  // =============================================================================
 444  // Authentication Methods
 445  // =============================================================================
 446  
 447  /// A field required for authentication.
 448  #[derive(Debug, Clone, Serialize, Deserialize)]
 449  #[serde(rename_all = "camelCase")]
 450  pub struct AuthField {
 451      /// Field name/key (e.g., "token", "client_id").
 452      pub name: String,
 453      /// Human-readable label (e.g., "API Token").
 454      pub label: String,
 455      /// Description of what this field is for.
 456      #[serde(skip_serializing_if = "Option::is_none")]
 457      pub description: Option<String>,
 458      /// Whether this field is required.
 459      #[serde(default)]
 460      pub required: bool,
 461      /// Whether this field should be masked (for tokens/passwords).
 462      #[serde(default)]
 463      pub secret: bool,
 464      /// Environment variable that can provide this value.
 465      #[serde(skip_serializing_if = "Option::is_none")]
 466      pub env_var: Option<String>,
 467      /// Default value, if any.
 468      #[serde(skip_serializing_if = "Option::is_none")]
 469      pub default: Option<String>,
 470  }
 471  
 472  /// Result of auth/fields request.
 473  #[derive(Debug, Clone, Serialize, Deserialize)]
 474  #[serde(rename_all = "camelCase")]
 475  pub struct AuthFieldsResult {
 476      pub fields: Vec<AuthField>,
 477  }
 478  
 479  /// Authentication status.
 480  #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 481  #[serde(tag = "status", rename_all = "snake_case")]
 482  pub enum AuthStatus {
 483      /// Not authenticated.
 484      NotAuthenticated,
 485      /// Authentication in progress.
 486      Authenticating,
 487      /// Successfully authenticated.
 488      Authenticated {
 489          /// Optional identity info (e.g., user email).
 490          #[serde(skip_serializing_if = "Option::is_none")]
 491          identity: Option<String>,
 492          /// When auth expires (Unix timestamp).
 493          #[serde(skip_serializing_if = "Option::is_none")]
 494          expires_at: Option<u64>,
 495      },
 496      /// Authentication failed.
 497      Failed {
 498          /// Error message.
 499          reason: String,
 500      },
 501      /// Authentication expired.
 502      Expired,
 503  }
 504  
 505  impl AuthStatus {
 506      pub fn is_authenticated(&self) -> bool {
 507          matches!(self, AuthStatus::Authenticated { .. })
 508      }
 509  
 510      pub fn is_failed(&self) -> bool {
 511          matches!(self, AuthStatus::Failed { .. })
 512      }
 513  }
 514  
 515  /// Result of auth/status request.
 516  #[derive(Debug, Clone, Serialize, Deserialize)]
 517  #[serde(rename_all = "camelCase")]
 518  pub struct AuthStatusResult {
 519      #[serde(flatten)]
 520      pub status: AuthStatus,
 521  }
 522  
 523  /// Parameters for auth/authenticate request.
 524  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 525  #[serde(rename_all = "camelCase")]
 526  pub struct AuthenticateParams {
 527      /// Credentials keyed by field name.
 528      pub credentials: HashMap<String, String>,
 529  }
 530  
 531  /// Result of auth/authenticate request.
 532  #[derive(Debug, Clone, Serialize, Deserialize)]
 533  #[serde(rename_all = "camelCase")]
 534  pub struct AuthenticateResult {
 535      #[serde(flatten)]
 536      pub status: AuthStatus,
 537  }
 538  
 539  /// Result of auth/revoke request.
 540  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 541  pub struct AuthRevokeResult {}
 542  
 543  // =============================================================================
 544  // Scope Methods
 545  // =============================================================================
 546  
 547  /// A level in the scope hierarchy (e.g., project, environment).
 548  #[derive(Debug, Clone, Serialize, Deserialize)]
 549  #[serde(rename_all = "camelCase")]
 550  pub struct ScopeLevel {
 551      /// Level name/key (e.g., "project", "environment").
 552      pub name: String,
 553      /// Human-readable label (e.g., "Project").
 554      pub display_name: String,
 555      /// Whether this level is required to fetch secrets.
 556      #[serde(default)]
 557      pub required: bool,
 558      /// Whether multiple selections are allowed.
 559      #[serde(default)]
 560      pub multi_select: bool,
 561      /// Optional description.
 562      #[serde(skip_serializing_if = "Option::is_none")]
 563      pub description: Option<String>,
 564  }
 565  
 566  /// Result of scope/levels request.
 567  #[derive(Debug, Clone, Serialize, Deserialize)]
 568  #[serde(rename_all = "camelCase")]
 569  pub struct ScopeLevelsResult {
 570      pub levels: Vec<ScopeLevel>,
 571  }
 572  
 573  /// An option available at a scope level.
 574  #[derive(Debug, Clone, Serialize, Deserialize)]
 575  #[serde(rename_all = "camelCase")]
 576  pub struct ScopeOption {
 577      /// Unique identifier for this option.
 578      pub id: String,
 579      /// Human-readable display name.
 580      pub display_name: String,
 581      /// Optional description or metadata.
 582      #[serde(skip_serializing_if = "Option::is_none")]
 583      pub description: Option<String>,
 584      /// Optional icon or indicator.
 585      #[serde(skip_serializing_if = "Option::is_none")]
 586      pub icon: Option<String>,
 587      /// Number of secrets at this scope (if known).
 588      #[serde(skip_serializing_if = "Option::is_none")]
 589      pub secret_count: Option<usize>,
 590  }
 591  
 592  /// Parameters for scope/options request.
 593  #[derive(Debug, Clone, Serialize, Deserialize)]
 594  #[serde(rename_all = "camelCase")]
 595  pub struct ScopeOptionsParams {
 596      /// The level to list options for.
 597      pub level: String,
 598      /// Parent scope selections (for hierarchical navigation).
 599      #[serde(default)]
 600      pub parent: HashMap<String, Vec<String>>,
 601  }
 602  
 603  /// Result of scope/options request.
 604  #[derive(Debug, Clone, Serialize, Deserialize)]
 605  #[serde(rename_all = "camelCase")]
 606  pub struct ScopeOptionsResult {
 607      pub options: Vec<ScopeOption>,
 608  }
 609  
 610  /// Current scope selection.
 611  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 612  #[serde(rename_all = "camelCase")]
 613  pub struct ScopeSelection {
 614      /// Map of level name to selected option IDs.
 615      pub selections: HashMap<String, Vec<String>>,
 616  }
 617  
 618  impl ScopeSelection {
 619      pub fn new() -> Self {
 620          Self::default()
 621      }
 622  
 623      pub fn with_selection(
 624          mut self,
 625          level: impl Into<String>,
 626          values: Vec<impl Into<String>>,
 627      ) -> Self {
 628          self.selections
 629              .insert(level.into(), values.into_iter().map(|v| v.into()).collect());
 630          self
 631      }
 632  
 633      pub fn set(&mut self, level: impl Into<String>, values: Vec<impl Into<String>>) {
 634          self.selections
 635              .insert(level.into(), values.into_iter().map(|v| v.into()).collect());
 636      }
 637  
 638      pub fn get(&self, level: &str) -> Option<&[String]> {
 639          self.selections.get(level).map(|v| v.as_slice())
 640      }
 641  
 642      pub fn get_single(&self, level: &str) -> Option<&str> {
 643          self.selections
 644              .get(level)
 645              .and_then(|v| v.first())
 646              .map(|s| s.as_str())
 647      }
 648  
 649      pub fn is_empty(&self) -> bool {
 650          self.selections.is_empty()
 651      }
 652  }
 653  
 654  // =============================================================================
 655  // Secrets Methods
 656  // =============================================================================
 657  
 658  /// A secret/environment variable.
 659  #[derive(Debug, Clone, Serialize, Deserialize)]
 660  #[serde(rename_all = "camelCase")]
 661  pub struct Secret {
 662      /// Variable key/name.
 663      pub key: String,
 664      /// Variable value.
 665      pub value: String,
 666      /// Optional description/comment.
 667      #[serde(skip_serializing_if = "Option::is_none")]
 668      pub description: Option<String>,
 669      /// Whether the secret is sensitive (should be masked in UI).
 670      #[serde(default)]
 671      pub sensitive: bool,
 672  }
 673  
 674  /// Parameters for secrets/fetch request.
 675  #[derive(Debug, Clone, Serialize, Deserialize)]
 676  #[serde(rename_all = "camelCase")]
 677  pub struct SecretsFetchParams {
 678      /// Scope to fetch secrets from.
 679      pub scope: ScopeSelection,
 680  }
 681  
 682  /// Result of secrets/fetch request.
 683  #[derive(Debug, Clone, Serialize, Deserialize)]
 684  #[serde(rename_all = "camelCase")]
 685  pub struct SecretsFetchResult {
 686      /// The fetched secrets.
 687      pub secrets: Vec<Secret>,
 688      /// Version/ETag for cache invalidation.
 689      #[serde(skip_serializing_if = "Option::is_none")]
 690      pub version: Option<String>,
 691      /// Scope path for display (e.g., "project/config").
 692      #[serde(skip_serializing_if = "Option::is_none")]
 693      pub scope_path: Option<String>,
 694  }
 695  
 696  /// Parameters for secrets/get request.
 697  #[derive(Debug, Clone, Serialize, Deserialize)]
 698  #[serde(rename_all = "camelCase")]
 699  pub struct SecretsGetParams {
 700      /// Scope to get secret from.
 701      pub scope: ScopeSelection,
 702      /// Key to get.
 703      pub key: String,
 704  }
 705  
 706  /// Result of secrets/get request.
 707  #[derive(Debug, Clone, Serialize, Deserialize)]
 708  #[serde(rename_all = "camelCase")]
 709  pub struct SecretsGetResult {
 710      pub secret: Option<Secret>,
 711  }
 712  
 713  /// Parameters for secrets/set request.
 714  #[derive(Debug, Clone, Serialize, Deserialize)]
 715  #[serde(rename_all = "camelCase")]
 716  pub struct SecretsSetParams {
 717      /// Scope to set secret in.
 718      pub scope: ScopeSelection,
 719      /// Key to set.
 720      pub key: String,
 721      /// Value to set.
 722      pub value: String,
 723      /// Optional description.
 724      #[serde(skip_serializing_if = "Option::is_none")]
 725      pub description: Option<String>,
 726  }
 727  
 728  /// Result of secrets/set request.
 729  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 730  pub struct SecretsSetResult {}
 731  
 732  /// Parameters for secrets/delete request.
 733  #[derive(Debug, Clone, Serialize, Deserialize)]
 734  #[serde(rename_all = "camelCase")]
 735  pub struct SecretsDeleteParams {
 736      /// Scope to delete secret from.
 737      pub scope: ScopeSelection,
 738      /// Key to delete.
 739      pub key: String,
 740  }
 741  
 742  /// Result of secrets/delete request.
 743  #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 744  pub struct SecretsDeleteResult {}
 745  
 746  // =============================================================================
 747  // Notifications
 748  // =============================================================================
 749  
 750  /// Parameters for secrets/changed notification.
 751  #[derive(Debug, Clone, Serialize, Deserialize)]
 752  #[serde(rename_all = "camelCase")]
 753  pub struct SecretsChangedParams {
 754      /// Scope that changed.
 755      pub scope: ScopeSelection,
 756      /// Keys that changed (empty means all).
 757      #[serde(default)]
 758      pub keys: Vec<String>,
 759  }
 760  
 761  /// Log level for log notifications.
 762  #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
 763  #[serde(rename_all = "lowercase")]
 764  pub enum LogLevel {
 765      Error,
 766      Warn,
 767      Info,
 768      Debug,
 769      Trace,
 770  }
 771  
 772  /// Parameters for log notification.
 773  #[derive(Debug, Clone, Serialize, Deserialize)]
 774  #[serde(rename_all = "camelCase")]
 775  pub struct LogParams {
 776      pub level: LogLevel,
 777      pub message: String,
 778  }
 779  
 780  // =============================================================================
 781  // Method Constants
 782  // =============================================================================
 783  
 784  pub mod methods {
 785      // Lifecycle
 786      pub const INITIALIZE: &str = "initialize";
 787      pub const INITIALIZED: &str = "initialized";
 788      pub const SHUTDOWN: &str = "shutdown";
 789      pub const EXIT: &str = "exit";
 790      pub const PING: &str = "ping";
 791  
 792      // Authentication
 793      pub const AUTH_STATUS: &str = "auth/status";
 794      pub const AUTH_FIELDS: &str = "auth/fields";
 795      pub const AUTH_AUTHENTICATE: &str = "auth/authenticate";
 796      pub const AUTH_REVOKE: &str = "auth/revoke";
 797  
 798      // Scope
 799      pub const SCOPE_LEVELS: &str = "scope/levels";
 800      pub const SCOPE_OPTIONS: &str = "scope/options";
 801  
 802      // Secrets
 803      pub const SECRETS_FETCH: &str = "secrets/fetch";
 804      pub const SECRETS_GET: &str = "secrets/get";
 805      pub const SECRETS_SET: &str = "secrets/set";
 806      pub const SECRETS_DELETE: &str = "secrets/delete";
 807  
 808      // Notifications
 809      pub const SECRETS_CHANGED: &str = "secrets/changed";
 810      pub const LOG: &str = "log";
 811  }
 812  
 813  // =============================================================================
 814  // Conversion utilities for existing types
 815  // =============================================================================
 816  
 817  impl From<AuthStatus> for super::traits::AuthStatus {
 818      fn from(status: AuthStatus) -> Self {
 819          match status {
 820              AuthStatus::NotAuthenticated => super::traits::AuthStatus::NotAuthenticated,
 821              AuthStatus::Authenticating => super::traits::AuthStatus::Authenticating,
 822              AuthStatus::Authenticated { identity, expires_at } => {
 823                  super::traits::AuthStatus::Authenticated {
 824                      identity: identity.map(CompactString::from),
 825                      expires_at,
 826                  }
 827              }
 828              AuthStatus::Failed { reason } => super::traits::AuthStatus::Failed {
 829                  reason: CompactString::from(reason),
 830              },
 831              AuthStatus::Expired => super::traits::AuthStatus::Expired,
 832          }
 833      }
 834  }
 835  
 836  impl From<super::traits::AuthStatus> for AuthStatus {
 837      fn from(status: super::traits::AuthStatus) -> Self {
 838          match status {
 839              super::traits::AuthStatus::NotAuthenticated => AuthStatus::NotAuthenticated,
 840              super::traits::AuthStatus::Authenticating => AuthStatus::Authenticating,
 841              super::traits::AuthStatus::Authenticated { identity, expires_at } => {
 842                  AuthStatus::Authenticated {
 843                      identity: identity.map(|s| s.to_string()),
 844                      expires_at,
 845                  }
 846              }
 847              super::traits::AuthStatus::Failed { reason } => AuthStatus::Failed {
 848                  reason: reason.to_string(),
 849              },
 850              super::traits::AuthStatus::Expired => AuthStatus::Expired,
 851          }
 852      }
 853  }
 854  
 855  impl From<AuthField> for super::traits::AuthField {
 856      fn from(field: AuthField) -> Self {
 857          super::traits::AuthField {
 858              name: CompactString::from(field.name),
 859              label: CompactString::from(field.label),
 860              description: field.description.map(CompactString::from),
 861              required: field.required,
 862              secret: field.secret,
 863              env_var: field.env_var.map(CompactString::from),
 864              default: field.default.map(CompactString::from),
 865          }
 866      }
 867  }
 868  
 869  impl From<super::traits::AuthField> for AuthField {
 870      fn from(field: super::traits::AuthField) -> Self {
 871          AuthField {
 872              name: field.name.to_string(),
 873              label: field.label.to_string(),
 874              description: field.description.map(|s| s.to_string()),
 875              required: field.required,
 876              secret: field.secret,
 877              env_var: field.env_var.map(|s| s.to_string()),
 878              default: field.default.map(|s| s.to_string()),
 879          }
 880      }
 881  }
 882  
 883  impl From<ScopeLevel> for super::traits::ScopeLevel {
 884      fn from(level: ScopeLevel) -> Self {
 885          super::traits::ScopeLevel {
 886              name: CompactString::from(level.name),
 887              display_name: CompactString::from(level.display_name),
 888              required: level.required,
 889              multi_select: level.multi_select,
 890              description: level.description.map(CompactString::from),
 891          }
 892      }
 893  }
 894  
 895  impl From<super::traits::ScopeLevel> for ScopeLevel {
 896      fn from(level: super::traits::ScopeLevel) -> Self {
 897          ScopeLevel {
 898              name: level.name.to_string(),
 899              display_name: level.display_name.to_string(),
 900              required: level.required,
 901              multi_select: level.multi_select,
 902              description: level.description.map(|s| s.to_string()),
 903          }
 904      }
 905  }
 906  
 907  impl From<ScopeOption> for super::traits::ScopeOption {
 908      fn from(option: ScopeOption) -> Self {
 909          super::traits::ScopeOption {
 910              id: CompactString::from(option.id),
 911              display_name: CompactString::from(option.display_name),
 912              description: option.description.map(CompactString::from),
 913              icon: option.icon.map(CompactString::from),
 914          }
 915      }
 916  }
 917  
 918  impl From<super::traits::ScopeOption> for ScopeOption {
 919      fn from(option: super::traits::ScopeOption) -> Self {
 920          ScopeOption {
 921              id: option.id.to_string(),
 922              display_name: option.display_name.to_string(),
 923              description: option.description.map(|s| s.to_string()),
 924              icon: option.icon.map(|s| s.to_string()),
 925              secret_count: None,
 926          }
 927      }
 928  }
 929  
 930  impl From<ScopeSelection> for super::traits::ScopeSelection {
 931      fn from(selection: ScopeSelection) -> Self {
 932          super::traits::ScopeSelection {
 933              selections: selection.selections,
 934          }
 935      }
 936  }
 937  
 938  impl From<super::traits::ScopeSelection> for ScopeSelection {
 939      fn from(selection: super::traits::ScopeSelection) -> Self {
 940          ScopeSelection {
 941              selections: selection.selections,
 942          }
 943      }
 944  }
 945  
 946  impl From<ProviderInfo> for super::traits::RemoteProviderInfo {
 947      fn from(info: ProviderInfo) -> Self {
 948          super::traits::RemoteProviderInfo {
 949              id: CompactString::from(info.id),
 950              display_name: CompactString::from(info.name),
 951              short_name: info
 952                  .short_name
 953                  .map(CompactString::from)
 954                  .unwrap_or_else(|| CompactString::from("")),
 955              description: info.description.map(CompactString::from),
 956              docs_url: info.docs_url,
 957          }
 958      }
 959  }
 960  
 961  #[cfg(test)]
 962  mod tests {
 963      use super::*;
 964  
 965      #[test]
 966      fn test_json_rpc_request_serialization() {
 967          let request = JsonRpcRequest::new(1i64, "initialize")
 968              .with_params(InitializeParams {
 969                  protocol_version: PROTOCOL_VERSION.to_string(),
 970                  client_info: ClientInfo {
 971                      name: "ecolog-lsp".to_string(),
 972                      version: "1.0.0".to_string(),
 973                  },
 974                  capabilities: ClientCapabilities::default(),
 975                  config: HashMap::new(),
 976              });
 977  
 978          let json = serde_json::to_string(&request).unwrap();
 979          assert!(json.contains("initialize"));
 980          assert!(json.contains("2.0"));
 981      }
 982  
 983      #[test]
 984      fn test_json_rpc_response_success() {
 985          let response = JsonRpcResponse::success(
 986              JsonRpcId::Number(1),
 987              InitializeResult {
 988                  protocol_version: PROTOCOL_VERSION.to_string(),
 989                  provider_info: ProviderInfo {
 990                      id: "doppler".to_string(),
 991                      name: "Doppler".to_string(),
 992                      version: "1.0.0".to_string(),
 993                      short_name: Some("DPL".to_string()),
 994                      description: None,
 995                      docs_url: None,
 996                  },
 997                  capabilities: ProviderCapabilities::default(),
 998              },
 999          );
1000  
1001          assert!(response.is_success());
1002      }
1003  
1004      #[test]
1005      fn test_json_rpc_error_codes() {
1006          let error = JsonRpcError::not_authenticated("Token expired");
1007          assert_eq!(error.code, ErrorCode::NotAuthenticated as i32);
1008  
1009          let error = JsonRpcError::rate_limited(60);
1010          assert_eq!(error.code, ErrorCode::RateLimited as i32);
1011      }
1012  
1013      #[test]
1014      fn test_auth_status_tagged_serialization() {
1015          let authenticated = AuthStatus::Authenticated {
1016              identity: Some("user@example.com".to_string()),
1017              expires_at: None,
1018          };
1019  
1020          let json = serde_json::to_string(&authenticated).unwrap();
1021          assert!(json.contains("authenticated"));
1022          assert!(json.contains("user@example.com"));
1023      }
1024  
1025      #[test]
1026      fn test_scope_selection() {
1027          let selection = ScopeSelection::new()
1028              .with_selection("project", vec!["my-project"])
1029              .with_selection("config", vec!["dev"]);
1030  
1031          assert_eq!(selection.get_single("project"), Some("my-project"));
1032          assert_eq!(selection.get_single("config"), Some("dev"));
1033          assert!(!selection.is_empty());
1034      }
1035  }