Skip to content

Commit 3f2756c

Browse files
authored
Merge pull request #8 from nkmr-jp/develop
💥 Supports structure generation for each request method and request parameter.
2 parents c72cc30 + d914a1e commit 3f2756c

22 files changed

+468
-48
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ node_modules
44
*.go
55
*.json
66
tmp
7-
.node-version
7+
.node-version
8+
tests/docs
9+
tests/jsonplaceholder.typicode.com

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ api-to-go https://api.github.com/users/github/repos
5151
# > Docs: https://docs.github.com/en/rest
5252
5353
# > Response Body:
54-
# > - api.github.com/users/user/repos.go:1
55-
# > - api.github.com/users/user/repos.json:1
54+
# > - api.github.com/users/user/repos_get.go:1
55+
# > - api.github.com/users/user/repos_get.json:1
5656
```
5757
5858
Generated files and directories.
@@ -63,8 +63,8 @@ Generated files and directories.
6363
# > └── api.github.com
6464
# > └── users
6565
# > └── user
66-
# > ├── repos.go
67-
# > └── repos.json
66+
# > ├── repos_get.go
67+
# > └── repos_get.json
6868
```
6969
7070
Generated struct file `./api.github.com/users/user/repos.go`.
@@ -78,13 +78,13 @@ package user
7878
7979
import "time"
8080
81-
// Repos represents the response body from an HTTP request.
81+
// ReposGet is the structure of the HTTP Response Body.
8282
//
8383
// Status: 200 OK
8484
// Request: GET https://api.github.com/users/github/repos
8585
// Format: /users/{user}/repos
8686
// Docs: https://docs.github.com/en/rest
87-
type Repos []struct {
87+
type ReposGet []struct {
8888
ID int `json:"id"`
8989
NodeID string `json:"node_id"`
9090
Name string `json:"name"`

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"scripts": {
1212
"sync-version": "npm version from-git --no-git-tag-version && git add package.json && git commit -m \":bookmark: [skip ci] v$(cat package.json | jq -r .version)\"",
1313
"fetch": "cd vendor && curl -OL https://raw.githubusercontent.com/mholt/json-to-go/master/json-to-go.js",
14-
"setup": "yarn install && yarn fetch && npm -f link && exec $SHELL -l"
14+
"setup": "yarn install && yarn fetch && npm -f link && exec $SHELL -l",
15+
"test": "jest"
1516
},
1617
"description": "Convert REST API's JSON payload to Golang struct.",
1718
"bin": {

src/buildPath.js

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
1-
const {loadYaml, loadConfig} = require("./common");
1+
const {loadYaml, loadConfig, capitalize} = require("./common");
22

3-
function buildPath(url, configFile) {
3+
function buildPath(url, configFile, opts) {
44
const path = _buildPath(url, configFile)
55
const pathArr = path.replacedUrl.split("/")
66
const pkg = pathArr[pathArr.length - 2].replace(/\./g, '')
77
const last = pathArr[pathArr.length - 1] || "index"
8-
const struct = _capitalize(last)
8+
const struct = capitalize(last)
99
pathArr.pop()
1010
const dir = pathArr.join("/")
11+
let method = opts?.method.toLowerCase()
12+
1113
return {
1214
path,
1315
struct,
1416
pkg,
1517
dir,
16-
jsonFilePath: `${dir}/${last}.json`,
17-
goFilePath: `${dir}/${last}.go`,
18-
paramJsonFilePath: `${dir}/${last}_param.json`,
19-
paramGoFilePath: `${dir}/${last}_param.go`,
18+
jsonFilePath: `${dir}/${last}_${method}.json`,
19+
goFilePath: `${dir}/${last}_${method}.go`,
20+
queryJsonFilePath: `${dir}/${last}_${method}_query.json`,
21+
queryGoFilePath: `${dir}/${last}_${method}_query.go`,
22+
bodyJsonFilePath: `${dir}/${last}_${method}_body.json`,
23+
bodyGoFilePath: `${dir}/${last}_${method}_body.go`,
2024
}
2125
}
2226

@@ -70,9 +74,4 @@ function _replacePath(pathname, format) {
7074
}
7175
}
7276

73-
function _capitalize(str) {
74-
const lower = str.toLowerCase();
75-
return str.charAt(0).toUpperCase() + lower.slice(1);
76-
}
77-
7877
module.exports = buildPath;

src/buildPath.test.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ const buildPath = require('./buildPath');
33
test('build path', () => {
44
const expected = {
55
"dir": "api.github.com/users/user",
6-
"goFilePath": "api.github.com/users/user/repos.go",
7-
"jsonFilePath": "api.github.com/users/user/repos.json",
8-
"paramGoFilePath": "api.github.com/users/user/repos_param.go",
9-
"paramJsonFilePath": "api.github.com/users/user/repos_param.json",
6+
"goFilePath": "api.github.com/users/user/repos_get.go",
7+
"jsonFilePath": "api.github.com/users/user/repos_get.json",
8+
"queryGoFilePath": "api.github.com/users/user/repos_get_query.go",
9+
"queryJsonFilePath": "api.github.com/users/user/repos_get_query.json",
10+
"bodyGoFilePath": "api.github.com/users/user/repos_get_body.go",
11+
"bodyJsonFilePath": "api.github.com/users/user/repos_get_body.json",
1012
"path": {
1113
"pathFormat": "/users/{user}/repos",
1214
"pathname": "/users/github/repos",
@@ -16,9 +18,13 @@ test('build path', () => {
1618
"pkg": "user",
1719
"struct": "Repos"
1820
}
21+
let opts = {
22+
method: "GET",
23+
}
1924
const received = buildPath(
2025
new URL("https://api.github.com/users/github/repos"),
21-
"./.api-to-go.test.yml"
26+
"./src/.api-to-go.test.yml",
27+
opts
2228
)
2329
expect(received).toEqual(expected);
2430
});
@@ -32,7 +38,7 @@ test('build path without format setting', () => {
3238
}
3339
const received = buildPath(
3440
new URL("https://api.github.com/organizations"),
35-
"./.api-to-go.test.yml"
41+
"./src/.api-to-go.test.yml"
3642
)
3743
expect(received.path).toEqual(expected);
3844
});

src/common.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ exports.loadFile = file => {
2020
return fs.readFileSync(file, 'utf8');
2121
};
2222

23+
exports.capitalize = str => {
24+
const lower = str.toLowerCase();
25+
return str.charAt(0).toUpperCase() + lower.slice(1);
26+
}
27+
2328
exports.isJsonString = str => {
2429
try {
2530
JSON.parse(str);

src/run.js

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const fetch = require('node-fetch');
22
const fs = require('fs');
33
const jsonToGo = require('../vendor/json-to-go.js');
44
const buildPath = require('./buildPath');
5-
const {isJsonString, loadConfig, loadFile, loadJson} = require("./common");
5+
const {isJsonString, loadConfig, loadFile, loadJson, capitalize} = require("./common");
66

77
let cliOpts
88

@@ -24,28 +24,38 @@ function run(urlStr, body, options) {
2424
const apiUrl = urlStr.replace(/\/$/, '')
2525
url = new URL(apiUrl);
2626
cfg = loadConfig(url, cliOpts.config)
27-
path = buildPath(url, cliOpts.config)
27+
path = buildPath(url, cliOpts.config, opts)
2828

2929
console.log(`Status: ${res.status} ${res.statusText}`)
3030
console.log(`Request: ${opts.method} ${url}`)
3131
if (path.path.pathFormat) console.log(`Format: ${path.path.pathFormat}`)
3232
if (cfg?.["docs"] !== undefined) console.log(`Docs: ${cfg?.["docs"].join(", ")}`)
3333
comment = buildComment(url, path, opts.method, res)
3434

35-
if (opts?.body) {
36-
const paramStruct = jsonToGo(opts?.body, path.struct + "Param");
37-
const paramContent = buildContent(
38-
paramStruct.go, path, buildComment(url, path, opts.method), true
39-
)
40-
writeParam(JSON.stringify(JSON.parse(opts?.body), null, "\t"), path, paramContent)
41-
}
42-
4335
return res.json()
4436
})
4537
.then(json => {
46-
const struct = jsonToGo(JSON.stringify(json), path.struct);
47-
const content = buildContent(struct.go, path, comment)
38+
let method = capitalize(opts?.method)
39+
const struct = jsonToGo(JSON.stringify(json), path.struct + method);
40+
const content = buildContent(struct.go, path, comment,"")
4841
write(json, path, content)
42+
43+
if (opts?.body) {
44+
const bodyStruct = jsonToGo(opts?.body, path.struct + method + "Body");
45+
const bodyContent = buildContent(
46+
bodyStruct.go, path, buildComment(url, path, opts.method), "body"
47+
)
48+
writeBodyParam(JSON.stringify(JSON.parse(opts?.body), null, "\t"), path, bodyContent)
49+
}
50+
if (url?.search) {
51+
const queryJson = queryToJson(new URLSearchParams(url.search))
52+
const queryStr = JSON.stringify(queryJson, null, "\t")
53+
const queryStruct = jsonToGo(queryStr, path.struct + method + "Query");
54+
const queryContent = buildContent(
55+
queryStruct.go, path, buildComment(url, path, opts.method), "query"
56+
)
57+
writeQueryParam(queryStr, path, queryContent)
58+
}
4959
}, () => {
5060
console.log()
5161
console.log("Response Body is empty.")
@@ -69,17 +79,30 @@ function write(json, path, content) {
6979
console.log(` - ${path.jsonFilePath}:1`)
7080
}
7181

72-
function writeParam(json, path, content) {
73-
fs.writeFile(path.paramJsonFilePath, json, (err) => {
82+
function writeBodyParam(json, path, content) {
83+
fs.writeFile(path.bodyJsonFilePath, json, (err) => {
7484
if (err) throw err;
7585
});
76-
fs.writeFile(path.paramGoFilePath, content, (err) => {
86+
fs.writeFile(path.bodyGoFilePath, content, (err) => {
7787
if (err) throw err;
7888
});
7989
console.log()
8090
console.log("Request Body Parameter:")
81-
console.log(` - ${path.paramGoFilePath}:1`)
82-
console.log(` - ${path.paramJsonFilePath}:1`)
91+
console.log(` - ${path.bodyGoFilePath}:1`)
92+
console.log(` - ${path.bodyJsonFilePath}:1`)
93+
}
94+
95+
function writeQueryParam(json, path, content) {
96+
fs.writeFile(path.queryJsonFilePath, json, (err) => {
97+
if (err) throw err;
98+
});
99+
fs.writeFile(path.queryGoFilePath, content, (err) => {
100+
if (err) throw err;
101+
});
102+
console.log()
103+
console.log("Request Query Parameter:")
104+
console.log(` - ${path.queryGoFilePath}:1`)
105+
console.log(` - ${path.queryJsonFilePath}:1`)
83106
}
84107

85108
function buildOpts(body, cliOpts) {
@@ -116,7 +139,7 @@ function buildOpts(body, cliOpts) {
116139
return opts
117140
}
118141

119-
function buildContent(go, path, comment, isParam = false) {
142+
function buildContent(go, path, comment, paramType) {
120143
let content = `// Generated Code But Editable.
121144
// Format The Code with \`go fmt\` or something and edit it manually to use it.
122145
//
@@ -127,10 +150,12 @@ function buildContent(go, path, comment, isParam = false) {
127150
if (go.indexOf('time.') !== -1) {
128151
content += `import "time"\n\n`
129152
}
130-
if (isParam) {
131-
content += `// ${go.split(" ")[1]} is the HTTP request's body parameter.\n//`
132-
} else {
133-
content += `// ${go.split(" ")[1]} represents the response body from an HTTP request.\n//`
153+
if (paramType === "body") {
154+
content += `// ${go.split(" ")[1]} is the structure of the the HTTP Request Body Parameter.\n//`
155+
} else if (paramType === "query") {
156+
content += `// ${go.split(" ")[1]} is the structure of the HTTP Request Query Parameter.\n//`
157+
}else{
158+
content += `// ${go.split(" ")[1]} is the structure of the HTTP Response Body.\n//`
134159
}
135160
content += comment
136161
content += go
@@ -154,4 +179,12 @@ function buildComment(url, path, method, res = false) {
154179
return `${comment}\n`
155180
}
156181

182+
function queryToJson(query) {
183+
const json = {}
184+
for (const [key, value] of query.entries()) {
185+
json[key] = value
186+
}
187+
return json
188+
}
189+
157190
module.exports = run;

tests/.api-to-go.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"jsonplaceholder.typicode.com":
2+
docs:
3+
- https://jsonplaceholder.typicode.com/
4+
format:
5+
- /posts/{post}
6+
- /todos/{todo}

tests/api-to-go.bats

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/usr/bin/env bats
2+
3+
load helper/helper
4+
5+
setup() {
6+
if [ "$BATS_TEST_NUMBER" -eq 1 ]; then
7+
init_doc
8+
rm -rf ./jsonplaceholder.typicode.com
9+
fi
10+
11+
BASE_HOST="jsonplaceholder.typicode.com"
12+
BASE_URL="https://$BASE_HOST"
13+
API_TO_GO="node ../bin/api-to-go.js"
14+
doc "## $BATS_TEST_DESCRIPTION"
15+
}
16+
17+
teardown(){
18+
write_doc_details "api-to-go"
19+
}
20+
21+
@test "Command with Path parameters" {
22+
COMMAND="$API_TO_GO $BASE_URL/todos/1"
23+
run eval "$COMMAND"
24+
[ "$status" -eq 0 ]
25+
26+
assert_files_match /todos/todo_get.go
27+
assert_files_match /todos/todo_get.json
28+
}
29+
30+
@test "Command with Query parameters" {
31+
QUERY='?userId=1&completed=false'
32+
COMMAND="$API_TO_GO $BASE_URL/todos$QUERY"
33+
run eval "$COMMAND"
34+
[ "$status" -eq 0 ]
35+
36+
assert_files_match /todos_get.go
37+
assert_files_match /todos_get.json
38+
assert_files_match /todos_get_query.go
39+
assert_files_match /todos_get_query.json
40+
}
41+
42+
@test "Command with Body parameters" {
43+
DATA=' {
44+
"userId": 1,
45+
"id": 1,
46+
"title": "delectus aut autem",
47+
"completed": false
48+
}'
49+
COMMAND="$API_TO_GO $BASE_URL/todos '$DATA'"
50+
run eval "$COMMAND"
51+
[ "$status" -eq 0 ]
52+
53+
assert_files_match /todos_post.go
54+
assert_files_match /todos_post.json
55+
assert_files_match /todos_post_body.go
56+
assert_files_match /todos_post_body.json
57+
}
58+
59+
@test "Command with custom headers" {
60+
HEADERS='{"Content-Type": "application/json"}'
61+
COMMAND="$API_TO_GO --headers '$HEADERS' $BASE_URL/todos/1"
62+
run eval "$COMMAND"
63+
[ "$status" -eq 0 ]
64+
}
65+
66+
@test "Command with GET method" {
67+
COMMAND="$API_TO_GO --method GET $BASE_URL/todos/1"
68+
run eval "$COMMAND"
69+
[ "$status" -eq 0 ]
70+
}
71+
72+
@test "Display help message" {
73+
COMMAND="$API_TO_GO --help"
74+
run eval "$COMMAND"
75+
[ "$status" -eq 0 ]
76+
[ "${lines[0]}" = "Usage: api-to-go [options] <url> [body]" ]
77+
}

tests/helper/diff_helper.bash

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env bash
2+
3+
assert_files_match() {
4+
local generated_file="./$BASE_HOST$1"
5+
local fixture_file="./fixture/$BASE_HOST$1"
6+
7+
diff_output=$(diff "$generated_file" "$fixture_file")
8+
diff_status=$?
9+
10+
[ "$diff_status" -eq 0 ]
11+
}

0 commit comments

Comments
 (0)