use std::error::Error;
use std::fmt;
use serde::de::Error as _;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::codec::from_canonical_json;
use crate::diagnostic::{Diagnostic, DiagnosticCategory};
use crate::fingerprint::{
CanonicalizationVersion, Fingerprint, FingerprintDomain, SemanticProfileId,
};
pub const TYPEDB_3_12_1_SCHEMA_LOWERING_PROFILE_ID: &str = "typedb-3.12.1-schema-lowering/v1";
pub const SCHEMA_LOWERING_PROFILE_FINGERPRINT_DOMAIN: &str = "typebridge.schema.lowering-profile";
pub const SCHEMA_LOWERING_PROFILE_CANONICALIZATION: &str = "typebridge.schema-lowering-profile/v1";
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SchemaLoweringProfileValidationError {
message: String,
}
impl SchemaLoweringProfileValidationError {
fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
}
impl fmt::Display for SchemaLoweringProfileValidationError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.message)
}
}
impl Error for SchemaLoweringProfileValidationError {}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct SchemaLoweringProfileId(String);
impl SchemaLoweringProfileId {
pub fn new(value: impl Into<String>) -> Result<Self, SchemaLoweringProfileValidationError> {
let value = value.into();
SemanticProfileId::new(value.clone()).map_err(|error| {
SchemaLoweringProfileValidationError::new(format!(
"invalid schema-lowering profile id: {error}"
))
})?;
if value != TYPEDB_3_12_1_SCHEMA_LOWERING_PROFILE_ID {
return Err(SchemaLoweringProfileValidationError::new(format!(
"unsupported schema-lowering profile id: {value}"
)));
}
Ok(Self(value))
}
pub fn typedb_3_12_1() -> Self {
Self::new(TYPEDB_3_12_1_SCHEMA_LOWERING_PROFILE_ID)
.expect("the fixed schema-lowering profile id is valid")
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for SchemaLoweringProfileId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl Serialize for SchemaLoweringProfileId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for SchemaLoweringProfileId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::new(value).map_err(D::Error::custom)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SchemaLoweringProfileFingerprint(Fingerprint);
impl SchemaLoweringProfileFingerprint {
pub fn compute(canonical_bytes: &[u8]) -> Self {
let domain = FingerprintDomain::new(SCHEMA_LOWERING_PROFILE_FINGERPRINT_DOMAIN)
.expect("the fixed schema-lowering fingerprint domain is valid");
let canonicalization =
CanonicalizationVersion::new(SCHEMA_LOWERING_PROFILE_CANONICALIZATION)
.expect("the fixed schema-lowering canonicalization is valid");
let profile = SemanticProfileId::new(TYPEDB_3_12_1_SCHEMA_LOWERING_PROFILE_ID)
.expect("the fixed schema-lowering profile id is a valid semantic profile id");
Self(Fingerprint::compute(
domain,
canonicalization,
Some(profile),
canonical_bytes,
))
}
pub fn as_fingerprint(&self) -> &Fingerprint {
&self.0
}
fn validate_metadata(
fingerprint: &Fingerprint,
) -> Result<(), SchemaLoweringProfileValidationError> {
let value = serde_json::to_value(fingerprint).map_err(|error| {
SchemaLoweringProfileValidationError::new(format!(
"cannot inspect schema-lowering fingerprint: {error}"
))
})?;
let expected = [
("domain", SCHEMA_LOWERING_PROFILE_FINGERPRINT_DOMAIN),
("canonicalization", SCHEMA_LOWERING_PROFILE_CANONICALIZATION),
("semantic_profile", TYPEDB_3_12_1_SCHEMA_LOWERING_PROFILE_ID),
];
for (field, expected_value) in expected {
if value.get(field).and_then(serde_json::Value::as_str) != Some(expected_value) {
return Err(SchemaLoweringProfileValidationError::new(format!(
"schema-lowering fingerprint has invalid {field}"
)));
}
}
Ok(())
}
}
impl Serialize for SchemaLoweringProfileFingerprint {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for SchemaLoweringProfileFingerprint {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let fingerprint = Fingerprint::deserialize(deserializer)?;
Self::validate_metadata(&fingerprint).map_err(D::Error::custom)?;
Ok(Self(fingerprint))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct SchemaLoweringProfileBinding {
fingerprint: SchemaLoweringProfileFingerprint,
id: SchemaLoweringProfileId,
}
impl SchemaLoweringProfileBinding {
pub fn from_canonical_profile_bytes(
canonical_profile_bytes: &[u8],
) -> Result<Self, Diagnostic> {
let value: serde_json::Value = from_canonical_json(canonical_profile_bytes)?;
if value.get("id").and_then(serde_json::Value::as_str)
!= Some(TYPEDB_3_12_1_SCHEMA_LOWERING_PROFILE_ID)
{
return Err(Diagnostic::stable(
DiagnosticCategory::InvalidContract,
"schema_lowering_profile_id_mismatch",
"canonical schema-lowering profile bytes do not carry the frozen profile id",
));
}
Ok(Self {
fingerprint: SchemaLoweringProfileFingerprint::compute(canonical_profile_bytes),
id: SchemaLoweringProfileId::typedb_3_12_1(),
})
}
pub const fn id(&self) -> &SchemaLoweringProfileId {
&self.id
}
pub const fn fingerprint(&self) -> &SchemaLoweringProfileFingerprint {
&self.fingerprint
}
}