-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrss.xml
264 lines (258 loc) · 49.4 KB
/
rss.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Gatsby Starter Blog RSS Feed]]></title><description><![CDATA[A starter blog demonstrating what Gatsby can do.]]></description><link>https://seokchan-H.github.io</link><generator>GatsbyJS</generator><lastBuildDate>Fri, 10 Jan 2025 08:31:40 GMT</lastBuildDate><item><title><![CDATA[[Kotlin] Spring Mockk 리셋 이슈]]></title><description><![CDATA[컴퓨터는 거짓말을 하지 않는다. 하지만 내가 짠 테스트 코드는 거짓말을 하고 있었다.
분명 한번만 호출했지만 검증에서 두번 호출되었다고 실패하고 있었다. SpringMockk 에서 MockkBean, SpykBean 은 MockkClear…]]></description><link>https://seokchan-H.github.io/trouble-shooting/spring-mockk/spykbean-not-reset/</link><guid isPermaLink="false">https://seokchan-H.github.io/trouble-shooting/spring-mockk/spykbean-not-reset/</guid><pubDate>Fri, 10 Jan 2025 00:00:00 GMT</pubDate><content:encoded><p>컴퓨터는 거짓말을 하지 않는다. 하지만 내가 짠 테스트 코드는 거짓말을 하고 있었다.
분명 한번만 호출했지만 검증에서 두번 호출되었다고 실패하고 있었다.</p>
<p>SpringMockk 에서 MockkBean, SpykBean 은 MockkClear 설정에 의해 Mocking 을 Reset 한다.
Reset 의 기본 설정은 MockkClear.AFTER 이다.</p>
<p>문제는 이 기능이 AOP 로 래핑된 객체에 대해서 정상작동 하지 않는다는 것이다.
예를 들어 <code class="language-text">@Transactional</code>, <code class="language-text">@Repository</code> 가 적용된 객체는 테스트시 verify 가 갱신되지 않아 테스트 검증이 실패될 수 있다.</p>
<p>해당 이슈는 CGLIB 기반의 프록시 객체에 대해서 발생한다.
그렇기에 다음과 같이 단순히 객체를 명시적으로 Reset 하면 실패하게 된다.</p>
<div class="gatsby-highlight" data-language="kotlin"><pre class="language-kotlin"><code class="language-kotlin"> <span class="token annotation builtin">@SpykBean</span>
<span class="token keyword">private</span> <span class="token keyword">lateinit</span> <span class="token keyword">var</span> testClass<span class="token operator">:</span> TestClass
<span class="token annotation builtin">@BeforeEach</span>
<span class="token keyword">fun</span> <span class="token function">clearSpykBean</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">clearMocks</span><span class="token punctuation">(</span>testClass<span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre></div>
<p><span
class="gatsby-resp-image-wrapper"
style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 630px; "
>
<a
class="gatsby-resp-image-link"
href="/static/3d9c4b46dea05d558c66d7b7dd02327a/f793b/img.png"
style="display: block"
target="_blank"
rel="noopener"
>
<span
class="gatsby-resp-image-background-image"
style="padding-bottom: 14.556962025316455%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAo0lEQVR42j3OSQ6DMAwFUPYtw66oEkOcmQBhUivU+5/r10RVF09Oonzb2UAagQw25eBaQpkXSXW5l6hq1pbpnrOiKHG753g+aizaY+HcxnVnE58z6hWitPiYgLfyiJ1Mdn5/9RorScxE2ISGNQOsH6G0g7cBgSmh/ohlHYevDQ/+dPCUq/E17eR6cmD1M0Y3IYYIbXxqJnkTKQ2aVqDpiIkfwhfe1FRK9BDbjAAAAABJRU5ErkJggg=='); background-size: cover; display: block;"
></span>
<img
class="gatsby-resp-image-image"
alt="img.png"
title=""
src="/static/3d9c4b46dea05d558c66d7b7dd02327a/f058b/img.png"
srcset="/static/3d9c4b46dea05d558c66d7b7dd02327a/c26ae/img.png 158w,
/static/3d9c4b46dea05d558c66d7b7dd02327a/6bdcf/img.png 315w,
/static/3d9c4b46dea05d558c66d7b7dd02327a/f058b/img.png 630w,
/static/3d9c4b46dea05d558c66d7b7dd02327a/40601/img.png 945w,
/static/3d9c4b46dea05d558c66d7b7dd02327a/78612/img.png 1260w,
/static/3d9c4b46dea05d558c66d7b7dd02327a/f793b/img.png 1404w"
sizes="(max-width: 630px) 100vw, 630px"
style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
loading="lazy"
decoding="async"
/>
</a>
</span></p>
<p>해결방법은 AOP 가 Unwrap 된 객체를 기준으로 명시적인 Reset 을 수행하면 된다.
이를 위해 AopTestUtils 를 사용하면 다음과 같다.</p>
<div class="gatsby-highlight" data-language="kotlin"><pre class="language-kotlin"><code class="language-kotlin"> <span class="token annotation builtin">@SpykBean</span>
<span class="token keyword">private</span> <span class="token keyword">lateinit</span> <span class="token keyword">var</span> testClass<span class="token operator">:</span> TestClass
<span class="token annotation builtin">@BeforeEach</span>
<span class="token keyword">fun</span> <span class="token function">clearSpykBean</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">clearMocks</span><span class="token punctuation">(</span>AopTestUtils<span class="token punctuation">.</span>getTargetObject<span class="token operator">&lt;</span>TestClass<span class="token operator">></span><span class="token punctuation">(</span>testClass<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre></div>
<p>이 이슈는 <a href="https://github.com/Ninja-Squad/springmockk/issues/85">@SpykBean not reset / cleared between tests #85</a>에서 확인할 수 있다.
현재 SpringMockk 프로젝트는 2023년 3월 4.0.2 버전을 마지막으로 업데이트 되고 있지 않다.
하지만 <a href="https://github.com/Ninja-Squad/springmockk/pull/112">Migration to mock organization #112</a> PR 에서 관리팀이 Ninja-Squad 에서 Mockk 로 이관 중임을 확인할 있다.</p></content:encoded></item><item><title><![CDATA[[Node.js] Express.js 서비스에 TSOA 적용하기.]]></title><description><![CDATA[새롭게 참여한 팀에서 API 스펙을 공유해달라는 요청을 받았다.
해당 팀은 API 스펙을 Postman 의 Request 로 공유하고 있었다. 내가 볼땐 매번 변경사항이 있을때마다 Request 를 갱신하는 것도 어렵지만,
Request…]]></description><link>https://seokchan-H.github.io/documentation/express/TSOA/</link><guid isPermaLink="false">https://seokchan-H.github.io/documentation/express/TSOA/</guid><pubDate>Sun, 01 Dec 2024 00:00:00 GMT</pubDate><content:encoded><p>새롭게 참여한 팀에서 API 스펙을 공유해달라는 요청을 받았다.
해당 팀은 API 스펙을 Postman 의 Request 로 공유하고 있었다.</p>
<p>내가 볼땐 매번 변경사항이 있을때마다 Request 를 갱신하는 것도 어렵지만,
Request 는 사용예시이기에 정확한 스펙을 공유하기엔 맞지 않다고 생각했다.
실제로 시간이 지남에 따라 스펙이 맞지 않는 문제를 겪고 있었다.</p>
<p>나는 이 문제를 해결하기 위해 OpenAPI Specification (OAS) 를 사용하기로 했다.
OAS 는 Postman 의 APIs 서비스나 Swagger 라이브러리로 간단하게 UI 를 제공할 수 있다.
전자는 버전에 따라 API 스펙을 공유할 수 있고, 후자는 API 스펙을 코드로 생성할 수 있다.
나는 후자를 선택했다.</p>
<h2>Node.js 의 Decorator</h2>
<p>코드로 API 스펙을 생성하는 방법은 메타데이터를 관리하는 것으로 시작된다.
메타데이터는 코드의 구조정보인데, Node.js 진영에서는 이를 TypeScript 의 Decorator 로 관리할 수 있다.</p>
<p>다음은 Decorator 를 사용하기 위한 tsconfig.json 의 설정이다.</p>
<div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span>
<span class="token property">"compilerOptions"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"experimentalDecorators"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token property">"emitDecoratorMetadata"</span><span class="token operator">:</span> <span class="token boolean">true</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div>
<ul>
<li>experimentalDecorators 는 데코레이터 문법 (e.g. Class, Method) 을 사용할 수 있도록 한다.</li>
<li>emitDecoratorMetadata 는 런타임에 메타데이터를 메모리에 적재하여 사용할 수 있도록 한다.</li>
</ul>
<blockquote>
<p>eperimental 접두사가 붙인 것은 아직 ECMAScript 표준이 아니기 때문이다.</p>
</blockquote>
<p>ECMAScript 표준화는 총 4단계를 거치며 3단계 이상이 되어야 엔진에 포함될 있다.
현재 Decorator 는 3단계에 있으며, TypeScript 는 이를 사용할 수 있도록 experimentalDecorators 옵션을 제공한다.</p>
<h2>Nest.js vs TSOA</h2>
<p>이제 메타데이터를 사용해서 OAS 를 작성해주는 라이브러리를 도입하면 된다.
Node.js 진영에서는 대표적으로 두가지 선택지가 있다.</p>
<ol>
<li>Nest.js 프레임워크를 사용한다.</li>
<li>TypeScript OpenAPI (TSOA) 라이브러리를 사용한다.</li>
</ol>
<p>Nest 프레임워크를 사용하면 해당 진영에서 제공해주는 파워풀한 모듈들이 Decorator 패턴을 기반으로 쉽게 설정된다.
만약 내가 Node.js 기반의 프로젝트를 신규로 시작한다면 전자를 선택했을 것이다.
하지만 나는 Express.js 기반의 레거시 프로젝트에서 스펙문서를 제공해야하는 상황이기에
비교적 수정범위가 적은 TSOA 를 선택했다. TSOA 는 Interface 계층만 수정해주면 된다.</p>
<h2>TSOA 설치하기</h2>
<p>우선 TSOA 를 설치한다.</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">npm</span> <span class="token function">install</span> tsoa</code></pre></div>
<p>다음은 TSOA 를 사용하기 위한 tsconfig.json 의 설정이다.</p>
<div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span>
<span class="token property">"compilerOptions"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"target"</span><span class="token operator">:</span> <span class="token string">"es2021"</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div>
<blockquote>
<p>TSOA 에서 Promise.any 사용한다.
Promise.any 는 es2021 에서부터 제공된다.
es2021 는 Node.js 버전 16부터 지원된다.</p>
</blockquote>
<p>이제 TSOA 를 설정할 차례이다.
프로젝트 루트에 tsoa.json 파일을 생성하고 다음과 같이 작성한다.</p>
<div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span>
<span class="token property">"entryFile"</span><span class="token operator">:</span> <span class="token string">"app.js"</span><span class="token punctuation">,</span> <span class="token comment">// Express.js 의 entry file 을 지정한다.</span>
<span class="token property">"noImplicitAdditionalProperties"</span><span class="token operator">:</span> <span class="token string">"throw-on-extras"</span><span class="token punctuation">,</span> <span class="token comment">// 요청 객체에 정의되지 않은 프로퍼티가 있을때 에러를 발생시킨다.</span>
<span class="token property">"controllerPathGlobs"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
<span class="token string">"controllers/**/*Controller.ts"</span> <span class="token comment">// 컨트롤러 파일의 위치를 지정한다.</span>
<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token property">"spec"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token comment">// OAS 파일을 생성할 때 필요한 설정을 지정한다.</span>
<span class="token property">"outputDirectory"</span><span class="token operator">:</span> <span class="token string">"./auto_generated/tsoa/swagger"</span><span class="token punctuation">,</span> <span class="token comment">// OAS 파일을 생성할 위치를 지정한다.</span>
<span class="token property">"specVersion"</span><span class="token operator">:</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token comment">// OAS 버전을 지정한다.</span>
<span class="token property">"securityDefinitions"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"sessionAuth"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token comment">// 내가 사용할 보안방식의 이름을 지정하고 OAS 에 나타낼 보안방식을 정의한다. (실제 보안처리는 직접 작성한 모듈에서 진행한다.)</span>
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"apiKey"</span><span class="token punctuation">,</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Cookie"</span><span class="token punctuation">,</span>
<span class="token property">"in"</span><span class="token operator">:</span> <span class="token string">"header"</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token property">"routes"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token comment">// 라우터 파일을 생성할 때 필요한 설정을 지정한다.</span>
<span class="token property">"routesDir"</span><span class="token operator">:</span> <span class="token string">"./auto_generated/tsoa/routes"</span><span class="token punctuation">,</span> <span class="token comment">// 라우터 파일을 생성할 위치를 지정한다.</span>
<span class="token property">"authenticationModule"</span><span class="token operator">:</span> <span class="token string">"./middleware/tsoa/expressAuthentication.ts"</span> <span class="token comment">// 인증 모듈을 지정한다.</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token property">"compilerOptions"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"baseUrl"</span><span class="token operator">:</span> <span class="token string">"../"</span><span class="token punctuation">,</span>
<span class="token property">"paths"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"@*"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"./*"</span><span class="token punctuation">]</span> <span class="token comment">// tsconfig.json 에서 지정한 symbolic link 를 tsoa 에도 지정해준다.</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div>
<p>TSOA 설정은 크게 인터페이스를 담당하는 Route 코드와 이를 문서화한 OAS 파일을 생성하는 설정으로 나뉜다.
파일경로는 tsoa.json 기준으로 지정하면 된다.</p>
<h2>TSOA 코드 작성하기</h2>
<h3>Controller 작성하기</h3>
<div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Controller<span class="token punctuation">,</span> Route<span class="token punctuation">,</span> Get<span class="token punctuation">,</span> Post<span class="token punctuation">,</span> Body<span class="token punctuation">,</span> SuccessResponse <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'tsoa'</span><span class="token punctuation">;</span>
<span class="token decorator"><span class="token at operator">@</span><span class="token function">Route</span></span><span class="token punctuation">(</span><span class="token string">'/tsoa-samle'</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">TsoaSampleController</span> <span class="token keyword">extends</span> <span class="token class-name">Controller</span> <span class="token punctuation">{</span>
<span class="token decorator"><span class="token at operator">@</span><span class="token function">Get</span></span><span class="token punctuation">(</span><span class="token string">'/data/{id}'</span><span class="token punctuation">)</span>
<span class="token decorator"><span class="token at operator">@</span><span class="token function">Security</span></span><span class="token punctuation">(</span><span class="token string">'sessionAuth'</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token function">getData</span><span class="token punctuation">(</span>
<span class="token decorator"><span class="token at operator">@</span><span class="token function">Request</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> req<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> <span class="token comment">// Request 객체 : 모듈에 의해 인증 &amp; 저장된 사용자 정보를 가져올 수 있다. (i.e. req.user.id)</span>
<span class="token decorator"><span class="token at operator">@</span><span class="token function">Path</span></span><span class="token punctuation">(</span><span class="token string">'dataId'</span><span class="token punctuation">)</span> dataId<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">,</span> <span class="token comment">// Path Parameter</span>
<span class="token decorator"><span class="token at operator">@</span><span class="token function">Query</span></span><span class="token punctuation">(</span><span class="token string">'optionId'</span><span class="token punctuation">)</span> optionId<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">,</span> <span class="token comment">// Query Parameter</span>
<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>Data<span class="token operator">></span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token punctuation">{</span> id<span class="token operator">:</span> dataId<span class="token punctuation">,</span> name<span class="token operator">:</span> <span class="token string">'data'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div>
<h3>인증 모듈 작성하기</h3>
<div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Request <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'express'</span><span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> expressAuthentication <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>
request<span class="token operator">:</span> Request<span class="token punctuation">,</span>
securityName<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span>
scopes<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>securityName <span class="token operator">===</span> <span class="token string">'sessionAuth'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 지정한 이름으로 인증처리를 분류한다.</span>
<span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token builtin">Promise</span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> reject<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>req<span class="token punctuation">.</span><span class="token function">isAuthenticated</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 인증 여부를 확인한다.</span>
<span class="token function">reject</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Unauthorized or limit exceeded'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token function">resolve</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Unsupported security method: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>securityName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div>
<h3>에러 핸들러 작성하기</h3>
<p>TSOA 의 에러를 어플리케이션에서 사용중이던 에러포맷으로 치환한다.</p>
<div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> ValidateError <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'tsoa'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token class-name">express</span> <span class="token keyword">from</span> <span class="token string">'express'</span><span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">TSOAErrorHandler</span> <span class="token punctuation">{</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token function">isValidateError</span><span class="token punctuation">(</span>err<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span><span class="token operator">:</span> err <span class="token keyword">is</span> ValidateError <span class="token punctuation">{</span> <span class="token comment">// 타입가드 함수로 에러를 캐스팅합니다.</span>
<span class="token keyword">return</span> <span class="token punctuation">(</span>
<span class="token keyword">typeof</span> err <span class="token operator">===</span> <span class="token string">'object'</span> <span class="token operator">&amp;&amp;</span> err <span class="token operator">!==</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> <span class="token string">'fields'</span> <span class="token keyword">in</span> err <span class="token operator">&amp;&amp;</span> <span class="token keyword">typeof</span> <span class="token punctuation">(</span>err <span class="token keyword">as</span> ValidateError<span class="token punctuation">)</span><span class="token punctuation">.</span>fields <span class="token operator">===</span> <span class="token string">'object'</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token function">handleValidateError</span><span class="token punctuation">(</span> <span class="token comment">// TSOA 에러 발생시키는 에러를 어플리케이션 에러로 치환합니다.</span>
err<span class="token operator">:</span> Error<span class="token punctuation">,</span>
req<span class="token operator">:</span> express<span class="token punctuation">.</span>Request<span class="token punctuation">,</span>
res<span class="token operator">:</span> express<span class="token punctuation">.</span>Response<span class="token punctuation">,</span>
next<span class="token operator">:</span> express<span class="token punctuation">.</span>NextFunction<span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">isValidateError</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">next</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> newError <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CustomException</span><span class="token punctuation">(</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>err<span class="token punctuation">.</span>fields<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">400</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
newError<span class="token punctuation">.</span>stack <span class="token operator">=</span> err<span class="token punctuation">.</span>stack<span class="token punctuation">;</span> <span class="token comment">// StackTrace 이력을 유지합니다.</span>
<span class="token function">next</span><span class="token punctuation">(</span>newError<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div>
<p>에러 모듈을 기존 에러핸들러보다 먼저 등록한다. (next 로 처리되기에 순서가 중요하다.)</p>
<div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token comment">// TSOA 에러 핸들러 등록 : 기존 에러형태로 파싱하여 체이닝</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token punctuation">(</span>err<span class="token punctuation">,</span> req<span class="token punctuation">,</span> res<span class="token punctuation">,</span> next<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
TSOAErrorHandler<span class="token punctuation">.</span><span class="token function">handleValidateError</span><span class="token punctuation">(</span>err<span class="token punctuation">,</span> req<span class="token punctuation">,</span> res<span class="token punctuation">,</span> next<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<h3>라우터 생성하기</h3>
<p>TSOA 는 작성된 Controller 를 기반으로 라우터 JS 파일을 생성한다.</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">npx tsoa routes </code></pre></div>
<p>생성된 파일에서 export 된 RegisterRoutes 함수로 앱에 라우터를 등록할 수 있다.</p>
<div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token comment">// TSOA 라우트 등록</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> RegisterRoutes <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">require</span><span class="token punctuation">(</span><span class="token string">'./auto_generated/tsoa/routes/routes'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// tsoa.json 에 설정한 위치</span>
<span class="token function">RegisterRoutes</span><span class="token punctuation">(</span><span class="token function">express</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<h3>Swagger UI 생성하기</h3>
<p>TSOA 는 작성된 Controller 를 기반으로 OAS 를 생성한다.</p>
<div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">npx tsoa spec </code></pre></div>
<p>생성된 파일을 Swagger UI 에서 사용할 수 있도록 설정한다.</p>
<div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token comment">// Swagger UI 설정</span>
<span class="token keyword">const</span> swaggerUi <span class="token operator">=</span> <span class="token keyword">require</span><span class="token punctuation">(</span><span class="token string">'swagger-ui-express'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> swaggerDocument <span class="token operator">=</span> <span class="token keyword">require</span><span class="token punctuation">(</span><span class="token string">'./auto_generated/tsoa/swagger/swagger'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// tsoa.json 에 설정한 위치</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token string">'/docs'</span><span class="token punctuation">,</span> swaggerUi<span class="token punctuation">.</span>serve<span class="token punctuation">,</span> swaggerUi<span class="token punctuation">.</span><span class="token function">setup</span><span class="token punctuation">(</span>swaggerDocument<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>
<p>지정한 <code class="language-text">/docs</code> 로 접근하면 Swagger UI 문서를 확인할 수 있다.</p>
<p><span
class="gatsby-resp-image-wrapper"
style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 630px; "
>
<a
class="gatsby-resp-image-link"
href="/static/1186e1fffa3d2cc23d4b463da6886c84/26a94/swagger-ui-express.png"
style="display: block"
target="_blank"
rel="noopener"
>
<span
class="gatsby-resp-image-background-image"
style="padding-bottom: 46.202531645569614%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABRklEQVR42pWRvUoDQRSF9w1ENr2l1dpZaLIYiTZiJNoJBlL6LL6BvQ8gWFmIjZYaNUZQFCxM9i8bk7ibzO8eZ0cFlRUTmI9779w7h3MZY3rKRHlvBku1WczPLaC0WoJlWcjlcjBNc2KMQt7G1m4R5e0VrK9toFrdQaWyieJyEfnCImy7oLDHxuCcg8YSgkpwzkApgUwkEpkA6kiR3vOxMRhjCF+78IIA/cEAjuuhcfuEZ7eOhnMAt9+E4AkYo0hn/8NIlJvm3T0Oj45xcnqGi6sGrm8e8fByjnprH+3epXIJ7f63m0xBSilGI4IoitHr9UEIAUtXH6lHBCBDpntRFGniOMabjsO/BR3H1au22i6CTqiio2vPd+F5PvwgVLWvCYKOjmHYzXSpBX01RAiFlB8fIIT45HsuVF/86GU6/EpS4UnJEnwHEYVarcuqYFMAAAAASUVORK5CYII='); background-size: cover; display: block;"
></span>
<img
class="gatsby-resp-image-image"
alt="img.png"
title=""
src="/static/1186e1fffa3d2cc23d4b463da6886c84/f058b/swagger-ui-express.png"
srcset="/static/1186e1fffa3d2cc23d4b463da6886c84/c26ae/swagger-ui-express.png 158w,
/static/1186e1fffa3d2cc23d4b463da6886c84/6bdcf/swagger-ui-express.png 315w,
/static/1186e1fffa3d2cc23d4b463da6886c84/f058b/swagger-ui-express.png 630w,
/static/1186e1fffa3d2cc23d4b463da6886c84/40601/swagger-ui-express.png 945w,
/static/1186e1fffa3d2cc23d4b463da6886c84/26a94/swagger-ui-express.png 1102w"
sizes="(max-width: 630px) 100vw, 630px"
style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
loading="lazy"
decoding="async"
/>
</a>
</span></p>
<p>이제 해당 URL 을 팀에 공유하면 된다.</p></content:encoded></item></channel></rss>