Skip to content

Commit 0fd6783

Browse files
Implement the uuid builtin and mock for tests
1 parent 0a40192 commit 0fd6783

File tree

12 files changed

+172
-10
lines changed

12 files changed

+172
-10
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.25.5
55
require (
66
github.com/BurntSushi/toml v1.5.0
77
github.com/charmbracelet/huh v0.8.0
8+
github.com/google/uuid v1.6.0
89
github.com/rogpeppe/go-internal v1.14.1
910
go.followtheprocess.codes/cli v0.18.2
1011
go.followtheprocess.codes/hue v1.0.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
5252
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
5353
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
5454
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
55+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
56+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
5557
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
5658
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
5759
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=

internal/syntax/ast/expression.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (i Ident) expressionNode() {}
4848
// The Builtin AST node is functionality identical to an [Ident], but must
4949
// be separate to allow differentiating builtins from regular idents.
5050
type Builtin struct {
51-
// Name is the ident's name.
51+
// Name is the name of the builtin ident.
5252
Name string `yaml:"name"`
5353

5454
// Dollar is the [token.Dollar] token marking
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Package builtins provides the implementation of various builtin identifiers and functions
2+
// to the resolver.
3+
package builtins
4+
5+
import (
6+
"fmt"
7+
8+
"github.com/google/uuid"
9+
)
10+
11+
// Builtin is an implementation of a zap builtin.
12+
type Builtin func() (string, error)
13+
14+
// Library is a library of builtins.
15+
type Library interface {
16+
// Get looks up a builtin from the library by name, returning it (or nil)
17+
// and a boolean indicating its existence.
18+
Get(name string) (Builtin, bool)
19+
}
20+
21+
// Builtins is a [Library] containing the builtin implementations.
22+
type Builtins struct {
23+
library map[string]Builtin
24+
}
25+
26+
// NewLibrary returns the zap builtins library.
27+
func NewLibrary() Builtins {
28+
library := map[string]Builtin{
29+
"uuid": builtinUUID,
30+
}
31+
32+
return Builtins{
33+
library: library,
34+
}
35+
}
36+
37+
// Get looks up a builtin by name, returning the builtin and a boolean
38+
// indicating its existence.
39+
func (b Builtins) Get(name string) (Builtin, bool) {
40+
fn, ok := b.library[name]
41+
if !ok {
42+
return nil, false
43+
}
44+
45+
return fn, true
46+
}
47+
48+
// builtinUUID is the implementation of the '$uuid' builtin.
49+
func builtinUUID() (string, error) {
50+
uid, err := uuid.NewRandom()
51+
if err != nil {
52+
return "", fmt.Errorf("failed to generate a new uuid: %w", err)
53+
}
54+
55+
return uid.String(), nil
56+
}

internal/syntax/resolver/resolver.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"go.followtheprocess.codes/zap/internal/spec"
1717
"go.followtheprocess.codes/zap/internal/syntax"
1818
"go.followtheprocess.codes/zap/internal/syntax/ast"
19+
"go.followtheprocess.codes/zap/internal/syntax/resolver/builtins"
1920
"go.followtheprocess.codes/zap/internal/syntax/token"
2021
)
2122

@@ -43,17 +44,19 @@ var ErrResolve = errors.New("resolve error")
4344
// parsing URLs and durations, and otherwise validating and checking the parse tree
4445
// along the way.
4546
type Resolver struct {
47+
library builtins.Library // Library of builtins to draw from.
4648
name string // The name of the file being resolved.
4749
src []byte // Raw source
4850
diagnostics []syntax.Diagnostic // Diagnostics collected during resolving.
4951
hadErrors bool // Whether we encountered resolver errors.
5052
}
5153

5254
// New returns a new [Resolver].
53-
func New(name string, src []byte) *Resolver {
55+
func New(name string, src []byte, library builtins.Library) *Resolver {
5456
return &Resolver{
55-
name: name,
56-
src: src,
57+
name: name,
58+
src: src,
59+
library: library,
5760
}
5861
}
5962

@@ -432,6 +435,8 @@ func (r *Resolver) resolveExpression(env *environment, expression ast.Expression
432435
return expr.Value, nil
433436
case ast.Ident:
434437
return r.resolveIdent(env, expr)
438+
case ast.Builtin:
439+
return r.resolveBuiltin(expr)
435440
case ast.InterpolatedExpression:
436441
return r.resolveInterpolatedExpression(env, expr)
437442
case ast.Interp:
@@ -651,3 +656,16 @@ func (r *Resolver) resolveIdent(env *environment, ident ast.Ident) (string, erro
651656

652657
return env.get(ident.Name)
653658
}
659+
660+
// resolveBuiltin resolves an [ast.Builtin] into the concrete value it
661+
// refers to.
662+
//
663+
// Note: no environment here as builtins are well... built in.
664+
func (r *Resolver) resolveBuiltin(b ast.Builtin) (string, error) {
665+
fn, ok := r.library.Get(b.Name)
666+
if !ok {
667+
return "", fmt.Errorf("no such builtin: %q", b.Name)
668+
}
669+
670+
return fn()
671+
}

internal/syntax/resolver/resolver_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"go.followtheprocess.codes/zap/internal/spec"
1616
"go.followtheprocess.codes/zap/internal/syntax/parser"
1717
"go.followtheprocess.codes/zap/internal/syntax/resolver"
18+
"go.followtheprocess.codes/zap/internal/syntax/syntaxtest"
1819
"go.uber.org/goleak"
1920
"go.yaml.in/yaml/v4"
2021
)
@@ -49,7 +50,7 @@ func TestValid(t *testing.T) {
4950
t.Logf("Diagnostics: %+v\n", p.Diagnostics())
5051
test.Ok(t, err, test.Context("unexpected parser error"))
5152

52-
res := resolver.New(name, []byte(src))
53+
res := resolver.New(name, []byte(src), syntaxtest.NewTestLibrary())
5354

5455
resolved, err := res.Resolve(parsed)
5556
if err != nil {
@@ -111,7 +112,7 @@ func TestInvalid(t *testing.T) {
111112
parsed, err := p.Parse()
112113
test.Ok(t, err, test.Context("unexpected parser error"))
113114

114-
res := resolver.New(name, []byte(src))
115+
res := resolver.New(name, []byte(src), syntaxtest.NewTestLibrary())
115116

116117
_, err = res.Resolve(parsed)
117118
test.Err(t, err, test.Context("resolved did not return an error but should have"))
@@ -153,7 +154,7 @@ func BenchmarkResolver(b *testing.B) {
153154
test.Ok(b, err)
154155

155156
for b.Loop() {
156-
res := resolver.New(file, []byte(src))
157+
res := resolver.New(file, []byte(src), syntaxtest.NewTestLibrary())
157158
_, err = res.Resolve(parsed)
158159
test.Ok(b, err)
159160
}
@@ -196,7 +197,7 @@ func FuzzResolver(f *testing.F) {
196197

197198
parsed, _ := parser.Parse() //nolint:errcheck // Just checking for panics and infinite loops
198199

199-
res := resolver.New(parsed.Name, []byte(src))
200+
res := resolver.New(parsed.Name, []byte(src), syntaxtest.NewTestLibrary())
200201

201202
resolved, err := res.Resolve(parsed)
202203
// Property: If there is an error, the file should be the zero spec.File{}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- src.http --
2+
###
3+
POST https://example.com/123
4+
Content-Type: application/json
5+
6+
{
7+
"id": "{{ $uuid }}"
8+
}
9+
-- want.yaml --
10+
name: interpolation-builtin-body.txtar
11+
requests:
12+
- headers:
13+
Content-Type:
14+
- application/json
15+
name: '#1'
16+
method: POST
17+
url: https://example.com/123
18+
body: |-
19+
{
20+
"id": "d0a43b68-b9a1-4e89-bd21-b06fc59fefb5"
21+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- src.http --
2+
@id = {{ $uuid }}
3+
4+
### Test
5+
GET https://example.com/{{ id }}
6+
-- want.yaml --
7+
name: interpolation-builtin-global.txtar
8+
vars:
9+
id: d0a43b68-b9a1-4e89-bd21-b06fc59fefb5
10+
requests:
11+
- name: '#1'
12+
comment: Test
13+
method: GET
14+
url: https://example.com/d0a43b68-b9a1-4e89-bd21-b06fc59fefb5
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- src.http --
2+
### Test
3+
GET https://example.com/{{ $uuid }}
4+
-- want.yaml --
5+
name: interpolation-builtin-inline.txtar
6+
requests:
7+
- name: '#1'
8+
comment: Test
9+
method: GET
10+
url: https://example.com/d0a43b68-b9a1-4e89-bd21-b06fc59fefb5

internal/syntax/syntax_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"go.followtheprocess.codes/zap/internal/syntax"
1111
"go.followtheprocess.codes/zap/internal/syntax/parser"
1212
"go.followtheprocess.codes/zap/internal/syntax/resolver"
13+
"go.followtheprocess.codes/zap/internal/syntax/syntaxtest"
1314
)
1415

1516
func TestPositionString(t *testing.T) {
@@ -306,7 +307,7 @@ func BenchmarkEntireParse(b *testing.B) {
306307
parsed, err := p.Parse()
307308
test.Ok(b, err)
308309

309-
res := resolver.New(file, src)
310+
res := resolver.New(file, src, syntaxtest.NewTestLibrary())
310311
_, err = res.Resolve(parsed)
311312
test.Ok(b, err)
312313
}

0 commit comments

Comments
 (0)