ryo-app 0.1.0

[preview] Application layer for RYO - Project management, Intent handling, API
Documentation
//! tarpc Service Definition
//!
//! This module defines the RPC service trait for ryo server.
//! Each method corresponds 1:1 to an Api method.
//!
//! See [tarpc-migration-design.md] for the full design.

use crate::api::{
    BorrowAnalysisRequest, BorrowAnalysisResponse, CascadeRequest, CascadeResponse,
    ChainAnalysisRequest, ChainAnalysisResponse, DiscoverRequest, DiscoverResponse,
    FlowAnalysisRequest, FlowAnalysisResponse, GraphSummaryRequest, GraphSummaryResponse,
    LiteralSearchRequest, LiteralSearchResponse, LockAnalysisRequest, LockAnalysisResponse,
    OverviewRequest, OverviewResponse, PingResponse, QueryResponse, RunRequest, RunResponse,
    RyoqlRequest, SpecRequest, SpecResponse, StatusResponse, SuggestApplyRequest,
    SuggestApplyResponse, SuggestChoicesRequest, SuggestChoicesResponse, SuggestCompareRequest,
    SuggestCompareResponse, SuggestGenerateRequest, SuggestGenerateResponse, SuggestRequest,
    SuggestResponse, SuggestVerifyRequest, SuggestVerifyResponse, TypeAnalysisRequest,
    TypeAnalysisResponse,
};
use serde::{Deserialize, Serialize};

// ============================================================================
// Error Types
// ============================================================================

/// Structured error type for RPC responses
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RyoError {
    /// Symbol or resource not found
    NotFound { name: String },
    /// Parse or syntax error
    ParseError { message: String },
    /// Invalid request parameters
    InvalidRequest { message: String },
    /// Internal server error
    Internal { message: String },
}

impl std::fmt::Display for RyoError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::NotFound { name } => write!(f, "not found: {}", name),
            Self::ParseError { message } => write!(f, "parse error: {}", message),
            Self::InvalidRequest { message } => write!(f, "invalid request: {}", message),
            Self::Internal { message } => write!(f, "internal error: {}", message),
        }
    }
}

impl std::error::Error for RyoError {}

// ============================================================================
// Error Conversion
// ============================================================================

impl From<crate::api::ApiError> for RyoError {
    fn from(e: crate::api::ApiError) -> Self {
        use crate::api::ApiError;
        match e {
            ApiError::NotFound(name) => RyoError::NotFound { name },
            ApiError::InvalidGoal(msg) => RyoError::InvalidRequest { message: msg },
            ApiError::Planning(e) => RyoError::Internal {
                message: e.to_string(),
            },
            ApiError::Execution(msg) => RyoError::Internal { message: msg },
            ApiError::Storage(e) => RyoError::Internal {
                message: e.to_string(),
            },
            ApiError::Conflicts(n) => RyoError::Internal {
                message: format!("{} conflicts detected", n),
            },
            ApiError::SyntaxError(msg) => RyoError::ParseError { message: msg },
            ApiError::Graph(e) => RyoError::Internal {
                message: e.to_string(),
            },
            ApiError::Discover(e) => RyoError::Internal {
                message: e.to_string(),
            },
            ApiError::Project(e) => RyoError::Internal {
                message: e.to_string(),
            },
            ApiError::Spec(e) => RyoError::Internal {
                message: e.to_string(),
            },
            ApiError::Resolve(e) => RyoError::InvalidRequest {
                message: e.to_string(),
            },
            ApiError::Sync(e) => RyoError::Internal {
                message: e.to_string(),
            },
        }
    }
}

// ============================================================================
// tarpc Service Definition
// ============================================================================

/// RPC service trait for ryo server
///
/// Each method corresponds 1:1 to an Api method.
///
/// ## Read/Write Separation
/// - Read methods (discover, cascade, suggest, status): No write lock required
/// - Write methods (run): Requires exclusive access
#[tarpc::service]
pub trait RyoService {
    /// Health check with version info for client/server compatibility verification.
    async fn ping() -> PingResponse;

    /// Get server status (Api::status) [READ]
    async fn status() -> StatusResponse;

    /// Shutdown server (graceful)
    async fn shutdown();

    /// Discover symbols by pattern (Api::discover) [READ]
    async fn discover(req: DiscoverRequest) -> Result<DiscoverResponse, RyoError>;

