From 35469b1e0dfc5fd88bfa21b9c0b96827990c88f5 Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Sat, 3 Dec 2022 16:15:00 +1100 Subject: [PATCH] feat: initial commit --- .github/workflows/ci.yml | 27 ++ .github/workflows/release.yml | 17 + .golangci.yml | 95 +++++ .goreleaser.yml | 37 ++ COPYING | 19 + README.md | 266 ++++++++++++++ bin/.go-1.19.3.pkg | 1 + bin/.golangci-lint-1.50.1.pkg | 1 + bin/.goreleaser-1.15.0.pkg | 1 + bin/.jq-1.6.pkg | 1 + bin/.reflex-0.3.1.pkg | 1 + bin/.shellcheck-0.8.0.pkg | 1 + bin/README.hermit.md | 7 + bin/activate-hermit | 21 ++ bin/go | 1 + bin/gofmt | 1 + bin/golangci-lint | 1 + bin/goreleaser | 1 + bin/hermit | 43 +++ bin/hermit.hcl | 3 + bin/jq | 1 + bin/reflex | 1 + bin/shellcheck | 1 + cmd/happy/main.go | 640 ++++++++++++++++++++++++++++++++++ codewriter/codewriter.go | 123 +++++++ codewriter/codewriter_test.go | 22 ++ go.mod | 19 + go.sum | 15 + reflex.conf | 5 + scripts/e2e-test | 20 ++ scripts/happy | 6 + testdata/main.go | 165 +++++++++ testdata/main_api.go | 181 ++++++++++ testdata/main_test.go | 30 ++ 34 files changed, 1774 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .golangci.yml create mode 100644 .goreleaser.yml create mode 100644 COPYING create mode 100644 README.md create mode 120000 bin/.go-1.19.3.pkg create mode 120000 bin/.golangci-lint-1.50.1.pkg create mode 120000 bin/.goreleaser-1.15.0.pkg create mode 120000 bin/.jq-1.6.pkg create mode 120000 bin/.reflex-0.3.1.pkg create mode 120000 bin/.shellcheck-0.8.0.pkg create mode 100644 bin/README.hermit.md create mode 100755 bin/activate-hermit create mode 120000 bin/go create mode 120000 bin/gofmt create mode 120000 bin/golangci-lint create mode 120000 bin/goreleaser create mode 100755 bin/hermit create mode 100644 bin/hermit.hcl create mode 120000 bin/jq create mode 120000 bin/reflex create mode 120000 bin/shellcheck create mode 100644 cmd/happy/main.go create mode 100644 codewriter/codewriter.go create mode 100644 codewriter/codewriter_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 reflex.conf create mode 100755 scripts/e2e-test create mode 100755 scripts/happy create mode 100644 testdata/main.go create mode 100644 testdata/main_api.go create mode 100644 testdata/main_test.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a4ce8bd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +on: + push: + branches: + - master + pull_request: +name: CI +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Init Hermit + run: ./bin/hermit env -r >> $GITHUB_ENV + - name: E2E Test + run: e2e-test + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Init Hermit + run: ./bin/hermit env -r >> $GITHUB_ENV + - name: golangci-lint + run: golangci-lint run diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3d06f8f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,17 @@ +name: Release +on: + push: + tags: + - 'v*' +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - run: ./bin/hermit env --raw >> $GITHUB_ENV + - run: goreleaser release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..a8d1b6c --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,95 @@ +run: + tests: true + skip-dirs: + - _examples + +output: + print-issued-lines: false + +linters: + enable-all: true + disable: + - maligned + - megacheck + - lll + - gocyclo + - dupl + - gochecknoglobals + - funlen + - godox + - wsl + - goimports + - gomnd + - gocognit + - goerr113 + - nolintlint + - testpackage + - godot + - nestif + - paralleltest + - nlreturn + - cyclop + - exhaustivestruct + - gci + - gofumpt + - errorlint + - exhaustive + - ifshort + - wrapcheck + - stylecheck + - nonamedreturns + - revive + - dupword + - exhaustruct + - varnamelen + - forcetypeassert + - ireturn + - maintidx + - govet + - nosnakecase + - testableexamples + - gochecknoinits + - prealloc + - forbidigo + - goprintffuncname + +linters-settings: + govet: + check-shadowing: true + gocyclo: + min-complexity: 10 + dupl: + threshold: 100 + goconst: + min-len: 8 + min-occurrences: 3 + forbidigo: + #forbid: + # - (Must)?NewLexer$ + exclude_godoc_examples: false + +issues: + max-per-linter: 0 + max-same: 0 + exclude-use-default: false + exclude: + # Captured by errcheck. + - '^(G104|G204):' + # Very commonly not checked. + - 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked' + - 'exported method (.*\.MarshalJSON|.*\.UnmarshalJSON|.*\.EntityURN|.*\.GoString|.*\.Pos) should have comment or be unexported' + - 'comment on exported method' + - 'composite literal uses unkeyed fields' + - 'declaration of "err" shadows declaration' + - 'should not use dot imports' + - 'Potential file inclusion via variable' + - 'should have comment or be unexported' + - 'comment on exported var .* should be of the form' + - 'at least one file in a package should have a package comment' + - 'string literal contains the Unicode' + - 'methods on the same type should have the same receiver name' + - '_TokenType_name should be _TokenTypeName' + - '`_TokenType_map` should be `_TokenTypeMap`' + - 'rewrite if-else to switch statement' + - 'comment.*should be of the form' + - 'should have comment' diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..c59c49a --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,37 @@ +project_name: happy +release: + github: + owner: thnxdev + name: happy +brews: + - + install: bin.install "happy" +env: + - CGO_ENABLED=0 +builds: +- goos: + - linux + - darwin + - windows + goarch: + - arm64 + - amd64 + - "386" + goarm: + - "6" + dir: ./cmd/happy + main: . + ldflags: -s -w -X main.version={{.Version}} + binary: happy +archives: + - + format: tar.gz + name_template: '{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ + .Arm }}{{ end }}' + files: + - COPYING + - README* +snapshot: + name_template: SNAPSHOT-{{ .Commit }} +checksum: + name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt' diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..bfa69e8 --- /dev/null +++ b/COPYING @@ -0,0 +1,19 @@ +Copyright (C) 2023 thanks.dev + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a7adbe0 --- /dev/null +++ b/README.md @@ -0,0 +1,266 @@ +# 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) + +happy automatically generates `http.RequestHandler` boilerplate for routing to Go +methods annotated with comment directives. The generated code decodes the +incoming HTTP request into the method's parameters, and encodes method return +values to HTTP responses. + +happy only supports JSON request/response payloads. That said, see +[below](#escape-hatch) for workarounds that can leverage just happy's routing. + +happy's generated code relies on only the standard library. + +Here's an example annotated method that happy will generate a request handler for: + +```go +//happy:api GET /users/:id +func (u *UsersService) GetUser(id string) (User, error) { + for _, user := range u.users { + if user.ID == id { + return user, nil + } + } + return User{}, Errorf(http.StatusNotFound, "user %q not found", id) +} +``` + +The generated request handler will map the path component `:id` to the parameter +`id`, and JSON encode the response payload or error. + +See [below](#example) for a full example. + +# Status + +happy is usable but has some limitations and missing features: + +- Does not support pointers to structs for JSON request payloads, only values. +- Limited (int and string) type support for path and query parameters. +- Does not support embedded structs for query parameters. +- A command to dump the API as an OpenAPI schema. + +# Protocol + +happy's protocol is in the form of Go comment directives. Each directive must be +placed on a method, not a free function. + +Annotations and methods are in the following form: + +```go +//happy:api [