github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/protocol/v1/reply.go (about) 1 // Copyright (c) 2017-2022, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package v1 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/choria-io/go-choria/protocol" 15 ) 16 17 type Reply struct { 18 Protocol protocol.ProtocolVersion `json:"protocol"` 19 MessageBody string `json:"message"` 20 Envelope *ReplyEnvelope `json:"envelope"` 21 22 mu sync.Mutex 23 } 24 25 type ReplyEnvelope struct { 26 RequestID string `json:"requestid"` 27 SenderID string `json:"senderid"` 28 Agent string `json:"agent"` 29 Time int64 `json:"time"` 30 31 seenBy [][3]string 32 federation *FederationTransportHeader 33 } 34 35 // RecordNetworkHop appends a hop onto the list of those who processed this message 36 func (r *Reply) RecordNetworkHop(in string, processor string, out string) { 37 r.mu.Lock() 38 defer r.mu.Unlock() 39 40 r.Envelope.seenBy = append(r.Envelope.seenBy, [3]string{in, processor, out}) 41 } 42 43 // NetworkHops returns a list of tuples this messaged traveled through 44 func (r *Reply) NetworkHops() [][3]string { 45 r.mu.Lock() 46 defer r.mu.Unlock() 47 48 return r.Envelope.seenBy 49 } 50 51 // SetMessage sets the data to be stored in the Reply. It should be JSON encoded already. 52 func (r *Reply) SetMessage(message []byte) { 53 r.mu.Lock() 54 defer r.mu.Unlock() 55 56 r.MessageBody = string(message) 57 } 58 59 // Message retrieves the JSON encoded message set using SetMessage 60 func (r *Reply) Message() (msg []byte) { 61 r.mu.Lock() 62 defer r.mu.Unlock() 63 64 return []byte(r.MessageBody) 65 } 66 67 // RequestID retrieves the unique request id 68 func (r *Reply) RequestID() string { 69 r.mu.Lock() 70 defer r.mu.Unlock() 71 72 return r.Envelope.RequestID 73 } 74 75 // SenderID retrieves the identity of the sending node 76 func (r *Reply) SenderID() string { 77 r.mu.Lock() 78 defer r.mu.Unlock() 79 80 return r.Envelope.SenderID 81 } 82 83 // Agent retrieves the agent name that sent this reply 84 func (r *Reply) Agent() string { 85 r.mu.Lock() 86 defer r.mu.Unlock() 87 88 return r.Envelope.Agent 89 } 90 91 // Time retrieves the time stamp that this message was made 92 func (r *Reply) Time() time.Time { 93 r.mu.Lock() 94 defer r.mu.Unlock() 95 96 return time.Unix(r.Envelope.Time, 0) 97 } 98 99 // JSON creates a JSON encoded reply 100 func (r *Reply) JSON() ([]byte, error) { 101 r.mu.Lock() 102 j, err := json.Marshal(r) 103 r.mu.Unlock() 104 if err != nil { 105 protocolErrorCtr.Inc() 106 return nil, fmt.Errorf("could not JSON Marshal: %s", err) 107 } 108 109 err = r.IsValidJSON(j) 110 if err != nil { 111 return nil, fmt.Errorf("serialized JSON produced from the Reply does not pass validation: %s", err) 112 } 113 114 return j, nil 115 } 116 117 // Version retrieves the protocol version for this message 118 func (r *Reply) Version() protocol.ProtocolVersion { 119 r.mu.Lock() 120 defer r.mu.Unlock() 121 122 return r.Protocol 123 } 124 125 // IsValidJSON validates the given JSON data against the schema 126 func (r *Reply) IsValidJSON(data []byte) (err error) { 127 if !protocol.ClientStrictValidation { 128 return nil 129 } 130 131 _, errors, err := schemaValidate(protocol.ReplyV1, data) 132 if err != nil { 133 return fmt.Errorf("could not validate Reply JSON data: %s", err) 134 } 135 136 if len(errors) != 0 { 137 return fmt.Errorf("supplied JSON document is not a valid Reply message: %s", strings.Join(errors, ", ")) 138 } 139 140 return nil 141 } 142 143 // FederationTargets retrieves the list of targets this message is destined for 144 func (r *Reply) FederationTargets() (targets []string, federated bool) { 145 r.mu.Lock() 146 defer r.mu.Unlock() 147 148 if r.Envelope.federation == nil { 149 return nil, false 150 } 151 152 return r.Envelope.federation.Targets, true 153 } 154 155 // FederationReplyTo retrieves the reply to string set by the federation broker 156 func (r *Reply) FederationReplyTo() (replyto string, federated bool) { 157 r.mu.Lock() 158 defer r.mu.Unlock() 159 160 if r.Envelope.federation == nil { 161 federated = false 162 return 163 } 164 165 federated = true 166 replyto = r.Envelope.federation.ReplyTo 167 168 return 169 } 170 171 // FederationRequestID retrieves the federation specific requestid 172 func (r *Reply) FederationRequestID() (id string, federated bool) { 173 r.mu.Lock() 174 defer r.mu.Unlock() 175 176 if r.Envelope.federation == nil { 177 federated = false 178 return 179 } 180 181 federated = true 182 id = r.Envelope.federation.RequestID 183 184 return 185 } 186 187 // SetFederationTargets sets the list of hosts this message should go to. 188 // 189 // Federation brokers will duplicate the message and send one for each target 190 func (r *Reply) SetFederationTargets(targets []string) { 191 r.mu.Lock() 192 defer r.mu.Unlock() 193 194 if r.Envelope.federation == nil { 195 r.Envelope.federation = &FederationTransportHeader{} 196 } 197 198 r.Envelope.federation.Targets = targets 199 } 200 201 // SetFederationReplyTo stores the original reply-to destination in the federation headers 202 func (r *Reply) SetFederationReplyTo(reply string) { 203 r.mu.Lock() 204 defer r.mu.Unlock() 205 206 if r.Envelope.federation == nil { 207 r.Envelope.federation = &FederationTransportHeader{} 208 } 209 210 r.Envelope.federation.ReplyTo = reply 211 } 212 213 // SetFederationRequestID sets the request ID for federation purposes 214 func (r *Reply) SetFederationRequestID(id string) { 215 r.mu.Lock() 216 defer r.mu.Unlock() 217 218 if r.Envelope.federation == nil { 219 r.Envelope.federation = &FederationTransportHeader{} 220 } 221 222 r.Envelope.federation.RequestID = id 223 } 224 225 // IsFederated determines if this message is federated 226 func (r *Reply) IsFederated() bool { 227 r.mu.Lock() 228 defer r.mu.Unlock() 229 230 return r.Envelope.federation != nil 231 } 232 233 // SetUnfederated removes any federation information from the message 234 func (r *Reply) SetUnfederated() { 235 r.mu.Lock() 236 defer r.mu.Unlock() 237 238 r.Envelope.federation = nil 239 }