Sum/Union/Variant Type in Go and Static Check Tool of switch-case handling

Sum Type in Go

datatype tree = Leaf
| Node of (int * tree * tree)

“internal” interface and “public” interface

How to use sum types(, or interface type) in Go

type switch

// Inspect the AST and print all identifiers and literals.
ast.Inspect(f, func(n ast.Node) bool {
var s string
switch x := n.(type) {
case *ast.BasicLit:
s = x.Value
case *ast.Ident:
s = x.Name
}
if s != "" {
fmt.Printf("%s:\t%s\n", fset.Position(n.Pos()), s)
}
return true
})

Ensure a type implements expected interface at compile time

// Ensure MyNode implements Node interface at compile time.
var _ Node = &MyNode{}
// You can also check by following line instead.
var _ Node = (*MyNode)(nil)

Decoding JSON of Sum Type

type Monitor interface {
MonitorType() string
MonitorID() string
MonitorName() string
isMonitor()
}
// MonitorConnectivity represents connectivity monitor.
type MonitorConnectivity struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
IsMute bool `json:"isMute,omitempty"`
NotificationInterval uint64 `json:"notificationInterval,omitempty"`
Scopes []string `json:"scopes,omitempty"`
ExcludeScopes []string `json:"excludeScopes,omitempty"`
}
// ...
{
"monitors": [
{
"id": "2cSZzK3XfmG",
"type": "connectivity",
"isMute": false,
"scopes": [],
"excludeScopes": []
},
{
"id" : "2cSZzK3XfmG",
"type": "host",
"isMute": false,
"name": "disk.aa-00.writes.delta",
"duration": 3,
"metric": "disk.aa-00.writes.delta",
"operator": ">",
"warning": 20000.0,
"critical": 400000.0,
"scopes": [
"SomeService"
],
"excludeScopes": [
"SomeService: db-slave-backup"
],
"notificationInterval": 60
}
]
}
func (c *Client) FindMonitors() ([]Monitor, error)
  • decode to a list of json.RawMessage
  • decode json.RawMessage to get only “type” value. var typeData struct { Type String 'json:"type"'}
  • decode json.RawMessage to monitor types using “type” value.
import "encoding/json"func decodeMonitorFromRawMessage(rawmes []byte) (monitorI, error) {
var typeData struct {
Type string `json:"type"`
}
if err := json.Unmarshal(rawmes, &typeData); err != nil {
return nil, err
}
var m monitorI
switch typeData.Type {
case monitorTypeConnectivity:
m = &MonitorConnectivity{}
case monitorTypeHostMeric:
m = &MonitorHostMetric{}
case monitorTypeServiceMetric:
m = &MonitorServiceMetric{}
case monitorTypeExternalHTTP:
m = &MonitorExternalHTTP{}
case monitorTypeExpression:
m = &MonitorExpression{}
}
if err := json.Unmarshal(rawmes, m); err != nil {
return nil, err
}
return m, nil
}
  • decode to list of map[string]interface{}
  • get “type” value
  • convert map[string]interface{} to each monitor types by using mitchellh/mapstructure
func decodeMonitorFromMap(mmap map[string]interface{}) (monitorI, error) {
typ, ok := mmap["type"]
if !ok {
return nil, errors.New("`type` field not found")
}
var m monitorI
switch typ {
case monitorTypeConnectivity:
m = &MonitorConnectivity{}
case monitorTypeHostMeric:
m = &MonitorHostMetric{}
case monitorTypeServiceMetric:
m = &MonitorServiceMetric{}
case monitorTypeExternalHTTP:
m = &MonitorExternalHTTP{}
case monitorTypeExpression:
m = &MonitorExpression{}
}
c := &mapstructure.DecoderConfig{
TagName: "json",
Result: m,
}
d, err := mapstructure.NewDecoder(c)
if err != nil {
return nil, err
}
if err := d.Decode(mmap); err != nil {
return nil, err
}
return m, nil
}
$ go test -v -run="^$" -bench=Monitor_JSON_ | prettybench
benchmark iter time/iter
--------- ---- ---------
BenchmarkMonitor_JSON_mapstructure-4 1000 1.98 ms/op
BenchmarkMonitor_JSON_rawmessage-4 1000 1.40 ms/op

gosumcheck — check all cases are handled in type-switch statically

go get -u github.com/haya14busa/gosum/cmd/gosumcheck

Heuristics for suppressing false positive results as much as possible

--

--

--

V!mm!shment Th!s World! https://github.com/haya14busa

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
haya14busa

haya14busa

V!mm!shment Th!s World! https://github.com/haya14busa

More from Medium

【Go】Implement SHA256, encryption and hashing in Go

BasicAuth for APIs in GO

Getting started with GO Programming Language — Part Two

Connecting dockerized Golang services via Kafka