From c01604c065bc0ddec401e77b97b11a2a0ec05764 Mon Sep 17 00:00:00 2001 From: utnim2 Date: Fri, 29 Nov 2024 22:19:59 +0530 Subject: [PATCH 1/4] implemented jsonplaceholders rest apis grpc --- build.rs | 37 ++++++- posts.proto | 35 +++++++ src/main.rs | 280 ++++++++++++++++++++++++++++++++++++++++++++++++++-- users.proto | 63 ++++++++++++ 4 files changed, 401 insertions(+), 14 deletions(-) create mode 100644 posts.proto create mode 100644 users.proto diff --git a/build.rs b/build.rs index 4f4fd27..0780165 100644 --- a/build.rs +++ b/build.rs @@ -1,14 +1,41 @@ use std::path::PathBuf; fn main() { + // Helper function to configure tonic_build with common settings + fn configure_tonic() -> tonic_build::Builder { + tonic_build::configure() + .protoc_arg("--experimental_allow_proto3_optional") // Enable proto3 optional fields + .build_server(true) + .build_client(true) + } + + // Compile news.proto let mut news = PathBuf::from(env!("CARGO_MANIFEST_DIR")); news.push("news.proto"); - tonic_build::compile_protos(news).expect("Failed to compile protos"); + // Configure and compile each proto file with descriptors let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); - tonic_build::configure() + // Compile news.proto + configure_tonic() .file_descriptor_set_path(out_dir.join("news_descriptor.bin")) - .compile(&["news.proto"], &["proto"]) - .unwrap(); -} + .compile(&["news.proto"], &["."]) + .expect("Failed to compile news.proto"); + + // Compile posts.proto + configure_tonic() + .file_descriptor_set_path(out_dir.join("posts_descriptor.bin")) + .compile(&["posts.proto"], &["."]) + .expect("Failed to compile posts.proto"); + + // Compile users.proto + configure_tonic() + .file_descriptor_set_path(out_dir.join("users_descriptor.bin")) + .compile(&["users.proto"], &["."]) + .expect("Failed to compile users.proto"); + + // Set up cargo rerun-if-changed directives + println!("cargo:rerun-if-changed=news.proto"); + println!("cargo:rerun-if-changed=posts.proto"); + println!("cargo:rerun-if-changed=users.proto"); +} \ No newline at end of file diff --git a/posts.proto b/posts.proto new file mode 100644 index 0000000..1111bea --- /dev/null +++ b/posts.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; +package posts; + +message Post { + int32 id = 1; + int32 userId = 2; + string title = 3; + string body = 4; +} + +message Filter { + optional int32 userId = 1; +} + +message PostRequest { + int32 id = 1; +} + +message PostList { + repeated Post posts = 1; +} + +message PostResponse { + Post post = 1; +} + +message DeleteResponse {} + +service PostService { + rpc ListPosts(Filter) returns (PostList); + rpc GetPost(PostRequest) returns (Post); + rpc CreatePost(Post) returns (PostResponse); + rpc UpdatePost(Post) returns (PostResponse); + rpc DeletePost(PostRequest) returns (DeleteResponse); +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 7e2aed4..bf563ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,6 @@ use tonic_tracing_opentelemetry::middleware::server; use tower::make::Shared; use news::news_service_server::NewsService; -use news::news_service_server::NewsServiceServer; use news::{MultipleNewsId, News, NewsId, NewsList}; use shuttle_runtime::Service; use tracing_subscriber::layer::SubscriberExt; @@ -25,11 +24,33 @@ pub mod news { tonic::include_file_descriptor_set!("news_descriptor"); } +pub mod posts { + tonic::include_proto!("posts"); + pub(crate) const FILE_DESCRIPTOR_SET: &[u8] = + tonic::include_file_descriptor_set!("posts_descriptor"); +} + +pub mod users { + tonic::include_proto!("users"); + pub(crate) const FILE_DESCRIPTOR_SET: &[u8] = + tonic::include_file_descriptor_set!("users_descriptor"); +} + #[derive(Debug, Default)] pub struct MyNewsService { news: Arc>>, // Using a simple vector to store news items in memory } +#[derive(Debug, Default)] +pub struct MyPostService { + posts: Arc>>, +} + +#[derive(Debug, Default)] +pub struct MyUserService { + users: Arc>>, +} + impl MyNewsService { fn new() -> MyNewsService { let news = vec![ @@ -177,6 +198,230 @@ static RESOURCE: Lazy = Lazy::new(|| { ])) }); +#[tonic::async_trait] +impl posts::post_service_server::PostService for MyPostService { + async fn list_posts( + &self, + request: tonic::Request, + ) -> Result, Status> { + let filter = request.into_inner(); + let posts = self.posts.lock().unwrap(); + let filtered = if let Some(user_id) = filter.user_id { + posts.iter() + .filter(|p| p.user_id == user_id) + .cloned() + .collect() + } else { + posts.clone() + }; + Ok(Response::new(posts::PostList { posts: filtered })) + } + + async fn get_post( + &self, + request: tonic::Request, + ) -> Result, Status> { + let id = request.into_inner().id; + let posts = self.posts.lock().unwrap(); + if let Some(post) = posts.iter().find(|p| p.id == id) { + Ok(Response::new(post.clone())) + } else { + Err(Status::not_found("Post not found")) + } + } + + async fn create_post( + &self, + request: tonic::Request, + ) -> Result, Status> { + let mut post = request.into_inner(); + let mut posts = self.posts.lock().unwrap(); + post.id = posts.iter().map(|p| p.id).max().unwrap_or(0) + 1; + posts.push(post.clone()); + Ok(Response::new(posts::PostResponse { post: Some(post) })) + } + + async fn update_post( + &self, + request: tonic::Request, + ) -> Result, Status> { + let new_post = request.into_inner(); + let mut posts = self.posts.lock().unwrap(); + if let Some(post) = posts.iter_mut().find(|p| p.id == new_post.id) { + *post = new_post.clone(); + Ok(Response::new(posts::PostResponse { post: Some(new_post) })) + } else { + Err(Status::not_found("Post not found")) + } + } + + async fn delete_post( + &self, + request: tonic::Request, + ) -> Result, Status> { + let id = request.into_inner().id; + let mut posts = self.posts.lock().unwrap(); + let len_before = posts.len(); + posts.retain(|p| p.id != id); + if posts.len() < len_before { + Ok(Response::new(posts::DeleteResponse {})) + } else { + Err(Status::not_found("Post not found")) + } + } +} + +#[tonic::async_trait] +impl users::user_service_server::UserService for MyUserService { + async fn list_users( + &self, + request: tonic::Request, + ) -> Result, Status> { + let filter = request.into_inner(); + let users = self.users.lock().unwrap(); + + let filtered = if !filter.ids.is_empty() { + users.iter() + .filter(|u| filter.ids.contains(&u.id)) + .cloned() + .collect() + } else { + users.clone() + }; + + Ok(Response::new(users::UserList { users: filtered })) + } + + async fn get_user( + &self, + request: tonic::Request, + ) -> Result, Status> { + let id = request.into_inner().id; + let users = self.users.lock().unwrap(); + + if let Some(user) = users.iter().find(|u| u.id == id) { + Ok(Response::new(user.clone())) + } else { + Err(Status::not_found("User not found")) + } + } + + async fn create_user( + &self, + request: tonic::Request, + ) -> Result, Status> { + let mut user = request.into_inner(); + let mut users = self.users.lock().unwrap(); + + user.id = users.iter().map(|u| u.id).max().unwrap_or(0) + 1; + users.push(user.clone()); + + Ok(Response::new(users::UserResponse { user: Some(user) })) + } + + async fn patch_user( + &self, + request: tonic::Request, + ) -> Result, Status> { + let patch_request = request.into_inner(); + let user_id = patch_request.id; + let new_user_data = patch_request.user.ok_or_else(|| { + Status::invalid_argument("User data must be provided for patch operation") + })?; + + let mut users = self.users.lock().unwrap(); + + if let Some(user) = users.iter_mut().find(|u| u.id == user_id) { + if !new_user_data.name.is_empty() { + user.name = new_user_data.name; + } + if !new_user_data.username.is_empty() { + user.username = new_user_data.username; + } + if !new_user_data.email.is_empty() { + user.email = new_user_data.email; + } + if !new_user_data.phone.is_empty() { + user.phone = new_user_data.phone; + } + if !new_user_data.website.is_empty() { + user.website = new_user_data.website; + } + + if let Some(new_address) = new_user_data.address { + if user.address.is_none() { + user.address = Some(new_address); + } else if let Some(ref mut address) = user.address { + if !new_address.street.is_empty() { + address.street = new_address.street; + } + if !new_address.suite.is_empty() { + address.suite = new_address.suite; + } + if !new_address.city.is_empty() { + address.city = new_address.city; + } + if !new_address.zipcode.is_empty() { + address.zipcode = new_address.zipcode; + } + // Update geo location if provided + if let Some(new_geo) = new_address.geo { + if let Some(ref mut geo) = address.geo { + if !new_geo.lat.is_empty() { + geo.lat = new_geo.lat; + } + if !new_geo.lng.is_empty() { + geo.lng = new_geo.lng; + } + } else { + address.geo = Some(new_geo); + } + } + } + } + + if let Some(new_company) = new_user_data.company { + if user.company.is_none() { + user.company = Some(new_company); + } else if let Some(ref mut company) = user.company { + if !new_company.name.is_empty() { + company.name = new_company.name; + } + if !new_company.catch_phrase.is_empty() { + company.catch_phrase = new_company.catch_phrase; + } + if !new_company.bs.is_empty() { + company.bs = new_company.bs; + } + } + } + + Ok(Response::new(users::UserResponse { + user: Some(user.clone()), + })) + } else { + Err(Status::not_found("User not found")) + } + } + + async fn delete_user( + &self, + request: tonic::Request, + ) -> Result, Status> { + let id = request.into_inner().id; + let mut users = self.users.lock().unwrap(); + let len_before = users.len(); + + users.retain(|u| u.id != id); + + if users.len() < len_before { + Ok(Response::new(users::DeleteResponse {})) + } else { + Err(Status::not_found("User not found")) + } + } +} + fn init_tracer() -> Result<()> { global::set_text_map_propagator(TraceContextPropagator::new()); @@ -216,32 +461,49 @@ fn init_tracer() -> Result<()> { Ok(()) } +#[derive(Debug)] +pub struct CompositeService { + news_service: MyNewsService, + post_service: MyPostService, + user_service: MyUserService, +} + #[shuttle_runtime::main] async fn shuttle_main() -> Result { if std::env::var("HONEYCOMB_API_KEY").is_ok() { init_tracer()?; } - let news_service = MyNewsService::new(); + + let composite_service = CompositeService { + news_service: MyNewsService::new(), + post_service: MyPostService::default(), + user_service: MyUserService::default(), + }; - Ok(news_service) + Ok(composite_service) } #[async_trait::async_trait] -impl Service for MyNewsService { - async fn bind(mut self, addr: std::net::SocketAddr) -> Result<(), shuttle_runtime::Error> { - let service = tonic_reflection::server::Builder::configure() +impl Service for CompositeService { + async fn bind(self, addr: std::net::SocketAddr) -> Result<(), shuttle_runtime::Error> { + let reflection_service = tonic_reflection::server::Builder::configure() .register_encoded_file_descriptor_set(news::FILE_DESCRIPTOR_SET) + .register_encoded_file_descriptor_set(posts::FILE_DESCRIPTOR_SET) + .register_encoded_file_descriptor_set(users::FILE_DESCRIPTOR_SET) .build() .unwrap(); - println!("NewsService server listening on {}", addr); + println!("Server listening on {}", addr); let tonic_service = TonicServer::builder() .layer(server::OtelGrpcLayer::default()) - .add_service(NewsServiceServer::new(self)) - .add_service(service) + .add_service(news::news_service_server::NewsServiceServer::new(self.news_service)) + .add_service(posts::post_service_server::PostServiceServer::new(self.post_service)) + .add_service(users::user_service_server::UserServiceServer::new(self.user_service)) + .add_service(reflection_service) .into_service(); + let make_svc = Shared::new(tonic_service); let server = hyper::Server::bind(&addr).serve(make_svc); diff --git a/users.proto b/users.proto new file mode 100644 index 0000000..74610b3 --- /dev/null +++ b/users.proto @@ -0,0 +1,63 @@ +syntax = "proto3"; +package users; + +message Address { + string street = 1; + string suite = 2; + string city = 3; + string zipcode = 4; + Geo geo = 5; +} + +message Geo { + string lat = 1; + string lng = 2; +} + +message Company { + string name = 1; + string catchPhrase = 2; + string bs = 3; +} + +message User { + int32 id = 1; + string name = 2; + string username = 3; + string email = 4; + Address address = 5; + string phone = 6; + string website = 7; + Company company = 8; +} + +message Filter { + repeated int32 ids = 1; +} + +message UserRequest { + int32 id = 1; +} + +message UserList { + repeated User users = 1; +} + +message UserResponse { + User user = 1; +} + +message PatchUserRequest { + int32 id = 1; + User user = 2; +} + +message DeleteResponse {} + +service UserService { + rpc ListUsers(Filter) returns (UserList); + rpc GetUser(UserRequest) returns (User); + rpc CreateUser(User) returns (UserResponse); + rpc PatchUser(PatchUserRequest) returns (UserResponse); + rpc DeleteUser(UserRequest) returns (DeleteResponse); +} \ No newline at end of file From 09538756f7ac3726edeb09c87e3ab0e186386ad6 Mon Sep 17 00:00:00 2001 From: utnim2 Date: Fri, 29 Nov 2024 22:27:14 +0530 Subject: [PATCH 2/4] cleanup --- build.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/build.rs b/build.rs index 0780165..2d66204 100644 --- a/build.rs +++ b/build.rs @@ -1,10 +1,9 @@ use std::path::PathBuf; fn main() { - // Helper function to configure tonic_build with common settings fn configure_tonic() -> tonic_build::Builder { tonic_build::configure() - .protoc_arg("--experimental_allow_proto3_optional") // Enable proto3 optional fields + .protoc_arg("--experimental_allow_proto3_optional") .build_server(true) .build_client(true) } @@ -33,9 +32,4 @@ fn main() { .file_descriptor_set_path(out_dir.join("users_descriptor.bin")) .compile(&["users.proto"], &["."]) .expect("Failed to compile users.proto"); - - // Set up cargo rerun-if-changed directives - println!("cargo:rerun-if-changed=news.proto"); - println!("cargo:rerun-if-changed=posts.proto"); - println!("cargo:rerun-if-changed=users.proto"); } \ No newline at end of file From 2650b02655487cfc6c2009a7a78bfe6fdce2ace2 Mon Sep 17 00:00:00 2001 From: utnim2 Date: Sat, 30 Nov 2024 00:55:04 +0530 Subject: [PATCH 3/4] formatting cleanup --- build.rs | 2 +- src/main.rs | 43 ++++++++++++++++++++++++++----------------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/build.rs b/build.rs index 2d66204..16762ad 100644 --- a/build.rs +++ b/build.rs @@ -32,4 +32,4 @@ fn main() { .file_descriptor_set_path(out_dir.join("users_descriptor.bin")) .compile(&["users.proto"], &["."]) .expect("Failed to compile users.proto"); -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index bf563ca..d893e71 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,13 +26,13 @@ pub mod news { pub mod posts { tonic::include_proto!("posts"); - pub(crate) const FILE_DESCRIPTOR_SET: &[u8] = + pub(crate) const FILE_DESCRIPTOR_SET: &[u8] = tonic::include_file_descriptor_set!("posts_descriptor"); } pub mod users { tonic::include_proto!("users"); - pub(crate) const FILE_DESCRIPTOR_SET: &[u8] = + pub(crate) const FILE_DESCRIPTOR_SET: &[u8] = tonic::include_file_descriptor_set!("users_descriptor"); } @@ -207,7 +207,8 @@ impl posts::post_service_server::PostService for MyPostService { let filter = request.into_inner(); let posts = self.posts.lock().unwrap(); let filtered = if let Some(user_id) = filter.user_id { - posts.iter() + posts + .iter() .filter(|p| p.user_id == user_id) .cloned() .collect() @@ -249,7 +250,9 @@ impl posts::post_service_server::PostService for MyPostService { let mut posts = self.posts.lock().unwrap(); if let Some(post) = posts.iter_mut().find(|p| p.id == new_post.id) { *post = new_post.clone(); - Ok(Response::new(posts::PostResponse { post: Some(new_post) })) + Ok(Response::new(posts::PostResponse { + post: Some(new_post), + })) } else { Err(Status::not_found("Post not found")) } @@ -281,14 +284,15 @@ impl users::user_service_server::UserService for MyUserService { let users = self.users.lock().unwrap(); let filtered = if !filter.ids.is_empty() { - users.iter() + users + .iter() .filter(|u| filter.ids.contains(&u.id)) .cloned() .collect() } else { users.clone() }; - + Ok(Response::new(users::UserList { users: filtered })) } @@ -298,7 +302,7 @@ impl users::user_service_server::UserService for MyUserService { ) -> Result, Status> { let id = request.into_inner().id; let users = self.users.lock().unwrap(); - + if let Some(user) = users.iter().find(|u| u.id == id) { Ok(Response::new(user.clone())) } else { @@ -312,10 +316,10 @@ impl users::user_service_server::UserService for MyUserService { ) -> Result, Status> { let mut user = request.into_inner(); let mut users = self.users.lock().unwrap(); - + user.id = users.iter().map(|u| u.id).max().unwrap_or(0) + 1; users.push(user.clone()); - + Ok(Response::new(users::UserResponse { user: Some(user) })) } @@ -330,7 +334,7 @@ impl users::user_service_server::UserService for MyUserService { })?; let mut users = self.users.lock().unwrap(); - + if let Some(user) = users.iter_mut().find(|u| u.id == user_id) { if !new_user_data.name.is_empty() { user.name = new_user_data.name; @@ -347,7 +351,7 @@ impl users::user_service_server::UserService for MyUserService { if !new_user_data.website.is_empty() { user.website = new_user_data.website; } - + if let Some(new_address) = new_user_data.address { if user.address.is_none() { user.address = Some(new_address); @@ -411,9 +415,9 @@ impl users::user_service_server::UserService for MyUserService { let id = request.into_inner().id; let mut users = self.users.lock().unwrap(); let len_before = users.len(); - + users.retain(|u| u.id != id); - + if users.len() < len_before { Ok(Response::new(users::DeleteResponse {})) } else { @@ -474,7 +478,6 @@ async fn shuttle_main() -> Result { init_tracer()?; } - let composite_service = CompositeService { news_service: MyNewsService::new(), post_service: MyPostService::default(), @@ -498,9 +501,15 @@ impl Service for CompositeService { let tonic_service = TonicServer::builder() .layer(server::OtelGrpcLayer::default()) - .add_service(news::news_service_server::NewsServiceServer::new(self.news_service)) - .add_service(posts::post_service_server::PostServiceServer::new(self.post_service)) - .add_service(users::user_service_server::UserServiceServer::new(self.user_service)) + .add_service(news::news_service_server::NewsServiceServer::new( + self.news_service, + )) + .add_service(posts::post_service_server::PostServiceServer::new( + self.post_service, + )) + .add_service(users::user_service_server::UserServiceServer::new( + self.user_service, + )) .add_service(reflection_service) .into_service(); From c644fe5295bdd7d4e351d5c0370aa089ae11cff2 Mon Sep 17 00:00:00 2001 From: utnim2 Date: Sat, 7 Dec 2024 15:54:19 +0530 Subject: [PATCH 4/4] added more filter methods --- posts.proto | 10 +++- src/main.rs | 142 ++++++++++++++++++++++++++++++++++++++++++++-------- users.proto | 13 +++-- 3 files changed, 138 insertions(+), 27 deletions(-) diff --git a/posts.proto b/posts.proto index 1111bea..7a18c39 100644 --- a/posts.proto +++ b/posts.proto @@ -9,7 +9,10 @@ message Post { } message Filter { - optional int32 userId = 1; + repeated int32 ids = 1; + optional int32 userId = 2; + optional int32 start = 3; + optional int32 limit = 4; } message PostRequest { @@ -24,7 +27,10 @@ message PostResponse { Post post = 1; } -message DeleteResponse {} +message DeleteResponse { + bool success = 1; + string message = 2; +} service PostService { rpc ListPosts(Filter) returns (PostList); diff --git a/src/main.rs b/src/main.rs index d893e71..a263815 100644 --- a/src/main.rs +++ b/src/main.rs @@ -198,6 +198,28 @@ static RESOURCE: Lazy = Lazy::new(|| { ])) }); +impl MyPostService { + fn new() -> Self { + let posts = vec![ + posts::Post { + id: 1, + user_id: 1, + title: "First post".into(), + body: "This is the first post content".into(), + }, + posts::Post { + id: 2, + user_id: 1, + title: "Second post".into(), + body: "This is the second post content".into(), + }, + ]; + MyPostService { + posts: Arc::new(Mutex::new(posts)), + } + } +} + #[tonic::async_trait] impl posts::post_service_server::PostService for MyPostService { async fn list_posts( @@ -206,15 +228,30 @@ impl posts::post_service_server::PostService for MyPostService { ) -> Result, Status> { let filter = request.into_inner(); let posts = self.posts.lock().unwrap(); - let filtered = if let Some(user_id) = filter.user_id { - posts - .iter() + let mut filtered = posts.clone(); + + if !filter.ids.is_empty() { + filtered = filtered + .into_iter() + .filter(|p| filter.ids.contains(&p.id)) + .collect(); + } + + if let Some(user_id) = filter.user_id { + filtered = filtered + .into_iter() .filter(|p| p.user_id == user_id) - .cloned() - .collect() - } else { - posts.clone() - }; + .collect(); + } + + if let (Some(start), Some(limit)) = (filter.start, filter.limit) { + filtered = filtered + .into_iter() + .skip(start as usize) + .take(limit as usize) + .collect(); + } + Ok(Response::new(posts::PostList { posts: filtered })) } @@ -267,9 +304,46 @@ impl posts::post_service_server::PostService for MyPostService { let len_before = posts.len(); posts.retain(|p| p.id != id); if posts.len() < len_before { - Ok(Response::new(posts::DeleteResponse {})) + Ok(Response::new(posts::DeleteResponse { + success: true, + message: format!("Post {} successfully deleted", id), + })) } else { - Err(Status::not_found("Post not found")) + Ok(Response::new(posts::DeleteResponse { + success: false, + message: format!("Post {} not found", id), + })) + } + } +} + +impl MyUserService { + fn new() -> Self { + let users = vec![users::User { + id: 1, + name: "Leanne Graham".to_string(), + username: "Bret".to_string(), + email: "Sincere@april.biz".to_string(), + address: Some(users::Address { + street: "Kulas Light".to_string(), + suite: "Apt. 556".to_string(), + city: "Gwenborough".to_string(), + zipcode: "92998-3874".to_string(), + geo: Some(users::Geo { + lat: "-37.3159".to_string(), + lng: "81.1496".to_string(), + }), + }), + phone: "1-770-736-8031 x56442".to_string(), + website: "hildegard.org".to_string(), + company: Some(users::Company { + name: "Romaguera-Crona".to_string(), + catch_phrase: "Multi-layered client-server neural-net".to_string(), + bs: "harness real-time e-markets".to_string(), + }), + }]; + MyUserService { + users: Arc::new(Mutex::new(users)), } } } @@ -283,15 +357,33 @@ impl users::user_service_server::UserService for MyUserService { let filter = request.into_inner(); let users = self.users.lock().unwrap(); - let filtered = if !filter.ids.is_empty() { - users - .iter() + let mut filtered = users.clone(); + + if !filter.ids.is_empty() { + filtered = filtered + .into_iter() .filter(|u| filter.ids.contains(&u.id)) - .cloned() - .collect() - } else { - users.clone() - }; + .collect(); + } + + if let Some(username) = filter.username { + filtered = filtered + .into_iter() + .filter(|u| u.username == username) + .collect(); + } + + if let Some(email) = filter.email { + filtered = filtered.into_iter().filter(|u| u.email == email).collect(); + } + + if let (Some(start), Some(limit)) = (filter.start, filter.limit) { + filtered = filtered + .into_iter() + .skip(start as usize) + .take(limit as usize) + .collect(); + } Ok(Response::new(users::UserList { users: filtered })) } @@ -419,9 +511,15 @@ impl users::user_service_server::UserService for MyUserService { users.retain(|u| u.id != id); if users.len() < len_before { - Ok(Response::new(users::DeleteResponse {})) + Ok(Response::new(users::DeleteResponse { + success: true, + message: format!("User {} successfully deleted", id), + })) } else { - Err(Status::not_found("User not found")) + Ok(Response::new(users::DeleteResponse { + success: false, + message: format!("User {} not found", id), + })) } } } @@ -480,8 +578,8 @@ async fn shuttle_main() -> Result { let composite_service = CompositeService { news_service: MyNewsService::new(), - post_service: MyPostService::default(), - user_service: MyUserService::default(), + post_service: MyPostService::new(), + user_service: MyUserService::new(), }; Ok(composite_service) diff --git a/users.proto b/users.proto index 74610b3..868cd4a 100644 --- a/users.proto +++ b/users.proto @@ -25,14 +25,18 @@ message User { string name = 2; string username = 3; string email = 4; - Address address = 5; + optional Address address = 5; string phone = 6; string website = 7; - Company company = 8; + optional Company company = 8; } message Filter { repeated int32 ids = 1; + optional string username = 2; + optional string email = 3; + optional int32 start = 4; + optional int32 limit = 5; } message UserRequest { @@ -52,7 +56,10 @@ message PatchUserRequest { User user = 2; } -message DeleteResponse {} +message DeleteResponse { + bool success = 1; + string message = 2; +} service UserService { rpc ListUsers(Filter) returns (UserList);