From e7c7d9f2703b800275a6c5a9ad4c3eff46e35bfd Mon Sep 17 00:00:00 2001 From: Jeff Baskin Date: Sat, 3 May 2025 08:40:51 -0400 Subject: [PATCH] Prevent overwriting existing documents. --- src/document.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++--- src/field.rs | 6 ++++-- src/lib.rs | 5 ++++- src/main.rs | 32 ++++++++++++++++++++++++++--- 4 files changed, 87 insertions(+), 9 deletions(-) diff --git a/src/document.rs b/src/document.rs index 120726b..84c2b28 100644 --- a/src/document.rs +++ b/src/document.rs @@ -52,6 +52,15 @@ impl Document { fn add(&mut self, msg: Message) { let name = msg.get_data("name").unwrap().to_string(); + match self.data.get(&name) { + Some(_) => { + let mut reply = msg.reply(MsgType::Error); + reply.add_data("error_type", ErrorType::DocumentAlreadyExists); + self.queue.send(reply).unwrap(); + return; + }, + None => {}, + } let doc: Value = serde_json::from_str(&msg.get_data("doc").unwrap().to_string()).unwrap(); self.data .insert(name, doc["template"].as_str().unwrap().to_string()); @@ -63,7 +72,7 @@ impl Document { Some(doc) => doc.to_string(), None => "root".to_string(), }; - let mut reply = match self.data.get(&name) { + let reply = match self.data.get(&name) { Some(data) => { let mut holder = msg.reply(MsgType::Document); holder.add_data("doc", data.clone()); @@ -140,10 +149,9 @@ pub mod documents { #[test] fn root_always_exists() { let (queue, rx) = setup_document(); - let name = format!("name-{}", Uuid::new_v4()); let mut msg = Message::new(MsgType::DocumentRequest); msg.add_data("name", "root"); - queue.send(msg); + queue.send(msg).unwrap(); let reply = rx.recv_timeout(TIMEOUT).unwrap(); match reply.get_msg_type() { MsgType::Document => {} @@ -191,4 +199,43 @@ pub mod documents { } assert_eq!(reply2.get_data("doc").unwrap().to_string(), content); } + + #[test] + fn add_does_not_overwrite_existing() { + let (queue, rx) = setup_document(); + let mut holder = Message::new(MsgType::DocumentRequest); + holder.add_data("name", "root"); + holder.add_data("action", ActionType::Get); + queue.send(holder.clone()).unwrap(); + let binding = rx.recv_timeout(TIMEOUT).unwrap(); + let expected = binding.get_data("doc").unwrap(); + let input = json!({ + "template": format!("content-{}", Uuid::new_v4()) + }); + let mut msg = Message::new(MsgType::DocumentRequest); + msg.add_data("name", "root"); + msg.add_data("action", ActionType::Add); + msg.add_data("doc", input.to_string()); + queue.send(msg.clone()).unwrap(); + let reply = rx.recv_timeout(TIMEOUT).unwrap(); + assert_eq!(reply.get_id(), msg.get_id()); + match reply.get_msg_type() { + MsgType::Error=> {}, + _ => unreachable!( + "got '{:?}': should have received document", + reply.get_msg_type() + ), + } + match reply.get_data("error_type") { + Some(err) => match err.to_error_type().unwrap() { + ErrorType::DocumentAlreadyExists => {} + _ => unreachable!("got {:?}: should have been document not found'", err), + }, + None => unreachable!("should contain error type"), + } + queue.send(holder).unwrap(); + let binding = rx.recv_timeout(TIMEOUT).unwrap(); + let result = binding.get_data("doc").unwrap(); + assert_eq!(result.to_string(), expected.to_string()); + } } diff --git a/src/field.rs b/src/field.rs index 4b5a662..d991781 100644 --- a/src/field.rs +++ b/src/field.rs @@ -233,7 +233,8 @@ mod fields { let field: Field = err.into(); match field { Field::ErrorType(data) => match data { - ErrorType::DocumentNotFound => {} //_ => unreachable!("got {:?}: should have been Document not found", data), + ErrorType::DocumentNotFound => {} + _ => unreachable!("got {:?}: should have been Document not found", data), }, _ => unreachable!("should have been an error type"), } @@ -251,7 +252,8 @@ mod fields { let field: Field = err.into(); let result = field.to_error_type().unwrap(); match result { - ErrorType::DocumentNotFound => {} //_ => unreachable!("got {:?}: should have been document not found", result), + ErrorType::DocumentNotFound => {} + _ => unreachable!("got {:?}: should have been document not found", result), } } diff --git a/src/lib.rs b/src/lib.rs index ec6594e..590f1da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,7 @@ pub enum ActionType { #[derive(Clone, Debug)] pub enum ErrorType { + DocumentAlreadyExists, DocumentNotFound, } @@ -74,7 +75,8 @@ mod mtt_replies { let reply = MTTReply::new(msg); match reply.get_error() { Some(err) => match err { - ErrorType::DocumentNotFound => {} + ErrorType::DocumentNotFound => {}, + _ => unreachable!("got {:?}: should have been document not found", err), }, None => unreachable!("should return an error type"), } @@ -96,6 +98,7 @@ mod mtt_replies { match reply.get_error() { Some(err) => match err { ErrorType::DocumentNotFound => {} + _ => unreachable!("got {:?}: should have been document not found", err), }, None => unreachable!("should return an error type"), } diff --git a/src/main.rs b/src/main.rs index 1f37996..e665e05 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use axum::{ RequestPartsExt, Router, }; use clap::Parser; -use morethantext::{ActionType, MoreThanText}; +use morethantext::{ActionType, ErrorType, MoreThanText}; use std::{collections::HashMap, convert::Infallible}; use tokio::{spawn, sync::mpsc::channel}; use tower_cookies::{Cookie, CookieManagerLayer, Cookies}; @@ -104,7 +104,10 @@ async fn mtt_conn( }); let reply = rx.recv().await.unwrap(); let status = match reply.get_error() { - Some(_) => StatusCode::NOT_FOUND, + Some(err) => match err { + ErrorType::DocumentAlreadyExists => StatusCode::CONFLICT, + ErrorType::DocumentNotFound => StatusCode::NOT_FOUND, + }, None => StatusCode::OK, }; (status, reply.get_document()) @@ -122,7 +125,6 @@ mod servers { }; use http_body_util::BodyExt; use serde_json::json; - use std::time::Duration; use tower::ServiceExt; #[tokio::test] @@ -235,4 +237,28 @@ mod servers { let body = response.into_body().collect().await.unwrap().to_bytes(); assert_eq!(body, content); } + + #[tokio::test] + async fn cannot_add_duplicate_document_names() { + let app = create_app(MoreThanText::new()).await; + let document = json!({ + "template": "something completely different." + }); + let response = app + .clone() + .oneshot( + Request::builder() + .method(Method::POST) + .uri("/api/root") + .body(document.to_string()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!( + response.status(), + StatusCode::CONFLICT, + "do not allow post to existing documents" + ); + } }