diff --git a/pkg/expression/expression.go b/pkg/expression/expression.go index e4a1e38..7da6c5a 100644 --- a/pkg/expression/expression.go +++ b/pkg/expression/expression.go @@ -36,6 +36,12 @@ func (e *Expression) Evaluate(ctx EvalCtx) (any, error) { } switch e.Op { + + case "@now": + v := time.Now().UTC().Format(time.RFC3339) + ctx.Log.V(8).Info("eval ready", "expression", e.String(), "result", v) + return v, nil + case "@bool": lit := e.Literal if e.Arg != nil { @@ -117,10 +123,6 @@ func (e *Expression) Evaluate(ctx EvalCtx) (any, error) { return nil, err } - if ret == "@now" { - ret = time.Now().UTC().Format("2006-01-02T15:04:05Z07:00") - } - ctx.Log.V(8).Info("eval ready", "expression", e.String(), "result", ret) return ret, nil diff --git a/pkg/expression/expression_test.go b/pkg/expression/expression_test.go index b17047e..724ae6f 100644 --- a/pkg/expression/expression_test.go +++ b/pkg/expression/expression_test.go @@ -128,13 +128,15 @@ var _ = Describe("Expressions", func() { Expect(reflect.ValueOf(res).String()).To(Equal("a10")) }) - It("should evaluate a @now expression", func() { + It("should deserialize and evaluate a @now expression", func() { jsonData := `"@now"` var exp Expression err := json.Unmarshal([]byte(jsonData), &exp) Expect(err).NotTo(HaveOccurred()) + Expect(exp).To(Equal(Expression{ + Op: "@now", + })) - now := time.Now() ctx := EvalCtx{Object: obj1.UnstructuredContent(), Log: logger} res, err := exp.Evaluate(ctx) Expect(err).NotTo(HaveOccurred()) @@ -143,11 +145,36 @@ var _ = Describe("Expressions", func() { t, err := time.Parse(time.RFC3339, res.(string)) Expect(err).NotTo(HaveOccurred()) - Expect(t).To(BeTemporally("~", now, 1*time.Second)) + Expect(t).To(BeTemporally("~", time.Now(), time.Second)) }) }) Describe("Evaluating compound expressions", func() { + It("should deserialize and evaluate a nested @now expression", func() { + jsonData := `{"time": "@now"}` + var exp Expression + err := json.Unmarshal([]byte(jsonData), &exp) + Expect(err).NotTo(HaveOccurred()) + Expect(exp).To(Equal(Expression{ + Op: "@dict", + Literal: map[string]Expression{ + "time": { + Op: "@now", + }, + }, + })) + + ctx := EvalCtx{Object: obj1.UnstructuredContent(), Log: logger} + res, err := exp.Evaluate(ctx) + Expect(err).NotTo(HaveOccurred()) + Expect(res).NotTo(BeNil()) + Expect(res).To(HaveKey("time")) + + t, err := time.Parse(time.RFC3339, res.(map[string]any)["time"].(string)) + Expect(err).NotTo(HaveOccurred()) + Expect(t).To(BeTemporally("~", time.Now(), time.Second)) + }) + It("should deserialize and evaluate a nil expression", func() { jsonData := `{"@isnil": 1}` var exp Expression diff --git a/pkg/expression/marshaler.go b/pkg/expression/marshaler.go index 4d3223d..efc09b5 100644 --- a/pkg/expression/marshaler.go +++ b/pkg/expression/marshaler.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "reflect" + "time" ) func (e *Expression) UnmarshalJSON(b []byte) error { @@ -33,6 +34,11 @@ func (e *Expression) UnmarshalJSON(b []byte) error { sv := "" if err := json.Unmarshal(b, &sv); err == nil && sv != "" { *e = Expression{Op: "@string", Literal: sv} + if sv == "@now" { + *e = Expression{Op: "@now"} + } else { + *e = Expression{Op: "@string", Literal: sv} + } return nil } @@ -73,6 +79,10 @@ func (e *Expression) MarshalJSON() ([]byte, error) { case "@any": return json.Marshal(e.Literal) + case "@now": + v := time.Now().UTC().Format(time.RFC3339) + return []byte(v), nil + case "@bool": if e.Arg != nil { // keep the op for a correct round-trip and possible side-effects (conversion)