pub mod v1;
pub const OPENAPI_V1: &str = include_str!("../assets/openapi/hyphae-v1.yaml");
pub const CAPABILITIES_SCHEMA_V1: &str =
include_str!("../assets/json-schema/capabilities-v1.schema.json");
pub const ERROR_SCHEMA_V1: &str = include_str!("../assets/json-schema/error-v1.schema.json");
pub const HEALTH_SCHEMA_V1: &str = include_str!("../assets/json-schema/health-v1.schema.json");
pub const PUT_REQUEST_SCHEMA_V1: &str =
include_str!("../assets/json-schema/put-request-v1.schema.json");
pub const DELETE_REQUEST_SCHEMA_V1: &str =
include_str!("../assets/json-schema/delete-request-v1.schema.json");
pub const GET_REQUEST_SCHEMA_V1: &str =
include_str!("../assets/json-schema/get-request-v1.schema.json");
pub const GET_RESPONSE_SCHEMA_V1: &str =
include_str!("../assets/json-schema/get-response-v1.schema.json");
pub const COMMIT_RECEIPT_SCHEMA_V1: &str =
include_str!("../assets/json-schema/commit-receipt-v1.schema.json");
pub const QUERY_REQUEST_SCHEMA_V1: &str =
include_str!("../assets/json-schema/query-request-v1.schema.json");
pub const QUERY_RESPONSE_SCHEMA_V1: &str =
include_str!("../assets/json-schema/query-response-v1.schema.json");
pub const PROOF_SCHEMA_V1: &str = include_str!("../assets/json-schema/proof-v1.schema.json");
#[cfg(test)]
mod tests {
use std::{error::Error, fs, io, path::Path};
use serde_json::Value as JsonValue;
use serde_yaml_ng::Value as YamlValue;
use schemars::{JsonSchema, SchemaGenerator};
use super::{
CAPABILITIES_SCHEMA_V1, COMMIT_RECEIPT_SCHEMA_V1, DELETE_REQUEST_SCHEMA_V1,
ERROR_SCHEMA_V1, GET_REQUEST_SCHEMA_V1, GET_RESPONSE_SCHEMA_V1, HEALTH_SCHEMA_V1,
OPENAPI_V1, PROOF_SCHEMA_V1, PUT_REQUEST_SCHEMA_V1, QUERY_REQUEST_SCHEMA_V1,
QUERY_RESPONSE_SCHEMA_V1,
v1::{
CapabilitiesV1, CommitReceiptV1, DeleteRequestV1, ErrorV1, GetRequestV1, GetResponseV1,
HealthV1, ProofV1, PutRequestV1, QueryRequestV1, QueryResponseV1,
},
};
#[test]
fn openapi_document_is_version_3_1() -> Result<(), Box<dyn Error>> {
let document: YamlValue = serde_yaml_ng::from_str(OPENAPI_V1)?;
assert_eq!(
document.get("openapi").and_then(YamlValue::as_str),
Some("3.1.0")
);
Ok(())
}
#[test]
fn openapi_defines_phase_five_surface_and_resolves_external_schemas()
-> Result<(), Box<dyn Error>> {
let document: YamlValue = serde_yaml_ng::from_str(OPENAPI_V1)?;
let paths = document
.get("paths")
.and_then(YamlValue::as_mapping)
.ok_or_else(|| io::Error::other("OpenAPI paths object is missing"))?;
for (path, method) in [
("/v1/capabilities", "get"),
("/v1/health/live", "get"),
("/v1/health/ready", "get"),
("/v1/kv/put", "post"),
("/v1/kv/get", "post"),
("/v1/kv/delete", "post"),
("/v1/query", "post"),
(
"/v1/witnesses/{checkpoint_sequence}/{snapshot_digest}",
"get",
),
] {
let operation = paths
.get(YamlValue::String(path.to_owned()))
.and_then(|item| item.get(method));
assert!(operation.is_some(), "missing {method} {path}");
}
let base = Path::new(env!("CARGO_MANIFEST_DIR")).join("assets/openapi");
validate_external_schema_refs(&document, &base)?;
Ok(())
}
#[test]
fn packaged_contract_assets_match_workspace_canonical_files() -> Result<(), Box<dyn Error>> {
let canonical_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../contracts");
if !canonical_root.is_dir() {
return Ok(());
}
for (relative, packaged) in [
("openapi/hyphae-v1.yaml", OPENAPI_V1),
(
"json-schema/capabilities-v1.schema.json",
CAPABILITIES_SCHEMA_V1,
),
("json-schema/error-v1.schema.json", ERROR_SCHEMA_V1),
("json-schema/health-v1.schema.json", HEALTH_SCHEMA_V1),
(
"json-schema/put-request-v1.schema.json",
PUT_REQUEST_SCHEMA_V1,
),
(
"json-schema/delete-request-v1.schema.json",
DELETE_REQUEST_SCHEMA_V1,
),
(
"json-schema/get-request-v1.schema.json",
GET_REQUEST_SCHEMA_V1,
),
(
"json-schema/get-response-v1.schema.json",
GET_RESPONSE_SCHEMA_V1,
),
(
"json-schema/commit-receipt-v1.schema.json",
COMMIT_RECEIPT_SCHEMA_V1,
),
(
"json-schema/query-request-v1.schema.json",
QUERY_REQUEST_SCHEMA_V1,
),
(
"json-schema/query-response-v1.schema.json",
QUERY_RESPONSE_SCHEMA_V1,
),
("json-schema/proof-v1.schema.json", PROOF_SCHEMA_V1),
] {
assert_eq!(
fs::read_to_string(canonical_root.join(relative))?,
packaged,
"packaged contract asset differs from contracts/{relative}"
);
}
Ok(())
}
#[test]
fn json_schemas_use_draft_2020_12() -> Result<(), Box<dyn Error>> {
for schema in [
CAPABILITIES_SCHEMA_V1,
ERROR_SCHEMA_V1,
HEALTH_SCHEMA_V1,
PUT_REQUEST_SCHEMA_V1,
DELETE_REQUEST_SCHEMA_V1,
GET_REQUEST_SCHEMA_V1,
GET_RESPONSE_SCHEMA_V1,
COMMIT_RECEIPT_SCHEMA_V1,
QUERY_REQUEST_SCHEMA_V1,
QUERY_RESPONSE_SCHEMA_V1,
PROOF_SCHEMA_V1,
] {
let document: JsonValue = serde_json::from_str(schema)?;
assert_eq!(
document.get("$schema").and_then(JsonValue::as_str),
Some("https://json-schema.org/draft/2020-12/schema")
);
}
Ok(())
}
#[test]
fn checked_in_json_schemas_match_rust_wire_models() -> Result<(), Box<dyn Error>> {
assert_schema::<CapabilitiesV1>(CAPABILITIES_SCHEMA_V1)?;
assert_schema::<ErrorV1>(ERROR_SCHEMA_V1)?;
assert_schema::<HealthV1>(HEALTH_SCHEMA_V1)?;
assert_schema::<PutRequestV1>(PUT_REQUEST_SCHEMA_V1)?;
assert_schema::<DeleteRequestV1>(DELETE_REQUEST_SCHEMA_V1)?;
assert_schema::<GetRequestV1>(GET_REQUEST_SCHEMA_V1)?;
assert_schema::<GetResponseV1>(GET_RESPONSE_SCHEMA_V1)?;
assert_schema::<CommitReceiptV1>(COMMIT_RECEIPT_SCHEMA_V1)?;
assert_schema::<QueryRequestV1>(QUERY_REQUEST_SCHEMA_V1)?;
assert_schema::<QueryResponseV1>(QUERY_RESPONSE_SCHEMA_V1)?;
assert_schema::<ProofV1>(PROOF_SCHEMA_V1)?;
Ok(())
}
fn assert_schema<T: JsonSchema>(checked_in: &str) -> Result<(), Box<dyn Error>> {
let generated = SchemaGenerator::default().into_root_schema_for::<T>();
let checked_in: JsonValue = serde_json::from_str(checked_in)?;
assert_eq!(serde_json::to_value(generated)?, checked_in);
Ok(())
}
fn validate_external_schema_refs(value: &YamlValue, base: &Path) -> Result<(), Box<dyn Error>> {
match value {
YamlValue::Mapping(mapping) => {
if let Some(reference) = mapping
.get(YamlValue::String("$ref".to_owned()))
.and_then(YamlValue::as_str)
.filter(|reference| reference.starts_with("../json-schema/"))
{
let encoded = fs::read_to_string(base.join(reference))?;
let _schema: JsonValue = serde_json::from_str(&encoded)?;
}
for (key, value) in mapping {
validate_external_schema_refs(key, base)?;
validate_external_schema_refs(value, base)?;
}
}
YamlValue::Sequence(values) => {
for value in values {
validate_external_schema_refs(value, base)?;
}
}
YamlValue::Tagged(value) => validate_external_schema_refs(&value.value, base)?,
YamlValue::Null | YamlValue::Bool(_) | YamlValue::Number(_) | YamlValue::String(_) => {}
}
Ok(())
}
}