Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define domain custom errors #70

Merged
merged 2 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jsonwebtoken = "9.3.0"
bech32 = "0.11.0"
argon2 = "0.5.3"
chrono = "0.4.38"
thiserror = "1.0.63"

[dev-dependencies]
mockall = "0.12.1"
Expand Down
51 changes: 51 additions & 0 deletions src/domain/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
#[error("Unauthorized")]
Unauthorized,
#[error("{0}")]
PermissionDenied(String),
#[error("{0}")]
CommandMalformed(String),
#[error("{0}")]
SecretExceeded(String),
#[error("{0}")]
Unexpected(String),
}

impl From<argon2::password_hash::Error> for Error {
fn from(value: argon2::password_hash::Error) -> Self {
Self::Unexpected(value.to_string())
}
}
impl From<argon2::Error> for Error {
fn from(value: argon2::Error) -> Self {
Self::Unexpected(value.to_string())
}
}
impl From<bech32::EncodeError> for Error {
fn from(value: bech32::EncodeError) -> Self {
Self::Unexpected(value.to_string())
}
}
impl From<bech32::primitives::hrp::Error> for Error {
fn from(value: bech32::primitives::hrp::Error) -> Self {
Self::Unexpected(value.to_string())
}
}
impl From<serde_json::Error> for Error {
fn from(value: serde_json::Error) -> Self {
Self::Unexpected(value.to_string())
}
}
impl From<sqlx::Error> for Error {
fn from(value: sqlx::Error) -> Self {
Self::Unexpected(value.to_string())
}
}
impl From<kube::Error> for Error {
fn from(value: kube::Error) -> Self {
Self::Unexpected(value.to_string())
}
}
22 changes: 13 additions & 9 deletions src/domain/event/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use anyhow::{bail, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use crate::domain::Result;

use super::error::Error;

macro_rules! into_event {
($name:ident) => {
impl From<$name> for Event {
Expand Down Expand Up @@ -78,14 +81,15 @@ impl Event {
}
}
pub fn from_key(key: &str, payload: &[u8]) -> Result<Self> {
let event = match key {
"ProjectCreated" => Self::ProjectCreated(serde_json::from_slice(payload)?),
"ProjectSecretCreated" => Self::ProjectSecretCreated(serde_json::from_slice(payload)?),
"ResourceCreated" => Self::ResourceCreated(serde_json::from_slice(payload)?),
"ResourceDeleted" => Self::ResourceDeleted(serde_json::from_slice(payload)?),
_ => bail!("Event key not implemented"),
};
Ok(event)
match key {
"ProjectCreated" => Ok(Self::ProjectCreated(serde_json::from_slice(payload)?)),
"ProjectSecretCreated" => {
Ok(Self::ProjectSecretCreated(serde_json::from_slice(payload)?))
}
"ResourceCreated" => Ok(Self::ResourceCreated(serde_json::from_slice(payload)?)),
"ResourceDeleted" => Ok(Self::ResourceDeleted(serde_json::from_slice(payload)?)),
_ => Err(Error::Unexpected("Event key not implemented".into())),
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/domain/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use error::Error;

pub mod auth;
pub mod error;
pub mod event;
pub mod project;
pub mod resource;
Expand All @@ -7,6 +10,8 @@ pub const PAGE_SIZE_DEFAULT: u32 = 12;
pub const PAGE_SIZE_MAX: u32 = 120;
pub const MAX_SECRET: usize = 2;

pub type Result<T, E = Error> = std::result::Result<T, E>;

#[cfg(test)]
mod tests {
pub const KEY: &str = "dmtr_apikey1g9gyswtcf3zxwd26v4x5jj3jw5wx3sn2";
Expand Down
3 changes: 1 addition & 2 deletions src/domain/project/cache.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use std::sync::Arc;

use anyhow::Result;

use crate::domain::event::{ProjectCreated, ProjectSecretCreated};
use crate::domain::Result;

use super::{Project, ProjectSecret, ProjectUser};

Expand Down
5 changes: 2 additions & 3 deletions src/domain/project/cluster.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use std::sync::Arc;

use anyhow::{bail, Result};
use k8s_openapi::api::core::v1::Namespace;
use kube::{api::ObjectMeta, ResourceExt};
use tracing::info;

use crate::domain::event::ProjectCreated;
use crate::domain::{error::Error, event::ProjectCreated, Result};

#[async_trait::async_trait]
pub trait ProjectDrivenCluster: Send + Sync {
Expand All @@ -18,7 +17,7 @@ pub async fn apply_manifest(
evt: ProjectCreated,
) -> Result<()> {
if cluster.find_by_name(&evt.namespace).await?.is_some() {
bail!("namespace alread exist")
return Err(Error::CommandMalformed("namespace alread exist".into()));
}

let namespace = Namespace {
Expand Down
44 changes: 25 additions & 19 deletions src/domain/project/command.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::sync::Arc;

use anyhow::{bail, ensure, Error, Result};
use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use bech32::{Bech32m, Hrp};
use chrono::Utc;
Expand All @@ -14,9 +13,10 @@ use uuid::Uuid;

use crate::domain::{
auth::{Credential, UserId},
error::Error,
event::{EventDrivenBridge, ProjectCreated, ProjectSecretCreated},
project::ProjectStatus,
MAX_SECRET, PAGE_SIZE_DEFAULT, PAGE_SIZE_MAX,
Result, MAX_SECRET, PAGE_SIZE_DEFAULT, PAGE_SIZE_MAX,
};

use super::{cache::ProjectDrivenCache, Project, ProjectSecret};
Expand All @@ -35,7 +35,7 @@ pub async fn create(
let user_id = assert_credential(&cmd.credential)?;

if cache.find_by_namespace(&cmd.namespace).await?.is_some() {
bail!("invalid project namespace")
return Err(Error::CommandMalformed("invalid project namespace".into()));
}

let evt = ProjectCreated {
Expand Down Expand Up @@ -63,12 +63,14 @@ pub async fn create_secret(
assert_permission(cache.clone(), &cmd.credential, &cmd.project_id).await?;

let Some(project) = cache.find_by_id(&cmd.project_id).await? else {
bail!("project doesnt exist")
return Err(Error::CommandMalformed("invalid project id".into()));
};

let secrets = cache.find_secret_by_project_id(&cmd.project_id).await?;
if secrets.len() >= MAX_SECRET {
bail!("secrets exceeded the limit of {MAX_SECRET}")
return Err(Error::SecretExceeded(format!(
"secrets exceeded the limit of {MAX_SECRET}"
)));
}

let key = Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
Expand All @@ -84,15 +86,13 @@ pub async fn create_secret(
Ok(argon2) => argon2.clone(),
Err(error) => {
error!(?error, "error to configure argon2 with secret");
bail!("internal error")
return Err(Error::Unexpected("error to create the secret".into()));
}
};

let key_bytes = key.into_bytes();

let password_hash = argon2
.hash_password(&key_bytes, salt_string.as_salt())
.map_err(|err| Error::msg(err.to_string()))?;
let password_hash = argon2.hash_password(&key_bytes, salt_string.as_salt())?;

let hrp = Hrp::parse("dmtr_apikey")?;
let key = bech32::encode::<Bech32m>(hrp, &key_bytes)?;
Expand All @@ -118,12 +118,12 @@ pub async fn verify_secret(
) -> Result<ProjectSecret> {
let (hrp, key) = bech32::decode(&cmd.key).map_err(|error| {
error!(?error, "invalid bech32");
Error::msg("invalid bech32")
Error::CommandMalformed("invalid bech32".into())
})?;

if !hrp.to_string().eq("dmtr_apikey") {
error!(?hrp, "invalid bech32 hrp");
bail!("invalid project secret")
return Err(Error::CommandMalformed("invalid project secret".into()));
}

let secrets = cache.find_secret_by_project_id(&cmd.project_id).await?;
Expand All @@ -150,7 +150,7 @@ pub async fn verify_secret(
});

let Some(secret) = secret else {
bail!("invalid project secret");
return Err(Error::CommandMalformed("invalid project secret".into()));
};

Ok(secret)
Expand All @@ -159,7 +159,7 @@ pub async fn verify_secret(
fn assert_credential(credential: &Credential) -> Result<UserId> {
match credential {
Credential::Auth0(user_id) => Ok(user_id.into()),
Credential::ApiKey(_) => bail!("rpc doesnt support api-key"),
Credential::ApiKey(_) => Err(Error::Unauthorized),
paulobressan marked this conversation as resolved.
Show resolved Hide resolved
}
}
async fn assert_permission(
Expand All @@ -170,10 +170,15 @@ async fn assert_permission(
match credential {
Credential::Auth0(user_id) => {
let result = cache.find_user_permission(user_id, project_id).await?;
ensure!(result.is_some(), "user doesnt have permission");
if result.is_none() {
return Err(Error::PermissionDenied(
paulobressan marked this conversation as resolved.
Show resolved Hide resolved
"user doesnt have permission".into(),
));
}

Ok(())
}
Credential::ApiKey(_) => bail!("rpc doesnt support api-key"),
Credential::ApiKey(_) => Err(Error::PermissionDenied("rpc doesnt support api-key".into())),
paulobressan marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand All @@ -188,10 +193,11 @@ impl FetchCmd {
let page = page.unwrap_or(1);
let page_size = page_size.unwrap_or(PAGE_SIZE_DEFAULT);

ensure!(
page_size <= PAGE_SIZE_MAX,
"page_size exceeded the limit of {PAGE_SIZE_MAX}"
);
if page_size >= PAGE_SIZE_MAX {
return Err(Error::CommandMalformed(format!(
"page_size exceeded the limit of {PAGE_SIZE_MAX}"
)));
}

Ok(Self {
credential,
Expand Down
8 changes: 5 additions & 3 deletions src/domain/project/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::{fmt::Display, str::FromStr};

use anyhow::{bail, Error};
use chrono::{DateTime, Utc};

use super::event::{ProjectCreated, ProjectSecretCreated};
use super::{
error::Error,
event::{ProjectCreated, ProjectSecretCreated},
};

pub mod cache;
pub mod cluster;
Expand Down Expand Up @@ -47,7 +49,7 @@ impl FromStr for ProjectStatus {
match s {
"active" => Ok(ProjectStatus::Active),
"deleted" => Ok(ProjectStatus::Deleted),
_ => bail!("project status not supported"),
_ => Err(Error::Unexpected("project status not supported".into())),
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/domain/resource/cache.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::sync::Arc;

use crate::domain::event::{ResourceCreated, ResourceDeleted};
use crate::domain::{
event::{ResourceCreated, ResourceDeleted},
Result,
};

use super::Resource;

use anyhow::Result;
use chrono::{DateTime, Utc};

#[async_trait::async_trait]
Expand Down
6 changes: 4 additions & 2 deletions src/domain/resource/cluster.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use std::sync::Arc;

use anyhow::Result;
use kube::{
api::{ApiResource, DynamicObject, ObjectMeta},
ResourceExt,
};
use serde_json::json;
use tracing::info;

use crate::domain::event::{ResourceCreated, ResourceDeleted};
use crate::domain::{
event::{ResourceCreated, ResourceDeleted},
Result,
};

#[async_trait::async_trait]
pub trait ResourceDrivenCluster: Send + Sync {
Expand Down
Loading
Loading