package protocol import ( "encoding/json" "fmt" ) // MaximumCommandBatchSize bounds the ordered commands in one publication. const MaximumCommandBatchSize = 256 // BatchPayload holds 2 through 256 commands for one device. IDs and targets are // distinct. Array order is operation order. Each member keeps its own expiry, // correlation, and causation. There is no combined result. type BatchPayload struct { Commands []BatchCommand } // MarshalJSON inserts each command's schema without caller-built envelopes. func (b BatchPayload) MarshalJSON() ([]byte, error) { commands := make([]batchWireCommand, 0, len(b.Commands)) for _, command := range b.Commands { commands = append(commands, batchWireCommand{ Schema: Schema, Metadata: command.Metadata, Capability: command.Capability, Payload: command.Payload, }) } data, err := json.Marshal(batchWirePayload{Commands: commands}) if err != nil { return nil, fmt.Errorf("encode batch members: %w", err) } return data, nil } // UnmarshalJSON rejects unknown fields and unsupported member schemas. func (b *BatchPayload) UnmarshalJSON(data []byte) error { var wire batchWirePayload err := decodeObject(data, &wire) if err != nil { return fmt.Errorf("decode batch members: %w", err) } commands := make([]BatchCommand, 0, len(wire.Commands)) for _, command := range wire.Commands { if command.Schema != Schema { return fmt.Errorf("unsupported batch member schema %d", command.Schema) } commands = append(commands, BatchCommand{ Metadata: command.Metadata, Capability: command.Capability, Payload: command.Payload, }) } b.Commands = commands return nil } // BatchCommand holds one complete request without a topic or schema field. The // outer batch message ID equals the first member's message ID. type BatchCommand struct { Metadata Capability string Payload ValuePayload } type batchWirePayload struct { Commands []batchWireCommand `json:"commands"` } type batchWireCommand struct { Schema int `json:"schema"` Metadata Capability string `json:"capability"` Payload ValuePayload `json:"payload"` }