Skip to content

Commit 185b56e

Browse files
committed
Initial commit
0 parents  commit 185b56e

File tree

7 files changed

+281
-0
lines changed

7 files changed

+281
-0
lines changed

.github/dependabot.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "gomod"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"
7+
- package-ecosystem: "github-actions"
8+
directory: "/"
9+
schedule:
10+
interval: "weekly"

.github/workflows/gotest.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: GoTests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
test-go:
15+
runs-on: ubuntu-latest
16+
timeout-minutes: 5
17+
steps:
18+
- uses: actions/checkout@v4.2.2
19+
- uses: actions/setup-go@v2
20+
with:
21+
go-version: 1.22.8
22+
- name: Install gotestsum
23+
shell: bash
24+
run: go install gotest.tools/gotestsum@latest
25+
- name: Run tests
26+
run: gotestsum ./...

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea

go.mod

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module github.com/coder/preview
2+
3+
go 1.23.5
4+
5+
require (
6+
github.com/stretchr/testify v1.10.0
7+
github.com/zclconf/go-cty v1.16.2
8+
)
9+
10+
require (
11+
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
12+
github.com/davecgh/go-spew v1.1.1 // indirect
13+
github.com/pmezard/go-difflib v1.0.0 // indirect
14+
golang.org/x/text v0.11.0 // indirect
15+
gopkg.in/yaml.v3 v3.0.1 // indirect
16+
)

go.sum

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
2+
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
3+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
6+
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
7+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
8+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
10+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
11+
github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70=
12+
github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
13+
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
14+
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
15+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
16+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
17+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
18+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

types/parameter.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package types
2+
3+
import (
4+
"crypto/sha256"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/zclconf/go-cty/cty"
9+
)
10+
11+
type RichParameter struct {
12+
Name string `json:"name"`
13+
Description string `json:"description"`
14+
Type string `json:"type"`
15+
Mutable bool `json:"mutable"`
16+
DefaultValue string `json:"default_value"`
17+
Icon string `json:"icon"`
18+
Options []*RichParameterOption `json:"options"`
19+
Validation *ParameterValidation `json:"validation"`
20+
Required bool `json:"required"`
21+
// legacy_variable_name was removed (= 14)
22+
DisplayName string `json:"display_name"`
23+
Order int32 `json:"order"`
24+
Ephemeral bool `json:"ephemeral"`
25+
}
26+
27+
type ParameterValidation struct {
28+
Regex string `json:"validation_regex"`
29+
Error string `json:"validation_error"`
30+
Min *int32 `json:"validation_min"`
31+
Max *int32 `json:"validation_max"`
32+
Monotonic string `json:"validation_monotonic"`
33+
}
34+
35+
type RichParameterOption struct {
36+
Name string `json:"name,omitempty"`
37+
Description string `json:"description,omitempty"`
38+
Value string `json:"value,omitempty"`
39+
Icon string `json:"icon,omitempty"`
40+
}
41+
42+
// Hash can be used to compare two RichParameter objects at a glance.
43+
func (r *RichParameter) Hash() ([32]byte, error) {
44+
// Option order matters, so just json marshal the whole thing.
45+
data, err := json.Marshal(r)
46+
if err != nil {
47+
return [32]byte{}, fmt.Errorf("marshal: %w", err)
48+
}
49+
50+
return sha256.Sum256(data), nil
51+
}
52+
53+
// CtyType returns the cty.Type for the RichParameter.
54+
// A fixed set of types are supported.
55+
func (r *RichParameter) CtyType() (cty.Type, error) {
56+
switch r.Type {
57+
case "string":
58+
return cty.String, nil
59+
case "number":
60+
return cty.Number, nil
61+
case "bool":
62+
return cty.Bool, nil
63+
case "list(string)":
64+
return cty.List(cty.String), nil
65+
default:
66+
return cty.Type{}, fmt.Errorf("unsupported type: %q", r.Type)
67+
}
68+
}

