Skip to content

Commit 2b8eff4

Browse files
authored
Merge pull request #1061 from chantouchsek/feat/1060-support-whildcard-fields
Feat/1060 support wildcard fields
2 parents e9ccb5b + 52e288a commit 2b8eff4

File tree

5 files changed

+85
-99
lines changed

5 files changed

+85
-99
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
"nuxt"
8484
],
8585
"dependencies": {
86-
"axios": "^0.27.2",
86+
"axios": "^0.28.1",
8787
"lodash": "^4.17.21",
8888
"qs": "^6.12.1"
8989
},

src/__tests__/validator.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ describe('Validator', () => {
1313
})
1414
test('Add an error', () => {
1515
validator.add('name', 'The name field is required.')
16+
validator.add('name', 'The name field min length is 3.')
1617
expect(validator.any()).toBeTruthy()
1718
})
1819
test('Add an error with forceUpdate', () => {
@@ -240,4 +241,18 @@ describe('Validator', () => {
240241

241242
expect(validator.has(['firstName'])).toBeFalsy()
242243
})
244+
245+
it('should work with wildcard fields', () => {
246+
const errors = {
247+
'items.0.name': ['This fist name field is required'],
248+
'items.1.name': ['This fist name field is required'],
249+
'items.2.name': ['This fist name field is required'],
250+
}
251+
validator.fill(errors)
252+
253+
expect(validator.has(['items.*'])).toBeTruthy()
254+
expect(validator.has(['items.*.name'])).toBeTruthy()
255+
expect(validator.first(['items.*.name'])).toEqual('This fist name field is required')
256+
expect(validator.first(['items.0.name'])).toEqual('This fist name field is required')
257+
})
243258
})

