Skip to content

Commit afa2467

Browse files
committed
feat: enhance API with OpenAPI support and refactor endpoint handling
1 parent e27913e commit afa2467

32 files changed

Lines changed: 1443 additions & 1236 deletions

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@
1919

2020
# Go workspace file
2121
go.work
22-
tmp/
22+
tmp/
23+
24+
coverage.*

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ test:
33

44
test-coverage:
55
go test -coverpkg ./... -cover -coverprofile=coverage.out -v ./...
6-
go tool cover -html=coverage.out
6+
go tool cover -html=coverage.out -o=coverage.html
7+
go tool cover -func=coverage.out -o=coverage.out
8+
grep -v '100.0%$$' coverage.out

README.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,14 @@ Swagger UI will be instantly available on `http://localhost:8000/try` and the sw
3737
To add routes, call the appropriate methods on the app.
3838

3939
```go
40-
app.GET(path, tags, handlers...)
41-
app.POST(path, tags, handlers...)
42-
app.PUT(path, tags, handlers...)
40+
app.GET(path, handlers...)
41+
app.POST(path, handlers...)
42+
app.PUT(path, handlers...)
4343
// and so on
4444
```
4545

4646
path: `string` - path to handle
4747

48-
tags: `[]string` - swagger tags
49-
5048
handlers: `[]interface{}` - functions that handle the request
5149

