Skip to content

Commit e834067

Browse files
authored
feat: announcements support (#501)
* feat: add announcement banner component * feat: read announcement from env * chore: add announcement docs * fix: prettier * fix: hide banner on close * fix: add info banner styles
1 parent f4d67dd commit e834067

File tree

28 files changed

+564
-40
lines changed

28 files changed

+564
-40
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ cover:
5353
@cat tools/cover.txt | xargs go test -v -covermode=count -coverprofile=/tmp/cover.out && \
5454
go tool cover -html=/tmp/cover.out
5555

56+
.PHONY:announcement
57+
announcement:
58+
@go run ./cmd/announcements -f "$(SRC_FILE)"
59+
5660
.PHONY: install
5761
install:
5862
@if [ ! -d "./target" ]; then echo "ERROR: Please build project first by calling 'make'." && exit 2; fi

cmd/announcements/main.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"flag"
7+
"fmt"
8+
"log"
9+
"os"
10+
11+
"github.com/x1unix/go-playground/internal/announcements"
12+
)
13+
14+
type flagValues struct {
15+
isDecode bool
16+
fileName string
17+
}
18+
19+
func main() {
20+
vals := &flagValues{}
21+
flag.BoolVar(&vals.isDecode, "d", false, "Decode a given announcement file")
22+
flag.StringVar(&vals.fileName, "f", "", "File name")
23+
flag.Parse()
24+
25+
if err := mainErr(vals); err != nil {
26+
log.Fatalln(err)
27+
}
28+
}
29+
30+
func mainErr(vals *flagValues) error {
31+
if vals.fileName == "" {
32+
return errors.New("missing file name")
33+
}
34+
35+
data, err := os.ReadFile(vals.fileName)
36+
if err != nil {
37+
return fmt.Errorf("can't open input file: %w", err)
38+
}
39+
40+
if vals.isDecode {
41+
out, err := announcements.DecodeFromBase64(string(data))
42+
if err != nil {
43+
return err
44+
}
45+
46+
enc := json.NewEncoder(os.Stdout)
47+
enc.SetIndent("", " ")
48+
return enc.Encode(out)
49+
}
50+
51+
v := &announcements.Announcement{}
52+
if err := json.Unmarshal(data, v); err != nil {
53+
return fmt.Errorf("can't parse input announcement from JSON: %w", err)
54+
}
55+
56+
if err := v.Validate(); err != nil {
57+
return err
58+
}
59+
60+
out, err := announcements.Encode(v)
61+
if err != nil {
62+
return err
63+
}
64+
65+
_, _ = fmt.Fprint(os.Stdout, out)
66+
return nil
67+
}

