diff --git a/design/types.go b/design/types.go index 3ea641e275ca9fed03207bd7d3e052865ae005c3..b17285e5daaa8e7b0aca59f7355012848ad2999a 100644 --- a/design/types.go +++ b/design/types.go @@ -7,7 +7,7 @@ var CacheGetRequest = Type("CacheGetRequest", func() { Field(1, "key", String) Field(2, "namespace", String) Field(3, "scope", String) // Initial implementation with a single scope - Required("key", "namespace", "scope") + Required("key") }) var CacheSetRequest = Type("CacheSetRequest", func() { @@ -15,5 +15,5 @@ var CacheSetRequest = Type("CacheSetRequest", func() { Field(2, "key", String) Field(3, "namespace", String) Field(4, "scope", String) // Initial implementation with a single scope - Required("data", "key", "namespace", "scope") + Required("data", "key") }) diff --git a/gen/cache/service.go b/gen/cache/service.go index ec2b3af1d8b783c03c9ab4e4416a0d1eccac9b28..bfae15ea75b37201caac3dc39b8ea600f82c19b3 100644 --- a/gen/cache/service.go +++ b/gen/cache/service.go @@ -32,14 +32,14 @@ var MethodNames = [2]string{"Get", "Set"} // CacheGetRequest is the payload type of the cache service Get method. type CacheGetRequest struct { Key string - Namespace string - Scope string + Namespace *string + Scope *string } // CacheSetRequest is the payload type of the cache service Set method. type CacheSetRequest struct { Data interface{} Key string - Namespace string - Scope string + Namespace *string + Scope *string } diff --git a/gen/http/cache/client/cli.go b/gen/http/cache/client/cli.go index 6502a89ae3ee866ac1389db01de6fa73d87081c5..813a41a7f13f7eb4f83ad939e50e08e0a813f1a9 100644 --- a/gen/http/cache/client/cli.go +++ b/gen/http/cache/client/cli.go @@ -20,13 +20,17 @@ func BuildGetPayload(cacheGetKey string, cacheGetNamespace string, cacheGetScope { key = cacheGetKey } - var namespace string + var namespace *string { - namespace = cacheGetNamespace + if cacheGetNamespace != "" { + namespace = &cacheGetNamespace + } } - var scope string + var scope *string { - scope = cacheGetScope + if cacheGetScope != "" { + scope = &cacheGetScope + } } v := &cache.CacheGetRequest{} v.Key = key @@ -50,13 +54,17 @@ func BuildSetPayload(cacheSetBody string, cacheSetKey string, cacheSetNamespace { key = cacheSetKey } - var namespace string + var namespace *string { - namespace = cacheSetNamespace + if cacheSetNamespace != "" { + namespace = &cacheSetNamespace + } } - var scope string + var scope *string { - scope = cacheSetScope + if cacheSetScope != "" { + scope = &cacheSetScope + } } v := body res := &cache.CacheSetRequest{ diff --git a/gen/http/cache/client/encode_decode.go b/gen/http/cache/client/encode_decode.go index be26cb474f49755658db16e76b03237c882009ed..623765fd4fc138164971ad9ba9dcd35000665f70 100644 --- a/gen/http/cache/client/encode_decode.go +++ b/gen/http/cache/client/encode_decode.go @@ -45,12 +45,12 @@ func EncodeGetRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Re head := p.Key req.Header.Set("x-cache-key", head) } - { - head := p.Namespace + if p.Namespace != nil { + head := *p.Namespace req.Header.Set("x-cache-namespace", head) } - { - head := p.Scope + if p.Scope != nil { + head := *p.Scope req.Header.Set("x-cache-scope", head) } return nil @@ -119,12 +119,12 @@ func EncodeSetRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Re head := p.Key req.Header.Set("x-cache-key", head) } - { - head := p.Namespace + if p.Namespace != nil { + head := *p.Namespace req.Header.Set("x-cache-namespace", head) } - { - head := p.Scope + if p.Scope != nil { + head := *p.Scope req.Header.Set("x-cache-scope", head) } body := p.Data diff --git a/gen/http/cache/server/encode_decode.go b/gen/http/cache/server/encode_decode.go index b6587e807c913d0cd21fc1001df3baddd7353f34..a58b4a904640446b1bd0173754f71e47cf72aed3 100644 --- a/gen/http/cache/server/encode_decode.go +++ b/gen/http/cache/server/encode_decode.go @@ -35,21 +35,21 @@ func DecodeGetRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Dec return func(r *http.Request) (interface{}, error) { var ( key string - namespace string - scope string + namespace *string + scope *string err error ) key = r.Header.Get("x-cache-key") if key == "" { err = goa.MergeErrors(err, goa.MissingFieldError("x-cache-key", "header")) } - namespace = r.Header.Get("x-cache-namespace") - if namespace == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("x-cache-namespace", "header")) + namespaceRaw := r.Header.Get("x-cache-namespace") + if namespaceRaw != "" { + namespace = &namespaceRaw } - scope = r.Header.Get("x-cache-scope") - if scope == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("x-cache-scope", "header")) + scopeRaw := r.Header.Get("x-cache-scope") + if scopeRaw != "" { + scope = &scopeRaw } if err != nil { return nil, err @@ -87,20 +87,20 @@ func DecodeSetRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Dec var ( key string - namespace string - scope string + namespace *string + scope *string ) key = r.Header.Get("x-cache-key") if key == "" { err = goa.MergeErrors(err, goa.MissingFieldError("x-cache-key", "header")) } - namespace = r.Header.Get("x-cache-namespace") - if namespace == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("x-cache-namespace", "header")) + namespaceRaw := r.Header.Get("x-cache-namespace") + if namespaceRaw != "" { + namespace = &namespaceRaw } - scope = r.Header.Get("x-cache-scope") - if scope == "" { - err = goa.MergeErrors(err, goa.MissingFieldError("x-cache-scope", "header")) + scopeRaw := r.Header.Get("x-cache-scope") + if scopeRaw != "" { + scope = &scopeRaw } if err != nil { return nil, err diff --git a/gen/http/cache/server/types.go b/gen/http/cache/server/types.go index c9d87f134992134625b260b82a3ceee77e0a0ee6..3efce37e69eeeb477c4424845f5f22afb2197710 100644 --- a/gen/http/cache/server/types.go +++ b/gen/http/cache/server/types.go @@ -12,7 +12,7 @@ import ( ) // NewGetCacheGetRequest builds a cache service Get endpoint payload. -func NewGetCacheGetRequest(key string, namespace string, scope string) *cache.CacheGetRequest { +func NewGetCacheGetRequest(key string, namespace *string, scope *string) *cache.CacheGetRequest { v := &cache.CacheGetRequest{} v.Key = key v.Namespace = namespace @@ -22,7 +22,7 @@ func NewGetCacheGetRequest(key string, namespace string, scope string) *cache.Ca } // NewSetCacheSetRequest builds a cache service Set endpoint payload. -func NewSetCacheSetRequest(body interface{}, key string, namespace string, scope string) *cache.CacheSetRequest { +func NewSetCacheSetRequest(body interface{}, key string, namespace *string, scope *string) *cache.CacheSetRequest { v := body res := &cache.CacheSetRequest{ Data: v, diff --git a/gen/http/cli/cache/cli.go b/gen/http/cli/cache/cli.go index 007ace38ca77de84e7e57b6d6a23f783ef89adbe..2b5bb1e94f74ca1ec99b453d3beaff6e7a5ab6aa 100644 --- a/gen/http/cli/cache/cli.go +++ b/gen/http/cli/cache/cli.go @@ -56,14 +56,14 @@ func ParseEndpoint( cacheGetFlags = flag.NewFlagSet("get", flag.ExitOnError) cacheGetKeyFlag = cacheGetFlags.String("key", "REQUIRED", "") - cacheGetNamespaceFlag = cacheGetFlags.String("namespace", "REQUIRED", "") - cacheGetScopeFlag = cacheGetFlags.String("scope", "REQUIRED", "") + cacheGetNamespaceFlag = cacheGetFlags.String("namespace", "", "") + cacheGetScopeFlag = cacheGetFlags.String("scope", "", "") cacheSetFlags = flag.NewFlagSet("set", flag.ExitOnError) cacheSetBodyFlag = cacheSetFlags.String("body", "REQUIRED", "") cacheSetKeyFlag = cacheSetFlags.String("key", "REQUIRED", "") - cacheSetNamespaceFlag = cacheSetFlags.String("namespace", "REQUIRED", "") - cacheSetScopeFlag = cacheSetFlags.String("scope", "REQUIRED", "") + cacheSetNamespaceFlag = cacheSetFlags.String("namespace", "", "") + cacheSetScopeFlag = cacheSetFlags.String("scope", "", "") ) healthFlags.Usage = healthUsage healthLivenessFlags.Usage = healthLivenessUsage diff --git a/gen/http/openapi.json b/gen/http/openapi.json index b9d430311b5a1b01b8f6f061e08c090c3e191545..2370bc6db32845023a201b929d950f54a833cbe5 100644 --- a/gen/http/openapi.json +++ b/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"Cache Service","description":"The cache service exposes interface for working with Redis.","version":""},"host":"localhost:8083","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/v1/cache":{"get":{"tags":["cache"],"summary":"Get cache","description":"Get JSON value from the cache.","operationId":"cache#Get","produces":["application/json"],"parameters":[{"name":"x-cache-key","in":"header","description":"Cache entry key","required":true,"type":"string"},{"name":"x-cache-namespace","in":"header","description":"Cache entry namespace","required":true,"type":"string"},{"name":"x-cache-scope","in":"header","description":"Cache entry scope","required":true,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"type":"string","format":"binary"}}},"schemes":["http"]},"post":{"tags":["cache"],"summary":"Set cache","description":"Set a JSON value in the cache.","operationId":"cache#Set","parameters":[{"name":"x-cache-key","in":"header","description":"Cache entry key","required":true,"type":"string"},{"name":"x-cache-namespace","in":"header","description":"Cache entry namespace","required":true,"type":"string"},{"name":"x-cache-scope","in":"header","description":"Cache entry scope","required":true,"type":"string"},{"name":"any","in":"body","required":true,"schema":{"type":"string","format":"binary"}}],"responses":{"201":{"description":"Created response."}},"schemes":["http"]}}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"Cache Service","description":"The cache service exposes interface for working with Redis.","version":""},"host":"localhost:8083","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/v1/cache":{"get":{"tags":["cache"],"summary":"Get cache","description":"Get JSON value from the cache.","operationId":"cache#Get","produces":["application/json"],"parameters":[{"name":"x-cache-key","in":"header","description":"Cache entry key","required":true,"type":"string"},{"name":"x-cache-namespace","in":"header","description":"Cache entry namespace","required":false,"type":"string"},{"name":"x-cache-scope","in":"header","description":"Cache entry scope","required":false,"type":"string"}],"responses":{"200":{"description":"OK response.","schema":{"type":"string","format":"binary"}}},"schemes":["http"]},"post":{"tags":["cache"],"summary":"Set cache","description":"Set a JSON value in the cache.","operationId":"cache#Set","parameters":[{"name":"x-cache-key","in":"header","description":"Cache entry key","required":true,"type":"string"},{"name":"x-cache-namespace","in":"header","description":"Cache entry namespace","required":false,"type":"string"},{"name":"x-cache-scope","in":"header","description":"Cache entry scope","required":false,"type":"string"},{"name":"any","in":"body","required":true,"schema":{"type":"string","format":"binary"}}],"responses":{"201":{"description":"Created response."}},"schemes":["http"]}}}} \ No newline at end of file diff --git a/gen/http/openapi.yaml b/gen/http/openapi.yaml index b1bac2cdadd957be492a9618414d12516397b6b2..4cd6fad9099a1009f12801ad407bb893254fef2a 100644 --- a/gen/http/openapi.yaml +++ b/gen/http/openapi.yaml @@ -53,12 +53,12 @@ paths: - name: x-cache-namespace in: header description: Cache entry namespace - required: true + required: false type: string - name: x-cache-scope in: header description: Cache entry scope - required: true + required: false type: string responses: "200": @@ -83,12 +83,12 @@ paths: - name: x-cache-namespace in: header description: Cache entry namespace - required: true + required: false type: string - name: x-cache-scope in: header description: Cache entry scope - required: true + required: false type: string - name: any in: body diff --git a/gen/http/openapi3.json b/gen/http/openapi3.json index 0ce517d7b5de2092ed45e6bb357aa14c8556a7c9..e32b4621b30aed7e9fef74ade57dad6b2249c0c1 100644 --- a/gen/http/openapi3.json +++ b/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"Cache Service","description":"The cache service exposes interface for working with Redis.","version":"1.0"},"servers":[{"url":"http://localhost:8083","description":"Cache Server"}],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}}}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}}}},"/v1/cache":{"get":{"tags":["cache"],"summary":"Get cache","description":"Get JSON value from the cache.","operationId":"cache#Get","parameters":[{"name":"x-cache-key","in":"header","description":"Cache entry key","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Cache entry key","example":"did:web:example.com"},"example":"did:web:example.com"},{"name":"x-cache-namespace","in":"header","description":"Cache entry namespace","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Cache entry namespace","example":"Login"},"example":"Login"},{"name":"x-cache-scope","in":"header","description":"Cache entry scope","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Cache entry scope","example":"administration"},"example":"administration"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Qui iusto enim est dolores dolorem et.","format":"binary"},"example":"Quisquam ab dolores distinctio quis."}}}}},"post":{"tags":["cache"],"summary":"Set cache","description":"Set a JSON value in the cache.","operationId":"cache#Set","parameters":[{"name":"x-cache-key","in":"header","description":"Cache entry key","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Cache entry key","example":"did:web:example.com"},"example":"did:web:example.com"},{"name":"x-cache-namespace","in":"header","description":"Cache entry namespace","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Cache entry namespace","example":"Login"},"example":"Login"},{"name":"x-cache-scope","in":"header","description":"Cache entry scope","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Cache entry scope","example":"administration"},"example":"administration"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"string","example":"Et illum fugiat ut.","format":"binary"},"example":"Optio aliquam error nam."}}},"responses":{"201":{"description":"Created response."}}}}},"components":{},"tags":[{"name":"health","description":"Health service provides health check endpoints."},{"name":"cache","description":"Cache service allows storing and retrieving data from distributed cache."}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"Cache Service","description":"The cache service exposes interface for working with Redis.","version":"1.0"},"servers":[{"url":"http://localhost:8083","description":"Cache Server"}],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}}}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}}}},"/v1/cache":{"get":{"tags":["cache"],"summary":"Get cache","description":"Get JSON value from the cache.","operationId":"cache#Get","parameters":[{"name":"x-cache-key","in":"header","description":"Cache entry key","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Cache entry key","example":"did:web:example.com"},"example":"did:web:example.com"},{"name":"x-cache-namespace","in":"header","description":"Cache entry namespace","allowEmptyValue":true,"schema":{"type":"string","description":"Cache entry namespace","example":"Login"},"example":"Login"},{"name":"x-cache-scope","in":"header","description":"Cache entry scope","allowEmptyValue":true,"schema":{"type":"string","description":"Cache entry scope","example":"administration"},"example":"administration"}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Qui iusto enim est dolores dolorem et.","format":"binary"},"example":"Quisquam ab dolores distinctio quis."}}}}},"post":{"tags":["cache"],"summary":"Set cache","description":"Set a JSON value in the cache.","operationId":"cache#Set","parameters":[{"name":"x-cache-key","in":"header","description":"Cache entry key","allowEmptyValue":true,"required":true,"schema":{"type":"string","description":"Cache entry key","example":"did:web:example.com"},"example":"did:web:example.com"},{"name":"x-cache-namespace","in":"header","description":"Cache entry namespace","allowEmptyValue":true,"schema":{"type":"string","description":"Cache entry namespace","example":"Login"},"example":"Login"},{"name":"x-cache-scope","in":"header","description":"Cache entry scope","allowEmptyValue":true,"schema":{"type":"string","description":"Cache entry scope","example":"administration"},"example":"administration"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"string","example":"Et illum fugiat ut.","format":"binary"},"example":"Optio aliquam error nam."}}},"responses":{"201":{"description":"Created response."}}}}},"components":{},"tags":[{"name":"health","description":"Health service provides health check endpoints."},{"name":"cache","description":"Cache service allows storing and retrieving data from distributed cache."}]} \ No newline at end of file diff --git a/gen/http/openapi3.yaml b/gen/http/openapi3.yaml index b936940cde419698a7133a062edd0436c1650930..625d26c77e0a17d0ab83f4257520e224c556580c 100644 --- a/gen/http/openapi3.yaml +++ b/gen/http/openapi3.yaml @@ -47,7 +47,6 @@ paths: in: header description: Cache entry namespace allowEmptyValue: true - required: true schema: type: string description: Cache entry namespace @@ -57,7 +56,6 @@ paths: in: header description: Cache entry scope allowEmptyValue: true - required: true schema: type: string description: Cache entry scope @@ -94,7 +92,6 @@ paths: in: header description: Cache entry namespace allowEmptyValue: true - required: true schema: type: string description: Cache entry namespace @@ -104,7 +101,6 @@ paths: in: header description: Cache entry scope allowEmptyValue: true - required: true schema: type: string description: Cache entry scope diff --git a/internal/service/cache/service.go b/internal/service/cache/service.go index 4644cc015ed6f44f86ea7dc4b6ead5f73420f384..d12dfd8dabf2748727b870106fb91326eca5d300 100644 --- a/internal/service/cache/service.go +++ b/internal/service/cache/service.go @@ -3,7 +3,6 @@ package cache import ( "context" "encoding/json" - "fmt" "time" "go.uber.org/zap" @@ -30,17 +29,26 @@ func New(cache Cache, logger *zap.Logger) *Service { } func (s *Service) Get(ctx context.Context, req *cache.CacheGetRequest) (interface{}, error) { - var operation = zap.String("operation", "get") - if req.Key == "" || req.Namespace == "" || req.Scope == "" { - s.logger.Error("bad request: missing key or namespace or scopes", operation) + logger := s.logger.With(zap.String("operation", "get")) + + if req.Key == "" { + logger.Error("bad request: missing key") return nil, errors.New(errors.BadRequest, "bad request") } + var namespace, scope string + if req.Namespace != nil { + namespace = *req.Namespace + } + if req.Scope != nil { + scope = *req.Scope + } + // create key from the input fields - key := makeCacheKey(req.Namespace, req.Scope, req.Key) + key := makeCacheKey(req.Key, namespace, scope) data, err := s.cache.Get(ctx, key) if err != nil { - s.logger.Error("error getting value from cache", zap.String("key", key), zap.Error(err)) + logger.Error("error getting value from cache", zap.String("key", key), zap.Error(err)) if errors.Is(errors.NotFound, err) { return nil, errors.New("key not found in cache", err) } @@ -49,7 +57,7 @@ func (s *Service) Get(ctx context.Context, req *cache.CacheGetRequest) (interfac var decodedValue interface{} if err := json.Unmarshal(data, &decodedValue); err != nil { - s.logger.Error("cannot decode json value from cache", zap.Error(err)) + logger.Error("cannot decode json value from cache", zap.Error(err)) return nil, errors.New("cannot decode json value from cache", err) } @@ -57,32 +65,47 @@ func (s *Service) Get(ctx context.Context, req *cache.CacheGetRequest) (interfac } func (s *Service) Set(ctx context.Context, req *cache.CacheSetRequest) error { - var operation = zap.String("operation", "set") + logger := s.logger.With(zap.String("operation", "set")) - if req.Key == "" || req.Namespace == "" || req.Scope == "" { - s.logger.Error("bad request: missing key or namespace or scope or data", operation) + if req.Key == "" { + logger.Error("bad request: missing key") return errors.New(errors.BadRequest, "bad request") } + var namespace, scope string + if req.Namespace != nil { + namespace = *req.Namespace + } + if req.Scope != nil { + scope = *req.Scope + } + // TODO(kinkov): issue #3 - evaluate key metadata (key, namespace and scope) and set TTL over a policy execution // create key from the input fields - key := makeCacheKey(req.Namespace, req.Scope, req.Key) + key := makeCacheKey(req.Key, namespace, scope) // encode payload to json bytes for storing in cache value, err := json.Marshal(req.Data) if err != nil { - s.logger.Error("error encode payload to json", zap.Error(err), operation) + logger.Error("error encode payload to json", zap.Error(err)) return errors.New("error encode payload to json", err) } if err := s.cache.Set(ctx, key, value, 0); err != nil { - s.logger.Error("error setting value in cache", zap.Error(err), operation) - return errors.New("error setting value in cache", err) + logger.Error("error storing value in cache", zap.Error(err)) + return errors.New("error storing value in cache", err) } return nil } -func makeCacheKey(namespace, scope, key string) string { - return fmt.Sprintf("%s,%s,%s", namespace, scope, key) +func makeCacheKey(key, namespace, scope string) string { + k := key + if namespace != "" { + k += "," + namespace + } + if scope != "" { + k += "," + scope + } + return k }