Commit dd9f9cc1 authored by Emmanuel Raviart's avatar Emmanuel Raviart
Browse files

Use Biryani validators.

parent 8b099e7c
......@@ -1736,6 +1736,11 @@
"to-fast-properties": "^2.0.0"
}
},
"@biryani/core": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@biryani/core/-/core-0.2.0.tgz",
"integrity": "sha512-9cj6BPW49Tgqex2eIFXYFo1HF0QvFOybxwozjk39tE5BjSo+QJHiEoKTBCCWzv4g+fx/HiLW3IRe7DZZwTG+SA=="
},
"@tricoteuses/assemblee": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/@tricoteuses/assemblee/-/assemblee-0.10.5.tgz",
......
......@@ -31,6 +31,7 @@
"type-check:watch": "npm run type-check -- --watch"
},
"dependencies": {
"@biryani/core": "^0.2.0",
"@tricoteuses/assemblee": "^0.10.5",
"command-line-args": "^5.1.1",
"cors": "^2.8.5",
......
import { validateNonEmptyTrimmedString } from "@biryani/core"
import fetch from "cross-fetch"
import { NextFunction, Request, Response } from "express"
......@@ -10,7 +11,6 @@ import {
getDocument,
retrieveTexteLoiParsed,
} from "../model"
import { validateNonEmptyTrimmedString } from "../validators/core"
import { validateFollow } from "../validators/query"
interface DocumentRequest extends Request {
......
import { NextFunction, Request, Response } from "express"
import { db, extractDataFromEntry } from "../database"
import { wrapAsyncMiddleware } from "../middlewares"
import { allFollows, DataGetter, DossierParlementaire, getDossier } from "../model"
import {
validateDateIso8601String,
validateInteger,
......@@ -12,7 +7,12 @@ import {
validateSetValue,
validateStringToNumber,
validateTest,
} from "../validators/core"
} from "@biryani/core"
import { NextFunction, Request, Response } from "express"
import { db, extractDataFromEntry } from "../database"
import { wrapAsyncMiddleware } from "../middlewares"
import { allFollows, DataGetter, DossierParlementaire, getDossier } from "../model"
import { validateFollow } from "../validators/query"
interface DossierRequest extends Request {
......
import { validateDateIso8601String, validateMissing, validateOption } from "@biryani/core"
import { NextFunction, Request, Response } from "express"
import { db, extractDataFromEntry } from "../database"
import { wrapAsyncMiddleware } from "../middlewares"
import { allFollows, DataGetter, getReunion, Reunion } from "../model"
import { validateActeurUid, validateOrganeUid } from "../validators/assemblee"
import {
validateDateIso8601String,
validateMissing,
validateOption,
} from "../validators/core"
import { validateFollow } from "../validators/query"
interface ReunionRequest extends Request {
......@@ -106,7 +102,7 @@ export const listReunions = wrapAsyncMiddleware(async function listReunions(
FROM reunions_acteurs_participants
WHERE acteur_uid = $<acteurUid>
)`,
`xsi_type = 'seance_type' AND lieu_code = 'AN'`,
"xsi_type = 'seance_type' AND lieu_code = 'AN'",
)
}
const whereActeurClause =
......
import {
validateNonEmptyTrimmedString,
} from "./core"
import { validateNonEmptyTrimmedString } from "@biryani/core"
const acteurUidRegExp = /^PA\d+$/
const organeUidRegExp = /^PO\d+$/
......
import assert from "assert"
export function assertValid([data, error]: [any, any]): any {
assert(
error === null,
`Error ${JSON.stringify(error, null, 2)} for ${JSON.stringify(
data,
null,
2,
)}`,
)
return data
}
export type Validator = (value: any) => [any, any]
export type AsyncValidator = (value: any) => Promise<[any, any]>
const booleanByString: { [s: string]: boolean } = {
"0": false,
"1": true,
false: false,
true: true,
}
const iso8601DateRegExp = /^\d{1,4}-[01]?\d-[0123]?\d$/
// Cf https://stackoverflow.com/questions/1303872/trying-to-validate-url-using-javascript
// But added support for "localhost"
const urlRegExp = /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)|localhost(?::\d{2,5})?(?:[/?#]\S*)?$/i
export function validateArray(itemValidator: Validator): Validator {
return function(array: any): [any, any] {
if (array === null || array === undefined) {
return [array, "Missing value"]
}
if (!Array.isArray(array)) {
return [array, `Expected an array, got "${typeof array}"`]
}
if (itemValidator === null || itemValidator === undefined) {
return [array, null]
}
const errors: { [key: string]: any } = {}
array = [...array]
for (const [index, value] of array.entries()) {
const [validatedValue, error] = itemValidator(value)
array[index] = validatedValue
if (error !== null) {
errors[index] = error
}
}
return [array, Object.keys(errors).length === 0 ? null : errors]
}
}
export function validateBoolean(value: any): [any, any] {
if (value === null || value === undefined) {
return [value, "Missing value"]
}
if (typeof value !== "boolean") {
return [value, `Expected a boolean, got "${typeof value}"`]
}
return [value, null]
}
export function validateChain(...validators: Validator[]) {
return function(value: any): [any, any] {
let error = null
for (const validator of validators) {
;[value, error] = validator(value)
if (error !== null) {
return [value, error]
}
}
return [value, null]
}
}
export function validateChoice(options: any[]): Validator {
return function(value: any): [any, any] {
if (!options.includes(value)) {
return [value, "Unexpected option"]
}
return [value, null]
}
}
export function validateDateIso8601String(input: any): [any, any] {
const [value, error] = validateNonEmptyTrimmedString(input)
if (error !== null) {
return [value, error]
}
if (!iso8601DateRegExp.test(value)) {
return [value, "Invalid date"]
}
return [value, null]
}
export function validateEmpty(value: any): [any, any] {
return validateChain(validateEmptyToNull, validateMissing)(value)
}
export function validateEmptyToNull(value: any): [any, any] {
if (value === null || value === undefined) {
return [null, null]
}
if (Array.isArray(value)) {
if (value.length === 0) {
return [null, null]
}
} else if (typeof value === "object") {
if (Object.keys(value).length === 0) {
return [null, null]
}
} else if (typeof value === "string") {
if (!value.trim()) {
return [null, null]
}
}
return [value, null]
}
export function validateFunction(func: (value: any) => any) {
return function(value: any): [any, any] {
return [func(value), null]
}
}
export function validateInteger(value: any): [any, any] {
if (value === null || value === undefined) {
return [value, "Missing value"]
}
if (!Number.isInteger(value)) {
return [value, `Expected an integer, got "${typeof value}"`]
}
return [value, null]
}
export function validateKeyValueDictionary(keyValidator: Validator, valueValidator:Validator): Validator {
return function(dict: any): [any, any] {
if (dict === null || dict === undefined) {
return [dict, "Missing value"]
}
if (typeof dict !== "object") {
return [dict, `Expected an object, got "${typeof dict}"`]
}
if (keyValidator === null || keyValidator === undefined) {
keyValidator = (key => [key, null])
}
if (valueValidator === null || valueValidator === undefined) {
valueValidator = (value => [value, null])
}
const errors: { [key: string]: any } = {}
const validatedDict: { [key: string]: any } = {}
for (const [key, value] of Object.entries(dict)) {
const [validatedKey, keyError] = keyValidator(value)
if (keyError !== null) {
errors[key] = `Key error: ${keyError}`
continue
}
const [validatedValue, valueError] = valueValidator(value)
validatedDict[validatedKey] = validatedValue
if (valueError !== null) {
errors[validatedKey] = valueError
}
}
return [validatedDict, Object.keys(errors).length === 0 ? null : errors]
}
}
export function validateMaybeTrimmedString(value: any): [any, any] {
return validateOption(validateMissing, [
validateTrimmedString,
validateEmptyToNull,
])(value)
}
export function validateMissing(value: any): [any, any] {
if (value === null || value === undefined) {
return [null, null]
}
return [value, "Expected null or undefined"]
}
export function validateNonEmpty(value: any): [any, any] {
if (value === null || value === undefined) {
return [value, "Missing value"]
}
if (Array.isArray(value)) {
if (value.length === 0) {
return [value, "Expected a non empty array"]
}
} else if (typeof value === "object") {
if (Object.keys(value).length === 0) {
return [value, "Expected a non empty object"]
}
} else if (typeof value === "string") {
if (!value) {
return [value, "Expected a non empty string"]
}
}
return [value, null]
}
export function validateNonEmptyTrimmedString(value: any): [any, any] {
return validateChain(validateTrimmedString, validateNonEmpty)(value)
}
export function validateNumber(value: any): [any, any] {
if (value === null || value === undefined) {
return [value, "Missing value"]
}
if (typeof value !== "number") {
return [value, `Expected a number, got "${typeof value}"`]
}
return [value, null]
}
export function validateOption(
...branches: (Validator | Validator[])[]
): Validator {
console.assert(Array.isArray(branches))
console.assert(branches.length > 0)
return function(value: any): [any, any] {
const errors = []
let error = null
const initialValue = value
for (const branch of branches) {
value = initialValue
const validators = Array.isArray(branch) ? branch : [branch]
{
;[value, error] = validators[0](value)
}
if (error !== null) {
errors.push(error)
continue
}
for (const validator of validators.slice(1)) {
;[value, error] = validator(value)
if (error !== null) {
return [value, error]
}
}
break
}
if (error !== null) {
return [initialValue, errors]
}
return [value, null]
}
}
export function validateSetValue(constant: any): Validator {
// eslint-disable-next-line no-unused-vars
return function(_value: any): [any, any] {
return [constant, null]
}
}
export function validateStrictEqual(expected: any): Validator {
return function(value: any): [any, any] {
if (value !== expected) {
return [value, `Expected a value equal to "${expected}"`]
}
return [value, null]
}
}
export function validateString(value: any): [any, any] {
if (value === null || value === undefined) {
return [value, "Missing value"]
}
if (typeof value !== "string") {
return [value, `Expected a string, got "${typeof value}"`]
}
return [value, null]
}
export function validateStringToBoolean(value: any): [any, any] {
if (value === null || value === undefined) {
return [value, "Missing value"]
}
const bool = booleanByString[value]
if (value === undefined) {
return [value, "Not a boolean"]
}
return [bool, null]
}
export function validateStringToNumber(value: any): [any, any] {
if (value === null || value === undefined) {
return [value, "Missing value"]
}
const number = Number(value)
if (isNaN(number)) {
return [value, "Not a number"]
}
return [number, null]
}
export function validateTest(
test: (value: any) => boolean,
errorMessage?: string | ((arg: any) => any) | null,
) {
return function(value: any): [any, any] {
return [
value,
test(value)
? null
: !errorMessage
? "Test failed"
: typeof errorMessage === "string"
? errorMessage
: errorMessage(value),
]
}
}
export function validateTrimmedString(value: any): [any, any] {
return validateChain(
validateString,
validateFunction((value: any) => value.trim()),
)(value)
}
export function validateTuple(...tupleValidator: Validator[]): Validator {
console.assert(Array.isArray(tupleValidator))
return function(array: any): [any, any] {
if (array === null || array === undefined) {
return [array, "Missing value"]
}
if (!Array.isArray(array)) {
return [array, `Expected an array, got "${typeof array}"`]
}
if (array.length !== tupleValidator.length) {
return [
array,
`Expected an array of length ${tupleValidator.length}, got "${array.length}"`,
]
}
const errors: { [key: string]: any } = {}
array = array.map((value, index) => {
const [validatedValue, error] = tupleValidator[index](value)
if (error !== null) {
errors[index] = error
}
return validatedValue
})
return [array, Object.keys(errors).length === 0 ? null : errors]
}
}
export function validateUrl(input: any): [any, any] {
const [value, error] = validateNonEmptyTrimmedString(input)
if (error !== null) {
return [value, error]
}
if (!urlRegExp.test(value)) {
return [value, "Invalid URL"]
}
// Wikibase-specific tests
if (value.length > 500) {
return [value, "URL too long for Wikibase"]
}
if (/[ \[\]]/.test(value)) {
return [value, "An URL can not contain spaces or square brackets"]
}
return [value, null]
}
......@@ -8,7 +8,7 @@ import {
validateSetValue,
validateString,
Validator,
} from "./core"
} from "@biryani/core"
export function validateFollow(possibleValues: string[]): Validator {
return validateChain(
......
......@@ -11,7 +11,7 @@ import {
validateStringToBoolean,
validateStringToNumber,
validateTest,
} from "./core"
} from "@biryani/core"
function validateDb(data: any): [any, any] {
if (data === null || data === undefined) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment