├── .gitignore ├── go.mod ├── Makefile ├── .goreleaser.yml ├── .travis.yml ├── example ├── go.mod ├── web │ ├── api_test.go │ ├── server.go │ ├── user.go │ └── enum.go └── go.sum ├── go.sum ├── example.go ├── README.md ├── gonum_test.go ├── download.sh ├── gonum.go └── enum.go /.gitignore: -------------------------------------------------------------------------------- 1 | gonum 2 | .idea/ 3 | /dist 4 | /bin -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/steinfletcher/gonum 2 | 3 | require golang.org/x/tools v0.0.0-20190407030857-0fdf0c73855b 4 | 5 | go 1.13 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | go build -ldflags "-X main.version=`git describe --tags`" gonum.go 3 | go generate 4 | go test . 5 | 6 | release: 7 | rm -rf dist 8 | goreleaser 9 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - binary: gonum 3 | goos: 4 | - windows 5 | - darwin 6 | - linux 7 | goarch: 8 | - amd64 9 | 10 | release: 11 | github: 12 | owner: steinfletcher 13 | name: gonum 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "stable" 5 | 6 | branches: 7 | only: 8 | - master 9 | 10 | script: 11 | - diff -u <(echo -n) <(gofmt -s -d ./) 12 | - diff -u <(echo -n) <(go vet ./...) 13 | - make test 14 | 15 | env: 16 | - GO111MODULE=on 17 | -------------------------------------------------------------------------------- /example/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/steinfletcher/gonum/example 2 | 3 | replace github.com/steinfletcher/gonum => ../ 4 | 5 | require ( 6 | github.com/gorilla/mux v1.7.1 7 | github.com/steinfletcher/apitest v1.1.1 8 | github.com/steinfletcher/apitest-jsonpath v1.0.2 9 | ) 10 | 11 | go 1.13 12 | -------------------------------------------------------------------------------- /example/web/api_test.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "github.com/steinfletcher/apitest" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func TestGetUser(t *testing.T) { 10 | apitest.New(). 11 | Debug(). 12 | Handler(newApp().Router). 13 | Post("/user/search"). 14 | Body(`{"name": "jan", "verification_status": "VERIFIED"}`). 15 | Expect(t). 16 | Status(http.StatusOK). 17 | Body(`{"id":"1234","name":"jan","verification_status":"VERIFIED"}`). 18 | End() 19 | } 20 | -------------------------------------------------------------------------------- /example/web/server.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "github.com/gorilla/mux" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | type App struct { 10 | Router *mux.Router 11 | } 12 | 13 | func newApp() *App { 14 | router := mux.NewRouter() 15 | router.HandleFunc("/user/search", getUser()).Methods("POST") 16 | return &App{Router: router} 17 | } 18 | 19 | func (a *App) start() { 20 | log.Fatal(http.ListenAndServe(":8888", a.Router)) 21 | } 22 | 23 | func main() { 24 | newApp().start() 25 | } 26 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 2 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 3 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 4 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 5 | golang.org/x/tools v0.0.0-20190407030857-0fdf0c73855b h1:n6jhaPv5tww7rCnmhFMMOIcXmi0woaJfZpxbYQ4WFzE= 6 | golang.org/x/tools v0.0.0-20190407030857-0fdf0c73855b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 7 | -------------------------------------------------------------------------------- /example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate ./gonum -types=ColorEnum,StatusEnum,SushiEnum,ErrorsEnum 4 | 5 | type ColorEnum struct { 6 | Red string `enum:"RED"` 7 | LightBlue string `enum:"LIGHT_BLUE"` 8 | } 9 | 10 | type StatusEnum struct { 11 | On string `enum:"-"` 12 | Off string `enum:"-"` 13 | } 14 | 15 | type SushiEnum struct { 16 | Maki string `enum:"MAKI,Rice and filling wrapped in seaweed"` 17 | Temaki string `enum:"TEMAKI,Hand rolled into a cone shape"` 18 | Sashimi string `enum:"SASHIMI,Fish or shellfish served alone without rice"` 19 | } 20 | 21 | type ErrorsEnum struct { 22 | InvalidCredentials string `enum:"INVALID_CREDENTIALS,The username or password is not recognised"` 23 | AccountLocked string `enum:"ACCOUNT_LOCKED,Your account has been locked. Contact support at me@admin.com"` 24 | } 25 | -------------------------------------------------------------------------------- /example/web/user.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | ) 8 | 9 | //go:generate gonum -types=VerificationStatusEnum 10 | 11 | type VerificationStatusEnum struct { 12 | Verified string `enum:"VERIFIED"` 13 | NotVerified string `enum:"NOT_VERIFIED"` 14 | Expired string `enum:"EXPIRED"` 15 | } 16 | 17 | type User struct { 18 | ID string `json:"id"` 19 | Name string `json:"name"` 20 | VerificationStatus VerificationStatus `json:"verification_status"` 21 | } 22 | 23 | func getUser() http.HandlerFunc { 24 | return func(w http.ResponseWriter, r *http.Request) { 25 | var user User 26 | bytes, err := ioutil.ReadAll(r.Body) 27 | if err != nil { 28 | panic(err) 29 | } 30 | err = json.Unmarshal(bytes, &user) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | if user.VerificationStatus == Verified && user.Name == "jan" { 36 | u := User{Name: user.Name, ID: "1234", VerificationStatus: Verified} 37 | jsonData, err := json.Marshal(u) 38 | if err != nil { 39 | panic(err) 40 | } 41 | _, err = w.Write(jsonData) 42 | if err != nil { 43 | panic(err) 44 | } 45 | w.WriteHeader(http.StatusOK) 46 | return 47 | } 48 | 49 | w.WriteHeader(http.StatusNotFound) 50 | return 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /example/go.sum: -------------------------------------------------------------------------------- 1 | github.com/PaesslerAG/gval v0.1.1 h1:NP0oqykQECq4U82Xr4Mr5U1lP9ifsbj2YMSQWiQcz6w= 2 | github.com/PaesslerAG/gval v0.1.1/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I= 3 | github.com/PaesslerAG/jsonpath v0.1.0 h1:gADYeifvlqK3R3i2cR5B4DGgxLXIPb3TRTH1mGi0jPI= 4 | github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8= 5 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/gorilla/mux v1.7.1 h1:Dw4jY2nghMMRsh1ol8dv1axHkDwMQK2DHerMNJsIpJU= 8 | github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/steinfletcher/apitest v1.0.0-alpha.9/go.mod h1:LdztYTeXBpK0x/m6xiPeZntdgNl/3tQCH00wTPsawmw= 12 | github.com/steinfletcher/apitest v1.1.1 h1:5LyK4Ho3Xfb9Bnj1lMfoqr8+5hkKOYmtJCy5KjHVjio= 13 | github.com/steinfletcher/apitest v1.1.1/go.mod h1:LOVbGzWvWCiiVE4PZByfhRnA5L00l5uZQEx403xQ4K8= 14 | github.com/steinfletcher/apitest-jsonpath v1.0.2 h1:0DZlrnIRdr0qfhZYaI0r/JNLyfwxfOc/Cx4t2sZtsOk= 15 | github.com/steinfletcher/apitest-jsonpath v1.0.2/go.mod h1:CpErNGWP2R9pDG86PAhy19ryIU2iDtv7+Ew7s1ZeymA= 16 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 17 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 18 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 19 | -------------------------------------------------------------------------------- /example/web/enum.go: -------------------------------------------------------------------------------- 1 | // Code generated by "gonum -types=VerificationStatusEnum"; DO NOT EDIT. 2 | 3 | package web 4 | 5 | import "encoding/json" 6 | import "errors" 7 | import "fmt" 8 | 9 | var verificationStatusInstance = VerificationStatusEnum{ 10 | Verified: "VERIFIED", 11 | NotVerified: "NOT_VERIFIED", 12 | Expired: "EXPIRED", 13 | } 14 | 15 | // VerificationStatus is the enum that instances should be created from 16 | type VerificationStatus struct { 17 | name string 18 | value string 19 | } 20 | 21 | // Enum instances 22 | var Verified = VerificationStatus{name: "VERIFIED", value: "Verified"} 23 | var NotVerified = VerificationStatus{name: "NOT_VERIFIED", value: "NotVerified"} 24 | var Expired = VerificationStatus{name: "EXPIRED", value: "Expired"} 25 | 26 | // NewVerificationStatus generates a new VerificationStatus from the given display value (name) 27 | func NewVerificationStatus(value string) (VerificationStatus, error) { 28 | switch value { 29 | case "VERIFIED": 30 | return Verified, nil 31 | case "NOT_VERIFIED": 32 | return NotVerified, nil 33 | case "EXPIRED": 34 | return Expired, nil 35 | default: 36 | return VerificationStatus{}, errors.New( 37 | fmt.Sprintf("'%s' is not a valid value for type", value)) 38 | } 39 | } 40 | 41 | // Name returns the enum display value 42 | func (g VerificationStatus) Name() string { 43 | switch g { 44 | case Verified: 45 | return Verified.name 46 | case NotVerified: 47 | return NotVerified.name 48 | case Expired: 49 | return Expired.name 50 | default: 51 | panic("Could not map enum") 52 | } 53 | } 54 | 55 | // String returns the enum display value and is an alias of Name to implement the Stringer interface 56 | func (g VerificationStatus) String() string { 57 | return g.Name() 58 | } 59 | 60 | // VerificationStatusNames returns the displays values of all enum instances as a slice 61 | func VerificationStatusNames() []string { 62 | return []string{ 63 | "VERIFIED", 64 | "NOT_VERIFIED", 65 | "EXPIRED", 66 | } 67 | } 68 | 69 | // VerificationStatusValues returns all enum instances as a slice 70 | func VerificationStatusValues() []VerificationStatus { 71 | return []VerificationStatus{ 72 | Verified, 73 | NotVerified, 74 | Expired, 75 | } 76 | } 77 | 78 | // MarshalJSON provides json marshalling support by implementing the Marshaler interface 79 | func (g VerificationStatus) MarshalJSON() ([]byte, error) { 80 | return json.Marshal(g.Name()) 81 | } 82 | 83 | // UnmarshalJSON provides json unmarshalling support by implementing the Unmarshaler interface 84 | func (g *VerificationStatus) UnmarshalJSON(b []byte) error { 85 | var v string 86 | err := json.Unmarshal(b, &v) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | instance, createErr := NewVerificationStatus(v) 92 | if createErr != nil { 93 | return createErr 94 | } 95 | 96 | g.name = instance.name 97 | g.value = instance.value 98 | 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gonum 2 | 3 | **I don't recommend using this in production, but rather as an example of how one could go about using AST to generate code. I am not planning to maintain this or add any new features.** 4 | 5 | `gonum` is an enum generator for Go. It is inspired by the powerful enum types found in Java. `gonum` has the following capabilities 6 | 7 | * Reference and compare enums using values 8 | * Provide a display value for the enumerated fields 9 | * Generate an enum instance from a string factory method 10 | * Generate a slice of display values 11 | * JSON support 12 | * Enum instances can be passed as errors since they implement `Error() string` 13 | 14 | ## Install 15 | 16 | From a github release 17 | 18 | ```bash 19 | curl https://raw.githubusercontent.com/steinfletcher/gonum/master/download.sh | sh 20 | mv bin/gonum /usr/local/bin 21 | ``` 22 | 23 | OR 24 | 25 | ```bash 26 | go get -u github.com/steinfletcher/gonum 27 | ``` 28 | 29 | ## Example 30 | 31 | To define an enum, create a `struct` with the suffix `Enum`. You can define a display value in the `struct` tag. Adding a hyphen will assign the field name to the display value. 32 | 33 | You can then generate the enum as follows. 34 | 35 | ```go 36 | //go:generate gonum -types=ColorEnum,StatusEnum,SushiEnum 37 | 38 | // generate an enum with display values. The display values are used for JSON serialization/deserialization 39 | type ColorEnum struct { 40 | Red string `enum:"RED"` 41 | LightBlue string `enum:"LIGHT_BLUE"` 42 | } 43 | 44 | // generate an enum with default display values. The display values are set to the field names, e.g. `On` and `Off` 45 | type StatusEnum struct { 46 | On string `enum:"-"` 47 | Off string `enum:"-"` 48 | } 49 | 50 | // generate an enum with display values and descriptions. 51 | type SushiEnum struct { 52 | Maki string `enum:"MAKI,Rice and filling wrapped in seaweed"` 53 | Temaki string `enum:"TEMAKI,Hand rolled into a cone shape"` 54 | Sashimi string `enum:"SASHIMI,Fish or shellfish served alone without rice"` 55 | } 56 | ``` 57 | 58 | When a description is defined the json is serialized as follows (not yet implemented) 59 | 60 | ```json 61 | { 62 | "sushi": { 63 | "name": "SASHIMI", 64 | "description": "Fish or shellfish served alone without rice" 65 | } 66 | } 67 | ``` 68 | 69 | ## Consumer api 70 | 71 | The generated code would yield the following consumer api 72 | 73 | ### Create an enum value 74 | 75 | ```go 76 | a := Red // OR var a Color = Red 77 | ``` 78 | 79 | ### Create an enum from a factory method 80 | 81 | ```go 82 | var name Color = NewColor("RED") 83 | ``` 84 | 85 | ### Get the display value 86 | 87 | ```go 88 | var name string = a.Name() // "RED" 89 | ``` 90 | 91 | ### Get all display values 92 | 93 | ```go 94 | var names []string = ColorNames() // []string{"RED", "BLUE"} 95 | ``` 96 | 97 | ### Get all values 98 | 99 | ```go 100 | var values []Color = ColorValues() // []string{Red, Blue} 101 | ``` 102 | 103 | ### Pass as an error 104 | 105 | Enums implement `Error() string` which means they can be passed as errors. 106 | 107 | ```go 108 | var a error = Red 109 | ``` 110 | 111 | ## Developing 112 | 113 | ```bash 114 | go build gonum.go 115 | go generate 116 | go test . 117 | ``` 118 | 119 | OR 120 | 121 | ```bash 122 | make test 123 | ``` 124 | -------------------------------------------------------------------------------- /gonum_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestName(t *testing.T) { 11 | var a = Red 12 | 13 | name := a.Name() 14 | 15 | if !reflect.DeepEqual(name, "RED") { 16 | t.Fail() 17 | } 18 | } 19 | 20 | func TestNewColorFromValue(t *testing.T) { 21 | color, _ := NewColor("RED") 22 | 23 | if color != Red { 24 | t.Fail() 25 | } 26 | } 27 | 28 | func TestNewColorFromValue_Failure(t *testing.T) { 29 | _, err := NewColor("NOT_A_COLOR") 30 | 31 | if err == nil { 32 | t.Fatal("expected err") 33 | } 34 | if err.Error() != "'NOT_A_COLOR' is not a valid value for type" { 35 | t.Fatalf("incorrect err: %s", err.Error()) 36 | } 37 | } 38 | 39 | func TestNames(t *testing.T) { 40 | values := ColorNames() 41 | 42 | if !reflect.DeepEqual(values, []string{"RED", "LIGHT_BLUE"}) { 43 | t.Fail() 44 | } 45 | } 46 | 47 | func TestEquality(t *testing.T) { 48 | var r1 = Red 49 | var r2 = Red 50 | var g = LightBlue 51 | 52 | if r1 == g { 53 | t.Fail() 54 | } 55 | 56 | if r1 != r2 { 57 | t.Fail() 58 | } 59 | } 60 | 61 | func TestJSONMarshal(t *testing.T) { 62 | type A struct { 63 | X string `json:"x"` 64 | Y Color `json:"y"` 65 | } 66 | 67 | a := A{ 68 | X: "x", 69 | Y: Red, 70 | } 71 | 72 | bytes, err := json.Marshal(&a) 73 | if err != nil { 74 | t.Fail() 75 | } 76 | 77 | if string(bytes) != `{"x":"x","y":"RED"}` { 78 | t.Fail() 79 | } 80 | } 81 | 82 | func TestUnmarshalJSON(t *testing.T) { 83 | type A struct { 84 | X string `json:"x"` 85 | Y Color `json:"y"` 86 | } 87 | data := []byte(`{"x":"x","y":"RED"}`) 88 | 89 | a := new(A) 90 | err := json.Unmarshal(data, a) 91 | if err != nil { 92 | t.Fail() 93 | } 94 | 95 | expected := A{X: "x", Y: Red} 96 | if *a != expected { 97 | t.Fatalf("expected x") 98 | } 99 | } 100 | 101 | func TestUnmarshalJSON_Error(t *testing.T) { 102 | type A struct { 103 | X string `json:"x"` 104 | Y Color `json:"y"` 105 | } 106 | data := []byte(`{"x":"x","y":"NOT_A_COLOR"}`) 107 | 108 | a := new(A) 109 | err := json.Unmarshal(data, a) 110 | 111 | if err == nil { 112 | log.Fatal("expected an error") 113 | } 114 | if err.Error() != "'NOT_A_COLOR' is not a valid value for type" { 115 | t.Fatalf("expected err: %v", err) 116 | } 117 | } 118 | 119 | func TestSupportsDescription(t *testing.T) { 120 | e := Maki 121 | 122 | if e.Description() != "Rice and filling wrapped in seaweed" { 123 | t.Fatalf("expected description but got %s", e.Description()) 124 | } 125 | } 126 | 127 | func TestSupportsSerializationWithDescription(t *testing.T) { 128 | e := Maki 129 | 130 | bytes, err := e.MarshalJSON() 131 | 132 | if err != nil { 133 | t.Fatal(err) 134 | } 135 | actual := string(bytes) 136 | expected := `{"name":"MAKI","description":"Rice and filling wrapped in seaweed"}` 137 | if !reflect.DeepEqual(actual, expected) { 138 | t.Fatalf("incorrect json, \nexpected: %s\nreceived: %s", expected, actual) 139 | } 140 | } 141 | 142 | func TestUnmarshalJSONWithDescription(t *testing.T) { 143 | type A struct { 144 | X string `json:"x"` 145 | Y Sushi `json:"y"` 146 | } 147 | data := []byte(`{"x":"x","y":"MAKI"}`) 148 | 149 | a := new(A) 150 | err := json.Unmarshal(data, a) 151 | if err != nil { 152 | t.Fail() 153 | } 154 | 155 | expected := A{X: "x", Y: Maki} 156 | if *a != expected { 157 | t.Fatalf("expected x") 158 | } 159 | } 160 | 161 | func TestSupportsUsageAsError(t *testing.T) { 162 | var e error = AccountLocked 163 | 164 | if e.Error() != "ACCOUNT_LOCKED" { 165 | t.Fail() 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # Code generated by godownloader on 2019-04-10T18:47:02Z. DO NOT EDIT. 4 | # 5 | 6 | usage() { 7 | this=$1 8 | cat </dev/null 120 | } 121 | echoerr() { 122 | echo "$@" 1>&2 123 | } 124 | log_prefix() { 125 | echo "$0" 126 | } 127 | _logp=6 128 | log_set_priority() { 129 | _logp="$1" 130 | } 131 | log_priority() { 132 | if test -z "$1"; then 133 | echo "$_logp" 134 | return 135 | fi 136 | [ "$1" -le "$_logp" ] 137 | } 138 | log_tag() { 139 | case $1 in 140 | 0) echo "emerg" ;; 141 | 1) echo "alert" ;; 142 | 2) echo "crit" ;; 143 | 3) echo "err" ;; 144 | 4) echo "warning" ;; 145 | 5) echo "notice" ;; 146 | 6) echo "info" ;; 147 | 7) echo "debug" ;; 148 | *) echo "$1" ;; 149 | esac 150 | } 151 | log_debug() { 152 | log_priority 7 || return 0 153 | echoerr "$(log_prefix)" "$(log_tag 7)" "$@" 154 | } 155 | log_info() { 156 | log_priority 6 || return 0 157 | echoerr "$(log_prefix)" "$(log_tag 6)" "$@" 158 | } 159 | log_err() { 160 | log_priority 3 || return 0 161 | echoerr "$(log_prefix)" "$(log_tag 3)" "$@" 162 | } 163 | log_crit() { 164 | log_priority 2 || return 0 165 | echoerr "$(log_prefix)" "$(log_tag 2)" "$@" 166 | } 167 | uname_os() { 168 | os=$(uname -s | tr '[:upper:]' '[:lower:]') 169 | case "$os" in 170 | msys_nt) os="windows" ;; 171 | esac 172 | echo "$os" 173 | } 174 | uname_arch() { 175 | arch=$(uname -m) 176 | case $arch in 177 | x86_64) arch="amd64" ;; 178 | x86) arch="386" ;; 179 | i686) arch="386" ;; 180 | i386) arch="386" ;; 181 | aarch64) arch="arm64" ;; 182 | armv5*) arch="armv5" ;; 183 | armv6*) arch="armv6" ;; 184 | armv7*) arch="armv7" ;; 185 | esac 186 | echo ${arch} 187 | } 188 | uname_os_check() { 189 | os=$(uname_os) 190 | case "$os" in 191 | darwin) return 0 ;; 192 | dragonfly) return 0 ;; 193 | freebsd) return 0 ;; 194 | linux) return 0 ;; 195 | android) return 0 ;; 196 | nacl) return 0 ;; 197 | netbsd) return 0 ;; 198 | openbsd) return 0 ;; 199 | plan9) return 0 ;; 200 | solaris) return 0 ;; 201 | windows) return 0 ;; 202 | esac 203 | log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib" 204 | return 1 205 | } 206 | uname_arch_check() { 207 | arch=$(uname_arch) 208 | case "$arch" in 209 | 386) return 0 ;; 210 | amd64) return 0 ;; 211 | arm64) return 0 ;; 212 | armv5) return 0 ;; 213 | armv6) return 0 ;; 214 | armv7) return 0 ;; 215 | ppc64) return 0 ;; 216 | ppc64le) return 0 ;; 217 | mips) return 0 ;; 218 | mipsle) return 0 ;; 219 | mips64) return 0 ;; 220 | mips64le) return 0 ;; 221 | s390x) return 0 ;; 222 | amd64p32) return 0 ;; 223 | esac 224 | log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib" 225 | return 1 226 | } 227 | untar() { 228 | tarball=$1 229 | case "${tarball}" in 230 | *.tar.gz | *.tgz) tar -xzf "${tarball}" ;; 231 | *.tar) tar -xf "${tarball}" ;; 232 | *.zip) unzip "${tarball}" ;; 233 | *) 234 | log_err "untar unknown archive format for ${tarball}" 235 | return 1 236 | ;; 237 | esac 238 | } 239 | http_download_curl() { 240 | local_file=$1 241 | source_url=$2 242 | header=$3 243 | if [ -z "$header" ]; then 244 | code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url") 245 | else 246 | code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url") 247 | fi 248 | if [ "$code" != "200" ]; then 249 | log_debug "http_download_curl received HTTP status $code" 250 | return 1 251 | fi 252 | return 0 253 | } 254 | http_download_wget() { 255 | local_file=$1 256 | source_url=$2 257 | header=$3 258 | if [ -z "$header" ]; then 259 | wget -q -O "$local_file" "$source_url" 260 | else 261 | wget -q --header "$header" -O "$local_file" "$source_url" 262 | fi 263 | } 264 | http_download() { 265 | log_debug "http_download $2" 266 | if is_command curl; then 267 | http_download_curl "$@" 268 | return 269 | elif is_command wget; then 270 | http_download_wget "$@" 271 | return 272 | fi 273 | log_crit "http_download unable to find wget or curl" 274 | return 1 275 | } 276 | http_copy() { 277 | tmp=$(mktemp) 278 | http_download "${tmp}" "$1" "$2" || return 1 279 | body=$(cat "$tmp") 280 | rm -f "${tmp}" 281 | echo "$body" 282 | } 283 | github_release() { 284 | owner_repo=$1 285 | version=$2 286 | test -z "$version" && version="latest" 287 | giturl="https://github.com/${owner_repo}/releases/${version}" 288 | json=$(http_copy "$giturl" "Accept:application/json") 289 | test -z "$json" && return 1 290 | version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//') 291 | test -z "$version" && return 1 292 | echo "$version" 293 | } 294 | hash_sha256() { 295 | TARGET=${1:-/dev/stdin} 296 | if is_command gsha256sum; then 297 | hash=$(gsha256sum "$TARGET") || return 1 298 | echo "$hash" | cut -d ' ' -f 1 299 | elif is_command sha256sum; then 300 | hash=$(sha256sum "$TARGET") || return 1 301 | echo "$hash" | cut -d ' ' -f 1 302 | elif is_command shasum; then 303 | hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1 304 | echo "$hash" | cut -d ' ' -f 1 305 | elif is_command openssl; then 306 | hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1 307 | echo "$hash" | cut -d ' ' -f a 308 | else 309 | log_crit "hash_sha256 unable to find command to compute sha-256 hash" 310 | return 1 311 | fi 312 | } 313 | hash_sha256_verify() { 314 | TARGET=$1 315 | checksums=$2 316 | if [ -z "$checksums" ]; then 317 | log_err "hash_sha256_verify checksum file not specified in arg2" 318 | return 1 319 | fi 320 | BASENAME=${TARGET##*/} 321 | want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1) 322 | if [ -z "$want" ]; then 323 | log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'" 324 | return 1 325 | fi 326 | got=$(hash_sha256 "$TARGET") 327 | if [ "$want" != "$got" ]; then 328 | log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got" 329 | return 1 330 | fi 331 | } 332 | cat /dev/null </enum.go") 26 | ) 27 | 28 | func Usage() { 29 | fmt.Fprintf(os.Stderr, "Usage of gonum (%s)", version) 30 | fmt.Fprintf(os.Stderr, "\tgonum [flags] -types T [directory]\n") 31 | fmt.Fprintf(os.Stderr, "\tgonum [flags] -types T files... # Must be a single package\n") 32 | fmt.Fprintf(os.Stderr, "For more information, see:\n") 33 | fmt.Fprintf(os.Stderr, "\thttps://github.com/steinfletcher/gonum\n") 34 | fmt.Fprintf(os.Stderr, "Flags:\n") 35 | flag.PrintDefaults() 36 | } 37 | 38 | func main() { 39 | log.SetFlags(0) 40 | log.SetPrefix("gonum: ") 41 | flag.Usage = Usage 42 | flag.Parse() 43 | if len(*typeNames) == 0 { 44 | flag.Usage() 45 | os.Exit(2) 46 | } 47 | typs := strings.Split(*typeNames, ",") 48 | 49 | // We accept either one directory or a list of files. Which do we have? 50 | args := flag.Args() 51 | if len(args) == 0 { 52 | // Default: process whole package in current directory. 53 | args = []string{"."} 54 | } 55 | 56 | // Parse the package once. 57 | var dir string 58 | g := Generator{} 59 | if len(args) == 1 && isDirectory(args[0]) { 60 | dir = args[0] 61 | } 62 | 63 | g.parsePackage(args) 64 | 65 | // Print the header and package clause. 66 | g.Printf("// Code generated by \"gonum %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " ")) 67 | g.Printf("// See https://github.com/steinfletcher/gonum") 68 | g.Printf("\n") 69 | g.Printf("package %s", g.pkg.name) 70 | g.Printf("\n") 71 | g.Printf("import \"encoding/json\"\n") 72 | g.Printf("import \"errors\"\n") 73 | g.Printf("import \"fmt\"\n") 74 | g.Printf("\n") 75 | 76 | // Run generate for each type. 77 | for _, typeName := range typs { 78 | g.generate(typeName) 79 | } 80 | 81 | // Format the output. 82 | src := g.format() 83 | 84 | // Write to file. 85 | outputName := *output 86 | if outputName == "" { 87 | outputName = filepath.Join(dir, "enum.go") 88 | } 89 | err := ioutil.WriteFile(outputName, src, 0644) 90 | if err != nil { 91 | log.Fatal(err) 92 | } 93 | } 94 | 95 | // generate produces the enum code for the named type. 96 | func (g *Generator) generate(typeName string) { 97 | var enums []enum 98 | for _, file := range g.pkg.files { 99 | file.enums = nil 100 | file.typeName = typeName 101 | ast.Inspect(file.file, file.genDecl) 102 | if len(file.enums) > 0 { 103 | enums = append(enums, file.enums...) 104 | } 105 | } 106 | 107 | if len(enums) == 0 { 108 | log.Fatalf("no values defined for type %s", typeName) 109 | } 110 | 111 | for _, enum := range enums { 112 | var fields []fieldModel 113 | for _, field := range enum.elements { 114 | 115 | fields = append(fields, fieldModel{ 116 | Key: field.name, 117 | Value: field.value, 118 | Description: field.description, 119 | }) 120 | } 121 | 122 | instanceModel := model{ 123 | InstanceVariable: fmt.Sprintf("%sInstance", lowerFirstChar(enum.newName)), 124 | OriginalType: enum.originalName, 125 | NewType: enum.newName, 126 | Fields: fields, 127 | } 128 | 129 | g.render(instanceTemplate, instanceModel) 130 | } 131 | } 132 | 133 | func (g *Generator) render(tmpl string, model interface{}) { 134 | t, err := template.New(tmpl).Parse(tmpl) 135 | if err != nil { 136 | log.Fatal("instance template parse: ", err) 137 | } 138 | 139 | err = t.Execute(&g.buf, model) 140 | if err != nil { 141 | log.Fatal("Execute: ", err) 142 | return 143 | } 144 | } 145 | 146 | func (f *File) genDecl(node ast.Node) bool { 147 | decl, ok := node.(*ast.GenDecl) 148 | if !ok || decl.Tok != token.TYPE { 149 | return true 150 | } 151 | 152 | for _, spec := range decl.Specs { 153 | vspec := spec.(*ast.TypeSpec) 154 | if vspec.Name.Name != f.typeName { 155 | continue 156 | } 157 | 158 | if structType, ok := vspec.Type.(*ast.StructType); ok { 159 | var e *enum 160 | if structType.Fields != nil { 161 | for _, field := range structType.Fields.List { 162 | if field.Tag != nil && strings.HasPrefix(field.Tag.Value, "`enum:") { 163 | if e == nil { 164 | e = &enum{ 165 | originalName: vspec.Name.Name, 166 | newName: strings.Replace(vspec.Name.Name, "Enum", "", -1), 167 | elements: []enumElement{}, 168 | } 169 | } 170 | if len(field.Names) > 0 { 171 | name, description := parseEnumStructTag(field.Tag.Value) 172 | if name == "-" { 173 | name = field.Names[0].Name 174 | } 175 | e.elements = append(e.elements, enumElement{ 176 | value: field.Names[0].Name, 177 | name: name, 178 | description: description, 179 | }) 180 | } 181 | } 182 | } 183 | } 184 | 185 | if e != nil { 186 | f.enums = append(f.enums, *e) 187 | } 188 | } 189 | } 190 | return false 191 | } 192 | 193 | func parseEnumStructTag(content string) (string, string) { 194 | if value, ok := parseStructTag(content, "`enum"); ok { 195 | splits := strings.Split(value, ",") 196 | name := splits[0] 197 | var description string 198 | if len(splits) > 1 { 199 | description = splits[1] 200 | } 201 | return name, description 202 | } 203 | log.Fatal("enum struct tag did not contain name") 204 | return "", "" 205 | } 206 | 207 | func parseStructTag(tag string, key string) (value string, ok bool) { 208 | for tag != "" { 209 | // Skip leading space.q 210 | i := 0 211 | for i < len(tag) && tag[i] == ' ' { 212 | i++ 213 | } 214 | tag = tag[i:] 215 | if tag == "" { 216 | break 217 | } 218 | 219 | // Scan to colon. A space, a quote or a control character is a syntax error. 220 | // Strictly speaking, control chars include the range [0x7f, 0x9f], not just 221 | // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters 222 | // as it is simpler to inspect the tag's bytes than the tag's runes. 223 | i = 0 224 | for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { 225 | i++ 226 | } 227 | if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { 228 | break 229 | } 230 | name := string(tag[:i]) 231 | tag = tag[i+1:] 232 | 233 | // Scan quoted string to find value. 234 | i = 1 235 | for i < len(tag) && tag[i] != '"' { 236 | if tag[i] == '\\' { 237 | i++ 238 | } 239 | i++ 240 | } 241 | if i >= len(tag) { 242 | break 243 | } 244 | qvalue := string(tag[:i+1]) 245 | tag = tag[i+1:] 246 | 247 | if key == name { 248 | value, err := strconv.Unquote(qvalue) 249 | if err != nil { 250 | break 251 | } 252 | return value, true 253 | } 254 | } 255 | return "", false 256 | } 257 | 258 | // parsePackage analyzes the single package constructed from the patterns and tags. 259 | // parsePackage exits if there is an error. 260 | func (g *Generator) parsePackage(patterns []string) { 261 | cfg := &packages.Config{ 262 | Mode: packages.LoadSyntax, 263 | Tests: false, 264 | } 265 | pkgs, err := packages.Load(cfg, patterns...) 266 | if err != nil { 267 | log.Fatal(err) 268 | } 269 | if len(pkgs) != 1 { 270 | log.Fatalf("error: %d packages found", len(pkgs)) 271 | } 272 | g.addPackage(pkgs[0]) 273 | } 274 | 275 | // Generator holds the state of the analysis. Primarily used to buffer 276 | // the output for format.Source. 277 | type Generator struct { 278 | buf bytes.Buffer // Accumulated output. 279 | pkg *Package // Package we are scanning. 280 | 281 | trimPrefix string 282 | lineComment bool 283 | } 284 | 285 | func (g *Generator) Printf(format string, args ...interface{}) { 286 | _, err := fmt.Fprintf(&g.buf, format, args...) 287 | if err != nil { 288 | log.Fatal(err) 289 | } 290 | } 291 | 292 | func isDirectory(name string) bool { 293 | info, err := os.Stat(name) 294 | if err != nil { 295 | log.Fatal(err) 296 | } 297 | return info.IsDir() 298 | } 299 | 300 | type Package struct { 301 | name string 302 | defs map[*ast.Ident]types.Object 303 | files []*File 304 | } 305 | 306 | type File struct { 307 | pkg *Package // Package to which this file belongs. 308 | file *ast.File // Parsed AST. 309 | typeName string // Name of the constant type. 310 | enums []enum 311 | } 312 | 313 | type enum struct { 314 | originalName string 315 | newName string 316 | elements []enumElement 317 | } 318 | 319 | type enumElement struct { 320 | value string 321 | name string 322 | description string 323 | } 324 | 325 | // format returns the gofmt-ed contents of the Generator's buffer. 326 | func (g *Generator) format() []byte { 327 | src, err := format.Source(g.buf.Bytes()) 328 | if err != nil { 329 | // Should never happen, but can arise when developing this code. 330 | // The user can compile the output to see the error. 331 | log.Printf("warning: internal error: invalid Go generated: %s", err) 332 | log.Printf("warning: compile the package to analyze the error") 333 | return g.buf.Bytes() 334 | } 335 | return src 336 | } 337 | 338 | // addPackage adds a type checked Package and its syntax files to the generator. 339 | func (g *Generator) addPackage(pkg *packages.Package) { 340 | g.pkg = &Package{ 341 | name: pkg.Name, 342 | defs: pkg.TypesInfo.Defs, 343 | files: make([]*File, len(pkg.Syntax)), 344 | } 345 | 346 | for i, file := range pkg.Syntax { 347 | g.pkg.files[i] = &File{ 348 | file: file, 349 | pkg: g.pkg, 350 | } 351 | } 352 | } 353 | 354 | func lowerFirstChar(in string) string { 355 | v := []byte(in) 356 | v[0] = byte(unicode.ToLower(rune(v[0]))) 357 | return string(v) 358 | } 359 | 360 | type model struct { 361 | InstanceVariable string 362 | OriginalType string 363 | NewType string 364 | Fields []fieldModel 365 | } 366 | 367 | type fieldModel struct { 368 | Key string 369 | Value string 370 | Description string 371 | } 372 | 373 | const instanceTemplate = ` 374 | type {{.InstanceVariable}}JsonDescriptionModel struct { 375 | Name string ` + "`json:" + `"name"` + "`" + ` 376 | Description string ` + "`json:" + `"description"` + "`" + ` 377 | } 378 | 379 | var {{.InstanceVariable}} = {{.OriginalType}}{ 380 | {{- range .Fields}} 381 | {{.Value}}: "{{.Key}}", 382 | {{- end}} 383 | } 384 | 385 | // {{.NewType}} is the enum that instances should be created from 386 | type {{.NewType}} struct { 387 | name string 388 | value string 389 | description string 390 | } 391 | 392 | // Enum instances 393 | {{- range $e := .Fields}} 394 | var {{.Value}} = {{$.NewType}}{name: "{{.Key}}", value: "{{.Value}}", description: "{{.Description}}"} 395 | {{- end}} 396 | 397 | // New{{.NewType}} generates a new {{.NewType}} from the given display value (name) 398 | func New{{.NewType}}(value string) ({{.NewType}}, error) { 399 | switch value { 400 | {{- range $e := .Fields}} 401 | case "{{.Key}}": 402 | return {{.Value}}, nil 403 | {{- end}} 404 | default: 405 | return {{.NewType}}{}, errors.New( 406 | fmt.Sprintf("'%s' is not a valid value for type", value)) 407 | } 408 | } 409 | 410 | // Name returns the enum display value 411 | func (g {{.NewType}}) Name() string { 412 | switch g { 413 | {{- range $e := .Fields}} 414 | case {{$e.Value}}: 415 | return {{$e.Value}}.name 416 | {{- end}} 417 | default: 418 | return "" 419 | } 420 | } 421 | 422 | // String returns the enum display value and is an alias of Name to implement the Stringer interface 423 | func (g {{.NewType}}) String() string { 424 | return g.Name() 425 | } 426 | 427 | // Error returns the enum name and implements the Error interface 428 | func (g {{.NewType}}) Error() string { 429 | return g.Name() 430 | } 431 | 432 | // Description returns the enum description if present. If no description is defined an empty string is returned 433 | func (g {{.NewType}}) Description() string { 434 | switch g { 435 | {{- range $e := .Fields}} 436 | case {{$e.Value}}: 437 | return "{{$e.Description}}" 438 | {{- end}} 439 | default: 440 | return "" 441 | } 442 | } 443 | 444 | // {{.NewType}}Names returns the displays values of all enum instances as a slice 445 | func {{.NewType}}Names() []string { 446 | return []string{ 447 | {{- range $e := .Fields}} 448 | "{{.Key}}", 449 | {{- end}} 450 | } 451 | } 452 | 453 | // {{.NewType}}Values returns all enum instances as a slice 454 | func {{.NewType}}Values() []{{.NewType}} { 455 | return []{{.NewType}}{ 456 | {{- range $e := .Fields}} 457 | {{.Value}}, 458 | {{- end}} 459 | } 460 | } 461 | 462 | // MarshalJSON provides json serialization support by implementing the Marshaler interface 463 | func (g {{.NewType}}) MarshalJSON() ([]byte, error) { 464 | if g.Description() != "" { 465 | m := {{.InstanceVariable}}JsonDescriptionModel { 466 | Name: g.Name(), 467 | Description: g.Description(), 468 | } 469 | return json.Marshal(m) 470 | } 471 | return json.Marshal(g.Name()) 472 | } 473 | 474 | // UnmarshalJSON provides json deserialization support by implementing the Unmarshaler interface 475 | func (g *{{.NewType}}) UnmarshalJSON(b []byte) error { 476 | var v interface{} 477 | err := json.Unmarshal(b, &v) 478 | if err != nil { 479 | return err 480 | } 481 | 482 | var value string 483 | switch v.(type) { 484 | case map[string]interface{}: 485 | value = v.(map[string]interface{})["name"].(string) 486 | case string: 487 | value = v.(string) 488 | } 489 | 490 | instance, createErr := New{{.NewType}}(value) 491 | if createErr != nil { 492 | return createErr 493 | } 494 | 495 | g.name = instance.name 496 | g.value = instance.value 497 | g.description = instance.description 498 | 499 | return nil 500 | } 501 | ` 502 | -------------------------------------------------------------------------------- /enum.go: -------------------------------------------------------------------------------- 1 | // Code generated by "gonum -types=ColorEnum,StatusEnum,SushiEnum,ErrorsEnum"; DO NOT EDIT. 2 | // See https://github.com/steinfletcher/gonum 3 | package main 4 | 5 | import "encoding/json" 6 | import "errors" 7 | import "fmt" 8 | 9 | type colorInstanceJsonDescriptionModel struct { 10 | Name string `json:"name"` 11 | Description string `json:"description"` 12 | } 13 | 14 | var colorInstance = ColorEnum{ 15 | Red: "RED", 16 | LightBlue: "LIGHT_BLUE", 17 | } 18 | 19 | // Color is the enum that instances should be created from 20 | type Color struct { 21 | name string 22 | value string 23 | description string 24 | } 25 | 26 | // Enum instances 27 | var Red = Color{name: "RED", value: "Red", description: ""} 28 | var LightBlue = Color{name: "LIGHT_BLUE", value: "LightBlue", description: ""} 29 | 30 | // NewColor generates a new Color from the given display value (name) 31 | func NewColor(value string) (Color, error) { 32 | switch value { 33 | case "RED": 34 | return Red, nil 35 | case "LIGHT_BLUE": 36 | return LightBlue, nil 37 | default: 38 | return Color{}, errors.New( 39 | fmt.Sprintf("'%s' is not a valid value for type", value)) 40 | } 41 | } 42 | 43 | // Name returns the enum display value 44 | func (g Color) Name() string { 45 | switch g { 46 | case Red: 47 | return Red.name 48 | case LightBlue: 49 | return LightBlue.name 50 | default: 51 | return "" 52 | } 53 | } 54 | 55 | // String returns the enum display value and is an alias of Name to implement the Stringer interface 56 | func (g Color) String() string { 57 | return g.Name() 58 | } 59 | 60 | // Error returns the enum name and implements the Error interface 61 | func (g Color) Error() string { 62 | return g.Name() 63 | } 64 | 65 | // Description returns the enum description if present. If no description is defined an empty string is returned 66 | func (g Color) Description() string { 67 | switch g { 68 | case Red: 69 | return "" 70 | case LightBlue: 71 | return "" 72 | default: 73 | return "" 74 | } 75 | } 76 | 77 | // ColorNames returns the displays values of all enum instances as a slice 78 | func ColorNames() []string { 79 | return []string{ 80 | "RED", 81 | "LIGHT_BLUE", 82 | } 83 | } 84 | 85 | // ColorValues returns all enum instances as a slice 86 | func ColorValues() []Color { 87 | return []Color{ 88 | Red, 89 | LightBlue, 90 | } 91 | } 92 | 93 | // MarshalJSON provides json serialization support by implementing the Marshaler interface 94 | func (g Color) MarshalJSON() ([]byte, error) { 95 | if g.Description() != "" { 96 | m := colorInstanceJsonDescriptionModel{ 97 | Name: g.Name(), 98 | Description: g.Description(), 99 | } 100 | return json.Marshal(m) 101 | } 102 | return json.Marshal(g.Name()) 103 | } 104 | 105 | // UnmarshalJSON provides json deserialization support by implementing the Unmarshaler interface 106 | func (g *Color) UnmarshalJSON(b []byte) error { 107 | var v interface{} 108 | err := json.Unmarshal(b, &v) 109 | if err != nil { 110 | return err 111 | } 112 | 113 | var value string 114 | switch v.(type) { 115 | case map[string]interface{}: 116 | value = v.(map[string]interface{})["name"].(string) 117 | case string: 118 | value = v.(string) 119 | } 120 | 121 | instance, createErr := NewColor(value) 122 | if createErr != nil { 123 | return createErr 124 | } 125 | 126 | g.name = instance.name 127 | g.value = instance.value 128 | g.description = instance.description 129 | 130 | return nil 131 | } 132 | 133 | type statusInstanceJsonDescriptionModel struct { 134 | Name string `json:"name"` 135 | Description string `json:"description"` 136 | } 137 | 138 | var statusInstance = StatusEnum{ 139 | On: "On", 140 | Off: "Off", 141 | } 142 | 143 | // Status is the enum that instances should be created from 144 | type Status struct { 145 | name string 146 | value string 147 | description string 148 | } 149 | 150 | // Enum instances 151 | var On = Status{name: "On", value: "On", description: ""} 152 | var Off = Status{name: "Off", value: "Off", description: ""} 153 | 154 | // NewStatus generates a new Status from the given display value (name) 155 | func NewStatus(value string) (Status, error) { 156 | switch value { 157 | case "On": 158 | return On, nil 159 | case "Off": 160 | return Off, nil 161 | default: 162 | return Status{}, errors.New( 163 | fmt.Sprintf("'%s' is not a valid value for type", value)) 164 | } 165 | } 166 | 167 | // Name returns the enum display value 168 | func (g Status) Name() string { 169 | switch g { 170 | case On: 171 | return On.name 172 | case Off: 173 | return Off.name 174 | default: 175 | return "" 176 | } 177 | } 178 | 179 | // String returns the enum display value and is an alias of Name to implement the Stringer interface 180 | func (g Status) String() string { 181 | return g.Name() 182 | } 183 | 184 | // Error returns the enum name and implements the Error interface 185 | func (g Status) Error() string { 186 | return g.Name() 187 | } 188 | 189 | // Description returns the enum description if present. If no description is defined an empty string is returned 190 | func (g Status) Description() string { 191 | switch g { 192 | case On: 193 | return "" 194 | case Off: 195 | return "" 196 | default: 197 | return "" 198 | } 199 | } 200 | 201 | // StatusNames returns the displays values of all enum instances as a slice 202 | func StatusNames() []string { 203 | return []string{ 204 | "On", 205 | "Off", 206 | } 207 | } 208 | 209 | // StatusValues returns all enum instances as a slice 210 | func StatusValues() []Status { 211 | return []Status{ 212 | On, 213 | Off, 214 | } 215 | } 216 | 217 | // MarshalJSON provides json serialization support by implementing the Marshaler interface 218 | func (g Status) MarshalJSON() ([]byte, error) { 219 | if g.Description() != "" { 220 | m := statusInstanceJsonDescriptionModel{ 221 | Name: g.Name(), 222 | Description: g.Description(), 223 | } 224 | return json.Marshal(m) 225 | } 226 | return json.Marshal(g.Name()) 227 | } 228 | 229 | // UnmarshalJSON provides json deserialization support by implementing the Unmarshaler interface 230 | func (g *Status) UnmarshalJSON(b []byte) error { 231 | var v interface{} 232 | err := json.Unmarshal(b, &v) 233 | if err != nil { 234 | return err 235 | } 236 | 237 | var value string 238 | switch v.(type) { 239 | case map[string]interface{}: 240 | value = v.(map[string]interface{})["name"].(string) 241 | case string: 242 | value = v.(string) 243 | } 244 | 245 | instance, createErr := NewStatus(value) 246 | if createErr != nil { 247 | return createErr 248 | } 249 | 250 | g.name = instance.name 251 | g.value = instance.value 252 | g.description = instance.description 253 | 254 | return nil 255 | } 256 | 257 | type sushiInstanceJsonDescriptionModel struct { 258 | Name string `json:"name"` 259 | Description string `json:"description"` 260 | } 261 | 262 | var sushiInstance = SushiEnum{ 263 | Maki: "MAKI", 264 | Temaki: "TEMAKI", 265 | Sashimi: "SASHIMI", 266 | } 267 | 268 | // Sushi is the enum that instances should be created from 269 | type Sushi struct { 270 | name string 271 | value string 272 | description string 273 | } 274 | 275 | // Enum instances 276 | var Maki = Sushi{name: "MAKI", value: "Maki", description: "Rice and filling wrapped in seaweed"} 277 | var Temaki = Sushi{name: "TEMAKI", value: "Temaki", description: "Hand rolled into a cone shape"} 278 | var Sashimi = Sushi{name: "SASHIMI", value: "Sashimi", description: "Fish or shellfish served alone without rice"} 279 | 280 | // NewSushi generates a new Sushi from the given display value (name) 281 | func NewSushi(value string) (Sushi, error) { 282 | switch value { 283 | case "MAKI": 284 | return Maki, nil 285 | case "TEMAKI": 286 | return Temaki, nil 287 | case "SASHIMI": 288 | return Sashimi, nil 289 | default: 290 | return Sushi{}, errors.New( 291 | fmt.Sprintf("'%s' is not a valid value for type", value)) 292 | } 293 | } 294 | 295 | // Name returns the enum display value 296 | func (g Sushi) Name() string { 297 | switch g { 298 | case Maki: 299 | return Maki.name 300 | case Temaki: 301 | return Temaki.name 302 | case Sashimi: 303 | return Sashimi.name 304 | default: 305 | return "" 306 | } 307 | } 308 | 309 | // String returns the enum display value and is an alias of Name to implement the Stringer interface 310 | func (g Sushi) String() string { 311 | return g.Name() 312 | } 313 | 314 | // Error returns the enum name and implements the Error interface 315 | func (g Sushi) Error() string { 316 | return g.Name() 317 | } 318 | 319 | // Description returns the enum description if present. If no description is defined an empty string is returned 320 | func (g Sushi) Description() string { 321 | switch g { 322 | case Maki: 323 | return "Rice and filling wrapped in seaweed" 324 | case Temaki: 325 | return "Hand rolled into a cone shape" 326 | case Sashimi: 327 | return "Fish or shellfish served alone without rice" 328 | default: 329 | return "" 330 | } 331 | } 332 | 333 | // SushiNames returns the displays values of all enum instances as a slice 334 | func SushiNames() []string { 335 | return []string{ 336 | "MAKI", 337 | "TEMAKI", 338 | "SASHIMI", 339 | } 340 | } 341 | 342 | // SushiValues returns all enum instances as a slice 343 | func SushiValues() []Sushi { 344 | return []Sushi{ 345 | Maki, 346 | Temaki, 347 | Sashimi, 348 | } 349 | } 350 | 351 | // MarshalJSON provides json serialization support by implementing the Marshaler interface 352 | func (g Sushi) MarshalJSON() ([]byte, error) { 353 | if g.Description() != "" { 354 | m := sushiInstanceJsonDescriptionModel{ 355 | Name: g.Name(), 356 | Description: g.Description(), 357 | } 358 | return json.Marshal(m) 359 | } 360 | return json.Marshal(g.Name()) 361 | } 362 | 363 | // UnmarshalJSON provides json deserialization support by implementing the Unmarshaler interface 364 | func (g *Sushi) UnmarshalJSON(b []byte) error { 365 | var v interface{} 366 | err := json.Unmarshal(b, &v) 367 | if err != nil { 368 | return err 369 | } 370 | 371 | var value string 372 | switch v.(type) { 373 | case map[string]interface{}: 374 | value = v.(map[string]interface{})["name"].(string) 375 | case string: 376 | value = v.(string) 377 | } 378 | 379 | instance, createErr := NewSushi(value) 380 | if createErr != nil { 381 | return createErr 382 | } 383 | 384 | g.name = instance.name 385 | g.value = instance.value 386 | g.description = instance.description 387 | 388 | return nil 389 | } 390 | 391 | type errorsInstanceJsonDescriptionModel struct { 392 | Name string `json:"name"` 393 | Description string `json:"description"` 394 | } 395 | 396 | var errorsInstance = ErrorsEnum{ 397 | InvalidCredentials: "INVALID_CREDENTIALS", 398 | AccountLocked: "ACCOUNT_LOCKED", 399 | } 400 | 401 | // Errors is the enum that instances should be created from 402 | type Errors struct { 403 | name string 404 | value string 405 | description string 406 | } 407 | 408 | // Enum instances 409 | var InvalidCredentials = Errors{name: "INVALID_CREDENTIALS", value: "InvalidCredentials", description: "The username or password is not recognised"} 410 | var AccountLocked = Errors{name: "ACCOUNT_LOCKED", value: "AccountLocked", description: "Your account has been locked. Contact support at me@admin.com"} 411 | 412 | // NewErrors generates a new Errors from the given display value (name) 413 | func NewErrors(value string) (Errors, error) { 414 | switch value { 415 | case "INVALID_CREDENTIALS": 416 | return InvalidCredentials, nil 417 | case "ACCOUNT_LOCKED": 418 | return AccountLocked, nil 419 | default: 420 | return Errors{}, errors.New( 421 | fmt.Sprintf("'%s' is not a valid value for type", value)) 422 | } 423 | } 424 | 425 | // Name returns the enum display value 426 | func (g Errors) Name() string { 427 | switch g { 428 | case InvalidCredentials: 429 | return InvalidCredentials.name 430 | case AccountLocked: 431 | return AccountLocked.name 432 | default: 433 | return "" 434 | } 435 | } 436 | 437 | // String returns the enum display value and is an alias of Name to implement the Stringer interface 438 | func (g Errors) String() string { 439 | return g.Name() 440 | } 441 | 442 | // Error returns the enum name and implements the Error interface 443 | func (g Errors) Error() string { 444 | return g.Name() 445 | } 446 | 447 | // Description returns the enum description if present. If no description is defined an empty string is returned 448 | func (g Errors) Description() string { 449 | switch g { 450 | case InvalidCredentials: 451 | return "The username or password is not recognised" 452 | case AccountLocked: 453 | return "Your account has been locked. Contact support at me@admin.com" 454 | default: 455 | return "" 456 | } 457 | } 458 | 459 | // ErrorsNames returns the displays values of all enum instances as a slice 460 | func ErrorsNames() []string { 461 | return []string{ 462 | "INVALID_CREDENTIALS", 463 | "ACCOUNT_LOCKED", 464 | } 465 | } 466 | 467 | // ErrorsValues returns all enum instances as a slice 468 | func ErrorsValues() []Errors { 469 | return []Errors{ 470 | InvalidCredentials, 471 | AccountLocked, 472 | } 473 | } 474 | 475 | // MarshalJSON provides json serialization support by implementing the Marshaler interface 476 | func (g Errors) MarshalJSON() ([]byte, error) { 477 | if g.Description() != "" { 478 | m := errorsInstanceJsonDescriptionModel{ 479 | Name: g.Name(), 480 | Description: g.Description(), 481 | } 482 | return json.Marshal(m) 483 | } 484 | return json.Marshal(g.Name()) 485 | } 486 | 487 | // UnmarshalJSON provides json deserialization support by implementing the Unmarshaler interface 488 | func (g *Errors) UnmarshalJSON(b []byte) error { 489 | var v interface{} 490 | err := json.Unmarshal(b, &v) 491 | if err != nil { 492 | return err 493 | } 494 | 495 | var value string 496 | switch v.(type) { 497 | case map[string]interface{}: 498 | value = v.(map[string]interface{})["name"].(string) 499 | case string: 500 | value = v.(string) 501 | } 502 | 503 | instance, createErr := NewErrors(value) 504 | if createErr != nil { 505 | return createErr 506 | } 507 | 508 | g.name = instance.name 509 | g.value = instance.value 510 | g.description = instance.description 511 | 512 | return nil 513 | } 514 | --------------------------------------------------------------------------------