├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── COPYING ├── README.md ├── bin ├── .go-1.19.3.pkg ├── .golangci-lint-1.50.1.pkg ├── .goreleaser-1.15.0.pkg ├── .jq-1.6.pkg ├── .reflex-0.3.1.pkg ├── .shellcheck-0.8.0.pkg ├── README.hermit.md ├── activate-hermit ├── go ├── gofmt ├── golangci-lint ├── goreleaser ├── hermit ├── hermit.hcl ├── jq ├── reflex └── shellcheck ├── cmd └── happy │ └── main.go ├── codewriter ├── codewriter.go └── codewriter_test.go ├── go.mod ├── go.sum ├── reflex.conf ├── scripts ├── e2e-test └── happy └── testdata ├── main.go ├── main_api.go └── main_test.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | name: CI 7 | jobs: 8 | test: 9 | name: Test 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | - name: Init Hermit 15 | run: ./bin/hermit env -r >> $GITHUB_ENV 16 | - name: E2E Test 17 | run: e2e-test 18 | lint: 19 | name: Lint 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v3 24 | - name: Init Hermit 25 | run: ./bin/hermit env -r >> $GITHUB_ENV 26 | - name: golangci-lint 27 | run: golangci-lint run 28 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | jobs: 7 | release: 8 | name: Release 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | fetch-depth: 0 14 | - run: ./bin/hermit env --raw >> $GITHUB_ENV 15 | - run: goreleaser release 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.hermit 2 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | tests: true 3 | skip-dirs: 4 | - _examples 5 | 6 | output: 7 | print-issued-lines: false 8 | 9 | linters: 10 | enable-all: true 11 | disable: 12 | - maligned 13 | - megacheck 14 | - lll 15 | - gocyclo 16 | - dupl 17 | - gochecknoglobals 18 | - funlen 19 | - godox 20 | - wsl 21 | - goimports 22 | - gomnd 23 | - gocognit 24 | - goerr113 25 | - nolintlint 26 | - testpackage 27 | - godot 28 | - nestif 29 | - paralleltest 30 | - nlreturn 31 | - cyclop 32 | - exhaustivestruct 33 | - gci 34 | - gofumpt 35 | - errorlint 36 | - exhaustive 37 | - ifshort 38 | - wrapcheck 39 | - stylecheck 40 | - nonamedreturns 41 | - revive 42 | - dupword 43 | - exhaustruct 44 | - varnamelen 45 | - forcetypeassert 46 | - ireturn 47 | - maintidx 48 | - govet 49 | - nosnakecase 50 | - testableexamples 51 | - gochecknoinits 52 | - prealloc 53 | - forbidigo 54 | - goprintffuncname 55 | 56 | linters-settings: 57 | govet: 58 | check-shadowing: true 59 | gocyclo: 60 | min-complexity: 10 61 | dupl: 62 | threshold: 100 63 | goconst: 64 | min-len: 8 65 | min-occurrences: 3 66 | forbidigo: 67 | #forbid: 68 | # - (Must)?NewLexer$ 69 | exclude_godoc_examples: false 70 | 71 | issues: 72 | max-per-linter: 0 73 | max-same: 0 74 | exclude-use-default: false 75 | exclude: 76 | # Captured by errcheck. 77 | - '^(G104|G204):' 78 | # Very commonly not checked. 79 | - 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked' 80 | - 'exported method (.*\.MarshalJSON|.*\.UnmarshalJSON|.*\.EntityURN|.*\.GoString|.*\.Pos) should have comment or be unexported' 81 | - 'comment on exported method' 82 | - 'composite literal uses unkeyed fields' 83 | - 'declaration of "err" shadows declaration' 84 | - 'should not use dot imports' 85 | - 'Potential file inclusion via variable' 86 | - 'should have comment or be unexported' 87 | - 'comment on exported var .* should be of the form' 88 | - 'at least one file in a package should have a package comment' 89 | - 'string literal contains the Unicode' 90 | - 'methods on the same type should have the same receiver name' 91 | - '_TokenType_name should be _TokenTypeName' 92 | - '`_TokenType_map` should be `_TokenTypeMap`' 93 | - 'rewrite if-else to switch statement' 94 | - 'comment.*should be of the form' 95 | - 'should have comment' 96 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: happy 2 | release: 3 | github: 4 | owner: thnxdev 5 | name: happy 6 | brews: 7 | - 8 | install: bin.install "happy" 9 | env: 10 | - CGO_ENABLED=0 11 | builds: 12 | - goos: 13 | - linux 14 | - darwin 15 | - windows 16 | goarch: 17 | - arm64 18 | - amd64 19 | - "386" 20 | goarm: 21 | - "6" 22 | dir: ./cmd/happy 23 | main: . 24 | ldflags: -s -w -X main.version={{.Version}} 25 | binary: happy 26 | archives: 27 | - 28 | format: tar.gz 29 | name_template: '{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ 30 | .Arm }}{{ end }}' 31 | files: 32 | - COPYING 33 | - README* 34 | snapshot: 35 | name_template: SNAPSHOT-{{ .Commit }} 36 | checksum: 37 | name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt' 38 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (C) 2023 thanks.dev 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # happy is an opinionated tool for generating request-handler boilerplate for Go 😊 [![CI](https://github.com/thnxdev/happy/actions/workflows/ci.yml/badge.svg)](https://github.com/thnxdev/happy/actions/workflows/ci.yml) 2 | 3 | happy automatically generates `http.RequestHandler` boilerplate for routing to Go 4 | methods annotated with comment directives. The generated code decodes the 5 | incoming HTTP request into the method's parameters, and encodes method return 6 | values to HTTP responses. 7 | 8 | happy only supports JSON request/response payloads. That said, see 9 | [below](#escape-hatch) for workarounds that can leverage just happy's routing. 10 | 11 | happy's generated code relies on only the standard library. 12 | 13 | Here's an example annotated method that happy will generate a request handler for: 14 | 15 | ```go 16 | //happy:api GET /users/:id 17 | func (u *UsersService) GetUser(id string) (User, error) { 18 | for _, user := range u.users { 19 | if user.ID == id { 20 | return user, nil 21 | } 22 | } 23 | return User{}, Errorf(http.StatusNotFound, "user %q not found", id) 24 | } 25 | ``` 26 | 27 | The generated request handler will map the path component `:id` to the parameter 28 | `id`, and JSON encode the response payload or error. 29 | 30 | See [below](#example) for a full example. 31 | 32 | # Status 33 | 34 | happy is usable but has some limitations and missing features: 35 | 36 | - Does not support pointers to structs for JSON request payloads, only values. 37 | - Limited (int and string) type support for path and query parameters. 38 | - Does not support embedded structs for query parameters. 39 | - A command to dump the API as an OpenAPI schema. 40 | 41 | # Protocol 42 | 43 | happy's protocol is in the form of Go comment directives. Each directive must be 44 | placed on a method, not a free function. 45 | 46 | Annotations and methods are in the following form: 47 | 48 | ```go 49 | //happy:api [