fix: optional empty validation (#7003)

internal logic does send null to validation in case of empty parameter input.

* refactor: early return to clarify cases
This commit is contained in:
Mahtis Michel
2021-03-03 21:19:43 +01:00
committed by GitHub
parent c65126d7ff
commit d32bd1ab7c

View File

@@ -432,11 +432,11 @@ export const validatePattern = (val, rxPattern) => {
} }
} }
function validateValueBySchema(value, schema, isParamRequired, bypassRequiredCheck, parameterContentMediaType) { function validateValueBySchema(value, schema, requiredByParam, bypassRequiredCheck, parameterContentMediaType) {
if(!schema) return [] if(!schema) return []
let errors = [] let errors = []
let nullable = schema.get("nullable") let nullable = schema.get("nullable")
let required = schema.get("required") let requiredBySchema = schema.get("required")
let maximum = schema.get("maximum") let maximum = schema.get("maximum")
let minimum = schema.get("minimum") let minimum = schema.get("minimum")
let type = schema.get("type") let type = schema.get("type")
@@ -448,155 +448,165 @@ function validateValueBySchema(value, schema, isParamRequired, bypassRequiredChe
let minItems = schema.get("minItems") let minItems = schema.get("minItems")
let pattern = schema.get("pattern") let pattern = schema.get("pattern")
if(nullable && value === null) { const needsExplicitConstraintValidation = type === "array"
const schemaRequiresValue = requiredByParam || requiredBySchema
const hasValue = value !== undefined && value !== null
const isValidEmpty = !schemaRequiresValue && !hasValue
const requiresFurtherValidation =
schemaRequiresValue
|| needsExplicitConstraintValidation
|| !isValidEmpty
const isValidNullable = nullable && value === null
// will not be included in the request or [schema / value] does not [allow / require] further analysis.
const noFurtherValidationNeeded =
isValidNullable
|| !type
|| !requiresFurtherValidation
if(noFurtherValidationNeeded) {
return [] return []
} }
/* // Further this point the parameter is considered worth to validate
If the parameter is required OR the parameter has a value (meaning optional, but filled in) let stringCheck = type === "string" && value
then we should do our validation routine. let arrayCheck = type === "array" && Array.isArray(value) && value.length
Only bother validating the parameter if the type was specified. let arrayListCheck = type === "array" && Im.List.isList(value) && value.count()
in case of array an empty value needs validation too because constrains can be set to require minItems let arrayStringCheck = type === "array" && typeof value === "string" && value
*/ let fileCheck = type === "file" && value instanceof win.File
if (type && (isParamRequired || required || value !== undefined || type === "array")) { let booleanCheck = type === "boolean" && (value || value === false)
// These checks should evaluate to true if there is a parameter let numberCheck = type === "number" && (value || value === 0)
let stringCheck = type === "string" && value let integerCheck = type === "integer" && (value || value === 0)
let arrayCheck = type === "array" && Array.isArray(value) && value.length let objectCheck = type === "object" && typeof value === "object" && value !== null
let arrayListCheck = type === "array" && Im.List.isList(value) && value.count() let objectStringCheck = type === "object" && typeof value === "string" && value
let arrayStringCheck = type === "array" && typeof value === "string" && value
let fileCheck = type === "file" && value instanceof win.File
let booleanCheck = type === "boolean" && (value || value === false)
let numberCheck = type === "number" && (value || value === 0)
let integerCheck = type === "integer" && (value || value === 0)
let objectCheck = type === "object" && typeof value === "object" && value !== null
let objectStringCheck = type === "object" && typeof value === "string" && value
const allChecks = [ const allChecks = [
stringCheck, arrayCheck, arrayListCheck, arrayStringCheck, fileCheck, stringCheck, arrayCheck, arrayListCheck, arrayStringCheck, fileCheck,
booleanCheck, numberCheck, integerCheck, objectCheck, objectStringCheck, booleanCheck, numberCheck, integerCheck, objectCheck, objectStringCheck,
] ]
const passedAnyCheck = allChecks.some(v => !!v) const passedAnyCheck = allChecks.some(v => !!v)
if ((isParamRequired || required) && !passedAnyCheck && !bypassRequiredCheck) { if (schemaRequiresValue && !passedAnyCheck && !bypassRequiredCheck) {
errors.push("Required field is not provided") errors.push("Required field is not provided")
return errors return errors
} }
if ( if (
type === "object" && type === "object" &&
(parameterContentMediaType === null || (parameterContentMediaType === null ||
parameterContentMediaType === "application/json") parameterContentMediaType === "application/json")
) { ) {
let objectVal = value let objectVal = value
if(typeof value === "string") { if(typeof value === "string") {
try { try {
objectVal = JSON.parse(value) objectVal = JSON.parse(value)
} catch (e) { } catch (e) {
errors.push("Parameter string value must be valid JSON") errors.push("Parameter string value must be valid JSON")
return errors
}
}
if(schema && schema.has("required") && isFunc(required.isList) && required.isList()) {
required.forEach(key => {
if(objectVal[key] === undefined) {
errors.push({ propKey: key, error: "Required property not found" })
}
})
}
if(schema && schema.has("properties")) {
schema.get("properties").forEach((val, key) => {
const errs = validateValueBySchema(objectVal[key], val, false, bypassRequiredCheck, parameterContentMediaType)
errors.push(...errs
.map((error) => ({ propKey: key, error })))
})
}
}
if (pattern) {
let err = validatePattern(value, pattern)
if (err) errors.push(err)
}
if (minItems) {
if (type === "array") {
let err = validateMinItems(value, minItems)
if (err) errors.push(err)
}
}
if (maxItems) {
if (type === "array") {
let err = validateMaxItems(value, maxItems)
if (err) errors.push({ needRemove: true, error: err })
}
}
if (uniqueItems) {
if (type === "array") {
let errorPerItem = validateUniqueItems(value, uniqueItems)
if (errorPerItem) errors.push(...errorPerItem)
}
}
if (maxLength || maxLength === 0) {
let err = validateMaxLength(value, maxLength)
if (err) errors.push(err)
}
if (minLength) {
let err = validateMinLength(value, minLength)
if (err) errors.push(err)
}
if (maximum || maximum === 0) {
let err = validateMaximum(value, maximum)
if (err) errors.push(err)
}
if (minimum || minimum === 0) {
let err = validateMinimum(value, minimum)
if (err) errors.push(err)
}
if (type === "string") {
let err
if (format === "date-time") {
err = validateDateTime(value)
} else if (format === "uuid") {
err = validateGuid(value)
} else {
err = validateString(value)
}
if (!err) return errors
errors.push(err)
} else if (type === "boolean") {
let err = validateBoolean(value)
if (!err) return errors
errors.push(err)
} else if (type === "number") {
let err = validateNumber(value)
if (!err) return errors
errors.push(err)
} else if (type === "integer") {
let err = validateInteger(value)
if (!err) return errors
errors.push(err)
} else if (type === "array") {
if (!(arrayCheck || arrayListCheck)) {
return errors return errors
} }
if(value) {
value.forEach((item, i) => {
const errs = validateValueBySchema(item, schema.get("items"), false, bypassRequiredCheck, parameterContentMediaType)
errors.push(...errs
.map((err) => ({ index: i, error: err })))
})
}
} else if (type === "file") {
let err = validateFile(value)
if (!err) return errors
errors.push(err)
} }
if(schema && schema.has("required") && isFunc(requiredBySchema.isList) && requiredBySchema.isList()) {
requiredBySchema.forEach(key => {
if(objectVal[key] === undefined) {
errors.push({ propKey: key, error: "Required property not found" })
}
})
}
if(schema && schema.has("properties")) {
schema.get("properties").forEach((val, key) => {
const errs = validateValueBySchema(objectVal[key], val, false, bypassRequiredCheck, parameterContentMediaType)
errors.push(...errs
.map((error) => ({ propKey: key, error })))
})
}
}
if (pattern) {
let err = validatePattern(value, pattern)
if (err) errors.push(err)
}
if (minItems) {
if (type === "array") {
let err = validateMinItems(value, minItems)
if (err) errors.push(err)
}
}
if (maxItems) {
if (type === "array") {
let err = validateMaxItems(value, maxItems)
if (err) errors.push({ needRemove: true, error: err })
}
}
if (uniqueItems) {
if (type === "array") {
let errorPerItem = validateUniqueItems(value, uniqueItems)
if (errorPerItem) errors.push(...errorPerItem)
}
}
if (maxLength || maxLength === 0) {
let err = validateMaxLength(value, maxLength)
if (err) errors.push(err)
}
if (minLength) {
let err = validateMinLength(value, minLength)
if (err) errors.push(err)
}
if (maximum || maximum === 0) {
let err = validateMaximum(value, maximum)
if (err) errors.push(err)
}
if (minimum || minimum === 0) {
let err = validateMinimum(value, minimum)
if (err) errors.push(err)
}
if (type === "string") {
let err
if (format === "date-time") {
err = validateDateTime(value)
} else if (format === "uuid") {
err = validateGuid(value)
} else {
err = validateString(value)
}
if (!err) return errors
errors.push(err)
} else if (type === "boolean") {
let err = validateBoolean(value)
if (!err) return errors
errors.push(err)
} else if (type === "number") {
let err = validateNumber(value)
if (!err) return errors
errors.push(err)
} else if (type === "integer") {
let err = validateInteger(value)
if (!err) return errors
errors.push(err)
} else if (type === "array") {
if (!(arrayCheck || arrayListCheck)) {
return errors
}
if(value) {
value.forEach((item, i) => {
const errs = validateValueBySchema(item, schema.get("items"), false, bypassRequiredCheck, parameterContentMediaType)
errors.push(...errs
.map((err) => ({ index: i, error: err })))
})
}
} else if (type === "file") {
let err = validateFile(value)
if (!err) return errors
errors.push(err)
} }
return errors return errors