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 }