Skip to content

Commit 693b948

Browse files
committed
fix: initial checkin
0 parents  commit 693b948

33 files changed

Lines changed: 4754 additions & 0 deletions

.github/workflows/go-test.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Go Test
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled]
8+
9+
permissions:
10+
contents: write
11+
12+
jobs:
13+
test:
14+
runs-on: ubuntu-latest
15+
name: Run Coverage Tests and update badge
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
with:
20+
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token.
21+
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
22+
23+
- name: Setup go
24+
uses: actions/setup-go@v4
25+
with:
26+
go-version-file: "go.mod"
27+
28+
- name: Run Test
29+
run: |
30+
go test -coverpkg ./... -cover -coverprofile=coverage.out -v ./...
31+
go tool cover -func=coverage.out -o=coverage.out
32+
33+
- name: Go Coverage Badge # Pass the `coverage.out` output to this action
34+
uses: tj-actions/coverage-badge-go@v2
35+
with:
36+
filename: coverage.out
37+
38+
- name: Verify Changed files
39+
uses: tj-actions/verify-changed-files@v16
40+
id: verify-changed-files
41+
with:
42+
files: README.md
43+
44+
- name: Commit changes
45+
if: steps.verify-changed-files.outputs.files_changed == 'true'
46+
run: |
47+
git config --local user.email "action@github.com"
48+
git config --local user.name "GitHub Action"
49+
git add README.md
50+
git commit -m "chore: Updated coverage badge."
51+
52+
- name: Push changes
53+
if: steps.verify-changed-files.outputs.files_changed == 'true'
54+
uses: ad-m/github-push-action@master
55+
with:
56+
github_token: ${{ github.token }}
57+
branch: ${{ github.head_ref }}

.gitignore

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# If you prefer the allow list template instead of the deny list, see community template:
2+
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3+
#
4+
# Binaries for programs and plugins
5+
*.exe
6+
*.exe~
7+
*.dll
8+
*.so
9+
*.dylib
10+
11+
# Test binary, built with `go test -c`
12+
*.test
13+
14+
# Output of the go coverage tool, specifically when used with LiteIDE
15+
*.out
16+
17+
# Dependency directories (remove the comment below to include it)
18+
# vendor/
19+
20+
# Go workspace file
21+
go.work
22+
tmp/

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Changelog
2+
3+
## [Unreleased]

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Sattvik Chakravarthy
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
test:
2+
go test -v ./...
3+
4+
test-coverage:
5+
go test -coverpkg ./... -cover -coverprofile=coverage.out -v ./...
6+
go tool cover -html=coverage.out

