use alkcall::protocol::wire::{CallError, ResponseEnvelope};
use alkcall::registry::spec::ErrorDefinition;
use serde_json::{json, Value};
use crate::hashing::Digest;
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> {
match Digest::from_git_oid_hex(digest_hex) {
Ok(digest) => Ok(Key::from(digest)),
Err(_) => Err(CallError::new(
INVALID_DIGEST,
"the digest is not canonical digest hex (lowercase, 40 or 64 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
})
}
const DIGEST_WIRE_PATTERN: &str = "^[0-9a-f]{40}$|^[0-9a-f]{64}$";
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(digest_hex) => format!("the put of {digest_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 canonical \
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_hex: &str, derived_hex: &str) -> CallError {
CallError::new(
DIGEST_MISMATCH,
format!(
"the offered digest {offered_hex} is not the canonical derivation {derived_hex} \
(the offered digest is a claim; the derivation is the authority) — nothing \
handed over"
),
false,
)
.with_details(json!({
"digest": offered_hex,
"derived": derived_hex,
}))
}
pub(crate) fn mint_refused_error(committed_hex: &str) -> CallError {
CallError::new(
MINT_REFUSED,
format!(
"the put of {committed_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!({ "digest": committed_hex }))
}
pub(crate) fn put_message_details_schema() -> Value {
json!({
"type": "object",
"properties": {
"message": { "type": "string" },
"digest": { "type": "string" },
"derived": { "type": "string" }
},
"required": ["message"],
"additionalProperties": false
})
}