I have a node application which is written in typescript, uses sqlite database and I want to have model validation with sequelize. Only something is working though.
For example:
#AllowNull(false)
#IsInt
#Length({min: 4, max: 4})
#Column
seasonFrom: number
Here the length validation work but #IsInt does not. If I have a string value there, the string value is saved to the db, no error has been thrown.
I have tried to use this:
#Column(DataType.INTEGER)
but the same situation.
I also want to validate some string and boolean values but I do not see any method like #IsString or #IsBoolean.
in the model definition you can specify your own validators as functions like this for each column :
// in the column definition
const model = sequelize.define('Model', {
numberColumn :{
allowNull:false,
type: Sequelize.INTEGER
validate: {
isNumber: function (val) {
// here you can use any method to determine if it's a
number or not
if (typeof val == "number") { return
return Number.isInteger(val);
}else{ return false; }
}
},
boolenColumn: {
type:Sequelize.BOOLEAN,
validate: {
// you can add here any validation you want
isBoolean:function (val) {
return (typeof(val)=='boolean')
}
}
}
Related
Background
I'm not sure how I should approach sanitizing data I get from a Java backend for usage in a React form. And also the other way around: sanitizing data I get from a form when making a backend request. For frontend/backend communication we use OpenApi that generates Typescript interfaces and API for us from DTOs defined in Java.
Scenario
Example of the Schema in Java:
public enum Pet {
CAT,
DOG
}
#Schema(description = "Read, create or update an account")
public class AccountDto {
#NotNull
private Boolean active;
#NotBlank
private String userName;
#NotNull
private Pet preferedPet;
#Nullable
private String bioDescription;
// Constructor and getter/setters skipped
}
Current implementation
Example of the generated Typescript interface:
enum Pet {
CAT,
DOG
}
interface AccountDto {
active: boolean,
userName: string,
preferedPet: Pet,
bioDescription?: string // Translates to: string | undefined
}
Example React.js:
import {getAccount, updateAccount, Pet, AccountDto} from "./api"
export default function UpdateAccount() {
const [formData, setFormData] = useState<AccountDto>({
active: true,
userName: "",
preferedPet: Pet.CAT,
bioDescription: ""
})
useEffect(() => {
async function fetchAccount() {
const response = await getAccount();
// Omitted error handling
setFormData(response.data);
// response.data could look like this:
// {
// active: true,
// userName: "John",
// preferedPet: Pet.DOG,
// bioDescription: null
// }
}
}, [])
async function updateAccountHandler() {
const response = await updateAccount(formData);
// Omitted error handling
// Example formData object:
// {
// active: true,
// userName: "John",
// preferedPet: Pet.CAT,
// bioDescription: ""
// }
}
return (
// All input fields
)
}
Problems
When fetching the account, bioDescription is null. React will throw a warning that a component (bioDescription input) is changing from uncontrolled to controlled.
If by any chance there is a situation where null is set for preferedPet we will get a warning that the select value is not valid.
When updating the account all empty strings should be null. Required for the database and generally cleaner in my opinion.
Questions
1.) I'm wondering how other React users prepare/sanitize their data for usage and requests. Is there a go to or good practice I'm not aware of?
2.) Currently I'm using the following function to sanitize my data. It seems to work and Typescript does not notify me about any type mismatches but I think it should since bioDescription can only be string | undefined and not null.
function sanitizeData<T>(data: T, type: "use" | "request"): T {
const sanitizedData = Object.create({});
for (const [key, value] of Object.entries(data)) {
if (!value && type === "use") {
sanitizedData[key] = "";
} else if (!value && type === "request") {
sanitizedData[key] = null;
} else {
sanitizedData[key] = value;
}
}
return sanitizedData;
}
I have a situation where I'm trying to manually change a prop without using the React setState.
formData.description = null;
At this point Typescript is telling me that null is not possible. That's how I detected that my sanitizer function might not be correct.
Demo
Sandbox - https://codesandbox.io/s/async-cdn-7nd2m?file=/src/App.tsx
I get input data in this form:
[
{ name: "Producent", checked: true },
{ name: "Handel", checked: true },
{ name: "Simple", checked: true }
];
Only the checked values from true to false and vice versa can change. This is assigned to the checkedTypeOfClient variable. Later, I'd like to filter out all my clients (the currentClientList array) based on the checkedTypeOfClient variable.
Here are the properties of the Client class:
export class Client {
clientId: number;
name: string ;
district: string;
province: string;
zip: string;
city: string;
// tslint:disable-next-line: variable-name
full_Address: string;
latitude: number;
longitude: number;
segment: string;
ph: string;
bh: number;
canal: string;
isSimple: string;
}
The complication of this task is that the filtration goes like this. The values Producent and Handel are values that can be placed in the canal column that are in the Client Class, and the Simple value is a value that is also in the Client class in the isSimple column and can take the value "YES" or "NO"
for now, what I was able to do is extract what values Producent , Handel, Simple are marked and grind the Simple field to "TAK" or NIE "
filterClients() {
console.log(this.checkedTypeOfClient);
const filter={
Producent:this.checkedTypeOfClient.find(x=>x.name=="Producent").checked,
Handel:this.checkedTypeOfClient.find(x=>x.name=="Handel").checked,
Simple:this.checkedTypeOfClient.find(x=>x.name=="Simple").checked
}
let simpleFilter = this.returnFilterSimpleValue(filter.Simple);
this.currentClientList = this.baseClientList;
}
returnFilterSimpleValue(value : boolean) : string {
switch (value) {
case true:
return "TAK";
case false:
return "NIE";
}
If(Producent = True){
this.currentClientList(client.producent)
}
If(Handel= True){
this.currentClientList(client.handel)
}
If(Handel= True || Producent = True){
this.currentClientList(client.handel) && this.currentClientList(client.producent)
}
The question is how to filter it?
I'm not sure that I'm fully understanding your question. However, here's how I'd solve it.
First of all, I'd like to introduce you to union types (a feature of typescript that allows you to combine multiple types, but can also be used for literal values.
In your case that would be useful for:
isSimple: "YES" | "NO" meaning that it can only have either values. Also, why not make it a boolean?
canal: "Handel" | "Producent" meaning that it can only have either the "Handel" or "Producent" string value.
Secondly you can simple use filter array method to filter the objects that have a certain property values, which can also be chained.
Is this what you wanted to do or did I miss something?
I'm not sure either if I get the question right. As i understand it, you have two different types of clients: CanalClient and SimpleClient.
with the superclass Client sharing the common attributes.
export class CanalClient extends Client {
canal: string;
}
export class Simpleclientextends Client {
isSimple: string; // as Ruben asked - why is this not a simple boolean?
}
In the filter Operation, you can then check the class with instanceof CanalClient etc. to have type safety.
Actually, in an if block- this is type guarded:
if(client instanceof CanalClient) {
console.log( client.canal) // Typescript will know this is of type Canalclient!
}
I tried to keep it as simple as I could by reusing your code as per my understanding. Try this:
filterClients() {
console.log(this.checkedTypeOfClient);
const filter={
Producent:this.checkedTypeOfClient.find(x=>x.name=="Producent").checked,
Handel:this.checkedTypeOfClient.find(x=>x.name=="Handel").checked,
Simple:this.checkedTypeOfClient.find(x=>x.name=="Simple").checked
}
let simpleFilter = this.returnFilterSimpleValue(filter.Simple);
this.currentClientList = this.baseClientList.filter(c => {
if (c.canal=='Producent' && filter.Producent) {
return true;
}
if (c.canal=='Handel' && filter.Handel) {
return true;
}
});
this.currentClientList.foEach(c => { c.isSimple = this.returnFilterSimpleValue(c.isSimple) });
}
I need value :
If(Producent = True){
this.currentClientList(client.producent)
}
If(Handel= True){
this.currentClientList(client.handel)
}
If(Handel= True || Producent = True){
this.currentClientList(client.handel) && this.currentClientList(client.producent)
}
I have a Dexie.js database with the table "businessLayers" in my React application. I'd like to ensure de data types of the tuples inserted in that table. I thought the method Table.defineClass() would do that, but it does not. My db is the following:
import Dexie from 'dexie';
const db = new Dexie('MyDB');
db.version(1).stores({
businessLayers: '++id, layer'
});
const BusinessLayer = db.businessLayers.defineClass({
id: Number,
layer: String,
values: Object
});
export default db;
I'd like to make not possible to insert an invalid data type on each field. I haven't found any built-in method to do this. Do you know any? Thank you!
Table.defineClass() was an old feature in Dexie 1.x for code completion only - no enforcements. The method should have been deprecated. But the functionality you need can be implemented using a DBCore middleware or creating/updating hooks. DBCore middlware would be the most performant solution as it does not need to verify existing data.
Below is a dry coded full example. Please test and reply if it works. It should support String, Number, Boolean, Array, Object, Set, Map, ArrayBuffer, Uint8Array, etc... and even custom classes. If anyone wants to make a package of this code, please go ahead! I think it could be a nice addon to dexie:
import Dexie from 'dexie';
const db = new Dexie('MyDB');
db.version(1).stores({
businessLayers: '++id, layer'
});
// Use a DBCore middleware "enforceSchema" defined further down...
db.use(
enforceSchema({
businessLayers: {
id: Number,
layer: String,
values: Object
}
}
);
// This is the function that returns the middlware:
function enforceSchema(dbSchema) {
return {
stack: "dbcore",
name: "SchemaEnforcement",
create (downlevelDatabase) {
return {
...downlevelDatabase,
table (tableName) {
const downlevelTable = downlevelDatabase.table(tableName);
const tableSchema = dbSchema[tableName];
if (!tableSchema) return downlevelTable; // No schema for this table.
return {
...downlevelTable,
mutate: req => {
if (req.type === "add" || req.type === "put") {
for (obj of req.values) {
validateSchema(tableName, tableSchema, obj);
}
}
return downlevelTable.mutate(req);
}
}
}
};
}
};
}
function validateSchema(tableName, schema, obj) {
const invalidProp = Object.keys(schema).find(key =>
{
const value = obj[key];
const type = schema[key];
switch (type) {
// Handle numbers, strings and booleans specifically:
case Number: return typeof value !== "number";
case String: return typeof value !== "string";
case Boolean: return typeof value !== "boolean";
// All other types will be supported in the following
// single line:
default: return !(value instanceof type);
}
});
if (invalidProp) {
// Throw exception to abort the transaction and make the
// user get a rejected promise:
throw new TypeError(`Invalid type given for property ${invalidProp} in table ${tableName}. ${schema[invalidProp].name} expected.`);
}
}
When I retrieve and modify a Lobby with this Schema it seems to automatically cast types. I could not find the documentation for that feature so I am wondering if I am mistaken something else for autocasting.
I convert the types of password and owner to true or false because this is an exposed API endpoint everyone can view.
When I run the anonymizer function it runs and results in password : "true" not password: true. I would like it to send password: true but I am not sure if that is possible.
// Schema
const LobbySchema = new mongoose.Schema({
name: String,
password: String,
owner: { type: String, require: true },
Player: [],
});
// Anonymizer function
lobby.password = !!lobby.password;
lobby.owner = lobby.owner === user ? true: false;
res.send(JSON.stringify(lobby));
Yes, Mongoose cast values if it is possible.
The problem here is your schema define type owner as String. So the value true or false will be casted to string.
This is why you get password : "true".
To get password as a boolean you can either set Boolean into the schema or use Custom casting
Not tested but following the documentation should be similar to this:
const originalCast = mongoose.Boolean.cast();
mongoose.Boolean.cast(v => {
if (v === 'true') {
return true;
}
if (v === 'false') {
return false;
}
return originalCast(v);
});
Is it possible to specify that a field in GraphQL should be a blackbox, similar to how Flow has an "any" type? I have a field in my schema that should be able to accept any arbitrary value, which could be a String, Boolean, Object, Array, etc.
I've come up with a middle-ground solution. Rather than trying to push this complexity onto GraphQL, I'm opting to just use the String type and JSON.stringifying my data before setting it on the field. So everything gets stringified, and later in my application when I need to consume this field, I JSON.parse the result to get back the desired object/array/boolean/ etc.
#mpen's answer is great, but I opted for a more compact solution:
const { GraphQLScalarType } = require('graphql')
const { Kind } = require('graphql/language')
const ObjectScalarType = new GraphQLScalarType({
name: 'Object',
description: 'Arbitrary object',
parseValue: (value) => {
return typeof value === 'object' ? value
: typeof value === 'string' ? JSON.parse(value)
: null
},
serialize: (value) => {
return typeof value === 'object' ? value
: typeof value === 'string' ? JSON.parse(value)
: null
},
parseLiteral: (ast) => {
switch (ast.kind) {
case Kind.STRING: return JSON.parse(ast.value)
case Kind.OBJECT: throw new Error(`Not sure what to do with OBJECT for ObjectScalarType`)
default: return null
}
}
})
Then my resolvers looks like:
{
Object: ObjectScalarType,
RootQuery: ...
RootMutation: ...
}
And my .gql looks like:
scalar Object
type Foo {
id: ID!
values: Object!
}
Yes. Just create a new GraphQLScalarType that allows anything.
Here's one I wrote that allows objects. You can extend it a bit to allow more root types.
import {GraphQLScalarType} from 'graphql';
import {Kind} from 'graphql/language';
import {log} from '../debug';
import Json5 from 'json5';
export default new GraphQLScalarType({
name: "Object",
description: "Represents an arbitrary object.",
parseValue: toObject,
serialize: toObject,
parseLiteral(ast) {
switch(ast.kind) {
case Kind.STRING:
return ast.value.charAt(0) === '{' ? Json5.parse(ast.value) : null;
case Kind.OBJECT:
return parseObject(ast);
}
return null;
}
});
function toObject(value) {
if(typeof value === 'object') {
return value;
}
if(typeof value === 'string' && value.charAt(0) === '{') {
return Json5.parse(value);
}
return null;
}
function parseObject(ast) {
const value = Object.create(null);
ast.fields.forEach((field) => {
value[field.name.value] = parseAst(field.value);
});
return value;
}
function parseAst(ast) {
switch (ast.kind) {
case Kind.STRING:
case Kind.BOOLEAN:
return ast.value;
case Kind.INT:
case Kind.FLOAT:
return parseFloat(ast.value);
case Kind.OBJECT:
return parseObject(ast);
case Kind.LIST:
return ast.values.map(parseAst);
default:
return null;
}
}
For most use cases, you can use a JSON scalar type to achieve this sort of functionality. There's a number of existing libraries you can just import rather than writing your own scalar -- for example, graphql-type-json.
If you need a more fine-tuned approach, than you'll want to write your own scalar type. Here's a simple example that you can start with:
const { GraphQLScalarType, Kind } = require('graphql')
const Anything = new GraphQLScalarType({
name: 'Anything',
description: 'Any value.',
parseValue: (value) => value,
parseLiteral,
serialize: (value) => value,
})
function parseLiteral (ast) {
switch (ast.kind) {
case Kind.BOOLEAN:
case Kind.STRING:
return ast.value
case Kind.INT:
case Kind.FLOAT:
return Number(ast.value)
case Kind.LIST:
return ast.values.map(parseLiteral)
case Kind.OBJECT:
return ast.fields.reduce((accumulator, field) => {
accumulator[field.name.value] = parseLiteral(field.value)
return accumulator
}, {})
case Kind.NULL:
return null
default:
throw new Error(`Unexpected kind in parseLiteral: ${ast.kind}`)
}
}
Note that scalars are used both as outputs (when returned in your response) and as inputs (when used as values for field arguments). The serialize method tells GraphQL how to serialize a value returned in a resolver into the data that's returned in the response. The parseLiteral method tells GraphQL what to do with a literal value that's passed to an argument (like "foo", or 4.2 or [12, 20]). The parseValue method tells GraphQL what to do with the value of a variable that's passed to an argument.
For parseValue and serialize we can just return the value we're given. Because parseLiteral is given an AST node object representing the literal value, we have to do a little bit of work to convert it into the appropriate format.
You can take the above scalar and customize it to your needs by adding validation logic as needed. In any of the three methods, you can throw an error to indicate an invalid value. For example, if we want to allow most values but don't want to serialize functions, we can do something like:
if (typeof value == 'function') {
throw new TypeError('Cannot serialize a function!')
}
return value
Using the above scalar in your schema is simple. If you're using vanilla GraphQL.js, then use it just like you would any of the other scalar types (GraphQLString, GraphQLInt, etc.) If you're using Apollo, you'll need to include the scalar in your resolver map as well as in your SDL:
const resolvers = {
...
// The property name here must match the name you specified in the constructor
Anything,
}
const typeDefs = `
# NOTE: The name here must match the name you specified in the constructor
scalar Anything
# the rest of your schema
`
Just send a stringified value via GraphQL and parse it on the other side, e.g. use this wrapper class.
export class Dynamic {
#Field(type => String)
private value: string;
getValue(): any {
return JSON.parse(this.value);
}
setValue(value: any) {
this.value = JSON.stringify(value);
}
}
For similar problem I've created schema like this:
"""`MetadataEntry` model"""
type MetadataEntry {
"""Key of the entry"""
key: String!
"""Value of the entry"""
value: String!
}
"""Object with metadata"""
type MyObjectWithMetadata {
"""
... rest of my object fields
"""
"""
Key-value entries that you can attach to an object. This can be useful for
storing additional information about the object in a structured format
"""
metadata: [MetadataEntry!]!
"""Returns value of `MetadataEntry` for given key if it exists"""
metadataValue(
"""`MetadataEntry` key"""
key: String!
): String
}
And my queries can look like this:
query {
listMyObjects {
# fetch meta values by key
meta1Value: metadataValue(key: "meta1")
meta2Value: metadataValue(key: "meta2")
# ... or list them all
metadata {
key
value
}
}
}