Skip to content

Commit

Permalink
feat: generate a basic swagger spec
Browse files Browse the repository at this point in the history
  • Loading branch information
Ismael GraHms committed Mar 15, 2024
1 parent c06f333 commit 447e36d
Show file tree
Hide file tree
Showing 5 changed files with 412 additions and 161 deletions.
37 changes: 27 additions & 10 deletions examples/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

type Address struct {
Name *string `json:"name"`
Name *string `json:"name" binding:"required"`
}

type Spec struct {
Expand All @@ -21,26 +21,43 @@ type Product struct {
BaseType *string `json:"@baseType" binding:"ignore"`
Type *string `json:"type" enums:"physical,digital"`
Url *string `json:"@Url" binding:"ignore"`
IsHumeid *bool `json:"isHumeid" binding:"required"`
Specification *[]Spec `json:"specification"`
Id *string `json:"id"`
Id *string `json:"id" binding:"ignore"`
}

func main() {
app := worx.NewApplication("/api", "Product Catalog API")
app := worx.NewApplication("/api", "Product Catalog API", "1.0.0", "Product Catalogue API")
productTags := router.WithTags([]string{"Product", "something"})
product := worx.NewRouter[Product, Product](app, "/products")
// scope, role =
product.HandleCreate("",
func(product Product, params *router.RequestParams) (*router.Err, *Product) {
return nil, &product
})
product.HandleRead("", handler)
//product.HandleRead("/sidy", handler)
product.HandleCreate("", createHandler, router.WithName("product name"), productTags)
product.HandleRead("", handler, productTags)
product.HandleRead("/:id/machava", handler, productTags, router.WithAllowedHeaders([]router.AllowedFields{
{
Name: "x-tower",
Description: "",
Required: false,
},
}))
err := app.Run(":8080")
if err != nil {
panic(err)
}
}

func createHandler(product Product, params *router.RequestParams) (*router.Err, *Product) {
if *product.Price < 5 {
return &router.Err{
StatusCode: 409,
ErrCode: "SOME_ERROR",
ErrReason: "Price not goood",
Message: "The message",
}, nil
}

return nil, &product
}

func handler(params *router.RequestParams) (*router.Err, *Product) {
return nil, &Product{}
}
104 changes: 89 additions & 15 deletions router/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ type Method struct {
Request interface{}
Response interface{}
Description string
Tags []string
Summery string
Configs EndpointConfigs
}

var Endpoints map[string]*Endpoint
Expand All @@ -32,10 +35,10 @@ func init() {

// we can have the global struct here
type APIEndpointer[Req, Resp any] interface {
HandleCreate(uri string, processRequest func(Req, *RequestParams) (*Err, *Resp))
HandleRead(pathSuffix string, processRequest func(*RequestParams) (*Err, *Resp))
HandleUpdate(pathString string, requestProcessor func(id string, reqBody Req, params *RequestParams) (*Err, *Resp))
HandleList(pathString string, requestProcessor func(params *RequestParams, limit int, offset int) ([]*Resp, *Err, int, int))
HandleCreate(uri string, processRequest func(Req, *RequestParams) (*Err, *Resp), opts ...HandleOption)
HandleRead(pathSuffix string, processRequest func(*RequestParams) (*Err, *Resp), opts ...HandleOption)
HandleUpdate(pathString string, requestProcessor func(id string, reqBody Req, params *RequestParams) (*Err, *Resp), opts ...HandleOption)
HandleList(pathString string, requestProcessor func(params *RequestParams, limit int, offset int) ([]*Resp, *Err, int, int), opts ...HandleOption)
}

func New[In, Out any](path string, group *gin.RouterGroup) *APIEndpoint[In, Out] {
Expand All @@ -57,10 +60,33 @@ type RequestParams struct {
Headers map[string]string
PathParams map[string]string
}
type EndpointConfigs struct {
Name string
Uri string
Tags []string
Descriptions string
AllowedHeaders []AllowedFields
AllowedParams []AllowedFields
}

type AllowedFields struct {
Name string
Description string
Required bool
}

func (r *APIEndpoint[Req, Resp]) HandleCreate(uri string, processRequest func(Req, *RequestParams) (*Err, *Resp)) {
registerEndpoint(r.Router.BasePath()+r.Path+uri, "POST", new(Req), new(Resp))
func getConfigs(opts ...HandleOption) *EndpointConfigs {
config := &EndpointConfigs{}
for _, opt := range opts {
opt(config)
}
return config
}
func (r *APIEndpoint[Req, Resp]) HandleCreate(uri string, processRequest func(Req, *RequestParams) (*Err, *Resp), opts ...HandleOption) {
config := getConfigs(opts...)
registerEndpoint(r.Router.BasePath()+r.Path+uri, "POST", new(Req), new(Resp), *config)
r.Router.POST(r.Path+uri, func(c *gin.Context) {

if statusCode, exception := r.validateJSONContentType(c); exception != nil {
c.JSON(statusCode, exception)
return
Expand All @@ -73,6 +99,7 @@ func (r *APIEndpoint[Req, Resp]) HandleCreate(uri string, processRequest func(Re
}

params := r.extractRequestParams(c)

perr, response := processRequest(requestBody, &params)
if perr != nil {
c.JSON(r.validator.ProcessorErr(perr))
Expand All @@ -84,8 +111,9 @@ func (r *APIEndpoint[Req, Resp]) HandleCreate(uri string, processRequest func(Re
})
}

func (r *APIEndpoint[Req, Resp]) HandleRead(pathSuffix string, processRequest func(*RequestParams) (*Err, *Resp)) {
registerEndpoint(r.Router.BasePath()+r.Path+pathSuffix, "GET", new(Req), new(Resp))
func (r *APIEndpoint[Req, Resp]) HandleRead(pathSuffix string, processRequest func(*RequestParams) (*Err, *Resp), opts ...HandleOption) {
config := getConfigs(opts...)
registerEndpoint(r.Router.BasePath()+r.Path+pathSuffix, "GET", new(Req), new(Resp), *config)
r.Router.GET(r.Path+pathSuffix, func(c *gin.Context) {
reqValues := r.extractRequestParams(c)
perr, resp := processRequest(&reqValues)
Expand All @@ -111,8 +139,9 @@ func (r *APIEndpoint[Req, Resp]) HandleRead(pathSuffix string, processRequest fu
})
}

func (r *APIEndpoint[Req, Resp]) HandleUpdate(pathString string, requestProcessor func(id string, reqBody Req, params *RequestParams) (*Err, *Resp)) {
registerEndpoint(r.Router.BasePath()+r.Path+pathString, "PATCH", new(Req), new(Resp))
func (r *APIEndpoint[Req, Resp]) HandleUpdate(pathString string, requestProcessor func(id string, reqBody Req, params *RequestParams) (*Err, *Resp), opts ...HandleOption) {
config := getConfigs(opts...)
registerEndpoint(r.Router.BasePath()+r.Path+pathString, "PATCH", new(Req), new(Resp), *config)
binder := godantic.Validate{}
binder.IgnoreRequired = true
binder.IgnoreMinLen = true
Expand Down Expand Up @@ -150,8 +179,9 @@ func (r *APIEndpoint[Req, Resp]) convertToMap(obj interface{}) map[string]interf
return data
}

func (r *APIEndpoint[Req, Resp]) HandleList(pathString string, requestProcessor func(params *RequestParams, limit int, offset int) ([]*Resp, *Err, int, int)) {
registerEndpoint(r.Router.BasePath()+r.Path+pathString, "POST", new(Req), new(Resp))
func (r *APIEndpoint[Req, Resp]) HandleList(pathString string, requestProcessor func(params *RequestParams, limit int, offset int) ([]*Resp, *Err, int, int), opts ...HandleOption) {
config := getConfigs(opts...)
registerEndpoint(r.Router.BasePath()+r.Path+pathString, "GET", new(Req), new(Resp), *config)
r.Router.GET(r.Path+pathString, func(c *gin.Context) {
params := r.extractRequestParams(c)
limit, err := strconv.Atoi(c.DefaultQuery("limit", "30"))
Expand Down Expand Up @@ -267,7 +297,7 @@ func ErrorBuilder(errCode, errReason, message string, statusCode int) *Err {
}
}

func registerEndpoint(path, method string, request, response interface{}) {
func registerEndpoint(path, method string, request, response interface{}, config EndpointConfigs) {
// Check if the endpoint already exists
if endpoint, ok := Endpoints[path]; ok {
// Check if the method already exists for this endpoint
Expand All @@ -282,7 +312,10 @@ func registerEndpoint(path, method string, request, response interface{}) {
HTTPMethod: method,
Request: request,
Response: response,
Description: "",
Description: config.Descriptions,
Tags: config.Tags,
Summery: config.Name,
Configs: config,
})
} else {
// If the endpoint doesn't exist, create a new entry with the method
Expand All @@ -293,9 +326,50 @@ func registerEndpoint(path, method string, request, response interface{}) {
HTTPMethod: method,
Request: request,
Response: response,
Description: "",
Description: config.Descriptions,
Tags: config.Tags,
Summery: config.Name,
Configs: config,
},
},
}
}
}

type HandleOption func(*EndpointConfigs)

func WithName(name string) HandleOption {
return func(c *EndpointConfigs) {
c.Name = name
}
}

func WithURI(uri string) HandleOption {
return func(c *EndpointConfigs) {
c.Uri = uri
}
}

func WithTags(tags []string) HandleOption {
return func(c *EndpointConfigs) {
c.Tags = tags
}
}

func WithDescriptions(descriptions string) HandleOption {
return func(c *EndpointConfigs) {
c.Descriptions = descriptions
}
}

func WithAllowedHeaders(headers []AllowedFields) HandleOption {
return func(c *EndpointConfigs) {
c.AllowedHeaders = headers
}
}

func WithAllowedParams(params []AllowedFields) HandleOption {
return func(c *EndpointConfigs) {
c.AllowedParams = params
}
}
16 changes: 8 additions & 8 deletions router/endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestCreate(t *testing.T) {
}

// Register the request processor function with the APIEndpoint instance
e.HandleCreate("", requestProcessor)
e.HandleCreate("", EndpointConfigs{}, requestProcessor)

// HandleCreate a mock HTTP POST request
req, err := http.NewRequest(http.MethodPost, "/foo", bytes.NewBuffer([]byte(`{"foo":"bar","baz":123}`)))
Expand Down Expand Up @@ -269,7 +269,7 @@ func TestShouldTryCreateWithWrongJsonFormat(t *testing.T) {
}

// Register the request processor function with the APIEndpoint instance
e.HandleCreate("", requestProcessor)
e.HandleCreate("", EndpointConfigs{}, requestProcessor)

// HandleCreate a mock HTTP POST request
req, err := http.NewRequest(http.MethodPost, "/foo",
Expand Down Expand Up @@ -312,7 +312,7 @@ func TestShouldTryCreateWithEmptyFIeld(t *testing.T) {
}

// Register the request processor function with the APIEndpoint instance
e.HandleCreate("", requestProcessor)
e.HandleCreate("", EndpointConfigs{}, requestProcessor)

// HandleCreate a mock HTTP POST request
req, err := http.NewRequest(http.MethodPost, "/foo",
Expand Down Expand Up @@ -354,7 +354,7 @@ func TestShouldTryCreateWithExtraField(t *testing.T) {
}

// Register the request processor function with the APIEndpoint instance
e.HandleCreate("", requestProcessor)
e.HandleCreate("", EndpointConfigs{}, requestProcessor)

// HandleCreate a mock HTTP POST request
req, err := http.NewRequest(http.MethodPost, "/foo",
Expand Down Expand Up @@ -402,7 +402,7 @@ func TestShouldTryCreateWithRequestProcessorError(t *testing.T) {
}

// Register the request processor function with the APIEndpoint instance
e.HandleCreate("", requestProcessor)
e.HandleCreate("", EndpointConfigs{}, requestProcessor)

// HandleCreate a mock HTTP POST request
req, err := http.NewRequest(http.MethodPost, "/foo",
Expand Down Expand Up @@ -444,7 +444,7 @@ func TestCreateWithInvalidBody(t *testing.T) {
}

// Register the request processor function with the APIEndpoint instance
e.HandleCreate("", requestProcessor)
e.HandleCreate("", EndpointConfigs{}, requestProcessor)

// HandleCreate a mock HTTP POST request
req, err := http.NewRequest(http.MethodPost, "/foo", bytes.NewBuffer([]byte(`{"fo}`)))
Expand Down Expand Up @@ -485,7 +485,7 @@ func TestCreateWithEmptyBody(t *testing.T) {
}

// Register the request processor function with the APIEndpoint instance
e.HandleCreate("", requestProcessor)
e.HandleCreate("", EndpointConfigs{}, requestProcessor)

// HandleCreate a mock HTTP POST request
req, err := http.NewRequest(http.MethodPost, "/foo", bytes.NewBuffer([]byte(`{}`)))
Expand Down Expand Up @@ -525,7 +525,7 @@ func TestCreateWithWrongContentType(t *testing.T) {
}

// Register the request processor function with the APIEndpoint instance
e.HandleCreate("", requestProcessor)
e.HandleCreate("", EndpointConfigs{}, requestProcessor)

// HandleCreate a mock HTTP POST request
req, err := http.NewRequest(http.MethodPost, "/foo", bytes.NewBuffer([]byte(`{"foo":"bar","baz":123}`)))
Expand Down
Loading

0 comments on commit 447e36d

Please sign in to comment.