Skip to content

Commit da9435c

Browse files
committed
feat: Adds Stoplight Elements integration
Implements Stoplight Elements to generate API documentation from OpenAPI specifications. This includes: - Embedding static assets for Stoplight Elements. - A configuration struct with configurable URLs, titles, and other settings. - A function to generate the HTML for Stoplight Elements based on the configuration. - Tests for the static assets and HTML generation.
1 parent 51f5065 commit da9435c

File tree

2 files changed

+203
-0
lines changed

2 files changed

+203
-0
lines changed

stopment.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package stopment
22

33
import (
44
"embed"
5+
"html/template"
56
"io/fs"
7+
"strings"
68
)
79

810
//go:embed src/stopments/static
@@ -21,3 +23,125 @@ var Favicon, _ = files.ReadFile("src/stopments/static/favicon.ico")
2123
var Styles, _ = files.ReadFile("src/stopments/static/styles.min.css")
2224
var WebComponents, _ = files.ReadFile("src/stopments/static/web-components.min.js")
2325
var ScalarApiReference, _ = files.ReadFile("src/stopments/static/scalar-api-reference.js")
26+
27+
// StoplightConfig holds the configuration for Stoplight Elements.
28+
// It allows you to customize the OpenAPI document URL, title, and other settings
29+
// for the Stoplight Elements documentation.
30+
// You can use NewConfig function to create a new StoplightConfig with default values.
31+
// The configuration can be used to generate the HTML for Stoplight Elements using GetStoplightElementsHtml function.
32+
type StoplightConfig struct {
33+
// OpenAPIURL is the URL to the OpenAPI document.
34+
// It can be a URL to a remote OpenAPI document or a local file path.
35+
OpenAPIURL string
36+
37+
// Title is the title of the API documentation.
38+
Title string
39+
40+
// StoplightElementsJSURL is the URL to the Stoplight Elements JavaScript file.
41+
// Default is "https://cdn.jsdelivr.net/npm/@stoplight/elements/web-components.min.js"
42+
StoplightElementsJSURL string
43+
44+
// StoplightElementsCSSURL is the URL to the Stoplight Elements CSS file.
45+
// Default is "https://cdn.jsdelivr.net/npm/@stoplight/elements/styles.min.css"
46+
StoplightElementsCSSURL string
47+
48+
// StoplightElementsFavicon is the URL to the favicon for Stoplight Elements.
49+
// Default is "https://docs.stoplight.io/favicons/favicon.ico"
50+
StoplightElementsFavicon string
51+
52+
// APIDescriptionDocument is the API description document in JSON or YAML format.
53+
APIDescriptionDocument string
54+
55+
// BasePath is the base path for the API.
56+
BasePath string
57+
58+
// HideInternal indicates whether to hide internal APIs in the documentation.
59+
HideInternal bool
60+
61+
// HideTryIt indicates whether to hide the "Try It" feature in the documentation.
62+
HideTryIt bool
63+
64+
// HideExport indicates whether to hide the "Export" feature in the documentation.
65+
HideExport bool
66+
67+
// TryItCORSProxy is the CORS proxy URL for the "Try It" feature.
68+
TryItCORSProxy string
69+
70+
// TryItCredentialPolicy defines the credential policy for the "Try It" feature.
71+
// default is "omit".
72+
// Possible values are "omit", "include", and "same-origin".
73+
TryItCredentialPolicy string // "omit", "include", "same-origin"
74+
75+
// Layout defines the layout of the Stoplight Elements documentation.
76+
// Default is "sidebar".
77+
// Possible values are "sidebar", "responsive", and "stacked".
78+
Layout string // "sidebar", "responsive", "stacked"
79+
80+
// Logo is the URL to the logo for the API documentation.
81+
// It can be a URL to an image or a local file path.
82+
// Default is an empty string, which means no logo is displayed.
83+
Logo string
84+
85+
// Router defines the routing strategy for Stoplight Elements.
86+
// Default is "hash".
87+
// Possible values are "history", "hash", "memory", and "static".
88+
Router string // "history", "hash", "memory", "static"
89+
}
90+
91+
// NewConfig creates a new StoplightConfig with default values for Stoplight Elements.
92+
func NewConfig(openapiURL string, title string) StoplightConfig {
93+
return StoplightConfig{
94+
OpenAPIURL: openapiURL,
95+
Title: title,
96+
StoplightElementsJSURL: "https://cdn.jsdelivr.net/npm/@stoplight/elements/web-components.min.js",
97+
StoplightElementsCSSURL: "https://cdn.jsdelivr.net/npm/@stoplight/elements/styles.min.css",
98+
StoplightElementsFavicon: "https://docs.stoplight.io/favicons/favicon.ico",
99+
TryItCredentialPolicy: "omit",
100+
Layout: "sidebar",
101+
Router: "hash",
102+
}
103+
}
104+
105+
const stoplightElementsTemplate = `<!doctype html>
106+
<html lang="en">
107+
<head>
108+
<meta charset="utf-8">
109+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
110+
<title>{{.Title}}</title>
111+
{{if .StoplightElementsFavicon}}<link rel="shortcut icon" href="{{.StoplightElementsFavicon}}">{{end}}
112+
<script src="{{.StoplightElementsJSURL}}"></script>
113+
<link rel="stylesheet" href="{{.StoplightElementsCSSURL}}">
114+
</head>
115+
<body>
116+
<elements-api
117+
{{if .OpenAPIURL}}apiDescriptionUrl="{{.OpenAPIURL}}"{{end}}
118+
{{if .APIDescriptionDocument}}apiDescriptionDocument="{{.APIDescriptionDocument}}"{{end}}
119+
{{if .BasePath}}basePath="{{.BasePath}}"{{end}}
120+
{{if .HideInternal}}hideInternal="true"{{end}}
121+
{{if .HideTryIt}}hideTryIt="true"{{end}}
122+
{{if .HideExport}}hideExport="true"{{end}}
123+
{{if .TryItCORSProxy}}tryItCorsProxy="{{.TryItCORSProxy}}"{{end}}
124+
tryItCredentialPolicy="{{.TryItCredentialPolicy}}"
125+
layout="{{.Layout}}"
126+
{{if .Logo}}logo="{{.Logo}}"{{end}}
127+
router="{{.Router}}"
128+
/>
129+
</body>
130+
</html>`
131+
132+
// GetStoplightElementsHtml generates the HTML for Stoplight Elements based on the provided configuration.
133+
// You can get config using NewConfig function.
134+
// It returns the HTML as a string or an error if the template execution fails.
135+
func GetStoplightElementsHtml(cfg StoplightConfig) (string, error) {
136+
tmpl, err := template.New("stoplight").Parse(stoplightElementsTemplate)
137+
if err != nil {
138+
return "", err
139+
}
140+
var builder strings.Builder
141+
142+
err = tmpl.Execute(&builder, cfg)
143+
if err != nil {
144+
return "", err
145+
}
146+
return builder.String(), nil
147+
}

