Skip to content

Commit 57452c0

Browse files
Merge pull request #204 from rakutentech/feature/route-param-ui
Feature/route param UI
2 parents 8e8aa8e + dfbd354 commit 57452c0

File tree

4 files changed

+209
-149
lines changed

4 files changed

+209
-149
lines changed

src/LaravelRequestDocs.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,12 @@ public function getControllersInfo(array $onlyMethods): Collection
189189
$controllerName = (new ReflectionClass($controllerFullPath))->getShortName();
190190
}
191191

192-
$pathParameters = $this->routePath->getPathParameters($route);
192+
$pathParameters = [];
193+
$pp = $this->routePath->getPathParameters($route);
194+
// same format as rules
195+
foreach ($pp as $k => $v) {
196+
$pathParameters[$k] = [$v];
197+
}
193198

194199
$doc = new Doc(
195200
$route->uri,

ui/src/components/ApiInfo.tsx

Lines changed: 37 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
22
import shortid from 'shortid';
33
import { explode } from '../libs/strings'
44
import type { IAPIInfo } from '../libs/types'
5+
import ApiInfoRules from './elements/ApiInfoRules'
56
import { ChevronRightIcon, LinkIcon, EnvelopeIcon } from '@heroicons/react/24/outline'
67

78
interface Props {
@@ -28,66 +29,6 @@ export default function ApiInfo(props: Props) {
2829
setHasFile(files.length > 0)
2930
}, [])
3031

31-
const StyledRule = (theRule: any): JSX.Element => {
32-
theRule = theRule.rule
33-
const split = theRule.split(':')
34-
35-
if (theRule == 'url') {
36-
return (
37-
<div className="block">
38-
<LinkIcon className='inline-block w-4 h-4' /> {theRule}
39-
</div>
40-
)
41-
}
42-
if (theRule == 'email') {
43-
return (
44-
<div className="block">
45-
<EnvelopeIcon className='inline-block w-4 h-4' /> {theRule}
46-
</div>
47-
)
48-
}
49-
50-
if (split.length < 2) {
51-
return (
52-
<div className='' dangerouslySetInnerHTML={{ __html: explode(theRule, 50, "<br/>") }} />
53-
)
54-
}
55-
56-
const keyPart = split[0]
57-
const valuePart = split.slice(1).join(' ')
58-
if (keyPart == 'max') {
59-
return (
60-
<div className="block badge badge-primary badge-outline mt-1 mb-1 rounded-sm">{`<= ${valuePart}`}</div>
61-
)
62-
}
63-
if (keyPart == 'min') {
64-
return (
65-
<div className="block badge badge-primary badge-outline mt-1 mb-1 rounded-sm">{`>= ${valuePart}`}</div>
66-
)
67-
}
68-
if (keyPart == 'date_format') {
69-
return (
70-
<div className="block badge badge-info badge-outline mt-1 mb-1 rounded-sm">
71-
{`Format: ${valuePart}`}
72-
</div>
73-
)
74-
}
75-
if (keyPart == 'regex') {
76-
return (
77-
<>
78-
<div className="inline-block badge badge-info badge-outline mt-1 mb-1 mr-2 rounded-sm">
79-
Regexp
80-
</div>
81-
<code>${valuePart}</code>
82-
</>
83-
)
84-
}
85-
86-
return (
87-
<div className='' dangerouslySetInnerHTML={{ __html: explode(theRule, 50, "<br/>") }} />
88-
)
89-
}
90-
9132
return (
9233
<>
9334
<h2 className='text-lg' id={method + lrdDocsItem.uri}>
@@ -96,105 +37,53 @@ export default function ApiInfo(props: Props) {
9637
</h2>
9738
<h3 className='pt-4'>
9839
<span className='text-sm text-slate-500'>REQUEST SCHEMA</span>
99-
<code className='pl-2 text-xs'>
40+
<br />
41+
<code className='text-xs'>
10042
{hasFile ? (
10143
'multipart/form-data'
10244
) : (
10345
'application/json'
10446
)}
10547
</code>
10648
</h3>
49+
{(Object.keys(lrdDocsItem.path_parameters).length > 0) && (
50+
<>
51+
<h3 className='pt-4'>
52+
<span className='text-sm text-slate-500'>
53+
PATH PARAMETERS
54+
</span>
55+
</h3>
56+
<div className='pt-4'>
57+
58+
<table className="table table-fixed table-compact w-full">
59+
<tbody>
60+
{Object.keys(lrdDocsItem.path_parameters).map((rule) => (
61+
<ApiInfoRules key={shortid.generate()} mainRule={rule} rules={lrdDocsItem.path_parameters[rule]} />
62+
))}
63+
</tbody>
64+
</table>
65+
</div>
66+
</>
67+
68+
69+
)}
70+
71+
<h3 className='pt-4'>
72+
<span className='text-sm text-slate-500'>
73+
{(method == 'POST' || method == 'PUT' || method == 'PATCH') ? 'REQUEST BODY PARAMETERS' : 'QUERY PARAMETERS'}
74+
</span>
75+
{(lrdDocsItem.rules && Object.keys(lrdDocsItem.rules).length == 0) && (
76+
<div className='text-sm text-slate-500'>
77+
No Rules Defined
78+
</div>
79+
)}
80+
</h3>
10781
<div className='pt-4'>
10882

10983
<table className="table table-fixed table-compact w-full">
11084
<tbody>
111-
{lrdDocsItem.rules && Object.keys(lrdDocsItem.rules).map((key) => (
112-
113-
<tr key={shortid.generate()}>
114-
<th className='param-cell'>
115-
<span className='text-blue-500 pr-1'>¬</span>
116-
<code className='pl-1'>
117-
{key}
118-
{(key.endsWith(".*")) ? (
119-
<ChevronRightIcon key={shortid.generate()} className='inline-block w-4 h-4' />
120-
) : (<span key={shortid.generate()}></span>)}
121-
</code>
122-
{lrdDocsItem.rules[key].map((rule) => (
123-
rule.split('|').map((theRule) => (
124-
(theRule == "file" || theRule == "image") ? (
125-
<div key={shortid.generate()} className="block badge badge-success badge-outline ml-4 mt-1 mb-1 rounded-sm title">{theRule}</div>
126-
) : (<span key={shortid.generate()}></span>)
127-
))
128-
))}
129-
{lrdDocsItem.rules[key].map((rule) => (
130-
rule.split('|').map((theRule) => (
131-
(theRule == "required") ? (
132-
<div className='block ml-6' key={shortid.generate()}>
133-
<code className='text-error font-normal'>{theRule}</code>
134-
</div>
135-
) : (<span key={shortid.generate()}></span>)
136-
))
137-
))}
138-
{lrdDocsItem.rules[key].map((rule) => (
139-
rule.split('|').map((theRule) => (
140-
(theRule.startsWith("required_if")) ? (
141-
<div className='block ml-6' key={shortid.generate()}>
142-
<code className='text-red-300 font-normal'>required_if</code>
143-
</div>
144-
) : (<span key={shortid.generate()}></span>)
145-
))
146-
))}
147-
</th>
148-
<td>
149-
{lrdDocsItem.rules[key].map((rule) => (
150-
rule.split('|').map((theRule) => {
151-
if (theRule == "required") {
152-
return (<span key={shortid.generate()}></span>)
153-
}
154-
if (theRule == "integer"
155-
|| theRule == "string"
156-
|| theRule == "bool"
157-
|| theRule == "date"
158-
|| theRule == "file"
159-
|| theRule == "image"
160-
|| theRule == "array"
161-
|| theRule == "nullable") {
162-
return (
163-
<div key={shortid.generate()} className='capitalize text-slate-500'>
164-
{theRule}
165-
</div>)
166-
}
167-
return (<span key={shortid.generate()}></span>)
168-
})
169-
))}
170-
{lrdDocsItem.rules[key].map((rule) => (
171-
rule.split('|').map((theRule) => {
172-
if (theRule == "required") {
173-
return (<span key={shortid.generate()}></span>)
174-
}
175-
return (<span key={shortid.generate()}></span>)
176-
})
177-
))}
178-
{lrdDocsItem.rules[key].map((rule) => (
179-
rule.split('|').map((theRule) => {
180-
if (theRule == "required"
181-
|| theRule == "integer"
182-
|| theRule == "string"
183-
|| theRule == "bool"
184-
|| theRule == "date"
185-
|| theRule == "file"
186-
|| theRule == "image"
187-
|| theRule == "array"
188-
|| theRule == "nullable") {
189-
return (<span key={shortid.generate()}></span>)
190-
}
191-
return (<span key={shortid.generate()}>
192-
<StyledRule rule={theRule} />
193-
</span>)
194-
})
195-
))}
196-
</td>
197-
</tr>
85+
{lrdDocsItem.rules && Object.keys(lrdDocsItem.rules).map((rule) => (
86+
<ApiInfoRules key={shortid.generate()} mainRule={rule} rules={lrdDocsItem.rules[rule]} />
19887
))}
19988
</tbody>
20089
</table>
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import React from 'react';
2+
import { explode } from '../../libs/strings'
3+
import shortid from 'shortid';
4+
import { ChevronRightIcon, LinkIcon, EnvelopeIcon } from '@heroicons/react/24/outline'
5+
6+
interface Props {
7+
rules: string[],
8+
mainRule: string,
9+
}
10+
11+
export default function ApiInfoRules(props: Props) {
12+
const { rules, mainRule } = props
13+
const StyledRule = (rule: any): JSX.Element => {
14+
const theRule = rule.rule
15+
const split = theRule.split(':')
16+
17+
if (theRule == 'url') {
18+
return (
19+
<div className="block">
20+
<LinkIcon className='inline-block w-4 h-4' /> {theRule}
21+
</div>
22+
)
23+
}
24+
if (theRule == 'email') {
25+
return (
26+
<div className="block">
27+
<EnvelopeIcon className='inline-block w-4 h-4' /> {theRule}
28+
</div>
29+
)
30+
}
31+
32+
if (split.length < 2) {
33+
return (
34+
<div className='' dangerouslySetInnerHTML={{ __html: explode(theRule, 50, "<br/>") }} />
35+
)
36+
}
37+
38+
const keyPart = split[0]
39+
const valuePart = split.slice(1).join(' ')
40+
if (keyPart == 'max') {
41+
return (
42+
<div className="block badge badge-primary badge-outline mt-1 mb-1 rounded-sm">{`<= ${valuePart}`}</div>
43+
)
44+
}
45+
if (keyPart == 'min') {
46+
return (
47+
<div className="block badge badge-primary badge-outline mt-1 mb-1 rounded-sm">{`>= ${valuePart}`}</div>
48+
)
49+
}
50+
if (keyPart == 'date_format') {
51+
return (
52+
<div className="block badge badge-info badge-outline mt-1 mb-1 rounded-sm">
53+
{`Format: ${valuePart}`}
54+
</div>
55+
)
56+
}
57+
if (keyPart == 'regex') {
58+
return (
59+
<>
60+
<div className="inline-block badge badge-info badge-outline mt-1 mb-1 mr-2 rounded-sm">
61+
Regexp
62+
</div>
63+
<code>${valuePart}</code>
64+
</>
65+
)
66+
}
67+
68+
return (
69+
<div className='' dangerouslySetInnerHTML={{ __html: explode(theRule, 50, "<br/>") }} />
70+
)
71+
}
72+
73+
74+
return (
75+
<>
76+
<tr>
77+
<th className='param-cell'>
78+
<span className='text-blue-500 pr-1'>¬</span>
79+
<code className='pl-1'>
80+
{mainRule}
81+
{(mainRule.endsWith(".*")) ? (
82+
<ChevronRightIcon key={shortid.generate()} className='inline-block w-4 h-4' />
83+
) : (<span key={shortid.generate()}></span>)}
84+
</code>
85+
{rules.map((rule) => (
86+
rule.split('|').map((theRule) => (
87+
(theRule == "file" || theRule == "image") ? (
88+
<div key={shortid.generate()} className="block badge badge-success badge-outline ml-4 mt-1 mb-1 rounded-sm title">{theRule}</div>
89+
) : (<span key={shortid.generate()}></span>)
90+
))
91+
))}
92+
{rules.map((rule) => (
93+
rule.split('|').map((theRule) => (
94+
(theRule == "required") ? (
95+
<div className='block ml-6' key={shortid.generate()}>
96+
<code className='text-error font-normal'>{theRule}</code>
97+
</div>
98+
) : (<span key={shortid.generate()}></span>)
99+
))
100+
))}
101+
{rules.map((rule) => (
102+
rule.split('|').map((theRule) => (
103+
(theRule.startsWith("required_if")) ? (
104+
<div className='block ml-6' key={shortid.generate()}>
105+
<code className='text-red-300 font-normal'>required_if</code>
106+
</div>
107+
) : (<span key={shortid.generate()}></span>)
108+
))
109+
))}
110+
</th>
111+
<td>
112+
{rules.map((rule) => (
113+
rule.split('|').map((theRule) => {
114+
if (theRule == "required") {
115+
return (<span key={shortid.generate()}></span>)
116+
}
117+
if (theRule == "integer"
118+
|| theRule == "string"
119+
|| theRule == "bool"
120+
|| theRule == "date"
121+
|| theRule == "file"
122+
|| theRule == "image"
123+
|| theRule == "array"
124+
|| theRule == "nullable") {
125+
return (
126+
<div key={shortid.generate()} className='capitalize text-slate-500'>
127+
{theRule}
128+
</div>)
129+
}
130+
return (<span key={shortid.generate()}></span>)
131+
})
132+
))}
133+
{rules.map((rule) => (
134+
rule.split('|').map((theRule) => {
135+
if (theRule == "required") {
136+
return (<span key={shortid.generate()}></span>)
137+
}
138+
return (<span key={shortid.generate()}></span>)
139+
})
140+
))}
141+
{rules.map((rule) => (
142+
rule.split('|').map((theRule) => {
143+
if (theRule == "required"
144+
|| theRule == "integer"
145+
|| theRule == "string"
146+
|| theRule == "bool"
147+
|| theRule == "date"
148+
|| theRule == "file"
149+
|| theRule == "image"
150+
|| theRule == "array"
151+
|| theRule == "nullable") {
152+
return (<span key={shortid.generate()}></span>)
153+
}
154+
return (<span key={shortid.generate()}>
155+
<StyledRule rule={theRule} />
156+
</span>)
157+
})
158+
))}
159+
</td>
160+
</tr>
161+
162+
</>
163+
)
164+
}

0 commit comments

Comments
 (0)