Skip to content

Commit f383357

Browse files
committed
feat: add TO_DURATION function with string parsing and validation, update related tests
1 parent 77cdb0f commit f383357

4 files changed

Lines changed: 102 additions & 0 deletions

File tree

pkg/stdlib/types/lib.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ func RegisterLib(ns runtime.Namespace) {
99
Add("TO_BOOL", ToBool).
1010
Add("TO_INT", ToInt).
1111
Add("TO_FLOAT", ToFloat).
12+
Add("TO_DURATION", ToDuration).
1213
Add("TO_STRING", ToString).
1314
Add("TO_DATETIME", ToDateTime).
1415
Add("TO_ARRAY", ToArray).

pkg/stdlib/types/to_duration.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package types
2+
3+
import (
4+
"context"
5+
6+
"github.com/MontFerret/ferret/v2/pkg/runtime"
7+
)
8+
9+
// ToDuration returns a native duration or parses one from its string representation.
10+
// Numeric values are not interpreted as durations.
11+
// @param {Duration|String} value - A native duration or duration string.
12+
// @return {Duration} - A native duration value.
13+
func ToDuration(_ context.Context, arg runtime.Value) (runtime.Value, error) {
14+
switch value := arg.(type) {
15+
case runtime.Duration:
16+
return value, nil
17+
case runtime.String:
18+
return runtime.ParseDuration(value.String())
19+
default:
20+
return runtime.None, runtime.TypeErrorOf(arg, runtime.TypeDuration, runtime.TypeString)
21+
}
22+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package types
2+
3+
import (
4+
"errors"
5+
"testing"
6+
"time"
7+
8+
"github.com/MontFerret/ferret/v2/pkg/runtime"
9+
)
10+
11+
func TestToDuration(t *testing.T) {
12+
t.Parallel()
13+
14+
tests := []struct {
15+
input runtime.Value
16+
name string
17+
expected runtime.Duration
18+
}{
19+
{
20+
name: "duration",
21+
input: runtime.NewDuration(250 * time.Millisecond),
22+
expected: runtime.NewDuration(250 * time.Millisecond),
23+
},
24+
{
25+
name: "literal string",
26+
input: runtime.NewString("1.5s"),
27+
expected: runtime.NewDuration(1500 * time.Millisecond),
28+
},
29+
{
30+
name: "normalized string",
31+
input: runtime.NewString("1h30m"),
32+
expected: runtime.NewDuration(90 * time.Minute),
33+
},
34+
}
35+
36+
for _, tt := range tests {
37+
t.Run(tt.name, func(t *testing.T) {
38+
t.Parallel()
39+
40+
actual, err := ToDuration(t.Context(), tt.input)
41+
if err != nil {
42+
t.Fatalf("ToDuration() error = %v", err)
43+
}
44+
45+
duration, err := runtime.CastDuration(actual)
46+
if err != nil {
47+
t.Fatalf("ToDuration() result type = %s, want Duration", runtime.TypeOf(actual))
48+
}
49+
50+
if duration != tt.expected {
51+
t.Fatalf("ToDuration() = %s, want %s", duration, tt.expected)
52+
}
53+
})
54+
}
55+
}
56+
57+
func TestToDurationRejectsUnsupportedType(t *testing.T) {
58+
t.Parallel()
59+
60+
_, err := ToDuration(t.Context(), runtime.NewInt(1))
61+
if !errors.Is(err, runtime.ErrInvalidType) {
62+
t.Fatalf("ToDuration() error = %v, want invalid type", err)
63+
}
64+
}
65+
66+
func TestToDurationRejectsInvalidString(t *testing.T) {
67+
t.Parallel()
68+
69+
if _, err := ToDuration(t.Context(), runtime.NewString("1fortnight")); err == nil {
70+
t.Fatal("ToDuration() accepted an invalid duration string")
71+
}
72+
}

test/integration/vm/vm_duration_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ func TestNativeDurationValues(t *testing.T) {
2828
S(`RETURN 5s > 4999ms`, true),
2929
S(`RETURN TYPENAME(5s)`, "Duration"),
3030
S(`RETURN IS_DURATION(5s)`, true),
31+
S(`RETURN TO_DURATION(5s)`, "5s"),
32+
S(`RETURN TO_DURATION("250ms")`, "250ms"),
33+
S(`RETURN TO_DURATION("1h30m")`, "1h30m0s"),
34+
S(`RETURN TYPENAME(TO_DURATION("500ms"))`, "Duration"),
35+
S(`RETURN TO_DURATION("500ms") + 500ms`, "1s"),
3136
S(`RETURN MATCH 5s (5000ms => true, _ => false)`, true),
3237
S(`RETURN DISTINCT [5s, 5000ms]`, []any{"5s"}),
3338
spec.NewSpec(`RETURN 1s + 1`).Expect().ExecError(ShouldBeRuntimeError, &ExpectedRuntimeError{
@@ -39,6 +44,8 @@ func TestNativeDurationValues(t *testing.T) {
3944
S(`RETURN 0.000001ms / 2`, "1ns"),
4045
S(`RETURN (-0.000001ms) / 2`, "-1ns"),
4146
Error(`RETURN 1s - 1`),
47+
Error(`RETURN TO_DURATION(5000)`),
48+
Error(`RETURN TO_DURATION("1fortnight")`),
4249
Error(`RETURN 1s + "1s"`),
4350
Error(`RETURN "1s" + 1s`),
4451
Error("RETURN `${1s}`"),

0 commit comments

Comments
 (0)