5250
### Handler
@@ -125,6 +123,6 @@ type InputType struct {
125123
type InputType struct {
126124
Form struct {
127125
UploadedFile *multipart.FileHeader `form:"file"`
128-
}
126+
} `body:"multipart"`
129127
}
130128
```

ROADMAP.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@
33
## Features
44

55
- [ ] Configurable Swagger Spec
6+
- [ ] Swagger features
7+
- [ ] Info
8+
- [ ] Servers
9+
- [ ] Security Scheme
10+
- [ ] Tags
11+
- [ ] Endpoint: Summary, Description, OperationId
12+
613
- [ ] Swagger descriptions
14+
- [ ] Input / Output validation
715
- [ ] Response Headers
816
- [ ] Non-json responses
917
- [ ] Middleware
@@ -13,8 +21,8 @@
1321

1422
## Framework support
1523

16-
- [-] Gin - https://github.com/gin-gonic/gin - 83k
17-
- [-] Fiber - https://github.com/gofiber/fiber - 37k
24+
- [x] Gin - https://github.com/gin-gonic/gin - 83k
25+
- [x] Fiber - https://github.com/gofiber/fiber - 37k
1826
- [ ] Beego - https://github.com/beego/beego - 32k
1927
- [ ] Echo - https://github.com/labstack/echo - 31k
2028
- [ ] go-zero - https://github.com/zeromicro/go-zero - 31k

app.go

Lines changed: 53 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,96 @@
11
package simplapi
22

33
import (
4+
"net/http"
5+
46
"github.com/go-simpl/simplapi/pkg/framework"
57
"github.com/go-simpl/simplapi/pkg/handler"
6-
"github.com/go-simpl/simplapi/pkg/swagger"
8+
"github.com/go-simpl/simplapi/pkg/spec"
79
)
810

911
type App struct {
10-
framework framework.Framework
11-
swaggerJson map[string]interface{}
12+
framework framework.Framework
13+
spec *spec.Spec
14+
15+
endpoints []*Endpoint
1216
}
1317

1418
func New(frameworkName ...string) *App {
1519
frameworkName = append(frameworkName, "fiber")
1620

1721
s := &App{
1822
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-
},
23+
spec: spec.New(),
24+
endpoints: []*Endpoint{},
2725
}
28-
addSwaggerRoutes(s)
26+
addOpenAPIRoutes(s)
2927
return s
3028
}
3129

3230
func (s *App) GetApp() framework.Framework {
3331
return s.framework
3432
}
3533

36-
func (s *App) ListenAndServe(addr string) error {
37-
return s.framework.ListenAndServe(addr)
34+
func (s *App) GET(path string, handlers ...interface{}) *Endpoint {
35+
endpoint := newEndpoint(http.MethodGet, path, handlers...)
36+
s.addEndpoint(endpoint)
37+
return endpoint
3838
}
3939

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)
40+
func (s *App) POST(path string, handlers ...interface{}) *Endpoint {
41+
endpoint := newEndpoint(http.MethodPost, path, handlers...)
42+
s.addEndpoint(endpoint)
43+
return endpoint
5744
}
5845

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)
46+
func (s *App) PUT(path string, handlers ...interface{}) *Endpoint {
47+
endpoint := newEndpoint(http.MethodPut, path, handlers...)
48+
s.addEndpoint(endpoint)
49+
return endpoint
6250
}
6351

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)
52+
func (s *App) DELETE(path string, handlers ...interface{}) *Endpoint {
53+
endpoint := newEndpoint(http.MethodDelete, path, handlers...)
54+
s.addEndpoint(endpoint)
55+
return endpoint
6756
}
6857

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)
58+
func (s *App) PATCH(path string, handlers ...interface{}) *Endpoint {
59+
endpoint := newEndpoint(http.MethodPatch, path, handlers...)
60+
s.addEndpoint(endpoint)
61+
return endpoint
7262
}
7363

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
64+
func (s *App) Sync() {
65+
for _, e := range s.endpoints {
66+
s.framework.Register(e.path, e.method, s.createHandler(e.handlers...))
67+
if e.addToSpec {
68+
s.spec.Register(e.path, e.method, e.handlers...)
69+
}
8270
}
71+
}
8372

84-
definition := map[string]interface{}{
85-
"parameters": []interface{}{},
86-
"responses": map[string]interface{}{},
87-
"tags": tags,
88-
}
73+
func (s *App) ListenAndServe(addr string) error {
74+
// Actual registration of endpoints happen here
75+
s.Sync()
8976

90-
if method != "get" {
91-
definition["requestBody"] = map[string]interface{}{}
92-
}
77+
return s.framework.ListenAndServe(addr)
78+
}
9379

94-
for _, handler := range handlers {
95-
swagger.UpdateDefinitionUsingHandler(definition, handler)
96-
}
80+
func (s *App) addEndpoint(endpoint *Endpoint) {
81+
s.endpoints = append(s.endpoints, endpoint)
82+
}
9783

98-
if _, ok := s.swaggerJson["paths"].(map[string]interface{})[path]; !ok {
99-
s.swaggerJson["paths"].(map[string]interface{})[path] = map[string]interface{}{}
84+
func (s *App) createHandler(handlers ...interface{}) framework.FrameworkHandler {
85+
// Reverse the handlers slice
86+
for i, j := 0, len(handlers)-1; i < j; i, j = i+1, j-1 {
87+
handlers[i], handlers[j] = handlers[j], handlers[i]
10088
}
10189

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
90+
var nextHandler framework.FrameworkHandler = nil
91+
for _, h := range handlers {
92+
nextHandler = handler.WrapHandler(h, nextHandler)
10493
}
10594

106-
responses := definition["responses"].(map[string]interface{})
107-
for _, handler := range handlers {
108-
swagger.UpdateResponseDefinitionUsingHandler(responses, handler)
109-
}
95+
return nextHandler
11096
}

endpoint.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package simplapi
2+
3+
type Endpoint struct {
4+
method string
5+
path string
6+
tags []string
7+
summary string
8+
description string
9+
operationId string
10+
handlers []interface{}
11+
addToSpec bool
12+
}
13+
14+
func newEndpoint(method string, path string, handlers ...interface{}) *Endpoint {
15+
return &Endpoint{
16+
method: method,
17+
path: path,
18+
handlers: handlers,
19+
addToSpec: true,
20+
}
21+
}
22+
23+
func (e *Endpoint) WithTag(tag string) *Endpoint {
24+
e.tags = append(e.tags, tag)
25+
return e
26+
}
27+
28+
func (e *Endpoint) WithSummary(summary string) *Endpoint {
29+
e.summary = summary
30+
return e
31+
}
32+
33+
func (e *Endpoint) WithDescription(description string) *Endpoint {
34+
e.description = description
35+
return e
36+
}
37+
38+
func (e *Endpoint) WithOperationId(operationId string) *Endpoint {
39+
e.operationId = operationId
40+
return e
41+
}
42+
43+
func (e *Endpoint) WithoutSpec() *Endpoint {
44+
e.addToSpec = false
45+
return e
46+
}

go.mod

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/go-simpl/simplapi
33
go 1.24
44

55
require (
6+
github.com/getkin/kin-openapi v0.132.0
67
github.com/gin-gonic/gin v1.10.1
78
github.com/gofiber/fiber/v2 v2.52.9
89
github.com/stretchr/testify v1.10.0
@@ -17,21 +18,29 @@ require (
1718
github.com/davecgh/go-spew v1.1.1 // indirect
1819
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
1920
github.com/gin-contrib/sse v0.1.0 // indirect
21+
github.com/go-openapi/jsonpointer v0.21.0 // indirect
22+
github.com/go-openapi/swag v0.23.0 // indirect
2023
github.com/go-playground/locales v0.14.1 // indirect
2124
github.com/go-playground/universal-translator v0.18.1 // indirect
2225
github.com/go-playground/validator/v10 v10.20.0 // indirect
2326
github.com/goccy/go-json v0.10.2 // indirect
2427
github.com/google/uuid v1.6.0 // indirect
28+
github.com/josharian/intern v1.0.0 // indirect
2529
github.com/json-iterator/go v1.1.12 // indirect
2630
github.com/klauspost/compress v1.17.9 // indirect
2731
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
2832
github.com/leodido/go-urn v1.4.0 // indirect
33+
github.com/mailru/easyjson v0.7.7 // indirect
2934
github.com/mattn/go-colorable v0.1.13 // indirect
3035
github.com/mattn/go-isatty v0.0.20 // indirect
3136
github.com/mattn/go-runewidth v0.0.16 // indirect
3237
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
3338
github.com/modern-go/reflect2 v1.0.2 // indirect
39+
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
40+
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
41+
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
3442
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
43+
github.com/perimeterx/marshmallow v1.1.5 // indirect
3544
github.com/pmezard/go-difflib v1.0.0 // indirect
3645
github.com/rivo/uniseg v0.2.0 // indirect
3746
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect

0 commit comments

Comments
 (0)