src/core/BaseService.ts

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -86,39 +86,34 @@ export default class BaseService {
8686

8787
$submit<T = any, F = any>(method: Method, param?: string | number, form?: F, config?: AxiosRequestConfig) {
8888
this.beforeSubmit()
89-
return new Promise<AxiosResponse<T>>((resolve, reject) => {
90-
const formData = hasFiles(form) ? objectToFormData(form) : form
91-
const endpointPath = param ? `/${this.endpoint}/${param}` : `/${this.endpoint}`
92-
const endpoint = endpointPath.replace(/\/\//g, '/')
93-
const url = this.__getParameterString(endpoint)
94-
const axiosConfig = { url, data: formData, method, ...config }
95-
this.$http(axiosConfig)
89+
const formData = hasFiles(form) ? objectToFormData(form) : form
90+
const endpointPath = param ? `/${this.endpoint}/${param}` : `/${this.endpoint}`
91+
const endpoint = endpointPath.replace(/\/\//g, '/')
92+
const url = this.__getParameterString(endpoint)
93+
const axiosConfig = { url, data: formData, method, ...config }
94+
return new Promise<AxiosResponse<AxiosResponse<T>>>((resolve, reject) => {
95+
this.$http<T>(axiosConfig)
9696
.then((response) => {
9797
this.onSuccess()
98+
if (this.$resetParameter) this.removeParameters()
9899
resolve(response)
99100
})
100101
.catch((error: AxiosError<AxiosResponseData>) => {
101102
this.errors.processing = false
102103
validator.processing = false
103-
const { response } = error
104-
if (response && response.status === UNPROCESSABLE_ENTITY) {
105-
const { data } = response
104+
if (error.response && error.response.status === UNPROCESSABLE_ENTITY) {
106105
const validationErrors: SimpleObject<any> = {}
107-
Object.assign(validationErrors, data[this.$errorProperty])
106+
Object.assign(validationErrors, error.response.data[this.$errorProperty])
108107
this.onFail(validationErrors)
109108
}
110109
reject(error)
111110
})
112-
if (this.$resetParameter) this.removeParameters()
113111
})
114112
}
115113

116-
submit<T = any, F = any>(method: Method, url?: string | number, form?: F, config?: AxiosRequestConfig) {
117-
return new Promise<T>((resolve, reject) => {
118-
this.$submit<T>(method, url, form, config)
119-
.then(({ data }) => resolve(data))
120-
.catch((err) => reject(err))
121-
})
114+
async submit<T = any, F = any>(method: Method, url?: string | number, form?: F, config?: AxiosRequestConfig) {
115+
const { data } = await this.$submit<T>(method, url, form, config)
116+
return data
122117
}
123118

124119
private __getParameterString(url: string) {
@@ -133,32 +128,27 @@ export default class BaseService {
133128
}
134129

135130
setParameters(parameters: SimpleObject<any>) {
136-
Object.keys(parameters).forEach((key) => {
137-
this.parameters[key] = parameters[key]
138-
})
131+
this.parameters = { ...this.parameters, ...parameters }
139132
return this
140133
}
141134

142135
setParameter(parameter: string, value?: any) {
143136
if (!value) {
144-
const options: IParseOptions = Object.assign({}, this.$parsedQs, {
145-
comma: true,
146-
allowDots: true,
147-
ignoreQueryPrefix: true,
148-
})
149-
const params = parse(parameter, options)
150-
return this.setParameters(params)
137+
return this.setParameters(
138+
parse(parameter, {
139+
...this.$parsedQs,
140+
comma: true,
141+
allowDots: true,
142+
ignoreQueryPrefix: true,
143+
}),
144+
)
151145
}
152146
this.parameters[parameter] = value
153147
return this
154148
}
155149

156150
removeParameters(parameters: string[] = []) {
157-
if (!parameters || !parameters.length) {
158-
this.parameters = {}
159-
} else if (Array.isArray(parameters)) {
160-
for (const parameter of parameters) delete this.parameters[parameter]
161-
}
151+
parameters.length ? parameters.forEach((param) => delete this.parameters[param]) : (this.parameters = {})
162152
return this
163153
}
164154

src/core/Validator.ts

Lines changed: 34 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
11
import type { SimpleObject } from '../types'
2-
import { cloneDeep, get, has, omit } from 'lodash'
2+
import { castArray, cloneDeep, get, has, omit, replace } from 'lodash'
33
import { is, toCamelCase, toSnakeCase } from '../util'
44

55
class Validator {
6-
public successful: boolean
7-
public processing: boolean
8-
9-
constructor(public errors: SimpleObject<any> = {}) {
10-
this.processing = false
11-
this.successful = false
12-
}
6+
constructor(public errors: SimpleObject<any> = {}, public processing = false, public successful = false) {}
137

148
add(field: string, message: string, forceUpdate?: boolean) {
15-
if (this.missed(field)) this.errors[field] = []
16-
if (!this.errors[field].includes(message)) this.errors[field].unshift(message)
17-
if (forceUpdate) {
18-
this.errors[field] = []
19-
this.errors[field].push(message)
9+
if (forceUpdate || this.missed(field)) {
10+
this.errors[field] = [message]
11+
} else if (!this.errors[field].includes(message)) {
12+
this.errors[field].unshift(message)
2013
}
2114
}
2215

@@ -25,33 +18,15 @@ class Validator {
2518
return is(Object.keys(this.errors), fields)
2619
}
2720

28-
first(field: string | string[]): string | undefined {
29-
if (Array.isArray(field)) {
30-
const fields = this.fields(field)
31-
let fd = ''
32-
for (const f of fields) {
33-
if (has(this.errors, f)) {
34-
fd = f
35-
break
36-
}
37-
}
38-
return this.first(fd)
39-
} else {
40-
const value = this.get(field)
41-
if (Array.isArray(value)) return value[0]
42-
return value
43-
}
21+
first(field: string | string[]) {
22+
const fields = this.fields(castArray(field))
23+
const foundField = fields.find((f) => has(this.errors, f)) ?? ''
24+
const value = this.get(foundField)
25+
return Array.isArray(value) ? value[0] : value
4426
}
4527

46-
firstBy(obj: SimpleObject<any>, field?: string) {
47-
let value: string
48-
if (!field) {
49-
value = obj[Object.keys(obj)[0]]
50-
} else {
51-
value = obj[field]
52-
}
53-
if (Array.isArray(value)) value = value[0]
54-
return value
28+
firstBy(obj: SimpleObject<any>, field: string = Object.keys(obj)[0]): string {
29+
return castArray(obj[field])[0]
5530
}
5631

5732
missed(field: string | string[]) {
@@ -64,20 +39,17 @@ class Validator {
6439

6540
any(field: string[] = [], returnObject?: boolean) {
6641
const fields = this.fields(field)
67-
if (returnObject) {
68-
const errors: SimpleObject<any> = {}
69-
if (!fields.length) return {}
70-
for (const f of fields) {
71-
const val = this.get(f)
72-
if (!val.length) continue
73-
errors[f] = val
74-
}
75-
return errors
76-
}
77-
if (!fields.length) return Object.keys(this.errors).length > 0
7842
const errors: SimpleObject<any> = {}
79-
fields.forEach((key: string) => (errors[key] = this.get(key)))
80-
return Object.keys(errors).length > 0
43+
44+
if (!fields.length) return returnObject ? {} : Object.keys(this.errors).length > 0
45+
46+
fields.forEach((f: string) => {
47+
const val = this.get(f)
48+
if (returnObject && val.length) errors[f] = val
49+
else if (!returnObject) errors[f] = val
50+
})
51+
52+
return returnObject ? errors : Object.keys(errors).length > 0
8153
}
8254

8355
get(field: string): string | string[] {
@@ -117,15 +89,18 @@ class Validator {
11789
this.clear(names)
11890
}
11991

120-
fields(field: string | string[]): string[] {
121-
const fields: string[] = []
122-
if (Array.isArray(field)) {
123-
for (const f of field) {
124-
fields.push(toCamelCase(f), toSnakeCase(f))
125-
}
126-
} else {
127-
fields.push(toCamelCase(field), toSnakeCase(field))
92+
fields(field: string | string[]) {
93+
const processField = (f: string) => {
94+
if (f.includes('*')) {
95+
const regex = new RegExp(`^${replace(f, '*', '.*')}$`, 'i')
96+
for (const key in this.errors) {
97+
if (regex.test(key)) fields.push(toCamelCase(key), toSnakeCase(key))
98+
}
99+
} else fields.push(toCamelCase(f), toSnakeCase(f))
128100
}
101+
102+
const fields: string[] = []
103+
Array.isArray(field) ? field.forEach(processField) : processField(field)
129104
return [...new Set(fields)].filter(Boolean)
130105
}
131106
}

yarn.lock

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3740,13 +3740,14 @@ axios-mock-adapter@^1.22.0:
37403740
fast-deep-equal "^3.1.3"
37413741
is-buffer "^2.0.5"
37423742

3743-
axios@^0.27.2:
3744-
version "0.27.2"
3745-
resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
3746-
integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
3743+
axios@^0.28.1:
3744+
version "0.28.1"
3745+
resolved "https://registry.yarnpkg.com/axios/-/axios-0.28.1.tgz#2a7bcd34a3837b71ee1a5ca3762214b86b703e70"
3746+
integrity sha512-iUcGA5a7p0mVb4Gm/sy+FSECNkPFT4y7wt6OM/CDpO/OnNCvSs3PoMG8ibrC9jRoGYU0gUK5pXVC4NPXq6lHRQ==
37473747
dependencies:
3748-
follow-redirects "^1.14.9"
3748+
follow-redirects "^1.15.0"
37493749
form-data "^4.0.0"
3750+
proxy-from-env "^1.1.0"
37503751

37513752
babel-loader@^8.3.0:
37523753
version "8.3.0"
@@ -6303,7 +6304,7 @@ flush-write-stream@^1.0.0:
63036304
inherits "^2.0.3"
63046305
readable-stream "^2.3.6"
63056306

6306-
follow-redirects@^1.14.9:
6307+
follow-redirects@^1.15.0:
63076308
version "1.15.6"
63086309
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
63096310
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
@@ -10161,6 +10162,11 @@ protocols@^2.0.0, protocols@^2.0.1:
1016110162
resolved "https://registry.yarnpkg.com/protocols/-/protocols-2.0.1.tgz#8f155da3fc0f32644e83c5782c8e8212ccf70a86"
1016210163
integrity sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==
1016310164

10165+
proxy-from-env@^1.1.0:
10166+
version "1.1.0"
10167+
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
10168+
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
10169+
1016410170
prr@~1.0.1:
1016510171
version "1.0.1"
1016610172
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"

0 commit comments

Comments
 (0)