github.com/ravendb/ravendb-go-client@v0.0.0-20240229102137-4474ee7aa0fa/raven_command.go (about)

     1  package ravendb
     2  
     3  import (
     4  	"io"
     5  	"io/ioutil"
     6  	"net/http"
     7  )
     8  
     9  var (
    10  	_ RavenCommand = &RavenCommandBase{}
    11  )
    12  
    13  // RavenCommand defines interface for server commands
    14  type RavenCommand interface {
    15  	// those are meant to be over-written
    16  	CreateRequest(node *ServerNode) (*http.Request, error)
    17  	SetResponse(response []byte, fromCache bool) error
    18  	SetResponseRaw(response *http.Response, body io.Reader) error
    19  
    20  	Send(client *http.Client, req *http.Request) (*http.Response, error)
    21  
    22  	// for all other functions, get access to underlying RavenCommandBase
    23  	GetBase() *RavenCommandBase
    24  }
    25  
    26  type RavenCommandBase struct {
    27  	StatusCode           int
    28  	ResponseType         RavenCommandResponseType
    29  	CanCache             bool
    30  	CanCacheAggressively bool
    31  
    32  	// if true, can be cached
    33  	IsReadRequest bool
    34  
    35  	FailedNodes map[*ServerNode]error
    36  }
    37  
    38  func NewRavenCommandBase() RavenCommandBase {
    39  	res := RavenCommandBase{
    40  		ResponseType:         RavenCommandResponseTypeObject,
    41  		CanCache:             true,
    42  		CanCacheAggressively: true,
    43  	}
    44  	return res
    45  }
    46  
    47  func (c *RavenCommandBase) GetBase() *RavenCommandBase {
    48  	return c
    49  }
    50  
    51  func (c *RavenCommandBase) SetResponse(response []byte, fromCache bool) error {
    52  	if c.ResponseType == RavenCommandResponseTypeEmpty || c.ResponseType == RavenCommandResponseTypeRaw {
    53  		return throwInvalidResponse()
    54  	}
    55  
    56  	return newUnsupportedOperationError(c.ResponseType + " command must override the SetResponse method which expects response with the following type: " + c.ResponseType)
    57  }
    58  
    59  func (c *RavenCommandBase) SetResponseRaw(response *http.Response, stream io.Reader) error {
    60  	panicIf(true, "When "+c.ResponseType+" is set to Raw then please override this method to handle the response. ")
    61  	return nil
    62  }
    63  
    64  func (c *RavenCommandBase) CreateRequest(node *ServerNode) (*http.Request, error) {
    65  	panicIf(true, "CreateRequest must be over-written by all types")
    66  	return nil, nil
    67  }
    68  
    69  func throwInvalidResponse() error {
    70  	return newIllegalStateError("Invalid response")
    71  }
    72  
    73  func (c *RavenCommandBase) Send(client *http.Client, req *http.Request) (*http.Response, error) {
    74  	rsp, err := client.Do(req)
    75  	return rsp, err
    76  }
    77  
    78  func (c *RavenCommandBase) urlEncode(value string) string {
    79  	return urlEncode(value)
    80  }
    81  
    82  func ensureIsNotNullOrString(value string, name string) error {
    83  	if value == "" {
    84  		return newIllegalArgumentError("%s cannot be null or empty", name)
    85  	}
    86  	return nil
    87  }
    88  
    89  // Note: unused
    90  func (c *RavenCommandBase) isFailedWithNode(node *ServerNode) bool {
    91  	if c.FailedNodes == nil {
    92  		return false
    93  	}
    94  	_, ok := c.FailedNodes[node]
    95  	return ok
    96  }
    97  
    98  // Note: in Java Raven.processResponse is virtual.
    99  // That's impossible in Go, so we replace with stand-alone function that dispatches based on type
   100  func ravenCommand_processResponse(cmd RavenCommand, cache *httpCache, response *http.Response, url string) (responseDisposeHandling, error) {
   101  	if cmdHead, ok := cmd.(*HeadDocumentCommand); ok {
   102  		return cmdHead.ProcessResponse(cache, response, url)
   103  	}
   104  
   105  	if cmdHead, ok := cmd.(*HeadAttachmentCommand); ok {
   106  		return cmdHead.processResponse(cache, response, url)
   107  	}
   108  
   109  	if cmdGet, ok := cmd.(*GetAttachmentCommand); ok {
   110  		return cmdGet.processResponse(cache, response, url)
   111  	}
   112  
   113  	if cmdQuery, ok := cmd.(*QueryStreamCommand); ok {
   114  		return cmdQuery.processResponse(cache, response, url)
   115  	}
   116  
   117  	if cmdStream, ok := cmd.(*StreamCommand); ok {
   118  		return cmdStream.processResponse(cache, response, url)
   119  	}
   120  
   121  	c := cmd.GetBase()
   122  
   123  	if response.Body == nil {
   124  		return responseDisposeHandlingAutomatic, nil
   125  	}
   126  
   127  	statusCode := response.StatusCode
   128  	if c.ResponseType == RavenCommandResponseTypeEmpty || statusCode == http.StatusNoContent {
   129  		return responseDisposeHandlingAutomatic, nil
   130  	}
   131  
   132  	if c.ResponseType == RavenCommandResponseTypeObject {
   133  		contentLength := response.ContentLength
   134  		if contentLength == 0 {
   135  			return responseDisposeHandlingAutomatic, nil
   136  		}
   137  
   138  		// we intentionally don't dispose the reader here, we'll be using it
   139  		// in the command, any associated memory will be released on context reset
   140  		js, err := ioutil.ReadAll(response.Body)
   141  		if err != nil {
   142  			return responseDisposeHandlingAutomatic, err
   143  		}
   144  
   145  		if cache != nil {
   146  			c.cacheResponse(cache, url, response, js)
   147  		}
   148  		err = cmd.SetResponse(js, false)
   149  		return responseDisposeHandlingAutomatic, err
   150  	}
   151  
   152  	err := cmd.SetResponseRaw(response, response.Body)
   153  	return responseDisposeHandlingAutomatic, err
   154  }
   155  
   156  func (c *RavenCommandBase) cacheResponse(cache *httpCache, url string, response *http.Response, responseJson []byte) {
   157  	if !c.CanCache {
   158  		return
   159  	}
   160  
   161  	changeVector := gttpExtensionsGetEtagHeader(response)
   162  	if changeVector == nil {
   163  		return
   164  	}
   165  
   166  	cache.set(url, changeVector, responseJson)
   167  }
   168  
   169  // Note: unused
   170  func (c *RavenCommandBase) addChangeVectorIfNotNull(changeVector *string, request *http.Request) {
   171  	if changeVector != nil {
   172  		request.Header.Add("If-Match", `"`+*changeVector+`"`)
   173  	}
   174  }
   175  
   176  func (c *RavenCommandBase) onResponseFailure(response *http.Response) {
   177  	// Note: it looks like it's meant to be virtual but there are no
   178  	// over-rides in Java code
   179  }
   180  
   181  // Note: hackish solution due to lack of generics
   182  // Returns OperationIDReuslt for commands that have it as a result
   183  // When new command returning OperationIDResult are added, we must extend it
   184  func getCommandOperationIDResult(cmd RavenCommand) *OperationIDResult {
   185  	switch c := cmd.(type) {
   186  	case *CompactDatabaseCommand:
   187  		return c.Result
   188  	case *PatchByQueryCommand:
   189  		return c.Result
   190  	case *DeleteByIndexCommand:
   191  		return c.Result
   192  	}
   193  
   194  	panicIf(true, "called on a command %T that doesn't return OperationIDResult", cmd)
   195  	return nil
   196  }