Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 246 additions & 1 deletion app.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"errors"
"fmt"
"io"
"mime"
"net"
"net/http"
"net/http/httputil"
Expand Down Expand Up @@ -778,6 +779,250 @@
return app
}

// Summary assigns a short summary to the most recently added route.
func (app *App) Summary(sum string) Router {
app.mutex.Lock()
app.latestRoute.Summary = sum
app.mutex.Unlock()
return app
}

// Description assigns a description to the most recently added route.
func (app *App) Description(desc string) Router {
app.mutex.Lock()
app.latestRoute.Description = desc
app.mutex.Unlock()
return app
}

// Consumes assigns a request media type to the most recently added route.
func (app *App) Consumes(typ string) Router {
if typ != "" {
if _, _, err := mime.ParseMediaType(typ); err != nil || !strings.Contains(typ, "/") {
panic("invalid media type: " + typ)
}
}
app.mutex.Lock()
app.latestRoute.Consumes = typ
app.mutex.Unlock()
return app
}

// Produces assigns a response media type to the most recently added route.
func (app *App) Produces(typ string) Router {
if typ != "" {
if _, _, err := mime.ParseMediaType(typ); err != nil || !strings.Contains(typ, "/") {
panic("invalid media type: " + typ)
}
}
app.mutex.Lock()
app.latestRoute.Produces = typ
app.mutex.Unlock()
return app
}

// RequestBody documents the request payload for the most recently added route.
func (app *App) RequestBody(description string, required bool, mediaTypes ...string) Router {
return app.RequestBodyWithExample(description, required, nil, "", nil, nil, mediaTypes...)
}

// RequestBodyWithExample documents the request payload with schema references and examples.
func (app *App) RequestBodyWithExample(description string, required bool, schema map[string]any, schemaRef string, example any, examples map[string]any, mediaTypes ...string) Router {
sanitized := sanitizeRequiredMediaTypes(mediaTypes)

body := &RouteRequestBody{
Description: description,
Required: required,
MediaTypes: append([]string(nil), sanitized...),
SchemaRef: schemaRef,
Example: example,
Examples: copyAnyMap(examples),
}
if schemaRef != "" {
body.Schema = map[string]any{"$ref": schemaRef}
} else if len(schema) > 0 {
body.Schema = copyAnyMap(schema)
}

app.mutex.Lock()
app.latestRoute.RequestBody = body
if len(sanitized) > 0 {
app.latestRoute.Consumes = sanitized[0]
}
app.mutex.Unlock()

return app
}

// Parameter documents an input parameter for the most recently added route.
func (app *App) Parameter(name, in string, required bool, schema map[string]any, description string) Router {
return app.addParameter(name, in, required, schema, "", description, nil, nil)
}

// ParameterWithExample documents an input parameter, including schema references and examples.
func (app *App) ParameterWithExample(name, in string, required bool, schema map[string]any, schemaRef string, description string, example any, examples map[string]any) Router {
return app.addParameter(name, in, required, schema, schemaRef, description, example, examples)
}

func (app *App) addParameter(name, in string, required bool, schema map[string]any, schemaRef string, description string, example any, examples map[string]any) Router {
if strings.TrimSpace(name) == "" {
panic("parameter name is required")
}

location := strings.ToLower(strings.TrimSpace(in))
switch location {
case "path", "query", "header", "cookie":
default:
panic("invalid parameter location: " + in)
}

if schemaRef != "" {
schema = map[string]any{"$ref": schemaRef}
} else if schema == nil {
schema = map[string]any{"type": "string"}
}

schemaCopy := copyAnyMap(schema)
if schemaCopy == nil {
schemaCopy = map[string]any{"type": "string"}
}
if schemaRef == "" {
if _, ok := schemaCopy["type"]; !ok {
schemaCopy["type"] = "string"
}
}

if location == "path" {
required = true
}

param := RouteParameter{
Name: name,
In: location,
Required: required,
Description: description,
Schema: schemaCopy,
SchemaRef: schemaRef,
Example: example,
Examples: copyAnyMap(examples),
}

app.mutex.Lock()
app.latestRoute.Parameters = append(app.latestRoute.Parameters, param)
app.mutex.Unlock()

return app
}

// Response documents an HTTP response for the most recently added route.
func (app *App) Response(status int, description string, mediaTypes ...string) Router {
return app.addResponse(status, description, nil, "", nil, nil, mediaTypes...)
}

