Skip to main content

ryo_app/
service.rs

1//! tarpc Service Definition
2//!
3//! This module defines the RPC service trait for ryo server.
4//! Each method corresponds 1:1 to an Api method.
5//!
6//! See [tarpc-migration-design.md] for the full design.
7
8use crate::api::{
9    BorrowAnalysisRequest, BorrowAnalysisResponse, CascadeRequest, CascadeResponse,
10    ChainAnalysisRequest, ChainAnalysisResponse, DiscoverRequest, DiscoverResponse,
11    FlowAnalysisRequest, FlowAnalysisResponse, GraphSummaryRequest, GraphSummaryResponse,
12    LiteralSearchRequest, LiteralSearchResponse, LockAnalysisRequest, LockAnalysisResponse,
13    OverviewRequest, OverviewResponse, PingResponse, QueryResponse, RunRequest, RunResponse,
14    RyoqlRequest, SpecRequest, SpecResponse, StatusResponse, SuggestApplyRequest,
15    SuggestApplyResponse, SuggestChoicesRequest, SuggestChoicesResponse, SuggestCompareRequest,
16    SuggestCompareResponse, SuggestGenerateRequest, SuggestGenerateResponse, SuggestRequest,
17    SuggestResponse, SuggestVerifyRequest, SuggestVerifyResponse, TypeAnalysisRequest,
18    TypeAnalysisResponse,
19};
20use serde::{Deserialize, Serialize};
21
22// ============================================================================
23// Error Types
24// ============================================================================
25
26/// Structured error type for RPC responses
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub enum RyoError {
29    /// Symbol or resource not found
30    NotFound { name: String },
31    /// Parse or syntax error
32    ParseError { message: String },
33    /// Invalid request parameters
34    InvalidRequest { message: String },
35    /// Internal server error
36    Internal { message: String },
37}
38
39impl std::fmt::Display for RyoError {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            Self::NotFound { name } => write!(f, "not found: {}", name),
43            Self::ParseError { message } => write!(f, "parse error: {}", message),
44            Self::InvalidRequest { message } => write!(f, "invalid request: {}", message),
45            Self::Internal { message } => write!(f, "internal error: {}", message),
46        }
47    }
48}
49
50impl std::error::Error for RyoError {}
51
52// ============================================================================
53// Error Conversion
54// ============================================================================
55
56impl From<crate::api::ApiError> for RyoError {
57    fn from(e: crate::api::ApiError) -> Self {
58        use crate::api::ApiError;
59        match e {
60            ApiError::NotFound(name) => RyoError::NotFound { name },
61            ApiError::InvalidGoal(msg) => RyoError::InvalidRequest { message: msg },
62            ApiError::Planning(e) => RyoError::Internal {
63                message: e.to_string(),
64            },
65            ApiError::Execution(msg) => RyoError::Internal { message: msg },
66            ApiError::Storage(e) => RyoError::Internal {
67                message: e.to_string(),
68            },
69            ApiError::Conflicts(n) => RyoError::Internal {
70                message: format!("{} conflicts detected", n),
71            },
72            ApiError::SyntaxError(msg) => RyoError::ParseError { message: msg },
73            ApiError::Graph(e) => RyoError::Internal {
74                message: e.to_string(),
75            },
76            ApiError::Discover(e) => RyoError::Internal {
77                message: e.to_string(),
78            },
79            ApiError::Project(e) => RyoError::Internal {
80                message: e.to_string(),
81            },
82            ApiError::Spec(e) => RyoError::Internal {
83                message: e.to_string(),
84            },
85            ApiError::Resolve(e) => RyoError::InvalidRequest {
86                message: e.to_string(),
87            },
88            ApiError::Sync(e) => RyoError::Internal {
89                message: e.to_string(),
90            },
91        }
92    }
93}
94
95// ============================================================================
96// tarpc Service Definition
97// ============================================================================
98
99/// RPC service trait for ryo server
100///
101/// Each method corresponds 1:1 to an Api method.
102///
103/// ## Read/Write Separation
104/// - Read methods (discover, cascade, suggest, status): No write lock required
105/// - Write methods (run): Requires exclusive access
106#[tarpc::service]
107pub trait RyoService {
108    /// Health check with version info for client/server compatibility verification.
109    async fn ping() -> PingResponse;
110
111    /// Get server status (Api::status) [READ]
112    async fn status() -> StatusResponse;
113
114    /// Shutdown server (graceful)
115    async fn shutdown();
116
117    /// Discover symbols by pattern (Api::discover) [READ]
118    async fn discover(req: DiscoverRequest) -> Result<DiscoverResponse, RyoError>;
119
120    /// Codebase overview (Api::overview) [READ]
121    async fn overview(req: OverviewRequest) -> Result<OverviewResponse, RyoError>;
122
123    /// Execute mutation (Api::run) [WRITE]
124    async fn run(req: RunRequest) -> Result<RunResponse, RyoError>;
125
126    /// Graph cascade analysis (Api::graph_cascade) [READ]
127    async fn cascade(req: CascadeRequest) -> Result<CascadeResponse, RyoError>;
128
129    /// Graph summary (Api::graph_summary) [READ]
130    async fn graph_summary(req: GraphSummaryRequest) -> Result<GraphSummaryResponse, RyoError>;
131
132    /// Type analysis (Api::graph_type) [READ]
133    async fn graph_type(req: TypeAnalysisRequest) -> Result<TypeAnalysisResponse, RyoError>;
134
135    /// Flow analysis (Api::graph_flow) [READ]
136    async fn graph_flow(req: FlowAnalysisRequest) -> Result<FlowAnalysisResponse, RyoError>;
137
138    /// Borrow analysis (Api::graph_borrow) [READ]
139    async fn graph_borrow(req: BorrowAnalysisRequest) -> Result<BorrowAnalysisResponse, RyoError>;
140
141    /// Lock analysis (Api::graph_lock) [READ]
142    async fn graph_lock(req: LockAnalysisRequest) -> Result<LockAnalysisResponse, RyoError>;
143
144    /// Chain analysis - transitive call chain traversal (Api::graph_chain) [READ]
145    async fn graph_chain(req: ChainAnalysisRequest) -> Result<ChainAnalysisResponse, RyoError>;
146
147    /// Code improvement suggestions (Api::suggest) [READ]
148    async fn suggest(req: SuggestRequest) -> Result<SuggestResponse, RyoError>;
149
150    /// Apply suggestions by ID (Api::suggest_apply) [WRITE]
151    async fn suggest_apply(req: SuggestApplyRequest) -> Result<SuggestApplyResponse, RyoError>;
152
153    /// Get design choices for a suggestion (Api::suggest_choices) [READ]
154    async fn suggest_choices(
155        req: SuggestChoicesRequest,
156    ) -> Result<SuggestChoicesResponse, RyoError>;
157
158    /// Verify a suggestion before applying (Api::suggest_verify) [READ]
159    async fn suggest_verify(req: SuggestVerifyRequest) -> Result<SuggestVerifyResponse, RyoError>;
160
161    /// Compare design choices for a suggestion (Api::suggest_compare) [READ]
162    async fn suggest_compare(
163        req: SuggestCompareRequest,
164    ) -> Result<SuggestCompareResponse, RyoError>;
165
166    /// Generate code from parameterized patterns (Api::suggest_generate) [READ/WRITE]
167    async fn suggest_generate(
168        req: SuggestGenerateRequest,
169    ) -> Result<SuggestGenerateResponse, RyoError>;
170
171    /// Query spec hierarchy (Api::spec) [READ]
172    async fn spec(req: SpecRequest) -> Result<SpecResponse, RyoError>;
173
174    /// Execute a RyoQL query (Api::query_ryoql) [READ]
175    async fn query_ryoql(req: RyoqlRequest) -> Result<QueryResponse, RyoError>;
176
177    /// Search literals in source code (Api::search_literal) [READ]
178    async fn search_literal(req: LiteralSearchRequest) -> Result<LiteralSearchResponse, RyoError>;
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn test_ryo_error_display() {
187        let err = RyoError::NotFound {
188            name: "foo".to_string(),
189        };
190        assert_eq!(err.to_string(), "not found: foo");
191
192        let err = RyoError::ParseError {
193            message: "syntax error".to_string(),
194        };
195        assert_eq!(err.to_string(), "parse error: syntax error");
196
197        let err = RyoError::InvalidRequest {
198            message: "bad input".to_string(),
199        };
200        assert_eq!(err.to_string(), "invalid request: bad input");
201
202        let err = RyoError::Internal {
203            message: "crash".to_string(),
204        };
205        assert_eq!(err.to_string(), "internal error: crash");
206    }
207
208    #[test]
209    fn test_ryo_error_from_api_error() {
210        use crate::api::ApiError;
211
212        let api_err = ApiError::NotFound("bar".to_string());
213        let ryo_err: RyoError = api_err.into();
214        assert!(matches!(ryo_err, RyoError::NotFound { name } if name == "bar"));
215    }
216}