cmd/playground/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,10 @@ func start(logger *zap.Logger, cfg *config.Config) error {
8686
// Initialize API endpoints
8787
r := mux.NewRouter()
8888
apiRouter := r.PathPrefix("/api").Subrouter()
89-
svcCfg := server.ServiceConfig{Version: Version}
89+
svcCfg := server.ServiceConfig{
90+
Version: Version,
91+
Announcement: cfg.Misc.Announcement.Value,
92+
}
9093
server.NewAPIv1Handler(svcCfg, playgroundClient, buildSvc, backendsInfoSvc).
9194
Mount(apiRouter)
9295

docs/deployment/docker/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,20 @@ Playground server can be configured using environment variables described below.
3636
| `HTTP_READ_TIMEOUT` | `15s` | HTTP request read timeout. |
3737
| `HTTP_WRITE_TIMEOUT` | `60s` | HTTP response timeout. |
3838
| `HTTP_IDLE_TIMEOUT` | `90s` | HTTP keep alive timeout. |
39+
| `SERVER_ANNOUNCEMENT` | | Server announcement message to be displayed on top of page. See [Announcements](#announcements) |
40+
41+
#### Announcements
42+
43+
Service supports displaying service announcements on a top of a page.\
44+
This is used to notify users about news suchs as planned server downtime or other important information.
45+
46+
Announcement file is a base64-encoded msgpack string passed into `SERVER_ANNOUNCEMENT` environment variable.
47+
48+
##### Encode announcement
49+
50+
Use [this tool](../../../cmd/announcements/main.go) to encode a JSON file into a encoded announcement string.
51+
52+
Announcement schema can be found [here](../../../internal/announcements/types.go).
3953

4054
## Building custom image
4155

go.mod

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@ require (
88
github.com/TheZeroSlave/zapsentry v1.10.0
99
github.com/avast/retry-go v3.0.0+incompatible
1010
github.com/gorilla/mux v1.7.3
11+
github.com/hashicorp/go-set/v3 v3.0.0
1112
github.com/kelseyhightower/envconfig v1.4.0
1213
github.com/pkg/errors v0.8.1
1314
github.com/samber/lo v1.38.1
15+
github.com/spf13/cobra v1.8.1
1416
github.com/stretchr/testify v1.8.2
1517
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5
16-
github.com/traefik/yaegi v0.15.1
18+
github.com/vmihailenco/msgpack/v5 v5.4.1
1719
github.com/x1unix/foundation v1.0.0
20+
go.uber.org/automaxprocs v1.6.0
1821
go.uber.org/mock v0.4.0
1922
go.uber.org/zap v1.21.0
2023
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
21-
golang.org/x/mod v0.14.0
2224
golang.org/x/sync v0.7.0
2325
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
2426
)
@@ -27,16 +29,12 @@ require (
2729
github.com/benbjohnson/clock v1.1.0 // indirect
2830
github.com/davecgh/go-spew v1.1.1 // indirect
2931
github.com/getsentry/sentry-go v0.13.0 // indirect
30-
github.com/hashicorp/go-set/v3 v3.0.0 // indirect
3132
github.com/inconshreveable/mousetrap v1.1.0 // indirect
3233
github.com/pmezard/go-difflib v1.0.0 // indirect
33-
github.com/spf13/cobra v1.8.1 // indirect
3434
github.com/spf13/pflag v1.0.5 // indirect
35+
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
3536
go.uber.org/atomic v1.9.0 // indirect
36-
go.uber.org/automaxprocs v1.6.0 // indirect
3737
go.uber.org/multierr v1.8.0 // indirect
3838
golang.org/x/sys v0.21.0 // indirect
39-
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
40-
google.golang.org/protobuf v1.34.2 // indirect
4139
gopkg.in/yaml.v3 v3.0.1 // indirect
4240
)

go.sum

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
1111
github.com/getsentry/sentry-go v0.13.0 h1:20dgTiUSfxRB/EhMPtxcL9ZEbM1ZdR+W/7f7NWD+xWo=
1212
github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0=
1313
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
14-
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
14+
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
15+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
16+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1517
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
1618
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
1719
github.com/hashicorp/go-set/v3 v3.0.0 h1:CaJBQvQCOWoftrBcDt7Nwgo0kdpmrKxar/x2o6pV9JA=
@@ -26,13 +28,18 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
2628
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
2729
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
2830
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
31+
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
2932
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
3033
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
3134
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
3235
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
36+
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
37+
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
3338
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
3439
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
3540
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
41+
github.com/shoenig/test v1.11.0 h1:NoPa5GIoBwuqzIviCrnUJa+t5Xb4xi5Z+zODJnIDsEQ=
42+
github.com/shoenig/test v1.11.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI=
3643
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
3744
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
3845
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -48,8 +55,10 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ
4855
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
4956
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 h1:hNna6Fi0eP1f2sMBe/rJicDmaHmoXGe1Ta84FPYHLuE=
5057
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5/go.mod h1:f1SCnEOt6sc3fOJfPQDRDzHOtSXuTtnz0ImG9kPRDV0=
51-
github.com/traefik/yaegi v0.15.1 h1:YA5SbaL6HZA0Exh9T/oArRHqGN2HQ+zgmCY7dkoTXu4=
52-
github.com/traefik/yaegi v0.15.1/go.mod h1:AVRxhaI2G+nUsaM1zyktzwXn69G3t/AuTDrCiTds9p0=
58+
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
59+
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
60+
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
61+
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
5362
github.com/x1unix/foundation v1.0.0 h1:tG0dG1sbiF9TGrjwns+wtX5feBprRD5iTvpmgQDnacA=
5463
github.com/x1unix/foundation v1.0.0/go.mod h1:y9E4igeUWi+njm4xCM48NItLhVH/Jj1KGE069I3J5Hc=
5564
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@@ -73,25 +82,19 @@ golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAb
7382
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
7483
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
7584
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
76-
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
77-
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
7885
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
7986
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
8087
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
8188
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
8289
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
8390
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
84-
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
85-
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
8691
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
8792
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
8893
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
8994
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
9095
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
9196
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
9297
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
93-
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
94-
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
9598
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
9699
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
97100
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -106,12 +109,6 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
106109
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
107110
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
108111
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
109-
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
110-
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
111-
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
112-
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
113-
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
114-
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
115112
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
116113
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
117114
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

internal/announcements/decode.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package announcements
2+
3+
import (
4+
"crypto/sha256"
5+
"encoding/base64"
6+
"encoding/hex"
7+
"fmt"
8+
9+
"github.com/vmihailenco/msgpack/v5"
10+
)
11+
12+
type TextMarshaler struct {
13+
Value *Announcement
14+
}
15+
16+
func (a *TextMarshaler) MarshalText() ([]byte, error) {
17+
if a.Value == nil {
18+
return nil, nil
19+
}
20+
21+
encoded, err := Encode(a.Value)
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
return []byte(encoded), nil
27+
}
28+
29+
func (a *TextMarshaler) UnmarshalText(data []byte) error {
30+
if len(data) == 0 {
31+
return nil
32+
}
33+
34+
decoded, err := DecodeFromBase64(string(data))
35+
if err != nil {
36+
return err
37+
}
38+
39+
a.Value = decoded
40+
return nil
41+
}
42+
43+
// DecodeFromBase64 decodes announcement from base64 string, validates it and returns.
44+
func DecodeFromBase64(payload string) (*Announcement, error) {
45+
if payload == "" {
46+
return nil, nil
47+
}
48+
49+
rawMsg, err := base64.StdEncoding.DecodeString(payload)
50+
if err != nil {
51+
return nil, fmt.Errorf("can't decode announcement from base64: %w", err)
52+
}
53+
54+
msg := &Announcement{}
55+
if err := msgpack.Unmarshal(rawMsg, msg); err != nil {
56+
return nil, fmt.Errorf("can't decode announcenment from msgpack: %w", err)
57+
}
58+
59+
if err := msg.Validate(); err != nil {
60+
return nil, fmt.Errorf("invalid announcement contents: %w", err)
61+
}
62+
63+
if msg.Key == "" {
64+
// auto-compute announcement key
65+
hash := sha256.Sum256(rawMsg)
66+
msg.Key = hex.EncodeToString(hash[:])
67+
}
68+
69+
return msg, nil
70+
}
71+
72+
// Encode encodes announcement message into a msgpack base64 string.
73+
func Encode(msg *Announcement) (string, error) {
74+
data, err := msgpack.Marshal(msg)
75+
if err != nil {
76+
return "", fmt.Errorf("can't encode message to msgpack: %w", err)
77+
}
78+
79+
return base64.StdEncoding.EncodeToString(data), nil
80+
}

internal/announcements/decode_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package announcements
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestEncodeDecode(t *testing.T) {
10+
testAnnouncement := &Announcement{
11+
Key: "test",
12+
Type: "info",
13+
Content: []MessagePart{
14+
{
15+
Type: "text",
16+
Value: "hello world",
17+
},
18+
},
19+
}
20+
21+
m := TextMarshaler{Value: testAnnouncement}
22+
encoded, err := m.MarshalText()
23+
require.NoError(t, err)
24+
require.NotEmpty(t, encoded)
25+
26+
m.Value = nil
27+
err = m.UnmarshalText(encoded)
28+
require.NoError(t, err)
29+
require.Equal(t, testAnnouncement, m.Value)
30+
}

internal/announcements/types.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package announcements
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
)
7+
8+
// AnnouncementMessagePart represents a part of an announcement message
9+
type MessagePart struct {
10+
Type string `json:"type" msgpack:"type"` // "link" or "text"
11+
Value string `json:"value" msgpack:"value"`
12+
Label string `json:"label,omitempty" msgpack:"label,omitempty"`
13+
Style string `json:"style,omitempty" msgpack:"style,omitempty"` // "bold", "italic", or "underline"
14+
}
15+
16+
// AnnouncementMessage represents a complete announcement message
17+
type Announcement struct {
18+
Key string `json:"key" msgpack:"key"`
19+
Type string `json:"type" msgpack:"type"` // "info", "error", "warning", or "success"
20+
IsIconHidden bool `json:"isIconHidden,omitempty" msgpack:"isIconHidden,omitempty"`
21+
IsCentered bool `json:"isCentered,omitempty" msgpack:"isCentered,omitempty"`
22+
Content []MessagePart `json:"content" msgpack:"content"`
23+
}
24+
25+
func (msg Announcement) Validate() error {
26+
switch msg.Type {
27+
case "info", "error", "warning", "success":
28+
break
29+
default:
30+
return fmt.Errorf("invalid announcement message type: %q", msg.Type)
31+
}
32+
33+
if len(msg.Content) == 0 {
34+
return errors.New("missing announcement message content")
35+
}
36+
37+
return nil
38+
}

0 commit comments

Comments
 (0)