use alkcall::protocol::wire::{CallError, ResponseEnvelope};
use alkcall::registry::spec::ErrorDefinition;
use serde_json::{json, Value};
use crate::keys::Key;
use crate::Error;
pub const BLOB_MISSING: &str = "BLOB_MISSING";
pub const INVALID_DIGEST: &str = "INVALID_DIGEST";
pub const INVALID_RENEWAL_FORM: &str = "INVALID_RENEWAL_FORM";
pub const GC_REFUSED: &str = "GC_REFUSED";
pub const VERIFICATION_REFUSED: &str = "VERIFICATION_REFUSED";
pub const PUT_REFUSED: &str = "PUT_REFUSED";
pub const BLOBS_CHANNEL_ALPN: &str = "alk/blobs";
pub const FETCH_OP_ID: &str = "channels/blobs/sub";
pub const PUT_OP_ID: &str = "channels/blobs/pub";
pub const DIGEST_MISMATCH: &str = "DIGEST_MISMATCH";
pub const MINT_REFUSED: &str = "MINT_REFUSED";
pub(crate) fn digest_ingress(digest_hex: &str) -> Result<Key, CallError> {
let decode = || -> Result<Key, Error> {
if !digest_hex
.bytes()
.all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'))
{
return Err(Error::KeyInvalid);
}
let bytes = hex::decode(digest_hex).map_err(|_| Error::KeyInvalid)?;
Key::from_bytes(&bytes)
};
match decode() {
Ok(key) => Ok(key),
Err(_) => Err(CallError::new(
INVALID_DIGEST,
"the digest is not tag-prefixed key hex (lowercase, 42 or 66 chars)",
false,
)
.with_details(json!({ "digest": digest_hex }))),
}
}
pub(crate) fn envelope_error(request_id: &str, error: CallError) -> ResponseEnvelope {
ResponseEnvelope::error(request_id.to_owned(), error)
}
pub(crate) fn schema_validation_error(request_id: &str) -> ResponseEnvelope {
envelope_error(
request_id,
CallError::invalid_input("input failed input_schema validation"),
)
}
pub(crate) fn store_internal_error(
request_id: &str,
stage: &str,
_error: Error,
) -> ResponseEnvelope {
envelope_error(
request_id,
CallError::internal(format!("the store core failed at {stage}")),
)
}
pub(crate) fn declared_error(code: &str, description: &str, schema: Value) -> ErrorDefinition {
ErrorDefinition {
code: code.to_owned(),
description: description.to_owned(),
schema,
http_status: None,
}
}
pub(crate) fn digest_details_schema() -> Value {
json!({
"type": "object",
"properties": {
"digest": { "type": "string" }
},
"required": ["digest"],
"additionalProperties": false
})
}
pub(crate) const DIGEST_WIRE_PATTERN: &str = "^[0-9a-f]{42}$|^[0-9a-f]{66}$";
pub(crate) fn namespace_schema() -> Value {
json!({ "type": "string", "minLength": 1 })
}
pub(crate) fn digest_wire_schema() -> Value {
json!({ "type": "string", "pattern": DIGEST_WIRE_PATTERN })
}
pub(crate) fn digests_wire_schema() -> Value {
json!({
"type": "array",
"items": digest_wire_schema()
})
}
pub(crate) fn token_wire_schema() -> Value {
json!({
"type": "string",
"maxLength": 64,
"pattern": "^[0-9a-fA-F]{64}$"
})
}
pub(crate) fn present_wire_schema() -> Value {
json!({
"type": "object",
"properties": { "present": { "type": "array", "items": { "type": "boolean" } } },
"required": ["present"],
"additionalProperties": false
})
}
pub(crate) fn json_string_field<'a>(input: &'a Value, field: &str) -> Option<&'a str> {
input.get(field).and_then(Value::as_str)
}
pub(crate) fn json_string_array<'a>(input: &'a Value, field: &str) -> Option<Vec<&'a str>> {
input.get(field).and_then(Value::as_array).map(|values| {
values
.iter()
.map(Value::as_str)
.collect::<Option<Vec<&str>>>()
})?
}
pub(crate) fn put_refused_error(stage: &str, offered_hex: Option<&str>, error: Error) -> CallError {
let prefix = match offered_hex {
Some(key_hex) => format!("the put of {key_hex} refused at {stage}"),
None => format!("the put refused at {stage}"),
};
match error {
Error::Verification => CallError::new(
VERIFICATION_REFUSED,
format!(
"{prefix}: the stream failed verification against the offered \
key's derivation — nothing committed"
),
false,
),
_other => CallError::new(
PUT_REFUSED,
format!("{prefix}: the store core refused the put — nothing committed"),
false,
),
}
}
pub(crate) fn digest_mismatch_error(offered_key_hex: &str, derived_key_hex: &str) -> CallError {
CallError::new(
DIGEST_MISMATCH,
format!(
"the offered key {offered_key_hex} is not the stream's derivation under the \
key's own algorithm {derived_key_hex} (the offered key is a claim; the \
derivation under its algorithm is the authority) — nothing handed over"
),
false,
)
.with_details(json!({
"key": offered_key_hex,
"derived": derived_key_hex,
}))
}
pub(crate) fn mint_refused_error(committed_key_hex: &str) -> CallError {
CallError::new(
MINT_REFUSED,
format!(
"the put of {committed_key_hex} committed on this node, but its hand-over was \
refused: the token registry is at its admission cap — no token was issued \
and no pin custody was taken (the entry stands as unreferenced content a \
sweep may reclaim; a fleet serving node's pin row stands and expires by \
its TTL); the re-put is CAS-idempotent"
),
false,
)
.with_details(json!({ "key": committed_key_hex }))
}
pub(crate) fn put_message_details_schema() -> Value {
json!({
"type": "object",
"properties": {
"message": { "type": "string" },
"key": { "type": "string" },
"derived": { "type": "string" }
},
"required": ["message"],
"additionalProperties": false
})
}