github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/protocol/v1/request.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 Request struct {
    18  	Protocol    protocol.ProtocolVersion `json:"protocol"`
    19  	MessageBody string                   `json:"message"`
    20  	Envelope    *RequestEnvelope         `json:"envelope"`
    21  
    22  	mu sync.Mutex
    23  }
    24  
    25  type RequestEnvelope struct {
    26  	RequestID  string           `json:"requestid"`
    27  	SenderID   string           `json:"senderid"`
    28  	CallerID   string           `json:"callerid"`
    29  	Collective string           `json:"collective"`
    30  	Agent      string           `json:"agent"`
    31  	TTL        int              `json:"ttl"`
    32  	Time       int64            `json:"time"`
    33  	Filter     *protocol.Filter `json:"filter"`
    34  
    35  	seenBy     [][3]string
    36  	federation *FederationTransportHeader
    37  }
    38  
    39  // RecordNetworkHop appends a hop onto the list of those who processed this message
    40  func (r *Request) RecordNetworkHop(in string, processor string, out string) {
    41  	r.Envelope.seenBy = append(r.Envelope.seenBy, [3]string{in, processor, out})
    42  }
    43  
    44  // NetworkHops returns a list of tuples this messaged traveled through
    45  func (r *Request) NetworkHops() [][3]string {
    46  	return r.Envelope.seenBy
    47  }
    48  
    49  // FederationTargets retrieves the list of targets this message is destined for
    50  func (r *Request) FederationTargets() (targets []string, federated bool) {
    51  	r.mu.Lock()
    52  	defer r.mu.Unlock()
    53  
    54  	if r.Envelope.federation == nil {
    55  		return nil, false
    56  	}
    57  
    58  	return r.Envelope.federation.Targets, true
    59  }
    60  
    61  // FederationReplyTo retrieves the reply to string set by the federation broker
    62  func (r *Request) FederationReplyTo() (replyto string, federated bool) {
    63  	r.mu.Lock()
    64  	defer r.mu.Unlock()
    65  
    66  	if r.Envelope.federation == nil {
    67  		return "", false
    68  	}
    69  
    70  	return r.Envelope.federation.ReplyTo, true
    71  }
    72  
    73  // FederationRequestID retrieves the federation specific requestid
    74  func (r *Request) FederationRequestID() (id string, federated bool) {
    75  	r.mu.Lock()
    76  	defer r.mu.Unlock()
    77  
    78  	if r.Envelope.federation == nil {
    79  		return "", false
    80  	}
    81  
    82  	return r.Envelope.federation.RequestID, true
    83  }
    84  
    85  // SetRequestID sets the request ID for this message
    86  func (r *Request) SetRequestID(id string) {
    87  	r.mu.Lock()
    88  	defer r.mu.Unlock()
    89  
    90  	r.Envelope.RequestID = id
    91  }
    92  
    93  // SetFederationTargets sets the list of hosts this message should go to.
    94  //
    95  // Federation brokers will duplicate the message and send one for each target
    96  func (r *Request) SetFederationTargets(targets []string) {
    97  	r.mu.Lock()
    98  	defer r.mu.Unlock()
    99  
   100  	if r.Envelope.federation == nil {
   101  		r.Envelope.federation = &FederationTransportHeader{}
   102  	}
   103  
   104  	r.Envelope.federation.Targets = targets
   105  }
   106  
   107  // SetFederationReplyTo stores the original reply-to destination in the federation headers
   108  func (r *Request) SetFederationReplyTo(reply string) {
   109  	r.mu.Lock()
   110  	defer r.mu.Unlock()
   111  
   112  	if r.Envelope.federation == nil {
   113  		r.Envelope.federation = &FederationTransportHeader{}
   114  	}
   115  
   116  	r.Envelope.federation.ReplyTo = reply
   117  }
   118  
   119  // SetFederationRequestID sets the request ID for federation purposes
   120  func (r *Request) SetFederationRequestID(id string) {
   121  	r.mu.Lock()
   122  	defer r.mu.Unlock()
   123  
   124  	if r.Envelope.federation == nil {
   125  		r.Envelope.federation = &FederationTransportHeader{}
   126  	}
   127  
   128  	r.Envelope.federation.RequestID = id
   129  }
   130  
   131  // IsFederated determines if this message is federated
   132  func (r *Request) IsFederated() bool {
   133  	r.mu.Lock()
   134  	defer r.mu.Unlock()
   135  
   136  	return r.Envelope.federation != nil
   137  }
   138  
   139  // SetUnfederated removes any federation information from the message
   140  func (r *Request) SetUnfederated() {
   141  	r.mu.Lock()
   142  	defer r.mu.Unlock()
   143  
   144  	r.Envelope.federation = nil
   145  }
   146  
   147  // SetMessage set the message body thats contained in this request.  It should be JSON encoded text
   148  func (r *Request) SetMessage(message []byte) {
   149  	r.mu.Lock()
   150  	defer r.mu.Unlock()
   151  
   152  	r.MessageBody = string(message)
   153  }
   154  
   155  // SetCallerID sets the caller id for this request
   156  func (r *Request) SetCallerID(id string) {
   157  	r.mu.Lock()
   158  	defer r.mu.Unlock()
   159  
   160  	// TODO validate it
   161  	r.Envelope.CallerID = id
   162  }
   163  
   164  // SetCollective sets the collective this request is directed at
   165  func (r *Request) SetCollective(collective string) {
   166  	r.mu.Lock()
   167  	defer r.mu.Unlock()
   168  
   169  	r.Envelope.Collective = collective
   170  }
   171  
   172  // SetAgent sets the agent this requires is created for
   173  func (r *Request) SetAgent(agent string) {
   174  	r.mu.Lock()
   175  	defer r.mu.Unlock()
   176  
   177  	r.Envelope.Agent = agent
   178  }
   179  
   180  // SetTTL sets the validity period for this message
   181  func (r *Request) SetTTL(ttl int) {
   182  	r.mu.Lock()
   183  	defer r.mu.Unlock()
   184  
   185  	r.Envelope.TTL = ttl
   186  }
   187  
   188  // Message retrieves the JSON encoded Message body
   189  func (r *Request) Message() []byte {
   190  	r.mu.Lock()
   191  	defer r.mu.Unlock()
   192  
   193  	return []byte(r.MessageBody)
   194  }
   195  
   196  // RequestID retrieves the unique request ID
   197  func (r *Request) RequestID() string {
   198  	r.mu.Lock()
   199  	defer r.mu.Unlock()
   200  
   201  	return r.Envelope.RequestID
   202  }
   203  
   204  // SenderID retrieves the sender id that sent the message
   205  func (r *Request) SenderID() string {
   206  	r.mu.Lock()
   207  	defer r.mu.Unlock()
   208  
   209  	return r.Envelope.SenderID
   210  }
   211  
   212  // CallerID retrieves the caller id that sent the message
   213  func (r *Request) CallerID() string {
   214  	r.mu.Lock()
   215  	defer r.mu.Unlock()
   216  
   217  	return r.Envelope.CallerID
   218  }
   219  
   220  // Collective retrieves the name of the sub collective this message is aimed at
   221  func (r *Request) Collective() string {
   222  	r.mu.Lock()
   223  	defer r.mu.Unlock()
   224  
   225  	return r.Envelope.Collective
   226  }
   227  
   228  // Agent retrieves the agent name this message is for
   229  func (r *Request) Agent() string {
   230  	return r.Envelope.Agent
   231  }
   232  
   233  // TTL retrieves the maximum allow lifetime of this message
   234  func (r *Request) TTL() int {
   235  	r.mu.Lock()
   236  	defer r.mu.Unlock()
   237  
   238  	return r.Envelope.TTL
   239  }
   240  
   241  // Time retrieves the time this message was first made
   242  func (r *Request) Time() time.Time {
   243  	r.mu.Lock()
   244  	defer r.mu.Unlock()
   245  
   246  	return time.Unix(r.Envelope.Time, 0)
   247  }
   248  
   249  // Filter retrieves the filter for the message.  The boolean is true when the filter is not empty
   250  func (r *Request) Filter() (filter *protocol.Filter, filtered bool) {
   251  	r.mu.Lock()
   252  	defer r.mu.Unlock()
   253  
   254  	return r.Envelope.Filter, !r.Envelope.Filter.Empty()
   255  }
   256  
   257  // NewFilter creates a new empty filter and sets it
   258  func (r *Request) NewFilter() *protocol.Filter {
   259  	r.mu.Lock()
   260  	defer r.mu.Unlock()
   261  
   262  	r.Envelope.Filter = protocol.NewFilter()
   263  
   264  	return r.Envelope.Filter
   265  }
   266  
   267  // JSON creates a JSON encoded request
   268  func (r *Request) JSON() ([]byte, error) {
   269  	r.mu.Lock()
   270  	j, err := json.Marshal(r)
   271  	r.mu.Unlock()
   272  	if err != nil {
   273  		protocolErrorCtr.Inc()
   274  		return nil, fmt.Errorf("could not JSON Marshal: %s", err)
   275  	}
   276  
   277  	if err = r.IsValidJSON(j); err != nil {
   278  		return nil, fmt.Errorf("serialized JSON produced from the Request does not pass validation: %s", err)
   279  	}
   280  
   281  	return j, nil
   282  }
   283  
   284  // SetFilter sets and overwrites the filter for a message with a new one
   285  func (r *Request) SetFilter(filter *protocol.Filter) {
   286  	r.mu.Lock()
   287  	defer r.mu.Unlock()
   288  
   289  	r.Envelope.Filter = filter
   290  }
   291  
   292  // Version retrieves the protocol version for this message
   293  func (r *Request) Version() protocol.ProtocolVersion {
   294  	r.mu.Lock()
   295  	defer r.mu.Unlock()
   296  
   297  	return r.Protocol
   298  }
   299  
   300  // IsValidJSON validates the given JSON data against the schema
   301  func (r *Request) IsValidJSON(data []byte) error {
   302  	_, errors, err := schemaValidate(protocol.RequestV1, data)
   303  	if err != nil {
   304  		return fmt.Errorf("could not validate Request JSON data: %s", err)
   305  	}
   306  
   307  	if len(errors) != 0 {
   308  		return fmt.Errorf("supplied JSON document is not a valid Request message: %s", strings.Join(errors, ", "))
   309  	}
   310  
   311  	return nil
   312  }
   313  
   314  // CallerPublicData is not supported for version 1 messages and is always an empty string
   315  func (r *Request) CallerPublicData() string {
   316  	return ""
   317  }
   318  
   319  // SignerPublicData is not supported for version 1 messages and is always an empty string
   320  func (r *Request) SignerPublicData() string {
   321  	return ""
   322  }