stopment_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package stopment
22

33
import (
44
"io/fs"
5+
"strings"
56
"testing"
67
)
78

@@ -35,3 +36,81 @@ func TestStaticFilesContent(t *testing.T) {
3536
t.Error("ScalarApiReference is empty")
3637
}
3738
}
39+
40+
func TestGetStoplightElementsHtml_Default(t *testing.T) {
41+
cfg := NewConfig("https://example.com/openapi.yaml", "Test API")
42+
html, err := GetStoplightElementsHtml(cfg)
43+
if err != nil {
44+
t.Fatalf("unexpected error: %v", err)
45+
}
46+
if !strings.Contains(html, "Test API") {
47+
t.Error("title not rendered in HTML")
48+
}
49+
if !strings.Contains(html, cfg.OpenAPIURL) {
50+
t.Error("openapi url not rendered in HTML")
51+
}
52+
if !strings.Contains(html, cfg.StoplightElementsJSURL) {
53+
t.Error("JS URL not rendered in HTML")
54+
}
55+
if !strings.Contains(html, cfg.StoplightElementsCSSURL) {
56+
t.Error("CSS URL not rendered in HTML")
57+
}
58+
if !strings.Contains(html, cfg.StoplightElementsFavicon) {
59+
t.Error("favicon not rendered in HTML")
60+
}
61+
if !strings.Contains(html, `tryItCredentialPolicy="omit"`) {
62+
t.Error("tryItCredentialPolicy not rendered in HTML")
63+
}
64+
if !strings.Contains(html, `layout="sidebar"`) {
65+
t.Error("layout not rendered in HTML")
66+
}
67+
if !strings.Contains(html, `router="hash"`) {
68+
t.Error("router not rendered in HTML")
69+
}
70+
}
71+
72+
func TestGetStoplightElementsHtml_AllOptions(t *testing.T) {
73+
cfg := StoplightConfig{
74+
OpenAPIURL: "https://api.com/openapi.json",
75+
Title: "All Options API",
76+
StoplightElementsJSURL: "/static/web-components.min.js",
77+
StoplightElementsCSSURL: "/static/styles.min.css",
78+
StoplightElementsFavicon: "/static/favicon.ico",
79+
APIDescriptionDocument: "{openapi:3}",
80+
BasePath: "/docs",
81+
HideInternal: true,
82+
HideTryIt: true,
83+
HideExport: true,
84+
TryItCORSProxy: "https://proxy.com",
85+
TryItCredentialPolicy: "include",
86+
Layout: "responsive",
87+
Logo: "/static/logo.png",
88+
Router: "history",
89+
}
90+
html, err := GetStoplightElementsHtml(cfg)
91+
if err != nil {
92+
t.Fatalf("unexpected error: %v", err)
93+
}
94+
checks := []string{
95+
cfg.Title,
96+
cfg.OpenAPIURL,
97+
cfg.StoplightElementsJSURL,
98+
cfg.StoplightElementsCSSURL,
99+
cfg.StoplightElementsFavicon,
100+
cfg.APIDescriptionDocument,
101+
cfg.BasePath,
102+
cfg.TryItCORSProxy,
103+
cfg.TryItCredentialPolicy,
104+
cfg.Layout,
105+
cfg.Logo,
106+
cfg.Router,
107+
"hideInternal=\"true\"",
108+
"hideTryIt=\"true\"",
109+
"hideExport=\"true\"",
110+
}
111+
for _, s := range checks {
112+
if !strings.Contains(html, s) {
113+
t.Errorf("expected %q in HTML", s)
114+
}
115+
}
116+
}

0 commit comments

Comments
 (0)