feat: migrate protocol and HTTP to structured interface references

T-20: identify interfaces by exact scope/name pairs; add optional opaque scope_ref metadata and separate shape validation from path-dependent scope compatibility. Preserve call error precedence, strict JSON, validators and transport boundaries. Mechanically migrate consumers and document API/wire compatibility and deferred lifecycle semantics.

Validation: cargo fmt --all --check; git diff --check; cargo check --workspace --all-targets; cargo test -p wip-protocol -p wip-http; cargo test --workspace (221 passing tests including doctests); cargo test --workspace --all-targets; nix develop --command cargo clippy --workspace --all-targets -- -D warnings; RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps. All pass. cargo/rustc 1.95.0; development-shell Clippy/rustc 1.97.1.
This commit is contained in:
2026-10-07 18:29:51 +09:00
parent bf5fa54f44
commit 8ecc307fc9
34 changed files with 2124 additions and 263 deletions
+26 -13
View File
@@ -3,12 +3,19 @@ use std::collections::BTreeMap;
use wip_protocol::{
CallOperationRequest, CallOperationResponse, EnumCase, FetchInterfaceRequest,
FetchInterfaceResponse, FieldDeclaration, INTERFACE_FORMAT_V1, InterfaceDescriptor,
InterfaceTarget, MAX_SAFE_INTEGER, NameNamespace, Object, ObjectObservation, ObserveRequest,
OperationDeclaration, ParameterDeclaration, PathSegment, ProtocolError, ProtocolErrorCode,
ProtocolInteraction, ProtocolResult, ReturnDeclaration, Target, TypeDeclaration, TypeExpr,
UnionCase, ValidationErrorKind, Value, ValueKind,
InterfaceReference, InterfaceTarget, MAX_SAFE_INTEGER, NameNamespace, Object,
ObjectObservation, ObserveRequest, OperationDeclaration, ParameterDeclaration, PathSegment,
ProtocolError, ProtocolErrorCode, ProtocolInteraction, ProtocolResult, ReturnDeclaration,
Target, TypeDeclaration, TypeExpr, UnionCase, ValidationErrorKind, Value, ValueKind,
};
fn reference(scope: &str, name: &str) -> InterfaceReference {
InterfaceReference {
scope: scope.into(),
name: name.into(),
}
}
fn record(entries: impl IntoIterator<Item = (&'static str, Value)>) -> Value {
Value::Record(
entries
@@ -135,7 +142,7 @@ fn object(name: &str) -> Object {
Object {
name: name.into(),
description: Some(format!("Object {name}")),
interfaces: vec!["catalog-v1".into(), "search-v2".into()],
interfaces: vec![reference("/", "catalog-v1"), reference("/", "search-v2")],
r#ref: Some("opaque-object-ref".into()),
validator: Some(vec![1, 2, 3]),
}
@@ -146,16 +153,19 @@ fn object_fields_have_canonical_identity_and_ordered_set_semantics() {
let object = object("article");
object.validate().unwrap();
object.validate_at_path("/articles/article").unwrap();
assert_eq!(object.interfaces, ["catalog-v1", "search-v2"]);
assert_eq!(
object.interfaces,
[reference("/", "catalog-v1"), reference("/", "search-v2")]
);
let duplicate = Object {
interfaces: vec!["catalog-v1".into(), "catalog-v1".into()],
interfaces: vec![reference("/", "catalog-v1"), reference("/", "catalog-v1")],
..object.clone()
};
assert_eq!(
duplicate.validate().unwrap_err().kind,
ValidationErrorKind::DuplicateInterface {
reference: "catalog-v1".into()
reference: reference("/", "catalog-v1")
}
);
@@ -574,7 +584,8 @@ fn calls_validate_canonical_paths_named_arguments_and_unit_results() {
validator: Some(vec![1]),
},
interface: InterfaceTarget {
reference: "catalog-v1".into(),
scope_ref: None,
reference: reference("/", "catalog-v1"),
validator: Some(vec![2]),
},
operation: "publish".into(),
@@ -659,17 +670,18 @@ fn observe_and_fetch_interface_dtos_validate_request_response_correspondence() {
}
let request = FetchInterfaceRequest {
interface: "catalog-v1".into(),
interface: reference("/", "catalog-v1"),
};
let response = FetchInterfaceResponse {
interface: "catalog-v1".into(),
scope_ref: None,
interface: reference("/", "catalog-v1"),
descriptor: descriptor(),
validator: Some(vec![9]),
};
response.validate_for(&request).unwrap();
assert!(matches!(
FetchInterfaceResponse {
interface: "other".into(),
interface: reference("/", "other"),
..response
}
.validate_for(&request)
@@ -847,7 +859,7 @@ fn invalid_success_payloads_remain_client_local_validation_failures() {
));
let interface_request = FetchInterfaceRequest {
interface: "catalog-v1".into(),
interface: reference("/", "catalog-v1"),
};
let mut invalid_descriptor = descriptor();
invalid_descriptor
@@ -856,6 +868,7 @@ fn invalid_success_payloads_remain_client_local_validation_failures() {
assert!(matches!(
FetchInterfaceResponse {
interface: interface_request.interface.clone(),
scope_ref: None,
descriptor: invalid_descriptor,
validator: None,
}