Skip to main content

dog_core/
service.rs

1use anyhow::{anyhow, Result};
2use async_trait::async_trait;
3
4use crate::tenant::TenantContext;
5
6/// Macro to generate custom methods in the DogService trait
7/// Usage: custom_methods!("read", "write", "analyze")
8/// Applications should use this macro to define their own custom methods
9#[macro_export]
10macro_rules! custom_methods {
11    ($($method:literal),*) => {
12        $(
13            paste::paste! {
14                /// Custom method generated by macro
15                async fn [<$method:lower>](&self, _ctx: &TenantContext, _data: R, _params: P) -> Result<R> {
16                    Err(anyhow!(concat!("Custom method not implemented: ", $method)))
17                }
18            }
19        )*
20    };
21}
22
23/// Standard service methods, similar to Feathers:
24/// find, get, create, update, patch, remove.
25///
26/// Custom methods are declared via `Custom("methodName")`.
27#[derive(Debug, Clone, PartialEq, Eq, Hash)]
28pub enum ServiceMethodKind {
29    Find,
30    Get,
31    Create,
32    Update,
33    Patch,
34    Remove,
35    Custom(&'static str),
36}
37
38/// Capabilities describe which methods a service wants to expose
39/// to the outside world (HTTP, WebSockets, P2P, etc.).
40///
41/// Adapters (like dog-axum) can use this to mount only allowed routes.
42#[derive(Debug, Clone)]
43pub struct ServiceCapabilities {
44    pub allowed_methods: Vec<ServiceMethodKind>,
45}
46
47impl ServiceCapabilities {
48    /// Full CRUD, equivalent to Feathers default:
49    /// ['find', 'get', 'create', 'patch', 'update', 'remove']
50    pub fn standard_crud() -> Self {
51        use ServiceMethodKind::*;
52        Self {
53            allowed_methods: vec![Find, Get, Create, Update, Patch, Remove],
54        }
55    }
56
57    /// Minimal example: only `find` and `create`.
58    pub fn minimal() -> Self {
59        use ServiceMethodKind::*;
60        Self {
61            allowed_methods: vec![Find, Create],
62        }
63    }
64
65    /// Helper for building from a list.
66    pub fn from_methods(methods: Vec<ServiceMethodKind>) -> Self {
67        Self {
68            allowed_methods: methods,
69        }
70    }
71}
72
73/// Core DogRS service trait, inspired by FeathersJS:
74///
75/// - `find`   → list/query many
76/// - `get`    → fetch one by id
77/// - `create` → create one (or conceptually many)
78/// - `update` → full replace
79/// - `patch`  → partial update
80/// - `remove` → delete one or many
81///
82/// All methods have default implementations that return
83/// "Method not implemented", so a service can override only
84/// what it actually supports.
85
86#[async_trait]
87pub trait DogService<R, P = ()>: Send + Sync
88where
89    R: Send + 'static,
90    P: Send + 'static,
91{
92    /// Describe which methods this service wants to expose.
93    ///
94    /// Adapters (HTTP, P2P, etc.) should respect this when deciding
95    /// what is callable from the outside world.
96    fn capabilities(&self) -> ServiceCapabilities {
97        // By default, assume full CRUD.
98        ServiceCapabilities::standard_crud()
99    }
100
101    /// Find many records (optionally filtered by params).
102    async fn find(&self, _ctx: &TenantContext, _params: P) -> Result<Vec<R>> {
103        Err(anyhow!("Method not implemented: find"))
104    }
105
106    /// Get a single record by id.
107    async fn get(&self, _ctx: &TenantContext, _id: &str, _params: P) -> Result<R> {
108        Err(anyhow!("Method not implemented: get"))
109    }
110
111    /// Create a new record.
112    ///
113    /// For many-record semantics, an adapter or higher-level
114    /// service can wrap this in a loop or accept Vec<R>.
115    async fn create(&self, _ctx: &TenantContext, _data: R, _params: P) -> Result<R> {
116        Err(anyhow!("Method not implemented: create"))
117    }
118
119    /// Fully replace an existing record.
120    ///
121    /// `id` is required (no multi-update here at core level).
122    async fn update(&self, _ctx: &TenantContext, _id: &str, _data: R, _params: P) -> Result<R> {
123        Err(anyhow!("Method not implemented: update"))
124    }
125
126    /// Partially update an existing record.
127    ///
128    /// `id` can be `None` to indicate "multi" semantics if
129    /// an adapter / implementation supports it.
130    async fn patch(
131        &self,
132        _ctx: &TenantContext,
133        _id: Option<&str>,
134        _data: R,
135        _params: P,
136    ) -> Result<R> {
137        Err(anyhow!("Method not implemented: patch"))
138    }
139
140    /// Remove an existing record.
141    ///
142    /// `id` can be `None` to indicate "multi" semantics if
143    /// an adapter / implementation supports it.
144    async fn remove(&self, _ctx: &TenantContext, _id: Option<&str>, _params: P) -> Result<R> {
145        Err(anyhow!("Method not implemented: remove"))
146    }
147
148    /// Handle custom methods - the best we can do in Rust for dynamic dispatch
149    /// Services implement this to route to their specific custom methods
150    async fn custom(
151        &self,
152        _ctx: &TenantContext,
153        _method: &str,
154        _data: Option<R>,
155        _params: P,
156    ) -> Result<R> {
157        Err(anyhow!("Custom methods not implemented by this service"))
158    }
159}