Skip to content

Commit

Permalink
fix: put repository method (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
kentSarmiento authored Dec 20, 2023
1 parent 03d2ea5 commit 786f6cf
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 178 deletions.
23 changes: 6 additions & 17 deletions link-for-later/src/controller/links.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ use axum::{
routing, Json, Router,
};

use crate::types::{
request::PostLink as PostLinkRequest, request::PutLink as PutLinkRequest, AppState,
};
use crate::types::{links::LinkItem, request::PostLink as PostLinkRequest, AppState};

const LINKS_ROUTE: &str = "/v1/links";
const LINKS_ID_ROUTE: &str = "/v1/links/:id";
Expand Down Expand Up @@ -62,11 +60,11 @@ async fn get(State(app_state): State<AppState>, Path(id): Path<String>) -> impl
async fn put(
State(app_state): State<AppState>,
Path(id): Path<String>,
Json(payload): extract::Json<PutLinkRequest>,
Json(payload): extract::Json<LinkItem>,
) -> impl IntoResponse {
match app_state
.get_links_service()
.put(&app_state, &id, &payload.into())
.put(&app_state, &id, &payload)
.await
{
Ok(link) => Json(link).into_response(),
Expand Down Expand Up @@ -188,13 +186,8 @@ mod tests {
let body = std::str::from_utf8(&body).unwrap();
let body: Value = serde_json::from_str(body).unwrap();

assert!(body["id"] != "");
assert!(body["owner"] == "1");
assert!(body["url"] == "http://link");
assert!(body["title"] == "");
assert!(body["description"] == "");
assert!(body["created_at"] != "");
assert!(body["updated_at"] == "");
}

#[tokio::test]
Expand Down Expand Up @@ -268,8 +261,8 @@ mod tests {
async fn test_put_links() {
let mut mock_links_service = MockService::new();
let mock_links_repo = MockRepository::new();
let request = PutLinkRequest::new("1", "http://link");
let item: LinkItem = request.clone().into();
let request = LinkItem::new("1", "http://link");
let item: LinkItem = request.clone();

mock_links_service
.expect_put()
Expand All @@ -289,17 +282,13 @@ mod tests {

assert!(body["owner"] == "1");
assert!(body["url"] == "http://link");
assert!(body["title"] == "");
assert!(body["description"] == "");
assert!(body["created_at"] == "");
assert!(body["updated_at"] != "");
}

#[tokio::test]
async fn test_put_links_service_error() {
let mut mock_links_service = MockService::new();
let mock_links_repo = MockRepository::new();
let request = PutLinkRequest::new("1", "http://link");
let request = LinkItem::new("1", "http://link");

mock_links_service
.expect_put()
Expand Down
18 changes: 17 additions & 1 deletion link-for-later/src/repository/mongodb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::str::FromStr;

use axum::async_trait;
use bson::{doc, Bson};
use mongodb::{Collection, Database};
use mongodb::{options::ReplaceOptions, Collection, Database};

use crate::types::{links::LinkItem, repository::Links, AppError, Result};

Expand Down Expand Up @@ -58,6 +58,22 @@ impl Links for MongoDbRepository {
}
}

async fn put(&self, id: &str, item: &LinkItem) -> Result<LinkItem> {
let Ok(id) = bson::oid::ObjectId::from_str(id) else {
tracing::error!("Error: {id} cannot be converted to Bson ObjectId");
return Err(AppError::ItemNotFound);
};
let filter = doc! {"_id": id};
let opts = ReplaceOptions::builder().upsert(true).build();
match self.collection.replace_one(filter, item, Some(opts)).await {
Ok(_) => Ok(item.clone()),
Err(e) => {
tracing::error!("Error: replace_one(): {e:?}");
Err(AppError::DatabaseError)
}
}
}

async fn delete(&self, id: &str) -> Result<()> {
let Ok(id) = bson::oid::ObjectId::from_str(id) else {
tracing::error!("Error: {id} cannot be converted to Bson ObjectId");
Expand Down
62 changes: 17 additions & 45 deletions link-for-later/src/service/links.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use axum::async_trait;
use chrono::Utc;

use crate::types::{links::LinkItem, service::Links, AppState, Result};

Expand All @@ -12,8 +13,11 @@ impl Links for Service {
}

async fn post<'a>(&self, app_state: &'a AppState, link_item: &LinkItem) -> Result<LinkItem> {
let now = Utc::now().to_rfc3339();
let created_link_item = link_item.created_at(&now).updated_at(&now);

let links_repo = app_state.get_links_repo();
links_repo.post(link_item).await
links_repo.post(&created_link_item).await
}

async fn get<'a>(&self, app_state: &'a AppState, id: &str) -> Result<LinkItem> {
Expand All @@ -27,12 +31,11 @@ impl Links for Service {
id: &str,
link_item: &LinkItem,
) -> Result<LinkItem> {
let links_repo = app_state.get_links_repo();

let repo_item = links_repo.get(id).await?;
let repo_item = repo_item.merge(link_item);
let now = Utc::now().to_rfc3339();
let updated_link_item = link_item.updated_at(&now);

links_repo.post(&repo_item).await
let links_repo = app_state.get_links_repo();
links_repo.put(id, &updated_link_item).await
}

async fn delete<'a>(&self, app_state: &'a AppState, id: &str) -> Result<()> {
Expand Down Expand Up @@ -200,19 +203,14 @@ mod tests {
let mock_links_service = MockService::new();
let mut mock_links_repo = MockRepository::new();

let get_item = LinkItem::new("1", "http://link");
let post_item = get_item.clone();
let request_item = get_item.clone();
let response_item = get_item.clone();
let put_item = LinkItem::new("1", "http://link");
let request_item = put_item.clone();
let response_item = put_item.clone();

mock_links_repo
.expect_get()
.expect_put()
.times(1)
.returning(move |_| Ok(get_item.clone()));
mock_links_repo
.expect_post()
.times(1)
.returning(move |_| Ok(post_item.clone()));
.returning(move |_, _| Ok(put_item.clone()));

let app_state = AppState::new(Arc::new(mock_links_service), Arc::new(mock_links_repo));

Expand All @@ -223,42 +221,16 @@ mod tests {
assert_eq!(response.unwrap(), response_item);
}

#[tokio::test]
async fn test_put_links_not_found() {
let mock_links_service = MockService::new();
let mut mock_links_repo = MockRepository::new();
let request_item = LinkItem::new("1", "http://link");

mock_links_repo
.expect_get()
.times(1)
.returning(|_| Err(AppError::ItemNotFound));
mock_links_repo.expect_post().times(0);

let app_state = AppState::new(Arc::new(mock_links_service), Arc::new(mock_links_repo));

let links_service = Service {};
let response = links_service.put(&app_state, "1111", &request_item).await;

assert!(response.is_err());
}

#[tokio::test]
async fn test_put_links_repo_error() {
let mock_links_service = MockService::new();
let mut mock_links_repo = MockRepository::new();

let item = LinkItem::new("1", "http://link");
let request_item = item.clone();
let request_item = LinkItem::new("1", "http://link");

mock_links_repo
.expect_get()
.expect_put()
.times(1)
.returning(move |_| Ok(item.clone()));
mock_links_repo
.expect_post()
.times(1)
.returning(|_| Err(AppError::TestError));
.returning(|_, _| Err(AppError::TestError));

let app_state = AppState::new(Arc::new(mock_links_service), Arc::new(mock_links_repo));

Expand Down
105 changes: 10 additions & 95 deletions link-for-later/src/types/links.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use chrono::Utc;
use serde::{Deserialize, Serialize};

use super::request::{PostLink, PutLink};
use super::request::PostLink;

#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct LinkItem {
Expand All @@ -19,77 +18,30 @@ impl From<PostLink> for LinkItem {
Self {
owner: post_link.owner,
url: post_link.url,
created_at: Utc::now().to_rfc3339(),
..Default::default()
}
}
}

impl From<PutLink> for LinkItem {
fn from(put_link: PutLink) -> Self {
impl LinkItem {
pub fn id(&self, id: &str) -> Self {
Self {
owner: put_link.owner,
url: put_link.url,
title: put_link.title,
description: put_link.description,
updated_at: Utc::now().to_rfc3339(),
..Default::default()
id: Some(id.to_string()),
..self.clone()
}
}
}

impl LinkItem {
pub fn id(&self, id: &str) -> Self {
pub fn created_at(&self, created_at: &str) -> Self {
Self {
id: Some(id.to_string()),
created_at: created_at.to_string(),
..self.clone()
}
}

pub fn merge(&self, other: &Self) -> Self {
let id = if other.id.is_some() {
other.id.clone()
} else {
self.id.clone()
};
let owner = if other.owner.is_empty() {
self.owner.clone()
} else {
other.owner.clone()
};
let url = if other.url.is_empty() {
self.url.clone()
} else {
other.url.clone()
};
let title = if other.title.is_empty() {
self.title.clone()
} else {
other.title.clone()
};
let description = if other.description.is_empty() {
self.description.clone()
} else {
other.description.clone()
};
let created_at = if other.created_at.is_empty() {
self.created_at.clone()
} else {
other.created_at.clone()
};
let updated_at = if other.updated_at.is_empty() {
self.updated_at.clone()
} else {
other.updated_at.clone()
};
pub fn updated_at(&self, updated_at: &str) -> Self {
Self {
id,
owner,
url,
title,
description,
created_at,
updated_at,
updated_at: updated_at.to_string(),
..self.clone()
}
}
}
Expand All @@ -104,40 +56,3 @@ impl LinkItem {
}
}
}

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

#[test]
fn test_merge_link_item() {
let link_item_1 = LinkItem {
id: Some("1111".to_string()),
owner: "1".to_string(),
url: "http://url".to_string(),
created_at: "1/Jan/2020:19:03:58 +0000".to_string(),
..Default::default()
};
let link_item_2 = LinkItem {
title: "Sample".to_string(),
description: "Sample url".to_string(),
updated_at: "1/Jan/2021:19:03:58 +0000".to_string(),
..Default::default()
};
let expected_link_item = LinkItem {
id: Some("1111".to_string()),
owner: "1".to_string(),
url: "http://url".to_string(),
title: "Sample".to_string(),
description: "Sample url".to_string(),
created_at: "1/Jan/2020:19:03:58 +0000".to_string(),
updated_at: "1/Jan/2021:19:03:58 +0000".to_string(),
};

let merged_link_item_1 = link_item_1.merge(&link_item_2);
assert_eq!(merged_link_item_1, expected_link_item);

let merged_link_item_2 = link_item_2.merge(&link_item_1);
assert_eq!(merged_link_item_2, expected_link_item);
}
}
2 changes: 1 addition & 1 deletion link-for-later/src/types/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub trait Links {
Err(AppError::NotSupported)
}

async fn put(&self, _id: &str) -> Result<LinkItem> {
async fn put(&self, _id: &str, _item: &LinkItem) -> Result<LinkItem> {
Err(AppError::NotSupported)
}

Expand Down
19 changes: 0 additions & 19 deletions link-for-later/src/types/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@ pub struct PostLink {
pub url: String,
}

#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct PutLink {
pub owner: String,
pub url: String,
pub title: String,
pub description: String,
}

#[cfg(test)]
impl PostLink {
pub fn new(owner: &str, url: &str) -> Self {
Expand All @@ -23,14 +15,3 @@ impl PostLink {
}
}
}

#[cfg(test)]
impl PutLink {
pub fn new(owner: &str, url: &str) -> Self {
Self {
owner: owner.to_string(),
url: url.to_string(),
..Default::default()
}
}
}

0 comments on commit 786f6cf

Please sign in to comment.