Skip to content

Commit 7a44a3f

Browse files
perf($ClassValidator): integrate Class Validator
1 parent 1473f79 commit 7a44a3f

File tree

8 files changed

+380
-2
lines changed

8 files changed

+380
-2
lines changed

src/App.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
<script lang="ts">
3232
import Vue from 'vue'
33-
import { AppUtil } from '@/utils/app-util.ts'
33+
import { AppUtil } from '@/utils/app-util'
3434
3535
export default Vue.extend({
3636
name: 'App',

src/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import '@mdi/font/css/materialdesignicons.css'
88
import '@/directives/throttled-click'
99
import '@/directives/debounced-click'
1010
import { listenToColorScheme } from '@/plugins/adaptive-color-scheme'
11+
import '@/plugins/mock'
1112

1213
Vue.config.productionTip = false
1314

src/plugins/axios/index.ts

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/**
2+
* @author Johnny Miller (鍾俊), e-mail: [email protected]
3+
* @date 1/2/20 9:15 AM
4+
*/
5+
// eslint-disable-next-line no-unused-vars
6+
import Axios, { ResponseType } from 'axios'
7+
import { validate } from 'class-validator'
8+
import { ClassValidationUtil } from '@/utils/class-validation-util'
9+
10+
// 1. Create an axios instance.
11+
export const service = Axios.create({
12+
// Base URL of API
13+
baseURL: process.env.VUE_APP_BASE_URL,
14+
// Request timeout: 30s
15+
timeout: 30000,
16+
/**
17+
* `validateStatus` defines whether to resolve or reject the promise for a given HTTP response status code.
18+
* The value of status must be less than 999.
19+
* @param {number} status HTTP's status code
20+
* @return {boolean} If `validateStatus` returns `true` (or is set to `null` or `undefined`),
21+
* the promise will be resolved; otherwise, the promise will be rejected.
22+
*/
23+
validateStatus: status => {
24+
// Only the HTTP status code is equal to 200, axios would resolve the promise
25+
return status === 200
26+
}
27+
})
28+
29+
// 2. Request interceptor's configuration.
30+
service.interceptors.request.use(
31+
async axiosRequestConfig => {
32+
if (axiosRequestConfig?.params) {
33+
const validation = await validate(axiosRequestConfig.params)
34+
if (validation.length > 0) {
35+
console.error('Validation failed! Validation:', validation)
36+
console.error('Validation failed! Error message:', ClassValidationUtil.getAllValidationError(validation))
37+
throw new Error(`Validation failed! The 1st error: ${ClassValidationUtil.getFirstValidationError(validation)}`)
38+
}
39+
}
40+
return axiosRequestConfig
41+
},
42+
error => {
43+
// Do something with request error
44+
// for debug
45+
console.error('[Axios] Error occurred when sending request!', error)
46+
return Promise.reject(error)
47+
}
48+
)
49+
50+
// 3. Response interceptor's configuration.
51+
service.interceptors.response.use(
52+
response => {
53+
// Only the HTTP status code is equal to 200, axios would resolve the promise
54+
const resp = response.data
55+
return Promise.resolve(resp)
56+
},
57+
error => {
58+
console.error('[Axios] Error occurred when handling response!', error)
59+
console.error('[Axios] Error occurred when handling response! Response:', error.response)
60+
return Promise.reject(error)
61+
}
62+
)
63+
64+
// noinspection JSUnusedGlobalSymbols
65+
/**
66+
* Send a GET request.
67+
*
68+
* The GET method requests a representation of the specified resource. Requests using GET should only retrieve data.
69+
*
70+
* @param url URL
71+
* @param params Params
72+
* @param responseType Response type.
73+
* @return {Promise<any>} Response data.
74+
* @see <a href='https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET'>HTTP request methods — GET | MDN</a>
75+
*/
76+
export function get (url: string, params?: any, responseType: ResponseType = 'json'): Promise<any> {
77+
return new Promise<any>((resolve, reject) => {
78+
service.get(url, {
79+
params: params,
80+
responseType: responseType
81+
}).then(resp => {
82+
resolve(resp)
83+
}).catch(rejectedReason => {
84+
reject(rejectedReason)
85+
})
86+
})
87+
}
88+
89+
// noinspection JSUnusedGlobalSymbols
90+
/**
91+
* The DELETE method deletes the specified resource.
92+
*
93+
* @param url URL.
94+
* @param params Params.
95+
* @return {Promise<any>} Response data.
96+
* @see <a href='https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE'>HTTP request methods — DELETE | MDN</a>
97+
*/
98+
export function deleteRequest (url: string, params: any): Promise<any> {
99+
return new Promise<any>((resolve, reject) => {
100+
service.delete(url, {
101+
params: params
102+
}).then(resp => {
103+
resolve(resp)
104+
}).catch(rejectedReason => {
105+
reject(rejectedReason)
106+
})
107+
})
108+
}
109+
110+
// noinspection JSUnusedGlobalSymbols
111+
/**
112+
* The HEAD method asks for a response identical to that of a GET request, but without the response body.
113+
*
114+
* The HTTP HEAD method requests the headers that are returned if the specified resource would be requested with an HTTP GET method.
115+
* Such a request can be done before deciding to download a large resource to save bandwidth, for example.
116+
*
117+
* @param url URL.
118+
* @param params Params.
119+
* @return {Promise<any>} Response data.
120+
* @see <a href='https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD'>HTTP request methods — HEAD | MDN</a>
121+
*/
122+
export function head (url: string, params: any): Promise<any> {
123+
return new Promise<any>((resolve, reject) => {
124+
service.head(url, {
125+
params: params
126+
}).then(resp => {
127+
resolve(resp)
128+
}).catch(rejectedReason => {
129+
reject(rejectedReason)
130+
})
131+
})
132+
}
133+
134+
// noinspection JSUnusedGlobalSymbols
135+
/**
136+
* Send a POST request with payload.
137+
*
138+
* The HTTP POST method sends data to the server. The type of the body of the request is indicated by the Content-Type header.
139+
*
140+
* @param url URL
141+
* @param params Payload.
142+
* @return {Promise<any>} Response data.
143+
* @see <a href='https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST'>HTTP request methods — POST | MDN</a>
144+
*/
145+
export function post (url: string, params: any): Promise<any> {
146+
return new Promise<any>((resolve, reject) => {
147+
service.post(url, params)
148+
.then(resp => {
149+
resolve(resp)
150+
})
151+
.catch(rejectedReason => {
152+
reject(rejectedReason)
153+
})
154+
})
155+
}
156+
157+
// noinspection JSUnusedGlobalSymbols
158+
/**
159+
* Send a legacy POST request with URL search params.
160+
*
161+
* The HTTP POST method sends data to the server. The type of the body of the request is indicated by the Content-Type header.
162+
*
163+
* @see <a href='https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST'>HTTP request methods — POST | MDN</a>
164+
* @param url URL.
165+
* @param params URL search params.
166+
* @return {Promise<any>} Response data.
167+
*/
168+
export function legacyPost (url: string, params: any): Promise<any> {
169+
const urlSearchParams = new URLSearchParams()
170+
Object.keys(params).forEach(key => {
171+
urlSearchParams.append(key, params[key])
172+
})
173+
return new Promise<any>((resolve, reject) => {
174+
service.post(url, urlSearchParams)
175+
.then(resp => {
176+
resolve(resp)
177+
})
178+
.catch(rejectedReason => {
179+
reject(rejectedReason)
180+
})
181+
})
182+
}
183+
184+
// noinspection JSUnusedGlobalSymbols
185+
/**
186+
* Send a PUT request.
187+
*
188+
* The HTTP PUT request method creates a new resource or replaces a representation of the target resource with the request payload.
189+
* The difference between PUT and POST is that PUT is idempotent:
190+
* calling it once or several times successively has the same effect (that is no side effect), where successive identical POST may have additional effects, like passing an order several times.
191+
*
192+
* @param url URL.
193+
* @param params Params.
194+
* @returns {Promise<any>} Response data.
195+
* @see <a href='https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT'>HTTP request methods — PUT | MDN</a>
196+
*/
197+
export function put (url: string, params: any): Promise<any> {
198+
return new Promise<any>((resolve, reject) => {
199+
service.put(url, {
200+
params: params
201+
}).then(resp => {
202+
resolve(resp)
203+
}).catch(rejectedReason => {
204+
reject(rejectedReason)
205+
})
206+
})
207+
}
208+
209+
// noinspection JSUnusedGlobalSymbols
210+
/**
211+
* The PATCH method is used to apply partial modifications to a resource.
212+
*
213+
* The HTTP PUT method only allows complete replacement of a document.
214+
* Unlike PUT, PATCH is not idempotent, meaning successive identical patch requests may have different effects.
215+
* However, it is possible to issue PATCH requests in such a way as to be idempotent.
216+
*
217+
* @param url URL.
218+
* @param params Params.
219+
* @return {Promise<any>} Response data.
220+
* @see <a href='https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH'>HTTP request methods — PATCH | MDN</a>
221+
*/
222+
export function patch (url: string, params: any): Promise<any> {
223+
return new Promise((resolve, reject) => {
224+
service.patch<any>(url, params)
225+
.then(resp => {
226+
resolve(resp)
227+
})
228+
.catch(rejectedReason => {
229+
reject(rejectedReason)
230+
})
231+
})
232+
}

src/plugins/mock/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// @ts-ignore
2+
import Mock from 'mockjs'
3+
4+
export const mock = Mock
5+
mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
6+
mock.XHR.prototype.send = function () {
7+
if (this.custom.xhr) {
8+
this.custom.xhr.withCredentials = this.withCredentials || false
9+
}
10+
this.proxy_send(...arguments)
11+
}
12+
13+
mock.mock(/\/api\/get/, 'get', login)
14+
15+
function login (res: any): any {
16+
return {
17+
code: 200,
18+
message: 'Response done.'
19+
}
20+
}

src/requests/vuetify-demo/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as Axios from '@/plugins/axios'
2+
// eslint-disable-next-line no-unused-vars
3+
import { GetDemoPayload } from '@/requests/vuetify-demo/payload/get-demo-payload'
4+
5+
export const vuetifyDemoApi = {
6+
getDemo: (getDemoPayload: GetDemoPayload) => Axios.get('/api/get', getDemoPayload)
7+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import {
2+
Contains,
3+
IsInt,
4+
IsNotEmpty,
5+
Length,
6+
IsEmail,
7+
IsFQDN,
8+
IsDate,
9+
Min,
10+
Max, IsOptional
11+
} from 'class-validator'
12+
13+
/**
14+
* Get demo payload
15+
*/
16+
export class GetDemoPayload {
17+
/**
18+
* Title.
19+
*/
20+
@Length(10, 20)
21+
@IsNotEmpty({ message: 'Title is required!' })
22+
title!: string
23+
24+
/**
25+
* Text.
26+
*/
27+
@Contains('hello')
28+
text!: string
29+
30+
/**
31+
* Rating.
32+
*/
33+
@IsInt()
34+
@Min(0)
35+
@Max(10)
36+
rating!: number
37+
38+
/**
39+
* Email.
40+
*/
41+
@IsEmail()
42+
email!: string
43+
44+
/**
45+
* site.
46+
*/
47+
@IsFQDN()
48+
@IsOptional()
49+
site?: string
50+
51+
/**
52+
* Create date.
53+
*/
54+
@IsDate()
55+
@IsOptional()
56+
createDate?: Date
57+
}

src/utils/class-validation-util.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// eslint-disable-next-line no-unused-vars
2+
import { ValidationError } from 'class-validator/validation/ValidationError'
3+
4+
export class ClassValidationUtil {
5+
/**
6+
* Get first validation error
7+
* @param {ValidationError[]} validationErrors validation error array
8+
* @return {string} error message
9+
* @author Johnny Miller (鍾俊), e-mail: [email protected]
10+
* @date 1/2/20 11:39 AM
11+
*/
12+
static getFirstValidationError = (validationErrors: ValidationError[]): string => {
13+
let firstErrorMessage = ''
14+
for (const property in validationErrors[0].constraints) {
15+
firstErrorMessage += `Field: ${validationErrors[0].property}, type: ${property}, error message: ${validationErrors[0].constraints[property]}`
16+
}
17+
return firstErrorMessage
18+
}
19+
20+
/**
21+
* Get all validation error
22+
* @param {ValidationError[]} validationErrors validation error array
23+
* @return {string} error message
24+
* @author Johnny Miller (鍾俊), e-mail: [email protected]
25+
* @date 1/2/20 12:58 PM
26+
*/
27+
static getAllValidationError = (validationErrors: ValidationError[]): string => {
28+
let errorMessage = ''
29+
validationErrors.forEach(validationError => {
30+
for (const property in validationError.constraints) {
31+
errorMessage += `Field: ${validationError.property}, type: ${property}, error message: ${validationError.constraints[property]}; `
32+
}
33+
})
34+
return errorMessage.substr(0, errorMessage.length - 2)
35+
}
36+
}

0 commit comments

Comments
 (0)