Skip to content

Commit 701b5b3

Browse files
committed
feat: /swagger
Signed-off-by: Ian Meyer <k@imeyer.io>
1 parent 0f5351c commit 701b5b3

File tree

1 file changed

+273
-3
lines changed

1 file changed

+273
-3
lines changed

main.go

Lines changed: 273 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import (
55
"fmt"
66
"log"
77
"net/http"
8+
"strconv"
9+
"strings"
10+
"time"
811
)
912

1013
type Response struct {
@@ -14,13 +17,17 @@ type Response struct {
1417
func writeJSONResponse(w http.ResponseWriter, message string, statusCode int) {
1518
w.Header().Set("Content-Type", "application/json")
1619
w.WriteHeader(statusCode)
17-
20+
1821
response := Response{Message: message}
1922
json.NewEncoder(w).Encode(response)
2023
}
2124

2225
func okHandler(w http.ResponseWriter, r *http.Request) {
23-
writeJSONResponse(w, "OK", http.StatusOK)
26+
if r.URL.Path == "/" {
27+
writeJSONResponse(w, "OK", http.StatusOK)
28+
return
29+
}
30+
http.NotFound(w, r)
2431
}
2532

2633
func error403Handler(w http.ResponseWriter, r *http.Request) {
@@ -41,13 +48,276 @@ func redirectTwoHandler(w http.ResponseWriter, r *http.Request) {
4148
writeJSONResponse(w, message, http.StatusOK)
4249
}
4350

51+
func timeoutHandler(w http.ResponseWriter, r *http.Request) {
52+
path := strings.TrimPrefix(r.URL.Path, "/timeout/")
53+
54+
seconds, err := strconv.Atoi(path)
55+
if err != nil {
56+
writeJSONResponse(w, "Invalid timeout value. Must be a number.", http.StatusBadRequest)
57+
return
58+
}
59+
60+
if seconds < 1 || seconds >= 300 {
61+
writeJSONResponse(w, "Timeout value must be between 1 and 299 seconds.", http.StatusBadRequest)
62+
return
63+
}
64+
65+
time.Sleep(time.Duration(seconds) * time.Second)
66+
message := fmt.Sprintf("Request completed after %d seconds", seconds)
67+
writeJSONResponse(w, message, http.StatusOK)
68+
}
69+
70+
const openAPISpec = `{
71+
"openapi": "3.0.3",
72+
"info": {
73+
"title": "Best API",
74+
"description": "A simple HTTP API with various endpoints for testing",
75+
"version": "1.0.0"
76+
},
77+
"servers": [
78+
{
79+
"url": "http://localhost:9999",
80+
"description": "Development server"
81+
}
82+
],
83+
"paths": {
84+
"/": {
85+
"get": {
86+
"summary": "Health check endpoint",
87+
"description": "Returns OK status",
88+
"responses": {
89+
"200": {
90+
"description": "Successful response",
91+
"content": {
92+
"application/json": {
93+
"schema": {
94+
"$ref": "#/components/schemas/Response"
95+
},
96+
"example": {
97+
"message": "OK"
98+
}
99+
}
100+
}
101+
}
102+
}
103+
}
104+
},
105+
"/error403": {
106+
"get": {
107+
"summary": "Forbidden error endpoint",
108+
"description": "Returns 403 Forbidden status",
109+
"responses": {
110+
"403": {
111+
"description": "Forbidden error",
112+
"content": {
113+
"application/json": {
114+
"schema": {
115+
"$ref": "#/components/schemas/Response"
116+
},
117+
"example": {
118+
"message": "Forbidden"
119+
}
120+
}
121+
}
122+
}
123+
}
124+
}
125+
},
126+
"/error500": {
127+
"get": {
128+
"summary": "Internal server error endpoint",
129+
"description": "Returns 500 Internal Server Error status",
130+
"responses": {
131+
"500": {
132+
"description": "Internal server error",
133+
"content": {
134+
"application/json": {
135+
"schema": {
136+
"$ref": "#/components/schemas/Response"
137+
},
138+
"example": {
139+
"message": "Internal Server Error"
140+
}
141+
}
142+
}
143+
}
144+
}
145+
}
146+
},
147+
"/redirect": {
148+
"get": {
149+
"summary": "Redirect endpoint",
150+
"description": "Returns redirect to /redirect-two",
151+
"responses": {
152+
"303": {
153+
"description": "See Other redirect",
154+
"content": {
155+
"application/json": {
156+
"schema": {
157+
"$ref": "#/components/schemas/Response"
158+
},
159+
"example": {
160+
"message": "Redirecting to /redirect-two"
161+
}
162+
}
163+
}
164+
}
165+
}
166+
}
167+
},
168+
"/redirecttwo": {
169+
"get": {
170+
"summary": "Redirect target endpoint",
171+
"description": "Shows referrer information",
172+
"responses": {
173+
"200": {
174+
"description": "Successful response with referrer info",
175+
"content": {
176+
"application/json": {
177+
"schema": {
178+
"$ref": "#/components/schemas/Response"
179+
},
180+
"example": {
181+
"message": "Redirected from http://localhost:9999/redirect"
182+
}
183+
}
184+
}
185+
}
186+
}
187+
}
188+
},
189+
"/timeout/{seconds}": {
190+
"get": {
191+
"summary": "Timeout endpoint",
192+
"description": "Waits for specified number of seconds (1-299) before responding",
193+
"parameters": [
194+
{
195+
"name": "seconds",
196+
"in": "path",
197+
"required": true,
198+
"schema": {
199+
"type": "integer",
200+
"minimum": 1,
201+
"maximum": 299
202+
},
203+
"description": "Number of seconds to wait before responding"
204+
}
205+
],
206+
"responses": {
207+
"200": {
208+
"description": "Successful response after timeout",
209+
"content": {
210+
"application/json": {
211+
"schema": {
212+
"$ref": "#/components/schemas/Response"
213+
},
214+
"example": {
215+
"message": "Request completed after 30 seconds"
216+
}
217+
}
218+
}
219+
},
220+
"400": {
221+
"description": "Bad request - invalid timeout value",
222+
"content": {
223+
"application/json": {
224+
"schema": {
225+
"$ref": "#/components/schemas/Response"
226+
},
227+
"examples": {
228+
"invalid_number": {
229+
"value": {
230+
"message": "Invalid timeout value. Must be a number."
231+
}
232+
},
233+
"out_of_range": {
234+
"value": {
235+
"message": "Timeout value must be between 1 and 299 seconds."
236+
}
237+
}
238+
}
239+
}
240+
}
241+
}
242+
}
243+
}
244+
}
245+
},
246+
"components": {
247+
"schemas": {
248+
"Response": {
249+
"type": "object",
250+
"properties": {
251+
"message": {
252+
"type": "string",
253+
"description": "Response message"
254+
}
255+
},
256+
"required": ["message"]
257+
}
258+
}
259+
}
260+
}`
261+
262+
func swaggerHandler(w http.ResponseWriter, r *http.Request) {
263+
if strings.HasSuffix(r.URL.Path, "/openapi.json") {
264+
w.Header().Set("Content-Type", "application/json")
265+
w.WriteHeader(http.StatusOK)
266+
w.Write([]byte(openAPISpec))
267+
return
268+
}
269+
270+
swaggerHTML := `<!DOCTYPE html>
271+
<html>
272+
<head>
273+
<title>API Documentation</title>
274+
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@3.52.5/swagger-ui.css" />
275+
<style>
276+
html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; }
277+
*, *:before, *:after { box-sizing: inherit; }
278+
body { margin:0; background: #fafafa; }
279+
</style>
280+
</head>
281+
<body>
282+
<div id="swagger-ui"></div>
283+
<script src="https://unpkg.com/swagger-ui-dist@3.52.5/swagger-ui-bundle.js"></script>
284+
<script src="https://unpkg.com/swagger-ui-dist@3.52.5/swagger-ui-standalone-preset.js"></script>
285+
<script>
286+
window.onload = function() {
287+
const ui = SwaggerUIBundle({
288+
url: '/swagger/openapi.json',
289+
dom_id: '#swagger-ui',
290+
deepLinking: true,
291+
presets: [
292+
SwaggerUIBundle.presets.apis,
293+
SwaggerUIStandalonePreset
294+
],
295+
plugins: [
296+
SwaggerUIBundle.plugins.DownloadUrl
297+
],
298+
layout: "StandaloneLayout"
299+
});
300+
};
301+
</script>
302+
</body>
303+
</html>`
304+
305+
w.Header().Set("Content-Type", "text/html")
306+
w.WriteHeader(http.StatusOK)
307+
w.Write([]byte(swaggerHTML))
308+
}
309+
44310
func main() {
45311
http.HandleFunc("/", okHandler)
46312
http.HandleFunc("/error403", error403Handler)
47313
http.HandleFunc("/error500", error500Handler)
48314
http.HandleFunc("/redirect", redirectHandler)
49315
http.HandleFunc("/redirecttwo", redirectTwoHandler)
316+
http.HandleFunc("/timeout/", timeoutHandler)
317+
http.HandleFunc("/swagger/", swaggerHandler)
318+
http.HandleFunc("/swagger/openapi.json", swaggerHandler)
50319

51320
log.Println("Server starting on :9999")
321+
log.Println("OpenAPI documentation available at http://localhost:9999/swagger/")
52322
log.Fatal(http.ListenAndServe(":9999", nil))
53-
}
323+
}

0 commit comments

Comments
 (0)