Does Mongoose auto cast types? - javascript

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);
});

Related

Possible to have `validate` fail on not specified elements?

The below returns success, even though obj contains an element that is not in the schema.
Question
Is it possible to have validate fail, when it sees elements that are not specified in the schema?
Jest have this expect(obj1).toEqual(obj2).
If Validate can't do it, what options do I then have to detect unwanted elements?
const Schema = require("validate");
const obj = {good: "1", bad: "2"};
const user = new Schema({
good: {
type: String,
required: true,
}
});
const r = user.validate(obj);
console.log(r);
Yes, there is a strict option on the Schema, from the documentation:
Schema
A Schema defines the structure that objects should be validated against.
Parameters
obj schema definition
opts options
opts.typecast
opts.strip
opts.strict
Validation fails when object contains properties not defined in the schema
(optional, default false)
So you'll need something like:
const options = { strict: true };
const user = new Schema({
good: {
type: String,
required: true,
}
}, options);

Correctly sanitize data for request - Typescript, React

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

Assume value exists if another value exists in Flow type checking

I'm trying to figure out if this is possible with flow.
I have a function that returns an object like this
{
isChannel: boolean,
channelName?: string,
streamName?: string,
}
if isChannel === true, I know channelName will exist.
In many places in our codebase we have something like this
const { channelName, streamName, isChannel } = parseUri(uri);
const name = isChannel ? channelName.slice(1) : streamName;
The slice(1) is because the channelName includes a leading #. I know there are ways around this, but since this is all existing code, I'd rather not have to change it all.
Is there anyway to say this with flow types? If isChannel is true, channelName will exist, if isChannel is false, then it might exist.
if isChannel === true
then channelName: string
else channelName?: string
You can define a union type where one type has isChannel: true and the other has isChannel: false:
type Channel =
| {| isChannel: false, channelName?: string, streamName?: string |}
| {| isChannel: true, channelName: string, streamName?: string |};
Try Flow
Then you can refine the type by checking isChannel without having to check channelName.

Parsing string literals into enum array

I'm receiving the following in an API response:
{ "roles": [ "ADMIN", "USER" ] }
where the response will always contain an array of roles (USER, PRESENTER, ORGANIZER, and ADMIN).
I want to convert it into a valid TypeScript array (Role[]), where the type Role is defined as follows:
export type Role = 'USER' | 'PRESENTER' | 'ORGANIZER' | 'ADMIN'
Any ideas?
Your Role type is not an enum. It is just a string type limited to certain values.
You can just cast the result as a Role[] and TypeScript will be happy. This assumes the incoming data never has a bad value!
const data: {roles: Role[]} = JSON.parse('{"roles": ["ADMIN", "USER"]}');
data.roles // TypeScript knows it is a Role[]
You can just cast it to your union type:
const apiRoleArray = ["ADMIN", "USER"];
const realRoleArray: Role[] = <Role[]>apiRoleArray;
BUT you probably want to validate its contents rather than just trusting the API. :-) Drawing on this question's answers, you can create the type by using the keys of an object rather than defining it literally (see the accepted answer there for why):
const roleStrings = {
USER: "",
PRESENTER: "",
ORGANIZER: "",
ADMIN: ""
};
export type Role = keyof typeof roleStrings;
then give yourself a validation function:
const isRole = (s: string): s is Role => {
return roleStrings.hasOwnProperty(s);
};
then a robust conversion function, for example:
const rawToRoleArray = (rawArray: string[]): Role[] => {
return rawArray.map(s => {
if (!isRole(s)) {
throw new Error("Invalid Role: " + s);
}
return <Role>s;
});
};
(you could combine those if you don't need them separately)
then use it:
// Valid
const realRoleArray: Role[] = rawToRoleArray(["ADMIN", "USER"]);
console.log(realRoleArray);
// Invalid
const realRoleArray2: Role[] = rawToRoleArray(["ADMIN", "FOO"]);
console.log(realRoleArray2);
Live in the playground | Live on jsFiddle
If I got you corectly thats what you want to do.
enum RoleEnum {
USER,
PRESENTER,
ORGANIZER,
ADMIN
}
const parseEnum = (name: String): RoleEnum => RoleEnum[`${name}`]
const parsed: RoleEnum[] = [ 'ADMIN', 'USER' ].map(parseEnum)
console.log(parsed)

How to validate int and boolean with sequelize and sqlite

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')
}
}
}

Categories