README.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Kha - Simple yet powerful API framework for GO (with swagger spec generation)
2+
![Coverage](https://img.shields.io/badge/Coverage-91.6%25-brightgreen)
3+
4+
A simplapie, developer-friendly API framework for Go, featuring automatic Swagger (OpenAPI) spec generation.
5+
6+
## 🚀 Features
7+
8+
- **Intuitive handler definitions** using Go structs
9+
- **Automatic request parsing** (JSON, form, multipart, etc.)
10+
- **Zero-boilerplate route registration** for all HTTP methods
11+
- **Instant Swagger docs** generated from your handlers
12+
13+
## 🛠️ Quick Start
14+
15+
**Create a new app**
16+
17+
```go
18+
import "github.com/go-simpl/simplapi"
19+
20+
func main() {
21+
app := simplapi.New()
22+
23+
// Add routes
24+
25+
app.ListenAndServe(":8000")
26+
}
27+
```
28+
29+
Swagger UI will be instantly available on `http://localhost:8000/try` and the swagger (OpenAPI) spec on `http://localhost:8000/openapi.json`
30+
31+
**Adding routes**
32+
33+
To add routes, call the appropriate methods on the app.
34+
35+
```go
36+
app.GET(path, tags, handlers...)
37+
app.POST(path, tags, handlers...)
38+
app.PUT(path, tags, handlers...)
39+
// and so on
40+
```
41+
42+
path: `string` - path to handle
43+
44+
tags: `[]string` - swagger tags
45+
46+
handlers: `[]interface{}` - functions that handle the request
47+
48+
### Handler
49+
50+
Handler function is the important building block that controls how the endpoints are processed and the specs are generated. The function should be of the following format:
51+
52+
```go
53+
func HandlerFunc(input InputType) (*OutputType1, *OutputType2, error) {
54+
// Handler code
55+
}
56+
```
57+
58+
The function must return one of the output types or error. The function must, at the least, return error.
59+
60+
Output type will be returned as `JSON` response by marshalling the struct.
61+
62+
Additionally, if the Output type defines GetStatusCode function, that will be used to set the status of the response.
63+
64+
If you provide multiple handler functions for a route, they are called one after another in the order you specify. Each handler is executed until one of them returns a non-nil output or an error. As soon as a handler returns a result (any non-nil output or error), the chain stops and no further handlers are called. If a handler returns only nil values, the next handler in the list will be executed.
65+
66+
#### InputType definition
67+
68+
Input for the API can be defined by struct field tags.
69+
70+
**Query Params**
71+
72+
```go
73+
type InputType struct {
74+
PageNum int `query:"page_num"`
75+
PageSize int `query:"page_size"`
76+
}
77+
```
78+
79+
**Path Params**
80+
81+
```go
82+
// Assuming path is `/books/:book_id`
83+
type InputType struct {
84+
BookId string `path:"book_id"`
85+
}
86+
```
87+
88+
**Headers**
89+
90+
```go
91+
type InputType struct {
92+
ApiKey string `header:"X-API-Key"`
93+
}
94+
```
95+
96+
**JSON Body**
97+
98+
```go
99+
type InputType struct {
100+
Body struct {
101+
Title string `json:"title"`
102+
// ... JSON fields here
103+
} `body:"json"`
104+
}
105+
```
106+
107+
**URL Encoded from**
108+
109+
```go
110+
type InputType struct {
111+
Form struct {
112+
Email string `form:"email"`
113+
Password string `form:"password"`
114+
} `body:"urlencoded"`
115+
}
116+
```
117+
118+
**Multipart form**
119+
120+
```go
121+
type InputType struct {
122+
Form struct {
123+
UploadedFile *multipart.FileHeader `form:"file"`
124+
}
125+
}
126+
```

ROADMAP.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Roadmap
2+
3+
## Features
4+
5+
- [ ] Shared context
6+
- [ ] Response Headers
7+
- [ ] Non-json responses
8+
- [ ] Middleware
9+
- [ ] Cookie as param injection
10+
- [ ] Response Cookies
11+
- [ ] Streaming responses
12+
13+
## Framework support
14+
15+
- [ ] Gin - https://github.com/gin-gonic/gin - 83k
16+
- [-] Fiber - https://github.com/gofiber/fiber - 37k
17+
- [ ] Beego - https://github.com/beego/beego - 32k
18+
- [ ] Echo - https://github.com/labstack/echo - 31k
19+
- [ ] go-zero - https://github.com/zeromicro/go-zero - 31k
20+
- [ ] go-kit - https://github.com/go-kit/kit - 27k
21+
- [ ] kratos - https://github.com/go-kratos/kratos - 24k
22+
- [ ] fasthttp - https://github.com/valyala/fasthttp - 22k
23+
- [ ] gorilla/mux - https://github.com/gorilla/mux - 21.5k
24+
- [ ] chi - https://github.com/go-chi/chi - 20k
25+
- [ ] httprouter - https://github.com/julienschmidt/httprouter - 17k
26+
- [ ] http (built-in)
27+
28+
Reference: https://github.com/mingrammer/go-web-framework-stars
29+
30+
## Performance Benchmark
31+
32+
- Refer: https://github.com/julienschmidt/go-http-routing-benchmark

app.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package simplapi
2+
3+
import (
4+
"github.com/go-simpl/simplapi/pkg/framework"
5+
"github.com/go-simpl/simplapi/pkg/handler"
6+
"github.com/go-simpl/simplapi/pkg/swagger"
7+
)
8+
9+
type App struct {
10+
framework framework.Framework
11+
swaggerJson map[string]interface{}
12+
}
13+
14+
func New(frameworkName ...string) *App {
15+
frameworkName = append(frameworkName, "fiber")
16+
17+
s := &App{
18+
framework: framework.GetFramework(frameworkName[0]),
19+
swaggerJson: map[string]interface{}{
20+
"openapi": "3.0.0",
21+
"info": map[string]interface{}{
22+
"title": "SimpleAPI",
23+
"version": "1.0.0",
24+
},
25+
"paths": map[string]interface{}{},
26+
},
27+
}
28+
addSwaggerRoutes(s)
29+
return s
30+
}
31+
32+
func (s *App) GetApp() framework.Framework {
33+
return s.framework
34+
}
35+
36+
func (s *App) ListenAndServe(addr string) error {
37+
return s.framework.ListenAndServe(addr)
38+
}
39+
40+
func (s *App) createHandler(handlers ...interface{}) framework.FrameworkHandler {
41+
// Reverse the handlers slice
42+
for i, j := 0, len(handlers)-1; i < j; i, j = i+1, j-1 {
43+
handlers[i], handlers[j] = handlers[j], handlers[i]
44+
}
45+
46+
var nextHandler framework.FrameworkHandler = nil
47+
for _, h := range handlers {
48+
nextHandler = handler.WrapHandler(h, nextHandler)
49+
}
50+
51+
return nextHandler
52+
}
53+
54+
func (s *App) GET(path string, tags []string, handlers ...interface{}) {
55+
s.framework.GET(path, s.createHandler(handlers...))
56+
s.addToSwagger(path, "get", handlers, tags)
57+
}
58+
59+
func (s *App) POST(path string, tags []string, handlers ...interface{}) {
60+
s.framework.POST(path, s.createHandler(handlers...))
61+
s.addToSwagger(path, "post", handlers, tags)
62+
}
63+
64+
func (s *App) PUT(path string, tags []string, handlers ...interface{}) {
65+
s.framework.PUT(path, s.createHandler(handlers...))
66+
s.addToSwagger(path, "put", handlers, tags)
67+
}
68+
69+
func (s *App) PATCH(path string, tags []string, handlers ...interface{}) {
70+
s.framework.PATCH(path, s.createHandler(handlers...))
71+
s.addToSwagger(path, "patch", handlers, tags)
72+
}
73+
74+
func (s *App) DELETE(path string, tags []string, handlers ...interface{}) {
75+
s.framework.DELETE(path, s.createHandler(handlers...))
76+
s.addToSwagger(path, "delete", handlers, tags)
77+
}
78+
79+
func (s *App) addToSwagger(path string, method string, handlers []interface{}, tags []string) {
80+
if path == "/try" || path == "/openapi.json" {
81+
return
82+
}
83+
84+
definition := map[string]interface{}{
85+
"parameters": []interface{}{},
86+
"responses": map[string]interface{}{},
87+
"tags": tags,
88+
}
89+
90+
if method != "get" {
91+
definition["requestBody"] = map[string]interface{}{}
92+
}
93+
94+
for _, handler := range handlers {
95+
swagger.UpdateDefinitionUsingHandler(definition, handler)
96+
}
97+
98+
if _, ok := s.swaggerJson["paths"].(map[string]interface{})[path]; !ok {
99+
s.swaggerJson["paths"].(map[string]interface{})[path] = map[string]interface{}{}
100+
}
101+
102+
if _, ok := s.swaggerJson["paths"].(map[string]interface{})[path].(map[string]interface{})[method]; !ok {
103+
s.swaggerJson["paths"].(map[string]interface{})[path].(map[string]interface{})[method] = definition
104+
}
105+
106+
responses := definition["responses"].(map[string]interface{})
107+
for _, handler := range handlers {
108+
swagger.UpdateResponseDefinitionUsingHandler(responses, handler)
109+
}
110+
}

0 commit comments

Comments
 (0)