// ResponseWithExample documents an HTTP response with schema references and examples.
func (app *App) ResponseWithExample(status int, description string, schema map[string]any, schemaRef string, example any, examples map[string]any, mediaTypes ...string) Router {
return app.addResponse(status, description, schema, schemaRef, example, examples, mediaTypes...)
}

func (app *App) addResponse(status int, description string, schema map[string]any, schemaRef string, example any, examples map[string]any, mediaTypes ...string) Router {
if status != 0 && (status < 100 || status > 599) {
panic("invalid status code")
}

sanitized := sanitizeMediaTypes(mediaTypes)

if description == "" {
if status == 0 {
description = "Default response"
} else if text := http.StatusText(status); text != "" {
description = text
} else {
description = "Status " + strconv.Itoa(status)
}
}

key := "default"
if status > 0 {
key = strconv.Itoa(status)
}

resp := RouteResponse{Description: description}
if len(sanitized) > 0 {
resp.MediaTypes = append([]string(nil), sanitized...)
}
if schemaRef != "" {
resp.SchemaRef = schemaRef
resp.Schema = map[string]any{"$ref": schemaRef}
} else if len(schema) > 0 {
resp.Schema = copyAnyMap(schema)
}
resp.Example = example
resp.Examples = copyAnyMap(examples)

app.mutex.Lock()
if app.latestRoute.Responses == nil {
app.latestRoute.Responses = make(map[string]RouteResponse)
}
app.latestRoute.Responses[key] = resp
if status == StatusOK && len(resp.MediaTypes) > 0 {
app.latestRoute.Produces = resp.MediaTypes[0]
}
app.mutex.Unlock()

return app
}

func sanitizeMediaTypes(mediaTypes []string) []string {
if len(mediaTypes) == 0 {
return nil
}

seen := make(map[string]struct{}, len(mediaTypes))
sanitized := make([]string, 0, len(mediaTypes))
for _, typ := range mediaTypes {
trimmed := strings.TrimSpace(typ)
if trimmed == "" {
continue
}
if _, _, err := mime.ParseMediaType(trimmed); err != nil || !strings.Contains(trimmed, "/") {
panic("invalid media type: " + trimmed)
}
if _, ok := seen[trimmed]; ok {
continue
}
seen[trimmed] = struct{}{}
sanitized = append(sanitized, trimmed)
}
if len(sanitized) == 0 {
return nil
}
return sanitized
}

func sanitizeRequiredMediaTypes(mediaTypes []string) []string {
sanitized := sanitizeMediaTypes(mediaTypes)
if len(sanitized) == 0 {
panic("at least one media type must be provided")
}
return sanitized
}

// Tags assigns tags to the most recently added route.
func (app *App) Tags(tags ...string) Router {
app.mutex.Lock()
app.latestRoute.Tags = tags
app.mutex.Unlock()
return app
}

// Deprecated marks the most recently added route as deprecated.
func (app *App) Deprecated() Router {
app.mutex.Lock()
app.latestRoute.Deprecated = true
app.mutex.Unlock()
return app
}

// GetRoute Get route by name
func (app *App) GetRoute(name string) Route {
for _, routes := range app.stack {
Expand Down Expand Up @@ -834,7 +1079,7 @@
var prefix string
var subApp *App
var prefixes []string
var handlers []Handler
var handlers []any

for i := range args {
switch arg := args[i].(type) {
Expand Down Expand Up @@ -862,7 +1107,7 @@
return app.mount(prefix, subApp)
}

app.register([]string{methodUse}, prefix, nil, handlers...)

Check failure on line 1110 in app.go

View workflow job for this annotation

GitHub Actions / modernize

cannot use handlers (variable of type []any) as []Handler value in argument to app.register

Check failure on line 1110 in app.go

View workflow job for this annotation

GitHub Actions / unit (1.25.x, ubuntu-latest)

cannot use handlers (variable of type []any) as []Handler value in argument to app.register

Check failure on line 1110 in app.go

View workflow job for this annotation

GitHub Actions / govulncheck-check

cannot use handlers (variable of type []any) as []Handler value in argument to app.register

Check failure on line 1110 in app.go

View workflow job for this annotation

GitHub Actions / repeated

cannot use handlers (variable of type []any) as []Handler value in argument to app.register

Check failure on line 1110 in app.go

View workflow job for this annotation

GitHub Actions / Compare

cannot use handlers (variable of type []any) as []Handler value in argument to app.register
}

return app
Expand Down
Loading
Loading