Skip to content

Commit

Permalink
refactored monitor verify checkpoint api endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
calvinrp committed Nov 13, 2023
1 parent 64078b3 commit a0504b6
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 389 deletions.
216 changes: 52 additions & 164 deletions crates/api/src/v1/monitor.rs
Original file line number Diff line number Diff line change
@@ -1,63 +1,62 @@
//! Types relating to the monitor API.
use crate::Status;
use serde::{de::Unexpected, Deserialize, Serialize, Serializer};
use serde::{Deserialize, Serialize, Serializer};
use std::borrow::Cow;
use thiserror::Error;
use warg_crypto::hash::AnyHash;
use warg_crypto::signing;
use warg_protocol::registry::RegistryLen;

/// Represents checkpoint verification response in one of the following states:
/// Represents checkpoint verification response.
#[derive(Serialize, Deserialize)]
#[serde(tag = "state", rename_all = "camelCase")]
#[allow(clippy::large_enum_variant)]
pub enum CheckpointVerificationResponse {
#[serde(rename_all = "camelCase")]
pub struct CheckpointVerificationResponse {
/// The checkpoint verification state.
pub checkpoint: CheckpointVerificationState,
/// The checkpoint signature verification state.
pub signature: CheckpointSignatureVerificationState,
}

/// Represents checkpoint verification state.
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum CheckpointVerificationState {
/// The checkpoint signature is unverified and could be valid or invalid.
#[serde(rename_all = "camelCase")]
Unverified,
/// The checkpoint is verified.
#[serde(rename_all = "camelCase")]
Verified,
/// The checkpoint is verified but has not verified the signature.
/// The checkpoint log length does not exist.
#[serde(rename_all = "camelCase")]
VerifiedButSignatureUnverified,
/// The checkpoint is unverified but should retry verification.
NotFound,
/// The checkpoint is invalid.
#[serde(rename_all = "camelCase")]
Retry {
/// Instructs to retry but wait the specified number of seconds.
wait_seconds: u16,
},
/// The checkpoint is unverified and should not retry.
Invalid,
}

/// Represents checkpoint signature verification state.
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum CheckpointSignatureVerificationState {
/// The checkpoint signature is unverified and could be valid or invalid.
#[serde(rename_all = "camelCase")]
Unverified,
/// The checkpoint signature is verified.
#[serde(rename_all = "camelCase")]
Verified,
/// The checkpoint signature key ID is known but not authorized to sign checkpoints.
#[serde(rename_all = "camelCase")]
Unauthorized,
/// The checkpoint signature is not valid. The key ID could be not known or the signature is incorrect.
#[serde(rename_all = "camelCase")]
Invalid,
}

/// Represents a monitor API error.
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum MonitorError {
/// The provided checkpoint log length is not known to the registry.
#[error("checkpoint log length `{0}` is not known to the registry")]
CheckpointNotFound(RegistryLen),
/// The provided checkpoint log length is greater than the most recent checkpoint. The monitor
/// is expected to only return this after it is certain that it has the most recent checkpoint.
#[error("checkpoint log length `{0}` is invalid and greater than the most recent checkpoint")]
CheckpointInvalidLogLength(RegistryLen),
/// The checkpoint signature keyId was not found.
#[error("checkpoint signature keyId `{0}` is unknown")]
CheckpointSignatureKeyIdNotFound(signing::KeyID),
/// The checkpoint signature keyId does not currently have permission to sign checkpoints.
#[error(
"checkpoint signature keyId `{0}` does not currently have permission to sign checkpoints"
)]
CheckpointSignatureKeyIdUnauthorized(signing::KeyID),
/// The checkpoint signature invalid.
#[error("checkpoint signature `{0}` is invalid")]
CheckpointSignatureInvalid(signing::Signature),
/// The provided checkpoint log root does not match.
#[error("checkpoint log root `{0}` is invalid")]
CheckpointLogRootInvalid(AnyHash),
/// The provided checkpoint map root does not match.
#[error("checkpoint map root `{0}` is invalid")]
CheckpointMapRootInvalid(AnyHash),
/// Instruct to retry after specified number of seconds.
#[error("retry after {0} seconds")]
RetryAfter(u16),
/// An error with a message occurred.
#[error("{message}")]
Message {
Expand All @@ -72,57 +71,19 @@ impl MonitorError {
/// Returns the HTTP status code of the error.
pub fn status(&self) -> u16 {
match self {
Self::CheckpointNotFound(_) | Self::CheckpointSignatureKeyIdNotFound(_) => 404,
Self::CheckpointInvalidLogLength(_)
| Self::CheckpointSignatureKeyIdUnauthorized(_)
| Self::CheckpointSignatureInvalid(_)
| Self::CheckpointLogRootInvalid(_)
| Self::CheckpointMapRootInvalid(_) => 422,
Self::RetryAfter(_) => 503,
Self::Message { status, .. } => *status,
}
}
}

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum EntityType {
LogLength,
KeyId,
Signature,
LogRoot,
MapRoot,
}

#[derive(Serialize, Deserialize)]
#[serde(untagged, rename_all = "camelCase")]
enum RawError<'a, T>
where
T: Clone + ToOwned,
<T as ToOwned>::Owned: Serialize + for<'b> Deserialize<'b>,
{
CheckpointNotFound {
status: Status<404>,
#[serde(rename = "type")]
ty: EntityType,
id: RegistryLen,
},
CheckpointInvalid {
status: Status<422>,
#[serde(rename = "type")]
ty: EntityType,
id: RegistryLen,
},
NotFound {
status: Status<404>,
#[serde(rename = "type")]
ty: EntityType,
id: Cow<'a, T>,
},
Invalid {
status: Status<422>,
#[serde(rename = "type")]
ty: EntityType,
id: Cow<'a, T>,
enum RawError<'a> {
#[serde(rename_all = "camelCase")]
RetryAfter {
status: u16,
retry_after: u16,
},
Message {
status: u16,
Expand All @@ -133,51 +94,12 @@ where
impl Serialize for MonitorError {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::CheckpointNotFound(log_length) => RawError::CheckpointNotFound::<RegistryLen> {
status: Status::<404>,
ty: EntityType::LogLength,
id: *log_length,
}
.serialize(serializer),
Self::CheckpointInvalidLogLength(log_length) => {
RawError::CheckpointInvalid::<RegistryLen> {
status: Status::<422>,
ty: EntityType::LogLength,
id: *log_length,
}
.serialize(serializer)
}
Self::CheckpointSignatureKeyIdNotFound(key_id) => RawError::NotFound {
status: Status::<404>,
ty: EntityType::KeyId,
id: Cow::Borrowed(key_id),
}
.serialize(serializer),
Self::CheckpointSignatureKeyIdUnauthorized(key_id) => RawError::Invalid {
status: Status::<422>,
ty: EntityType::KeyId,
id: Cow::Borrowed(key_id),
}
.serialize(serializer),
Self::CheckpointSignatureInvalid(signature) => RawError::Invalid {
status: Status::<422>,
ty: EntityType::Signature,
id: Cow::Borrowed(signature),
}
.serialize(serializer),
Self::CheckpointLogRootInvalid(log_root) => RawError::Invalid {
status: Status::<422>,
ty: EntityType::LogRoot,
id: Cow::Borrowed(log_root),
}
.serialize(serializer),
Self::CheckpointMapRootInvalid(map_root) => RawError::Invalid {
status: Status::<422>,
ty: EntityType::MapRoot,
id: Cow::Borrowed(map_root),
Self::RetryAfter(seconds) => RawError::RetryAfter {
status: 503,
retry_after: *seconds,
}
.serialize(serializer),
Self::Message { status, message } => RawError::Message::<()> {
Self::Message { status, message } => RawError::Message {
status: *status,
message: Cow::Borrowed(message),
}
Expand All @@ -191,42 +113,8 @@ impl<'de> Deserialize<'de> for MonitorError {
where
D: serde::Deserializer<'de>,
{
match RawError::<String>::deserialize(deserializer)? {
RawError::CheckpointNotFound { id, .. } => Ok(Self::CheckpointNotFound(id)),
RawError::CheckpointInvalid { id, .. } => Ok(Self::CheckpointInvalidLogLength(id)),
RawError::NotFound { status: _, ty, id } => match ty {
EntityType::KeyId => Ok(Self::CheckpointSignatureKeyIdNotFound(
signing::KeyID::from(id.into_owned()),
)),
_ => Err(serde::de::Error::invalid_value(
Unexpected::Str(&id),
&"unexpected type",
)),
},
RawError::Invalid { status: _, ty, id } => match ty {
EntityType::KeyId => Ok(Self::CheckpointSignatureKeyIdUnauthorized(
signing::KeyID::from(id.into_owned()),
)),
EntityType::Signature => Ok(Self::CheckpointSignatureInvalid(
id.parse::<signing::Signature>().map_err(|_| {
serde::de::Error::invalid_value(Unexpected::Str(&id), &"a valid signature")
})?,
)),
EntityType::LogRoot => Ok(Self::CheckpointLogRootInvalid(
id.parse::<AnyHash>().map_err(|_| {
serde::de::Error::invalid_value(Unexpected::Str(&id), &"a valid log root")
})?,
)),
EntityType::MapRoot => Ok(Self::CheckpointMapRootInvalid(
id.parse::<AnyHash>().map_err(|_| {
serde::de::Error::invalid_value(Unexpected::Str(&id), &"a valid map root")
})?,
)),
_ => Err(serde::de::Error::invalid_value(
Unexpected::Str(&id),
&"unexpected type",
)),
},
match RawError::deserialize(deserializer)? {
RawError::RetryAfter { retry_after, .. } => Ok(Self::RetryAfter(retry_after)),
RawError::Message { status, message } => Ok(Self::Message {
status,
message: message.into_owned(),
Expand Down
Loading

0 comments on commit a0504b6

Please sign in to comment.