Skip to content

Commit c6db608

Browse files
authored
feat: get service names and descriptions from api specs (#3)
1 parent 3e0fd17 commit c6db608

File tree

6 files changed

+156
-186
lines changed

6 files changed

+156
-186
lines changed

Dockerfile

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
FROM scratch
2-
ARG PROJECT_NAME="oasbinder"
3-
ENV PROJECT_NAME=${PROJECT_NAME}
4-
COPY ${PROJECT_NAME} /
2+
COPY oasbinder /
53
WORKDIR /
64
# hadolint ignore=DL3025
7-
ENTRYPOINT ${PROJECT_NAME}
5+
EXPOSE 8080
6+
ENTRYPOINT ["/oasbinder"]

README.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
Let's assume we have the following service in the configuration file and the user accesses `oasbinder` at <https://oasbinder.example.com> (`proxyAddress`).
88
```yaml
99
services:
10-
- name: hogwarts
11-
endpoint: /hogwarts
10+
- endpoint: /hogwarts
1211
url: http://localhost:8000/hogwarts/
1312
```
1413
@@ -58,11 +57,9 @@ proxyAddress: http://localhost:8080
5857
listenPort: 8080
5958

6059
services:
61-
- name: gringotts
62-
endpoint: /gringotts
60+
- endpoint: /gringotts
6361
url: http://localhost:8000/gringotts/
64-
- name: hogwarts
65-
endpoint: /hogwarts
62+
- endpoint: /hogwarts
6663
url: http://localhost:8000/hogwarts/
6764

6865
# Additional headers to pass to microservices, e.g. for authentication.

cmd/server.go

Lines changed: 60 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmd
22

33
import (
44
_ "embed"
5+
"encoding/json"
56
"fmt"
67
"html/template"
78
"io"
@@ -24,16 +25,30 @@ type Microservice struct {
2425
URL string `yaml:"url"`
2526
}
2627

28+
type MicroserviceList struct {
29+
Name string
30+
Endpoint string
31+
Selected bool
32+
}
33+
34+
// OpenAPISpec represents the structure of the OpenAPI specification for extracting info fields.
35+
type OpenAPISpec struct {
36+
Info struct {
37+
ServiceTitle string `json:"title"`
38+
ServiceSummary string `json:"summary"`
39+
} `json:"info"`
40+
}
41+
2742
// GetOASSpec retrieves the OpenAPI specification from the specified microservice URL
28-
func GetOASSpec(url string) ([]byte, error) {
43+
func GetOASSpec(url string) ([]byte, string, string, error) {
2944
if !strings.HasSuffix(url, "/") {
30-
return nil, fmt.Errorf("microservice URL doesn't have a trailing '/'")
45+
return nil, "", "", fmt.Errorf("microservice URL doesn't have a trailing '/'")
3146
}
3247
requestURL := fmt.Sprintf("%s%s", url, apiSpecsPath)
3348
log.Debug("Requesting ", requestURL)
3449
req, err := http.NewRequest("GET", requestURL, nil)
3550
if err != nil {
36-
return nil, err
51+
return nil, "", "", err
3752
}
3853

3954
for key, value := range headers {
@@ -43,53 +58,47 @@ func GetOASSpec(url string) ([]byte, error) {
4358
client := &http.Client{}
4459
resp, err := client.Do(req)
4560
if err != nil {
46-
return nil, err
61+
return nil, "", "", err
4762
}
4863
defer resp.Body.Close()
4964

5065
if resp.StatusCode != http.StatusOK {
51-
return nil, fmt.Errorf("failed to retrieve OpenAPI spec: received status code %d", resp.StatusCode)
66+
return nil, "", "", fmt.Errorf("failed to retrieve OpenAPI spec: received status code %d", resp.StatusCode)
5267
}
5368

5469
body, err := io.ReadAll(resp.Body)
5570
if err != nil {
56-
return nil, err
71+
return nil, "", "", err
5772
}
5873

59-
return body, nil
74+
var spec OpenAPISpec
75+
if err := json.Unmarshal(body, &spec); err != nil {
76+
return nil, "", "", err
77+
}
78+
79+
return body, spec.Info.ServiceTitle, spec.Info.ServiceSummary, nil
6080
}
6181

6282
// GenerateHTML generates the HTML for viewing the OpenAPI spec using Swagger UI
63-
func GenerateHTML(spec []byte, serviceURL string, selectedService string, message string) (string, error) {
64-
type MicroserviceOption struct {
65-
Name string
66-
Selected bool
67-
}
68-
83+
func GenerateHTML(spec []byte, microserviceList []MicroserviceList, serviceURL, selectedService, serviceSummary, message string) (string, error) {
6984
type SwaggerUIParams struct {
7085
Spec string
7186
Host string
7287
ProxyAddress string
7388
Headers map[string]string
74-
MicroserviceList []MicroserviceOption
89+
MicroserviceList []MicroserviceList
7590
SelectedService string
76-
}
77-
78-
microserviceOptions := []MicroserviceOption{}
79-
for _, ms := range services {
80-
microserviceOptions = append(microserviceOptions, MicroserviceOption{
81-
Name: ms.Name,
82-
Selected: ms.Name == selectedService,
83-
})
91+
ServiceSummary string
8492
}
8593

8694
params := SwaggerUIParams{
8795
Spec: string(spec),
8896
Host: serviceURL,
8997
ProxyAddress: proxyAddress + "/",
9098
Headers: headers,
91-
MicroserviceList: microserviceOptions,
99+
MicroserviceList: microserviceList,
92100
SelectedService: selectedService,
101+
ServiceSummary: serviceSummary,
93102
}
94103

95104
tmpl := htmlTemplate
@@ -116,33 +125,45 @@ func GenerateHTML(spec []byte, serviceURL string, selectedService string, messag
116125
}
117126

118127
func handler(w http.ResponseWriter, r *http.Request) {
119-
log.Debug("path = ", r.URL.Path)
120-
serviceName := strings.TrimPrefix(r.URL.Path, "/")
121-
log.Debug("serviceName = ", serviceName)
122-
microserviceURL := ""
128+
path := r.URL.Path
129+
log.Debug("path = ", path)
130+
131+
message := ""
132+
var spec []byte
133+
var selectedSpec []byte
134+
var err error
135+
var serviceName, serviceSummary, microserviceURL string
136+
137+
microserviceList := []MicroserviceList{}
123138
for _, service := range services {
124-
if service.Name == serviceName {
139+
// Retrieve name and summary of each microservice to construct a drop-down list.
140+
spec, serviceName, serviceSummary, err = GetOASSpec(service.URL)
141+
if service.Endpoint == path {
125142
microserviceURL = service.URL
126-
break
143+
// selectedSpec is the one which will be rendered by SwaggerUIBundle
144+
selectedSpec = spec
145+
if err != nil {
146+
message = "Could not retrieve OpenAPI spec: " + err.Error()
147+
}
127148
}
149+
if err != nil {
150+
log.Error("Could not retrieve OpenAPI spec: " + err.Error())
151+
continue
152+
}
153+
microserviceList = append(microserviceList, MicroserviceList{
154+
Name: serviceName + " — " + serviceSummary,
155+
Endpoint: service.Endpoint,
156+
Selected: service.Endpoint == path,
157+
})
128158
}
129159

130160
log.Debug("microserviceURL = ", microserviceURL)
131161

132-
message := ""
133-
var spec []byte
134-
var err error
135-
136162
if microserviceURL == "" {
137163
message = "Please select a service from the list."
138-
} else {
139-
spec, err = GetOASSpec(microserviceURL)
140-
if err != nil {
141-
message = "Could not retrieve OpenAPI spec: " + err.Error()
142-
}
143164
}
144165

145-
html, err := GenerateHTML(spec, microserviceURL, serviceName, message)
166+
html, err := GenerateHTML(selectedSpec, microserviceList, microserviceURL, serviceName, serviceSummary, message)
146167
if err != nil {
147168
http.Error(w, "Could not generate HTML", http.StatusInternalServerError)
148169
log.Error(err)

cmd/template.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<!DOCTYPE html>
22
<html>
33
<head>
4+
<meta charset="utf-8">
45
<title>Swagger UI</title>
56
<!-- Load the latest Swagger UI code and style from npm using unpkg.com -->
67
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.17.14/swagger-ui-bundle.min.js" integrity="sha512-7ihPQv5ibiTr0DW6onbl2MIKegdT6vjpPySyIb4Ftp68kER6Z7Yiub0tFoMmCHzZfQE9+M+KSjQndv6NhYxDgg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
@@ -22,7 +23,7 @@
2223
<select id="microservice-select" class="form-control">
2324
<option value=""></option>
2425
{{range .MicroserviceList}}
25-
<option value="{{.Name}}" {{if .Selected}}selected{{end}}>{{.Name}}</option>
26+
<option value="{{.Endpoint}}" {{if .Selected}}selected{{end}}>{{.Name}}</option>
2627
{{end}}
2728
</select>
2829
</div>

go.mod

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,49 +13,48 @@ require (
1313
)
1414

1515
require (
16+
dario.cat/mergo v1.0.1 // indirect
1617
github.com/Masterminds/goutils v1.1.1 // indirect
17-
github.com/Masterminds/semver/v3 v3.2.0 // indirect
18-
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
18+
github.com/Masterminds/semver/v3 v3.3.1 // indirect
19+
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
1920
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
2021
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
2122
github.com/dustin/go-humanize v1.0.1 // indirect
22-
github.com/fatih/color v1.14.1 // indirect
23-
github.com/fsnotify/fsnotify v1.7.0 // indirect
24-
github.com/goccy/go-yaml v1.11.0 // indirect
25-
github.com/google/uuid v1.4.0 // indirect
26-
github.com/hashicorp/go-version v1.6.0 // indirect
23+
github.com/fatih/color v1.18.0 // indirect
24+
github.com/fsnotify/fsnotify v1.8.0 // indirect
25+
github.com/goccy/go-yaml v1.15.22 // indirect
26+
github.com/google/uuid v1.6.0 // indirect
27+
github.com/hashicorp/go-version v1.7.0 // indirect
2728
github.com/hashicorp/hcl v1.0.0 // indirect
2829
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect
29-
github.com/huandu/xstrings v1.3.3 // indirect
30-
github.com/imdario/mergo v0.3.11 // indirect
30+
github.com/huandu/xstrings v1.5.0 // indirect
3131
github.com/inconshreveable/mousetrap v1.1.0 // indirect
3232
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
33-
github.com/magiconair/properties v1.8.7 // indirect
34-
github.com/mattn/go-colorable v0.1.13 // indirect
35-
github.com/mattn/go-isatty v0.0.18 // indirect
36-
github.com/mattn/go-runewidth v0.0.14 // indirect
37-
github.com/mitchellh/copystructure v1.0.0 // indirect
33+
github.com/magiconair/properties v1.8.9 // indirect
34+
github.com/mattn/go-colorable v0.1.14 // indirect
35+
github.com/mattn/go-isatty v0.0.20 // indirect
36+
github.com/mattn/go-runewidth v0.0.16 // indirect
37+
github.com/mitchellh/copystructure v1.2.0 // indirect
3838
github.com/mitchellh/mapstructure v1.5.0 // indirect
39-
github.com/mitchellh/reflectwalk v1.0.0 // indirect
40-
github.com/muesli/termenv v0.15.1 // indirect
41-
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
42-
github.com/rivo/uniseg v0.2.0 // indirect
43-
github.com/sagikazarmark/locafero v0.4.0 // indirect
39+
github.com/mitchellh/reflectwalk v1.0.2 // indirect
40+
github.com/muesli/termenv v0.15.2 // indirect
41+
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
42+
github.com/rivo/uniseg v0.4.7 // indirect
43+
github.com/sagikazarmark/locafero v0.7.0 // indirect
4444
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
45-
github.com/shopspring/decimal v1.2.0 // indirect
45+
github.com/shopspring/decimal v1.4.0 // indirect
4646
github.com/sourcegraph/conc v0.3.0 // indirect
47-
github.com/spf13/afero v1.11.0 // indirect
48-
github.com/spf13/cast v1.6.0 // indirect
49-
github.com/spf13/pflag v1.0.5 // indirect
50-
github.com/stretchr/testify v1.10.0 // indirect
47+
github.com/spf13/afero v1.12.0 // indirect
48+
github.com/spf13/cast v1.7.1 // indirect
49+
github.com/spf13/pflag v1.0.6 // indirect
5150
github.com/subosito/gotenv v1.6.0 // indirect
52-
go.uber.org/atomic v1.9.0 // indirect
53-
go.uber.org/multierr v1.9.0 // indirect
54-
golang.org/x/crypto v0.31.0 // indirect
55-
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
56-
golang.org/x/sys v0.28.0 // indirect
57-
golang.org/x/text v0.21.0 // indirect
58-
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
51+
go.uber.org/multierr v1.11.0 // indirect
52+
golang.org/x/crypto v0.33.0 // indirect
53+
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
54+
golang.org/x/sys v0.30.0 // indirect
55+
golang.org/x/text v0.22.0 // indirect
5956
gopkg.in/ini.v1 v1.67.0 // indirect
6057
gopkg.in/yaml.v3 v3.0.1 // indirect
6158
)
59+
60+
replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.16

0 commit comments

Comments
 (0)