Skip to content

Commit 3c9c914

Browse files
committed
OpenAPI Explorer displays a set of API definitions on the web. (#1)
The API listing file (apis.json) can be generated using `tree` and `jq`. Instructions are in the README.
0 parents  commit 3c9c914

13 files changed

+2741
-0
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/elm-stuff
2+
/node_modules
3+
/public/*
4+
!/public/index.html
5+
!/public/bulma.min.css

Jenkinsfile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
pipeline {
2+
agent {
3+
docker {
4+
image 'node:14'
5+
}
6+
}
7+
stages {
8+
stage("build") {
9+
steps {
10+
dir("docs/explorer") {
11+
sh '''
12+
yarn install --production --frozen-lockfile
13+
yarn build
14+
'''
15+
archiveArtifacts artifacts: "public/*"
16+
}
17+
}
18+
}
19+
}
20+
}

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# openapi-explorer
2+
Open API specifications are usually stored as YAML files which are hard
3+
to read. This app will render YAML files in a legible format.
4+
5+
## Develop
6+
- nodejs runtime
7+
- yarn package manager
8+
9+
Install project dependencies by running `yarn install`.
10+
11+
## Build
12+
Add APIs to the explorer by copying them to the [public](public) folder and
13+
generating an api index file.
14+
15+
- Copy APIs - `cp -r <api-dir>/ public/`
16+
- Generate API Index - `tree -J -P "*.yaml" public | jq '{title: "<title>" , contents: .[0].contents}' > public/apis.json`
17+
- `yarn build`
18+
19+
Replace `<api-dir>` and `<title>` in the above commands as needed.
20+
21+
[public](public) folder will have a static website that can be deployed to any web server.
22+
The generated site does not depend on any external resources.
23+

elm.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"type": "application",
3+
"source-directories": [
4+
"src"
5+
],
6+
"elm-version": "0.19.1",
7+
"dependencies": {
8+
"direct": {
9+
"dillonkearns/elm-markdown": "4.0.2",
10+
"elm/browser": "1.0.2",
11+
"elm/core": "1.0.5",
12+
"elm/html": "1.0.0",
13+
"elm/http": "2.0.0",
14+
"elm/json": "1.1.3",
15+
"elm/url": "1.0.0",
16+
"krisajenkins/remotedata": "6.0.1"
17+
},
18+
"indirect": {
19+
"elm/bytes": "1.0.8",
20+
"elm/file": "1.0.5",
21+
"elm/parser": "1.1.0",
22+
"elm/regex": "1.0.0",
23+
"elm/time": "1.0.0",
24+
"elm/virtual-dom": "1.0.2",
25+
"rtfeldman/elm-hex": "1.0.0"
26+
}
27+
},
28+
"test-dependencies": {
29+
"direct": {},
30+
"indirect": {}
31+
}
32+
}

index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Elm from "./src/Main.elm";
2+
import "rapidoc";
3+
4+
const serversKey = "servers";
5+
const storedServers = window.localStorage.getItem(serversKey);
6+
const flags = storedServers ? JSON.parse(storedServers) : null;
7+
const program = Elm.Main.init({flags: flags});
8+
program.ports.saveServers
9+
.subscribe(servers => window.localStorage.setItem(serversKey, JSON.stringify(servers)));

package.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"devDependencies": {
3+
"elm-format": "^0.8.3"
4+
},
5+
"dependencies": {
6+
"@rollup/plugin-node-resolve": "^7.1.3",
7+
"elm": "^0.19.1-3",
8+
"rapidoc": "^8.0.0",
9+
"rollup": "^2.7.6",
10+
"rollup-plugin-elm": "^2.0.2",
11+
"rollup-plugin-terser": "^5.3.0"
12+
},
13+
"name": "openapi-explorer",
14+
"version": "1.0.0",
15+
"main": "index.js",
16+
"license": "UNLICENSED",
17+
"private": true,
18+
"scripts": {
19+
"start": "rollup -c --watch",
20+
"build": "rollup -c --environment TERSE"
21+
}
22+
}

public/bulma.min.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>OpenAPI Explorer</title>
7+
<link rel="stylesheet" href="/bulma.min.css">
8+
<script async type="module" src="/index.js"></script>
9+
<style>rapi-doc { min-height: 100vh; }</style>
10+
</head>
11+
</html>

rollup.config.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {terser} from "rollup-plugin-terser";
2+
import resolve from "@rollup/plugin-node-resolve";
3+
import elm from "rollup-plugin-elm";
4+
5+
const terse = Boolean(process.env.TERSE)
6+
7+
let plugins = [
8+
resolve(),
9+
elm({compiler: {debug: !terse, optimize: terse}}),
10+
];
11+
12+
if (terse) {
13+
plugins.push(
14+
terser({
15+
ecma: 6,
16+
output: {comments: false},
17+
compress: {
18+
pure_funcs: [
19+
"F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9",
20+
"A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9"
21+
],
22+
pure_getters: true,
23+
keep_fargs: false,
24+
unsafe_comps: true,
25+
unsafe: true,
26+
passes: 2
27+
},
28+
mangle: true
29+
})
30+
)
31+
}
32+
33+
export default {
34+
input: ["index.js"],
35+
output: {
36+
dir: "public",
37+
format: "esm",
38+
sourcemap: false,
39+
},
40+
plugins: plugins
41+
};

src/Api.elm

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
port module Api exposing
2+
( Apis
3+
, Data
4+
, Entry(..)
5+
, IndexData
6+
, Servers
7+
, apisDecoder
8+
, dropExtension
9+
, getApis
10+
, getIndex
11+
, reCase
12+
, saveServers
13+
, serversDecoder
14+
)
15+
16+
import Http
17+
import Json.Decode as Decode exposing (Decoder, field, string)
18+
import RemoteData
19+
20+
21+
dropExtension : String -> String
22+
dropExtension =
23+
splitExtensions >> Tuple.first
24+
25+
26+
splitExtensions : String -> ( String, String )
27+
splitExtensions path =
28+
case String.split "." path of
29+
[] ->
30+
( "", "" )
31+
32+
[ a ] ->
33+
( a, "" )
34+
35+
x :: xs ->
36+
( x, String.join "." xs )
37+
38+
39+
{-| Convert a string from dash-case to title-case.
40+
41+
reCase "foo-bar"
42+
-- "Foo Bar"
43+
44+
-}
45+
reCase : String -> String
46+
reCase =
47+
String.split "-"
48+
>> List.map (\w -> (String.left 1 w |> String.toUpper) ++ String.dropLeft 1 w)
49+
>> String.join " "
50+
51+
52+
53+
-- APIS
54+
55+
56+
type alias Apis =
57+
{ title : String
58+
, contents : List Entry
59+
}
60+
61+
62+
type alias Data =
63+
RemoteData.WebData Apis
64+
65+
66+
getApis : (Data -> msg) -> Cmd msg
67+
getApis got =
68+
Http.get
69+
{ url = "/apis.json"
70+
, expect =
71+
Http.expectJson
72+
(RemoteData.fromResult >> got)
73+
apisDecoder
74+
}
75+
76+
77+
apisDecoder : Decoder Apis
78+
apisDecoder =
79+
Decode.map2 Apis
80+
(field "title" string)
81+
(field "contents" (Decode.list entryDecoder))
82+
83+
84+
type Entry
85+
= Directory String (List Entry)
86+
| File String
87+
88+
89+
entryDecoder : Decoder Entry
90+
entryDecoder =
91+
field "type" string
92+
|> Decode.andThen
93+
(\typ ->
94+
case typ of
95+
"file" ->
96+
Decode.map File
97+
(field "name" string)
98+
99+
"directory" ->
100+
Decode.map2 Directory
101+
(field "name" string)
102+
(field "contents" (Decode.list entryDecoder))
103+
104+
_ ->
105+
Decode.fail <| typ ++ " is not a valid entry type"
106+
)
107+
108+
109+
110+
-- SERVERS
111+
112+
113+
type alias Servers =
114+
List String
115+
116+
117+
port saveServers : Servers -> Cmd msg
118+
119+
120+
serversDecoder : Decoder Servers
121+
serversDecoder =
122+
Decode.list Decode.string
123+
124+
125+
126+
-- INDEX
127+
128+
129+
type alias IndexData =
130+
RemoteData.WebData String
131+
132+
133+
getIndex : String -> (IndexData -> msg) -> Cmd msg
134+
getIndex idx gotIdx =
135+
let
136+
indexFile =
137+
"README.md"
138+
in
139+
Http.get
140+
{ url =
141+
if idx == "index" then
142+
"/" ++ indexFile
143+
144+
else
145+
"/" ++ idx ++ "/" ++ indexFile
146+
, expect = Http.expectString (RemoteData.fromResult >> gotIdx)
147+
}

0 commit comments

Comments
 (0)