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}