@gomjellie/lazyapi 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +162 -0
- package/dist/App.d.ts +9 -0
- package/dist/App.js +65 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +39 -0
- package/dist/components/common/ErrorMessage.d.ts +10 -0
- package/dist/components/common/ErrorMessage.js +17 -0
- package/dist/components/common/KeybindingFooter.d.ts +18 -0
- package/dist/components/common/KeybindingFooter.js +20 -0
- package/dist/components/common/LoadingSpinner.d.ts +9 -0
- package/dist/components/common/LoadingSpinner.js +15 -0
- package/dist/components/common/SearchInput.d.ts +14 -0
- package/dist/components/common/SearchInput.js +72 -0
- package/dist/components/common/StatusBar.d.ts +14 -0
- package/dist/components/common/StatusBar.js +36 -0
- package/dist/components/detail-view/DetailView.d.ts +5 -0
- package/dist/components/detail-view/DetailView.js +266 -0
- package/dist/components/detail-view/LeftPanel.d.ts +19 -0
- package/dist/components/detail-view/LeftPanel.js +363 -0
- package/dist/components/detail-view/ParameterForm.d.ts +17 -0
- package/dist/components/detail-view/ParameterForm.js +77 -0
- package/dist/components/detail-view/RightPanel.d.ts +22 -0
- package/dist/components/detail-view/RightPanel.js +251 -0
- package/dist/components/list-view/EndpointList.d.ts +12 -0
- package/dist/components/list-view/EndpointList.js +72 -0
- package/dist/components/list-view/Explorer.d.ts +13 -0
- package/dist/components/list-view/Explorer.js +83 -0
- package/dist/components/list-view/FilterBar.d.ts +12 -0
- package/dist/components/list-view/FilterBar.js +33 -0
- package/dist/components/list-view/ListView.d.ts +5 -0
- package/dist/components/list-view/ListView.js +304 -0
- package/dist/components/list-view/SearchBar.d.ts +11 -0
- package/dist/components/list-view/SearchBar.js +14 -0
- package/dist/hooks/useApiClient.d.ts +11 -0
- package/dist/hooks/useApiClient.js +34 -0
- package/dist/hooks/useKeyPress.d.ts +26 -0
- package/dist/hooks/useKeyPress.js +57 -0
- package/dist/hooks/useNavigation.d.ts +10 -0
- package/dist/hooks/useNavigation.js +33 -0
- package/dist/services/http-client.d.ts +21 -0
- package/dist/services/http-client.js +173 -0
- package/dist/services/openapi-parser.d.ts +47 -0
- package/dist/services/openapi-parser.js +318 -0
- package/dist/store/appSlice.d.ts +14 -0
- package/dist/store/appSlice.js +144 -0
- package/dist/store/hooks.d.ts +9 -0
- package/dist/store/hooks.js +7 -0
- package/dist/store/index.d.ts +12 -0
- package/dist/store/index.js +12 -0
- package/dist/types/app-state.d.ts +126 -0
- package/dist/types/app-state.js +4 -0
- package/dist/types/openapi.d.ts +86 -0
- package/dist/types/openapi.js +115 -0
- package/dist/utils/clipboard.d.ts +11 -0
- package/dist/utils/clipboard.js +26 -0
- package/dist/utils/formatters.d.ts +35 -0
- package/dist/utils/formatters.js +93 -0
- package/dist/utils/schema-formatter.d.ts +21 -0
- package/dist/utils/schema-formatter.js +301 -0
- package/dist/utils/validators.d.ts +16 -0
- package/dist/utils/validators.js +158 -0
- package/package.json +88 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP 클라이언트 서비스
|
|
3
|
+
*/
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
export class HttpClientService {
|
|
6
|
+
client;
|
|
7
|
+
constructor(baseURL) {
|
|
8
|
+
this.client = axios.create({
|
|
9
|
+
baseURL,
|
|
10
|
+
timeout: 30000,
|
|
11
|
+
validateStatus: () => true, // 모든 상태 코드를 허용
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* API 요청 실행
|
|
16
|
+
*/
|
|
17
|
+
async executeRequest(endpoint, parameterValues, headers, requestBody, baseURL) {
|
|
18
|
+
const startTime = Date.now();
|
|
19
|
+
try {
|
|
20
|
+
// URL 빌드 (path parameters 치환)
|
|
21
|
+
let url = endpoint.path;
|
|
22
|
+
const pathParams = endpoint.parameters.filter((p) => p.in === 'path');
|
|
23
|
+
for (const param of pathParams) {
|
|
24
|
+
const value = parameterValues[param.name] || '';
|
|
25
|
+
url = url.replace(`{${param.name}}`, encodeURIComponent(value));
|
|
26
|
+
}
|
|
27
|
+
// Query parameters 빌드
|
|
28
|
+
const queryParams = endpoint.parameters.filter((p) => p.in === 'query');
|
|
29
|
+
const params = {};
|
|
30
|
+
for (const param of queryParams) {
|
|
31
|
+
const value = parameterValues[param.name];
|
|
32
|
+
if (value !== undefined && value !== '') {
|
|
33
|
+
params[param.name] = value;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Headers 빌드
|
|
37
|
+
const headerParams = endpoint.parameters.filter((p) => p.in === 'header');
|
|
38
|
+
const requestHeaders = { ...headers };
|
|
39
|
+
for (const param of headerParams) {
|
|
40
|
+
const value = parameterValues[param.name];
|
|
41
|
+
if (value !== undefined && value !== '') {
|
|
42
|
+
requestHeaders[param.name] = value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Request body 파싱
|
|
46
|
+
let data;
|
|
47
|
+
if (requestBody && requestBody.trim()) {
|
|
48
|
+
try {
|
|
49
|
+
data = JSON.parse(requestBody);
|
|
50
|
+
requestHeaders['Content-Type'] = 'application/json';
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// JSON 파싱 실패시 문자열로 전송
|
|
54
|
+
data = requestBody;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Axios 요청 설정
|
|
58
|
+
const config = {
|
|
59
|
+
method: endpoint.method.toLowerCase(),
|
|
60
|
+
url,
|
|
61
|
+
params,
|
|
62
|
+
headers: requestHeaders,
|
|
63
|
+
data,
|
|
64
|
+
};
|
|
65
|
+
// baseURL이 제공된 경우 사용
|
|
66
|
+
if (baseURL) {
|
|
67
|
+
config.baseURL = baseURL;
|
|
68
|
+
}
|
|
69
|
+
// 요청 실행
|
|
70
|
+
const response = await this.client.request(config);
|
|
71
|
+
const duration = Date.now() - startTime;
|
|
72
|
+
// 응답 크기 계산
|
|
73
|
+
const responseString = JSON.stringify(response.data);
|
|
74
|
+
const size = new Blob([responseString]).size;
|
|
75
|
+
// AxiosHeaders를 일반 객체로 변환
|
|
76
|
+
// AxiosHeaders 객체가 화면에 표시되지 않도록 일반 객체로 변환
|
|
77
|
+
// JSON 직렬화/역직렬화를 통해 순수 객체로 변환
|
|
78
|
+
const responseHeaders = {};
|
|
79
|
+
if (response.headers) {
|
|
80
|
+
try {
|
|
81
|
+
// AxiosHeaders를 일반 객체로 변환
|
|
82
|
+
const headersObj = JSON.parse(JSON.stringify(response.headers));
|
|
83
|
+
for (const key in headersObj) {
|
|
84
|
+
if (Object.prototype.hasOwnProperty.call(headersObj, key)) {
|
|
85
|
+
const value = headersObj[key];
|
|
86
|
+
// 값이 문자열이거나 변환 가능한 경우만 추가
|
|
87
|
+
if (typeof value === 'string') {
|
|
88
|
+
responseHeaders[key] = value;
|
|
89
|
+
}
|
|
90
|
+
else if (Array.isArray(value)) {
|
|
91
|
+
// 배열인 경우 첫 번째 요소 사용 (일반적으로 헤더는 단일 값)
|
|
92
|
+
responseHeaders[key] = String(value[0] || '');
|
|
93
|
+
}
|
|
94
|
+
else if (value != null) {
|
|
95
|
+
responseHeaders[key] = String(value);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// JSON 변환 실패 시 직접 순회
|
|
102
|
+
const headersObj = response.headers;
|
|
103
|
+
for (const key in headersObj) {
|
|
104
|
+
if (Object.prototype.hasOwnProperty.call(headersObj, key)) {
|
|
105
|
+
const value = headersObj[key];
|
|
106
|
+
if (typeof value === 'string') {
|
|
107
|
+
responseHeaders[key] = value;
|
|
108
|
+
}
|
|
109
|
+
else if (Array.isArray(value)) {
|
|
110
|
+
responseHeaders[key] = String(value[0] || '');
|
|
111
|
+
}
|
|
112
|
+
else if (value != null) {
|
|
113
|
+
responseHeaders[key] = String(value);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Cookies 추출
|
|
120
|
+
const cookies = {};
|
|
121
|
+
const setCookieHeader = responseHeaders['set-cookie'];
|
|
122
|
+
if (setCookieHeader) {
|
|
123
|
+
const cookieArray = Array.isArray(setCookieHeader) ? setCookieHeader : [setCookieHeader];
|
|
124
|
+
for (const cookie of cookieArray) {
|
|
125
|
+
const [nameValue] = cookie.split(';');
|
|
126
|
+
const [name, value] = nameValue.split('=');
|
|
127
|
+
if (name && value) {
|
|
128
|
+
cookies[name.trim()] = value.trim();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
status: response.status,
|
|
134
|
+
statusText: response.statusText,
|
|
135
|
+
headers: responseHeaders,
|
|
136
|
+
body: response.data,
|
|
137
|
+
size,
|
|
138
|
+
duration,
|
|
139
|
+
cookies: Object.keys(cookies).length > 0 ? cookies : undefined,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
const duration = Date.now() - startTime;
|
|
144
|
+
if (axios.isAxiosError(error)) {
|
|
145
|
+
// 네트워크 에러 또는 타임아웃
|
|
146
|
+
return {
|
|
147
|
+
status: 0,
|
|
148
|
+
statusText: error.message,
|
|
149
|
+
headers: {},
|
|
150
|
+
body: { error: error.message, code: error.code },
|
|
151
|
+
size: 0,
|
|
152
|
+
duration,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 기본 URL 설정
|
|
160
|
+
*/
|
|
161
|
+
setBaseURL(baseURL) {
|
|
162
|
+
this.client.defaults.baseURL = baseURL;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* 기본 헤더 설정
|
|
166
|
+
*/
|
|
167
|
+
setDefaultHeaders(headers) {
|
|
168
|
+
this.client.defaults.headers.common = {
|
|
169
|
+
...this.client.defaults.headers.common,
|
|
170
|
+
...headers,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI 문서 파싱 서비스
|
|
3
|
+
*/
|
|
4
|
+
import { OpenAPIDocument, Endpoint, HttpMethod } from '../types/openapi.js';
|
|
5
|
+
import type { OpenAPI } from 'openapi-types';
|
|
6
|
+
export declare class OpenAPIParserService {
|
|
7
|
+
/**
|
|
8
|
+
* OpenAPI 문서를 파일에서 로드하고 파싱
|
|
9
|
+
*/
|
|
10
|
+
parseFromFile(filePath: string): Promise<OpenAPI.Document>;
|
|
11
|
+
/**
|
|
12
|
+
* OpenAPI 문서에서 엔드포인트 목록 추출
|
|
13
|
+
*/
|
|
14
|
+
extractEndpoints(document: OpenAPIDocument): Endpoint[];
|
|
15
|
+
/**
|
|
16
|
+
* OpenAPI v3 Operation 객체에서 Endpoint 생성
|
|
17
|
+
*/
|
|
18
|
+
private createEndpointV3;
|
|
19
|
+
/**
|
|
20
|
+
* Swagger 2.0 Operation 객체에서 Endpoint 생성
|
|
21
|
+
*/
|
|
22
|
+
private createEndpointV2;
|
|
23
|
+
/**
|
|
24
|
+
* OpenAPI v3 파라미터를 정규화
|
|
25
|
+
*/
|
|
26
|
+
private normalizeParameterV3;
|
|
27
|
+
/**
|
|
28
|
+
* Swagger 2.0 파라미터를 정규화
|
|
29
|
+
*/
|
|
30
|
+
private normalizeParameterV2;
|
|
31
|
+
/**
|
|
32
|
+
* OpenAPI v3 RequestBody를 정규화
|
|
33
|
+
*/
|
|
34
|
+
private normalizeRequestBodyV3;
|
|
35
|
+
/**
|
|
36
|
+
* 엔드포인트 필터링
|
|
37
|
+
*/
|
|
38
|
+
filterEndpoints(endpoints: Endpoint[], method: HttpMethod | 'ALL'): Endpoint[];
|
|
39
|
+
/**
|
|
40
|
+
* 엔드포인트 검색
|
|
41
|
+
*/
|
|
42
|
+
searchEndpoints(endpoints: Endpoint[], query: string): Endpoint[];
|
|
43
|
+
/**
|
|
44
|
+
* 태그별로 엔드포인트 그룹화
|
|
45
|
+
*/
|
|
46
|
+
groupByTags(endpoints: Endpoint[]): Map<string, Endpoint[]>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI 문서 파싱 서비스
|
|
3
|
+
*/
|
|
4
|
+
import SwaggerParser from '@apidevtools/swagger-parser';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { isOpenAPIV2Document, isReferenceObject, resolveRef, } from '../types/openapi.js';
|
|
8
|
+
export class OpenAPIParserService {
|
|
9
|
+
/**
|
|
10
|
+
* OpenAPI 문서를 파일에서 로드하고 파싱
|
|
11
|
+
*/
|
|
12
|
+
async parseFromFile(filePath) {
|
|
13
|
+
try {
|
|
14
|
+
// 파일 존재 확인
|
|
15
|
+
if (!fs.existsSync(filePath)) {
|
|
16
|
+
throw new Error(`파일을 찾을 수 없습니다: ${filePath}`);
|
|
17
|
+
}
|
|
18
|
+
// 파일 확장자 확인
|
|
19
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
20
|
+
if (!['.json', '.yaml', '.yml'].includes(ext)) {
|
|
21
|
+
throw new Error('지원하지 않는 파일 형식입니다. JSON 또는 YAML 파일만 지원합니다.');
|
|
22
|
+
}
|
|
23
|
+
// SwaggerParser로 문서 파싱
|
|
24
|
+
// bundle()을 사용하여 $ref를 내부 참조로 유지하면서 외부 파일만 번들링
|
|
25
|
+
// validate()는 모든 $ref를 resolve하므로 사용하지 않음
|
|
26
|
+
const api = await SwaggerParser.bundle(filePath);
|
|
27
|
+
return api;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error instanceof Error) {
|
|
31
|
+
throw new Error(`OpenAPI 문서 파싱 실패: ${error.message}`);
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* OpenAPI 문서에서 엔드포인트 목록 추출
|
|
38
|
+
*/
|
|
39
|
+
extractEndpoints(document) {
|
|
40
|
+
const endpoints = [];
|
|
41
|
+
if (!document.paths) {
|
|
42
|
+
return endpoints;
|
|
43
|
+
}
|
|
44
|
+
const isV2 = isOpenAPIV2Document(document);
|
|
45
|
+
if (isV2) {
|
|
46
|
+
// Swagger 2.0 처리
|
|
47
|
+
const v2Doc = document;
|
|
48
|
+
for (const [path, pathItem] of Object.entries(v2Doc.paths)) {
|
|
49
|
+
if (!pathItem)
|
|
50
|
+
continue;
|
|
51
|
+
const methods = [
|
|
52
|
+
'get',
|
|
53
|
+
'post',
|
|
54
|
+
'put',
|
|
55
|
+
'delete',
|
|
56
|
+
'patch',
|
|
57
|
+
'options',
|
|
58
|
+
'head',
|
|
59
|
+
'trace',
|
|
60
|
+
];
|
|
61
|
+
for (const method of methods) {
|
|
62
|
+
const operation = pathItem[method];
|
|
63
|
+
if (operation) {
|
|
64
|
+
endpoints.push(this.createEndpointV2(path, method.toUpperCase(), operation, pathItem));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// OpenAPI 3.x 처리
|
|
71
|
+
const v3Doc = document;
|
|
72
|
+
for (const [path, pathItem] of Object.entries(v3Doc.paths)) {
|
|
73
|
+
if (!pathItem)
|
|
74
|
+
continue;
|
|
75
|
+
const methods = [
|
|
76
|
+
'get',
|
|
77
|
+
'post',
|
|
78
|
+
'put',
|
|
79
|
+
'delete',
|
|
80
|
+
'patch',
|
|
81
|
+
'options',
|
|
82
|
+
'head',
|
|
83
|
+
'trace',
|
|
84
|
+
];
|
|
85
|
+
for (const method of methods) {
|
|
86
|
+
const operation = pathItem[method];
|
|
87
|
+
if (operation) {
|
|
88
|
+
endpoints.push(this.createEndpointV3(path, method.toUpperCase(), operation, pathItem, document));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return endpoints;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* OpenAPI v3 Operation 객체에서 Endpoint 생성
|
|
97
|
+
*/
|
|
98
|
+
createEndpointV3(path, method, operation, pathItem, document) {
|
|
99
|
+
const id = `${method}_${path.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
100
|
+
// 파라미터 병합 및 정규화
|
|
101
|
+
const pathParameters = pathItem.parameters || [];
|
|
102
|
+
const operationParameters = operation.parameters || [];
|
|
103
|
+
const allParams = [...pathParameters, ...operationParameters];
|
|
104
|
+
const parameters = allParams
|
|
105
|
+
.filter((p) => !isReferenceObject(p))
|
|
106
|
+
.map((p) => this.normalizeParameterV3(p));
|
|
107
|
+
// RequestBody 정규화
|
|
108
|
+
let requestBody;
|
|
109
|
+
if (operation.requestBody) {
|
|
110
|
+
if (isReferenceObject(operation.requestBody)) {
|
|
111
|
+
// $ref를 resolve
|
|
112
|
+
if (document) {
|
|
113
|
+
const resolved = resolveRef(operation.requestBody.$ref, document);
|
|
114
|
+
if (resolved && !isReferenceObject(resolved)) {
|
|
115
|
+
requestBody = this.normalizeRequestBodyV3(resolved);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
requestBody = this.normalizeRequestBodyV3(operation.requestBody);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
id,
|
|
125
|
+
method,
|
|
126
|
+
path,
|
|
127
|
+
summary: operation.summary,
|
|
128
|
+
description: operation.description ?? pathItem.description,
|
|
129
|
+
tags: operation.tags || [],
|
|
130
|
+
parameters,
|
|
131
|
+
requestBody,
|
|
132
|
+
responses: operation.responses || {},
|
|
133
|
+
operationId: operation.operationId,
|
|
134
|
+
deprecated: operation.deprecated,
|
|
135
|
+
security: operation.security,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Swagger 2.0 Operation 객체에서 Endpoint 생성
|
|
140
|
+
*/
|
|
141
|
+
createEndpointV2(path, method, operation, pathItem) {
|
|
142
|
+
const id = `${method}_${path.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
143
|
+
// 파라미터 병합 및 정규화
|
|
144
|
+
const pathParameters = pathItem.parameters || [];
|
|
145
|
+
const operationParameters = operation.parameters || [];
|
|
146
|
+
const allParams = [...pathParameters, ...operationParameters];
|
|
147
|
+
const parameters = [];
|
|
148
|
+
let requestBody;
|
|
149
|
+
for (const p of allParams) {
|
|
150
|
+
if (isReferenceObject(p))
|
|
151
|
+
continue;
|
|
152
|
+
const param = p;
|
|
153
|
+
// Swagger 2.0의 body 파라미터 처리
|
|
154
|
+
if (param.in === 'body') {
|
|
155
|
+
const bodyParam = param;
|
|
156
|
+
// requestBody로도 저장
|
|
157
|
+
requestBody = {
|
|
158
|
+
description: bodyParam.description,
|
|
159
|
+
required: bodyParam.required,
|
|
160
|
+
schema: bodyParam.schema,
|
|
161
|
+
};
|
|
162
|
+
// UI 표시를 위해 parameters에도 포함
|
|
163
|
+
parameters.push(this.normalizeParameterV2(param));
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
// 나머지 파라미터들 (path, query, header, formData)
|
|
167
|
+
parameters.push(this.normalizeParameterV2(param));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
id,
|
|
172
|
+
method,
|
|
173
|
+
path,
|
|
174
|
+
summary: operation.summary,
|
|
175
|
+
description: operation.description,
|
|
176
|
+
tags: operation.tags || [],
|
|
177
|
+
parameters,
|
|
178
|
+
requestBody,
|
|
179
|
+
responses: operation.responses || {},
|
|
180
|
+
operationId: operation.operationId,
|
|
181
|
+
deprecated: operation.deprecated,
|
|
182
|
+
security: operation.security,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* OpenAPI v3 파라미터를 정규화
|
|
187
|
+
*/
|
|
188
|
+
normalizeParameterV3(param) {
|
|
189
|
+
return {
|
|
190
|
+
name: param.name,
|
|
191
|
+
in: param.in,
|
|
192
|
+
description: param.description,
|
|
193
|
+
required: param.required,
|
|
194
|
+
deprecated: param.deprecated,
|
|
195
|
+
schema: isReferenceObject(param.schema) ? undefined : param.schema,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Swagger 2.0 파라미터를 정규화
|
|
200
|
+
*/
|
|
201
|
+
normalizeParameterV2(param) {
|
|
202
|
+
// body 파라미터는 이미 schema를 가지고 있음 ($ref도 포함)
|
|
203
|
+
if (param.in === 'body') {
|
|
204
|
+
const bodyParam = param;
|
|
205
|
+
return {
|
|
206
|
+
name: bodyParam.name,
|
|
207
|
+
in: bodyParam.in,
|
|
208
|
+
description: bodyParam.description,
|
|
209
|
+
required: bodyParam.required,
|
|
210
|
+
schema: bodyParam.schema, // $ref인 경우도 그대로 전달
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
// 일반 파라미터: type, items, enum 등을 schema로 변환
|
|
214
|
+
const generalParam = param;
|
|
215
|
+
// Swagger 2.0의 파라미터 속성들을 SchemaObject로 변환
|
|
216
|
+
const schema = {
|
|
217
|
+
type: generalParam.type,
|
|
218
|
+
format: generalParam.format,
|
|
219
|
+
items: generalParam.items,
|
|
220
|
+
default: generalParam.default,
|
|
221
|
+
maximum: generalParam.maximum,
|
|
222
|
+
exclusiveMaximum: generalParam.exclusiveMaximum,
|
|
223
|
+
minimum: generalParam.minimum,
|
|
224
|
+
exclusiveMinimum: generalParam.exclusiveMinimum,
|
|
225
|
+
maxLength: generalParam.maxLength,
|
|
226
|
+
minLength: generalParam.minLength,
|
|
227
|
+
pattern: generalParam.pattern,
|
|
228
|
+
maxItems: generalParam.maxItems,
|
|
229
|
+
minItems: generalParam.minItems,
|
|
230
|
+
uniqueItems: generalParam.uniqueItems,
|
|
231
|
+
enum: generalParam.enum,
|
|
232
|
+
multipleOf: generalParam.multipleOf,
|
|
233
|
+
};
|
|
234
|
+
// undefined 속성 제거
|
|
235
|
+
Object.keys(schema).forEach((key) => {
|
|
236
|
+
if (schema[key] === undefined) {
|
|
237
|
+
delete schema[key];
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
return {
|
|
241
|
+
name: generalParam.name,
|
|
242
|
+
in: generalParam.in,
|
|
243
|
+
description: generalParam.description,
|
|
244
|
+
required: generalParam.required,
|
|
245
|
+
schema: Object.keys(schema).length > 0 ? schema : undefined,
|
|
246
|
+
type: generalParam.type,
|
|
247
|
+
format: generalParam.format,
|
|
248
|
+
enum: generalParam.enum,
|
|
249
|
+
default: generalParam.default,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* OpenAPI v3 RequestBody를 정규화
|
|
254
|
+
*/
|
|
255
|
+
normalizeRequestBodyV3(requestBody) {
|
|
256
|
+
// application/json 스키마 우선 추출
|
|
257
|
+
const jsonContent = requestBody.content?.['application/json'];
|
|
258
|
+
const schema = jsonContent && !isReferenceObject(jsonContent.schema) ? jsonContent.schema : undefined;
|
|
259
|
+
return {
|
|
260
|
+
description: requestBody.description,
|
|
261
|
+
required: requestBody.required,
|
|
262
|
+
schema,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* 엔드포인트 필터링
|
|
267
|
+
*/
|
|
268
|
+
filterEndpoints(endpoints, method) {
|
|
269
|
+
if (method === 'ALL') {
|
|
270
|
+
return endpoints;
|
|
271
|
+
}
|
|
272
|
+
return endpoints.filter((endpoint) => endpoint.method === method);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* 엔드포인트 검색
|
|
276
|
+
*/
|
|
277
|
+
searchEndpoints(endpoints, query) {
|
|
278
|
+
if (!query.trim()) {
|
|
279
|
+
return endpoints;
|
|
280
|
+
}
|
|
281
|
+
const lowerQuery = query.toLowerCase();
|
|
282
|
+
return endpoints.filter((endpoint) => {
|
|
283
|
+
// 경로, 요약, 설명, 태그에서 검색
|
|
284
|
+
const searchableText = [
|
|
285
|
+
endpoint.path,
|
|
286
|
+
endpoint.summary || '',
|
|
287
|
+
endpoint.description || '',
|
|
288
|
+
...endpoint.tags,
|
|
289
|
+
]
|
|
290
|
+
.join(' ')
|
|
291
|
+
.toLowerCase();
|
|
292
|
+
return searchableText.includes(lowerQuery);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* 태그별로 엔드포인트 그룹화
|
|
297
|
+
*/
|
|
298
|
+
groupByTags(endpoints) {
|
|
299
|
+
const groups = new Map();
|
|
300
|
+
for (const endpoint of endpoints) {
|
|
301
|
+
if (endpoint.tags.length === 0) {
|
|
302
|
+
// 태그가 없는 경우 'default'로 분류
|
|
303
|
+
const defaultGroup = groups.get('default') || [];
|
|
304
|
+
defaultGroup.push(endpoint);
|
|
305
|
+
groups.set('default', defaultGroup);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
// 각 태그에 추가
|
|
309
|
+
for (const tag of endpoint.tags) {
|
|
310
|
+
const group = groups.get(tag) || [];
|
|
311
|
+
group.push(endpoint);
|
|
312
|
+
groups.set(tag, group);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return groups;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 애플리케이션 Redux Slice
|
|
3
|
+
*/
|
|
4
|
+
import { AppState, AppMode, Screen, ApiResponse } from '../types/app-state.js';
|
|
5
|
+
import { Endpoint, HttpMethod, OpenAPIDocument } from '../types/openapi.js';
|
|
6
|
+
export declare const setDocument: import("@reduxjs/toolkit").ActionCreatorWithPayload<OpenAPIDocument, "app/setDocument">, setEndpoints: import("@reduxjs/toolkit").ActionCreatorWithPayload<Endpoint[], "app/setEndpoints">, setScreen: import("@reduxjs/toolkit").ActionCreatorWithPayload<Screen, "app/setScreen">, setMode: import("@reduxjs/toolkit").ActionCreatorWithPayload<AppMode, "app/setMode">, setCommandInput: import("@reduxjs/toolkit").ActionCreatorWithPayload<string, "app/setCommandInput">, setError: import("@reduxjs/toolkit").ActionCreatorWithPayload<string | null, "app/setError">, setLoading: import("@reduxjs/toolkit").ActionCreatorWithPayload<boolean, "app/setLoading">, listSelect: import("@reduxjs/toolkit").ActionCreatorWithPayload<number, "app/listSelect">, listSetFilter: import("@reduxjs/toolkit").ActionCreatorWithPayload<HttpMethod | "ALL", "app/listSetFilter">, listSetTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<string | null, "app/listSetTag">, listSetFocus: import("@reduxjs/toolkit").ActionCreatorWithPayload<"LIST" | "TAGS", "app/listSetFocus">, listSelectTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<number, "app/listSelectTag">, listSetSearch: import("@reduxjs/toolkit").ActionCreatorWithPayload<string, "app/listSetSearch">, listSetTagSearch: import("@reduxjs/toolkit").ActionCreatorWithPayload<string, "app/listSetTagSearch">, listScroll: import("@reduxjs/toolkit").ActionCreatorWithPayload<number, "app/listScroll">, detailSet: import("@reduxjs/toolkit").ActionCreatorWithPayload<Endpoint, "app/detailSet">, detailSetFocus: import("@reduxjs/toolkit").ActionCreatorWithPayload<number, "app/detailSetFocus">, detailSetParam: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
7
|
+
key: string;
|
|
8
|
+
value: string;
|
|
9
|
+
}, "app/detailSetParam">, detailSetBody: import("@reduxjs/toolkit").ActionCreatorWithPayload<string, "app/detailSetBody">, detailSetHeader: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
10
|
+
key: string;
|
|
11
|
+
value: string;
|
|
12
|
+
}, "app/detailSetHeader">, detailSetPanel: import("@reduxjs/toolkit").ActionCreatorWithPayload<"left" | "request" | "response", "app/detailSetPanel">, detailSetRequestTab: import("@reduxjs/toolkit").ActionCreatorWithPayload<"headers" | "query" | "body", "app/detailSetRequestTab">, detailSetResponseTab: import("@reduxjs/toolkit").ActionCreatorWithPayload<"headers" | "body" | "cookies" | "curl", "app/detailSetResponseTab">, detailSetResponse: import("@reduxjs/toolkit").ActionCreatorWithPayload<ApiResponse | null, "app/detailSetResponse">, reset: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"app/reset">;
|
|
13
|
+
declare const _default: import("@reduxjs/toolkit").Reducer<AppState>;
|
|
14
|
+
export default _default;
|