diff --git a/lib/dump.go b/lib/dump.go
new file mode 100644
index 0000000..6520e42
--- /dev/null
+++ b/lib/dump.go
@@ -0,0 +1,173 @@
+// Licensed to Elasticsearch B.V. under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Elasticsearch B.V. licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package lib
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "sort"
+ "strings"
+
+ "github.com/google/cel-go/cel"
+ "github.com/google/cel-go/common"
+)
+
+// NewDump returns an evaluation dump that can be used to examine the complete
+// set of evaluation states from a CEL program. The program must have been
+// constructed with a cel.Env.Program call including the cel.OptTrackState
+// evaluation option. The ast and details parameters must be valid for the
+// program.
+func NewDump(ast *cel.Ast, details *cel.EvalDetails) *Dump {
+ if ast == nil || details == nil {
+ return nil
+ }
+ return &Dump{ast: ast, det: details}
+}
+
+// Dump is an evaluation dump.
+type Dump struct {
+ ast *cel.Ast
+ det *cel.EvalDetails
+}
+
+func (d *Dump) String() string {
+ if d == nil {
+ return ""
+ }
+ var buf strings.Builder
+ for i, v := range d.NodeValues() {
+ if i != 0 {
+ buf.WriteByte('\n')
+ }
+ fmt.Fprint(&buf, v)
+ }
+ return buf.String()
+}
+
+// NodeValues returns the evaluation results, source location and source
+// snippets for the expressions in the dump. The nodes are sorted in
+// source order.
+func (d *Dump) NodeValues() []NodeValue {
+ if d == nil {
+ return nil
+ }
+ es := d.det.State()
+ var values []NodeValue
+ for _, id := range es.IDs() {
+ if id == 0 {
+ continue
+ }
+ v, ok := es.Value(id)
+ if !ok {
+ continue
+ }
+ values = append(values, d.nodeValue(v, id))
+ }
+ sort.Slice(values, func(i, j int) bool {
+ vi := values[i].loc
+ vj := values[j].loc
+ switch {
+ case vi.Line() < vj.Line():
+ return true
+ case vi.Line() > vj.Line():
+ return false
+ }
+ switch {
+ case vi.Column() < vj.Column():
+ return true
+ case vi.Column() > vj.Column():
+ return false
+ default:
+ // If we are here we have executed more than once
+ // and have different values, so sort lexically.
+ // This is not ideal given that values may include
+ // maps which do not render consistently and so
+ // we're breaking the sort invariant that comparisons
+ // will be consistent. For what we are doing this is
+ // good enough.
+ return fmt.Sprint(values[i].val) < fmt.Sprint(values[j].val)
+ }
+ })
+ return values
+}
+
+func (d *Dump) nodeValue(val any, id int64) NodeValue {
+ v := NodeValue{
+ loc: d.ast.NativeRep().SourceInfo().GetStartLocation(id),
+ src: d.ast.Source(),
+ val: val,
+ }
+ return v
+}
+
+// NodeValue is a CEL expression node value and annotation.
+type NodeValue struct {
+ loc common.Location
+ src common.Source
+ val any
+}
+
+func (v NodeValue) MarshalJSON() ([]byte, error) {
+ type val struct {
+ Location string `json:"loc"`
+ Src string `json:"src"`
+ Val any `json:"val"`
+ }
+ var buf bytes.Buffer
+ enc := json.NewEncoder(&buf)
+ enc.SetEscapeHTML(false)
+ err := enc.Encode(val{
+ Location: v.Loc(),
+ Src: v.Src(),
+ Val: v.val,
+ })
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}
+
+func (v NodeValue) String() string {
+ return fmt.Sprintf("%s\n%s\n%v\n", v.Loc(), v.Src(), v.Val())
+}
+
+func (v NodeValue) Val() any {
+ return v.val
+}
+
+func (v NodeValue) Loc() string {
+ return fmt.Sprintf("%s:%d:%d", v.src.Description(), v.loc.Line(), v.loc.Column()+1)
+}
+
+func (v NodeValue) Src() string {
+ snippet, ok := v.src.Snippet(v.loc.Line())
+ if !ok {
+ return ""
+ }
+ src := " | " + strings.Replace(snippet, "\t", " ", -1)
+ ind := "\n | " + strings.Repeat(".", minInt(v.loc.Column(), len(snippet))) + "^"
+ return src + ind
+}
+
+func minInt(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}
diff --git a/mito.go b/mito.go
index d21ee10..77cc65e 100644
--- a/mito.go
+++ b/mito.go
@@ -74,6 +74,7 @@ func Main() int {
logTrace := flag.Bool("log_requests", false, "log request traces to stderr (go1.21+)")
maxTraceBody := flag.Int("max_log_body", 1000, "maximum length of body logged in request traces (go1.21+)")
fold := flag.Bool("fold", false, "apply constant folding optimisation")
+ dumpState := flag.String("dump", "", "dump eval state ('always' or 'error')")
version := flag.Bool("version", false, "print version and exit")
flag.Parse()
if *version {
@@ -195,8 +196,14 @@ func Main() int {
}
for n := int(0); *maxExecutions < 0 || n < *maxExecutions; n++ {
- res, val, err := eval(string(b), root, input, *fold, libs...)
+ res, val, dump, err := eval(string(b), root, input, *fold, *dumpState != "", libs...)
+ if *dumpState == "always" {
+ fmt.Fprint(os.Stderr, dump)
+ }
if err != nil {
+ if *dumpState == "error" {
+ fmt.Fprint(os.Stderr, dump)
+ }
fmt.Fprintln(os.Stderr, err)
return 1
}
@@ -325,15 +332,20 @@ func debug(tag string, value any) {
fmt.Fprintf(os.Stderr, "%s: logging %q: %v\n", level, tag, value)
}
-func eval(src, root string, input interface{}, fold bool, libs ...cel.EnvOption) (string, any, error) {
- prg, ast, err := compile(src, root, fold, libs...)
+func eval(src, root string, input interface{}, fold, details bool, libs ...cel.EnvOption) (string, any, *lib.Dump, error) {
+ prg, ast, err := compile(src, root, fold, details, libs...)
if err != nil {
- return "", nil, fmt.Errorf("failed program instantiation: %v", err)
+ return "", nil, nil, fmt.Errorf("failed program instantiation: %v", err)
}
- return run(prg, ast, false, input)
+ res, val, det, err := run(prg, ast, false, input)
+ var dump *lib.Dump
+ if details {
+ dump = lib.NewDump(ast, det)
+ }
+ return res, val, dump, err
}
-func compile(src, root string, fold bool, libs ...cel.EnvOption) (cel.Program, *cel.Ast, error) {
+func compile(src, root string, fold, details bool, libs ...cel.EnvOption) (cel.Program, *cel.Ast, error) {
opts := append([]cel.EnvOption{
cel.Declarations(decls.NewVar(root, decls.Dyn)),
}, libs...)
@@ -358,40 +370,44 @@ func compile(src, root string, fold bool, libs ...cel.EnvOption) (cel.Program, *
}
}
- prg, err := env.Program(ast)
+ var progOpts []cel.ProgramOption
+ if details {
+ progOpts = []cel.ProgramOption{cel.EvalOptions(cel.OptTrackState)}
+ }
+ prg, err := env.Program(ast, progOpts...)
if err != nil {
return nil, nil, fmt.Errorf("failed program instantiation: %v", err)
}
return prg, ast, nil
}
-func run(prg cel.Program, ast *cel.Ast, fast bool, input interface{}) (string, any, error) {
+func run(prg cel.Program, ast *cel.Ast, fast bool, input interface{}) (string, any, *cel.EvalDetails, error) {
if input == nil {
input = interpreter.EmptyActivation()
}
- out, _, err := prg.Eval(input)
+ out, det, err := prg.Eval(input)
if err != nil {
- return "", nil, fmt.Errorf("failed eval: %v", lib.DecoratedError{AST: ast, Err: err})
+ return "", nil, det, fmt.Errorf("failed eval: %v", lib.DecoratedError{AST: ast, Err: err})
}
v, err := out.ConvertToNative(reflect.TypeOf(&structpb.Value{}))
if err != nil {
- return "", nil, fmt.Errorf("failed proto conversion: %v", err)
+ return "", nil, det, fmt.Errorf("failed proto conversion: %v", err)
}
val := v.(*structpb.Value).AsInterface()
if fast {
b, err := protojson.MarshalOptions{}.Marshal(v.(proto.Message))
if err != nil {
- return "", nil, fmt.Errorf("failed native conversion: %v", err)
+ return "", nil, det, fmt.Errorf("failed native conversion: %v", err)
}
- return string(b), val, nil
+ return string(b), val, det, nil
}
var buf strings.Builder
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.SetIndent("", "\t")
err = enc.Encode(val)
- return strings.TrimRight(buf.String(), "\n"), val, err
+ return strings.TrimRight(buf.String(), "\n"), val, det, err
}
// rot13 is provided for testing purposes.
diff --git a/mito_bench_test.go b/mito_bench_test.go
index ff447c5..9770eaa 100644
--- a/mito_bench_test.go
+++ b/mito_bench_test.go
@@ -46,6 +46,7 @@ var benchmarks = []struct {
`"hello world"`,
root,
fold,
+ false,
)
return prg, ast, nil, err
},
@@ -57,6 +58,7 @@ var benchmarks = []struct {
`{"greeting":"hello world"}`,
root,
fold,
+ false,
)
return prg, ast, nil, err
},
@@ -68,6 +70,7 @@ var benchmarks = []struct {
`{"a":{"b":{"c":{"d":{"e":"f"}}}}}`,
root,
fold,
+ false,
)
return prg, ast, nil, err
},
@@ -79,6 +82,7 @@ var benchmarks = []struct {
`{"a":{"b":{"c":{"d":{"e":"f"}}}}}.encode_json()`,
root,
fold,
+ false,
lib.JSON(nil),
)
return prg, ast, nil, err
@@ -91,6 +95,7 @@ var benchmarks = []struct {
`{"a":{"b":{"c":{"d":{"e":"f"}}}}}.collate("a.b.c.d.e")`,
root,
fold,
+ false,
lib.Collections(),
)
return prg, ast, nil, err
@@ -101,7 +106,7 @@ var benchmarks = []struct {
{
name: "hello_world_state",
setup: func(b *testing.B, fold bool) (cel.Program, *cel.Ast, any, error) {
- prg, ast, err := compile(root, root, fold)
+ prg, ast, err := compile(root, root, fold, false)
state := map[string]any{root: "hello world"}
return prg, ast, state, err
},
@@ -113,6 +118,7 @@ var benchmarks = []struct {
`{"greeting":state.greeting}`,
root,
fold,
+ false,
)
state := map[string]any{root: mustParseJSON(`{"greeting": "hello world}"}`)}
return prg, ast, state, err
@@ -121,7 +127,7 @@ var benchmarks = []struct {
{
name: "nested_state",
setup: func(b *testing.B, fold bool) (cel.Program, *cel.Ast, any, error) {
- prg, ast, err := compile(root, root, fold)
+ prg, ast, err := compile(root, root, fold, false)
state := map[string]any{root: mustParseJSON(`{"a":{"b":{"c":{"d":{"e":"f"}}}}}`)}
return prg, ast, state, err
},
@@ -132,6 +138,7 @@ var benchmarks = []struct {
prg, ast, err := compile(`state.encode_json()`,
root,
fold,
+ false,
lib.JSON(nil),
)
state := map[string]any{root: mustParseJSON(`{"a":{"b":{"c":{"d":{"e":"f"}}}}}`)}
@@ -151,6 +158,7 @@ var benchmarks = []struct {
prg, ast, err := compile(`[state].collate("a.b.c.d.e")`,
root,
fold,
+ false,
lib.Collections(),
)
state := map[string]any{root: mustParseJSON(`{"a":{"b":{"c":{"d":{"e":"f"}}}}}`)}
@@ -163,6 +171,7 @@ var benchmarks = []struct {
prg, ast, err := compile(`{"state": state}.collate("state.a.b.c.d.e")`,
root,
fold,
+ false,
lib.Collections(),
)
state := map[string]any{root: mustParseJSON(`{"a":{"b":{"c":{"d":{"e":"f"}}}}}`)}
@@ -185,6 +194,7 @@ var benchmarks = []struct {
fmt.Sprintf(`get(%q).size()`, srv.URL),
root,
fold,
+ false,
lib.HTTP(srv.Client(), nil, nil),
)
return prg, ast, nil, err
@@ -201,6 +211,7 @@ var benchmarks = []struct {
fmt.Sprintf(`string(get(%q).Body)`, srv.URL),
root,
fold,
+ false,
lib.HTTP(srv.Client(), nil, nil),
)
return prg, ast, nil, err
@@ -217,6 +228,7 @@ var benchmarks = []struct {
fmt.Sprintf(`{"greeting":bytes(get(%q).Body).decode_json().greeting}`, srv.URL),
root,
fold,
+ false,
lib.HTTP(srv.Client(), nil, nil),
lib.JSON(nil),
)
@@ -234,6 +246,7 @@ var benchmarks = []struct {
fmt.Sprintf(`bytes(get(%q).Body).decode_json()`, srv.URL),
root,
fold,
+ false,
lib.HTTP(srv.Client(), nil, nil),
lib.JSON(nil),
)
@@ -251,6 +264,7 @@ var benchmarks = []struct {
fmt.Sprintf(`get(%q).Body`, srv.URL),
root,
fold,
+ false,
lib.HTTP(srv.Client(), nil, nil),
lib.JSON(nil),
)
@@ -270,6 +284,7 @@ var benchmarks = []struct {
fmt.Sprintf(`bytes(get(%q).Body).decode_json().encode_json()`, srv.URL),
root,
fold,
+ false,
lib.HTTP(srv.Client(), nil, nil),
lib.JSON(nil),
)
@@ -287,6 +302,7 @@ var benchmarks = []struct {
fmt.Sprintf(`[bytes(get(%q).Body).decode_json()].collate("a.b.c.d.e")`, srv.URL),
root,
fold,
+ false,
lib.HTTP(srv.Client(), nil, nil),
lib.JSON(nil),
lib.Collections(),
@@ -305,6 +321,7 @@ var benchmarks = []struct {
fmt.Sprintf(`{"body": bytes(get(%q).Body).decode_json()}.collate("body.a.b.c.d.e")`, srv.URL),
root,
fold,
+ false,
lib.HTTP(srv.Client(), nil, nil),
lib.JSON(nil),
lib.Collections(),
@@ -332,7 +349,7 @@ func BenchmarkMito(b *testing.B) {
b.StartTimer()
for i := 0; i < b.N; i++ {
- v, _, err := run(prg, ast, *fastMarshal, state)
+ v, _, _, err := run(prg, ast, *fastMarshal, state)
if err != nil {
b.Fatalf("failed operation: %v", err)
}
diff --git a/mito_test.go b/mito_test.go
index 5484309..d39dd11 100644
--- a/mito_test.go
+++ b/mito_test.go
@@ -150,7 +150,7 @@ func TestSend(t *testing.T) {
got = <-chans["ch"]
}()
- res, _, err := eval(`42.send_to("ch").close("ch")`, "", nil, fold, send)
+ res, _, _, err := eval(`42.send_to("ch").close("ch")`, "", nil, fold, false, send)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@@ -257,7 +257,7 @@ func TestVars(t *testing.T) {
name = "folded"
}
t.Run(name, func(t *testing.T) {
- got, _, err := eval(src, "", interpreter.EmptyActivation(), fold, vars)
+ got, _, _, err := eval(src, "", interpreter.EmptyActivation(), fold, false, vars)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@@ -381,7 +381,7 @@ func TestRegaxp(t *testing.T) {
name = "folded"
}
t.Run(name, func(t *testing.T) {
- got, _, err := eval(test.src, "", interpreter.EmptyActivation(), fold, lib.Regexp(test.regexps))
+ got, _, _, err := eval(test.src, "", interpreter.EmptyActivation(), fold, false, lib.Regexp(test.regexps))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
diff --git a/testdata/as_dump.txt b/testdata/as_dump.txt
new file mode 100644
index 0000000..440ebee
--- /dev/null
+++ b/testdata/as_dump.txt
@@ -0,0 +1,55 @@
+mito -dump always -use collections src.cel
+cmp stdout want.txt
+cmp stderr want_err.txt
+
+-- src.cel --
+{"a":1}.as(v, [v, v])
+-- want.txt --
+[
+ {
+ "a": 1
+ },
+ {
+ "a": 1
+ }
+]
+-- want_err.txt --
+:1:1
+ | {"a":1}.as(v, [v, v])
+ | ^
+{a: 1}
+
+:1:2
+ | {"a":1}.as(v, [v, v])
+ | .^
+a
+
+:1:6
+ | {"a":1}.as(v, [v, v])
+ | .....^
+1
+
+:1:11
+ | {"a":1}.as(v, [v, v])
+ | ..........^
+[]
+
+:1:11
+ | {"a":1}.as(v, [v, v])
+ | ..........^
+[{a: 1}, {a: 1}]
+
+:1:15
+ | {"a":1}.as(v, [v, v])
+ | ..............^
+[{a: 1}, {a: 1}]
+
+:1:16
+ | {"a":1}.as(v, [v, v])
+ | ...............^
+{a: 1}
+
+:1:19
+ | {"a":1}.as(v, [v, v])
+ | ..................^
+{a: 1}
diff --git a/testdata/optional_types.txt b/testdata/optional_types.txt
index 699842b..6cfc516 100644
--- a/testdata/optional_types.txt
+++ b/testdata/optional_types.txt
@@ -1,6 +1,6 @@
-mito -data state.json src.cel
-! stderr .
+mito -dump always -data state.json src.cel
cmp stdout want.txt
+cmp stderr want_err.txt
-- state.json --
{"n": 0}
@@ -12,3 +12,18 @@ cmp stdout want.txt
{
"has_x_y_z": false
}
+-- want_err.txt --
+:1:1
+ | {
+ | ^
+{has_x_y_z: false}
+
+:2:2
+ | "has_x_y_z": has(state.?x.?y.z),
+ | .^
+has_x_y_z
+
+:2:18
+ | "has_x_y_z": has(state.?x.?y.z),
+ | .................^
+false
diff --git a/testdata/post.txt b/testdata/post.txt
index 5612223..e98e9bc 100644
--- a/testdata/post.txt
+++ b/testdata/post.txt
@@ -1,9 +1,13 @@
+serve hello.text
+expand src_var.cel src.cel
+cmpenv src.cel src_var.cel
+
mito -use http,collections src.cel
! stderr .
cmp stdout want.txt
--- src.cel --
-post("http://www.example.com/", "text/plain", "test").drop([
+-- src_var.cel --
+post("${URL}", "text/plain", "test").drop([
"Header.Accept-Ranges",
"Header.Age",
"Header.Date",
@@ -15,18 +19,22 @@ post("http://www.example.com/", "text/plain", "test").drop([
"Header.Expires",
"Header.Server",
"TransferEncoding",
+ "Request.Host",
+ "Request.URL",
])
+-- hello.text --
+hello
-- want.txt --
{
- "Body": "PCFkb2N0eXBlIGh0bWw+CjxodG1sPgo8aGVhZD4KICAgIDx0aXRsZT5FeGFtcGxlIERvbWFpbjwvdGl0bGU+CgogICAgPG1ldGEgY2hhcnNldD0idXRmLTgiIC8+CiAgICA8bWV0YSBodHRwLWVxdWl2PSJDb250ZW50LXR5cGUiIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD11dGYtOCIgLz4KICAgIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MSIgLz4KICAgIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CiAgICBib2R5IHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjBmMGYyOwogICAgICAgIG1hcmdpbjogMDsKICAgICAgICBwYWRkaW5nOiAwOwogICAgICAgIGZvbnQtZmFtaWx5OiAtYXBwbGUtc3lzdGVtLCBzeXN0ZW0tdWksIEJsaW5rTWFjU3lzdGVtRm9udCwgIlNlZ29lIFVJIiwgIk9wZW4gU2FucyIsICJIZWx2ZXRpY2EgTmV1ZSIsIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7CiAgICAgICAgCiAgICB9CiAgICBkaXYgewogICAgICAgIHdpZHRoOiA2MDBweDsKICAgICAgICBtYXJnaW46IDVlbSBhdXRvOwogICAgICAgIHBhZGRpbmc6IDJlbTsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZmRmZGZmOwogICAgICAgIGJvcmRlci1yYWRpdXM6IDAuNWVtOwogICAgICAgIGJveC1zaGFkb3c6IDJweCAzcHggN3B4IDJweCByZ2JhKDAsMCwwLDAuMDIpOwogICAgfQogICAgYTpsaW5rLCBhOnZpc2l0ZWQgewogICAgICAgIGNvbG9yOiAjMzg0ODhmOwogICAgICAgIHRleHQtZGVjb3JhdGlvbjogbm9uZTsKICAgIH0KICAgIEBtZWRpYSAobWF4LXdpZHRoOiA3MDBweCkgewogICAgICAgIGRpdiB7CiAgICAgICAgICAgIG1hcmdpbjogMCBhdXRvOwogICAgICAgICAgICB3aWR0aDogYXV0bzsKICAgICAgICB9CiAgICB9CiAgICA8L3N0eWxlPiAgICAKPC9oZWFkPgoKPGJvZHk+CjxkaXY+CiAgICA8aDE+RXhhbXBsZSBEb21haW48L2gxPgogICAgPHA+VGhpcyBkb21haW4gaXMgZm9yIHVzZSBpbiBpbGx1c3RyYXRpdmUgZXhhbXBsZXMgaW4gZG9jdW1lbnRzLiBZb3UgbWF5IHVzZSB0aGlzCiAgICBkb21haW4gaW4gbGl0ZXJhdHVyZSB3aXRob3V0IHByaW9yIGNvb3JkaW5hdGlvbiBvciBhc2tpbmcgZm9yIHBlcm1pc3Npb24uPC9wPgogICAgPHA+PGEgaHJlZj0iaHR0cHM6Ly93d3cuaWFuYS5vcmcvZG9tYWlucy9leGFtcGxlIj5Nb3JlIGluZm9ybWF0aW9uLi4uPC9hPjwvcD4KPC9kaXY+CjwvYm9keT4KPC9odG1sPgo=",
+ "Body": "aGVsbG8K",
"Close": false,
- "ContentLength": 1256,
+ "ContentLength": 6,
"Header": {
"Content-Length": [
- "1256"
+ "6"
],
"Content-Type": [
- "text/html; charset=UTF-8"
+ "text/plain; charset=utf-8"
]
},
"Proto": "HTTP/1.1",
@@ -40,12 +48,10 @@ post("http://www.example.com/", "text/plain", "test").drop([
"text/plain"
]
},
- "Host": "www.example.com",
"Method": "POST",
"Proto": "HTTP/1.1",
"ProtoMajor": 1,
- "ProtoMinor": 1,
- "URL": "http://www.example.com/"
+ "ProtoMinor": 1
},
"Status": "200 OK",
"StatusCode": 200,
diff --git a/testdata/request_do.txt b/testdata/request_do.txt
index 38da383..f7a1e6a 100644
--- a/testdata/request_do.txt
+++ b/testdata/request_do.txt
@@ -1,30 +1,35 @@
+serve hello.text
+expand src_var.cel src.cel
+cmpenv src.cel src_var.cel
+
mito -use http,collections src.cel
! stderr .
cmp stdout want.txt
--- src.cel --
-post_request("http://www.example.com/", "text/plain", "request data").do_request().drop([
+-- src_var.cel --
+post_request("${URL}", "text/plain", "request data").do_request().drop([
"Header.Cache-Control",
"Header.Date",
"Header.Etag",
"Header.Expires",
"Header.Last-Modified",
"Header.Server",
+ "Request.Host",
+ "Request.URL",
])
+-- hello.text --
+hello
-- want.txt --
{
- "Body": "PCFkb2N0eXBlIGh0bWw+CjxodG1sPgo8aGVhZD4KICAgIDx0aXRsZT5FeGFtcGxlIERvbWFpbjwvdGl0bGU+CgogICAgPG1ldGEgY2hhcnNldD0idXRmLTgiIC8+CiAgICA8bWV0YSBodHRwLWVxdWl2PSJDb250ZW50LXR5cGUiIGNvbnRlbnQ9InRleHQvaHRtbDsgY2hhcnNldD11dGYtOCIgLz4KICAgIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MSIgLz4KICAgIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CiAgICBib2R5IHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjBmMGYyOwogICAgICAgIG1hcmdpbjogMDsKICAgICAgICBwYWRkaW5nOiAwOwogICAgICAgIGZvbnQtZmFtaWx5OiAtYXBwbGUtc3lzdGVtLCBzeXN0ZW0tdWksIEJsaW5rTWFjU3lzdGVtRm9udCwgIlNlZ29lIFVJIiwgIk9wZW4gU2FucyIsICJIZWx2ZXRpY2EgTmV1ZSIsIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7CiAgICAgICAgCiAgICB9CiAgICBkaXYgewogICAgICAgIHdpZHRoOiA2MDBweDsKICAgICAgICBtYXJnaW46IDVlbSBhdXRvOwogICAgICAgIHBhZGRpbmc6IDJlbTsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZmRmZGZmOwogICAgICAgIGJvcmRlci1yYWRpdXM6IDAuNWVtOwogICAgICAgIGJveC1zaGFkb3c6IDJweCAzcHggN3B4IDJweCByZ2JhKDAsMCwwLDAuMDIpOwogICAgfQogICAgYTpsaW5rLCBhOnZpc2l0ZWQgewogICAgICAgIGNvbG9yOiAjMzg0ODhmOwogICAgICAgIHRleHQtZGVjb3JhdGlvbjogbm9uZTsKICAgIH0KICAgIEBtZWRpYSAobWF4LXdpZHRoOiA3MDBweCkgewogICAgICAgIGRpdiB7CiAgICAgICAgICAgIG1hcmdpbjogMCBhdXRvOwogICAgICAgICAgICB3aWR0aDogYXV0bzsKICAgICAgICB9CiAgICB9CiAgICA8L3N0eWxlPiAgICAKPC9oZWFkPgoKPGJvZHk+CjxkaXY+CiAgICA8aDE+RXhhbXBsZSBEb21haW48L2gxPgogICAgPHA+VGhpcyBkb21haW4gaXMgZm9yIHVzZSBpbiBpbGx1c3RyYXRpdmUgZXhhbXBsZXMgaW4gZG9jdW1lbnRzLiBZb3UgbWF5IHVzZSB0aGlzCiAgICBkb21haW4gaW4gbGl0ZXJhdHVyZSB3aXRob3V0IHByaW9yIGNvb3JkaW5hdGlvbiBvciBhc2tpbmcgZm9yIHBlcm1pc3Npb24uPC9wPgogICAgPHA+PGEgaHJlZj0iaHR0cHM6Ly93d3cuaWFuYS5vcmcvZG9tYWlucy9leGFtcGxlIj5Nb3JlIGluZm9ybWF0aW9uLi4uPC9hPjwvcD4KPC9kaXY+CjwvYm9keT4KPC9odG1sPgo=",
+ "Body": "aGVsbG8K",
"Close": false,
- "ContentLength": 1256,
+ "ContentLength": 6,
"Header": {
- "Accept-Ranges": [
- "bytes"
- ],
"Content-Length": [
- "1256"
+ "6"
],
"Content-Type": [
- "text/html; charset=UTF-8"
+ "text/plain; charset=utf-8"
]
},
"Proto": "HTTP/1.1",
@@ -38,12 +43,10 @@ post_request("http://www.example.com/", "text/plain", "request data").do_request
"text/plain"
]
},
- "Host": "www.example.com",
"Method": "POST",
"Proto": "HTTP/1.1",
"ProtoMajor": 1,
- "ProtoMinor": 1,
- "URL": "http://www.example.com/"
+ "ProtoMinor": 1
},
"Status": "200 OK",
"StatusCode": 200,