    /// Codebase overview (Api::overview) [READ]
    async fn overview(req: OverviewRequest) -> Result<OverviewResponse, RyoError>;

    /// Execute mutation (Api::run) [WRITE]
    async fn run(req: RunRequest) -> Result<RunResponse, RyoError>;

    /// Graph cascade analysis (Api::graph_cascade) [READ]
    async fn cascade(req: CascadeRequest) -> Result<CascadeResponse, RyoError>;

    /// Graph summary (Api::graph_summary) [READ]
    async fn graph_summary(req: GraphSummaryRequest) -> Result<GraphSummaryResponse, RyoError>;

    /// Type analysis (Api::graph_type) [READ]
    async fn graph_type(req: TypeAnalysisRequest) -> Result<TypeAnalysisResponse, RyoError>;

    /// Flow analysis (Api::graph_flow) [READ]
    async fn graph_flow(req: FlowAnalysisRequest) -> Result<FlowAnalysisResponse, RyoError>;

    /// Borrow analysis (Api::graph_borrow) [READ]
    async fn graph_borrow(req: BorrowAnalysisRequest) -> Result<BorrowAnalysisResponse, RyoError>;

    /// Lock analysis (Api::graph_lock) [READ]
    async fn graph_lock(req: LockAnalysisRequest) -> Result<LockAnalysisResponse, RyoError>;

    /// Chain analysis - transitive call chain traversal (Api::graph_chain) [READ]
    async fn graph_chain(req: ChainAnalysisRequest) -> Result<ChainAnalysisResponse, RyoError>;

    /// Code improvement suggestions (Api::suggest) [READ]
    async fn suggest(req: SuggestRequest) -> Result<SuggestResponse, RyoError>;

    /// Apply suggestions by ID (Api::suggest_apply) [WRITE]
    async fn suggest_apply(req: SuggestApplyRequest) -> Result<SuggestApplyResponse, RyoError>;

    /// Get design choices for a suggestion (Api::suggest_choices) [READ]
    async fn suggest_choices(
        req: SuggestChoicesRequest,
    ) -> Result<SuggestChoicesResponse, RyoError>;

    /// Verify a suggestion before applying (Api::suggest_verify) [READ]
    async fn suggest_verify(req: SuggestVerifyRequest) -> Result<SuggestVerifyResponse, RyoError>;

    /// Compare design choices for a suggestion (Api::suggest_compare) [READ]
    async fn suggest_compare(
        req: SuggestCompareRequest,
    ) -> Result<SuggestCompareResponse, RyoError>;

    /// Generate code from parameterized patterns (Api::suggest_generate) [READ/WRITE]
    async fn suggest_generate(
        req: SuggestGenerateRequest,
    ) -> Result<SuggestGenerateResponse, RyoError>;

    /// Query spec hierarchy (Api::spec) [READ]
    async fn spec(req: SpecRequest) -> Result<SpecResponse, RyoError>;

    /// Execute a RyoQL query (Api::query_ryoql) [READ]
    async fn query_ryoql(req: RyoqlRequest) -> Result<QueryResponse, RyoError>;

    /// Search literals in source code (Api::search_literal) [READ]
    async fn search_literal(req: LiteralSearchRequest) -> Result<LiteralSearchResponse, RyoError>;
}

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

    #[test]
    fn test_ryo_error_display() {
        let err = RyoError::NotFound {
            name: "foo".to_string(),
        };
        assert_eq!(err.to_string(), "not found: foo");

        let err = RyoError::ParseError {
            message: "syntax error".to_string(),
        };
        assert_eq!(err.to_string(), "parse error: syntax error");

        let err = RyoError::InvalidRequest {
            message: "bad input".to_string(),
        };
        assert_eq!(err.to_string(), "invalid request: bad input");

        let err = RyoError::Internal {
            message: "crash".to_string(),
        };
        assert_eq!(err.to_string(), "internal error: crash");
    }

    #[test]
    fn test_ryo_error_from_api_error() {
        use crate::api::ApiError;

        let api_err = ApiError::NotFound("bar".to_string());
        let ryo_err: RyoError = api_err.into();
        assert!(matches!(ryo_err, RyoError::NotFound { name } if name == "bar"));
    }
}