Add REST endpoint for slot
This commit is contained in:
parent
82e89baeeb
commit
8f378472ea
28 changed files with 1925 additions and 28 deletions
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||
[dependencies]
|
||||
async-trait = "0.1.80"
|
||||
mockall = "0.12.1"
|
||||
tokio = "1.37.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
|
@ -15,6 +16,14 @@ path = "../service"
|
|||
[dependencies.dao]
|
||||
path = "../dao"
|
||||
|
||||
[dependencies.time]
|
||||
version = "0.3.36"
|
||||
features = ["std"]
|
||||
|
||||
[dependencies.uuid]
|
||||
version = "1.8.0"
|
||||
features = ["v4"]
|
||||
|
||||
[dev-dependencies.tokio]
|
||||
version = "1.37.0"
|
||||
features = ["full"]
|
||||
|
|
|
|||
16
service_impl/src/clock.rs
Normal file
16
service_impl/src/clock.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use service::clock::ClockService;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub struct ClockServiceImpl;
|
||||
impl ClockService for ClockServiceImpl {
|
||||
fn time_now(&self) -> time::Time {
|
||||
OffsetDateTime::now_utc().time()
|
||||
}
|
||||
fn date_now(&self) -> time::Date {
|
||||
OffsetDateTime::now_utc().date()
|
||||
}
|
||||
fn date_time_now(&self) -> time::PrimitiveDateTime {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
time::PrimitiveDateTime::new(now.date(), now.time())
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,11 @@ use std::sync::Arc;
|
|||
|
||||
use async_trait::async_trait;
|
||||
|
||||
mod permission;
|
||||
#[cfg(test)]
|
||||
pub mod clock;
|
||||
pub mod permission;
|
||||
pub mod slot;
|
||||
mod test;
|
||||
pub mod uuid_service;
|
||||
|
||||
pub use permission::PermissionServiceImpl;
|
||||
|
||||
|
|
|
|||
199
service_impl/src/slot.rs
Normal file
199
service_impl/src/slot.rs
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use service::{slot::Slot, ServiceError, ValidationFailureItem};
|
||||
use tokio::join;
|
||||
use uuid::Uuid;
|
||||
|
||||
const SLOT_SERVICE_PROCESS: &str = "slot-service";
|
||||
|
||||
pub struct SlotServiceImpl<SlotDao, PermissionService, ClockService, UuidService>
|
||||
where
|
||||
SlotDao: dao::slot::SlotDao + Send + Sync,
|
||||
PermissionService: service::permission::PermissionService + Send + Sync,
|
||||
ClockService: service::clock::ClockService + Send + Sync,
|
||||
UuidService: service::uuid_service::UuidService + Send + Sync,
|
||||
{
|
||||
pub slot_dao: Arc<SlotDao>,
|
||||
pub permission_service: Arc<PermissionService>,
|
||||
pub clock_service: Arc<ClockService>,
|
||||
pub uuid_service: Arc<UuidService>,
|
||||
}
|
||||
impl<SlotDao, PermissionService, ClockService, UuidService>
|
||||
SlotServiceImpl<SlotDao, PermissionService, ClockService, UuidService>
|
||||
where
|
||||
SlotDao: dao::slot::SlotDao + Send + Sync,
|
||||
PermissionService: service::permission::PermissionService + Send + Sync,
|
||||
ClockService: service::clock::ClockService + Send + Sync,
|
||||
UuidService: service::uuid_service::UuidService + Send + Sync,
|
||||
{
|
||||
pub fn new(
|
||||
slot_dao: Arc<SlotDao>,
|
||||
permission_service: Arc<PermissionService>,
|
||||
clock_service: Arc<ClockService>,
|
||||
uuid_service: Arc<UuidService>,
|
||||
) -> Self {
|
||||
Self {
|
||||
slot_dao,
|
||||
permission_service,
|
||||
clock_service,
|
||||
uuid_service,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_overlapping_slots(slot_1: &Slot, slot_2: &Slot) -> bool {
|
||||
slot_1.day_of_week == slot_2.day_of_week
|
||||
&& (slot_2.from < slot_1.from && slot_1.from < slot_2.to
|
||||
|| slot_1.from < slot_2.from && slot_2.from < slot_1.to
|
||||
|| slot_1.from == slot_2.from && slot_1.to == slot_2.to)
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<SlotDao, PermissionService, ClockService, UuidService> service::slot::SlotService
|
||||
for SlotServiceImpl<SlotDao, PermissionService, ClockService, UuidService>
|
||||
where
|
||||
SlotDao: dao::slot::SlotDao + Send + Sync,
|
||||
PermissionService: service::permission::PermissionService + Send + Sync,
|
||||
ClockService: service::clock::ClockService + Send + Sync,
|
||||
UuidService: service::uuid_service::UuidService + Send + Sync,
|
||||
{
|
||||
async fn get_slots(&self) -> Result<Arc<[Slot]>, ServiceError> {
|
||||
let (hr_permission, sales_permission) = join!(
|
||||
self.permission_service.check_permission("hr"),
|
||||
self.permission_service.check_permission("sales"),
|
||||
);
|
||||
hr_permission.or(sales_permission)?;
|
||||
|
||||
Ok(self
|
||||
.slot_dao
|
||||
.get_slots()
|
||||
.await?
|
||||
.iter()
|
||||
.map(Slot::from)
|
||||
.collect())
|
||||
}
|
||||
async fn get_slot(&self, id: &Uuid) -> Result<Slot, ServiceError> {
|
||||
let (hr_permission, sales_permission) = join!(
|
||||
self.permission_service.check_permission("hr"),
|
||||
self.permission_service.check_permission("sales"),
|
||||
);
|
||||
hr_permission.or(sales_permission)?;
|
||||
|
||||
let slot_entity = self.slot_dao.get_slot(id).await?;
|
||||
let slot = slot_entity
|
||||
.as_ref()
|
||||
.map(Slot::from)
|
||||
.ok_or_else(move || ServiceError::EntityNotFound(*id))?;
|
||||
Ok(slot)
|
||||
}
|
||||
async fn create_slot(&self, slot: &Slot) -> Result<Slot, ServiceError> {
|
||||
self.permission_service.check_permission("hr").await?;
|
||||
|
||||
if slot.id != Uuid::nil() {
|
||||
return Err(ServiceError::IdSetOnCreate);
|
||||
}
|
||||
if slot.version != Uuid::nil() {
|
||||
return Err(ServiceError::VersionSetOnCreate);
|
||||
}
|
||||
if slot.from > slot.to {
|
||||
return Err(ServiceError::TimeOrderWrong(slot.from, slot.to));
|
||||
}
|
||||
if slot.valid_to.is_some() && slot.valid_to.unwrap() < slot.valid_from {
|
||||
return Err(ServiceError::DateOrderWrong(
|
||||
slot.valid_from,
|
||||
slot.valid_to.unwrap(),
|
||||
));
|
||||
}
|
||||
|
||||
if self
|
||||
.get_slots()
|
||||
.await?
|
||||
.iter()
|
||||
.any(|s| test_overlapping_slots(slot, s))
|
||||
{
|
||||
return Err(ServiceError::OverlappingTimeRange);
|
||||
}
|
||||
|
||||
let slot = Slot {
|
||||
id: self.uuid_service.new_uuid("slot-id"),
|
||||
version: self.uuid_service.new_uuid("slot-version"),
|
||||
..slot.clone()
|
||||
};
|
||||
self.slot_dao
|
||||
.create_slot(&(&slot).into(), SLOT_SERVICE_PROCESS)
|
||||
.await?;
|
||||
Ok(slot)
|
||||
}
|
||||
|
||||
async fn delete_slot(&self, id: &Uuid) -> Result<(), ServiceError> {
|
||||
self.permission_service.check_permission("hr").await?;
|
||||
let mut slot = self
|
||||
.slot_dao
|
||||
.get_slot(id)
|
||||
.await?
|
||||
.ok_or(ServiceError::EntityNotFound(*id))?;
|
||||
slot.deleted = Some(self.clock_service.date_time_now());
|
||||
self.slot_dao
|
||||
.update_slot(&slot, SLOT_SERVICE_PROCESS)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn update_slot(&self, slot: &Slot) -> Result<(), ServiceError> {
|
||||
self.permission_service.check_permission("hr").await?;
|
||||
let persisted_slot = self
|
||||
.slot_dao
|
||||
.get_slot(&slot.id)
|
||||
.await?
|
||||
.ok_or(ServiceError::EntityNotFound(slot.id))?;
|
||||
if persisted_slot.version != slot.version {
|
||||
return Err(ServiceError::EntityConflicts(
|
||||
slot.id,
|
||||
persisted_slot.version,
|
||||
slot.version,
|
||||
));
|
||||
}
|
||||
if slot.valid_to.is_some() && slot.valid_to.unwrap() < slot.valid_from {
|
||||
return Err(ServiceError::DateOrderWrong(
|
||||
slot.valid_from,
|
||||
slot.valid_to.unwrap(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut validation = Vec::new();
|
||||
if persisted_slot.day_of_week != slot.day_of_week.into() {
|
||||
validation.push(ValidationFailureItem::ModificationNotAllowed(
|
||||
"day_of_week".into(),
|
||||
));
|
||||
}
|
||||
if persisted_slot.from != slot.from {
|
||||
validation.push(ValidationFailureItem::ModificationNotAllowed("from".into()));
|
||||
}
|
||||
if persisted_slot.to != slot.to {
|
||||
validation.push(ValidationFailureItem::ModificationNotAllowed("to".into()));
|
||||
}
|
||||
if persisted_slot.valid_from != slot.valid_from {
|
||||
validation.push(ValidationFailureItem::ModificationNotAllowed(
|
||||
"valid_from".into(),
|
||||
));
|
||||
}
|
||||
if persisted_slot.valid_to.is_some() && persisted_slot.valid_to != slot.valid_to {
|
||||
validation.push(ValidationFailureItem::ModificationNotAllowed(
|
||||
"valid_to".into(),
|
||||
));
|
||||
}
|
||||
|
||||
if !validation.is_empty() {
|
||||
return Err(ServiceError::ValidationError(validation.into()));
|
||||
}
|
||||
|
||||
let slot = Slot {
|
||||
version: self.uuid_service.new_uuid("slot-version"),
|
||||
..slot.clone()
|
||||
};
|
||||
self.slot_dao
|
||||
.update_slot(&(&slot).into(), SLOT_SERVICE_PROCESS)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1,4 @@
|
|||
#[cfg(test)]
|
||||
mod permission_test;
|
||||
#[cfg(test)]
|
||||
pub mod slot;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ fn generate_dependencies_mocks_permission(
|
|||
(permission_dao, user_service)
|
||||
}
|
||||
|
||||
fn test_forbidden<T>(result: &Result<T, service::ServiceError>) {
|
||||
pub fn test_forbidden<T>(result: &Result<T, service::ServiceError>) {
|
||||
if let Err(service::ServiceError::Forbidden) = result {
|
||||
// All good
|
||||
} else {
|
||||
|
|
|
|||
924
service_impl/src/test/slot.rs
Normal file
924
service_impl/src/test/slot.rs
Normal file
|
|
@ -0,0 +1,924 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::slot::*;
|
||||
use crate::test::permission_test::test_forbidden;
|
||||
use dao::slot::{MockSlotDao, SlotEntity};
|
||||
use mockall::predicate::eq;
|
||||
use service::{
|
||||
clock::MockClockService, slot::*, uuid_service::MockUuidService, MockPermissionService,
|
||||
ValidationFailureItem,
|
||||
};
|
||||
use time::{Date, Month, PrimitiveDateTime, Time};
|
||||
use tokio;
|
||||
use uuid::{uuid, Uuid};
|
||||
|
||||
pub fn default_id() -> Uuid {
|
||||
uuid!("682DA62E-20CB-49D9-A2A7-3F53C6842405")
|
||||
}
|
||||
pub fn default_version() -> Uuid {
|
||||
uuid!("86DE856C-D176-4F1F-A4FE-0D9844C02C03")
|
||||
}
|
||||
pub fn default_changed_version() -> Uuid {
|
||||
uuid!("4A818852-45D2-400F-A02A-755D34FFE815")
|
||||
}
|
||||
|
||||
pub fn generate_default_slot() -> Slot {
|
||||
Slot {
|
||||
id: default_id(),
|
||||
day_of_week: DayOfWeek::Monday,
|
||||
from: time::Time::from_hms(10, 0, 0).unwrap(),
|
||||
to: time::Time::from_hms(11, 0, 0).unwrap(),
|
||||
valid_from: time::Date::from_calendar_date(2022, 1.try_into().unwrap(), 1).unwrap(),
|
||||
valid_to: None,
|
||||
deleted: None,
|
||||
version: default_version(),
|
||||
}
|
||||
}
|
||||
pub fn generate_default_slot_entity() -> SlotEntity {
|
||||
SlotEntity {
|
||||
id: uuid!("682DA62E-20CB-49D9-A2A7-3F53C6842405"),
|
||||
day_of_week: dao::slot::DayOfWeek::Monday,
|
||||
from: time::Time::from_hms(10, 0, 0).unwrap(),
|
||||
to: time::Time::from_hms(11, 0, 0).unwrap(),
|
||||
valid_from: time::Date::from_calendar_date(2022, 1.try_into().unwrap(), 1).unwrap(),
|
||||
valid_to: None,
|
||||
deleted: None,
|
||||
version: uuid!("86DE856C-D176-4F1F-A4FE-0D9844C02C03"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_not_found<T>(result: &Result<T, service::ServiceError>, target_id: &Uuid) {
|
||||
if let Err(service::ServiceError::EntityNotFound(id)) = result {
|
||||
assert_eq!(
|
||||
id, target_id,
|
||||
"Expected entity {} not found but got {}",
|
||||
target_id, id
|
||||
);
|
||||
} else {
|
||||
panic!("Expected entity {} not found error", target_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_zero_id_error<T>(result: &Result<T, service::ServiceError>) {
|
||||
if let Err(service::ServiceError::IdSetOnCreate) = result {
|
||||
} else {
|
||||
panic!("Expected id set on create error");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_zero_version_error<T>(result: &Result<T, service::ServiceError>) {
|
||||
if let Err(service::ServiceError::VersionSetOnCreate) = result {
|
||||
} else {
|
||||
panic!("Expected version set on create error");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_overlapping_time_range_error<T>(result: &Result<T, service::ServiceError>) {
|
||||
if let Err(service::ServiceError::OverlappingTimeRange) = result {
|
||||
} else {
|
||||
panic!("Expected overlapping time range error");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_time_order_wrong<T>(result: &Result<T, service::ServiceError>) {
|
||||
if let Err(service::ServiceError::TimeOrderWrong(_from, _to)) = result {
|
||||
} else {
|
||||
panic!("Expected time order failure");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_date_order_wrong<T>(result: &Result<T, service::ServiceError>) {
|
||||
if let Err(service::ServiceError::DateOrderWrong(_from, _to)) = result {
|
||||
} else {
|
||||
panic!("Expected date order failure");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_conflicts<T>(
|
||||
result: &Result<T, service::ServiceError>,
|
||||
target_id: &Uuid,
|
||||
expected_version: &Uuid,
|
||||
actual_version: &Uuid,
|
||||
) {
|
||||
if let Err(service::ServiceError::EntityConflicts(
|
||||
err_id,
|
||||
err_expected_version,
|
||||
err_actual_version,
|
||||
)) = result
|
||||
{
|
||||
assert_eq!(
|
||||
err_id, target_id,
|
||||
"Expected entity {} conflicts but got {}",
|
||||
target_id, err_id
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
expected_version, err_expected_version,
|
||||
"Expected expected version {} but got {}",
|
||||
expected_version, err_expected_version
|
||||
);
|
||||
assert_eq!(
|
||||
actual_version, err_actual_version,
|
||||
"Expected actual version {} but got {}",
|
||||
actual_version, err_actual_version
|
||||
);
|
||||
} else {
|
||||
panic!("Expected entity {} conflicts error", target_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_validation_error(
|
||||
result: &Result<(), service::ServiceError>,
|
||||
validation_failure: &ValidationFailureItem,
|
||||
fail_count: usize,
|
||||
) {
|
||||
if let Err(service::ServiceError::ValidationError(validation_failure_items)) = result {
|
||||
if !validation_failure_items.contains(validation_failure) {
|
||||
panic!(
|
||||
"Validation failure not found: {:?} in {:?}",
|
||||
validation_failure, validation_failure_items
|
||||
);
|
||||
}
|
||||
assert_eq!(fail_count, validation_failure_items.len());
|
||||
} else {
|
||||
panic!("Expected validation error");
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SlotServiceDependencies {
|
||||
pub slot_dao: MockSlotDao,
|
||||
pub permission_service: MockPermissionService,
|
||||
pub clock_service: MockClockService,
|
||||
pub uuid_service: MockUuidService,
|
||||
}
|
||||
impl SlotServiceDependencies {
|
||||
pub fn build_service(
|
||||
self,
|
||||
) -> SlotServiceImpl<MockSlotDao, MockPermissionService, MockClockService, MockUuidService>
|
||||
{
|
||||
SlotServiceImpl::new(
|
||||
self.slot_dao.into(),
|
||||
self.permission_service.into(),
|
||||
self.clock_service.into(),
|
||||
self.uuid_service.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_dependencies(permission: bool, role: &'static str) -> SlotServiceDependencies {
|
||||
let slot_dao = MockSlotDao::new();
|
||||
let mut permission_service = MockPermissionService::new();
|
||||
permission_service
|
||||
.expect_check_permission()
|
||||
.with(eq(role))
|
||||
.returning(move |_| {
|
||||
if permission {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(service::ServiceError::Forbidden)
|
||||
}
|
||||
});
|
||||
permission_service
|
||||
.expect_check_permission()
|
||||
.returning(move |_| Err(service::ServiceError::Forbidden));
|
||||
let mut clock_service = MockClockService::new();
|
||||
clock_service
|
||||
.expect_time_now()
|
||||
.returning(|| time::Time::from_hms(23, 42, 0).unwrap());
|
||||
clock_service
|
||||
.expect_date_now()
|
||||
.returning(|| time::Date::from_calendar_date(2063, 4.try_into().unwrap(), 5).unwrap());
|
||||
clock_service.expect_date_time_now().returning(|| {
|
||||
time::PrimitiveDateTime::new(
|
||||
time::Date::from_calendar_date(2063, 4.try_into().unwrap(), 5).unwrap(),
|
||||
time::Time::from_hms(23, 42, 0).unwrap(),
|
||||
)
|
||||
});
|
||||
let uuid_service = MockUuidService::new();
|
||||
|
||||
SlotServiceDependencies {
|
||||
slot_dao,
|
||||
permission_service,
|
||||
clock_service,
|
||||
uuid_service,
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_slots() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies.slot_dao.expect_get_slots().returning(|| {
|
||||
Ok(Arc::new([
|
||||
SlotEntity {
|
||||
id: uuid!("DA703BC1-F488-4E4F-BA10-0972196639F7"),
|
||||
version: uuid!("FAC4FAD9-89AE-4E56-9608-03C56558B192"),
|
||||
..generate_default_slot_entity()
|
||||
},
|
||||
generate_default_slot_entity(),
|
||||
]))
|
||||
});
|
||||
|
||||
let slot_service = dependencies.build_service();
|
||||
|
||||
let result = slot_service.get_slots().await;
|
||||
assert!(result.is_ok());
|
||||
let result = result.unwrap();
|
||||
|
||||
assert_eq!(result.len(), 2);
|
||||
assert_eq!(
|
||||
result[0],
|
||||
Slot {
|
||||
id: uuid!("DA703BC1-F488-4E4F-BA10-0972196639F7"),
|
||||
version: uuid!("FAC4FAD9-89AE-4E56-9608-03C56558B192"),
|
||||
..generate_default_slot()
|
||||
},
|
||||
);
|
||||
assert_eq!(result[1], generate_default_slot(),);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_slots_sales_role() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slots()
|
||||
.returning(|| Ok(Arc::new([])));
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service.get_slots().await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_slots_no_permission() {
|
||||
let mut dependencies = build_dependencies(false, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slots()
|
||||
.returning(|| Ok(Arc::new([])));
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service.get_slots().await;
|
||||
test_forbidden(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_slot() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.times(1)
|
||||
.returning(|_| Ok(Some(generate_default_slot_entity())));
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service.get_slot(&default_id()).await;
|
||||
assert!(result.is_ok());
|
||||
let result = result.unwrap();
|
||||
assert_eq!(result, generate_default_slot());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_slot_sales_role() {
|
||||
let mut dependencies = build_dependencies(true, "sales");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.times(1)
|
||||
.returning(|_| Ok(Some(generate_default_slot_entity())));
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service.get_slot(&default_id()).await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_slot_not_found() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.times(1)
|
||||
.returning(|_| Ok(None));
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service.get_slot(&default_id()).await;
|
||||
test_not_found(&result, &default_id());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_slot_no_permission() {
|
||||
let dependencies = build_dependencies(false, "hr");
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service.get_slot(&default_id()).await;
|
||||
test_forbidden(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_slot() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_create_slot()
|
||||
.with(eq(generate_default_slot_entity()), eq("slot-service"))
|
||||
.times(1)
|
||||
.returning(|_, _| Ok(()));
|
||||
dependencies
|
||||
.uuid_service
|
||||
.expect_new_uuid()
|
||||
.with(eq("slot-id"))
|
||||
.returning(|_| default_id());
|
||||
dependencies
|
||||
.uuid_service
|
||||
.expect_new_uuid()
|
||||
.with(eq("slot-version"))
|
||||
.returning(|_| default_version());
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slots()
|
||||
.returning(|| Ok(Arc::new([])));
|
||||
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service
|
||||
.create_slot(&Slot {
|
||||
id: Uuid::nil(),
|
||||
version: Uuid::nil(),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), generate_default_slot());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_slot_no_permission() {
|
||||
let dependencies = build_dependencies(false, "hr");
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service.create_slot(&generate_default_slot()).await;
|
||||
test_forbidden(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_slot_non_zero_id() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.uuid_service
|
||||
.expect_new_uuid()
|
||||
.with(eq("slot-id"))
|
||||
.returning(|_| default_id());
|
||||
dependencies
|
||||
.uuid_service
|
||||
.expect_new_uuid()
|
||||
.with(eq("slot-version"))
|
||||
.returning(|_| default_version());
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service
|
||||
.create_slot(&Slot {
|
||||
version: Uuid::nil(),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_zero_id_error(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_slot_non_zero_version() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.uuid_service
|
||||
.expect_new_uuid()
|
||||
.with(eq("slot-id"))
|
||||
.returning(|_| default_id());
|
||||
dependencies
|
||||
.uuid_service
|
||||
.expect_new_uuid()
|
||||
.with(eq("slot-version"))
|
||||
.returning(|_| default_version());
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service
|
||||
.create_slot(&Slot {
|
||||
id: Uuid::nil(),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_zero_version_error(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_slot_intersects() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies.slot_dao.expect_get_slots().returning(|| {
|
||||
Ok(Arc::new([
|
||||
generate_default_slot_entity(),
|
||||
SlotEntity {
|
||||
id: Uuid::new_v4(),
|
||||
from: Time::from_hms(12, 0, 0).unwrap(),
|
||||
to: Time::from_hms(13, 0, 0).unwrap(),
|
||||
..generate_default_slot_entity()
|
||||
},
|
||||
SlotEntity {
|
||||
id: Uuid::new_v4(),
|
||||
day_of_week: DayOfWeek::Wednesday.into(),
|
||||
from: Time::from_hms(11, 0, 0).unwrap(),
|
||||
to: Time::from_hms(12, 0, 0).unwrap(),
|
||||
..generate_default_slot_entity()
|
||||
},
|
||||
]))
|
||||
});
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_create_slot()
|
||||
.returning(|_, _| Ok(()));
|
||||
dependencies
|
||||
.uuid_service
|
||||
.expect_new_uuid()
|
||||
.with(eq("slot-id"))
|
||||
.returning(|_| default_id());
|
||||
dependencies
|
||||
.uuid_service
|
||||
.expect_new_uuid()
|
||||
.with(eq("slot-version"))
|
||||
.returning(|_| default_version());
|
||||
let slot_service = dependencies.build_service();
|
||||
|
||||
// Test successful case, directly between two existing slots.
|
||||
let result = slot_service
|
||||
.create_slot(&Slot {
|
||||
id: Uuid::nil(),
|
||||
version: Uuid::nil(),
|
||||
from: Time::from_hms(11, 0, 0).unwrap(),
|
||||
to: Time::from_hms(12, 0, 0).unwrap(),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Test case where it is exactly on an existing slot.
|
||||
let result = slot_service
|
||||
.create_slot(&Slot {
|
||||
id: Uuid::nil(),
|
||||
version: Uuid::nil(),
|
||||
from: Time::from_hms(10, 0, 0).unwrap(),
|
||||
to: Time::from_hms(11, 0, 0).unwrap(),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_overlapping_time_range_error(&result);
|
||||
|
||||
// Test case where from is inside an existing slot.
|
||||
let result = slot_service
|
||||
.create_slot(&Slot {
|
||||
id: Uuid::nil(),
|
||||
version: Uuid::nil(),
|
||||
from: Time::from_hms(10, 30, 0).unwrap(),
|
||||
to: Time::from_hms(11, 30, 0).unwrap(),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_overlapping_time_range_error(&result);
|
||||
|
||||
// Test case where to is inside an existing slot.
|
||||
let result = slot_service
|
||||
.create_slot(&Slot {
|
||||
id: Uuid::nil(),
|
||||
version: Uuid::nil(),
|
||||
from: Time::from_hms(11, 30, 0).unwrap(),
|
||||
to: Time::from_hms(12, 30, 0).unwrap(),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_overlapping_time_range_error(&result);
|
||||
|
||||
// Test case where is completely inside an existing slot.
|
||||
let result = slot_service
|
||||
.create_slot(&Slot {
|
||||
id: Uuid::nil(),
|
||||
version: Uuid::nil(),
|
||||
from: Time::from_hms(10, 15, 0).unwrap(),
|
||||
to: Time::from_hms(10, 45, 0).unwrap(),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_overlapping_time_range_error(&result);
|
||||
|
||||
// Test case where is completely outside of an existing slot.
|
||||
let result = slot_service
|
||||
.create_slot(&Slot {
|
||||
id: Uuid::nil(),
|
||||
version: Uuid::nil(),
|
||||
from: Time::from_hms(9, 0, 0).unwrap(),
|
||||
to: Time::from_hms(11, 0, 0).unwrap(),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_overlapping_time_range_error(&result);
|
||||
|
||||
// Test case where is would intersect on monday but not on tuesday.
|
||||
// Test case where is completely outside of an existing slot.
|
||||
let result = slot_service
|
||||
.create_slot(&Slot {
|
||||
id: Uuid::nil(),
|
||||
version: Uuid::nil(),
|
||||
day_of_week: DayOfWeek::Tuesday.into(),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_slot_time_order() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_create_slot()
|
||||
.returning(|_, _| Ok(()));
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slots()
|
||||
.returning(|| Ok(Arc::new([])));
|
||||
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service
|
||||
.create_slot(&Slot {
|
||||
id: Uuid::nil(),
|
||||
version: Uuid::nil(),
|
||||
from: Time::from_hms(12, 00, 0).unwrap(),
|
||||
to: Time::from_hms(11, 00, 00).unwrap(),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_time_order_wrong(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_slot_date_order() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_create_slot()
|
||||
.returning(|_, _| Ok(()));
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slots()
|
||||
.returning(|| Ok(Arc::new([])));
|
||||
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service
|
||||
.create_slot(&Slot {
|
||||
id: Uuid::nil(),
|
||||
version: Uuid::nil(),
|
||||
valid_from: Date::from_calendar_date(2022, Month::January, 2).unwrap(),
|
||||
valid_to: Some(Date::from_calendar_date(2022, Month::January, 1).unwrap()),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_date_order_wrong(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete_slot() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.times(1)
|
||||
.returning(|_| Ok(Some(generate_default_slot_entity())));
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_update_slot()
|
||||
.with(
|
||||
eq(SlotEntity {
|
||||
deleted: Some(PrimitiveDateTime::new(
|
||||
Date::from_calendar_date(2063, time::Month::April, 5).unwrap(),
|
||||
Time::from_hms(23, 42, 0).unwrap(),
|
||||
)),
|
||||
..generate_default_slot_entity()
|
||||
}),
|
||||
eq("slot-service"),
|
||||
)
|
||||
.times(1)
|
||||
.returning(|_, _| Ok(()));
|
||||
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service.delete_slot(&default_id()).await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete_slot_no_permission() {
|
||||
let dependencies = build_dependencies(false, "hr");
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service.delete_slot(&default_id()).await;
|
||||
test_forbidden(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete_slot_not_found() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.times(1)
|
||||
.returning(|_| Ok(None));
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service.delete_slot(&default_id()).await;
|
||||
test_not_found(&result, &default_id());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_slot_no_permission() {
|
||||
let dependencies = build_dependencies(false, "hr");
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service.update_slot(&generate_default_slot()).await;
|
||||
test_forbidden(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_slot_not_found() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.times(1)
|
||||
.returning(|_| Ok(None));
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service.update_slot(&generate_default_slot()).await;
|
||||
test_not_found(&result, &default_id());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_slot_version_mismatch() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.returning(|_| Ok(Some(generate_default_slot_entity())));
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service
|
||||
.update_slot(&service::slot::Slot {
|
||||
version: uuid!("86DE856C-D176-4F1F-A4FE-0D9844C02C04"),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_conflicts(
|
||||
&result,
|
||||
&default_id(),
|
||||
&default_version(),
|
||||
&uuid!("86DE856C-D176-4F1F-A4FE-0D9844C02C04"),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_slot_valid_to() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_update_slot()
|
||||
.once()
|
||||
.with(
|
||||
eq(dao::slot::SlotEntity {
|
||||
valid_to: Some(
|
||||
time::Date::from_calendar_date(2022, 1.try_into().unwrap(), 10).unwrap(),
|
||||
),
|
||||
version: default_changed_version(),
|
||||
..generate_default_slot_entity()
|
||||
}),
|
||||
eq("slot-service"),
|
||||
)
|
||||
.times(1)
|
||||
.returning(|_, _| Ok(()));
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.returning(|_| Ok(Some(generate_default_slot_entity())));
|
||||
dependencies
|
||||
.uuid_service
|
||||
.expect_new_uuid()
|
||||
.with(eq("slot-version"))
|
||||
.returning(|_| default_changed_version());
|
||||
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service
|
||||
.update_slot(&service::slot::Slot {
|
||||
valid_to: Some(
|
||||
time::Date::from_calendar_date(2022, 1.try_into().unwrap(), 10).unwrap(),
|
||||
),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
dbg!(&result);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_slot_valid_to_before_valid_from() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.returning(|_| Ok(Some(generate_default_slot_entity())));
|
||||
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service
|
||||
.update_slot(&service::slot::Slot {
|
||||
valid_to: Some(
|
||||
time::Date::from_calendar_date(2021, 1.try_into().unwrap(), 10).unwrap(),
|
||||
),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_date_order_wrong(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_slot_deleted() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.returning(|_| Ok(Some(generate_default_slot_entity())));
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_update_slot()
|
||||
.once()
|
||||
.with(
|
||||
eq(dao::slot::SlotEntity {
|
||||
deleted: Some(time::PrimitiveDateTime::new(
|
||||
Date::from_calendar_date(2022, 1.try_into().unwrap(), 10).unwrap(),
|
||||
Time::from_hms(0, 0, 0).unwrap(),
|
||||
)),
|
||||
version: default_changed_version(),
|
||||
..generate_default_slot_entity()
|
||||
}),
|
||||
eq("slot-service"),
|
||||
)
|
||||
.returning(|_, _| Ok(()));
|
||||
dependencies
|
||||
.uuid_service
|
||||
.expect_new_uuid()
|
||||
.with(eq("slot-version"))
|
||||
.returning(|_| default_changed_version());
|
||||
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service
|
||||
.update_slot(&Slot {
|
||||
deleted: Some(time::PrimitiveDateTime::new(
|
||||
Date::from_calendar_date(2022, 1.try_into().unwrap(), 10).unwrap(),
|
||||
Time::from_hms(0, 0, 0).unwrap(),
|
||||
)),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_slot_day_of_week_forbidden() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.returning(|_| Ok(Some(generate_default_slot_entity())));
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service
|
||||
.update_slot(&service::slot::Slot {
|
||||
day_of_week: service::slot::DayOfWeek::Friday,
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_validation_error(
|
||||
&result,
|
||||
&ValidationFailureItem::ModificationNotAllowed("day_of_week".into()),
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_to_forbidden_when_not_none() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.returning(|_| {
|
||||
Ok(Some(SlotEntity {
|
||||
valid_to: Some(
|
||||
time::Date::from_calendar_date(2022, 1.try_into().unwrap(), 3).unwrap(),
|
||||
),
|
||||
..generate_default_slot_entity()
|
||||
}))
|
||||
});
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service
|
||||
.update_slot(&service::slot::Slot {
|
||||
valid_to: Some(time::Date::from_calendar_date(2022, 1.try_into().unwrap(), 4).unwrap()),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_validation_error(
|
||||
&result,
|
||||
&ValidationFailureItem::ModificationNotAllowed("valid_to".into()),
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_from_forbidden() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.returning(|_| Ok(Some(generate_default_slot_entity())));
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service
|
||||
.update_slot(&service::slot::Slot {
|
||||
from: time::Time::from_hms(14, 0, 0).unwrap(),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_validation_error(
|
||||
&result,
|
||||
&ValidationFailureItem::ModificationNotAllowed("from".into()),
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_to_forbidden() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.returning(|_| Ok(Some(generate_default_slot_entity())));
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service
|
||||
.update_slot(&service::slot::Slot {
|
||||
to: time::Time::from_hms(14, 0, 0).unwrap(),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_validation_error(
|
||||
&result,
|
||||
&ValidationFailureItem::ModificationNotAllowed("to".into()),
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_valid_from_forbidden() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.returning(|_| Ok(Some(generate_default_slot_entity())));
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service
|
||||
.update_slot(&service::slot::Slot {
|
||||
valid_from: time::Date::from_calendar_date(2022, 1.try_into().unwrap(), 10).unwrap(),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_validation_error(
|
||||
&result,
|
||||
&ValidationFailureItem::ModificationNotAllowed("valid_from".into()),
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_valid_multiple_forbidden_changes() {
|
||||
let mut dependencies = build_dependencies(true, "hr");
|
||||
dependencies
|
||||
.slot_dao
|
||||
.expect_get_slot()
|
||||
.with(eq(default_id()))
|
||||
.returning(|_| Ok(Some(generate_default_slot_entity())));
|
||||
let slot_service = dependencies.build_service();
|
||||
let result = slot_service
|
||||
.update_slot(&service::slot::Slot {
|
||||
valid_from: time::Date::from_calendar_date(2022, 1.try_into().unwrap(), 10).unwrap(),
|
||||
from: time::Time::from_hms(14, 0, 0).unwrap(),
|
||||
..generate_default_slot()
|
||||
})
|
||||
.await;
|
||||
test_validation_error(
|
||||
&result,
|
||||
&ValidationFailureItem::ModificationNotAllowed("valid_from".into()),
|
||||
2,
|
||||
);
|
||||
test_validation_error(
|
||||
&result,
|
||||
&ValidationFailureItem::ModificationNotAllowed("from".into()),
|
||||
2,
|
||||
);
|
||||
}
|
||||
9
service_impl/src/uuid_service.rs
Normal file
9
service_impl/src/uuid_service.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use uuid::Uuid;
|
||||
|
||||
pub struct UuidServiceImpl;
|
||||
|
||||
impl service::uuid_service::UuidService for UuidServiceImpl {
|
||||
fn new_uuid(&self, _usage: &str) -> Uuid {
|
||||
Uuid::new_v4()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue