fix(protocol): fence gateway work recovery
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
{"version":"1","session_id":"session-1","gateway_id":"gateway-1","reconnect_sequence":2,"operation_id":"12345678-1234-1234-1234-123456789abc","revision":7,"lease_generation":3,"outcome":"applied","current_applied_revision":6}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":"1","session_id":"session-1","gateway_id":"gateway-1","reconnect_sequence":2,"operation_id":"12345678-1234-1234-1234-123456789ABC","revision":7,"lease_generation":3,"outcome":"unknown"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":"1","session_id":"session-1","gateway_id":"gateway-1","reconnect_sequence":2,"operation_id":"00000000-0000-0000-0000-000000000000","revision":7,"lease_generation":3,"outcome":"unknown"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":"1","session_id":"session-1","gateway_id":"gateway-1","reconnect_sequence":2,"acquisition":"poll","operation_id":"12345678-1234-1234-1234-123456789abc","revision":7}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"operation_id":"12345678-1234-1234-1234-123456789abc","session_id":"session-1","revision":7,"state":"pending","requested_bitrate_preference":{"mode":"auto"},"effective_bitrate_kbps":20000,"governing_policy_version":"policy-1","session_version":2,"created_at":"2099-01-01T00:00:00.100Z","deadline_at":"2099-01-01T00:00:30Z","updated_at":"2099-01-01T00:00:00Z"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"operation_id":"12345678-1234-1234-1234-123456789abc","session_id":"session-1","revision":7,"state":"pending","requested_bitrate_preference":{"mode":"auto"},"effective_bitrate_kbps":20000,"governing_policy_version":"policy-1","session_version":2,"created_at":"2099-01-01T00:00:00+00:00","deadline_at":"2099-01-01T00:00:30Z","updated_at":"2099-01-01T00:00:00Z"}
|
||||||
@@ -17,20 +17,28 @@
|
|||||||
"fixtures/invalid/bitrate-preference-auto-target.json",
|
"fixtures/invalid/bitrate-preference-auto-target.json",
|
||||||
"fixtures/invalid/browser-session-native-identity.json",
|
"fixtures/invalid/browser-session-native-identity.json",
|
||||||
"fixtures/invalid/capability-rc5-opaque.json",
|
"fixtures/invalid/capability-rc5-opaque.json",
|
||||||
|
"fixtures/invalid/gateway-quality-ack-contradictory.json",
|
||||||
|
"fixtures/invalid/gateway-quality-ack-uppercase-uuid.json",
|
||||||
|
"fixtures/invalid/gateway-quality-ack-zero-uuid.json",
|
||||||
|
"fixtures/invalid/gateway-quality-poll-coordinates.json",
|
||||||
"fixtures/invalid/manifest-provider-field.json",
|
"fixtures/invalid/manifest-provider-field.json",
|
||||||
"fixtures/invalid/native-session-missing-identity.json",
|
"fixtures/invalid/native-session-missing-identity.json",
|
||||||
"fixtures/invalid/provider-stream-policy-rc5.json",
|
"fixtures/invalid/provider-stream-policy-rc5.json",
|
||||||
|
"fixtures/invalid/quality-operation-noncanonical-fraction.json",
|
||||||
|
"fixtures/invalid/quality-operation-offset-time.json",
|
||||||
"fixtures/invalid/selected-session-descriptor-provider-field.json",
|
"fixtures/invalid/selected-session-descriptor-provider-field.json",
|
||||||
"fixtures/invalid/session-request-policy-snapshot.json",
|
"fixtures/invalid/session-request-policy-snapshot.json",
|
||||||
"fixtures/invalid/session-request-rc5.json",
|
"fixtures/invalid/session-request-rc5.json",
|
||||||
"fixtures/invalid/video-profile-unknown.json",
|
"fixtures/invalid/video-profile-unknown.json",
|
||||||
"fixtures/valid/authenticated-browser-session.json",
|
"fixtures/valid/authenticated-browser-session.json",
|
||||||
"fixtures/valid/authenticated-native-session.json",
|
"fixtures/valid/authenticated-native-session.json",
|
||||||
|
"fixtures/valid/gateway-quality-ack-applied.json",
|
||||||
|
"fixtures/valid/gateway-quality-poll.json",
|
||||||
"fixtures/valid/manifest.json",
|
"fixtures/valid/manifest.json",
|
||||||
"fixtures/valid/native-tunnel-credential.json",
|
"fixtures/valid/native-tunnel-credential.json",
|
||||||
"fixtures/valid/selected-session-descriptor.json",
|
"fixtures/valid/selected-session-descriptor.json",
|
||||||
"fixtures/valid/session-quality-limits.json",
|
"fixtures/valid/session-quality-limits.json",
|
||||||
"fixtures/valid/session-request.json"
|
"fixtures/valid/session-request.json"
|
||||||
],
|
],
|
||||||
"json_corpus_sha256": "2b111ca1c1723d6063aeafbf08c4e77972b16422ac3a84a054170039b2eff4d3"
|
"json_corpus_sha256": "08dea278526a523d21ea4ca6270ba1e33d44dc0d701681917d7ffc6fb13053d7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":"1","session_id":"session-1","gateway_id":"gateway-1","reconnect_sequence":2,"operation_id":"12345678-1234-1234-1234-123456789abc","revision":7,"lease_generation":3,"outcome":"applied","current_applied_revision":7}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":"1","session_id":"session-1","gateway_id":"gateway-1","reconnect_sequence":2,"acquisition":"poll"}
|
||||||
+71
-27
@@ -14,7 +14,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const SchemaSHA256 = "614fa11dd1f49b8e10bf21b8c10eadbc1468d16bfcc6ed0a26d29671a5e61300"
|
const SchemaSHA256 = "8c1ed430127cf5774a919f14ff4770b2509d322c8518bf30c412e1e89eeffced"
|
||||||
const ProtocolVersion = "1.0.0"
|
const ProtocolVersion = "1.0.0"
|
||||||
const CurrentWireVersion = "2"
|
const CurrentWireVersion = "2"
|
||||||
const NMinus1WireVersion = "1"
|
const NMinus1WireVersion = "1"
|
||||||
@@ -330,8 +330,9 @@ type GatewayQualityAck struct {
|
|||||||
ReconnectSequence int64 `json:"reconnect_sequence"`
|
ReconnectSequence int64 `json:"reconnect_sequence"`
|
||||||
OperationID string `json:"operation_id"`
|
OperationID string `json:"operation_id"`
|
||||||
Revision int64 `json:"revision"`
|
Revision int64 `json:"revision"`
|
||||||
|
LeaseGeneration int64 `json:"lease_generation"`
|
||||||
Outcome string `json:"outcome"`
|
Outcome string `json:"outcome"`
|
||||||
CurrentAppliedRevision int64 `json:"current_applied_revision"`
|
CurrentAppliedRevision *int64 `json:"current_applied_revision,omitempty"`
|
||||||
FailureCode string `json:"failure_code,omitempty"`
|
FailureCode string `json:"failure_code,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,6 +343,7 @@ type GatewayQualityWork struct {
|
|||||||
ReconnectSequence int64 `json:"reconnect_sequence"`
|
ReconnectSequence int64 `json:"reconnect_sequence"`
|
||||||
OperationID string `json:"operation_id"`
|
OperationID string `json:"operation_id"`
|
||||||
Revision int64 `json:"revision"`
|
Revision int64 `json:"revision"`
|
||||||
|
LeaseGeneration int64 `json:"lease_generation"`
|
||||||
LeaseExpiresAt string `json:"lease_expires_at"`
|
LeaseExpiresAt string `json:"lease_expires_at"`
|
||||||
SelectedDescriptor SelectedSessionDescriptor `json:"selected_descriptor"`
|
SelectedDescriptor SelectedSessionDescriptor `json:"selected_descriptor"`
|
||||||
CurrentAppliedRevision *int64 `json:"current_applied_revision,omitempty"`
|
CurrentAppliedRevision *int64 `json:"current_applied_revision,omitempty"`
|
||||||
@@ -352,8 +354,10 @@ type GatewayQualityWorkRequest struct {
|
|||||||
SessionID string `json:"session_id"`
|
SessionID string `json:"session_id"`
|
||||||
GatewayID string `json:"gateway_id"`
|
GatewayID string `json:"gateway_id"`
|
||||||
ReconnectSequence int64 `json:"reconnect_sequence"`
|
ReconnectSequence int64 `json:"reconnect_sequence"`
|
||||||
OperationID string `json:"operation_id"`
|
Acquisition string `json:"acquisition"`
|
||||||
Revision int64 `json:"revision"`
|
OperationID string `json:"operation_id,omitempty"`
|
||||||
|
Revision *int64 `json:"revision,omitempty"`
|
||||||
|
LeaseGeneration *int64 `json:"lease_generation,omitempty"`
|
||||||
CurrentAppliedRevision *int64 `json:"current_applied_revision,omitempty"`
|
CurrentAppliedRevision *int64 `json:"current_applied_revision,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,7 +401,8 @@ type GatewayStopWorkRequest struct {
|
|||||||
SessionID string `json:"session_id"`
|
SessionID string `json:"session_id"`
|
||||||
GatewayID string `json:"gateway_id"`
|
GatewayID string `json:"gateway_id"`
|
||||||
ReconnectSequence int64 `json:"reconnect_sequence"`
|
ReconnectSequence int64 `json:"reconnect_sequence"`
|
||||||
OperationID string `json:"operation_id"`
|
Acquisition string `json:"acquisition"`
|
||||||
|
OperationID string `json:"operation_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GatewayTelemetry struct {
|
type GatewayTelemetry struct {
|
||||||
@@ -3487,13 +3492,19 @@ func (v GatewayQualityAck) Validate() error {
|
|||||||
if v.Revision != 0 && v.Revision < 1 {
|
if v.Revision != 0 && v.Revision < 1 {
|
||||||
violations = append(violations, FieldViolation{Field: "revision", Code: "minimum"})
|
violations = append(violations, FieldViolation{Field: "revision", Code: "minimum"})
|
||||||
}
|
}
|
||||||
|
if v.LeaseGeneration == 0 {
|
||||||
|
violations = append(violations, FieldViolation{Field: "lease_generation", Code: "required"})
|
||||||
|
}
|
||||||
|
if v.LeaseGeneration != 0 && v.LeaseGeneration < 1 {
|
||||||
|
violations = append(violations, FieldViolation{Field: "lease_generation", Code: "minimum"})
|
||||||
|
}
|
||||||
if v.Outcome == "" {
|
if v.Outcome == "" {
|
||||||
violations = append(violations, FieldViolation{Field: "outcome", Code: "required"})
|
violations = append(violations, FieldViolation{Field: "outcome", Code: "required"})
|
||||||
}
|
}
|
||||||
if v.Outcome != "" && !(v.Outcome == "applied" || v.Outcome == "not_applied" || v.Outcome == "uncertain") {
|
if v.Outcome != "" && !(v.Outcome == "applied" || v.Outcome == "proven_prior" || v.Outcome == "unknown") {
|
||||||
violations = append(violations, FieldViolation{Field: "outcome", Code: "invalid_value"})
|
violations = append(violations, FieldViolation{Field: "outcome", Code: "invalid_value"})
|
||||||
}
|
}
|
||||||
if v.CurrentAppliedRevision != 0 && v.CurrentAppliedRevision < 0 {
|
if v.CurrentAppliedRevision != nil && *v.CurrentAppliedRevision != 0 && *v.CurrentAppliedRevision < 0 {
|
||||||
violations = append(violations, FieldViolation{Field: "current_applied_revision", Code: "minimum"})
|
violations = append(violations, FieldViolation{Field: "current_applied_revision", Code: "minimum"})
|
||||||
}
|
}
|
||||||
if len(v.FailureCode) < 1 && v.FailureCode != "" {
|
if len(v.FailureCode) < 1 && v.FailureCode != "" {
|
||||||
@@ -3502,6 +3513,15 @@ func (v GatewayQualityAck) Validate() error {
|
|||||||
if len(v.FailureCode) > 128 {
|
if len(v.FailureCode) > 128 {
|
||||||
violations = append(violations, FieldViolation{Field: "failure_code", Code: "max_length"})
|
violations = append(violations, FieldViolation{Field: "failure_code", Code: "max_length"})
|
||||||
}
|
}
|
||||||
|
if v.Outcome == "applied" && (v.CurrentAppliedRevision == nil || *v.CurrentAppliedRevision != v.Revision) {
|
||||||
|
violations = append(violations, FieldViolation{Field: "current_applied_revision", Code: "invalid_tagged_value"})
|
||||||
|
}
|
||||||
|
if v.Outcome == "proven_prior" && (v.CurrentAppliedRevision == nil || *v.CurrentAppliedRevision >= v.Revision) {
|
||||||
|
violations = append(violations, FieldViolation{Field: "current_applied_revision", Code: "invalid_tagged_value"})
|
||||||
|
}
|
||||||
|
if v.Outcome == "unknown" && v.CurrentAppliedRevision != nil {
|
||||||
|
violations = append(violations, FieldViolation{Field: "current_applied_revision", Code: "invalid_tagged_value"})
|
||||||
|
}
|
||||||
if len(violations) > 0 {
|
if len(violations) > 0 {
|
||||||
return ValidationError{Violations: violations}
|
return ValidationError{Violations: violations}
|
||||||
}
|
}
|
||||||
@@ -3520,12 +3540,12 @@ func DecodeGatewayQualityAck(data []byte) (GatewayQualityAck, error) {
|
|||||||
if err := json.Unmarshal(data, &fields); err != nil {
|
if err := json.Unmarshal(data, &fields); err != nil {
|
||||||
return value, err
|
return value, err
|
||||||
}
|
}
|
||||||
if raw, ok := fields["current_applied_revision"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
|
||||||
return value, ValidationError{Violations: []FieldViolation{{Field: "current_applied_revision", Code: "required"}}}
|
|
||||||
}
|
|
||||||
if raw, ok := fields["gateway_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
if raw, ok := fields["gateway_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||||
return value, ValidationError{Violations: []FieldViolation{{Field: "gateway_id", Code: "required"}}}
|
return value, ValidationError{Violations: []FieldViolation{{Field: "gateway_id", Code: "required"}}}
|
||||||
}
|
}
|
||||||
|
if raw, ok := fields["lease_generation"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||||
|
return value, ValidationError{Violations: []FieldViolation{{Field: "lease_generation", Code: "required"}}}
|
||||||
|
}
|
||||||
if raw, ok := fields["operation_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
if raw, ok := fields["operation_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||||
return value, ValidationError{Violations: []FieldViolation{{Field: "operation_id", Code: "required"}}}
|
return value, ValidationError{Violations: []FieldViolation{{Field: "operation_id", Code: "required"}}}
|
||||||
}
|
}
|
||||||
@@ -3616,6 +3636,12 @@ func (v GatewayQualityWork) Validate() error {
|
|||||||
if v.Revision != 0 && v.Revision < 1 {
|
if v.Revision != 0 && v.Revision < 1 {
|
||||||
violations = append(violations, FieldViolation{Field: "revision", Code: "minimum"})
|
violations = append(violations, FieldViolation{Field: "revision", Code: "minimum"})
|
||||||
}
|
}
|
||||||
|
if v.LeaseGeneration == 0 {
|
||||||
|
violations = append(violations, FieldViolation{Field: "lease_generation", Code: "required"})
|
||||||
|
}
|
||||||
|
if v.LeaseGeneration != 0 && v.LeaseGeneration < 1 {
|
||||||
|
violations = append(violations, FieldViolation{Field: "lease_generation", Code: "minimum"})
|
||||||
|
}
|
||||||
if v.LeaseExpiresAt == "" {
|
if v.LeaseExpiresAt == "" {
|
||||||
violations = append(violations, FieldViolation{Field: "lease_expires_at", Code: "required"})
|
violations = append(violations, FieldViolation{Field: "lease_expires_at", Code: "required"})
|
||||||
}
|
}
|
||||||
@@ -3660,6 +3686,9 @@ func DecodeGatewayQualityWork(data []byte) (GatewayQualityWork, error) {
|
|||||||
if raw, ok := fields["lease_expires_at"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
if raw, ok := fields["lease_expires_at"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||||
return value, ValidationError{Violations: []FieldViolation{{Field: "lease_expires_at", Code: "required"}}}
|
return value, ValidationError{Violations: []FieldViolation{{Field: "lease_expires_at", Code: "required"}}}
|
||||||
}
|
}
|
||||||
|
if raw, ok := fields["lease_generation"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||||
|
return value, ValidationError{Violations: []FieldViolation{{Field: "lease_generation", Code: "required"}}}
|
||||||
|
}
|
||||||
if raw, ok := fields["operation_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
if raw, ok := fields["operation_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||||
return value, ValidationError{Violations: []FieldViolation{{Field: "operation_id", Code: "required"}}}
|
return value, ValidationError{Violations: []FieldViolation{{Field: "operation_id", Code: "required"}}}
|
||||||
}
|
}
|
||||||
@@ -3732,8 +3761,11 @@ func (v GatewayQualityWorkRequest) Validate() error {
|
|||||||
if v.ReconnectSequence != 0 && v.ReconnectSequence < 0 {
|
if v.ReconnectSequence != 0 && v.ReconnectSequence < 0 {
|
||||||
violations = append(violations, FieldViolation{Field: "reconnect_sequence", Code: "minimum"})
|
violations = append(violations, FieldViolation{Field: "reconnect_sequence", Code: "minimum"})
|
||||||
}
|
}
|
||||||
if v.OperationID == "" {
|
if v.Acquisition == "" {
|
||||||
violations = append(violations, FieldViolation{Field: "operation_id", Code: "required"})
|
violations = append(violations, FieldViolation{Field: "acquisition", Code: "required"})
|
||||||
|
}
|
||||||
|
if v.Acquisition != "" && !(v.Acquisition == "poll" || v.Acquisition == "prompt" || v.Acquisition == "observation") {
|
||||||
|
violations = append(violations, FieldViolation{Field: "acquisition", Code: "invalid_value"})
|
||||||
}
|
}
|
||||||
if len(v.OperationID) < 36 && v.OperationID != "" {
|
if len(v.OperationID) < 36 && v.OperationID != "" {
|
||||||
violations = append(violations, FieldViolation{Field: "operation_id", Code: "min_length"})
|
violations = append(violations, FieldViolation{Field: "operation_id", Code: "min_length"})
|
||||||
@@ -3744,15 +3776,24 @@ func (v GatewayQualityWorkRequest) Validate() error {
|
|||||||
if v.OperationID != "" && !validCanonicalUUID(v.OperationID) {
|
if v.OperationID != "" && !validCanonicalUUID(v.OperationID) {
|
||||||
violations = append(violations, FieldViolation{Field: "operation_id", Code: "invalid_uuid"})
|
violations = append(violations, FieldViolation{Field: "operation_id", Code: "invalid_uuid"})
|
||||||
}
|
}
|
||||||
if v.Revision == 0 {
|
if v.Revision != nil && *v.Revision != 0 && *v.Revision < 1 {
|
||||||
violations = append(violations, FieldViolation{Field: "revision", Code: "required"})
|
|
||||||
}
|
|
||||||
if v.Revision != 0 && v.Revision < 1 {
|
|
||||||
violations = append(violations, FieldViolation{Field: "revision", Code: "minimum"})
|
violations = append(violations, FieldViolation{Field: "revision", Code: "minimum"})
|
||||||
}
|
}
|
||||||
|
if v.LeaseGeneration != nil && *v.LeaseGeneration != 0 && *v.LeaseGeneration < 1 {
|
||||||
|
violations = append(violations, FieldViolation{Field: "lease_generation", Code: "minimum"})
|
||||||
|
}
|
||||||
if v.CurrentAppliedRevision != nil && *v.CurrentAppliedRevision != 0 && *v.CurrentAppliedRevision < 0 {
|
if v.CurrentAppliedRevision != nil && *v.CurrentAppliedRevision != 0 && *v.CurrentAppliedRevision < 0 {
|
||||||
violations = append(violations, FieldViolation{Field: "current_applied_revision", Code: "minimum"})
|
violations = append(violations, FieldViolation{Field: "current_applied_revision", Code: "minimum"})
|
||||||
}
|
}
|
||||||
|
if v.Acquisition == "poll" && (v.OperationID != "" || v.Revision != nil || v.LeaseGeneration != nil || v.CurrentAppliedRevision != nil) {
|
||||||
|
violations = append(violations, FieldViolation{Field: "acquisition", Code: "invalid_tagged_value"})
|
||||||
|
}
|
||||||
|
if v.Acquisition == "prompt" && (v.OperationID == "" || v.Revision == nil || v.LeaseGeneration != nil || v.CurrentAppliedRevision != nil) {
|
||||||
|
violations = append(violations, FieldViolation{Field: "acquisition", Code: "invalid_tagged_value"})
|
||||||
|
}
|
||||||
|
if v.Acquisition == "observation" && (v.OperationID == "" || v.Revision == nil || v.LeaseGeneration == nil || v.CurrentAppliedRevision == nil) {
|
||||||
|
violations = append(violations, FieldViolation{Field: "acquisition", Code: "invalid_tagged_value"})
|
||||||
|
}
|
||||||
if len(violations) > 0 {
|
if len(violations) > 0 {
|
||||||
return ValidationError{Violations: violations}
|
return ValidationError{Violations: violations}
|
||||||
}
|
}
|
||||||
@@ -3771,18 +3812,15 @@ func DecodeGatewayQualityWorkRequest(data []byte) (GatewayQualityWorkRequest, er
|
|||||||
if err := json.Unmarshal(data, &fields); err != nil {
|
if err := json.Unmarshal(data, &fields); err != nil {
|
||||||
return value, err
|
return value, err
|
||||||
}
|
}
|
||||||
|
if raw, ok := fields["acquisition"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||||
|
return value, ValidationError{Violations: []FieldViolation{{Field: "acquisition", Code: "required"}}}
|
||||||
|
}
|
||||||
if raw, ok := fields["gateway_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
if raw, ok := fields["gateway_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||||
return value, ValidationError{Violations: []FieldViolation{{Field: "gateway_id", Code: "required"}}}
|
return value, ValidationError{Violations: []FieldViolation{{Field: "gateway_id", Code: "required"}}}
|
||||||
}
|
}
|
||||||
if raw, ok := fields["operation_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
|
||||||
return value, ValidationError{Violations: []FieldViolation{{Field: "operation_id", Code: "required"}}}
|
|
||||||
}
|
|
||||||
if raw, ok := fields["reconnect_sequence"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
if raw, ok := fields["reconnect_sequence"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||||
return value, ValidationError{Violations: []FieldViolation{{Field: "reconnect_sequence", Code: "required"}}}
|
return value, ValidationError{Violations: []FieldViolation{{Field: "reconnect_sequence", Code: "required"}}}
|
||||||
}
|
}
|
||||||
if raw, ok := fields["revision"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
|
||||||
return value, ValidationError{Violations: []FieldViolation{{Field: "revision", Code: "required"}}}
|
|
||||||
}
|
|
||||||
if raw, ok := fields["session_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
if raw, ok := fields["session_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||||
return value, ValidationError{Violations: []FieldViolation{{Field: "session_id", Code: "required"}}}
|
return value, ValidationError{Violations: []FieldViolation{{Field: "session_id", Code: "required"}}}
|
||||||
}
|
}
|
||||||
@@ -4273,8 +4311,11 @@ func (v GatewayStopWorkRequest) Validate() error {
|
|||||||
if v.ReconnectSequence != 0 && v.ReconnectSequence < 0 {
|
if v.ReconnectSequence != 0 && v.ReconnectSequence < 0 {
|
||||||
violations = append(violations, FieldViolation{Field: "reconnect_sequence", Code: "minimum"})
|
violations = append(violations, FieldViolation{Field: "reconnect_sequence", Code: "minimum"})
|
||||||
}
|
}
|
||||||
if v.OperationID == "" {
|
if v.Acquisition == "" {
|
||||||
violations = append(violations, FieldViolation{Field: "operation_id", Code: "required"})
|
violations = append(violations, FieldViolation{Field: "acquisition", Code: "required"})
|
||||||
|
}
|
||||||
|
if v.Acquisition != "" && !(v.Acquisition == "poll" || v.Acquisition == "prompt") {
|
||||||
|
violations = append(violations, FieldViolation{Field: "acquisition", Code: "invalid_value"})
|
||||||
}
|
}
|
||||||
if len(v.OperationID) < 36 && v.OperationID != "" {
|
if len(v.OperationID) < 36 && v.OperationID != "" {
|
||||||
violations = append(violations, FieldViolation{Field: "operation_id", Code: "min_length"})
|
violations = append(violations, FieldViolation{Field: "operation_id", Code: "min_length"})
|
||||||
@@ -4285,6 +4326,9 @@ func (v GatewayStopWorkRequest) Validate() error {
|
|||||||
if v.OperationID != "" && !validCanonicalUUID(v.OperationID) {
|
if v.OperationID != "" && !validCanonicalUUID(v.OperationID) {
|
||||||
violations = append(violations, FieldViolation{Field: "operation_id", Code: "invalid_uuid"})
|
violations = append(violations, FieldViolation{Field: "operation_id", Code: "invalid_uuid"})
|
||||||
}
|
}
|
||||||
|
if v.Acquisition == "poll" && v.OperationID != "" || v.Acquisition == "prompt" && v.OperationID == "" {
|
||||||
|
violations = append(violations, FieldViolation{Field: "acquisition", Code: "invalid_tagged_value"})
|
||||||
|
}
|
||||||
if len(violations) > 0 {
|
if len(violations) > 0 {
|
||||||
return ValidationError{Violations: violations}
|
return ValidationError{Violations: violations}
|
||||||
}
|
}
|
||||||
@@ -4303,12 +4347,12 @@ func DecodeGatewayStopWorkRequest(data []byte) (GatewayStopWorkRequest, error) {
|
|||||||
if err := json.Unmarshal(data, &fields); err != nil {
|
if err := json.Unmarshal(data, &fields); err != nil {
|
||||||
return value, err
|
return value, err
|
||||||
}
|
}
|
||||||
|
if raw, ok := fields["acquisition"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||||
|
return value, ValidationError{Violations: []FieldViolation{{Field: "acquisition", Code: "required"}}}
|
||||||
|
}
|
||||||
if raw, ok := fields["gateway_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
if raw, ok := fields["gateway_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||||
return value, ValidationError{Violations: []FieldViolation{{Field: "gateway_id", Code: "required"}}}
|
return value, ValidationError{Violations: []FieldViolation{{Field: "gateway_id", Code: "required"}}}
|
||||||
}
|
}
|
||||||
if raw, ok := fields["operation_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
|
||||||
return value, ValidationError{Violations: []FieldViolation{{Field: "operation_id", Code: "required"}}}
|
|
||||||
}
|
|
||||||
if raw, ok := fields["reconnect_sequence"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
if raw, ok := fields["reconnect_sequence"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||||
return value, ValidationError{Violations: []FieldViolation{{Field: "reconnect_sequence", Code: "required"}}}
|
return value, ValidationError{Violations: []FieldViolation{{Field: "reconnect_sequence", Code: "required"}}}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -12,7 +12,7 @@
|
|||||||
"3"
|
"3"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"generator_sha256": "9f6e95fac0f3c8389d8111e6ba0a03b48e155b71e345a11640ef824b8b3a4910",
|
"generator_sha256": "c5756780f59a82d8f56762e52f58e22a4d6aca6648157f45e5bad6559b9deeec",
|
||||||
"protocol_version": "1.0.0",
|
"protocol_version": "1.0.0",
|
||||||
"schema_sha256": "614fa11dd1f49b8e10bf21b8c10eadbc1468d16bfcc6ed0a26d29671a5e61300"
|
"schema_sha256": "8c1ed430127cf5774a919f14ff4770b2509d322c8518bf30c412e1e89eeffced"
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
+58
-28
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by tools/generate.py; DO NOT EDIT.
|
// Code generated by tools/generate.py; DO NOT EDIT.
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
pub const SCHEMA_SHA256: &str = "614fa11dd1f49b8e10bf21b8c10eadbc1468d16bfcc6ed0a26d29671a5e61300";
|
pub const SCHEMA_SHA256: &str = "8c1ed430127cf5774a919f14ff4770b2509d322c8518bf30c412e1e89eeffced";
|
||||||
pub const CURRENT_WIRE_VERSION: &str = "2";
|
pub const CURRENT_WIRE_VERSION: &str = "2";
|
||||||
pub const N_MINUS_1_WIRE_VERSION: &str = "1";
|
pub const N_MINUS_1_WIRE_VERSION: &str = "1";
|
||||||
pub const N_MINUS_2_WIRE_VERSION: &str = "0";
|
pub const N_MINUS_2_WIRE_VERSION: &str = "0";
|
||||||
@@ -1055,14 +1055,15 @@ pub struct GatewayQualityAck {
|
|||||||
reconnectSequence: i64,
|
reconnectSequence: i64,
|
||||||
operationId: String,
|
operationId: String,
|
||||||
revision: i64,
|
revision: i64,
|
||||||
|
leaseGeneration: i64,
|
||||||
outcome: String,
|
outcome: String,
|
||||||
currentAppliedRevision: i64,
|
currentAppliedRevision: Option<i64>,
|
||||||
failureCode: Option<String>,
|
failureCode: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GatewayQualityAck {
|
impl GatewayQualityAck {
|
||||||
pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, operationId: String, revision: i64, outcome: String, currentAppliedRevision: i64, failureCode: Option<String>) -> Result<Self, ValidationError> {
|
pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, operationId: String, revision: i64, leaseGeneration: i64, outcome: String, currentAppliedRevision: Option<i64>, failureCode: Option<String>) -> Result<Self, ValidationError> {
|
||||||
let value = Self { version, sessionId, gatewayId, reconnectSequence, operationId, revision, outcome, currentAppliedRevision, failureCode };
|
let value = Self { version, sessionId, gatewayId, reconnectSequence, operationId, revision, leaseGeneration, outcome, currentAppliedRevision, failureCode };
|
||||||
value.validate()?;
|
value.validate()?;
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
@@ -1080,12 +1081,18 @@ impl GatewayQualityAck {
|
|||||||
if self.operationId.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); }
|
if self.operationId.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); }
|
||||||
if !valid_canonical_uuid(self.operationId.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); }
|
if !valid_canonical_uuid(self.operationId.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); }
|
||||||
if self.revision < 1 { return Err(ValidationError::new("revision", "minimum")); }
|
if self.revision < 1 { return Err(ValidationError::new("revision", "minimum")); }
|
||||||
if self.outcome != "applied" && self.outcome != "not_applied" && self.outcome != "uncertain" { return Err(ValidationError::new("outcome", "invalid_value")); }
|
if self.leaseGeneration < 1 { return Err(ValidationError::new("lease_generation", "minimum")); }
|
||||||
if self.currentAppliedRevision < 0 { return Err(ValidationError::new("current_applied_revision", "minimum")); }
|
if self.outcome != "applied" && self.outcome != "proven_prior" && self.outcome != "unknown" { return Err(ValidationError::new("outcome", "invalid_value")); }
|
||||||
|
if let Some(value) = &self.currentAppliedRevision {
|
||||||
|
if *value < 0 { return Err(ValidationError::new("current_applied_revision", "minimum")); }
|
||||||
|
}
|
||||||
if let Some(value) = &self.failureCode {
|
if let Some(value) = &self.failureCode {
|
||||||
if !value.is_empty() && value.len() < 1 { return Err(ValidationError::new("failure_code", "min_length")); }
|
if !value.is_empty() && value.len() < 1 { return Err(ValidationError::new("failure_code", "min_length")); }
|
||||||
if value.len() > 128 { return Err(ValidationError::new("failure_code", "max_length")); }
|
if value.len() > 128 { return Err(ValidationError::new("failure_code", "max_length")); }
|
||||||
}
|
}
|
||||||
|
if self.outcome == "applied" && self.currentAppliedRevision != Some(self.revision) { return Err(ValidationError::new("current_applied_revision", "invalid_tagged_value")); }
|
||||||
|
if self.outcome == "proven_prior" && self.currentAppliedRevision.map_or(true, |current| current >= self.revision) { return Err(ValidationError::new("current_applied_revision", "invalid_tagged_value")); }
|
||||||
|
if self.outcome == "unknown" && self.currentAppliedRevision.is_some() { return Err(ValidationError::new("current_applied_revision", "invalid_tagged_value")); }
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn version(&self) -> &String { &self.version }
|
pub fn version(&self) -> &String { &self.version }
|
||||||
@@ -1094,8 +1101,9 @@ impl GatewayQualityAck {
|
|||||||
pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence }
|
pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence }
|
||||||
pub fn operationId(&self) -> &String { &self.operationId }
|
pub fn operationId(&self) -> &String { &self.operationId }
|
||||||
pub fn revision(&self) -> &i64 { &self.revision }
|
pub fn revision(&self) -> &i64 { &self.revision }
|
||||||
|
pub fn leaseGeneration(&self) -> &i64 { &self.leaseGeneration }
|
||||||
pub fn outcome(&self) -> &String { &self.outcome }
|
pub fn outcome(&self) -> &String { &self.outcome }
|
||||||
pub fn currentAppliedRevision(&self) -> &i64 { &self.currentAppliedRevision }
|
pub fn currentAppliedRevision(&self) -> &Option<i64> { &self.currentAppliedRevision }
|
||||||
pub fn failureCode(&self) -> &Option<String> { &self.failureCode }
|
pub fn failureCode(&self) -> &Option<String> { &self.failureCode }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1107,14 +1115,15 @@ pub struct GatewayQualityWork {
|
|||||||
reconnectSequence: i64,
|
reconnectSequence: i64,
|
||||||
operationId: String,
|
operationId: String,
|
||||||
revision: i64,
|
revision: i64,
|
||||||
|
leaseGeneration: i64,
|
||||||
leaseExpiresAt: String,
|
leaseExpiresAt: String,
|
||||||
selectedDescriptor: SelectedSessionDescriptor,
|
selectedDescriptor: SelectedSessionDescriptor,
|
||||||
currentAppliedRevision: Option<i64>,
|
currentAppliedRevision: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GatewayQualityWork {
|
impl GatewayQualityWork {
|
||||||
pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, operationId: String, revision: i64, leaseExpiresAt: String, selectedDescriptor: SelectedSessionDescriptor, currentAppliedRevision: Option<i64>) -> Result<Self, ValidationError> {
|
pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, operationId: String, revision: i64, leaseGeneration: i64, leaseExpiresAt: String, selectedDescriptor: SelectedSessionDescriptor, currentAppliedRevision: Option<i64>) -> Result<Self, ValidationError> {
|
||||||
let value = Self { version, sessionId, gatewayId, reconnectSequence, operationId, revision, leaseExpiresAt, selectedDescriptor, currentAppliedRevision };
|
let value = Self { version, sessionId, gatewayId, reconnectSequence, operationId, revision, leaseGeneration, leaseExpiresAt, selectedDescriptor, currentAppliedRevision };
|
||||||
value.validate()?;
|
value.validate()?;
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
@@ -1132,6 +1141,7 @@ impl GatewayQualityWork {
|
|||||||
if self.operationId.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); }
|
if self.operationId.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); }
|
||||||
if !valid_canonical_uuid(self.operationId.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); }
|
if !valid_canonical_uuid(self.operationId.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); }
|
||||||
if self.revision < 1 { return Err(ValidationError::new("revision", "minimum")); }
|
if self.revision < 1 { return Err(ValidationError::new("revision", "minimum")); }
|
||||||
|
if self.leaseGeneration < 1 { return Err(ValidationError::new("lease_generation", "minimum")); }
|
||||||
if self.leaseExpiresAt.len() > 64 { return Err(ValidationError::new("lease_expires_at", "max_length")); }
|
if self.leaseExpiresAt.len() > 64 { return Err(ValidationError::new("lease_expires_at", "max_length")); }
|
||||||
if !valid_rfc3339_utc(self.leaseExpiresAt.as_str()) { return Err(ValidationError::new("lease_expires_at", "invalid_time")); }
|
if !valid_rfc3339_utc(self.leaseExpiresAt.as_str()) { return Err(ValidationError::new("lease_expires_at", "invalid_time")); }
|
||||||
self.selectedDescriptor.validate().map_err(|_| ValidationError::new("selected_descriptor", "invalid_object"))?;
|
self.selectedDescriptor.validate().map_err(|_| ValidationError::new("selected_descriptor", "invalid_object"))?;
|
||||||
@@ -1146,6 +1156,7 @@ impl GatewayQualityWork {
|
|||||||
pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence }
|
pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence }
|
||||||
pub fn operationId(&self) -> &String { &self.operationId }
|
pub fn operationId(&self) -> &String { &self.operationId }
|
||||||
pub fn revision(&self) -> &i64 { &self.revision }
|
pub fn revision(&self) -> &i64 { &self.revision }
|
||||||
|
pub fn leaseGeneration(&self) -> &i64 { &self.leaseGeneration }
|
||||||
pub fn leaseExpiresAt(&self) -> &String { &self.leaseExpiresAt }
|
pub fn leaseExpiresAt(&self) -> &String { &self.leaseExpiresAt }
|
||||||
pub fn selectedDescriptor(&self) -> &SelectedSessionDescriptor { &self.selectedDescriptor }
|
pub fn selectedDescriptor(&self) -> &SelectedSessionDescriptor { &self.selectedDescriptor }
|
||||||
pub fn currentAppliedRevision(&self) -> &Option<i64> { &self.currentAppliedRevision }
|
pub fn currentAppliedRevision(&self) -> &Option<i64> { &self.currentAppliedRevision }
|
||||||
@@ -1157,14 +1168,16 @@ pub struct GatewayQualityWorkRequest {
|
|||||||
sessionId: String,
|
sessionId: String,
|
||||||
gatewayId: String,
|
gatewayId: String,
|
||||||
reconnectSequence: i64,
|
reconnectSequence: i64,
|
||||||
operationId: String,
|
acquisition: String,
|
||||||
revision: i64,
|
operationId: Option<String>,
|
||||||
|
revision: Option<i64>,
|
||||||
|
leaseGeneration: Option<i64>,
|
||||||
currentAppliedRevision: Option<i64>,
|
currentAppliedRevision: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GatewayQualityWorkRequest {
|
impl GatewayQualityWorkRequest {
|
||||||
pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, operationId: String, revision: i64, currentAppliedRevision: Option<i64>) -> Result<Self, ValidationError> {
|
pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, acquisition: String, operationId: Option<String>, revision: Option<i64>, leaseGeneration: Option<i64>, currentAppliedRevision: Option<i64>) -> Result<Self, ValidationError> {
|
||||||
let value = Self { version, sessionId, gatewayId, reconnectSequence, operationId, revision, currentAppliedRevision };
|
let value = Self { version, sessionId, gatewayId, reconnectSequence, acquisition, operationId, revision, leaseGeneration, currentAppliedRevision };
|
||||||
value.validate()?;
|
value.validate()?;
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
@@ -1177,22 +1190,34 @@ impl GatewayQualityWorkRequest {
|
|||||||
if !self.gatewayId.is_empty() && self.gatewayId.len() < 1 { return Err(ValidationError::new("gateway_id", "min_length")); }
|
if !self.gatewayId.is_empty() && self.gatewayId.len() < 1 { return Err(ValidationError::new("gateway_id", "min_length")); }
|
||||||
if self.gatewayId.len() > 128 { return Err(ValidationError::new("gateway_id", "max_length")); }
|
if self.gatewayId.len() > 128 { return Err(ValidationError::new("gateway_id", "max_length")); }
|
||||||
if self.reconnectSequence < 0 { return Err(ValidationError::new("reconnect_sequence", "minimum")); }
|
if self.reconnectSequence < 0 { return Err(ValidationError::new("reconnect_sequence", "minimum")); }
|
||||||
if self.operationId.is_empty() { return Err(ValidationError::new("operation_id", "required")); }
|
if self.acquisition != "poll" && self.acquisition != "prompt" && self.acquisition != "observation" { return Err(ValidationError::new("acquisition", "invalid_value")); }
|
||||||
if !self.operationId.is_empty() && self.operationId.len() < 36 { return Err(ValidationError::new("operation_id", "min_length")); }
|
if let Some(value) = &self.operationId {
|
||||||
if self.operationId.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); }
|
if !value.is_empty() && value.len() < 36 { return Err(ValidationError::new("operation_id", "min_length")); }
|
||||||
if !valid_canonical_uuid(self.operationId.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); }
|
if value.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); }
|
||||||
if self.revision < 1 { return Err(ValidationError::new("revision", "minimum")); }
|
if !valid_canonical_uuid(value.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); }
|
||||||
|
}
|
||||||
|
if let Some(value) = &self.revision {
|
||||||
|
if *value < 1 { return Err(ValidationError::new("revision", "minimum")); }
|
||||||
|
}
|
||||||
|
if let Some(value) = &self.leaseGeneration {
|
||||||
|
if *value < 1 { return Err(ValidationError::new("lease_generation", "minimum")); }
|
||||||
|
}
|
||||||
if let Some(value) = &self.currentAppliedRevision {
|
if let Some(value) = &self.currentAppliedRevision {
|
||||||
if *value < 0 { return Err(ValidationError::new("current_applied_revision", "minimum")); }
|
if *value < 0 { return Err(ValidationError::new("current_applied_revision", "minimum")); }
|
||||||
}
|
}
|
||||||
|
if self.acquisition == "poll" && (self.operationId.is_some() || self.revision.is_some() || self.leaseGeneration.is_some() || self.currentAppliedRevision.is_some()) { return Err(ValidationError::new("acquisition", "invalid_tagged_value")); }
|
||||||
|
if self.acquisition == "prompt" && (self.operationId.is_none() || self.revision.is_none() || self.leaseGeneration.is_some() || self.currentAppliedRevision.is_some()) { return Err(ValidationError::new("acquisition", "invalid_tagged_value")); }
|
||||||
|
if self.acquisition == "observation" && (self.operationId.is_none() || self.revision.is_none() || self.leaseGeneration.is_none() || self.currentAppliedRevision.is_none()) { return Err(ValidationError::new("acquisition", "invalid_tagged_value")); }
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn version(&self) -> &String { &self.version }
|
pub fn version(&self) -> &String { &self.version }
|
||||||
pub fn sessionId(&self) -> &String { &self.sessionId }
|
pub fn sessionId(&self) -> &String { &self.sessionId }
|
||||||
pub fn gatewayId(&self) -> &String { &self.gatewayId }
|
pub fn gatewayId(&self) -> &String { &self.gatewayId }
|
||||||
pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence }
|
pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence }
|
||||||
pub fn operationId(&self) -> &String { &self.operationId }
|
pub fn acquisition(&self) -> &String { &self.acquisition }
|
||||||
pub fn revision(&self) -> &i64 { &self.revision }
|
pub fn operationId(&self) -> &Option<String> { &self.operationId }
|
||||||
|
pub fn revision(&self) -> &Option<i64> { &self.revision }
|
||||||
|
pub fn leaseGeneration(&self) -> &Option<i64> { &self.leaseGeneration }
|
||||||
pub fn currentAppliedRevision(&self) -> &Option<i64> { &self.currentAppliedRevision }
|
pub fn currentAppliedRevision(&self) -> &Option<i64> { &self.currentAppliedRevision }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1362,12 +1387,13 @@ pub struct GatewayStopWorkRequest {
|
|||||||
sessionId: String,
|
sessionId: String,
|
||||||
gatewayId: String,
|
gatewayId: String,
|
||||||
reconnectSequence: i64,
|
reconnectSequence: i64,
|
||||||
operationId: String,
|
acquisition: String,
|
||||||
|
operationId: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GatewayStopWorkRequest {
|
impl GatewayStopWorkRequest {
|
||||||
pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, operationId: String) -> Result<Self, ValidationError> {
|
pub fn new(version: String, sessionId: String, gatewayId: String, reconnectSequence: i64, acquisition: String, operationId: Option<String>) -> Result<Self, ValidationError> {
|
||||||
let value = Self { version, sessionId, gatewayId, reconnectSequence, operationId };
|
let value = Self { version, sessionId, gatewayId, reconnectSequence, acquisition, operationId };
|
||||||
value.validate()?;
|
value.validate()?;
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
@@ -1380,17 +1406,21 @@ impl GatewayStopWorkRequest {
|
|||||||
if !self.gatewayId.is_empty() && self.gatewayId.len() < 1 { return Err(ValidationError::new("gateway_id", "min_length")); }
|
if !self.gatewayId.is_empty() && self.gatewayId.len() < 1 { return Err(ValidationError::new("gateway_id", "min_length")); }
|
||||||
if self.gatewayId.len() > 128 { return Err(ValidationError::new("gateway_id", "max_length")); }
|
if self.gatewayId.len() > 128 { return Err(ValidationError::new("gateway_id", "max_length")); }
|
||||||
if self.reconnectSequence < 0 { return Err(ValidationError::new("reconnect_sequence", "minimum")); }
|
if self.reconnectSequence < 0 { return Err(ValidationError::new("reconnect_sequence", "minimum")); }
|
||||||
if self.operationId.is_empty() { return Err(ValidationError::new("operation_id", "required")); }
|
if self.acquisition != "poll" && self.acquisition != "prompt" { return Err(ValidationError::new("acquisition", "invalid_value")); }
|
||||||
if !self.operationId.is_empty() && self.operationId.len() < 36 { return Err(ValidationError::new("operation_id", "min_length")); }
|
if let Some(value) = &self.operationId {
|
||||||
if self.operationId.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); }
|
if !value.is_empty() && value.len() < 36 { return Err(ValidationError::new("operation_id", "min_length")); }
|
||||||
if !valid_canonical_uuid(self.operationId.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); }
|
if value.len() > 36 { return Err(ValidationError::new("operation_id", "max_length")); }
|
||||||
|
if !valid_canonical_uuid(value.as_str()) { return Err(ValidationError::new("operation_id", "invalid_uuid")); }
|
||||||
|
}
|
||||||
|
if self.acquisition == "poll" && self.operationId.is_some() || self.acquisition == "prompt" && self.operationId.is_none() { return Err(ValidationError::new("acquisition", "invalid_tagged_value")); }
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn version(&self) -> &String { &self.version }
|
pub fn version(&self) -> &String { &self.version }
|
||||||
pub fn sessionId(&self) -> &String { &self.sessionId }
|
pub fn sessionId(&self) -> &String { &self.sessionId }
|
||||||
pub fn gatewayId(&self) -> &String { &self.gatewayId }
|
pub fn gatewayId(&self) -> &String { &self.gatewayId }
|
||||||
pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence }
|
pub fn reconnectSequence(&self) -> &i64 { &self.reconnectSequence }
|
||||||
pub fn operationId(&self) -> &String { &self.operationId }
|
pub fn acquisition(&self) -> &String { &self.acquisition }
|
||||||
|
pub fn operationId(&self) -> &Option<String> { &self.operationId }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
|||||||
+65
-28
@@ -1,13 +1,14 @@
|
|||||||
// Code generated by tools/generate.py; DO NOT EDIT.
|
// Code generated by tools/generate.py; DO NOT EDIT.
|
||||||
import Foundation
|
import Foundation
|
||||||
public typealias JSONObject = [String: String]
|
public typealias JSONObject = [String: String]
|
||||||
public let schemaSHA256 = "614fa11dd1f49b8e10bf21b8c10eadbc1468d16bfcc6ed0a26d29671a5e61300"
|
public let schemaSHA256 = "8c1ed430127cf5774a919f14ff4770b2509d322c8518bf30c412e1e89eeffced"
|
||||||
public let currentWireVersion = "2"
|
public let currentWireVersion = "2"
|
||||||
public let nMinus1WireVersion = "1"
|
public let nMinus1WireVersion = "1"
|
||||||
public let nMinus2WireVersion = "0"
|
public let nMinus2WireVersion = "0"
|
||||||
public struct ContractValidationError: Error, Equatable { public let field: String; public let code: String }
|
public struct ContractValidationError: Error, Equatable { public let field: String; public let code: String }
|
||||||
private struct AnyCodingKey: CodingKey { let stringValue: String; let intValue: Int?; init?(stringValue: String) { self.stringValue = stringValue; self.intValue = nil }; init?(intValue: Int) { self.stringValue = String(intValue); self.intValue = intValue } }
|
private struct AnyCodingKey: CodingKey { let stringValue: String; let intValue: Int?; init?(stringValue: String) { self.stringValue = stringValue; self.intValue = nil }; init?(intValue: Int) { self.stringValue = String(intValue); self.intValue = intValue } }
|
||||||
private func rejectDuplicateJSONKeys(_ data: Data) throws {
|
private func rejectDuplicateJSONKeys(_ data: Data) throws {
|
||||||
|
guard data.count <= 1_048_576 else { throw ContractValidationError(field: "json", code: "payload_too_large") }
|
||||||
var index = 0
|
var index = 0
|
||||||
func skipWhitespace() { while index < data.count && [9, 10, 13, 32].contains(data[index]) { index += 1 } }
|
func skipWhitespace() { while index < data.count && [9, 10, 13, 32].contains(data[index]) { index += 1 } }
|
||||||
func parseString() throws -> String {
|
func parseString() throws -> String {
|
||||||
@@ -21,7 +22,8 @@ private func rejectDuplicateJSONKeys(_ data: Data) throws {
|
|||||||
}
|
}
|
||||||
throw ContractValidationError(field: "json", code: "invalid_json")
|
throw ContractValidationError(field: "json", code: "invalid_json")
|
||||||
}
|
}
|
||||||
func parseValue() throws {
|
func parseValue(_ depth: Int) throws {
|
||||||
|
guard depth <= 64 else { throw ContractValidationError(field: "json", code: "nesting_too_deep") }
|
||||||
skipWhitespace()
|
skipWhitespace()
|
||||||
guard index < data.count else { throw ContractValidationError(field: "json", code: "invalid_json") }
|
guard index < data.count else { throw ContractValidationError(field: "json", code: "invalid_json") }
|
||||||
if data[index] == 123 {
|
if data[index] == 123 {
|
||||||
@@ -36,7 +38,7 @@ private func rejectDuplicateJSONKeys(_ data: Data) throws {
|
|||||||
skipWhitespace()
|
skipWhitespace()
|
||||||
guard index < data.count, data[index] == 58 else { throw ContractValidationError(field: "json", code: "invalid_json") }
|
guard index < data.count, data[index] == 58 else { throw ContractValidationError(field: "json", code: "invalid_json") }
|
||||||
index += 1
|
index += 1
|
||||||
try parseValue()
|
try parseValue(depth + 1)
|
||||||
skipWhitespace()
|
skipWhitespace()
|
||||||
guard index < data.count else { throw ContractValidationError(field: "json", code: "invalid_json") }
|
guard index < data.count else { throw ContractValidationError(field: "json", code: "invalid_json") }
|
||||||
if data[index] == 125 { index += 1; return }
|
if data[index] == 125 { index += 1; return }
|
||||||
@@ -49,7 +51,7 @@ private func rejectDuplicateJSONKeys(_ data: Data) throws {
|
|||||||
skipWhitespace()
|
skipWhitespace()
|
||||||
if index < data.count, data[index] == 93 { index += 1; return }
|
if index < data.count, data[index] == 93 { index += 1; return }
|
||||||
while true {
|
while true {
|
||||||
try parseValue()
|
try parseValue(depth + 1)
|
||||||
skipWhitespace()
|
skipWhitespace()
|
||||||
guard index < data.count else { throw ContractValidationError(field: "json", code: "invalid_json") }
|
guard index < data.count else { throw ContractValidationError(field: "json", code: "invalid_json") }
|
||||||
if data[index] == 93 { index += 1; return }
|
if data[index] == 93 { index += 1; return }
|
||||||
@@ -62,7 +64,7 @@ private func rejectDuplicateJSONKeys(_ data: Data) throws {
|
|||||||
while index < data.count && ![9, 10, 13, 32, 44, 93, 125].contains(data[index]) { index += 1 }
|
while index < data.count && ![9, 10, 13, 32, 44, 93, 125].contains(data[index]) { index += 1 }
|
||||||
guard index > start else { throw ContractValidationError(field: "json", code: "invalid_json") }
|
guard index > start else { throw ContractValidationError(field: "json", code: "invalid_json") }
|
||||||
}
|
}
|
||||||
try parseValue()
|
try parseValue(0)
|
||||||
skipWhitespace()
|
skipWhitespace()
|
||||||
guard index == data.count else { throw ContractValidationError(field: "json", code: "trailing_json") }
|
guard index == data.count else { throw ContractValidationError(field: "json", code: "trailing_json") }
|
||||||
}
|
}
|
||||||
@@ -1434,8 +1436,9 @@ public struct GatewayQualityAck: Codable, Equatable {
|
|||||||
public let reconnectSequence: Int64
|
public let reconnectSequence: Int64
|
||||||
public let operationId: String
|
public let operationId: String
|
||||||
public let revision: Int64
|
public let revision: Int64
|
||||||
|
public let leaseGeneration: Int64
|
||||||
public let outcome: String
|
public let outcome: String
|
||||||
public let currentAppliedRevision: Int64
|
public let currentAppliedRevision: Int64?
|
||||||
public let failureCode: String?
|
public let failureCode: String?
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case version = "version"
|
case version = "version"
|
||||||
@@ -1444,18 +1447,20 @@ public struct GatewayQualityAck: Codable, Equatable {
|
|||||||
case reconnectSequence = "reconnect_sequence"
|
case reconnectSequence = "reconnect_sequence"
|
||||||
case operationId = "operation_id"
|
case operationId = "operation_id"
|
||||||
case revision = "revision"
|
case revision = "revision"
|
||||||
|
case leaseGeneration = "lease_generation"
|
||||||
case outcome = "outcome"
|
case outcome = "outcome"
|
||||||
case currentAppliedRevision = "current_applied_revision"
|
case currentAppliedRevision = "current_applied_revision"
|
||||||
case failureCode = "failure_code"
|
case failureCode = "failure_code"
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, operationId: String, revision: Int64, outcome: String, currentAppliedRevision: Int64, failureCode: String?) throws {
|
public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, operationId: String, revision: Int64, leaseGeneration: Int64, outcome: String, currentAppliedRevision: Int64?, failureCode: String?) throws {
|
||||||
self.version = version
|
self.version = version
|
||||||
self.sessionId = sessionId
|
self.sessionId = sessionId
|
||||||
self.gatewayId = gatewayId
|
self.gatewayId = gatewayId
|
||||||
self.reconnectSequence = reconnectSequence
|
self.reconnectSequence = reconnectSequence
|
||||||
self.operationId = operationId
|
self.operationId = operationId
|
||||||
self.revision = revision
|
self.revision = revision
|
||||||
|
self.leaseGeneration = leaseGeneration
|
||||||
self.outcome = outcome
|
self.outcome = outcome
|
||||||
self.currentAppliedRevision = currentAppliedRevision
|
self.currentAppliedRevision = currentAppliedRevision
|
||||||
self.failureCode = failureCode
|
self.failureCode = failureCode
|
||||||
@@ -1466,7 +1471,7 @@ public struct GatewayQualityAck: Codable, Equatable {
|
|||||||
let all = try decoder.container(keyedBy: AnyCodingKey.self)
|
let all = try decoder.container(keyedBy: AnyCodingKey.self)
|
||||||
for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") }
|
for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") }
|
||||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), operationId: try c.decode(String.self, forKey: .operationId), revision: try c.decode(Int64.self, forKey: .revision), outcome: try c.decode(String.self, forKey: .outcome), currentAppliedRevision: try c.decode(Int64.self, forKey: .currentAppliedRevision), failureCode: try c.decodeIfPresent(String.self, forKey: .failureCode))
|
try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), operationId: try c.decode(String.self, forKey: .operationId), revision: try c.decode(Int64.self, forKey: .revision), leaseGeneration: try c.decode(Int64.self, forKey: .leaseGeneration), outcome: try c.decode(String.self, forKey: .outcome), currentAppliedRevision: try c.decodeIfPresent(Int64.self, forKey: .currentAppliedRevision), failureCode: try c.decodeIfPresent(String.self, forKey: .failureCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
public func validate() throws {
|
public func validate() throws {
|
||||||
@@ -1483,12 +1488,18 @@ public struct GatewayQualityAck: Codable, Equatable {
|
|||||||
if self.operationId.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") }
|
if self.operationId.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") }
|
||||||
if !validCanonicalUUID(self.operationId) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") }
|
if !validCanonicalUUID(self.operationId) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") }
|
||||||
if self.revision < 1 { throw ContractValidationError(field: "revision", code: "minimum") }
|
if self.revision < 1 { throw ContractValidationError(field: "revision", code: "minimum") }
|
||||||
if !["applied", "not_applied", "uncertain"].contains(self.outcome) { throw ContractValidationError(field: "outcome", code: "invalid_value") }
|
if self.leaseGeneration < 1 { throw ContractValidationError(field: "lease_generation", code: "minimum") }
|
||||||
if self.currentAppliedRevision < 0 { throw ContractValidationError(field: "current_applied_revision", code: "minimum") }
|
if !["applied", "proven_prior", "unknown"].contains(self.outcome) { throw ContractValidationError(field: "outcome", code: "invalid_value") }
|
||||||
|
if let value = self.currentAppliedRevision {
|
||||||
|
if value < 0 { throw ContractValidationError(field: "current_applied_revision", code: "minimum") }
|
||||||
|
}
|
||||||
if let value = self.failureCode {
|
if let value = self.failureCode {
|
||||||
if !value.isEmpty && value.utf8.count < 1 { throw ContractValidationError(field: "failure_code", code: "min_length") }
|
if !value.isEmpty && value.utf8.count < 1 { throw ContractValidationError(field: "failure_code", code: "min_length") }
|
||||||
if value.utf8.count > 128 { throw ContractValidationError(field: "failure_code", code: "max_length") }
|
if value.utf8.count > 128 { throw ContractValidationError(field: "failure_code", code: "max_length") }
|
||||||
}
|
}
|
||||||
|
if outcome == "applied" && currentAppliedRevision != revision { throw ContractValidationError(field: "current_applied_revision", code: "invalid_tagged_value") }
|
||||||
|
if outcome == "proven_prior" && (currentAppliedRevision == nil || currentAppliedRevision! >= revision) { throw ContractValidationError(field: "current_applied_revision", code: "invalid_tagged_value") }
|
||||||
|
if outcome == "unknown" && currentAppliedRevision != nil { throw ContractValidationError(field: "current_applied_revision", code: "invalid_tagged_value") }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func decodeJSON(_ data: Data) throws -> Self { try rejectDuplicateJSONKeys(data); return try JSONDecoder().decode(Self.self, from: data) }
|
public static func decodeJSON(_ data: Data) throws -> Self { try rejectDuplicateJSONKeys(data); return try JSONDecoder().decode(Self.self, from: data) }
|
||||||
@@ -1502,6 +1513,7 @@ public struct GatewayQualityWork: Codable, Equatable {
|
|||||||
public let reconnectSequence: Int64
|
public let reconnectSequence: Int64
|
||||||
public let operationId: String
|
public let operationId: String
|
||||||
public let revision: Int64
|
public let revision: Int64
|
||||||
|
public let leaseGeneration: Int64
|
||||||
public let leaseExpiresAt: String
|
public let leaseExpiresAt: String
|
||||||
public let selectedDescriptor: SelectedSessionDescriptor
|
public let selectedDescriptor: SelectedSessionDescriptor
|
||||||
public let currentAppliedRevision: Int64?
|
public let currentAppliedRevision: Int64?
|
||||||
@@ -1512,18 +1524,20 @@ public struct GatewayQualityWork: Codable, Equatable {
|
|||||||
case reconnectSequence = "reconnect_sequence"
|
case reconnectSequence = "reconnect_sequence"
|
||||||
case operationId = "operation_id"
|
case operationId = "operation_id"
|
||||||
case revision = "revision"
|
case revision = "revision"
|
||||||
|
case leaseGeneration = "lease_generation"
|
||||||
case leaseExpiresAt = "lease_expires_at"
|
case leaseExpiresAt = "lease_expires_at"
|
||||||
case selectedDescriptor = "selected_descriptor"
|
case selectedDescriptor = "selected_descriptor"
|
||||||
case currentAppliedRevision = "current_applied_revision"
|
case currentAppliedRevision = "current_applied_revision"
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, operationId: String, revision: Int64, leaseExpiresAt: String, selectedDescriptor: SelectedSessionDescriptor, currentAppliedRevision: Int64?) throws {
|
public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, operationId: String, revision: Int64, leaseGeneration: Int64, leaseExpiresAt: String, selectedDescriptor: SelectedSessionDescriptor, currentAppliedRevision: Int64?) throws {
|
||||||
self.version = version
|
self.version = version
|
||||||
self.sessionId = sessionId
|
self.sessionId = sessionId
|
||||||
self.gatewayId = gatewayId
|
self.gatewayId = gatewayId
|
||||||
self.reconnectSequence = reconnectSequence
|
self.reconnectSequence = reconnectSequence
|
||||||
self.operationId = operationId
|
self.operationId = operationId
|
||||||
self.revision = revision
|
self.revision = revision
|
||||||
|
self.leaseGeneration = leaseGeneration
|
||||||
self.leaseExpiresAt = leaseExpiresAt
|
self.leaseExpiresAt = leaseExpiresAt
|
||||||
self.selectedDescriptor = selectedDescriptor
|
self.selectedDescriptor = selectedDescriptor
|
||||||
self.currentAppliedRevision = currentAppliedRevision
|
self.currentAppliedRevision = currentAppliedRevision
|
||||||
@@ -1534,7 +1548,7 @@ public struct GatewayQualityWork: Codable, Equatable {
|
|||||||
let all = try decoder.container(keyedBy: AnyCodingKey.self)
|
let all = try decoder.container(keyedBy: AnyCodingKey.self)
|
||||||
for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") }
|
for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") }
|
||||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), operationId: try c.decode(String.self, forKey: .operationId), revision: try c.decode(Int64.self, forKey: .revision), leaseExpiresAt: try c.decode(String.self, forKey: .leaseExpiresAt), selectedDescriptor: try c.decode(SelectedSessionDescriptor.self, forKey: .selectedDescriptor), currentAppliedRevision: try c.decodeIfPresent(Int64.self, forKey: .currentAppliedRevision))
|
try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), operationId: try c.decode(String.self, forKey: .operationId), revision: try c.decode(Int64.self, forKey: .revision), leaseGeneration: try c.decode(Int64.self, forKey: .leaseGeneration), leaseExpiresAt: try c.decode(String.self, forKey: .leaseExpiresAt), selectedDescriptor: try c.decode(SelectedSessionDescriptor.self, forKey: .selectedDescriptor), currentAppliedRevision: try c.decodeIfPresent(Int64.self, forKey: .currentAppliedRevision))
|
||||||
}
|
}
|
||||||
|
|
||||||
public func validate() throws {
|
public func validate() throws {
|
||||||
@@ -1551,6 +1565,7 @@ public struct GatewayQualityWork: Codable, Equatable {
|
|||||||
if self.operationId.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") }
|
if self.operationId.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") }
|
||||||
if !validCanonicalUUID(self.operationId) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") }
|
if !validCanonicalUUID(self.operationId) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") }
|
||||||
if self.revision < 1 { throw ContractValidationError(field: "revision", code: "minimum") }
|
if self.revision < 1 { throw ContractValidationError(field: "revision", code: "minimum") }
|
||||||
|
if self.leaseGeneration < 1 { throw ContractValidationError(field: "lease_generation", code: "minimum") }
|
||||||
if self.leaseExpiresAt.utf8.count > 64 { throw ContractValidationError(field: "lease_expires_at", code: "max_length") }
|
if self.leaseExpiresAt.utf8.count > 64 { throw ContractValidationError(field: "lease_expires_at", code: "max_length") }
|
||||||
if !validRFC3339UTC(self.leaseExpiresAt) { throw ContractValidationError(field: "lease_expires_at", code: "invalid_time") }
|
if !validRFC3339UTC(self.leaseExpiresAt) { throw ContractValidationError(field: "lease_expires_at", code: "invalid_time") }
|
||||||
try self.selectedDescriptor.validate()
|
try self.selectedDescriptor.validate()
|
||||||
@@ -1568,26 +1583,32 @@ public struct GatewayQualityWorkRequest: Codable, Equatable {
|
|||||||
public let sessionId: String
|
public let sessionId: String
|
||||||
public let gatewayId: String
|
public let gatewayId: String
|
||||||
public let reconnectSequence: Int64
|
public let reconnectSequence: Int64
|
||||||
public let operationId: String
|
public let acquisition: String
|
||||||
public let revision: Int64
|
public let operationId: String?
|
||||||
|
public let revision: Int64?
|
||||||
|
public let leaseGeneration: Int64?
|
||||||
public let currentAppliedRevision: Int64?
|
public let currentAppliedRevision: Int64?
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case version = "version"
|
case version = "version"
|
||||||
case sessionId = "session_id"
|
case sessionId = "session_id"
|
||||||
case gatewayId = "gateway_id"
|
case gatewayId = "gateway_id"
|
||||||
case reconnectSequence = "reconnect_sequence"
|
case reconnectSequence = "reconnect_sequence"
|
||||||
|
case acquisition = "acquisition"
|
||||||
case operationId = "operation_id"
|
case operationId = "operation_id"
|
||||||
case revision = "revision"
|
case revision = "revision"
|
||||||
|
case leaseGeneration = "lease_generation"
|
||||||
case currentAppliedRevision = "current_applied_revision"
|
case currentAppliedRevision = "current_applied_revision"
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, operationId: String, revision: Int64, currentAppliedRevision: Int64?) throws {
|
public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, acquisition: String, operationId: String?, revision: Int64?, leaseGeneration: Int64?, currentAppliedRevision: Int64?) throws {
|
||||||
self.version = version
|
self.version = version
|
||||||
self.sessionId = sessionId
|
self.sessionId = sessionId
|
||||||
self.gatewayId = gatewayId
|
self.gatewayId = gatewayId
|
||||||
self.reconnectSequence = reconnectSequence
|
self.reconnectSequence = reconnectSequence
|
||||||
|
self.acquisition = acquisition
|
||||||
self.operationId = operationId
|
self.operationId = operationId
|
||||||
self.revision = revision
|
self.revision = revision
|
||||||
|
self.leaseGeneration = leaseGeneration
|
||||||
self.currentAppliedRevision = currentAppliedRevision
|
self.currentAppliedRevision = currentAppliedRevision
|
||||||
try validate()
|
try validate()
|
||||||
}
|
}
|
||||||
@@ -1596,7 +1617,7 @@ public struct GatewayQualityWorkRequest: Codable, Equatable {
|
|||||||
let all = try decoder.container(keyedBy: AnyCodingKey.self)
|
let all = try decoder.container(keyedBy: AnyCodingKey.self)
|
||||||
for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") }
|
for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") }
|
||||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), operationId: try c.decode(String.self, forKey: .operationId), revision: try c.decode(Int64.self, forKey: .revision), currentAppliedRevision: try c.decodeIfPresent(Int64.self, forKey: .currentAppliedRevision))
|
try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), acquisition: try c.decode(String.self, forKey: .acquisition), operationId: try c.decodeIfPresent(String.self, forKey: .operationId), revision: try c.decodeIfPresent(Int64.self, forKey: .revision), leaseGeneration: try c.decodeIfPresent(Int64.self, forKey: .leaseGeneration), currentAppliedRevision: try c.decodeIfPresent(Int64.self, forKey: .currentAppliedRevision))
|
||||||
}
|
}
|
||||||
|
|
||||||
public func validate() throws {
|
public func validate() throws {
|
||||||
@@ -1608,14 +1629,24 @@ public struct GatewayQualityWorkRequest: Codable, Equatable {
|
|||||||
if !self.gatewayId.isEmpty && self.gatewayId.utf8.count < 1 { throw ContractValidationError(field: "gateway_id", code: "min_length") }
|
if !self.gatewayId.isEmpty && self.gatewayId.utf8.count < 1 { throw ContractValidationError(field: "gateway_id", code: "min_length") }
|
||||||
if self.gatewayId.utf8.count > 128 { throw ContractValidationError(field: "gateway_id", code: "max_length") }
|
if self.gatewayId.utf8.count > 128 { throw ContractValidationError(field: "gateway_id", code: "max_length") }
|
||||||
if self.reconnectSequence < 0 { throw ContractValidationError(field: "reconnect_sequence", code: "minimum") }
|
if self.reconnectSequence < 0 { throw ContractValidationError(field: "reconnect_sequence", code: "minimum") }
|
||||||
if self.operationId.isEmpty { throw ContractValidationError(field: "operation_id", code: "required") }
|
if !["poll", "prompt", "observation"].contains(self.acquisition) { throw ContractValidationError(field: "acquisition", code: "invalid_value") }
|
||||||
if !self.operationId.isEmpty && self.operationId.utf8.count < 36 { throw ContractValidationError(field: "operation_id", code: "min_length") }
|
if let value = self.operationId {
|
||||||
if self.operationId.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") }
|
if !value.isEmpty && value.utf8.count < 36 { throw ContractValidationError(field: "operation_id", code: "min_length") }
|
||||||
if !validCanonicalUUID(self.operationId) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") }
|
if value.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") }
|
||||||
if self.revision < 1 { throw ContractValidationError(field: "revision", code: "minimum") }
|
if !validCanonicalUUID(value) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") }
|
||||||
|
}
|
||||||
|
if let value = self.revision {
|
||||||
|
if value < 1 { throw ContractValidationError(field: "revision", code: "minimum") }
|
||||||
|
}
|
||||||
|
if let value = self.leaseGeneration {
|
||||||
|
if value < 1 { throw ContractValidationError(field: "lease_generation", code: "minimum") }
|
||||||
|
}
|
||||||
if let value = self.currentAppliedRevision {
|
if let value = self.currentAppliedRevision {
|
||||||
if value < 0 { throw ContractValidationError(field: "current_applied_revision", code: "minimum") }
|
if value < 0 { throw ContractValidationError(field: "current_applied_revision", code: "minimum") }
|
||||||
}
|
}
|
||||||
|
if acquisition == "poll" && (operationId != nil || revision != nil || leaseGeneration != nil || currentAppliedRevision != nil) { throw ContractValidationError(field: "acquisition", code: "invalid_tagged_value") }
|
||||||
|
if acquisition == "prompt" && (operationId == nil || revision == nil || leaseGeneration != nil || currentAppliedRevision != nil) { throw ContractValidationError(field: "acquisition", code: "invalid_tagged_value") }
|
||||||
|
if acquisition == "observation" && (operationId == nil || revision == nil || leaseGeneration == nil || currentAppliedRevision == nil) { throw ContractValidationError(field: "acquisition", code: "invalid_tagged_value") }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func decodeJSON(_ data: Data) throws -> Self { try rejectDuplicateJSONKeys(data); return try JSONDecoder().decode(Self.self, from: data) }
|
public static func decodeJSON(_ data: Data) throws -> Self { try rejectDuplicateJSONKeys(data); return try JSONDecoder().decode(Self.self, from: data) }
|
||||||
@@ -1834,20 +1865,23 @@ public struct GatewayStopWorkRequest: Codable, Equatable {
|
|||||||
public let sessionId: String
|
public let sessionId: String
|
||||||
public let gatewayId: String
|
public let gatewayId: String
|
||||||
public let reconnectSequence: Int64
|
public let reconnectSequence: Int64
|
||||||
public let operationId: String
|
public let acquisition: String
|
||||||
|
public let operationId: String?
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case version = "version"
|
case version = "version"
|
||||||
case sessionId = "session_id"
|
case sessionId = "session_id"
|
||||||
case gatewayId = "gateway_id"
|
case gatewayId = "gateway_id"
|
||||||
case reconnectSequence = "reconnect_sequence"
|
case reconnectSequence = "reconnect_sequence"
|
||||||
|
case acquisition = "acquisition"
|
||||||
case operationId = "operation_id"
|
case operationId = "operation_id"
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, operationId: String) throws {
|
public init(version: String, sessionId: String, gatewayId: String, reconnectSequence: Int64, acquisition: String, operationId: String?) throws {
|
||||||
self.version = version
|
self.version = version
|
||||||
self.sessionId = sessionId
|
self.sessionId = sessionId
|
||||||
self.gatewayId = gatewayId
|
self.gatewayId = gatewayId
|
||||||
self.reconnectSequence = reconnectSequence
|
self.reconnectSequence = reconnectSequence
|
||||||
|
self.acquisition = acquisition
|
||||||
self.operationId = operationId
|
self.operationId = operationId
|
||||||
try validate()
|
try validate()
|
||||||
}
|
}
|
||||||
@@ -1856,7 +1890,7 @@ public struct GatewayStopWorkRequest: Codable, Equatable {
|
|||||||
let all = try decoder.container(keyedBy: AnyCodingKey.self)
|
let all = try decoder.container(keyedBy: AnyCodingKey.self)
|
||||||
for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") }
|
for key in all.allKeys where CodingKeys(stringValue: key.stringValue) == nil { throw ContractValidationError(field: key.stringValue, code: "unknown_field") }
|
||||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), operationId: try c.decode(String.self, forKey: .operationId))
|
try self.init(version: try c.decode(String.self, forKey: .version), sessionId: try c.decode(String.self, forKey: .sessionId), gatewayId: try c.decode(String.self, forKey: .gatewayId), reconnectSequence: try c.decode(Int64.self, forKey: .reconnectSequence), acquisition: try c.decode(String.self, forKey: .acquisition), operationId: try c.decodeIfPresent(String.self, forKey: .operationId))
|
||||||
}
|
}
|
||||||
|
|
||||||
public func validate() throws {
|
public func validate() throws {
|
||||||
@@ -1868,10 +1902,13 @@ public struct GatewayStopWorkRequest: Codable, Equatable {
|
|||||||
if !self.gatewayId.isEmpty && self.gatewayId.utf8.count < 1 { throw ContractValidationError(field: "gateway_id", code: "min_length") }
|
if !self.gatewayId.isEmpty && self.gatewayId.utf8.count < 1 { throw ContractValidationError(field: "gateway_id", code: "min_length") }
|
||||||
if self.gatewayId.utf8.count > 128 { throw ContractValidationError(field: "gateway_id", code: "max_length") }
|
if self.gatewayId.utf8.count > 128 { throw ContractValidationError(field: "gateway_id", code: "max_length") }
|
||||||
if self.reconnectSequence < 0 { throw ContractValidationError(field: "reconnect_sequence", code: "minimum") }
|
if self.reconnectSequence < 0 { throw ContractValidationError(field: "reconnect_sequence", code: "minimum") }
|
||||||
if self.operationId.isEmpty { throw ContractValidationError(field: "operation_id", code: "required") }
|
if !["poll", "prompt"].contains(self.acquisition) { throw ContractValidationError(field: "acquisition", code: "invalid_value") }
|
||||||
if !self.operationId.isEmpty && self.operationId.utf8.count < 36 { throw ContractValidationError(field: "operation_id", code: "min_length") }
|
if let value = self.operationId {
|
||||||
if self.operationId.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") }
|
if !value.isEmpty && value.utf8.count < 36 { throw ContractValidationError(field: "operation_id", code: "min_length") }
|
||||||
if !validCanonicalUUID(self.operationId) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") }
|
if value.utf8.count > 36 { throw ContractValidationError(field: "operation_id", code: "max_length") }
|
||||||
|
if !validCanonicalUUID(value) { throw ContractValidationError(field: "operation_id", code: "invalid_uuid") }
|
||||||
|
}
|
||||||
|
if acquisition == "poll" && operationId != nil || acquisition == "prompt" && operationId == nil { throw ContractValidationError(field: "acquisition", code: "invalid_tagged_value") }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func decodeJSON(_ data: Data) throws -> Self { try rejectDuplicateJSONKeys(data); return try JSONDecoder().decode(Self.self, from: data) }
|
public static func decodeJSON(_ data: Data) throws -> Self { try rejectDuplicateJSONKeys(data); return try JSONDecoder().decode(Self.self, from: data) }
|
||||||
|
|||||||
+26
-18
@@ -334,7 +334,7 @@ paths:
|
|||||||
operationId: getSessionQualityLimits
|
operationId: getSessionQualityLimits
|
||||||
security: [{nativeBearer: []}]
|
security: [{nativeBearer: []}]
|
||||||
responses:
|
responses:
|
||||||
'200': {description: Current global native session quality limits., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/SessionQualityLimits}}}}
|
'200': {description: 'Current global native session quality limits. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/SessionQualityLimits}}}}
|
||||||
'401': {$ref: '#/components/responses/Unauthorized'}
|
'401': {$ref: '#/components/responses/Unauthorized'}
|
||||||
/api/v1/session-quality-limits/assignments/{assignment_id}:
|
/api/v1/session-quality-limits/assignments/{assignment_id}:
|
||||||
get:
|
get:
|
||||||
@@ -343,7 +343,7 @@ paths:
|
|||||||
security: [{nativeBearer: []}]
|
security: [{nativeBearer: []}]
|
||||||
parameters: [{$ref: '#/components/parameters/AssignmentID'}]
|
parameters: [{$ref: '#/components/parameters/AssignmentID'}]
|
||||||
responses:
|
responses:
|
||||||
'200': {description: Current assignment-specific native session quality limits., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/SessionQualityLimits}}}}
|
'200': {description: 'Current assignment-specific native session quality limits. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/SessionQualityLimits}}}}
|
||||||
'401': {$ref: '#/components/responses/Unauthorized'}
|
'401': {$ref: '#/components/responses/Unauthorized'}
|
||||||
'404': {$ref: '#/components/responses/NotFound'}
|
'404': {$ref: '#/components/responses/NotFound'}
|
||||||
/api/v1/session-quality-limits/pools/{pool_id}:
|
/api/v1/session-quality-limits/pools/{pool_id}:
|
||||||
@@ -353,7 +353,7 @@ paths:
|
|||||||
security: [{nativeBearer: []}]
|
security: [{nativeBearer: []}]
|
||||||
parameters: [{$ref: '#/components/parameters/PoolID'}]
|
parameters: [{$ref: '#/components/parameters/PoolID'}]
|
||||||
responses:
|
responses:
|
||||||
'200': {description: Current pool-specific native session quality limits., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/SessionQualityLimits}}}}
|
'200': {description: 'Current pool-specific native session quality limits. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/SessionQualityLimits}}}}
|
||||||
'401': {$ref: '#/components/responses/Unauthorized'}
|
'401': {$ref: '#/components/responses/Unauthorized'}
|
||||||
'404': {$ref: '#/components/responses/NotFound'}
|
'404': {$ref: '#/components/responses/NotFound'}
|
||||||
/api/v1/admin/entitlements/{entitlement_id}/display-limit-override:
|
/api/v1/admin/entitlements/{entitlement_id}/display-limit-override:
|
||||||
@@ -365,7 +365,7 @@ paths:
|
|||||||
browserCsrfCookie: []
|
browserCsrfCookie: []
|
||||||
browserCsrfHeader: []
|
browserCsrfHeader: []
|
||||||
parameters: [{$ref: '#/components/parameters/EntitlementID'}]
|
parameters: [{$ref: '#/components/parameters/EntitlementID'}]
|
||||||
requestBody: {required: true, content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/DisplayLimitOverrideRequest}}}}
|
requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/DisplayLimitOverrideRequest}}}}
|
||||||
responses:
|
responses:
|
||||||
'204': {description: Direct entitlement display override updated.}
|
'204': {description: Direct entitlement display override updated.}
|
||||||
'400': {$ref: '#/components/responses/InvalidRequest'}
|
'400': {$ref: '#/components/responses/InvalidRequest'}
|
||||||
@@ -377,12 +377,13 @@ paths:
|
|||||||
post:
|
post:
|
||||||
tags: [session-quality]
|
tags: [session-quality]
|
||||||
operationId: createSessionQualityChange
|
operationId: createSessionQualityChange
|
||||||
|
description: The owning principal and active device/key MUST match the subject-scoped broker session. Maximum JSON body: 16384 bytes.
|
||||||
security: [{nativeBearer: []}]
|
security: [{nativeBearer: []}]
|
||||||
parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||||
requestBody: {required: true, content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeRequest}}}}
|
requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeRequest}}}}
|
||||||
responses:
|
responses:
|
||||||
'201': {description: Durable quality change created., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeOperation}}}}
|
'201': {description: 'Durable quality change created. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeOperation}}}}
|
||||||
'200': {description: Existing idempotent quality change returned., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeOperation}}}}
|
'200': {description: 'Existing idempotent quality change returned. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeOperation}}}}
|
||||||
'400': {$ref: '#/components/responses/InvalidRequest'}
|
'400': {$ref: '#/components/responses/InvalidRequest'}
|
||||||
'401': {$ref: '#/components/responses/Unauthorized'}
|
'401': {$ref: '#/components/responses/Unauthorized'}
|
||||||
'404': {$ref: '#/components/responses/NotFound'}
|
'404': {$ref: '#/components/responses/NotFound'}
|
||||||
@@ -391,21 +392,23 @@ paths:
|
|||||||
get:
|
get:
|
||||||
tags: [session-quality]
|
tags: [session-quality]
|
||||||
operationId: getSessionQualityChange
|
operationId: getSessionQualityChange
|
||||||
|
description: The owning principal and active device/key MUST match the subject-scoped broker session and operation. Maximum JSON body: 16384 bytes.
|
||||||
security: [{nativeBearer: []}]
|
security: [{nativeBearer: []}]
|
||||||
parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/OperationID'}]
|
parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/OperationID'}]
|
||||||
responses:
|
responses:
|
||||||
'200': {description: Durable quality change state., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeOperation}}}}
|
'200': {description: 'Durable quality change state. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/QualityChangeOperation}}}}
|
||||||
'401': {$ref: '#/components/responses/Unauthorized'}
|
'401': {$ref: '#/components/responses/Unauthorized'}
|
||||||
'404': {$ref: '#/components/responses/NotFound'}
|
'404': {$ref: '#/components/responses/NotFound'}
|
||||||
/api/v1/broker/sessions/{session_id}/stop-operations:
|
/api/v1/broker/sessions/{session_id}/stop-operations:
|
||||||
post:
|
post:
|
||||||
tags: [session-quality]
|
tags: [session-quality]
|
||||||
operationId: createSessionStopOperation
|
operationId: createSessionStopOperation
|
||||||
|
description: The owning principal and active device/key MUST match the subject-scoped broker session. Maximum JSON body: 16384 bytes.
|
||||||
security: [{nativeBearer: []}]
|
security: [{nativeBearer: []}]
|
||||||
parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||||
responses:
|
responses:
|
||||||
'201': {description: Durable Stop operation created., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/StopOperation}}}}
|
'201': {description: 'Durable Stop operation created. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/StopOperation}}}}
|
||||||
'200': {description: Existing idempotent Stop operation returned., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/StopOperation}}}}
|
'200': {description: 'Existing idempotent Stop operation returned. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/StopOperation}}}}
|
||||||
'401': {$ref: '#/components/responses/Unauthorized'}
|
'401': {$ref: '#/components/responses/Unauthorized'}
|
||||||
'404': {$ref: '#/components/responses/NotFound'}
|
'404': {$ref: '#/components/responses/NotFound'}
|
||||||
'409': {$ref: '#/components/responses/Conflict'}
|
'409': {$ref: '#/components/responses/Conflict'}
|
||||||
@@ -413,20 +416,22 @@ paths:
|
|||||||
get:
|
get:
|
||||||
tags: [session-quality]
|
tags: [session-quality]
|
||||||
operationId: getSessionStopOperation
|
operationId: getSessionStopOperation
|
||||||
|
description: The owning principal and active device/key MUST match the subject-scoped broker session and operation. Maximum JSON body: 16384 bytes.
|
||||||
security: [{nativeBearer: []}]
|
security: [{nativeBearer: []}]
|
||||||
parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/OperationID'}]
|
parameters: [{$ref: '#/components/parameters/SessionID'}, {$ref: '#/components/parameters/OperationID'}]
|
||||||
responses:
|
responses:
|
||||||
'200': {description: Durable Stop operation state., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/StopOperation}}}}
|
'200': {description: 'Durable Stop operation state. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/StopOperation}}}}
|
||||||
'401': {$ref: '#/components/responses/Unauthorized'}
|
'401': {$ref: '#/components/responses/Unauthorized'}
|
||||||
'404': {$ref: '#/components/responses/NotFound'}
|
'404': {$ref: '#/components/responses/NotFound'}
|
||||||
/api/v1/gateway/quality-work:
|
/api/v1/gateway/quality-work:
|
||||||
post:
|
post:
|
||||||
tags: [gateway]
|
tags: [gateway]
|
||||||
operationId: acquireGatewayQualityWork
|
operationId: acquireGatewayQualityWork
|
||||||
|
description: The authenticated gateway mTLS certificate identity MUST match `gateway_id`; Server authority MUST match the session, gateway, reconnect sequence, and supplied operation/revision/lease generation coordinates. A `poll` acquisition omits unknown operation coordinates and discovers pending session work after a lost prompt; `prompt` and `observation` coordinates MUST match exactly. Maximum JSON body: 16384 bytes.
|
||||||
security: [{gatewayMutualTLS: []}]
|
security: [{gatewayMutualTLS: []}]
|
||||||
requestBody: {required: true, content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayQualityWorkRequest}}}}
|
requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayQualityWorkRequest}}}}
|
||||||
responses:
|
responses:
|
||||||
'200': {description: Leased quality work or reconciliation observation., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayQualityWork}}}}
|
'200': {description: 'Leased quality work or reconciliation observation. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayQualityWork}}}}
|
||||||
'204': {description: No matching work is available.}
|
'204': {description: No matching work is available.}
|
||||||
'400': {$ref: '#/components/responses/InvalidRequest'}
|
'400': {$ref: '#/components/responses/InvalidRequest'}
|
||||||
'401': {$ref: '#/components/responses/Unauthorized'}
|
'401': {$ref: '#/components/responses/Unauthorized'}
|
||||||
@@ -435,8 +440,9 @@ paths:
|
|||||||
post:
|
post:
|
||||||
tags: [gateway]
|
tags: [gateway]
|
||||||
operationId: acknowledgeGatewayQualityWork
|
operationId: acknowledgeGatewayQualityWork
|
||||||
|
description: The authenticated gateway mTLS certificate identity MUST match `gateway_id`; Server authority MUST exactly match the session, gateway, reconnect sequence, operation, revision, and monotonic lease generation tuple. Stale lease generations MUST be rejected. Maximum JSON body: 16384 bytes.
|
||||||
security: [{gatewayMutualTLS: []}]
|
security: [{gatewayMutualTLS: []}]
|
||||||
requestBody: {required: true, content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayQualityAck}}}}
|
requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayQualityAck}}}}
|
||||||
responses:
|
responses:
|
||||||
'204': {description: Quality acknowledgement committed idempotently.}
|
'204': {description: Quality acknowledgement committed idempotently.}
|
||||||
'400': {$ref: '#/components/responses/InvalidRequest'}
|
'400': {$ref: '#/components/responses/InvalidRequest'}
|
||||||
@@ -446,10 +452,11 @@ paths:
|
|||||||
post:
|
post:
|
||||||
tags: [gateway]
|
tags: [gateway]
|
||||||
operationId: acquireGatewayStopWork
|
operationId: acquireGatewayStopWork
|
||||||
|
description: The authenticated gateway mTLS certificate identity MUST match `gateway_id`; Server authority MUST match the session, gateway, reconnect sequence, and any supplied operation coordinate. A `poll` acquisition omits the unknown operation ID and discovers pending session work after a lost prompt; a `prompt` coordinate MUST match exactly. Maximum JSON body: 16384 bytes.
|
||||||
security: [{gatewayMutualTLS: []}]
|
security: [{gatewayMutualTLS: []}]
|
||||||
requestBody: {required: true, content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayStopWorkRequest}}}}
|
requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayStopWorkRequest}}}}
|
||||||
responses:
|
responses:
|
||||||
'200': {description: One-way non-reissued Stop work claim., content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayStopWork}}}}
|
'200': {description: 'One-way non-reissued Stop work claim. Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayStopWork}}}}
|
||||||
'204': {description: No matching Stop work is available.}
|
'204': {description: No matching Stop work is available.}
|
||||||
'400': {$ref: '#/components/responses/InvalidRequest'}
|
'400': {$ref: '#/components/responses/InvalidRequest'}
|
||||||
'401': {$ref: '#/components/responses/Unauthorized'}
|
'401': {$ref: '#/components/responses/Unauthorized'}
|
||||||
@@ -458,8 +465,9 @@ paths:
|
|||||||
post:
|
post:
|
||||||
tags: [gateway]
|
tags: [gateway]
|
||||||
operationId: acknowledgeGatewayStopWork
|
operationId: acknowledgeGatewayStopWork
|
||||||
|
description: The authenticated gateway mTLS certificate identity MUST match `gateway_id`; Server authority MUST exactly match the session, gateway, reconnect sequence, and operation tuple. Maximum JSON body: 16384 bytes.
|
||||||
security: [{gatewayMutualTLS: []}]
|
security: [{gatewayMutualTLS: []}]
|
||||||
requestBody: {required: true, content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayStopAck}}}}
|
requestBody: {required: true, description: 'Maximum JSON body: 16384 bytes.', content: {application/json: {schema: {$ref: ../schemas/control-v1.schema.json#/$defs/GatewayStopAck}}}}
|
||||||
responses:
|
responses:
|
||||||
'204': {description: Stop acknowledgement committed idempotently.}
|
'204': {description: Stop acknowledgement committed idempotently.}
|
||||||
'400': {$ref: '#/components/responses/InvalidRequest'}
|
'400': {$ref: '#/components/responses/InvalidRequest'}
|
||||||
@@ -556,7 +564,7 @@ components:
|
|||||||
name: operation_id
|
name: operation_id
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
schema: {type: string, format: uuid}
|
schema: {type: string, format: uuid, pattern: '^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'}
|
||||||
Limit:
|
Limit:
|
||||||
name: limit
|
name: limit
|
||||||
in: query
|
in: query
|
||||||
|
|||||||
@@ -205,8 +205,10 @@ message GatewayQualityWorkRequest {
|
|||||||
string gateway_id = 3;
|
string gateway_id = 3;
|
||||||
uint64 reconnect_sequence = 4;
|
uint64 reconnect_sequence = 4;
|
||||||
string operation_id = 5;
|
string operation_id = 5;
|
||||||
uint64 revision = 6;
|
optional uint64 revision = 6;
|
||||||
optional uint64 current_applied_revision = 7;
|
optional uint64 current_applied_revision = 7;
|
||||||
|
string acquisition = 8;
|
||||||
|
optional uint64 lease_generation = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GatewayQualityWork {
|
message GatewayQualityWork {
|
||||||
@@ -219,6 +221,7 @@ message GatewayQualityWork {
|
|||||||
google.protobuf.Timestamp lease_expires_at = 7;
|
google.protobuf.Timestamp lease_expires_at = 7;
|
||||||
SelectedSessionDescriptor selected_descriptor = 8;
|
SelectedSessionDescriptor selected_descriptor = 8;
|
||||||
optional uint64 current_applied_revision = 9;
|
optional uint64 current_applied_revision = 9;
|
||||||
|
uint64 lease_generation = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GatewayQualityAck {
|
message GatewayQualityAck {
|
||||||
@@ -229,8 +232,9 @@ message GatewayQualityAck {
|
|||||||
string operation_id = 5;
|
string operation_id = 5;
|
||||||
uint64 revision = 6;
|
uint64 revision = 6;
|
||||||
string outcome = 7;
|
string outcome = 7;
|
||||||
uint64 current_applied_revision = 8;
|
optional uint64 current_applied_revision = 8;
|
||||||
string failure_code = 9;
|
string failure_code = 9;
|
||||||
|
uint64 lease_generation = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GatewayStopWork {
|
message GatewayStopWork {
|
||||||
@@ -248,6 +252,7 @@ message GatewayStopWorkRequest {
|
|||||||
string gateway_id = 3;
|
string gateway_id = 3;
|
||||||
uint64 reconnect_sequence = 4;
|
uint64 reconnect_sequence = 4;
|
||||||
string operation_id = 5;
|
string operation_id = 5;
|
||||||
|
string acquisition = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GatewayStopAck {
|
message GatewayStopAck {
|
||||||
|
|||||||
@@ -269,6 +269,7 @@
|
|||||||
},
|
},
|
||||||
"SessionQualityLimits": {
|
"SessionQualityLimits": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"x-max-bytes": 16384,
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["policy_version_id", "policy_display_limit", "selectable_display_maximum", "display_limit_override", "bitrate_minimum_kbps", "bitrate_target_kbps", "bitrate_maximum_kbps"],
|
"required": ["policy_version_id", "policy_display_limit", "selectable_display_maximum", "display_limit_override", "bitrate_minimum_kbps", "bitrate_target_kbps", "bitrate_maximum_kbps"],
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -654,6 +655,7 @@
|
|||||||
},
|
},
|
||||||
"DisplayLimitOverrideRequest": {
|
"DisplayLimitOverrideRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"x-max-bytes": 16384,
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["allow_display_limit_override", "expected_version"],
|
"required": ["allow_display_limit_override", "expected_version"],
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -663,6 +665,7 @@
|
|||||||
},
|
},
|
||||||
"QualityChangeRequest": {
|
"QualityChangeRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"x-max-bytes": 16384,
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["bitrate_preference", "expected_session_version", "expected_policy_version"],
|
"required": ["bitrate_preference", "expected_session_version", "expected_policy_version"],
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -673,10 +676,11 @@
|
|||||||
},
|
},
|
||||||
"QualityChangeOperation": {
|
"QualityChangeOperation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"x-max-bytes": 16384,
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["operation_id", "session_id", "revision", "state", "requested_bitrate_preference", "effective_bitrate_kbps", "governing_policy_version", "session_version", "created_at", "deadline_at", "updated_at"],
|
"required": ["operation_id", "session_id", "revision", "state", "requested_bitrate_preference", "effective_bitrate_kbps", "governing_policy_version", "session_version", "created_at", "deadline_at", "updated_at"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36},
|
"operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36},
|
||||||
"session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
"session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
||||||
"revision": {"type": "integer", "minimum": 1},
|
"revision": {"type": "integer", "minimum": 1},
|
||||||
"state": {"type": "string", "enum": ["pending", "applying", "applied", "failed", "timed_out"]},
|
"state": {"type": "string", "enum": ["pending", "applying", "applied", "failed", "timed_out"]},
|
||||||
@@ -684,84 +688,97 @@
|
|||||||
"effective_bitrate_kbps": {"type": "integer", "minimum": 100, "maximum": 1000000},
|
"effective_bitrate_kbps": {"type": "integer", "minimum": 100, "maximum": 1000000},
|
||||||
"governing_policy_version": {"type": "string", "minLength": 1, "maxLength": 128},
|
"governing_policy_version": {"type": "string", "minLength": 1, "maxLength": 128},
|
||||||
"session_version": {"type": "integer", "minimum": 1},
|
"session_version": {"type": "integer", "minimum": 1},
|
||||||
"created_at": {"type": "string", "format": "date-time", "maxLength": 64},
|
"created_at": {"type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{0,8}[1-9])?Z$", "maxLength": 64},
|
||||||
"deadline_at": {"type": "string", "format": "date-time", "maxLength": 64},
|
"deadline_at": {"type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{0,8}[1-9])?Z$", "maxLength": 64},
|
||||||
"updated_at": {"type": "string", "format": "date-time", "maxLength": 64},
|
"updated_at": {"type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{0,8}[1-9])?Z$", "maxLength": 64},
|
||||||
"failure_code": {"type": "string", "minLength": 1, "maxLength": 128}
|
"failure_code": {"type": "string", "minLength": 1, "maxLength": 128}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"StopOperation": {
|
"StopOperation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"x-max-bytes": 16384,
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["operation_id", "session_id", "state", "session_version", "created_at", "deadline_at", "updated_at"],
|
"required": ["operation_id", "session_id", "state", "session_version", "created_at", "deadline_at", "updated_at"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36},
|
"operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36},
|
||||||
"session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
"session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
||||||
"state": {"type": "string", "enum": ["pending", "attempting", "applied", "failed", "termination_unconfirmed"]},
|
"state": {"type": "string", "enum": ["pending", "attempting", "applied", "failed", "termination_unconfirmed"]},
|
||||||
"session_version": {"type": "integer", "minimum": 1},
|
"session_version": {"type": "integer", "minimum": 1},
|
||||||
"created_at": {"type": "string", "format": "date-time", "maxLength": 64},
|
"created_at": {"type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{0,8}[1-9])?Z$", "maxLength": 64},
|
||||||
"deadline_at": {"type": "string", "format": "date-time", "maxLength": 64},
|
"deadline_at": {"type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{0,8}[1-9])?Z$", "maxLength": 64},
|
||||||
"updated_at": {"type": "string", "format": "date-time", "maxLength": 64},
|
"updated_at": {"type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{0,8}[1-9])?Z$", "maxLength": 64},
|
||||||
"failure_code": {"type": "string", "minLength": 1, "maxLength": 128}
|
"failure_code": {"type": "string", "minLength": 1, "maxLength": 128}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GatewayQualityWorkRequest": {
|
"GatewayQualityWorkRequest": {
|
||||||
"type": "object", "additionalProperties": false,
|
"type": "object", "additionalProperties": false,
|
||||||
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "revision"],
|
"x-max-bytes": 16384,
|
||||||
|
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "acquisition"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
||||||
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
|
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
|
||||||
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36}, "revision": {"type": "integer", "minimum": 1},
|
"acquisition": {"type": "string", "enum": ["poll", "prompt", "observation"]},
|
||||||
|
"operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36},
|
||||||
|
"revision": {"type": "integer", "minimum": 1, "x-optional-pointer": true},
|
||||||
|
"lease_generation": {"type": "integer", "minimum": 1, "x-optional-pointer": true},
|
||||||
"current_applied_revision": {"type": "integer", "minimum": 0, "x-optional-pointer": true}
|
"current_applied_revision": {"type": "integer", "minimum": 0, "x-optional-pointer": true}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GatewayQualityWork": {
|
"GatewayQualityWork": {
|
||||||
"type": "object", "additionalProperties": false,
|
"type": "object", "additionalProperties": false,
|
||||||
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "revision", "lease_expires_at", "selected_descriptor"],
|
"x-max-bytes": 16384,
|
||||||
|
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "revision", "lease_generation", "lease_expires_at", "selected_descriptor"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
||||||
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
|
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
|
||||||
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36}, "revision": {"type": "integer", "minimum": 1},
|
"operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36}, "revision": {"type": "integer", "minimum": 1},
|
||||||
"lease_expires_at": {"type": "string", "format": "date-time", "maxLength": 64}, "selected_descriptor": {"$ref": "#/$defs/SelectedSessionDescriptor"},
|
"lease_generation": {"type": "integer", "minimum": 1},
|
||||||
|
"lease_expires_at": {"type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{0,8}[1-9])?Z$", "maxLength": 64}, "selected_descriptor": {"$ref": "#/$defs/SelectedSessionDescriptor"},
|
||||||
"current_applied_revision": {"type": "integer", "minimum": 0, "x-optional-pointer": true}
|
"current_applied_revision": {"type": "integer", "minimum": 0, "x-optional-pointer": true}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GatewayQualityAck": {
|
"GatewayQualityAck": {
|
||||||
"type": "object", "additionalProperties": false,
|
"type": "object", "additionalProperties": false,
|
||||||
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "revision", "outcome", "current_applied_revision"],
|
"x-max-bytes": 16384,
|
||||||
|
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "revision", "lease_generation", "outcome"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
||||||
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
|
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
|
||||||
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36}, "revision": {"type": "integer", "minimum": 1},
|
"operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36}, "revision": {"type": "integer", "minimum": 1},
|
||||||
"outcome": {"type": "string", "enum": ["applied", "not_applied", "uncertain"]}, "current_applied_revision": {"type": "integer", "minimum": 0},
|
"lease_generation": {"type": "integer", "minimum": 1},
|
||||||
|
"outcome": {"type": "string", "enum": ["applied", "proven_prior", "unknown"]}, "current_applied_revision": {"type": "integer", "minimum": 0, "x-optional-pointer": true},
|
||||||
"failure_code": {"type": "string", "minLength": 1, "maxLength": 128}
|
"failure_code": {"type": "string", "minLength": 1, "maxLength": 128}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GatewayStopWorkRequest": {
|
"GatewayStopWorkRequest": {
|
||||||
"type": "object", "additionalProperties": false,
|
"type": "object", "additionalProperties": false,
|
||||||
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id"],
|
"x-max-bytes": 16384,
|
||||||
|
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "acquisition"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
||||||
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
|
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
|
||||||
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36}
|
"acquisition": {"type": "string", "enum": ["poll", "prompt"]},
|
||||||
|
"operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GatewayStopWork": {
|
"GatewayStopWork": {
|
||||||
"type": "object", "additionalProperties": false,
|
"type": "object", "additionalProperties": false,
|
||||||
|
"x-max-bytes": 16384,
|
||||||
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "attempt"],
|
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "attempt"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
||||||
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
|
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
|
||||||
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36}, "attempt": {"type": "integer", "minimum": 1, "maximum": 1}
|
"operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36}, "attempt": {"type": "integer", "minimum": 1, "maximum": 1}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GatewayStopAck": {
|
"GatewayStopAck": {
|
||||||
"type": "object", "additionalProperties": false,
|
"type": "object", "additionalProperties": false,
|
||||||
|
"x-max-bytes": 16384,
|
||||||
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "outcome"],
|
"required": ["version", "session_id", "gateway_id", "reconnect_sequence", "operation_id", "outcome"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
"version": {"type": "string", "const": "1"}, "session_id": {"type": "string", "minLength": 1, "maxLength": 128},
|
||||||
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
|
"gateway_id": {"type": "string", "minLength": 1, "maxLength": 128}, "reconnect_sequence": {"type": "integer", "minimum": 0},
|
||||||
"operation_id": {"type": "string", "format": "uuid", "minLength": 36, "maxLength": 36}, "outcome": {"type": "string", "enum": ["applied", "failed", "termination_unconfirmed"]},
|
"operation_id": {"type": "string", "format": "uuid", "pattern": "^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "minLength": 36, "maxLength": 36}, "outcome": {"type": "string", "enum": ["applied", "failed", "termination_unconfirmed"]},
|
||||||
"failure_code": {"type": "string", "minLength": 1, "maxLength": 128}
|
"failure_code": {"type": "string", "minLength": 1, "maxLength": 128}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -89,11 +89,125 @@ func TestRC6BitratePreferenceTaggedBounds(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRC6GatewayQualityDiscoveryAndLeaseFencing(t *testing.T) {
|
||||||
|
operationID := "12345678-1234-1234-1234-123456789abc"
|
||||||
|
revision := int64(7)
|
||||||
|
leaseGeneration := int64(3)
|
||||||
|
currentRevision := int64(6)
|
||||||
|
|
||||||
|
poll := protocol.GatewayQualityWorkRequest{Version: "1", SessionID: "session-1", GatewayID: "gateway-1", ReconnectSequence: 2, Acquisition: "poll"}
|
||||||
|
if err := poll.Validate(); err != nil {
|
||||||
|
t.Fatalf("session-bound lost-prompt poll rejected: %v", err)
|
||||||
|
}
|
||||||
|
prompt := poll
|
||||||
|
prompt.Acquisition = "prompt"
|
||||||
|
prompt.OperationID = operationID
|
||||||
|
prompt.Revision = &revision
|
||||||
|
if err := prompt.Validate(); err != nil {
|
||||||
|
t.Fatalf("prompt-bound acquisition rejected: %v", err)
|
||||||
|
}
|
||||||
|
observation := prompt
|
||||||
|
observation.Acquisition = "observation"
|
||||||
|
observation.LeaseGeneration = &leaseGeneration
|
||||||
|
observation.CurrentAppliedRevision = ¤tRevision
|
||||||
|
if err := observation.Validate(); err != nil {
|
||||||
|
t.Fatalf("lease-bound applied-revision observation rejected: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, invalid := range map[string]protocol.GatewayQualityWorkRequest{
|
||||||
|
"poll with unknown coordinates": prompt,
|
||||||
|
"prompt missing revision": {Version: "1", SessionID: "session-1", GatewayID: "gateway-1", Acquisition: "prompt", OperationID: operationID},
|
||||||
|
"observation missing lease": {Version: "1", SessionID: "session-1", GatewayID: "gateway-1", Acquisition: "observation", OperationID: operationID, Revision: &revision, CurrentAppliedRevision: ¤tRevision},
|
||||||
|
} {
|
||||||
|
if name == "poll with unknown coordinates" {
|
||||||
|
invalid.Acquisition = "poll"
|
||||||
|
}
|
||||||
|
if invalid.Validate() == nil {
|
||||||
|
t.Fatalf("accepted invalid %s: %+v", name, invalid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stopPoll := protocol.GatewayStopWorkRequest{Version: "1", SessionID: "session-1", GatewayID: "gateway-1", ReconnectSequence: 2, Acquisition: "poll"}
|
||||||
|
if err := stopPoll.Validate(); err != nil {
|
||||||
|
t.Fatalf("session-bound Stop poll rejected: %v", err)
|
||||||
|
}
|
||||||
|
stopPrompt := stopPoll
|
||||||
|
stopPrompt.Acquisition = "prompt"
|
||||||
|
stopPrompt.OperationID = operationID
|
||||||
|
if err := stopPrompt.Validate(); err != nil {
|
||||||
|
t.Fatalf("prompt-bound Stop acquisition rejected: %v", err)
|
||||||
|
}
|
||||||
|
stopPoll.OperationID = operationID
|
||||||
|
if stopPoll.Validate() == nil {
|
||||||
|
t.Fatal("Stop poll accepted unknown operation coordinates")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRC6GatewayQualityAckHasUnambiguousObservation(t *testing.T) {
|
||||||
|
revision := int64(7)
|
||||||
|
prior := int64(6)
|
||||||
|
base := protocol.GatewayQualityAck{
|
||||||
|
Version: "1", SessionID: "session-1", GatewayID: "gateway-1", ReconnectSequence: 2,
|
||||||
|
OperationID: "12345678-1234-1234-1234-123456789abc", Revision: revision, LeaseGeneration: 3,
|
||||||
|
}
|
||||||
|
for _, valid := range []protocol.GatewayQualityAck{
|
||||||
|
func() protocol.GatewayQualityAck {
|
||||||
|
value := base
|
||||||
|
value.Outcome = "applied"
|
||||||
|
value.CurrentAppliedRevision = &revision
|
||||||
|
return value
|
||||||
|
}(),
|
||||||
|
func() protocol.GatewayQualityAck {
|
||||||
|
value := base
|
||||||
|
value.Outcome = "proven_prior"
|
||||||
|
value.CurrentAppliedRevision = &prior
|
||||||
|
return value
|
||||||
|
}(),
|
||||||
|
func() protocol.GatewayQualityAck { value := base; value.Outcome = "unknown"; return value }(),
|
||||||
|
} {
|
||||||
|
if err := valid.Validate(); err != nil {
|
||||||
|
t.Fatalf("valid quality acknowledgement rejected: %+v: %v", valid, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, invalid := range []protocol.GatewayQualityAck{
|
||||||
|
func() protocol.GatewayQualityAck {
|
||||||
|
value := base
|
||||||
|
value.Outcome = "applied"
|
||||||
|
value.CurrentAppliedRevision = &prior
|
||||||
|
return value
|
||||||
|
}(),
|
||||||
|
func() protocol.GatewayQualityAck {
|
||||||
|
value := base
|
||||||
|
value.Outcome = "proven_prior"
|
||||||
|
value.CurrentAppliedRevision = &revision
|
||||||
|
return value
|
||||||
|
}(),
|
||||||
|
func() protocol.GatewayQualityAck {
|
||||||
|
value := base
|
||||||
|
value.Outcome = "unknown"
|
||||||
|
value.CurrentAppliedRevision = &prior
|
||||||
|
return value
|
||||||
|
}(),
|
||||||
|
func() protocol.GatewayQualityAck {
|
||||||
|
value := base
|
||||||
|
value.Outcome = "unknown"
|
||||||
|
value.LeaseGeneration = 0
|
||||||
|
return value
|
||||||
|
}(),
|
||||||
|
} {
|
||||||
|
if invalid.Validate() == nil {
|
||||||
|
t.Fatalf("contradictory or unfenced quality acknowledgement accepted: %+v", invalid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRC6JSONFixturesAndCanonicalOperationFields(t *testing.T) {
|
func TestRC6JSONFixturesAndCanonicalOperationFields(t *testing.T) {
|
||||||
validFixtures := map[string]func([]byte) error{
|
validFixtures := map[string]func([]byte) error{
|
||||||
"../../fixtures/valid/session-request.json": func(raw []byte) error { _, err := protocol.DecodeSessionRequest(raw); return err },
|
"../../fixtures/valid/session-request.json": func(raw []byte) error { _, err := protocol.DecodeSessionRequest(raw); return err },
|
||||||
"../../fixtures/valid/selected-session-descriptor.json": func(raw []byte) error { _, err := protocol.DecodeSelectedSessionDescriptor(raw); return err },
|
"../../fixtures/valid/selected-session-descriptor.json": func(raw []byte) error { _, err := protocol.DecodeSelectedSessionDescriptor(raw); return err },
|
||||||
"../../fixtures/valid/session-quality-limits.json": func(raw []byte) error { _, err := protocol.DecodeSessionQualityLimits(raw); return err },
|
"../../fixtures/valid/session-quality-limits.json": func(raw []byte) error { _, err := protocol.DecodeSessionQualityLimits(raw); return err },
|
||||||
|
"../../fixtures/valid/gateway-quality-poll.json": func(raw []byte) error { _, err := protocol.DecodeGatewayQualityWorkRequest(raw); return err },
|
||||||
|
"../../fixtures/valid/gateway-quality-ack-applied.json": func(raw []byte) error { _, err := protocol.DecodeGatewayQualityAck(raw); return err },
|
||||||
}
|
}
|
||||||
for path, decode := range validFixtures {
|
for path, decode := range validFixtures {
|
||||||
raw, err := os.ReadFile(path)
|
raw, err := os.ReadFile(path)
|
||||||
@@ -111,6 +225,12 @@ func TestRC6JSONFixturesAndCanonicalOperationFields(t *testing.T) {
|
|||||||
"../../fixtures/invalid/video-profile-unknown.json": func(raw []byte) error { _, err := protocol.DecodeVideoProfile(raw); return err },
|
"../../fixtures/invalid/video-profile-unknown.json": func(raw []byte) error { _, err := protocol.DecodeVideoProfile(raw); return err },
|
||||||
"../../fixtures/invalid/bitrate-preference-auto-target.json": func(raw []byte) error { _, err := protocol.DecodeBitratePreference(raw); return err },
|
"../../fixtures/invalid/bitrate-preference-auto-target.json": func(raw []byte) error { _, err := protocol.DecodeBitratePreference(raw); return err },
|
||||||
"../../fixtures/invalid/selected-session-descriptor-provider-field.json": func(raw []byte) error { _, err := protocol.DecodeSelectedSessionDescriptor(raw); return err },
|
"../../fixtures/invalid/selected-session-descriptor-provider-field.json": func(raw []byte) error { _, err := protocol.DecodeSelectedSessionDescriptor(raw); return err },
|
||||||
|
"../../fixtures/invalid/gateway-quality-poll-coordinates.json": func(raw []byte) error { _, err := protocol.DecodeGatewayQualityWorkRequest(raw); return err },
|
||||||
|
"../../fixtures/invalid/gateway-quality-ack-contradictory.json": func(raw []byte) error { _, err := protocol.DecodeGatewayQualityAck(raw); return err },
|
||||||
|
"../../fixtures/invalid/gateway-quality-ack-uppercase-uuid.json": func(raw []byte) error { _, err := protocol.DecodeGatewayQualityAck(raw); return err },
|
||||||
|
"../../fixtures/invalid/gateway-quality-ack-zero-uuid.json": func(raw []byte) error { _, err := protocol.DecodeGatewayQualityAck(raw); return err },
|
||||||
|
"../../fixtures/invalid/quality-operation-offset-time.json": func(raw []byte) error { _, err := protocol.DecodeQualityChangeOperation(raw); return err },
|
||||||
|
"../../fixtures/invalid/quality-operation-noncanonical-fraction.json": func(raw []byte) error { _, err := protocol.DecodeQualityChangeOperation(raw); return err },
|
||||||
}
|
}
|
||||||
for path, decode := range invalidFixtures {
|
for path, decode := range invalidFixtures {
|
||||||
raw, err := os.ReadFile(path)
|
raw, err := os.ReadFile(path)
|
||||||
|
|||||||
+36
-4
@@ -183,6 +183,16 @@ def go_validation(definition: dict[str, Any]) -> list[str]:
|
|||||||
lines.append("\tif v.BitrateTargetKbps > v.BitrateMaximumKbps { violations = append(violations, FieldViolation{Field: \"bitrate_bounds\", Code: \"invalid_order\"}) }")
|
lines.append("\tif v.BitrateTargetKbps > v.BitrateMaximumKbps { violations = append(violations, FieldViolation{Field: \"bitrate_bounds\", Code: \"invalid_order\"}) }")
|
||||||
if name == "BitratePreference":
|
if name == "BitratePreference":
|
||||||
lines.append("\tif v.Mode == \"auto\" && v.TargetKbps != nil || v.Mode == \"explicit\" && v.TargetKbps == nil { violations = append(violations, FieldViolation{Field: \"target_kbps\", Code: \"invalid_tagged_value\"}) }")
|
lines.append("\tif v.Mode == \"auto\" && v.TargetKbps != nil || v.Mode == \"explicit\" && v.TargetKbps == nil { violations = append(violations, FieldViolation{Field: \"target_kbps\", Code: \"invalid_tagged_value\"}) }")
|
||||||
|
if name == "GatewayQualityWorkRequest":
|
||||||
|
lines.append("\tif v.Acquisition == \"poll\" && (v.OperationID != \"\" || v.Revision != nil || v.LeaseGeneration != nil || v.CurrentAppliedRevision != nil) { violations = append(violations, FieldViolation{Field: \"acquisition\", Code: \"invalid_tagged_value\"}) }")
|
||||||
|
lines.append("\tif v.Acquisition == \"prompt\" && (v.OperationID == \"\" || v.Revision == nil || v.LeaseGeneration != nil || v.CurrentAppliedRevision != nil) { violations = append(violations, FieldViolation{Field: \"acquisition\", Code: \"invalid_tagged_value\"}) }")
|
||||||
|
lines.append("\tif v.Acquisition == \"observation\" && (v.OperationID == \"\" || v.Revision == nil || v.LeaseGeneration == nil || v.CurrentAppliedRevision == nil) { violations = append(violations, FieldViolation{Field: \"acquisition\", Code: \"invalid_tagged_value\"}) }")
|
||||||
|
if name == "GatewayStopWorkRequest":
|
||||||
|
lines.append("\tif v.Acquisition == \"poll\" && v.OperationID != \"\" || v.Acquisition == \"prompt\" && v.OperationID == \"\" { violations = append(violations, FieldViolation{Field: \"acquisition\", Code: \"invalid_tagged_value\"}) }")
|
||||||
|
if name == "GatewayQualityAck":
|
||||||
|
lines.append("\tif v.Outcome == \"applied\" && (v.CurrentAppliedRevision == nil || *v.CurrentAppliedRevision != v.Revision) { violations = append(violations, FieldViolation{Field: \"current_applied_revision\", Code: \"invalid_tagged_value\"}) }")
|
||||||
|
lines.append("\tif v.Outcome == \"proven_prior\" && (v.CurrentAppliedRevision == nil || *v.CurrentAppliedRevision >= v.Revision) { violations = append(violations, FieldViolation{Field: \"current_applied_revision\", Code: \"invalid_tagged_value\"}) }")
|
||||||
|
lines.append("\tif v.Outcome == \"unknown\" && v.CurrentAppliedRevision != nil { violations = append(violations, FieldViolation{Field: \"current_applied_revision\", Code: \"invalid_tagged_value\"}) }")
|
||||||
if name == "GatewayRegistration":
|
if name == "GatewayRegistration":
|
||||||
lines.append("\tif v.ProtocolMinVersion > v.ProtocolMaxVersion { violations = append(violations, FieldViolation{Field: \"protocol_version\", Code: \"invalid_order\"}) }")
|
lines.append("\tif v.ProtocolMinVersion > v.ProtocolMaxVersion { violations = append(violations, FieldViolation{Field: \"protocol_version\", Code: \"invalid_order\"}) }")
|
||||||
if name == "ChannelFrame":
|
if name == "ChannelFrame":
|
||||||
@@ -471,6 +481,16 @@ def rust_validation(definition: dict[str, Any]) -> list[str]:
|
|||||||
lines.append(" if self.bitrateTargetKbps > self.bitrateMaximumKbps { return Err(ValidationError::new(\"bitrate_bounds\", \"invalid_order\")); }")
|
lines.append(" if self.bitrateTargetKbps > self.bitrateMaximumKbps { return Err(ValidationError::new(\"bitrate_bounds\", \"invalid_order\")); }")
|
||||||
if name == "BitratePreference":
|
if name == "BitratePreference":
|
||||||
lines.append(" if self.mode == \"auto\" && self.targetKbps.is_some() || self.mode == \"explicit\" && self.targetKbps.is_none() { return Err(ValidationError::new(\"target_kbps\", \"invalid_tagged_value\")); }")
|
lines.append(" if self.mode == \"auto\" && self.targetKbps.is_some() || self.mode == \"explicit\" && self.targetKbps.is_none() { return Err(ValidationError::new(\"target_kbps\", \"invalid_tagged_value\")); }")
|
||||||
|
if name == "GatewayQualityWorkRequest":
|
||||||
|
lines.append(" if self.acquisition == \"poll\" && (self.operationId.is_some() || self.revision.is_some() || self.leaseGeneration.is_some() || self.currentAppliedRevision.is_some()) { return Err(ValidationError::new(\"acquisition\", \"invalid_tagged_value\")); }")
|
||||||
|
lines.append(" if self.acquisition == \"prompt\" && (self.operationId.is_none() || self.revision.is_none() || self.leaseGeneration.is_some() || self.currentAppliedRevision.is_some()) { return Err(ValidationError::new(\"acquisition\", \"invalid_tagged_value\")); }")
|
||||||
|
lines.append(" if self.acquisition == \"observation\" && (self.operationId.is_none() || self.revision.is_none() || self.leaseGeneration.is_none() || self.currentAppliedRevision.is_none()) { return Err(ValidationError::new(\"acquisition\", \"invalid_tagged_value\")); }")
|
||||||
|
if name == "GatewayStopWorkRequest":
|
||||||
|
lines.append(" if self.acquisition == \"poll\" && self.operationId.is_some() || self.acquisition == \"prompt\" && self.operationId.is_none() { return Err(ValidationError::new(\"acquisition\", \"invalid_tagged_value\")); }")
|
||||||
|
if name == "GatewayQualityAck":
|
||||||
|
lines.append(" if self.outcome == \"applied\" && self.currentAppliedRevision != Some(self.revision) { return Err(ValidationError::new(\"current_applied_revision\", \"invalid_tagged_value\")); }")
|
||||||
|
lines.append(" if self.outcome == \"proven_prior\" && self.currentAppliedRevision.map_or(true, |current| current >= self.revision) { return Err(ValidationError::new(\"current_applied_revision\", \"invalid_tagged_value\")); }")
|
||||||
|
lines.append(" if self.outcome == \"unknown\" && self.currentAppliedRevision.is_some() { return Err(ValidationError::new(\"current_applied_revision\", \"invalid_tagged_value\")); }")
|
||||||
if name == "GatewayRegistration":
|
if name == "GatewayRegistration":
|
||||||
lines.append(" if self.protocolMinVersion > self.protocolMaxVersion { return Err(ValidationError::new(\"protocol_version\", \"invalid_order\")); }")
|
lines.append(" if self.protocolMinVersion > self.protocolMaxVersion { return Err(ValidationError::new(\"protocol_version\", \"invalid_order\")); }")
|
||||||
if name == "ChannelFrame":
|
if name == "ChannelFrame":
|
||||||
@@ -687,6 +707,16 @@ def swift_validation(definition: dict[str, Any]) -> list[str]:
|
|||||||
lines.append(" if bitrateTargetKbps > bitrateMaximumKbps { throw ContractValidationError(field: \"bitrate_bounds\", code: \"invalid_order\") }")
|
lines.append(" if bitrateTargetKbps > bitrateMaximumKbps { throw ContractValidationError(field: \"bitrate_bounds\", code: \"invalid_order\") }")
|
||||||
if name == "BitratePreference":
|
if name == "BitratePreference":
|
||||||
lines.append(" if mode == \"auto\" && targetKbps != nil || mode == \"explicit\" && targetKbps == nil { throw ContractValidationError(field: \"target_kbps\", code: \"invalid_tagged_value\") }")
|
lines.append(" if mode == \"auto\" && targetKbps != nil || mode == \"explicit\" && targetKbps == nil { throw ContractValidationError(field: \"target_kbps\", code: \"invalid_tagged_value\") }")
|
||||||
|
if name == "GatewayQualityWorkRequest":
|
||||||
|
lines.append(" if acquisition == \"poll\" && (operationId != nil || revision != nil || leaseGeneration != nil || currentAppliedRevision != nil) { throw ContractValidationError(field: \"acquisition\", code: \"invalid_tagged_value\") }")
|
||||||
|
lines.append(" if acquisition == \"prompt\" && (operationId == nil || revision == nil || leaseGeneration != nil || currentAppliedRevision != nil) { throw ContractValidationError(field: \"acquisition\", code: \"invalid_tagged_value\") }")
|
||||||
|
lines.append(" if acquisition == \"observation\" && (operationId == nil || revision == nil || leaseGeneration == nil || currentAppliedRevision == nil) { throw ContractValidationError(field: \"acquisition\", code: \"invalid_tagged_value\") }")
|
||||||
|
if name == "GatewayStopWorkRequest":
|
||||||
|
lines.append(" if acquisition == \"poll\" && operationId != nil || acquisition == \"prompt\" && operationId == nil { throw ContractValidationError(field: \"acquisition\", code: \"invalid_tagged_value\") }")
|
||||||
|
if name == "GatewayQualityAck":
|
||||||
|
lines.append(" if outcome == \"applied\" && currentAppliedRevision != revision { throw ContractValidationError(field: \"current_applied_revision\", code: \"invalid_tagged_value\") }")
|
||||||
|
lines.append(" if outcome == \"proven_prior\" && (currentAppliedRevision == nil || currentAppliedRevision! >= revision) { throw ContractValidationError(field: \"current_applied_revision\", code: \"invalid_tagged_value\") }")
|
||||||
|
lines.append(" if outcome == \"unknown\" && currentAppliedRevision != nil { throw ContractValidationError(field: \"current_applied_revision\", code: \"invalid_tagged_value\") }")
|
||||||
if name == "GatewayRegistration":
|
if name == "GatewayRegistration":
|
||||||
lines.append(" if protocolMinVersion > protocolMaxVersion { throw ContractValidationError(field: \"protocol_version\", code: \"invalid_order\") }")
|
lines.append(" if protocolMinVersion > protocolMaxVersion { throw ContractValidationError(field: \"protocol_version\", code: \"invalid_order\") }")
|
||||||
if name == "ChannelFrame":
|
if name == "ChannelFrame":
|
||||||
@@ -706,6 +736,7 @@ def generate_swift(defs: dict[str, dict[str, Any]], schema_hash: str, compatibil
|
|||||||
"public struct ContractValidationError: Error, Equatable { public let field: String; public let code: String }",
|
"public struct ContractValidationError: Error, Equatable { public let field: String; public let code: String }",
|
||||||
"private struct AnyCodingKey: CodingKey { let stringValue: String; let intValue: Int?; init?(stringValue: String) { self.stringValue = stringValue; self.intValue = nil }; init?(intValue: Int) { self.stringValue = String(intValue); self.intValue = intValue } }",
|
"private struct AnyCodingKey: CodingKey { let stringValue: String; let intValue: Int?; init?(stringValue: String) { self.stringValue = stringValue; self.intValue = nil }; init?(intValue: Int) { self.stringValue = String(intValue); self.intValue = intValue } }",
|
||||||
"private func rejectDuplicateJSONKeys(_ data: Data) throws {",
|
"private func rejectDuplicateJSONKeys(_ data: Data) throws {",
|
||||||
|
" guard data.count <= 1_048_576 else { throw ContractValidationError(field: \"json\", code: \"payload_too_large\") }",
|
||||||
" var index = 0",
|
" var index = 0",
|
||||||
" func skipWhitespace() { while index < data.count && [9, 10, 13, 32].contains(data[index]) { index += 1 } }",
|
" func skipWhitespace() { while index < data.count && [9, 10, 13, 32].contains(data[index]) { index += 1 } }",
|
||||||
" func parseString() throws -> String {",
|
" func parseString() throws -> String {",
|
||||||
@@ -719,7 +750,8 @@ def generate_swift(defs: dict[str, dict[str, Any]], schema_hash: str, compatibil
|
|||||||
" }",
|
" }",
|
||||||
" throw ContractValidationError(field: \"json\", code: \"invalid_json\")",
|
" throw ContractValidationError(field: \"json\", code: \"invalid_json\")",
|
||||||
" }",
|
" }",
|
||||||
" func parseValue() throws {",
|
" func parseValue(_ depth: Int) throws {",
|
||||||
|
" guard depth <= 64 else { throw ContractValidationError(field: \"json\", code: \"nesting_too_deep\") }",
|
||||||
" skipWhitespace()",
|
" skipWhitespace()",
|
||||||
" guard index < data.count else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
|
" guard index < data.count else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
|
||||||
" if data[index] == 123 {",
|
" if data[index] == 123 {",
|
||||||
@@ -734,7 +766,7 @@ def generate_swift(defs: dict[str, dict[str, Any]], schema_hash: str, compatibil
|
|||||||
" skipWhitespace()",
|
" skipWhitespace()",
|
||||||
" guard index < data.count, data[index] == 58 else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
|
" guard index < data.count, data[index] == 58 else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
|
||||||
" index += 1",
|
" index += 1",
|
||||||
" try parseValue()",
|
" try parseValue(depth + 1)",
|
||||||
" skipWhitespace()",
|
" skipWhitespace()",
|
||||||
" guard index < data.count else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
|
" guard index < data.count else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
|
||||||
" if data[index] == 125 { index += 1; return }",
|
" if data[index] == 125 { index += 1; return }",
|
||||||
@@ -747,7 +779,7 @@ def generate_swift(defs: dict[str, dict[str, Any]], schema_hash: str, compatibil
|
|||||||
" skipWhitespace()",
|
" skipWhitespace()",
|
||||||
" if index < data.count, data[index] == 93 { index += 1; return }",
|
" if index < data.count, data[index] == 93 { index += 1; return }",
|
||||||
" while true {",
|
" while true {",
|
||||||
" try parseValue()",
|
" try parseValue(depth + 1)",
|
||||||
" skipWhitespace()",
|
" skipWhitespace()",
|
||||||
" guard index < data.count else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
|
" guard index < data.count else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
|
||||||
" if data[index] == 93 { index += 1; return }",
|
" if data[index] == 93 { index += 1; return }",
|
||||||
@@ -760,7 +792,7 @@ def generate_swift(defs: dict[str, dict[str, Any]], schema_hash: str, compatibil
|
|||||||
" while index < data.count && ![9, 10, 13, 32, 44, 93, 125].contains(data[index]) { index += 1 }",
|
" while index < data.count && ![9, 10, 13, 32, 44, 93, 125].contains(data[index]) { index += 1 }",
|
||||||
" guard index > start else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
|
" guard index > start else { throw ContractValidationError(field: \"json\", code: \"invalid_json\") }",
|
||||||
" }",
|
" }",
|
||||||
" try parseValue()",
|
" try parseValue(0)",
|
||||||
" skipWhitespace()",
|
" skipWhitespace()",
|
||||||
" guard index == data.count else { throw ContractValidationError(field: \"json\", code: \"trailing_json\") }",
|
" guard index == data.count else { throw ContractValidationError(field: \"json\", code: \"trailing_json\") }",
|
||||||
"}",
|
"}",
|
||||||
|
|||||||
@@ -69,6 +69,21 @@ let audio = try AudioProfile(codec: "opus", sampleRateHz: 48000, channels: 2, ch
|
|||||||
let display = try DisplayMode(resolutionWidth: 2560, resolutionHeight: 1440, fps: 120)
|
let display = try DisplayMode(resolutionWidth: 2560, resolutionHeight: 1440, fps: 120)
|
||||||
let adjustment = try SessionAdjustment(displayReason: "none", bitrateReason: "none")
|
let adjustment = try SessionAdjustment(displayReason: "none", bitrateReason: "none")
|
||||||
let descriptor = try SelectedSessionDescriptor(videoProfile: video, audioProfile: audio, displayMode: display, bitrateTargetKbps: 40000, bitrateMaximumKbps: 50000, adjustment: adjustment, mediaTimestampBasis: "gateway-send-wall-clock-ms")
|
let descriptor = try SelectedSessionDescriptor(videoProfile: video, audioProfile: audio, displayMode: display, bitrateTargetKbps: 40000, bitrateMaximumKbps: 50000, adjustment: adjustment, mediaTimestampBasis: "gateway-send-wall-clock-ms")
|
||||||
|
let operationId = "12345678-1234-1234-1234-123456789abc"
|
||||||
|
_ = try GatewayQualityWorkRequest(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, acquisition: "poll", operationId: nil, revision: nil, leaseGeneration: nil, currentAppliedRevision: nil)
|
||||||
|
_ = try GatewayQualityWorkRequest(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, acquisition: "prompt", operationId: operationId, revision: 7, leaseGeneration: nil, currentAppliedRevision: nil)
|
||||||
|
_ = try GatewayQualityWorkRequest(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, acquisition: "observation", operationId: operationId, revision: 7, leaseGeneration: 3, currentAppliedRevision: 6)
|
||||||
|
do {
|
||||||
|
_ = try GatewayQualityWorkRequest(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, acquisition: "poll", operationId: operationId, revision: 7, leaseGeneration: nil, currentAppliedRevision: nil)
|
||||||
|
fatalError("poll accepted unknown operation coordinates")
|
||||||
|
} catch { }
|
||||||
|
_ = try GatewayQualityAck(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, operationId: operationId, revision: 7, leaseGeneration: 3, outcome: "applied", currentAppliedRevision: 7, failureCode: nil)
|
||||||
|
_ = try GatewayQualityAck(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, operationId: operationId, revision: 7, leaseGeneration: 3, outcome: "proven_prior", currentAppliedRevision: 6, failureCode: nil)
|
||||||
|
_ = try GatewayQualityAck(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, operationId: operationId, revision: 7, leaseGeneration: 3, outcome: "unknown", currentAppliedRevision: nil, failureCode: nil)
|
||||||
|
do {
|
||||||
|
_ = try GatewayQualityAck(version: "1", sessionId: "session", gatewayId: "gateway", reconnectSequence: 2, operationId: operationId, revision: 7, leaseGeneration: 3, outcome: "applied", currentAppliedRevision: 6, failureCode: nil)
|
||||||
|
fatalError("applied ack accepted a contradictory revision")
|
||||||
|
} catch { }
|
||||||
let capability = try CapabilityProfile(
|
let capability = try CapabilityProfile(
|
||||||
transport: "quic-tls13", framing: "datagram-v1", media: "encoded",
|
transport: "quic-tls13", framing: "datagram-v1", media: "encoded",
|
||||||
sourceRateControl: "server", videoProfiles: [video], audioProfiles: [audio]
|
sourceRateControl: "server", videoProfiles: [video], audioProfiles: [audio]
|
||||||
@@ -191,6 +206,19 @@ do {
|
|||||||
_ = try CapabilityProfile.decodeJSON(duplicateCapability)
|
_ = try CapabilityProfile.decodeJSON(duplicateCapability)
|
||||||
fatalError("capability accepted duplicate JSON keys")
|
fatalError("capability accepted duplicate JSON keys")
|
||||||
} catch { }
|
} catch { }
|
||||||
|
do {
|
||||||
|
_ = try CapabilityProfile.decodeJSON(Data(repeating: 32, count: 1_048_577))
|
||||||
|
fatalError("capability accepted oversized JSON")
|
||||||
|
} catch let error as ContractValidationError {
|
||||||
|
guard error.code == "payload_too_large" else { fatalError("oversized JSON was not rejected before parsing") }
|
||||||
|
}
|
||||||
|
let deeplyNested = Data((String(repeating: "[", count: 65) + "null" + String(repeating: "]", count: 65)).utf8)
|
||||||
|
do {
|
||||||
|
_ = try CapabilityProfile.decodeJSON(deeplyNested)
|
||||||
|
fatalError("capability accepted over-deep JSON")
|
||||||
|
} catch let error as ContractValidationError {
|
||||||
|
guard error.code == "nesting_too_deep" else { fatalError("over-deep JSON was not rejected before decoding") }
|
||||||
|
}
|
||||||
for field in ["version", "session_id", "gateway_id", "audience", "reconnect_sequence", "expires_at", "capabilities", "selected_descriptor"] {
|
for field in ["version", "session_id", "gateway_id", "audience", "reconnect_sequence", "expires_at", "capabilities", "selected_descriptor"] {
|
||||||
var missing = clientAuthorityObject
|
var missing = clientAuthorityObject
|
||||||
missing.removeValue(forKey: field)
|
missing.removeValue(forKey: field)
|
||||||
@@ -385,6 +413,15 @@ fn main() {
|
|||||||
let display = DisplayMode::new(2560, 1440, 120).unwrap();
|
let display = DisplayMode::new(2560, 1440, 120).unwrap();
|
||||||
let adjustment = SessionAdjustment::new("none".into(), "none".into()).unwrap();
|
let adjustment = SessionAdjustment::new("none".into(), "none".into()).unwrap();
|
||||||
let descriptor = SelectedSessionDescriptor::new(video.clone(), audio.clone(), display.clone(), 40000, 50000, adjustment, "gateway-send-wall-clock-ms".into()).unwrap();
|
let descriptor = SelectedSessionDescriptor::new(video.clone(), audio.clone(), display.clone(), 40000, 50000, adjustment, "gateway-send-wall-clock-ms".into()).unwrap();
|
||||||
|
let operation_id = "12345678-1234-1234-1234-123456789abc".to_string();
|
||||||
|
assert!(GatewayQualityWorkRequest::new("1".into(), "session".into(), "gateway".into(), 2, "poll".into(), None, None, None, None).is_ok());
|
||||||
|
assert!(GatewayQualityWorkRequest::new("1".into(), "session".into(), "gateway".into(), 2, "prompt".into(), Some(operation_id.clone()), Some(7), None, None).is_ok());
|
||||||
|
assert!(GatewayQualityWorkRequest::new("1".into(), "session".into(), "gateway".into(), 2, "observation".into(), Some(operation_id.clone()), Some(7), Some(3), Some(6)).is_ok());
|
||||||
|
assert!(GatewayQualityWorkRequest::new("1".into(), "session".into(), "gateway".into(), 2, "poll".into(), Some(operation_id.clone()), Some(7), None, None).is_err());
|
||||||
|
assert!(GatewayQualityAck::new("1".into(), "session".into(), "gateway".into(), 2, operation_id.clone(), 7, 3, "applied".into(), Some(7), None).is_ok());
|
||||||
|
assert!(GatewayQualityAck::new("1".into(), "session".into(), "gateway".into(), 2, operation_id.clone(), 7, 3, "proven_prior".into(), Some(6), None).is_ok());
|
||||||
|
assert!(GatewayQualityAck::new("1".into(), "session".into(), "gateway".into(), 2, operation_id.clone(), 7, 3, "unknown".into(), None, None).is_ok());
|
||||||
|
assert!(GatewayQualityAck::new("1".into(), "session".into(), "gateway".into(), 2, operation_id, 7, 3, "applied".into(), Some(6), None).is_err());
|
||||||
let capabilities = CapabilityProfile::new(
|
let capabilities = CapabilityProfile::new(
|
||||||
"quic-tls13".into(), "datagram-v1".into(), "encoded".into(),
|
"quic-tls13".into(), "datagram-v1".into(), "encoded".into(),
|
||||||
"server".into(), vec![video.clone()], vec![audio.clone()],
|
"server".into(), vec![video.clone()], vec![audio.clone()],
|
||||||
@@ -544,7 +581,8 @@ fn main() {
|
|||||||
)
|
)
|
||||||
run(["rustc", str(rust), "-o", str(workspace / "rust-contracts")], ROOT)
|
run(["rustc", str(rust), "-o", str(workspace / "rust-contracts")], ROOT)
|
||||||
run([str(workspace / "rust-contracts")], ROOT)
|
run([str(workspace / "rust-contracts")], ROOT)
|
||||||
expected_protobuf_fields = [
|
expected_protobuf_messages = {
|
||||||
|
"ClientSessionAuthority": [
|
||||||
("version", 1),
|
("version", 1),
|
||||||
("session_id", 2),
|
("session_id", 2),
|
||||||
("gateway_id", 3),
|
("gateway_id", 3),
|
||||||
@@ -553,11 +591,56 @@ fn main() {
|
|||||||
("expires_at", 6),
|
("expires_at", 6),
|
||||||
("capabilities", 7),
|
("capabilities", 7),
|
||||||
("selected_descriptor", 8),
|
("selected_descriptor", 8),
|
||||||
]
|
],
|
||||||
actual_protobuf_fields = protobuf_message_fields("ClientSessionAuthority")
|
"GatewayQualityWorkRequest": [
|
||||||
|
("version", 1),
|
||||||
|
("session_id", 2),
|
||||||
|
("gateway_id", 3),
|
||||||
|
("reconnect_sequence", 4),
|
||||||
|
("operation_id", 5),
|
||||||
|
("revision", 6),
|
||||||
|
("current_applied_revision", 7),
|
||||||
|
("acquisition", 8),
|
||||||
|
("lease_generation", 9),
|
||||||
|
],
|
||||||
|
"GatewayQualityWork": [
|
||||||
|
("version", 1),
|
||||||
|
("session_id", 2),
|
||||||
|
("gateway_id", 3),
|
||||||
|
("reconnect_sequence", 4),
|
||||||
|
("operation_id", 5),
|
||||||
|
("revision", 6),
|
||||||
|
("lease_expires_at", 7),
|
||||||
|
("selected_descriptor", 8),
|
||||||
|
("current_applied_revision", 9),
|
||||||
|
("lease_generation", 10),
|
||||||
|
],
|
||||||
|
"GatewayQualityAck": [
|
||||||
|
("version", 1),
|
||||||
|
("session_id", 2),
|
||||||
|
("gateway_id", 3),
|
||||||
|
("reconnect_sequence", 4),
|
||||||
|
("operation_id", 5),
|
||||||
|
("revision", 6),
|
||||||
|
("outcome", 7),
|
||||||
|
("current_applied_revision", 8),
|
||||||
|
("failure_code", 9),
|
||||||
|
("lease_generation", 10),
|
||||||
|
],
|
||||||
|
"GatewayStopWorkRequest": [
|
||||||
|
("version", 1),
|
||||||
|
("session_id", 2),
|
||||||
|
("gateway_id", 3),
|
||||||
|
("reconnect_sequence", 4),
|
||||||
|
("operation_id", 5),
|
||||||
|
("acquisition", 6),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
for message, expected_protobuf_fields in expected_protobuf_messages.items():
|
||||||
|
actual_protobuf_fields = protobuf_message_fields(message)
|
||||||
if actual_protobuf_fields != expected_protobuf_fields:
|
if actual_protobuf_fields != expected_protobuf_fields:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"ClientSessionAuthority protobuf fields = {actual_protobuf_fields}; "
|
f"{message} protobuf fields = {actual_protobuf_fields}; "
|
||||||
f"want {expected_protobuf_fields}"
|
f"want {expected_protobuf_fields}"
|
||||||
)
|
)
|
||||||
rust_unknown = workspace / "unknown.rs"
|
rust_unknown = workspace / "unknown.rs"
|
||||||
|
|||||||
@@ -53,6 +53,27 @@ def main() -> int:
|
|||||||
for owner in ("SessionAuthority", "ClientSessionAuthority"):
|
for owner in ("SessionAuthority", "ClientSessionAuthority"):
|
||||||
assert defs[owner]["required"][-1] == "selected_descriptor"
|
assert defs[owner]["required"][-1] == "selected_descriptor"
|
||||||
assert defs["ConnectionManifest"]["required"][-1] == "selected_descriptor"
|
assert defs["ConnectionManifest"]["required"][-1] == "selected_descriptor"
|
||||||
|
quality_request = defs["GatewayQualityWorkRequest"]
|
||||||
|
assert quality_request["required"] == ["version", "session_id", "gateway_id", "reconnect_sequence", "acquisition"]
|
||||||
|
assert quality_request["properties"]["acquisition"]["enum"] == ["poll", "prompt", "observation"]
|
||||||
|
assert quality_request["properties"]["lease_generation"]["minimum"] == 1
|
||||||
|
assert defs["GatewayStopWorkRequest"]["required"] == ["version", "session_id", "gateway_id", "reconnect_sequence", "acquisition"]
|
||||||
|
assert defs["GatewayQualityWork"]["required"][-3:] == ["lease_generation", "lease_expires_at", "selected_descriptor"]
|
||||||
|
assert defs["GatewayQualityAck"]["required"][-3:] == ["revision", "lease_generation", "outcome"]
|
||||||
|
assert defs["GatewayQualityAck"]["properties"]["outcome"]["enum"] == ["applied", "proven_prior", "unknown"]
|
||||||
|
|
||||||
|
operation_id_pattern = r"^(?!00000000-0000-0000-0000-000000000000$)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
||||||
|
canonical_time_pattern = r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{0,8}[1-9])?Z$"
|
||||||
|
for definition in ("QualityChangeOperation", "StopOperation", "GatewayQualityWorkRequest", "GatewayQualityWork", "GatewayQualityAck", "GatewayStopWorkRequest", "GatewayStopWork", "GatewayStopAck"):
|
||||||
|
assert defs[definition]["properties"]["operation_id"]["pattern"] == operation_id_pattern, definition
|
||||||
|
assert defs[definition]["x-max-bytes"] == 16384, definition
|
||||||
|
for definition, fields in {
|
||||||
|
"QualityChangeOperation": ("created_at", "deadline_at", "updated_at"),
|
||||||
|
"StopOperation": ("created_at", "deadline_at", "updated_at"),
|
||||||
|
"GatewayQualityWork": ("lease_expires_at",),
|
||||||
|
}.items():
|
||||||
|
for field in fields:
|
||||||
|
assert defs[definition]["properties"][field]["pattern"] == canonical_time_pattern, (definition, field)
|
||||||
|
|
||||||
display_mode = defs["DisplayMode"]
|
display_mode = defs["DisplayMode"]
|
||||||
assert display_mode["required"] == ["resolution_width", "resolution_height", "fps"]
|
assert display_mode["required"] == ["resolution_width", "resolution_height", "fps"]
|
||||||
@@ -207,6 +228,18 @@ def main() -> int:
|
|||||||
):
|
):
|
||||||
operation = openapi.split(f" operationId: {operation_id}\n", 1)[1].split(" responses:\n", 1)[0]
|
operation = openapi.split(f" operationId: {operation_id}\n", 1)[1].split(" responses:\n", 1)[0]
|
||||||
assert "gatewayMutualTLS: []" in operation and "nativeBearer" not in operation and "browserSession" not in operation, operation_id
|
assert "gatewayMutualTLS: []" in operation and "nativeBearer" not in operation and "browserSession" not in operation, operation_id
|
||||||
|
assert "certificate identity MUST match" in operation, operation_id
|
||||||
|
assert "Maximum JSON body: 16384 bytes." in operation, operation_id
|
||||||
|
quality_acquisition = openapi.split(" operationId: acquireGatewayQualityWork\n", 1)[1].split(" responses:\n", 1)[0]
|
||||||
|
assert "`poll` acquisition omits unknown operation coordinates" in quality_acquisition
|
||||||
|
assert "coordinates MUST match exactly" in quality_acquisition
|
||||||
|
quality_acknowledgement = openapi.split(" operationId: acknowledgeGatewayQualityWork\n", 1)[1].split(" responses:\n", 1)[0]
|
||||||
|
assert "Stale lease generations MUST be rejected" in quality_acknowledgement
|
||||||
|
for operation_id in ("createSessionQualityChange", "getSessionQualityChange", "createSessionStopOperation", "getSessionStopOperation"):
|
||||||
|
operation = openapi.split(f" operationId: {operation_id}\n", 1)[1].split(" responses:\n", 1)[0]
|
||||||
|
assert "owning principal and active device/key" in operation, operation_id
|
||||||
|
assert openapi.count("Maximum JSON body: 16384 bytes.") >= 8
|
||||||
|
assert operation_id_pattern in openapi
|
||||||
assert "provider_url" not in openapi and "vm_address" not in openapi
|
assert "provider_url" not in openapi and "vm_address" not in openapi
|
||||||
session_endpoint = openapi.split(" /api/v1/auth/session:", 1)[1].split("\n /api/", 1)[0]
|
session_endpoint = openapi.split(" /api/v1/auth/session:", 1)[1].split("\n /api/", 1)[0]
|
||||||
assert "$defs/BrowserAuthenticatedSession" in session_endpoint
|
assert "$defs/BrowserAuthenticatedSession" in session_endpoint
|
||||||
@@ -238,6 +271,7 @@ def main() -> int:
|
|||||||
"""
|
"""
|
||||||
admin_override = openapi.split(" operationId: updateEntitlementDisplayLimitOverride\n", 1)[1].split(" responses:\n", 1)[0]
|
admin_override = openapi.split(" operationId: updateEntitlementDisplayLimitOverride\n", 1)[1].split(" responses:\n", 1)[0]
|
||||||
assert browser_requirement.removeprefix(" ") in admin_override
|
assert browser_requirement.removeprefix(" ") in admin_override
|
||||||
|
assert "Maximum JSON body: 16384 bytes." in admin_override
|
||||||
for operation_id in (
|
for operation_id in (
|
||||||
"issueReauthenticationGrant", "logoutSession", "registerDevice", "proveDevice", "revokeDevice",
|
"issueReauthenticationGrant", "logoutSession", "registerDevice", "proveDevice", "revokeDevice",
|
||||||
"requestBrokerSession", "allocateBrokerSession", "reconnectBrokerSession", "cancelBrokerSession",
|
"requestBrokerSession", "allocateBrokerSession", "reconnectBrokerSession", "cancelBrokerSession",
|
||||||
|
|||||||
Reference in New Issue
Block a user