feat(protocol): define native session credentials
This commit is contained in:
+474
-19
@@ -13,11 +13,11 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const SchemaSHA256 = "b2bb0a8ac8ef56dbc0e1443eeb5b3028be9e71ec2f5fd8e73928d71b7cd9340c"
|
||||
const SchemaSHA256 = "dea3dd210c53d5a2d37050dd6afd8b0ac5bb8edcb7ab25a02e4026489ce8a00f"
|
||||
const ProtocolVersion = "1.0.0"
|
||||
const CurrentWireVersion = "1"
|
||||
const NMinus1WireVersion = "0"
|
||||
const NMinus2WireVersion = "-1"
|
||||
const CurrentWireVersion = "2"
|
||||
const NMinus1WireVersion = "1"
|
||||
const NMinus2WireVersion = "0"
|
||||
|
||||
type FieldViolation struct {
|
||||
Field string `json:"field"`
|
||||
@@ -69,6 +69,13 @@ type BrokerSession struct {
|
||||
EffectiveDisplayMode *DisplayMode `json:"effective_display_mode,omitempty"`
|
||||
}
|
||||
|
||||
type BrowserAuthenticatedSession struct {
|
||||
Username string `json:"username"`
|
||||
Provider string `json:"provider"`
|
||||
Roles []string `json:"roles"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
type CapabilityProfile struct {
|
||||
Transport string `json:"transport"`
|
||||
Framing string `json:"framing"`
|
||||
@@ -281,6 +288,14 @@ type ManifestTunnel struct {
|
||||
Features []string `json:"features"`
|
||||
}
|
||||
|
||||
type NativeAuthenticatedSession struct {
|
||||
Username string `json:"username"`
|
||||
Provider string `json:"provider"`
|
||||
Roles []string `json:"roles"`
|
||||
Role string `json:"role"`
|
||||
NativeIdentity NativeSessionIdentity `json:"native_identity"`
|
||||
}
|
||||
|
||||
type NativeCredential struct {
|
||||
DeviceID string `json:"device_id,omitempty"`
|
||||
FamilyID string `json:"family_id"`
|
||||
@@ -290,6 +305,19 @@ type NativeCredential struct {
|
||||
RefreshExpiresAt string `json:"refresh_expires_at,omitempty"`
|
||||
}
|
||||
|
||||
type NativeSessionIdentity struct {
|
||||
ClientDeviceID string `json:"client_device_id"`
|
||||
DeviceKeyID string `json:"device_key_id"`
|
||||
}
|
||||
|
||||
type NativeTunnelCredential struct {
|
||||
ClientDeviceID string `json:"client_device_id"`
|
||||
DeviceKeyID string `json:"device_key_id"`
|
||||
CertificateChainPem string `json:"certificate_chain_pem"`
|
||||
TrustBundlePem string `json:"trust_bundle_pem"`
|
||||
ExpiresAt string `json:"expires_at"`
|
||||
}
|
||||
|
||||
type PageInfo struct {
|
||||
Limit int64 `json:"limit"`
|
||||
NextCursor string `json:"next_cursor"`
|
||||
@@ -392,12 +420,11 @@ type SessionAuthority struct {
|
||||
}
|
||||
|
||||
type SessionRequest struct {
|
||||
ClientDeviceID string `json:"client_device_id"`
|
||||
DeviceKeyID string `json:"device_key_id"`
|
||||
PoolID string `json:"pool_id"`
|
||||
IdempotencyKey string `json:"idempotency_key"`
|
||||
PolicySnapshot AllocationPolicy `json:"policy_snapshot"`
|
||||
RequestedDisplayMode *DisplayMode `json:"requested_display_mode,omitempty"`
|
||||
ClientDeviceID string `json:"client_device_id"`
|
||||
DeviceKeyID string `json:"device_key_id"`
|
||||
PoolID string `json:"pool_id"`
|
||||
IdempotencyKey string `json:"idempotency_key"`
|
||||
RequestedDisplayMode *DisplayMode `json:"requested_display_mode,omitempty"`
|
||||
}
|
||||
|
||||
type StableError struct {
|
||||
@@ -863,6 +890,105 @@ func EncodeBrokerSession(value BrokerSession) ([]byte, error) {
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
func (v BrowserAuthenticatedSession) Validate() error {
|
||||
var violations []FieldViolation
|
||||
if v.Username == "" {
|
||||
violations = append(violations, FieldViolation{Field: "username", Code: "required"})
|
||||
}
|
||||
if len(v.Username) < 1 && v.Username != "" {
|
||||
violations = append(violations, FieldViolation{Field: "username", Code: "min_length"})
|
||||
}
|
||||
if len(v.Username) > 256 {
|
||||
violations = append(violations, FieldViolation{Field: "username", Code: "max_length"})
|
||||
}
|
||||
if v.Provider == "" {
|
||||
violations = append(violations, FieldViolation{Field: "provider", Code: "required"})
|
||||
}
|
||||
if len(v.Provider) < 1 && v.Provider != "" {
|
||||
violations = append(violations, FieldViolation{Field: "provider", Code: "min_length"})
|
||||
}
|
||||
if len(v.Provider) > 64 {
|
||||
violations = append(violations, FieldViolation{Field: "provider", Code: "max_length"})
|
||||
}
|
||||
if v.Roles == nil {
|
||||
violations = append(violations, FieldViolation{Field: "roles", Code: "required"})
|
||||
}
|
||||
if len(v.Roles) > 16 {
|
||||
violations = append(violations, FieldViolation{Field: "roles", Code: "max_items"})
|
||||
}
|
||||
for _, item := range v.Roles {
|
||||
if len(item) < 1 {
|
||||
violations = append(violations, FieldViolation{Field: "roles", Code: "min_item_length"})
|
||||
}
|
||||
}
|
||||
for _, item := range v.Roles {
|
||||
if len(item) > 64 {
|
||||
violations = append(violations, FieldViolation{Field: "roles", Code: "max_item_length"})
|
||||
}
|
||||
}
|
||||
for _, item := range v.Roles {
|
||||
if len(item) > 64 {
|
||||
violations = append(violations, FieldViolation{Field: "roles", Code: "max_item_bytes"})
|
||||
}
|
||||
}
|
||||
if v.Role == "" {
|
||||
violations = append(violations, FieldViolation{Field: "role", Code: "required"})
|
||||
}
|
||||
if v.Role != "" && !(v.Role == "user" || v.Role == "admin") {
|
||||
violations = append(violations, FieldViolation{Field: "role", Code: "invalid_value"})
|
||||
}
|
||||
if len(violations) > 0 {
|
||||
return ValidationError{Violations: violations}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DecodeBrowserAuthenticatedSession(data []byte) (BrowserAuthenticatedSession, error) {
|
||||
var value BrowserAuthenticatedSession
|
||||
if len(data) > 1024*1024 {
|
||||
return value, errors.New("protocol payload exceeds limit")
|
||||
}
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &fields); err != nil {
|
||||
return value, err
|
||||
}
|
||||
if raw, ok := fields["provider"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "provider", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["role"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "role", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["roles"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "roles", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["username"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "username", Code: "required"}}}
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&value); err != nil {
|
||||
return value, err
|
||||
}
|
||||
var trailing any
|
||||
if err := decoder.Decode(&trailing); err != io.EOF {
|
||||
if err == nil {
|
||||
return value, errors.New("trailing JSON value")
|
||||
}
|
||||
return value, err
|
||||
}
|
||||
if err := value.Validate(); err != nil {
|
||||
return value, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func EncodeBrowserAuthenticatedSession(value BrowserAuthenticatedSession) ([]byte, error) {
|
||||
if err := value.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
func (v CapabilityProfile) Validate() error {
|
||||
var violations []FieldViolation
|
||||
if v.Transport == "" {
|
||||
@@ -2689,6 +2815,16 @@ func (v GatewayRegistration) Validate() error {
|
||||
if len(v.Features) > 64 {
|
||||
violations = append(violations, FieldViolation{Field: "features", Code: "max_items"})
|
||||
}
|
||||
for _, item := range v.Features {
|
||||
if len(item) < 1 {
|
||||
violations = append(violations, FieldViolation{Field: "features", Code: "min_item_length"})
|
||||
}
|
||||
}
|
||||
for _, item := range v.Features {
|
||||
if len(item) > 64 {
|
||||
violations = append(violations, FieldViolation{Field: "features", Code: "max_item_length"})
|
||||
}
|
||||
}
|
||||
if reflect.DeepEqual(v.Capabilities, CapabilityProfile{}) {
|
||||
violations = append(violations, FieldViolation{Field: "capabilities", Code: "required"})
|
||||
}
|
||||
@@ -3231,6 +3367,16 @@ func (v ManifestGateway) Validate() error {
|
||||
if len(v.Addresses) > 4 {
|
||||
violations = append(violations, FieldViolation{Field: "addresses", Code: "max_items"})
|
||||
}
|
||||
for _, item := range v.Addresses {
|
||||
if len(item) < 1 {
|
||||
violations = append(violations, FieldViolation{Field: "addresses", Code: "min_item_length"})
|
||||
}
|
||||
}
|
||||
for _, item := range v.Addresses {
|
||||
if len(item) > 256 {
|
||||
violations = append(violations, FieldViolation{Field: "addresses", Code: "max_item_length"})
|
||||
}
|
||||
}
|
||||
if v.PublicIdentity == "" {
|
||||
violations = append(violations, FieldViolation{Field: "public_identity", Code: "required"})
|
||||
}
|
||||
@@ -3371,12 +3517,32 @@ func (v ManifestTunnel) Validate() error {
|
||||
if len(v.Versions) > 4 {
|
||||
violations = append(violations, FieldViolation{Field: "versions", Code: "max_items"})
|
||||
}
|
||||
for _, item := range v.Versions {
|
||||
if len(item) < 1 {
|
||||
violations = append(violations, FieldViolation{Field: "versions", Code: "min_item_length"})
|
||||
}
|
||||
}
|
||||
for _, item := range v.Versions {
|
||||
if len(item) > 64 {
|
||||
violations = append(violations, FieldViolation{Field: "versions", Code: "max_item_length"})
|
||||
}
|
||||
}
|
||||
if v.Features == nil {
|
||||
violations = append(violations, FieldViolation{Field: "features", Code: "required"})
|
||||
}
|
||||
if len(v.Features) > 32 {
|
||||
violations = append(violations, FieldViolation{Field: "features", Code: "max_items"})
|
||||
}
|
||||
for _, item := range v.Features {
|
||||
if len(item) < 1 {
|
||||
violations = append(violations, FieldViolation{Field: "features", Code: "min_item_length"})
|
||||
}
|
||||
}
|
||||
for _, item := range v.Features {
|
||||
if len(item) > 64 {
|
||||
violations = append(violations, FieldViolation{Field: "features", Code: "max_item_length"})
|
||||
}
|
||||
}
|
||||
if len(violations) > 0 {
|
||||
return ValidationError{Violations: violations}
|
||||
}
|
||||
@@ -3423,6 +3589,114 @@ func EncodeManifestTunnel(value ManifestTunnel) ([]byte, error) {
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
func (v NativeAuthenticatedSession) Validate() error {
|
||||
var violations []FieldViolation
|
||||
if v.Username == "" {
|
||||
violations = append(violations, FieldViolation{Field: "username", Code: "required"})
|
||||
}
|
||||
if len(v.Username) < 1 && v.Username != "" {
|
||||
violations = append(violations, FieldViolation{Field: "username", Code: "min_length"})
|
||||
}
|
||||
if len(v.Username) > 256 {
|
||||
violations = append(violations, FieldViolation{Field: "username", Code: "max_length"})
|
||||
}
|
||||
if v.Provider == "" {
|
||||
violations = append(violations, FieldViolation{Field: "provider", Code: "required"})
|
||||
}
|
||||
if len(v.Provider) < 1 && v.Provider != "" {
|
||||
violations = append(violations, FieldViolation{Field: "provider", Code: "min_length"})
|
||||
}
|
||||
if len(v.Provider) > 64 {
|
||||
violations = append(violations, FieldViolation{Field: "provider", Code: "max_length"})
|
||||
}
|
||||
if v.Roles == nil {
|
||||
violations = append(violations, FieldViolation{Field: "roles", Code: "required"})
|
||||
}
|
||||
if len(v.Roles) > 16 {
|
||||
violations = append(violations, FieldViolation{Field: "roles", Code: "max_items"})
|
||||
}
|
||||
for _, item := range v.Roles {
|
||||
if len(item) < 1 {
|
||||
violations = append(violations, FieldViolation{Field: "roles", Code: "min_item_length"})
|
||||
}
|
||||
}
|
||||
for _, item := range v.Roles {
|
||||
if len(item) > 64 {
|
||||
violations = append(violations, FieldViolation{Field: "roles", Code: "max_item_length"})
|
||||
}
|
||||
}
|
||||
for _, item := range v.Roles {
|
||||
if len(item) > 64 {
|
||||
violations = append(violations, FieldViolation{Field: "roles", Code: "max_item_bytes"})
|
||||
}
|
||||
}
|
||||
if v.Role == "" {
|
||||
violations = append(violations, FieldViolation{Field: "role", Code: "required"})
|
||||
}
|
||||
if v.Role != "" && !(v.Role == "user" || v.Role == "admin") {
|
||||
violations = append(violations, FieldViolation{Field: "role", Code: "invalid_value"})
|
||||
}
|
||||
if reflect.DeepEqual(v.NativeIdentity, NativeSessionIdentity{}) {
|
||||
violations = append(violations, FieldViolation{Field: "native_identity", Code: "required"})
|
||||
}
|
||||
if err := v.NativeIdentity.Validate(); err != nil {
|
||||
violations = append(violations, FieldViolation{Field: "native_identity", Code: "invalid_object"})
|
||||
}
|
||||
if len(violations) > 0 {
|
||||
return ValidationError{Violations: violations}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DecodeNativeAuthenticatedSession(data []byte) (NativeAuthenticatedSession, error) {
|
||||
var value NativeAuthenticatedSession
|
||||
if len(data) > 1024*1024 {
|
||||
return value, errors.New("protocol payload exceeds limit")
|
||||
}
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &fields); err != nil {
|
||||
return value, err
|
||||
}
|
||||
if raw, ok := fields["native_identity"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "native_identity", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["provider"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "provider", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["role"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "role", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["roles"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "roles", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["username"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "username", Code: "required"}}}
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&value); err != nil {
|
||||
return value, err
|
||||
}
|
||||
var trailing any
|
||||
if err := decoder.Decode(&trailing); err != io.EOF {
|
||||
if err == nil {
|
||||
return value, errors.New("trailing JSON value")
|
||||
}
|
||||
return value, err
|
||||
}
|
||||
if err := value.Validate(); err != nil {
|
||||
return value, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func EncodeNativeAuthenticatedSession(value NativeAuthenticatedSession) ([]byte, error) {
|
||||
if err := value.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
func (v NativeCredential) Validate() error {
|
||||
var violations []FieldViolation
|
||||
if len(v.DeviceID) > 128 {
|
||||
@@ -3526,6 +3800,176 @@ func EncodeNativeCredential(value NativeCredential) ([]byte, error) {
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
func (v NativeSessionIdentity) Validate() error {
|
||||
var violations []FieldViolation
|
||||
if v.ClientDeviceID == "" {
|
||||
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "required"})
|
||||
}
|
||||
if len(v.ClientDeviceID) < 1 && v.ClientDeviceID != "" {
|
||||
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "min_length"})
|
||||
}
|
||||
if len(v.ClientDeviceID) > 128 {
|
||||
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "max_length"})
|
||||
}
|
||||
if v.DeviceKeyID == "" {
|
||||
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "required"})
|
||||
}
|
||||
if len(v.DeviceKeyID) < 1 && v.DeviceKeyID != "" {
|
||||
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "min_length"})
|
||||
}
|
||||
if len(v.DeviceKeyID) > 128 {
|
||||
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "max_length"})
|
||||
}
|
||||
if len(violations) > 0 {
|
||||
return ValidationError{Violations: violations}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DecodeNativeSessionIdentity(data []byte) (NativeSessionIdentity, error) {
|
||||
var value NativeSessionIdentity
|
||||
if len(data) > 1024*1024 {
|
||||
return value, errors.New("protocol payload exceeds limit")
|
||||
}
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &fields); err != nil {
|
||||
return value, err
|
||||
}
|
||||
if raw, ok := fields["client_device_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "client_device_id", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["device_key_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "device_key_id", Code: "required"}}}
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&value); err != nil {
|
||||
return value, err
|
||||
}
|
||||
var trailing any
|
||||
if err := decoder.Decode(&trailing); err != io.EOF {
|
||||
if err == nil {
|
||||
return value, errors.New("trailing JSON value")
|
||||
}
|
||||
return value, err
|
||||
}
|
||||
if err := value.Validate(); err != nil {
|
||||
return value, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func EncodeNativeSessionIdentity(value NativeSessionIdentity) ([]byte, error) {
|
||||
if err := value.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
func (v NativeTunnelCredential) Validate() error {
|
||||
var violations []FieldViolation
|
||||
if v.ClientDeviceID == "" {
|
||||
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "required"})
|
||||
}
|
||||
if len(v.ClientDeviceID) < 1 && v.ClientDeviceID != "" {
|
||||
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "min_length"})
|
||||
}
|
||||
if len(v.ClientDeviceID) > 128 {
|
||||
violations = append(violations, FieldViolation{Field: "client_device_id", Code: "max_length"})
|
||||
}
|
||||
if v.DeviceKeyID == "" {
|
||||
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "required"})
|
||||
}
|
||||
if len(v.DeviceKeyID) < 1 && v.DeviceKeyID != "" {
|
||||
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "min_length"})
|
||||
}
|
||||
if len(v.DeviceKeyID) > 128 {
|
||||
violations = append(violations, FieldViolation{Field: "device_key_id", Code: "max_length"})
|
||||
}
|
||||
if v.CertificateChainPem == "" {
|
||||
violations = append(violations, FieldViolation{Field: "certificate_chain_pem", Code: "required"})
|
||||
}
|
||||
if len(v.CertificateChainPem) < 1 && v.CertificateChainPem != "" {
|
||||
violations = append(violations, FieldViolation{Field: "certificate_chain_pem", Code: "min_length"})
|
||||
}
|
||||
if len(v.CertificateChainPem) > 65536 {
|
||||
violations = append(violations, FieldViolation{Field: "certificate_chain_pem", Code: "max_length"})
|
||||
}
|
||||
if v.TrustBundlePem == "" {
|
||||
violations = append(violations, FieldViolation{Field: "trust_bundle_pem", Code: "required"})
|
||||
}
|
||||
if len(v.TrustBundlePem) < 1 && v.TrustBundlePem != "" {
|
||||
violations = append(violations, FieldViolation{Field: "trust_bundle_pem", Code: "min_length"})
|
||||
}
|
||||
if len(v.TrustBundlePem) > 65536 {
|
||||
violations = append(violations, FieldViolation{Field: "trust_bundle_pem", Code: "max_length"})
|
||||
}
|
||||
if v.ExpiresAt == "" {
|
||||
violations = append(violations, FieldViolation{Field: "expires_at", Code: "required"})
|
||||
}
|
||||
if len(v.ExpiresAt) > 64 {
|
||||
violations = append(violations, FieldViolation{Field: "expires_at", Code: "max_length"})
|
||||
}
|
||||
if v.ExpiresAt != "" {
|
||||
if parsed, err := time.Parse(time.RFC3339Nano, v.ExpiresAt); err != nil || parsed.UTC().Format(time.RFC3339Nano) != v.ExpiresAt {
|
||||
violations = append(violations, FieldViolation{Field: "expires_at", Code: "invalid_time"})
|
||||
}
|
||||
}
|
||||
if len(violations) > 0 {
|
||||
return ValidationError{Violations: violations}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DecodeNativeTunnelCredential(data []byte) (NativeTunnelCredential, error) {
|
||||
var value NativeTunnelCredential
|
||||
if len(data) > 1024*1024 {
|
||||
return value, errors.New("protocol payload exceeds limit")
|
||||
}
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &fields); err != nil {
|
||||
return value, err
|
||||
}
|
||||
if raw, ok := fields["certificate_chain_pem"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "certificate_chain_pem", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["client_device_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "client_device_id", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["device_key_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "device_key_id", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["expires_at"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "expires_at", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["trust_bundle_pem"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "trust_bundle_pem", Code: "required"}}}
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&value); err != nil {
|
||||
return value, err
|
||||
}
|
||||
var trailing any
|
||||
if err := decoder.Decode(&trailing); err != io.EOF {
|
||||
if err == nil {
|
||||
return value, errors.New("trailing JSON value")
|
||||
}
|
||||
return value, err
|
||||
}
|
||||
if err := value.Validate(); err != nil {
|
||||
return value, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func EncodeNativeTunnelCredential(value NativeTunnelCredential) ([]byte, error) {
|
||||
if err := value.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
func (v PageInfo) Validate() error {
|
||||
var violations []FieldViolation
|
||||
if v.Limit == 0 {
|
||||
@@ -3875,6 +4319,16 @@ func (v ProviderState) Validate() error {
|
||||
if len(v.Channels) > 8 {
|
||||
violations = append(violations, FieldViolation{Field: "channels", Code: "max_items"})
|
||||
}
|
||||
for _, item := range v.Channels {
|
||||
if len(item) < 1 {
|
||||
violations = append(violations, FieldViolation{Field: "channels", Code: "min_item_length"})
|
||||
}
|
||||
}
|
||||
for _, item := range v.Channels {
|
||||
if len(item) > 64 {
|
||||
violations = append(violations, FieldViolation{Field: "channels", Code: "max_item_length"})
|
||||
}
|
||||
}
|
||||
if len(violations) > 0 {
|
||||
return ValidationError{Violations: violations}
|
||||
}
|
||||
@@ -4761,12 +5215,6 @@ func (v SessionRequest) Validate() error {
|
||||
if len(v.IdempotencyKey) > 256 {
|
||||
violations = append(violations, FieldViolation{Field: "idempotency_key", Code: "max_length"})
|
||||
}
|
||||
if reflect.DeepEqual(v.PolicySnapshot, AllocationPolicy{}) {
|
||||
violations = append(violations, FieldViolation{Field: "policy_snapshot", Code: "required"})
|
||||
}
|
||||
if err := v.PolicySnapshot.Validate(); err != nil {
|
||||
violations = append(violations, FieldViolation{Field: "policy_snapshot", Code: "invalid_object"})
|
||||
}
|
||||
if v.RequestedDisplayMode != nil {
|
||||
if err := v.RequestedDisplayMode.Validate(); err != nil {
|
||||
violations = append(violations, FieldViolation{Field: "requested_display_mode", Code: "invalid_object"})
|
||||
@@ -4796,9 +5244,6 @@ func DecodeSessionRequest(data []byte) (SessionRequest, error) {
|
||||
if raw, ok := fields["idempotency_key"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "idempotency_key", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["policy_snapshot"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "policy_snapshot", Code: "required"}}}
|
||||
}
|
||||
if raw, ok := fields["pool_id"]; !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
||||
return value, ValidationError{Violations: []FieldViolation{{Field: "pool_id", Code: "required"}}}
|
||||
}
|
||||
@@ -5057,12 +5502,22 @@ func (v VersionNegotiation) Validate() error {
|
||||
if len(v.SupportedVersions) > 3 {
|
||||
violations = append(violations, FieldViolation{Field: "supported_versions", Code: "max_items"})
|
||||
}
|
||||
for _, item := range v.SupportedVersions {
|
||||
if len(item) > 16 {
|
||||
violations = append(violations, FieldViolation{Field: "supported_versions", Code: "max_item_length"})
|
||||
}
|
||||
}
|
||||
if v.Features == nil {
|
||||
violations = append(violations, FieldViolation{Field: "features", Code: "required"})
|
||||
}
|
||||
if len(v.Features) > 64 {
|
||||
violations = append(violations, FieldViolation{Field: "features", Code: "max_items"})
|
||||
}
|
||||
for _, item := range v.Features {
|
||||
if len(item) > 64 {
|
||||
violations = append(violations, FieldViolation{Field: "features", Code: "max_item_length"})
|
||||
}
|
||||
}
|
||||
if len(violations) > 0 {
|
||||
return ValidationError{Violations: violations}
|
||||
}
|
||||
|
||||
+7
-7
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"compatibility": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"current": "1",
|
||||
"current": "2",
|
||||
"datagram_registry": "registries/datagrams.json",
|
||||
"feature_registry": "registries/features.json",
|
||||
"n_minus_1": "0",
|
||||
"n_minus_2": "-1",
|
||||
"n_minus_1": "1",
|
||||
"n_minus_2": "0",
|
||||
"protocol": "versevdi-control",
|
||||
"unsupported": [
|
||||
"-2",
|
||||
"2"
|
||||
"-1",
|
||||
"3"
|
||||
]
|
||||
},
|
||||
"generator_sha256": "992235a56d3467313148f86e47931f247591e4c8de737b55ac9c9eee35725fc5",
|
||||
"generator_sha256": "8a153cf1e99682d010ff91c754ef056c64aece8f8bbca0ca58f8eef2b9039119",
|
||||
"protocol_version": "1.0.0",
|
||||
"schema_sha256": "b2bb0a8ac8ef56dbc0e1443eeb5b3028be9e71ec2f5fd8e73928d71b7cd9340c"
|
||||
"schema_sha256": "dea3dd210c53d5a2d37050dd6afd8b0ac5bb8edcb7ab25a02e4026489ce8a00f"
|
||||
}
|
||||
|
||||
+179
-9
@@ -1,9 +1,9 @@
|
||||
// Code generated by tools/generate.py; DO NOT EDIT.
|
||||
#![allow(non_snake_case)]
|
||||
pub const SCHEMA_SHA256: &str = "b2bb0a8ac8ef56dbc0e1443eeb5b3028be9e71ec2f5fd8e73928d71b7cd9340c";
|
||||
pub const CURRENT_WIRE_VERSION: &str = "1";
|
||||
pub const N_MINUS_1_WIRE_VERSION: &str = "0";
|
||||
pub const N_MINUS_2_WIRE_VERSION: &str = "-1";
|
||||
pub const SCHEMA_SHA256: &str = "dea3dd210c53d5a2d37050dd6afd8b0ac5bb8edcb7ab25a02e4026489ce8a00f";
|
||||
pub const CURRENT_WIRE_VERSION: &str = "2";
|
||||
pub const N_MINUS_1_WIRE_VERSION: &str = "1";
|
||||
pub const N_MINUS_2_WIRE_VERSION: &str = "0";
|
||||
pub type JsonObject = std::collections::BTreeMap<String, String>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -30,6 +30,19 @@ fn valid_base64_url(value: &str) -> bool {
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
fn valid_rfc3339_utc(value: &str) -> bool {
|
||||
let bytes = value.as_bytes();
|
||||
if bytes.len() < 20 || bytes.len() > 30 || bytes[4] != b'-' || bytes[7] != b'-' || bytes[10] != b'T' || bytes[13] != b':' || bytes[16] != b':' || *bytes.last().unwrap() != b'Z' { return false; }
|
||||
let digits = |start: usize, end: usize| -> Option<u32> { bytes.get(start..end)?.iter().try_fold(0u32, |value, byte| if byte.is_ascii_digit() { Some(value * 10 + u32::from(*byte - b'0')) } else { None }) };
|
||||
let (year, month, day, hour, minute, second) = match (digits(0, 4), digits(5, 7), digits(8, 10), digits(11, 13), digits(14, 16), digits(17, 19)) { (Some(year), Some(month), Some(day), Some(hour), Some(minute), Some(second)) => (year, month, day, hour, minute, second), _ => return false };
|
||||
if hour > 23 || minute > 59 || second > 59 { return false; }
|
||||
let leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
|
||||
let days = match month { 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, 4 | 6 | 9 | 11 => 30, 2 if leap => 29, 2 => 28, _ => return false };
|
||||
if day == 0 || day > days { return false; }
|
||||
if bytes.len() == 20 { return true; }
|
||||
let fraction = &bytes[20..bytes.len() - 1];
|
||||
bytes[19] == b'.' && !fraction.is_empty() && fraction.len() <= 9 && fraction.iter().all(u8::is_ascii_digit) && *fraction.last().unwrap() != b'0'
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AllocationPolicy {
|
||||
@@ -165,6 +178,7 @@ impl BrokerSession {
|
||||
self.policySnapshot.validate().map_err(|_| ValidationError::new("policy_snapshot", "invalid_object"))?;
|
||||
if let Some(value) = &self.reconnectDeadline {
|
||||
if value.len() > 64 { return Err(ValidationError::new("reconnect_deadline", "max_length")); }
|
||||
if !valid_rfc3339_utc(value.as_str()) { return Err(ValidationError::new("reconnect_deadline", "invalid_time")); }
|
||||
}
|
||||
if let Some(value) = &self.outcome {
|
||||
if value.len() > 64 { return Err(ValidationError::new("outcome", "max_length")); }
|
||||
@@ -182,8 +196,10 @@ impl BrokerSession {
|
||||
if !self.correlationId.is_empty() && self.correlationId.len() < 1 { return Err(ValidationError::new("correlation_id", "min_length")); }
|
||||
if self.correlationId.len() > 128 { return Err(ValidationError::new("correlation_id", "max_length")); }
|
||||
if self.requestedAt.len() > 64 { return Err(ValidationError::new("requested_at", "max_length")); }
|
||||
if !valid_rfc3339_utc(self.requestedAt.as_str()) { return Err(ValidationError::new("requested_at", "invalid_time")); }
|
||||
if let Some(value) = &self.endedAt {
|
||||
if value.len() > 64 { return Err(ValidationError::new("ended_at", "max_length")); }
|
||||
if !valid_rfc3339_utc(value.as_str()) { return Err(ValidationError::new("ended_at", "invalid_time")); }
|
||||
}
|
||||
if self.version < 1 { return Err(ValidationError::new("version", "minimum")); }
|
||||
if let Some(value) = &self.requestedDisplayMode {
|
||||
@@ -213,6 +229,40 @@ impl BrokerSession {
|
||||
pub fn effectiveDisplayMode(&self) -> &Option<DisplayMode> { &self.effectiveDisplayMode }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BrowserAuthenticatedSession {
|
||||
username: String,
|
||||
provider: String,
|
||||
roles: Vec<String>,
|
||||
role: String,
|
||||
}
|
||||
|
||||
impl BrowserAuthenticatedSession {
|
||||
pub fn new(username: String, provider: String, roles: Vec<String>, role: String) -> Result<Self, ValidationError> {
|
||||
let value = Self { username, provider, roles, role };
|
||||
value.validate()?;
|
||||
Ok(value)
|
||||
}
|
||||
pub fn validate(&self) -> Result<(), ValidationError> {
|
||||
if self.username.is_empty() { return Err(ValidationError::new("username", "required")); }
|
||||
if !self.username.is_empty() && self.username.len() < 1 { return Err(ValidationError::new("username", "min_length")); }
|
||||
if self.username.len() > 256 { return Err(ValidationError::new("username", "max_length")); }
|
||||
if self.provider.is_empty() { return Err(ValidationError::new("provider", "required")); }
|
||||
if !self.provider.is_empty() && self.provider.len() < 1 { return Err(ValidationError::new("provider", "min_length")); }
|
||||
if self.provider.len() > 64 { return Err(ValidationError::new("provider", "max_length")); }
|
||||
if self.roles.len() > 16 { return Err(ValidationError::new("roles", "max_items")); }
|
||||
for item in self.roles.iter() { if item.as_bytes().len() < 1 { return Err(ValidationError::new("roles", "min_item_length")); } }
|
||||
for item in self.roles.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("roles", "max_item_length")); } }
|
||||
for item in self.roles.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("roles", "max_item_bytes")); } }
|
||||
if self.role != "user" && self.role != "admin" { return Err(ValidationError::new("role", "invalid_value")); }
|
||||
Ok(())
|
||||
}
|
||||
pub fn username(&self) -> &String { &self.username }
|
||||
pub fn provider(&self) -> &String { &self.provider }
|
||||
pub fn roles(&self) -> &Vec<String> { &self.roles }
|
||||
pub fn role(&self) -> &String { &self.role }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CapabilityProfile {
|
||||
transport: String,
|
||||
@@ -428,6 +478,7 @@ impl DeviceChallenge {
|
||||
if !self.challenge.is_empty() && self.challenge.len() < 1 { return Err(ValidationError::new("challenge", "min_length")); }
|
||||
if self.challenge.len() > 256 { return Err(ValidationError::new("challenge", "max_length")); }
|
||||
if self.expiresAt.len() > 64 { return Err(ValidationError::new("expires_at", "max_length")); }
|
||||
if !valid_rfc3339_utc(self.expiresAt.as_str()) { return Err(ValidationError::new("expires_at", "invalid_time")); }
|
||||
if self.algorithm != "ed25519" { return Err(ValidationError::new("algorithm", "invalid_value")); }
|
||||
if self.signatureFormat != "ed25519-domain-separated-v1" { return Err(ValidationError::new("signature_format", "invalid_value")); }
|
||||
Ok(())
|
||||
@@ -636,6 +687,7 @@ impl EventEnvelope {
|
||||
if self.version < 1 { return Err(ValidationError::new("version", "minimum")); }
|
||||
self.resource.validate().map_err(|_| ValidationError::new("resource", "invalid_object"))?;
|
||||
if self.occurredAt.len() > 64 { return Err(ValidationError::new("occurred_at", "max_length")); }
|
||||
if !valid_rfc3339_utc(self.occurredAt.as_str()) { return Err(ValidationError::new("occurred_at", "invalid_time")); }
|
||||
if self.correlationId.is_empty() { return Err(ValidationError::new("correlation_id", "required")); }
|
||||
if !self.correlationId.is_empty() && self.correlationId.len() < 1 { return Err(ValidationError::new("correlation_id", "min_length")); }
|
||||
if self.correlationId.len() > 128 { return Err(ValidationError::new("correlation_id", "max_length")); }
|
||||
@@ -789,6 +841,7 @@ impl GatewayDrain {
|
||||
if !self.reason.is_empty() && self.reason.len() < 1 { return Err(ValidationError::new("reason", "min_length")); }
|
||||
if self.reason.len() > 256 { return Err(ValidationError::new("reason", "max_length")); }
|
||||
if self.deadline.len() > 64 { return Err(ValidationError::new("deadline", "max_length")); }
|
||||
if !valid_rfc3339_utc(self.deadline.as_str()) { return Err(ValidationError::new("deadline", "invalid_time")); }
|
||||
Ok(())
|
||||
}
|
||||
pub fn version(&self) -> &String { &self.version }
|
||||
@@ -823,6 +876,7 @@ impl GatewayHeartbeat {
|
||||
if self.gatewayId.len() > 128 { return Err(ValidationError::new("gateway_id", "max_length")); }
|
||||
if self.sequence < 1 { return Err(ValidationError::new("sequence", "minimum")); }
|
||||
if self.observedAt.len() > 64 { return Err(ValidationError::new("observed_at", "max_length")); }
|
||||
if !valid_rfc3339_utc(self.observedAt.as_str()) { return Err(ValidationError::new("observed_at", "invalid_time")); }
|
||||
if self.activeConnections < 0 { return Err(ValidationError::new("active_connections", "minimum")); }
|
||||
if self.activeConnections > 1000000 { return Err(ValidationError::new("active_connections", "maximum")); }
|
||||
if self.egressKbps < 0 { return Err(ValidationError::new("egress_kbps", "minimum")); }
|
||||
@@ -893,6 +947,8 @@ impl GatewayRegistration {
|
||||
if self.bandwidthCapacityKbps < 1 { return Err(ValidationError::new("bandwidth_capacity_kbps", "minimum")); }
|
||||
if self.bandwidthCapacityKbps > 1000000000 { return Err(ValidationError::new("bandwidth_capacity_kbps", "maximum")); }
|
||||
if self.features.len() > 64 { return Err(ValidationError::new("features", "max_items")); }
|
||||
for item in self.features.iter() { if item.as_bytes().len() < 1 { return Err(ValidationError::new("features", "min_item_length")); } }
|
||||
for item in self.features.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("features", "max_item_length")); } }
|
||||
self.capabilities.validate().map_err(|_| ValidationError::new("capabilities", "invalid_object"))?;
|
||||
if self.protocolMinVersion > self.protocolMaxVersion { return Err(ValidationError::new("protocol_version", "invalid_order")); }
|
||||
Ok(())
|
||||
@@ -1016,6 +1072,7 @@ impl GrantReference {
|
||||
if !self.opaqueValue.is_empty() && self.opaqueValue.len() < 43 { return Err(ValidationError::new("opaque_value", "min_length")); }
|
||||
if self.opaqueValue.len() > 256 { return Err(ValidationError::new("opaque_value", "max_length")); }
|
||||
if self.expiresAt.len() > 64 { return Err(ValidationError::new("expires_at", "max_length")); }
|
||||
if !valid_rfc3339_utc(self.expiresAt.as_str()) { return Err(ValidationError::new("expires_at", "invalid_time")); }
|
||||
if self.audience.is_empty() { return Err(ValidationError::new("audience", "required")); }
|
||||
if !self.audience.is_empty() && self.audience.len() < 1 { return Err(ValidationError::new("audience", "min_length")); }
|
||||
if self.audience.len() > 128 { return Err(ValidationError::new("audience", "max_length")); }
|
||||
@@ -1103,6 +1160,8 @@ impl ManifestGateway {
|
||||
if self.id.len() > 128 { return Err(ValidationError::new("id", "max_length")); }
|
||||
if self.addresses.len() < 1 { return Err(ValidationError::new("addresses", "min_items")); }
|
||||
if self.addresses.len() > 4 { return Err(ValidationError::new("addresses", "max_items")); }
|
||||
for item in self.addresses.iter() { if item.as_bytes().len() < 1 { return Err(ValidationError::new("addresses", "min_item_length")); } }
|
||||
for item in self.addresses.iter() { if item.as_bytes().len() > 256 { return Err(ValidationError::new("addresses", "max_item_length")); } }
|
||||
if self.publicIdentity.is_empty() { return Err(ValidationError::new("public_identity", "required")); }
|
||||
if !self.publicIdentity.is_empty() && self.publicIdentity.len() < 1 { return Err(ValidationError::new("public_identity", "min_length")); }
|
||||
if self.publicIdentity.len() > 256 { return Err(ValidationError::new("public_identity", "max_length")); }
|
||||
@@ -1156,13 +1215,54 @@ impl ManifestTunnel {
|
||||
pub fn validate(&self) -> Result<(), ValidationError> {
|
||||
if self.versions.len() < 1 { return Err(ValidationError::new("versions", "min_items")); }
|
||||
if self.versions.len() > 4 { return Err(ValidationError::new("versions", "max_items")); }
|
||||
for item in self.versions.iter() { if item.as_bytes().len() < 1 { return Err(ValidationError::new("versions", "min_item_length")); } }
|
||||
for item in self.versions.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("versions", "max_item_length")); } }
|
||||
if self.features.len() > 32 { return Err(ValidationError::new("features", "max_items")); }
|
||||
for item in self.features.iter() { if item.as_bytes().len() < 1 { return Err(ValidationError::new("features", "min_item_length")); } }
|
||||
for item in self.features.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("features", "max_item_length")); } }
|
||||
Ok(())
|
||||
}
|
||||
pub fn versions(&self) -> &Vec<String> { &self.versions }
|
||||
pub fn features(&self) -> &Vec<String> { &self.features }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct NativeAuthenticatedSession {
|
||||
username: String,
|
||||
provider: String,
|
||||
roles: Vec<String>,
|
||||
role: String,
|
||||
nativeIdentity: NativeSessionIdentity,
|
||||
}
|
||||
|
||||
impl NativeAuthenticatedSession {
|
||||
pub fn new(username: String, provider: String, roles: Vec<String>, role: String, nativeIdentity: NativeSessionIdentity) -> Result<Self, ValidationError> {
|
||||
let value = Self { username, provider, roles, role, nativeIdentity };
|
||||
value.validate()?;
|
||||
Ok(value)
|
||||
}
|
||||
pub fn validate(&self) -> Result<(), ValidationError> {
|
||||
if self.username.is_empty() { return Err(ValidationError::new("username", "required")); }
|
||||
if !self.username.is_empty() && self.username.len() < 1 { return Err(ValidationError::new("username", "min_length")); }
|
||||
if self.username.len() > 256 { return Err(ValidationError::new("username", "max_length")); }
|
||||
if self.provider.is_empty() { return Err(ValidationError::new("provider", "required")); }
|
||||
if !self.provider.is_empty() && self.provider.len() < 1 { return Err(ValidationError::new("provider", "min_length")); }
|
||||
if self.provider.len() > 64 { return Err(ValidationError::new("provider", "max_length")); }
|
||||
if self.roles.len() > 16 { return Err(ValidationError::new("roles", "max_items")); }
|
||||
for item in self.roles.iter() { if item.as_bytes().len() < 1 { return Err(ValidationError::new("roles", "min_item_length")); } }
|
||||
for item in self.roles.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("roles", "max_item_length")); } }
|
||||
for item in self.roles.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("roles", "max_item_bytes")); } }
|
||||
if self.role != "user" && self.role != "admin" { return Err(ValidationError::new("role", "invalid_value")); }
|
||||
self.nativeIdentity.validate().map_err(|_| ValidationError::new("native_identity", "invalid_object"))?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn username(&self) -> &String { &self.username }
|
||||
pub fn provider(&self) -> &String { &self.provider }
|
||||
pub fn roles(&self) -> &Vec<String> { &self.roles }
|
||||
pub fn role(&self) -> &String { &self.role }
|
||||
pub fn nativeIdentity(&self) -> &NativeSessionIdentity { &self.nativeIdentity }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct NativeCredential {
|
||||
deviceId: Option<String>,
|
||||
@@ -1193,8 +1293,10 @@ impl NativeCredential {
|
||||
if !self.refreshToken.is_empty() && self.refreshToken.len() < 1 { return Err(ValidationError::new("refresh_token", "min_length")); }
|
||||
if self.refreshToken.len() > 256 { return Err(ValidationError::new("refresh_token", "max_length")); }
|
||||
if self.expiresAt.len() > 64 { return Err(ValidationError::new("expires_at", "max_length")); }
|
||||
if !valid_rfc3339_utc(self.expiresAt.as_str()) { return Err(ValidationError::new("expires_at", "invalid_time")); }
|
||||
if let Some(value) = &self.refreshExpiresAt {
|
||||
if value.len() > 64 { return Err(ValidationError::new("refresh_expires_at", "max_length")); }
|
||||
if !valid_rfc3339_utc(value.as_str()) { return Err(ValidationError::new("refresh_expires_at", "invalid_time")); }
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1206,6 +1308,70 @@ impl NativeCredential {
|
||||
pub fn refreshExpiresAt(&self) -> &Option<String> { &self.refreshExpiresAt }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct NativeSessionIdentity {
|
||||
clientDeviceId: String,
|
||||
deviceKeyId: String,
|
||||
}
|
||||
|
||||
impl NativeSessionIdentity {
|
||||
pub fn new(clientDeviceId: String, deviceKeyId: String) -> Result<Self, ValidationError> {
|
||||
let value = Self { clientDeviceId, deviceKeyId };
|
||||
value.validate()?;
|
||||
Ok(value)
|
||||
}
|
||||
pub fn validate(&self) -> Result<(), ValidationError> {
|
||||
if self.clientDeviceId.is_empty() { return Err(ValidationError::new("client_device_id", "required")); }
|
||||
if !self.clientDeviceId.is_empty() && self.clientDeviceId.len() < 1 { return Err(ValidationError::new("client_device_id", "min_length")); }
|
||||
if self.clientDeviceId.len() > 128 { return Err(ValidationError::new("client_device_id", "max_length")); }
|
||||
if self.deviceKeyId.is_empty() { return Err(ValidationError::new("device_key_id", "required")); }
|
||||
if !self.deviceKeyId.is_empty() && self.deviceKeyId.len() < 1 { return Err(ValidationError::new("device_key_id", "min_length")); }
|
||||
if self.deviceKeyId.len() > 128 { return Err(ValidationError::new("device_key_id", "max_length")); }
|
||||
Ok(())
|
||||
}
|
||||
pub fn clientDeviceId(&self) -> &String { &self.clientDeviceId }
|
||||
pub fn deviceKeyId(&self) -> &String { &self.deviceKeyId }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct NativeTunnelCredential {
|
||||
clientDeviceId: String,
|
||||
deviceKeyId: String,
|
||||
certificateChainPem: String,
|
||||
trustBundlePem: String,
|
||||
expiresAt: String,
|
||||
}
|
||||
|
||||
impl NativeTunnelCredential {
|
||||
pub fn new(clientDeviceId: String, deviceKeyId: String, certificateChainPem: String, trustBundlePem: String, expiresAt: String) -> Result<Self, ValidationError> {
|
||||
let value = Self { clientDeviceId, deviceKeyId, certificateChainPem, trustBundlePem, expiresAt };
|
||||
value.validate()?;
|
||||
Ok(value)
|
||||
}
|
||||
pub fn validate(&self) -> Result<(), ValidationError> {
|
||||
if self.clientDeviceId.is_empty() { return Err(ValidationError::new("client_device_id", "required")); }
|
||||
if !self.clientDeviceId.is_empty() && self.clientDeviceId.len() < 1 { return Err(ValidationError::new("client_device_id", "min_length")); }
|
||||
if self.clientDeviceId.len() > 128 { return Err(ValidationError::new("client_device_id", "max_length")); }
|
||||
if self.deviceKeyId.is_empty() { return Err(ValidationError::new("device_key_id", "required")); }
|
||||
if !self.deviceKeyId.is_empty() && self.deviceKeyId.len() < 1 { return Err(ValidationError::new("device_key_id", "min_length")); }
|
||||
if self.deviceKeyId.len() > 128 { return Err(ValidationError::new("device_key_id", "max_length")); }
|
||||
if self.certificateChainPem.is_empty() { return Err(ValidationError::new("certificate_chain_pem", "required")); }
|
||||
if !self.certificateChainPem.is_empty() && self.certificateChainPem.len() < 1 { return Err(ValidationError::new("certificate_chain_pem", "min_length")); }
|
||||
if self.certificateChainPem.len() > 65536 { return Err(ValidationError::new("certificate_chain_pem", "max_length")); }
|
||||
if self.trustBundlePem.is_empty() { return Err(ValidationError::new("trust_bundle_pem", "required")); }
|
||||
if !self.trustBundlePem.is_empty() && self.trustBundlePem.len() < 1 { return Err(ValidationError::new("trust_bundle_pem", "min_length")); }
|
||||
if self.trustBundlePem.len() > 65536 { return Err(ValidationError::new("trust_bundle_pem", "max_length")); }
|
||||
if self.expiresAt.len() > 64 { return Err(ValidationError::new("expires_at", "max_length")); }
|
||||
if !valid_rfc3339_utc(self.expiresAt.as_str()) { return Err(ValidationError::new("expires_at", "invalid_time")); }
|
||||
Ok(())
|
||||
}
|
||||
pub fn clientDeviceId(&self) -> &String { &self.clientDeviceId }
|
||||
pub fn deviceKeyId(&self) -> &String { &self.deviceKeyId }
|
||||
pub fn certificateChainPem(&self) -> &String { &self.certificateChainPem }
|
||||
pub fn trustBundlePem(&self) -> &String { &self.trustBundlePem }
|
||||
pub fn expiresAt(&self) -> &String { &self.expiresAt }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PageInfo {
|
||||
limit: i64,
|
||||
@@ -1268,6 +1434,7 @@ impl ProviderSessionWork {
|
||||
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.expiresAt.len() > 64 { return Err(ValidationError::new("expires_at", "max_length")); }
|
||||
if !valid_rfc3339_utc(self.expiresAt.as_str()) { return Err(ValidationError::new("expires_at", "invalid_time")); }
|
||||
if self.providerProfile != "apollo" { return Err(ValidationError::new("provider_profile", "invalid_value")); }
|
||||
if self.providerIdentity.is_empty() { return Err(ValidationError::new("provider_identity", "required")); }
|
||||
if !self.providerIdentity.is_empty() && self.providerIdentity.len() < 1 { return Err(ValidationError::new("provider_identity", "min_length")); }
|
||||
@@ -1348,6 +1515,8 @@ impl ProviderState {
|
||||
if self.sessionId.len() > 128 { return Err(ValidationError::new("session_id", "max_length")); }
|
||||
if self.state != "starting" && self.state != "ready" && self.state != "disconnected" && self.state != "terminating" && self.state != "terminated" && self.state != "cleanup_pending" && self.state != "failed" { return Err(ValidationError::new("state", "invalid_value")); }
|
||||
if self.channels.len() > 8 { return Err(ValidationError::new("channels", "max_items")); }
|
||||
for item in self.channels.iter() { if item.as_bytes().len() < 1 { return Err(ValidationError::new("channels", "min_item_length")); } }
|
||||
for item in self.channels.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("channels", "max_item_length")); } }
|
||||
Ok(())
|
||||
}
|
||||
pub fn version(&self) -> &String { &self.version }
|
||||
@@ -1414,6 +1583,7 @@ impl ReauthGrant {
|
||||
if !self.purpose.is_empty() && self.purpose.len() < 1 { return Err(ValidationError::new("purpose", "min_length")); }
|
||||
if self.purpose.len() > 64 { return Err(ValidationError::new("purpose", "max_length")); }
|
||||
if self.expiresAt.len() > 64 { return Err(ValidationError::new("expires_at", "max_length")); }
|
||||
if !valid_rfc3339_utc(self.expiresAt.as_str()) { return Err(ValidationError::new("expires_at", "invalid_time")); }
|
||||
Ok(())
|
||||
}
|
||||
pub fn token(&self) -> &String { &self.token }
|
||||
@@ -1630,6 +1800,7 @@ impl SessionAuthority {
|
||||
if self.audience.len() > 256 { return Err(ValidationError::new("audience", "max_length")); }
|
||||
if self.reconnectSequence < 0 { return Err(ValidationError::new("reconnect_sequence", "minimum")); }
|
||||
if self.expiresAt.len() > 64 { return Err(ValidationError::new("expires_at", "max_length")); }
|
||||
if !valid_rfc3339_utc(self.expiresAt.as_str()) { return Err(ValidationError::new("expires_at", "invalid_time")); }
|
||||
self.capabilities.validate().map_err(|_| ValidationError::new("capabilities", "invalid_object"))?;
|
||||
if self.providerProfile != "apollo" { return Err(ValidationError::new("provider_profile", "invalid_value")); }
|
||||
if self.providerIdentity.is_empty() { return Err(ValidationError::new("provider_identity", "required")); }
|
||||
@@ -1654,13 +1825,12 @@ pub struct SessionRequest {
|
||||
deviceKeyId: String,
|
||||
poolId: String,
|
||||
idempotencyKey: String,
|
||||
policySnapshot: AllocationPolicy,
|
||||
requestedDisplayMode: Option<DisplayMode>,
|
||||
}
|
||||
|
||||
impl SessionRequest {
|
||||
pub fn new(clientDeviceId: String, deviceKeyId: String, poolId: String, idempotencyKey: String, policySnapshot: AllocationPolicy, requestedDisplayMode: Option<DisplayMode>) -> Result<Self, ValidationError> {
|
||||
let value = Self { clientDeviceId, deviceKeyId, poolId, idempotencyKey, policySnapshot, requestedDisplayMode };
|
||||
pub fn new(clientDeviceId: String, deviceKeyId: String, poolId: String, idempotencyKey: String, requestedDisplayMode: Option<DisplayMode>) -> Result<Self, ValidationError> {
|
||||
let value = Self { clientDeviceId, deviceKeyId, poolId, idempotencyKey, requestedDisplayMode };
|
||||
value.validate()?;
|
||||
Ok(value)
|
||||
}
|
||||
@@ -1677,7 +1847,6 @@ impl SessionRequest {
|
||||
if self.idempotencyKey.is_empty() { return Err(ValidationError::new("idempotency_key", "required")); }
|
||||
if !self.idempotencyKey.is_empty() && self.idempotencyKey.len() < 1 { return Err(ValidationError::new("idempotency_key", "min_length")); }
|
||||
if self.idempotencyKey.len() > 256 { return Err(ValidationError::new("idempotency_key", "max_length")); }
|
||||
self.policySnapshot.validate().map_err(|_| ValidationError::new("policy_snapshot", "invalid_object"))?;
|
||||
if let Some(value) = &self.requestedDisplayMode {
|
||||
value.validate().map_err(|_| ValidationError::new("requested_display_mode", "invalid_object"))?;
|
||||
}
|
||||
@@ -1687,7 +1856,6 @@ impl SessionRequest {
|
||||
pub fn deviceKeyId(&self) -> &String { &self.deviceKeyId }
|
||||
pub fn poolId(&self) -> &String { &self.poolId }
|
||||
pub fn idempotencyKey(&self) -> &String { &self.idempotencyKey }
|
||||
pub fn policySnapshot(&self) -> &AllocationPolicy { &self.policySnapshot }
|
||||
pub fn requestedDisplayMode(&self) -> &Option<DisplayMode> { &self.requestedDisplayMode }
|
||||
}
|
||||
|
||||
@@ -1799,7 +1967,9 @@ impl VersionNegotiation {
|
||||
pub fn validate(&self) -> Result<(), ValidationError> {
|
||||
if self.supportedVersions.len() < 1 { return Err(ValidationError::new("supported_versions", "min_items")); }
|
||||
if self.supportedVersions.len() > 3 { return Err(ValidationError::new("supported_versions", "max_items")); }
|
||||
for item in self.supportedVersions.iter() { if item.as_bytes().len() > 16 { return Err(ValidationError::new("supported_versions", "max_item_length")); } }
|
||||
if self.features.len() > 64 { return Err(ValidationError::new("features", "max_items")); }
|
||||
for item in self.features.iter() { if item.as_bytes().len() > 64 { return Err(ValidationError::new("features", "max_item_length")); } }
|
||||
Ok(())
|
||||
}
|
||||
pub fn supportedVersions(&self) -> &Vec<String> { &self.supportedVersions }
|
||||
|
||||
+227
-23
@@ -1,10 +1,10 @@
|
||||
// Code generated by tools/generate.py; DO NOT EDIT.
|
||||
import Foundation
|
||||
public typealias JSONObject = [String: String]
|
||||
public let schemaSHA256 = "b2bb0a8ac8ef56dbc0e1443eeb5b3028be9e71ec2f5fd8e73928d71b7cd9340c"
|
||||
public let currentWireVersion = "1"
|
||||
public let nMinus1WireVersion = "0"
|
||||
public let nMinus2WireVersion = "-1"
|
||||
public let schemaSHA256 = "dea3dd210c53d5a2d37050dd6afd8b0ac5bb8edcb7ab25a02e4026489ce8a00f"
|
||||
public let currentWireVersion = "2"
|
||||
public let nMinus1WireVersion = "1"
|
||||
public let nMinus2WireVersion = "0"
|
||||
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 func validBase64URL(_ value: String) -> Bool {
|
||||
@@ -16,6 +16,23 @@ private func validBase64URL(_ value: String) -> Bool {
|
||||
guard let decoded = Data(base64Encoded: standard) else { return false }
|
||||
return decoded.base64EncodedString().replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "") == value
|
||||
}
|
||||
private func validRFC3339UTC(_ value: String) -> Bool {
|
||||
let bytes = Array(value.utf8)
|
||||
guard (20...30).contains(bytes.count), bytes[4] == 45, bytes[7] == 45, bytes[10] == 84, bytes[13] == 58, bytes[16] == 58, bytes.last == 90 else { return false }
|
||||
func digits(_ range: Range<Int>) -> Int? {
|
||||
var result = 0
|
||||
for index in range { guard bytes[index] >= 48 && bytes[index] <= 57 else { return nil }; result = result * 10 + Int(bytes[index] - 48) }
|
||||
return result
|
||||
}
|
||||
guard let year = digits(0..<4), let month = digits(5..<7), let day = digits(8..<10), let hour = digits(11..<13), let minute = digits(14..<16), let second = digits(17..<19), hour <= 23, minute <= 59, second <= 59 else { return false }
|
||||
let leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
|
||||
let days: Int
|
||||
switch month { case 1, 3, 5, 7, 8, 10, 12: days = 31; case 4, 6, 9, 11: days = 30; case 2: days = leap ? 29 : 28; default: return false }
|
||||
guard day > 0 && day <= days else { return false }
|
||||
if bytes.count == 20 { return true }
|
||||
let fraction = bytes[20..<(bytes.count - 1)]
|
||||
return bytes[19] == 46 && !fraction.isEmpty && fraction.count <= 9 && fraction.allSatisfy { $0 >= 48 && $0 <= 57 } && fraction.last != 48
|
||||
}
|
||||
|
||||
public struct AllocationPolicy: Codable, Equatable {
|
||||
public let minimumKbps: Int64
|
||||
@@ -217,7 +234,7 @@ public struct BrokerSession: Codable, Equatable {
|
||||
try self.policySnapshot.validate()
|
||||
if let value = self.reconnectDeadline {
|
||||
if value.utf8.count > 64 { throw ContractValidationError(field: "reconnect_deadline", code: "max_length") }
|
||||
if ISO8601DateFormatter().date(from: value) == nil { throw ContractValidationError(field: "reconnect_deadline", code: "invalid_time") }
|
||||
if !validRFC3339UTC(value) { throw ContractValidationError(field: "reconnect_deadline", code: "invalid_time") }
|
||||
}
|
||||
if let value = self.outcome {
|
||||
if value.utf8.count > 64 { throw ContractValidationError(field: "outcome", code: "max_length") }
|
||||
@@ -235,10 +252,10 @@ public struct BrokerSession: Codable, Equatable {
|
||||
if !self.correlationId.isEmpty && self.correlationId.utf8.count < 1 { throw ContractValidationError(field: "correlation_id", code: "min_length") }
|
||||
if self.correlationId.utf8.count > 128 { throw ContractValidationError(field: "correlation_id", code: "max_length") }
|
||||
if self.requestedAt.utf8.count > 64 { throw ContractValidationError(field: "requested_at", code: "max_length") }
|
||||
if ISO8601DateFormatter().date(from: self.requestedAt) == nil { throw ContractValidationError(field: "requested_at", code: "invalid_time") }
|
||||
if !validRFC3339UTC(self.requestedAt) { throw ContractValidationError(field: "requested_at", code: "invalid_time") }
|
||||
if let value = self.endedAt {
|
||||
if value.utf8.count > 64 { throw ContractValidationError(field: "ended_at", code: "max_length") }
|
||||
if ISO8601DateFormatter().date(from: value) == nil { throw ContractValidationError(field: "ended_at", code: "invalid_time") }
|
||||
if !validRFC3339UTC(value) { throw ContractValidationError(field: "ended_at", code: "invalid_time") }
|
||||
}
|
||||
if self.version < 1 { throw ContractValidationError(field: "version", code: "minimum") }
|
||||
if let value = self.requestedDisplayMode {
|
||||
@@ -253,6 +270,51 @@ public struct BrokerSession: Codable, Equatable {
|
||||
public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }
|
||||
}
|
||||
|
||||
public struct BrowserAuthenticatedSession: Codable, Equatable {
|
||||
public let username: String
|
||||
public let provider: String
|
||||
public let roles: [String]
|
||||
public let role: String
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case username = "username"
|
||||
case provider = "provider"
|
||||
case roles = "roles"
|
||||
case role = "role"
|
||||
}
|
||||
|
||||
public init(username: String, provider: String, roles: [String], role: String) throws {
|
||||
self.username = username
|
||||
self.provider = provider
|
||||
self.roles = roles
|
||||
self.role = role
|
||||
try validate()
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
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") }
|
||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||
try self.init(username: try c.decode(String.self, forKey: .username), provider: try c.decode(String.self, forKey: .provider), roles: try c.decode([String].self, forKey: .roles), role: try c.decode(String.self, forKey: .role))
|
||||
}
|
||||
|
||||
public func validate() throws {
|
||||
if self.username.isEmpty { throw ContractValidationError(field: "username", code: "required") }
|
||||
if !self.username.isEmpty && self.username.utf8.count < 1 { throw ContractValidationError(field: "username", code: "min_length") }
|
||||
if self.username.utf8.count > 256 { throw ContractValidationError(field: "username", code: "max_length") }
|
||||
if self.provider.isEmpty { throw ContractValidationError(field: "provider", code: "required") }
|
||||
if !self.provider.isEmpty && self.provider.utf8.count < 1 { throw ContractValidationError(field: "provider", code: "min_length") }
|
||||
if self.provider.utf8.count > 64 { throw ContractValidationError(field: "provider", code: "max_length") }
|
||||
if self.roles.count > 16 { throw ContractValidationError(field: "roles", code: "max_items") }
|
||||
for item in self.roles where item.utf8.count < 1 { throw ContractValidationError(field: "roles", code: "min_item_length") }
|
||||
for item in self.roles where item.utf8.count > 64 { throw ContractValidationError(field: "roles", code: "max_item_length") }
|
||||
for item in self.roles where item.utf8.count > 64 { throw ContractValidationError(field: "roles", code: "max_item_bytes") }
|
||||
if !["user", "admin"].contains(self.role) { throw ContractValidationError(field: "role", code: "invalid_value") }
|
||||
}
|
||||
|
||||
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
|
||||
public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }
|
||||
}
|
||||
|
||||
public struct CapabilityProfile: Codable, Equatable {
|
||||
public let transport: String
|
||||
public let framing: String
|
||||
@@ -551,7 +613,7 @@ public struct DeviceChallenge: Codable, Equatable {
|
||||
if !self.challenge.isEmpty && self.challenge.utf8.count < 1 { throw ContractValidationError(field: "challenge", code: "min_length") }
|
||||
if self.challenge.utf8.count > 256 { throw ContractValidationError(field: "challenge", code: "max_length") }
|
||||
if self.expiresAt.utf8.count > 64 { throw ContractValidationError(field: "expires_at", code: "max_length") }
|
||||
if ISO8601DateFormatter().date(from: self.expiresAt) == nil { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
|
||||
if !validRFC3339UTC(self.expiresAt) { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
|
||||
if self.algorithm != "ed25519" { throw ContractValidationError(field: "algorithm", code: "invalid_value") }
|
||||
if self.signatureFormat != "ed25519-domain-separated-v1" { throw ContractValidationError(field: "signature_format", code: "invalid_value") }
|
||||
}
|
||||
@@ -831,7 +893,7 @@ public struct EventEnvelope: Codable, Equatable {
|
||||
if self.version < 1 { throw ContractValidationError(field: "version", code: "minimum") }
|
||||
try self.resource.validate()
|
||||
if self.occurredAt.utf8.count > 64 { throw ContractValidationError(field: "occurred_at", code: "max_length") }
|
||||
if ISO8601DateFormatter().date(from: self.occurredAt) == nil { throw ContractValidationError(field: "occurred_at", code: "invalid_time") }
|
||||
if !validRFC3339UTC(self.occurredAt) { throw ContractValidationError(field: "occurred_at", code: "invalid_time") }
|
||||
if self.correlationId.isEmpty { throw ContractValidationError(field: "correlation_id", code: "required") }
|
||||
if !self.correlationId.isEmpty && self.correlationId.utf8.count < 1 { throw ContractValidationError(field: "correlation_id", code: "min_length") }
|
||||
if self.correlationId.utf8.count > 128 { throw ContractValidationError(field: "correlation_id", code: "max_length") }
|
||||
@@ -1036,7 +1098,7 @@ public struct GatewayDrain: Codable, Equatable {
|
||||
if !self.reason.isEmpty && self.reason.utf8.count < 1 { throw ContractValidationError(field: "reason", code: "min_length") }
|
||||
if self.reason.utf8.count > 256 { throw ContractValidationError(field: "reason", code: "max_length") }
|
||||
if self.deadline.utf8.count > 64 { throw ContractValidationError(field: "deadline", code: "max_length") }
|
||||
if ISO8601DateFormatter().date(from: self.deadline) == nil { throw ContractValidationError(field: "deadline", code: "invalid_time") }
|
||||
if !validRFC3339UTC(self.deadline) { throw ContractValidationError(field: "deadline", code: "invalid_time") }
|
||||
}
|
||||
|
||||
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
|
||||
@@ -1089,7 +1151,7 @@ public struct GatewayHeartbeat: Codable, Equatable {
|
||||
if self.gatewayId.utf8.count > 128 { throw ContractValidationError(field: "gateway_id", code: "max_length") }
|
||||
if self.sequence < 1 { throw ContractValidationError(field: "sequence", code: "minimum") }
|
||||
if self.observedAt.utf8.count > 64 { throw ContractValidationError(field: "observed_at", code: "max_length") }
|
||||
if ISO8601DateFormatter().date(from: self.observedAt) == nil { throw ContractValidationError(field: "observed_at", code: "invalid_time") }
|
||||
if !validRFC3339UTC(self.observedAt) { throw ContractValidationError(field: "observed_at", code: "invalid_time") }
|
||||
if self.activeConnections < 0 { throw ContractValidationError(field: "active_connections", code: "minimum") }
|
||||
if self.activeConnections > 1000000 { throw ContractValidationError(field: "active_connections", code: "maximum") }
|
||||
if self.egressKbps < 0 { throw ContractValidationError(field: "egress_kbps", code: "minimum") }
|
||||
@@ -1185,6 +1247,8 @@ public struct GatewayRegistration: Codable, Equatable {
|
||||
if self.bandwidthCapacityKbps < 1 { throw ContractValidationError(field: "bandwidth_capacity_kbps", code: "minimum") }
|
||||
if self.bandwidthCapacityKbps > 1000000000 { throw ContractValidationError(field: "bandwidth_capacity_kbps", code: "maximum") }
|
||||
if self.features.count > 64 { throw ContractValidationError(field: "features", code: "max_items") }
|
||||
for item in self.features where item.utf8.count < 1 { throw ContractValidationError(field: "features", code: "min_item_length") }
|
||||
for item in self.features where item.utf8.count > 64 { throw ContractValidationError(field: "features", code: "max_item_length") }
|
||||
try self.capabilities.validate()
|
||||
if protocolMinVersion > protocolMaxVersion { throw ContractValidationError(field: "protocol_version", code: "invalid_order") }
|
||||
}
|
||||
@@ -1333,7 +1397,7 @@ public struct GrantReference: Codable, Equatable {
|
||||
if !self.opaqueValue.isEmpty && self.opaqueValue.utf8.count < 43 { throw ContractValidationError(field: "opaque_value", code: "min_length") }
|
||||
if self.opaqueValue.utf8.count > 256 { throw ContractValidationError(field: "opaque_value", code: "max_length") }
|
||||
if self.expiresAt.utf8.count > 64 { throw ContractValidationError(field: "expires_at", code: "max_length") }
|
||||
if ISO8601DateFormatter().date(from: self.expiresAt) == nil { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
|
||||
if !validRFC3339UTC(self.expiresAt) { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
|
||||
if self.audience.isEmpty { throw ContractValidationError(field: "audience", code: "required") }
|
||||
if !self.audience.isEmpty && self.audience.utf8.count < 1 { throw ContractValidationError(field: "audience", code: "min_length") }
|
||||
if self.audience.utf8.count > 128 { throw ContractValidationError(field: "audience", code: "max_length") }
|
||||
@@ -1451,6 +1515,8 @@ public struct ManifestGateway: Codable, Equatable {
|
||||
if self.id.utf8.count > 128 { throw ContractValidationError(field: "id", code: "max_length") }
|
||||
if self.addresses.count < 1 { throw ContractValidationError(field: "addresses", code: "min_items") }
|
||||
if self.addresses.count > 4 { throw ContractValidationError(field: "addresses", code: "max_items") }
|
||||
for item in self.addresses where item.utf8.count < 1 { throw ContractValidationError(field: "addresses", code: "min_item_length") }
|
||||
for item in self.addresses where item.utf8.count > 256 { throw ContractValidationError(field: "addresses", code: "max_item_length") }
|
||||
if self.publicIdentity.isEmpty { throw ContractValidationError(field: "public_identity", code: "required") }
|
||||
if !self.publicIdentity.isEmpty && self.publicIdentity.utf8.count < 1 { throw ContractValidationError(field: "public_identity", code: "min_length") }
|
||||
if self.publicIdentity.utf8.count > 256 { throw ContractValidationError(field: "public_identity", code: "max_length") }
|
||||
@@ -1522,7 +1588,60 @@ public struct ManifestTunnel: Codable, Equatable {
|
||||
public func validate() throws {
|
||||
if self.versions.count < 1 { throw ContractValidationError(field: "versions", code: "min_items") }
|
||||
if self.versions.count > 4 { throw ContractValidationError(field: "versions", code: "max_items") }
|
||||
for item in self.versions where item.utf8.count < 1 { throw ContractValidationError(field: "versions", code: "min_item_length") }
|
||||
for item in self.versions where item.utf8.count > 64 { throw ContractValidationError(field: "versions", code: "max_item_length") }
|
||||
if self.features.count > 32 { throw ContractValidationError(field: "features", code: "max_items") }
|
||||
for item in self.features where item.utf8.count < 1 { throw ContractValidationError(field: "features", code: "min_item_length") }
|
||||
for item in self.features where item.utf8.count > 64 { throw ContractValidationError(field: "features", code: "max_item_length") }
|
||||
}
|
||||
|
||||
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
|
||||
public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }
|
||||
}
|
||||
|
||||
public struct NativeAuthenticatedSession: Codable, Equatable {
|
||||
public let username: String
|
||||
public let provider: String
|
||||
public let roles: [String]
|
||||
public let role: String
|
||||
public let nativeIdentity: NativeSessionIdentity
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case username = "username"
|
||||
case provider = "provider"
|
||||
case roles = "roles"
|
||||
case role = "role"
|
||||
case nativeIdentity = "native_identity"
|
||||
}
|
||||
|
||||
public init(username: String, provider: String, roles: [String], role: String, nativeIdentity: NativeSessionIdentity) throws {
|
||||
self.username = username
|
||||
self.provider = provider
|
||||
self.roles = roles
|
||||
self.role = role
|
||||
self.nativeIdentity = nativeIdentity
|
||||
try validate()
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
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") }
|
||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||
try self.init(username: try c.decode(String.self, forKey: .username), provider: try c.decode(String.self, forKey: .provider), roles: try c.decode([String].self, forKey: .roles), role: try c.decode(String.self, forKey: .role), nativeIdentity: try c.decode(NativeSessionIdentity.self, forKey: .nativeIdentity))
|
||||
}
|
||||
|
||||
public func validate() throws {
|
||||
if self.username.isEmpty { throw ContractValidationError(field: "username", code: "required") }
|
||||
if !self.username.isEmpty && self.username.utf8.count < 1 { throw ContractValidationError(field: "username", code: "min_length") }
|
||||
if self.username.utf8.count > 256 { throw ContractValidationError(field: "username", code: "max_length") }
|
||||
if self.provider.isEmpty { throw ContractValidationError(field: "provider", code: "required") }
|
||||
if !self.provider.isEmpty && self.provider.utf8.count < 1 { throw ContractValidationError(field: "provider", code: "min_length") }
|
||||
if self.provider.utf8.count > 64 { throw ContractValidationError(field: "provider", code: "max_length") }
|
||||
if self.roles.count > 16 { throw ContractValidationError(field: "roles", code: "max_items") }
|
||||
for item in self.roles where item.utf8.count < 1 { throw ContractValidationError(field: "roles", code: "min_item_length") }
|
||||
for item in self.roles where item.utf8.count > 64 { throw ContractValidationError(field: "roles", code: "max_item_length") }
|
||||
for item in self.roles where item.utf8.count > 64 { throw ContractValidationError(field: "roles", code: "max_item_bytes") }
|
||||
if !["user", "admin"].contains(self.role) { throw ContractValidationError(field: "role", code: "invalid_value") }
|
||||
try self.nativeIdentity.validate()
|
||||
}
|
||||
|
||||
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
|
||||
@@ -1576,10 +1695,10 @@ public struct NativeCredential: Codable, Equatable {
|
||||
if !self.refreshToken.isEmpty && self.refreshToken.utf8.count < 1 { throw ContractValidationError(field: "refresh_token", code: "min_length") }
|
||||
if self.refreshToken.utf8.count > 256 { throw ContractValidationError(field: "refresh_token", code: "max_length") }
|
||||
if self.expiresAt.utf8.count > 64 { throw ContractValidationError(field: "expires_at", code: "max_length") }
|
||||
if ISO8601DateFormatter().date(from: self.expiresAt) == nil { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
|
||||
if !validRFC3339UTC(self.expiresAt) { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
|
||||
if let value = self.refreshExpiresAt {
|
||||
if value.utf8.count > 64 { throw ContractValidationError(field: "refresh_expires_at", code: "max_length") }
|
||||
if ISO8601DateFormatter().date(from: value) == nil { throw ContractValidationError(field: "refresh_expires_at", code: "invalid_time") }
|
||||
if !validRFC3339UTC(value) { throw ContractValidationError(field: "refresh_expires_at", code: "invalid_time") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1587,6 +1706,91 @@ public struct NativeCredential: Codable, Equatable {
|
||||
public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }
|
||||
}
|
||||
|
||||
public struct NativeSessionIdentity: Codable, Equatable {
|
||||
public let clientDeviceId: String
|
||||
public let deviceKeyId: String
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case clientDeviceId = "client_device_id"
|
||||
case deviceKeyId = "device_key_id"
|
||||
}
|
||||
|
||||
public init(clientDeviceId: String, deviceKeyId: String) throws {
|
||||
self.clientDeviceId = clientDeviceId
|
||||
self.deviceKeyId = deviceKeyId
|
||||
try validate()
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
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") }
|
||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||
try self.init(clientDeviceId: try c.decode(String.self, forKey: .clientDeviceId), deviceKeyId: try c.decode(String.self, forKey: .deviceKeyId))
|
||||
}
|
||||
|
||||
public func validate() throws {
|
||||
if self.clientDeviceId.isEmpty { throw ContractValidationError(field: "client_device_id", code: "required") }
|
||||
if !self.clientDeviceId.isEmpty && self.clientDeviceId.utf8.count < 1 { throw ContractValidationError(field: "client_device_id", code: "min_length") }
|
||||
if self.clientDeviceId.utf8.count > 128 { throw ContractValidationError(field: "client_device_id", code: "max_length") }
|
||||
if self.deviceKeyId.isEmpty { throw ContractValidationError(field: "device_key_id", code: "required") }
|
||||
if !self.deviceKeyId.isEmpty && self.deviceKeyId.utf8.count < 1 { throw ContractValidationError(field: "device_key_id", code: "min_length") }
|
||||
if self.deviceKeyId.utf8.count > 128 { throw ContractValidationError(field: "device_key_id", code: "max_length") }
|
||||
}
|
||||
|
||||
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
|
||||
public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }
|
||||
}
|
||||
|
||||
public struct NativeTunnelCredential: Codable, Equatable {
|
||||
public let clientDeviceId: String
|
||||
public let deviceKeyId: String
|
||||
public let certificateChainPem: String
|
||||
public let trustBundlePem: String
|
||||
public let expiresAt: String
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case clientDeviceId = "client_device_id"
|
||||
case deviceKeyId = "device_key_id"
|
||||
case certificateChainPem = "certificate_chain_pem"
|
||||
case trustBundlePem = "trust_bundle_pem"
|
||||
case expiresAt = "expires_at"
|
||||
}
|
||||
|
||||
public init(clientDeviceId: String, deviceKeyId: String, certificateChainPem: String, trustBundlePem: String, expiresAt: String) throws {
|
||||
self.clientDeviceId = clientDeviceId
|
||||
self.deviceKeyId = deviceKeyId
|
||||
self.certificateChainPem = certificateChainPem
|
||||
self.trustBundlePem = trustBundlePem
|
||||
self.expiresAt = expiresAt
|
||||
try validate()
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
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") }
|
||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||
try self.init(clientDeviceId: try c.decode(String.self, forKey: .clientDeviceId), deviceKeyId: try c.decode(String.self, forKey: .deviceKeyId), certificateChainPem: try c.decode(String.self, forKey: .certificateChainPem), trustBundlePem: try c.decode(String.self, forKey: .trustBundlePem), expiresAt: try c.decode(String.self, forKey: .expiresAt))
|
||||
}
|
||||
|
||||
public func validate() throws {
|
||||
if self.clientDeviceId.isEmpty { throw ContractValidationError(field: "client_device_id", code: "required") }
|
||||
if !self.clientDeviceId.isEmpty && self.clientDeviceId.utf8.count < 1 { throw ContractValidationError(field: "client_device_id", code: "min_length") }
|
||||
if self.clientDeviceId.utf8.count > 128 { throw ContractValidationError(field: "client_device_id", code: "max_length") }
|
||||
if self.deviceKeyId.isEmpty { throw ContractValidationError(field: "device_key_id", code: "required") }
|
||||
if !self.deviceKeyId.isEmpty && self.deviceKeyId.utf8.count < 1 { throw ContractValidationError(field: "device_key_id", code: "min_length") }
|
||||
if self.deviceKeyId.utf8.count > 128 { throw ContractValidationError(field: "device_key_id", code: "max_length") }
|
||||
if self.certificateChainPem.isEmpty { throw ContractValidationError(field: "certificate_chain_pem", code: "required") }
|
||||
if !self.certificateChainPem.isEmpty && self.certificateChainPem.utf8.count < 1 { throw ContractValidationError(field: "certificate_chain_pem", code: "min_length") }
|
||||
if self.certificateChainPem.utf8.count > 65536 { throw ContractValidationError(field: "certificate_chain_pem", code: "max_length") }
|
||||
if self.trustBundlePem.isEmpty { throw ContractValidationError(field: "trust_bundle_pem", code: "required") }
|
||||
if !self.trustBundlePem.isEmpty && self.trustBundlePem.utf8.count < 1 { throw ContractValidationError(field: "trust_bundle_pem", code: "min_length") }
|
||||
if self.trustBundlePem.utf8.count > 65536 { throw ContractValidationError(field: "trust_bundle_pem", code: "max_length") }
|
||||
if self.expiresAt.utf8.count > 64 { throw ContractValidationError(field: "expires_at", code: "max_length") }
|
||||
if !validRFC3339UTC(self.expiresAt) { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
|
||||
}
|
||||
|
||||
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
|
||||
public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }
|
||||
}
|
||||
|
||||
public struct PageInfo: Codable, Equatable {
|
||||
public let limit: Int64
|
||||
public let nextCursor: String
|
||||
@@ -1703,7 +1907,7 @@ public struct ProviderSessionWork: Codable, Equatable {
|
||||
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.expiresAt.utf8.count > 64 { throw ContractValidationError(field: "expires_at", code: "max_length") }
|
||||
if ISO8601DateFormatter().date(from: self.expiresAt) == nil { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
|
||||
if !validRFC3339UTC(self.expiresAt) { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
|
||||
if self.providerProfile != "apollo" { throw ContractValidationError(field: "provider_profile", code: "invalid_value") }
|
||||
if self.providerIdentity.isEmpty { throw ContractValidationError(field: "provider_identity", code: "required") }
|
||||
if !self.providerIdentity.isEmpty && self.providerIdentity.utf8.count < 1 { throw ContractValidationError(field: "provider_identity", code: "min_length") }
|
||||
@@ -1781,6 +1985,8 @@ public struct ProviderState: Codable, Equatable {
|
||||
if self.sessionId.utf8.count > 128 { throw ContractValidationError(field: "session_id", code: "max_length") }
|
||||
if !["starting", "ready", "disconnected", "terminating", "terminated", "cleanup_pending", "failed"].contains(self.state) { throw ContractValidationError(field: "state", code: "invalid_value") }
|
||||
if self.channels.count > 8 { throw ContractValidationError(field: "channels", code: "max_items") }
|
||||
for item in self.channels where item.utf8.count < 1 { throw ContractValidationError(field: "channels", code: "min_item_length") }
|
||||
for item in self.channels where item.utf8.count > 64 { throw ContractValidationError(field: "channels", code: "max_item_length") }
|
||||
}
|
||||
|
||||
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
|
||||
@@ -1868,7 +2074,7 @@ public struct ReauthGrant: Codable, Equatable {
|
||||
if !self.purpose.isEmpty && self.purpose.utf8.count < 1 { throw ContractValidationError(field: "purpose", code: "min_length") }
|
||||
if self.purpose.utf8.count > 64 { throw ContractValidationError(field: "purpose", code: "max_length") }
|
||||
if self.expiresAt.utf8.count > 64 { throw ContractValidationError(field: "expires_at", code: "max_length") }
|
||||
if ISO8601DateFormatter().date(from: self.expiresAt) == nil { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
|
||||
if !validRFC3339UTC(self.expiresAt) { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
|
||||
}
|
||||
|
||||
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
|
||||
@@ -2169,7 +2375,7 @@ public struct SessionAuthority: Codable, Equatable {
|
||||
if self.audience.utf8.count > 256 { throw ContractValidationError(field: "audience", code: "max_length") }
|
||||
if self.reconnectSequence < 0 { throw ContractValidationError(field: "reconnect_sequence", code: "minimum") }
|
||||
if self.expiresAt.utf8.count > 64 { throw ContractValidationError(field: "expires_at", code: "max_length") }
|
||||
if ISO8601DateFormatter().date(from: self.expiresAt) == nil { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
|
||||
if !validRFC3339UTC(self.expiresAt) { throw ContractValidationError(field: "expires_at", code: "invalid_time") }
|
||||
try self.capabilities.validate()
|
||||
if !["apollo"].contains(self.providerProfile) { throw ContractValidationError(field: "provider_profile", code: "invalid_value") }
|
||||
if self.providerIdentity.isEmpty { throw ContractValidationError(field: "provider_identity", code: "required") }
|
||||
@@ -2186,23 +2392,20 @@ public struct SessionRequest: Codable, Equatable {
|
||||
public let deviceKeyId: String
|
||||
public let poolId: String
|
||||
public let idempotencyKey: String
|
||||
public let policySnapshot: AllocationPolicy
|
||||
public let requestedDisplayMode: DisplayMode?
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case clientDeviceId = "client_device_id"
|
||||
case deviceKeyId = "device_key_id"
|
||||
case poolId = "pool_id"
|
||||
case idempotencyKey = "idempotency_key"
|
||||
case policySnapshot = "policy_snapshot"
|
||||
case requestedDisplayMode = "requested_display_mode"
|
||||
}
|
||||
|
||||
public init(clientDeviceId: String, deviceKeyId: String, poolId: String, idempotencyKey: String, policySnapshot: AllocationPolicy, requestedDisplayMode: DisplayMode?) throws {
|
||||
public init(clientDeviceId: String, deviceKeyId: String, poolId: String, idempotencyKey: String, requestedDisplayMode: DisplayMode?) throws {
|
||||
self.clientDeviceId = clientDeviceId
|
||||
self.deviceKeyId = deviceKeyId
|
||||
self.poolId = poolId
|
||||
self.idempotencyKey = idempotencyKey
|
||||
self.policySnapshot = policySnapshot
|
||||
self.requestedDisplayMode = requestedDisplayMode
|
||||
try validate()
|
||||
}
|
||||
@@ -2211,7 +2414,7 @@ public struct SessionRequest: Codable, Equatable {
|
||||
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") }
|
||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||
try self.init(clientDeviceId: try c.decode(String.self, forKey: .clientDeviceId), deviceKeyId: try c.decode(String.self, forKey: .deviceKeyId), poolId: try c.decode(String.self, forKey: .poolId), idempotencyKey: try c.decode(String.self, forKey: .idempotencyKey), policySnapshot: try c.decode(AllocationPolicy.self, forKey: .policySnapshot), requestedDisplayMode: try c.contains(.requestedDisplayMode) ? c.decode(DisplayMode.self, forKey: .requestedDisplayMode) : nil)
|
||||
try self.init(clientDeviceId: try c.decode(String.self, forKey: .clientDeviceId), deviceKeyId: try c.decode(String.self, forKey: .deviceKeyId), poolId: try c.decode(String.self, forKey: .poolId), idempotencyKey: try c.decode(String.self, forKey: .idempotencyKey), requestedDisplayMode: try c.contains(.requestedDisplayMode) ? c.decode(DisplayMode.self, forKey: .requestedDisplayMode) : nil)
|
||||
}
|
||||
|
||||
public func validate() throws {
|
||||
@@ -2227,7 +2430,6 @@ public struct SessionRequest: Codable, Equatable {
|
||||
if self.idempotencyKey.isEmpty { throw ContractValidationError(field: "idempotency_key", code: "required") }
|
||||
if !self.idempotencyKey.isEmpty && self.idempotencyKey.utf8.count < 1 { throw ContractValidationError(field: "idempotency_key", code: "min_length") }
|
||||
if self.idempotencyKey.utf8.count > 256 { throw ContractValidationError(field: "idempotency_key", code: "max_length") }
|
||||
try self.policySnapshot.validate()
|
||||
if let value = self.requestedDisplayMode {
|
||||
try value.validate()
|
||||
}
|
||||
@@ -2372,7 +2574,9 @@ public struct VersionNegotiation: Codable, Equatable {
|
||||
public func validate() throws {
|
||||
if self.supportedVersions.count < 1 { throw ContractValidationError(field: "supported_versions", code: "min_items") }
|
||||
if self.supportedVersions.count > 3 { throw ContractValidationError(field: "supported_versions", code: "max_items") }
|
||||
for item in self.supportedVersions where item.utf8.count > 16 { throw ContractValidationError(field: "supported_versions", code: "max_item_length") }
|
||||
if self.features.count > 64 { throw ContractValidationError(field: "features", code: "max_items") }
|
||||
for item in self.features where item.utf8.count > 64 { throw ContractValidationError(field: "features", code: "max_item_length") }
|
||||
}
|
||||
|
||||
public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }
|
||||
|
||||
Reference in New Issue
Block a user