diff --git a/.gitignore b/.gitignore index 970658c..bf2ed39 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ exported/ -schemas/*.json +schemas/**/*.json .idea/* node_js/node_modules \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 77b5e10..6168e59 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,7 @@ COPY --from=builder /bin/autodoc /bin/autodoc # Copy Node.js app + deps COPY --from=node-builder /app /node_js COPY ./html-swagger.sh /html-swagger.sh +COPY ./html-stoplight.sh /html-stoplight.sh WORKDIR / RUN chmod +x /node_js/deref.js diff --git a/api/handlers.go b/api/handlers.go index d93e57a..7695cae 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -46,13 +46,14 @@ type ErrorResponse struct { // ExportSuccess represents a successful export response. // @description Returned when the OpenAPI schema has been successfully exported. type ExportSuccess struct { - SwaggerUrl string `json:"url" example:"https://cdn.example.com/example-service/index.html"` - RedocUrl string `json:"redocUrl" example:"https://cdn.example.com/example-service/redoc.html"` + SwaggerUrl string `json:"url" example:"https://cdn.example.com/example-service/swagger.html"` + RedocUrl string `json:"redocUrl" example:"https://cdn.example.com/example-service/redoc.html"` + StoplightUrl string `json:"stoplightUrl" example:"https://cdn.example.com/example-service/stoplight.html"` } // returnError sends an error response to the client. func returnError(w http.ResponseWriter, err error) { - slog.Error("Error handling request: %v", err) + slog.Error("Error handling request", "error", err) w.WriteHeader(http.StatusBadRequest) newError := ErrorResponse{Error: err.Error()} _ = json.NewEncoder(w).Encode(newError) @@ -72,41 +73,41 @@ func returnError(w http.ResponseWriter, err error) { func openapiExport(w http.ResponseWriter, r *http.Request) { jsonData, err := io.ReadAll(r.Body) if err != nil { - slog.Error("Error reading body: %v", err) + slog.Error("Error reading body", "error", err) returnError(w, err) return } fullSchema := FullOpenAPI{} if err := json.NewDecoder(bytes.NewReader(jsonData)).Decode(&fullSchema); err != nil { - slog.Error("Error decoding JSON: %v", err) + slog.Error("Error decoding JSON", "error", err) returnError(w, err) return } - slog.Info("Accepted a new schema: %s", fullSchema.Info.Title) + slog.Info("Accepted a new schema", "title", fullSchema.Info.Title) fullPth := "./schemas/" + fullSchema.Info.Title + ".json" if err = os.MkdirAll(filepath.Dir(fullPth), 0777); err != nil { - slog.Error("Error creating directory %s: %v", filepath.Dir(fullPth), err) + slog.Error("Error creating directory", "path", filepath.Dir(fullPth), "error", err) returnError(w, err) return } err = os.WriteFile(fullPth, jsonData, 0644) if err != nil { - slog.Error("Error writing file: %v", err) + slog.Error("Error writing file", "error", err) returnError(w, err) return } - slog.Info("Wrote file: %s", fullSchema.Info.Title+".json") - slog.Info("File path: %s", fullPth) + slog.Info("Wrote file", "file", fullSchema.Info.Title+".json") + slog.Info("File path", "path", fullPth) redocShortPath := fmt.Sprintf("./exported/%s", fullSchema.Info.Title) fullPth, _ = filepath.Abs(fullPth) redocPath, _ := filepath.Abs(redocShortPath) makeUI := func(cmdCommand []string, cmdDir string) error { - slog.Info("Running command: %v", cmdCommand) + slog.Info("Running command", "command", cmdCommand) _ = os.MkdirAll(redocPath, 0755) var stderr bytes.Buffer cmd := exec.Command(cmdCommand[0], cmdCommand[1:]...) @@ -137,14 +138,22 @@ func openapiExport(w http.ResponseWriter, r *http.Request) { returnError(w, err) return } + err = makeUI([]string{"/bin/bash", "html-stoplight.sh", fullPth, filepath.Join(redocPath, "stoplight.html")}, "") + if err != nil { + returnError(w, err) + return + } success := ExportSuccess{ - SwaggerUrl: fmt.Sprintf("%s%s/swagger.html", BaseCDNUrl, fullSchema.Info.Title), - RedocUrl: fmt.Sprintf("%s%s/redoc.html", BaseCDNUrl, fullSchema.Info.Title), + SwaggerUrl: fmt.Sprintf("%s%s/swagger.html", BaseCDNUrl, fullSchema.Info.Title), + RedocUrl: fmt.Sprintf("%s%s/redoc.html", BaseCDNUrl, fullSchema.Info.Title), + StoplightUrl: fmt.Sprintf("%s%s/stoplight.html", BaseCDNUrl, fullSchema.Info.Title), } _ = json.NewEncoder(w).Encode(success) - slog.Info("Exported: %s", fullSchema.Info.Title) - slog.Info("URL: %s", success.SwaggerUrl) + slog.Info("Exported", "title", fullSchema.Info.Title) + slog.Info("Swagger URL", "url", success.SwaggerUrl) + slog.Info("Redoc URL", "url", success.RedocUrl) + slog.Info("Stoplight URL", "url", success.StoplightUrl) } // expandedOpenapi handles OpenAPI export requests. @@ -161,7 +170,7 @@ func openapiExport(w http.ResponseWriter, r *http.Request) { func expandedOpenapi(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) schemaName := vars["name"] - slog.Info("Received request for schema: %s", schemaName) + slog.Info("Received request for schema", "name", schemaName) schemaPath, err := filepath.Abs( fmt.Sprintf("./schemas/%s.json", strings.TrimSuffix(schemaName, ".json")), ) @@ -177,7 +186,7 @@ func expandedOpenapi(w http.ResponseWriter, r *http.Request) { return } if !json.Valid(output) { - slog.Error("Failed to parse JSON: %s", output) + slog.Error("Failed to parse JSON", "output", string(output)) returnError(w, fmt.Errorf("failed to parse JSON: %s", output)) return } diff --git a/docs/docs.go b/docs/docs.go index 3672607..c354aa7 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -150,9 +150,13 @@ const docTemplate = `{ "type": "string", "example": "https://cdn.example.com/example-service/redoc.html" }, + "stoplightUrl": { + "type": "string", + "example": "https://cdn.example.com/example-service/stoplight.html" + }, "url": { "type": "string", - "example": "https://cdn.example.com/example-service/index.html" + "example": "https://cdn.example.com/example-service/swagger.html" } } }, diff --git a/docs/swagger.json b/docs/swagger.json index 3f4c883..f66b621 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -143,9 +143,13 @@ "type": "string", "example": "https://cdn.example.com/example-service/redoc.html" }, + "stoplightUrl": { + "type": "string", + "example": "https://cdn.example.com/example-service/stoplight.html" + }, "url": { "type": "string", - "example": "https://cdn.example.com/example-service/index.html" + "example": "https://cdn.example.com/example-service/swagger.html" } } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index c1dd9c2..836e233 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -20,8 +20,11 @@ definitions: redocUrl: example: https://cdn.example.com/example-service/redoc.html type: string + stoplightUrl: + example: https://cdn.example.com/example-service/stoplight.html + type: string url: - example: https://cdn.example.com/example-service/index.html + example: https://cdn.example.com/example-service/swagger.html type: string type: object api.FullOpenAPI: diff --git a/html-stoplight.sh b/html-stoplight.sh new file mode 100755 index 0000000..f917da0 --- /dev/null +++ b/html-stoplight.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -e + +INPUT_JSON="$1" +OUTPUT_HTML="$2" + +if [[ -z "$INPUT_JSON" || -z "$OUTPUT_HTML" ]]; then + echo "Usage: $0 path/to/openapi.json output.html" + exit 1 +fi + +if [[ ! -f "$INPUT_JSON" ]]; then + echo "Error: File '$INPUT_JSON' does not exist" + exit 1 +fi + +python3 << PYEOF +import base64 +import json + +with open("$INPUT_JSON", "r") as f: + spec = json.load(f) + +spec_json = json.dumps(spec) +spec_b64 = base64.b64encode(spec_json.encode()).decode() + +html = f''' + + + + + API Documentation + + + + + + + + +''' + +with open("$OUTPUT_HTML", "w") as f: + f.write(html) + +print("✅ Generated $OUTPUT_HTML with Stoplight Elements", flush=True) +PYEOF