2023-04-13 00:30:33 +08:00
package caching
import (
2025-09-15 23:07:47 +08:00
"bytes"
2023-04-13 00:30:33 +08:00
"context"
2025-09-15 23:07:47 +08:00
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"strings"
2023-04-13 00:30:33 +08:00
"github.com/grafana/grafana-plugin-sdk-go/backend"
)
const (
XCacheHeader = "X-Cache"
StatusHit = "HIT"
StatusMiss = "MISS"
StatusBypass = "BYPASS"
StatusError = "ERROR"
StatusDisabled = "DISABLED"
)
type CacheQueryResponseFn func ( context . Context , * backend . QueryDataResponse )
2023-04-22 01:03:49 +08:00
type CacheResourceResponseFn func ( context . Context , * backend . CallResourceResponse )
2023-04-13 00:30:33 +08:00
type CachedQueryDataResponse struct {
// The cached data response associated with a query, or nil if no cached data is found
Response * backend . QueryDataResponse
// A function that should be used to cache a QueryDataResponse for a given query.
// It can be set to nil by the method implementation (if there is an error, for example), so it should be checked before being called.
UpdateCacheFn CacheQueryResponseFn
}
2023-04-22 01:03:49 +08:00
type CachedResourceDataResponse struct {
// The cached response associated with a resource request, or nil if no cached data is found
Response * backend . CallResourceResponse
// A function that should be used to cache a CallResourceResponse for a given resource request.
// It can be set to nil by the method implementation (if there is an error, for example), so it should be checked before being called.
2023-06-22 16:43:38 +08:00
// Because plugins can send multiple responses asynchronously, the implementation should be able to handle multiple calls to this function for one request.
2023-04-22 01:03:49 +08:00
UpdateCacheFn CacheResourceResponseFn
}
2023-04-13 00:30:33 +08:00
func ProvideCachingService ( ) * OSSCachingService {
return & OSSCachingService { }
}
type CachingService interface {
// HandleQueryRequest uses a QueryDataRequest to check the cache for any existing results for that query.
// If none are found, it should return false and a CachedQueryDataResponse with an UpdateCacheFn which can be used to update the results cache after the fact.
// This function may populate any response headers (accessible through the context) with the cache status using the X-Cache header.
HandleQueryRequest ( context . Context , * backend . QueryDataRequest ) ( bool , CachedQueryDataResponse )
// HandleResourceRequest uses a CallResourceRequest to check the cache for any existing results for that request. If none are found, it should return false.
// This function may populate any response headers (accessible through the context) with the cache status using the X-Cache header.
2023-04-22 01:03:49 +08:00
HandleResourceRequest ( context . Context , * backend . CallResourceRequest ) ( bool , CachedResourceDataResponse )
2023-04-13 00:30:33 +08:00
}
// Implementation of interface - does nothing
type OSSCachingService struct {
}
func ( s * OSSCachingService ) HandleQueryRequest ( ctx context . Context , req * backend . QueryDataRequest ) ( bool , CachedQueryDataResponse ) {
return false , CachedQueryDataResponse { }
}
2023-04-22 01:03:49 +08:00
func ( s * OSSCachingService ) HandleResourceRequest ( ctx context . Context , req * backend . CallResourceRequest ) ( bool , CachedResourceDataResponse ) {
return false , CachedResourceDataResponse { }
2023-04-13 00:30:33 +08:00
}
var _ CachingService = & OSSCachingService { }
2025-09-15 23:07:47 +08:00
// GetKey creates a prefixed cache key and uses the internal `encoder` to encode the query into a string
func GetKey ( prefix string , query interface { } ) ( string , error ) {
keybuf := bytes . NewBuffer ( nil )
encoder := & JSONEncoder { }
if err := encoder . Encode ( keybuf , query ) ; err != nil {
return "" , err
}
key , err := SHA256KeyFunc ( keybuf )
if err != nil {
return "" , err
}
return strings . Join ( [ ] string { prefix , key } , ":" ) , nil
}
// SHA256KeyFunc copies the data from `r` into a sha256.Hash, and returns the encoded Sum.
func SHA256KeyFunc ( r io . Reader ) ( string , error ) {
hash := sha256 . New ( )
// Read all data from the provided reader
if _ , err := io . Copy ( hash , r ) ; err != nil {
return "" , err
}
// Encode the written values to SHA256
return hex . EncodeToString ( hash . Sum ( nil ) ) , nil
}
// JSONEncoder encodes and decodes struct data to/from JSON
type JSONEncoder struct { }
// NewJSONEncoder creates a pointer to a new JSONEncoder, which implements the `Encoder` interface
func NewJSONEncoder ( ) * JSONEncoder {
return & JSONEncoder { }
}
func ( e * JSONEncoder ) EncodeBytes ( w io . Writer , b [ ] byte ) error {
_ , err := w . Write ( b )
if err != nil {
return err
}
return nil
}
func ( e * JSONEncoder ) DecodeBytes ( r io . Reader ) ( [ ] byte , error ) {
encBytes , err := io . ReadAll ( r )
if err != nil {
return [ ] byte { } , err
}
return encBytes , err
}
// Encode encodes the `v` interface into `w` using a json.Encoder
func ( e * JSONEncoder ) Encode ( w io . Writer , v interface { } ) error {
return json . NewEncoder ( w ) . Encode ( v )
}
// Decode encodes the io.Reader `r` into the interface `v` using a json.Decoder
func ( e * JSONEncoder ) Decode ( r io . Reader , v interface { } ) error {
return json . NewDecoder ( r ) . Decode ( v )
}