types/parameter_test.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package types_test
2+
3+
import (
4+
"crypto/sha256"
5+
"fmt"
6+
"math/rand/v2"
7+
"strings"
8+
"testing"
9+
10+
"github.com/stretchr/testify/require"
11+
12+
"github.com/coder/preview/types"
13+
)
14+
15+
// TestParameterEquality might be a bit pointless. It just ensures the
16+
// Hash function returns a consistent value for the same input.
17+
// TODO: Remove this, just wanted to create some random parameters
18+
func TestParameterEquality(t *testing.T) {
19+
t.Parallel()
20+
21+
for i := 0; i < 100; i++ {
22+
t.Run(fmt.Sprintf("EqualityCheck_%d", i), func(t *testing.T) {
23+
t.Parallel()
24+
seed := sha256.Sum256([]byte(t.Name()))
25+
src := rand.NewChaCha8(seed)
26+
27+
param := randomParameter(src)
28+
a, err := param.Hash()
29+
require.NoError(t, err)
30+
31+
b, err := param.Hash()
32+
require.NoError(t, err)
33+
34+
require.Equal(t, a, b)
35+
})
36+
}
37+
}
38+
39+
func randomParameter(src *rand.ChaCha8) *types.RichParameter {
40+
ty := randomElement(src, "string", "number", "bool", "list(string)")
41+
opts := make([]*types.RichParameterOption, randomInt(src, 0, 5))
42+
for i := range opts {
43+
opts[i] = randomParameterOption(src, ty)
44+
}
45+
46+
return &types.RichParameter{
47+
Name: randomString(src, 20),
48+
Description: randomString(src, 20),
49+
Type: ty,
50+
Mutable: randomElement(src, true, false),
51+
DefaultValue: randomValue(src, ty),
52+
Icon: randomString(src, 10),
53+
Options: opts,
54+
Validation: randomValidation(src, ty),
55+
Required: randomElement(src, true, false),
56+
DisplayName: randomString(src, 10),
57+
Order: int32(randomInt(src, 0, 10)),
58+
Ephemeral: randomElement(src, true, false),
59+
}
60+
}
61+
62+
func randomValidation(src *rand.ChaCha8, ty string) *types.ParameterValidation {
63+
var minVal, maxVal *int32
64+
mono := ""
65+
if ty == "number" {
66+
mv := int32(randomInt(src, 0, 10))
67+
mxv := int32(randomInt(src, uint64(mv), uint64(10+mv)))
68+
minVal = &mv
69+
maxVal = &mxv
70+
mono = randomElement(src, "", "increasing", "decreasing")
71+
}
72+
73+
return &types.ParameterValidation{
74+
Regex: randomString(src, 10),
75+
Error: randomString(src, 10),
76+
Min: minVal,
77+
Max: maxVal,
78+
Monotonic: mono,
79+
}
80+
}
81+
82+
func randomParameterOption(src *rand.ChaCha8, ty string) *types.RichParameterOption {
83+
return &types.RichParameterOption{
84+
Name: randomString(src, 10),
85+
Description: randomString(src, 20),
86+
Value: randomValue(src, ty),
87+
Icon: randomString(src, 10),
88+
}
89+
}
90+
91+
func randomValue(src *rand.ChaCha8, ty string) string {
92+
switch ty {
93+
case "string":
94+
return randomString(src, 20)
95+
case "number":
96+
return fmt.Sprintf("%d", randomInt(src, 0, 100))
97+
case "bool":
98+
return fmt.Sprintf("%t", randomElement(src, true, false))
99+
case "list(string)":
100+
elems := make([]string, randomInt(src, 0, 5))
101+
for i := range elems {
102+
elems[i] = fmt.Sprintf("%q", randomString(src, 7))
103+
}
104+
return fmt.Sprintf("[%s]", strings.Join(elems, ", "))
105+
}
106+
panic(fmt.Sprintf("unsupported type: %s", ty))
107+
}
108+
109+
func randomInt(src *rand.ChaCha8, min, max uint64) int64 {
110+
v := src.Uint64() % max
111+
return int64(v) + int64(min)
112+
}
113+
114+
func randomElement[T any](src *rand.ChaCha8, elements ...T) T {
115+
if len(elements) > 255 {
116+
panic(fmt.Sprintf("randomElement only supports 255 elements, got %d", len(elements)))
117+
}
118+
b := make([]byte, 1)
119+
_, err := src.Read(b)
120+
if err != nil {
121+
panic(fmt.Errorf("rand.Read: %w", err))
122+
}
123+
return elements[int(b[0])%len(elements)]
124+
}
125+
126+
func randomString(src *rand.ChaCha8, length int) string {
127+
out := make([]byte, length)
128+
_, err := src.Read(out)
129+
if err != nil {
130+
panic(fmt.Errorf("rand.Read: %w", err))
131+
}
132+
133+
for i := range out {
134+
out[i] = out[i] % (26 * 2)
135+
if out[i] >= 26 {
136+
out[i] += 'A' - 26 // subtract off the 0-26 lowercase
137+
} else {
138+
out[i] += 'a'
139+
}
140+
}
141+
return string(out)
142+
}

0 commit comments

Comments
 (0)