Merge pull request 'cultnet: add unsupported_schema_version to the closed cultnet.error.v0 code set' (#26) from libby/error-code-unsupported-schema-version into main
Some checks failed
CultMesh QUIC native / Build win32-x64 (push) Has been skipped
CultMesh QUIC native / Build linux-x64 (push) Has been skipped
CultMesh QUIC native / Release native binaries (push) Has been skipped
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (push) Has been cancelled
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (push) Has been cancelled
Some checks failed
CultMesh QUIC native / Build win32-x64 (push) Has been skipped
CultMesh QUIC native / Build linux-x64 (push) Has been skipped
CultMesh QUIC native / Release native binaries (push) Has been skipped
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (push) Has been cancelled
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (push) Has been cancelled
This commit is contained in:
commit
ec7a009b02
18 changed files with 98 additions and 32 deletions
|
|
@ -112,8 +112,9 @@ not guess. Adding a code is therefore a coordinated change across every runtime,
|
|||
copies), the C# `CultNetErrorMessage` factory, the Rust `CultNetErrorCode`, and a captured-bytes case in
|
||||
`packages/cultnet-rs/tests/error_message.rs` for it. TypeScript reads the schema. The current codes are
|
||||
`selection_invalid`, `cursor_stale`, `cursor_invalid`, `reference_outside_target`, `variant_unsupported`,
|
||||
`unowned_schema` (a write, or a remote put or delete, for a schema and key no shard owns) and
|
||||
`write_not_authorized` (a peer's put or delete its server's remote write authority did not grant).
|
||||
`unowned_schema` (a write, or a remote put or delete, for a schema and key no shard owns),
|
||||
`write_not_authorized` (a peer's put or delete its server's remote write authority did not grant) and
|
||||
`unsupported_schema_version` (a message whose `schemaVersion` the peer does not speak; `details` names `schemaVersion` and the offered value).
|
||||
|
||||
## Live Invariants
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
"code": {
|
||||
"description": "Machine-readable refusal code (R-N). Additive: a v0 peer that does not know this field ignores it. The set is a closed enum: Rust and TypeScript refuse a message whose code is not listed, so adding a code is one coordinated change across every runtime (this schema, the C# factory, the Rust CultNetErrorCode, and a captured-bytes case for each).",
|
||||
"type": ["string", "null"],
|
||||
"enum": [null, "selection_invalid", "cursor_stale", "cursor_invalid", "reference_outside_target", "variant_unsupported", "unowned_schema", "write_not_authorized"]
|
||||
"enum": [null, "selection_invalid", "cursor_stale", "cursor_invalid", "reference_outside_target", "variant_unsupported", "unowned_schema", "write_not_authorized", "unsupported_schema_version"]
|
||||
},
|
||||
"details": {
|
||||
"description": "Code-specific structured detail (R-N). Every field is populated only for the code that defines it; the others are null, not omitted.",
|
||||
|
|
@ -36,8 +36,8 @@
|
|||
"additionalProperties": false,
|
||||
"required": ["field", "value", "asOf", "current"],
|
||||
"properties": {
|
||||
"field": { "description": "selection_invalid: the selection field that failed. variant_unsupported: \"subscriptionId\", when a subscription was ended. unowned_schema: \"schemaId\". write_not_authorized: \"recordKey\".", "type": ["string", "null"] },
|
||||
"value": { "description": "selection_invalid: the offending value, when there is one string worth naming. variant_unsupported: the id of the subscription that ended. unowned_schema: the schema id no shard owns. write_not_authorized: the key of the refused write.", "type": ["string", "null"] },
|
||||
"field": { "description": "selection_invalid: the selection field that failed. variant_unsupported: \"subscriptionId\", when a subscription was ended. unowned_schema: \"schemaId\". write_not_authorized: \"recordKey\". unsupported_schema_version: \"schemaVersion\".", "type": ["string", "null"] },
|
||||
"value": { "description": "selection_invalid: the offending value, when there is one string worth naming. variant_unsupported: the id of the subscription that ended. unowned_schema: the schema id no shard owns. write_not_authorized: the key of the refused write. unsupported_schema_version: the schemaVersion the peer does not speak.", "type": ["string", "null"] },
|
||||
"asOf": { "description": "cursor_stale: the cursor's own minted asOf.", "type": ["integer", "null"], "minimum": 0 },
|
||||
"current": { "description": "cursor_stale: the answering server's current asOf.", "type": ["integer", "null"], "minimum": 0 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,6 +129,22 @@
|
|||
"current": null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "unsupported_schema_version",
|
||||
"messagePackHex": "85AD736368656D6156657273696F6EB063756C746E65742E6572726F722E7630A56572726F72D92C756E737570706F727465645F736368656D615F76657273696F6E3A2063756C746E65742E68656C6C6F2E7631AB726F7574696E6748696E74C0A4636F6465BA756E737570706F727465645F736368656D615F76657273696F6EA764657461696C7384A56669656C64AD736368656D6156657273696F6EA576616C7565B063756C746E65742E68656C6C6F2E7631A461734F66C0A763757272656E74C0",
|
||||
"message": {
|
||||
"schemaVersion": "cultnet.error.v0",
|
||||
"error": "unsupported_schema_version: cultnet.hello.v1",
|
||||
"routingHint": null,
|
||||
"code": "unsupported_schema_version",
|
||||
"details": {
|
||||
"field": "schemaVersion",
|
||||
"value": "cultnet.hello.v1",
|
||||
"asOf": null,
|
||||
"current": null
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from typing import Any
|
|||
import msgpack
|
||||
|
||||
from cultnet_py import (
|
||||
unsupported_schema_version_error,
|
||||
CultNetRawSnapshotResponse,
|
||||
CultNetSimulationObservationHub,
|
||||
CultNetRudpPacket,
|
||||
|
|
@ -229,12 +230,7 @@ class CultMeshLocalServer:
|
|||
if schema_version == "cultnet.simulation_observation.v0":
|
||||
candidates = self._candidate_responses(message)
|
||||
return candidates[0] if candidates else None
|
||||
return self._error_response(
|
||||
f"Unsupported CultNet message schema: {schema_version!r}.",
|
||||
message_id=str(message.get("messageId") or ""),
|
||||
code="unsupported_schema_version",
|
||||
details={"schemaVersion": schema_version},
|
||||
)
|
||||
return unsupported_schema_version_error(schema_version).to_wire()
|
||||
|
||||
def _accept_loop(self) -> None:
|
||||
while not self._stop.is_set():
|
||||
|
|
|
|||
|
|
@ -5048,17 +5048,16 @@ class CultMeshTests(unittest.TestCase):
|
|||
finally:
|
||||
server.stop()
|
||||
|
||||
self.assertEqual(unsupported["messageId"], "bad-schema")
|
||||
self.assertEqual(unsupported["code"], "unsupported_schema_version")
|
||||
self.assertEqual(unsupported["details"]["schemaVersion"], "cultnet.nope.v0")
|
||||
self.assertIn("Unsupported CultNet message schema", unsupported["error"])
|
||||
self.assertEqual(unsupported["details"]["field"], "schemaVersion")
|
||||
self.assertEqual(unsupported["details"]["value"], "cultnet.nope.v0")
|
||||
self.assertIn("unsupported_schema_version", unsupported["error"])
|
||||
self.assertEqual(malformed_put["messageId"], "bad-put")
|
||||
self.assertEqual(malformed_put["code"], "malformed_document_put")
|
||||
self.assertEqual(malformed_delete["messageId"], "bad-delete")
|
||||
self.assertEqual(malformed_delete["code"], "malformed_document_delete")
|
||||
self.assertEqual(unsupported_v1["messageId"], "bad-v1-selection")
|
||||
self.assertEqual(unsupported_v1["code"], "unsupported_schema_version")
|
||||
self.assertEqual(unsupported_v1["details"]["schemaVersion"], "cultnet.snapshot_request.v1")
|
||||
self.assertEqual(unsupported_v1["details"]["value"], "cultnet.snapshot_request.v1")
|
||||
|
||||
def test_cultmesh_authority_lease_requires_live_matching_lease(self) -> None:
|
||||
peer = CultMeshPeerCard(
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ from .messages import (
|
|||
document_put_raw,
|
||||
encode_witness_artifact_bundle_payload,
|
||||
error_message,
|
||||
unsupported_schema_version_error,
|
||||
hello,
|
||||
login,
|
||||
login_success,
|
||||
|
|
@ -261,6 +262,7 @@ __all__ = [
|
|||
"encode_frame",
|
||||
"encode_rudp_packet",
|
||||
"error_message",
|
||||
"unsupported_schema_version_error",
|
||||
"hello",
|
||||
"INTEROP_WIRE_CONTRACT",
|
||||
"login",
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ from .cultmesh_contracts import (
|
|||
simulation_fact_document,
|
||||
)
|
||||
|
||||
from .messages import unsupported_schema_version_error as _unsupported_schema_version_error
|
||||
from .schema_catalog import INTEROP_WIRE_CONTRACT, wire_message_schema_descriptors
|
||||
from .client import create_rudp_schema_transport
|
||||
from .transport import (
|
||||
|
|
@ -407,14 +408,7 @@ def is_unsupported_cultnet_version(schema_version: Any) -> bool:
|
|||
|
||||
|
||||
def unsupported_schema_version_error(message: dict[str, Any]) -> dict[str, Any]:
|
||||
schema_version = message.get("schemaVersion")
|
||||
return {
|
||||
"schemaVersion": "cultnet.error.v0",
|
||||
"messageId": str(message.get("messageId") or ""),
|
||||
"error": f"Unsupported CultNet message schema: {schema_version!r}.",
|
||||
"code": "unsupported_schema_version",
|
||||
"details": {"schemaVersion": schema_version},
|
||||
}
|
||||
return _unsupported_schema_version_error(message.get("schemaVersion")).to_wire()
|
||||
|
||||
|
||||
def handle_database_subscribe(state: PeerState, message: dict[str, Any], subscriptions: dict[str, DatabaseSubscription]) -> list[dict[str, Any]]:
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ CULTNET_ERROR_CODES: tuple[str, ...] = (
|
|||
"variant_unsupported",
|
||||
"unowned_schema",
|
||||
"write_not_authorized",
|
||||
"unsupported_schema_version",
|
||||
)
|
||||
|
||||
_ERROR_DETAILS_KEYS = ("field", "value", "asOf", "current")
|
||||
|
|
@ -174,6 +175,16 @@ def error_message(
|
|||
return CultNetMessage("cultnet.error.v0", body)
|
||||
|
||||
|
||||
def unsupported_schema_version_error(schema_version: Any) -> CultNetMessage:
|
||||
"""The refusal for a message whose schemaVersion this peer does not speak (C# ForUnsupportedSchemaVersion)."""
|
||||
value = None if schema_version is None else str(schema_version)
|
||||
return error_message(
|
||||
f"unsupported_schema_version: {value or ''}",
|
||||
code="unsupported_schema_version",
|
||||
details={"field": "schemaVersion", "value": value},
|
||||
)
|
||||
|
||||
|
||||
def login_success(
|
||||
*,
|
||||
nonce: str,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ CULTNET_ERROR_CODES = (
|
|||
"variant_unsupported",
|
||||
"unowned_schema",
|
||||
"write_not_authorized",
|
||||
"unsupported_schema_version",
|
||||
)
|
||||
|
||||
WIRE_MESSAGE_SCHEMA_VERSIONS = (
|
||||
|
|
|
|||
|
|
@ -2580,7 +2580,8 @@ class CultNetTests(unittest.TestCase):
|
|||
self.assertEqual(len(responses), 1)
|
||||
self.assertEqual(responses[0]["schemaVersion"], "cultnet.error.v0")
|
||||
self.assertEqual(responses[0]["code"], "unsupported_schema_version")
|
||||
self.assertEqual(responses[0]["messageId"], "v1-refused")
|
||||
self.assertEqual(responses[0]["details"]["value"], schema_version)
|
||||
self.assertNotIn("messageId", responses[0])
|
||||
|
||||
def test_cultnet_simulation_consensus_dedupes_witnesses_and_requires_quorum(self) -> None:
|
||||
from cultnet_py import CultNetSimulationConsensus
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from pathlib import Path
|
|||
|
||||
import msgpack
|
||||
|
||||
from cultnet_py import CULTNET_ERROR_CODES, error_message, parse_message
|
||||
from cultnet_py import CULTNET_ERROR_CODES, error_message, parse_message, unsupported_schema_version_error
|
||||
|
||||
# contracts/cultnet/interop/error-vectors.json: bytes captured from MessagePack C# 3.1.7 and pinned
|
||||
# by cultnet-rs/tests/error_message.rs (Rust encodes to exactly these bytes).
|
||||
|
|
@ -50,6 +50,7 @@ class CultNetErrorMessageTests(unittest.TestCase):
|
|||
details={"field": "keys", "value": " "},
|
||||
)
|
||||
self.assertEqual(selection.to_bytes(), by_name["selection_invalid_field_value"])
|
||||
self.assertEqual(unsupported_schema_version_error("cultnet.hello.v1").to_bytes(), by_name["unsupported_schema_version"])
|
||||
|
||||
def test_builder_writes_nil_keys_and_accepts_a_null_code(self) -> None:
|
||||
wire = error_message("boom").to_wire()
|
||||
|
|
@ -57,7 +58,7 @@ class CultNetErrorMessageTests(unittest.TestCase):
|
|||
|
||||
def test_unknown_code_is_refused_by_builder_and_decoder(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
error_message("x", code="unsupported_schema_version")
|
||||
error_message("x", code="not_a_code")
|
||||
bad = msgpack.packb(
|
||||
{"schemaVersion": "cultnet.error.v0", "error": "x", "routingHint": None, "code": "nope", "details": None},
|
||||
use_bin_type=True,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
"code": {
|
||||
"description": "Machine-readable refusal code (R-N). Additive: a v0 peer that does not know this field ignores it. The set is a closed enum: Rust and TypeScript refuse a message whose code is not listed, so adding a code is one coordinated change across every runtime (this schema, the C# factory, the Rust CultNetErrorCode, and a captured-bytes case for each).",
|
||||
"type": ["string", "null"],
|
||||
"enum": [null, "selection_invalid", "cursor_stale", "cursor_invalid", "reference_outside_target", "variant_unsupported", "unowned_schema", "write_not_authorized"]
|
||||
"enum": [null, "selection_invalid", "cursor_stale", "cursor_invalid", "reference_outside_target", "variant_unsupported", "unowned_schema", "write_not_authorized", "unsupported_schema_version"]
|
||||
},
|
||||
"details": {
|
||||
"description": "Code-specific structured detail (R-N). Every field is populated only for the code that defines it; the others are null, not omitted.",
|
||||
|
|
@ -36,8 +36,8 @@
|
|||
"additionalProperties": false,
|
||||
"required": ["field", "value", "asOf", "current"],
|
||||
"properties": {
|
||||
"field": { "description": "selection_invalid: the selection field that failed. variant_unsupported: \"subscriptionId\", when a subscription was ended. unowned_schema: \"schemaId\". write_not_authorized: \"recordKey\".", "type": ["string", "null"] },
|
||||
"value": { "description": "selection_invalid: the offending value, when there is one string worth naming. variant_unsupported: the id of the subscription that ended. unowned_schema: the schema id no shard owns. write_not_authorized: the key of the refused write.", "type": ["string", "null"] },
|
||||
"field": { "description": "selection_invalid: the selection field that failed. variant_unsupported: \"subscriptionId\", when a subscription was ended. unowned_schema: \"schemaId\". write_not_authorized: \"recordKey\". unsupported_schema_version: \"schemaVersion\".", "type": ["string", "null"] },
|
||||
"value": { "description": "selection_invalid: the offending value, when there is one string worth naming. variant_unsupported: the id of the subscription that ended. unowned_schema: the schema id no shard owns. write_not_authorized: the key of the refused write. unsupported_schema_version: the schemaVersion the peer does not speak.", "type": ["string", "null"] },
|
||||
"asOf": { "description": "cursor_stale: the cursor's own minted asOf.", "type": ["integer", "null"], "minimum": 0 },
|
||||
"current": { "description": "cursor_stale: the answering server's current asOf.", "type": ["integer", "null"], "minimum": 0 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -247,6 +247,7 @@ pub enum CultNetErrorCode {
|
|||
VariantUnsupported,
|
||||
UnownedSchema,
|
||||
WriteNotAuthorized,
|
||||
UnsupportedSchemaVersion,
|
||||
}
|
||||
|
||||
impl CultNetErrorCode {
|
||||
|
|
@ -259,6 +260,7 @@ impl CultNetErrorCode {
|
|||
Self::VariantUnsupported => "variant_unsupported",
|
||||
Self::UnownedSchema => "unowned_schema",
|
||||
Self::WriteNotAuthorized => "write_not_authorized",
|
||||
Self::UnsupportedSchemaVersion => "unsupported_schema_version",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -271,6 +273,7 @@ impl CultNetErrorCode {
|
|||
"variant_unsupported" => Ok(Self::VariantUnsupported),
|
||||
"unowned_schema" => Ok(Self::UnownedSchema),
|
||||
"write_not_authorized" => Ok(Self::WriteNotAuthorized),
|
||||
"unsupported_schema_version" => Ok(Self::UnsupportedSchemaVersion),
|
||||
_ => Err(anyhow!("ErrorMessage.Code is not a recognized cultnet.error.v0 code")),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,6 +173,21 @@ fn cases() -> Vec<Case> {
|
|||
}),
|
||||
},
|
||||
},
|
||||
Case {
|
||||
name: "unsupported_schema_version",
|
||||
// C#: ForUnsupportedSchemaVersion("cultnet.hello.v1")
|
||||
csharp_hex: "85AD736368656D6156657273696F6EB063756C746E65742E6572726F722E7630A56572726F72D92C756E737570706F727465645F736368656D615F76657273696F6E3A2063756C746E65742E68656C6C6F2E7631AB726F7574696E6748696E74C0A4636F6465BA756E737570706F727465645F736368656D615F76657273696F6EA764657461696C7384A56669656C64AD736368656D6156657273696F6EA576616C7565B063756C746E65742E68656C6C6F2E7631A461734F66C0A763757272656E74C0",
|
||||
message: || CultNetMessage::Error {
|
||||
error: "unsupported_schema_version: cultnet.hello.v1".to_string(),
|
||||
code: Some(CultNetErrorCode::UnsupportedSchemaVersion),
|
||||
details: Some(CultNetErrorDetails {
|
||||
field: Some("schemaVersion".to_string()),
|
||||
value: Some("cultnet.hello.v1".to_string()),
|
||||
as_of: None,
|
||||
current: None,
|
||||
}),
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -194,6 +194,7 @@ export const CULTNET_ERROR_CODES = [
|
|||
"variant_unsupported",
|
||||
"unowned_schema",
|
||||
"write_not_authorized",
|
||||
"unsupported_schema_version",
|
||||
] as const;
|
||||
|
||||
export type CultNetErrorCode = (typeof CULTNET_ERROR_CODES)[number];
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ test("dropping code or details breaks the round trip and the decode", () => {
|
|||
|
||||
test("a code outside the closed enum is refused, as is a malformed details object", () => {
|
||||
const base = decode(bytesOf(vectors.find((v) => v.name === "cursor_stale")!.messagePackHex)) as Record<string, unknown>;
|
||||
assert.throws(() => parseCultNetMessage({ ...base, code: "unsupported_schema_version" }), /cultnet\.error\.v0/);
|
||||
assert.throws(() => parseCultNetMessage({ ...base, code: "not_a_code" }), /cultnet\.error\.v0/);
|
||||
assert.throws(() => parseCultNetMessage({ ...base, code: 7 }), /cultnet\.error\.v0/);
|
||||
assert.throws(
|
||||
() => parseCultNetMessage({ ...base, details: { field: null, value: null, asOf: 1, current: 2, extra: 1 } }),
|
||||
|
|
|
|||
|
|
@ -511,7 +511,7 @@ namespace GameCult.Networking
|
|||
[Key("routingHint")] public CultNetShardRoutingHint? RoutingHint { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the machine-readable refusal code (R-N), e.g. <c>selection_invalid</c>,
|
||||
/// <c>cursor_stale</c>, <c>cursor_invalid</c>, <c>reference_outside_target</c>, <c>unowned_schema</c>, <c>write_not_authorized</c>. Additive to
|
||||
/// <c>cursor_stale</c>, <c>cursor_invalid</c>, <c>reference_outside_target</c>, <c>unowned_schema</c>, <c>write_not_authorized</c>, <c>unsupported_schema_version</c>. Additive to
|
||||
/// <c>cultnet.error.v0</c>; a v0 peer that does not know this field ignores it.
|
||||
/// </summary>
|
||||
[Key("code")] public string? Code { get; set; }
|
||||
|
|
@ -555,6 +555,18 @@ namespace GameCult.Networking
|
|||
Details = new CultNetErrorDetails { Field = "recordKey", Value = request.RecordKey.Value }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Builds the wire error for a message whose <c>schemaVersion</c> this peer does not speak: code
|
||||
/// <c>unsupported_schema_version</c>, details <c>{ field: "schemaVersion", value }</c>.
|
||||
/// </summary>
|
||||
public static CultNetErrorMessage ForUnsupportedSchemaVersion(string? schemaVersion) =>
|
||||
new CultNetErrorMessage
|
||||
{
|
||||
Error = $"unsupported_schema_version: {schemaVersion}",
|
||||
Code = "unsupported_schema_version",
|
||||
Details = new CultNetErrorDetails { Field = "schemaVersion", Value = schemaVersion }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Builds the wire error for a cursor refusal (R-N): code <c>cursor_stale</c> with details
|
||||
/// <c>{ asOf, current }</c>, or code <c>cursor_invalid</c> with no details.
|
||||
|
|
|
|||
|
|
@ -220,6 +220,19 @@ namespace GameCult.Networking.Tests
|
|||
Assert.That(Convert.ToHexString(CultNetSchemaMessageSerialization.Serialize(message)), Is.EqualTo("85AD736368656D6156657273696F6EB063756C746E65742E6572726F722E7630A56572726F72D92177726974655F6E6F745F617574686F72697A65643A206E6F74206772616E746564AB726F7574696E6748696E74C0A4636F6465B477726974655F6E6F745F617574686F72697A6564A764657461696C7384A56669656C64A97265636F72644B6579A576616C7565A8736F6D653A6B6579A461734F66C0A763757272656E74C0"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ErrorMessage_UnsupportedSchemaVersionRealBytes_MatchesItsSchema()
|
||||
{
|
||||
var message = CultNetErrorMessage.ForUnsupportedSchemaVersion("cultnet.hello.v1");
|
||||
|
||||
Assert.That(message.Code, Is.EqualTo("unsupported_schema_version"));
|
||||
Assert.That(message.Details!.Field, Is.EqualTo("schemaVersion"));
|
||||
Assert.That(message.Details.Value, Is.EqualTo("cultnet.hello.v1"));
|
||||
AssertErrorMatchesSchema(message);
|
||||
// The bytes cultnet-rs's error_message.rs unsupported_schema_version case decodes and re-encodes.
|
||||
Assert.That(Convert.ToHexString(CultNetSchemaMessageSerialization.Serialize(message)), Is.EqualTo("85AD736368656D6156657273696F6EB063756C746E65742E6572726F722E7630A56572726F72D92C756E737570706F727465645F736368656D615F76657273696F6E3A2063756C746E65742E68656C6C6F2E7631AB726F7574696E6748696E74C0A4636F6465BA756E737570706F727465645F736368656D615F76657273696F6EA764657461696C7384A56669656C64AD736368656D6156657273696F6EA576616C7565B063756C746E65742E68656C6C6F2E7631A461734F66C0A763757272656E74C0"));
|
||||
}
|
||||
|
||||
private static void AssertErrorMatchesSchema(CultNetErrorMessage message)
|
||||
{
|
||||
var validator = new MiniJsonSchemaValidator(SchemaDir());
|
||||
|
|
|
|||
Loading…
Reference in a new issue