My "AppState" enum has following possible enum values:
export enum AppState {
SUCCESS,
ERROR,
RUNNING
}
I have a UpdateAppStateDTO with an appState which should accept every enum value except RUNNING.
export class UpdateAppStateDTO {
#IsEnum(AppState)
#NotEquals(AppState.RUNNING) // Doesn't work properly
public appState: AppState;
}
For the route I have this example
#Patch()
public setState(#Body() { appState }: UpdateAppStateDTO): void {
console.log(appState);
}
If the request has an empty body or a non valid enum value like "foobar" for appState I'm getting a 400, which is fine.
The problem is that when I send "RUNNING" I'm still getting a 200 instead of a 400.
How can I prevent this behaviour?
I assume you are sending in the string 'RUNNING', and you're trying to make sure that that is what is not used, correct? With what you've currently got, your enum maps to these values:
export enum AppState {
SUCCESS = 0,
ERROR = 1,
RUNNING = 2
}
So if you send in the string 'RUNNING', the validator checks that RUNNING !== 2 which is in fact true leading to successful validation. The #IsEnum() decorator checks that the value sent in in a valid key of the enum, so sending in 'RUNNING' passes that check, hence why you don't get some sort of error there.
The most verbose way to fix this is to make your enum a string enum like so:
export enum AppState {
SUCCESS = 'SUCCESS',
ERROR = 'ERROR',
RUNNING = 'RUNNING'
}
This will make each AppState value map to its corresponding string, though that does lead to having to type out a lot of declarations and can lead to duplicate code.
Another way to manage this is to set your #NotEquals() enum to the key provided by the enum value like so:
export class UpdateAppStateDTO {
#IsEnum(AppState)
#NotEquals(AppState[AppState.RUNNING])
public appState: AppState;
}
But keep in mind that with this approach when you look at appState later it will still be a numeric value instead of a string.
You can play around with this stackblitz I made for this to see some running code.
The problem is that when I send "RUNNING" I'm still getting a 200 instead of a 400.
it seems that you are using the string(!) "RUNNING" as value in your request payload as such:
{ appState: "RUNNING" }
In this case, IsEnum and NotEquals both regard the payload as valid.
Why is that?
First of all numeric enums are reverse mapped by typescript so your enum is internally (as javascript object) represented as follows:
{
'0': 'SUCCESS',
'1': 'ERROR',
'2': 'RUNNING',
'SUCCESS': 0,
'ERROR': 1,
'RUNNING': 2
}
Now class-validator's isEnum() is coded as follows:
isEnum(value: unknown, entity: any): boolean {
const enumValues = Object.keys(entity)
.map(k => entity[k]);
return enumValues.indexOf(value) >= 0;
}
and since the enum is reverse-mapped isEnum('RUNNNING', AppState) will return true.
At the same time NotEquals, which is coded as such...
notEquals(value: unknown, comparison: unknown): boolean {
return value !== comparison;
}
will compare the string 'RUNNING' against AppState.RUNNING (which equates to 2) and also conclude that this is valid since 'RUNNING' != 2.
So there you have it why the payload { appState: "RUNNING" } will result in a 200 instead of a 400 status code.
How can I prevent this behaviour?
The enum value AppState.RUNNING equates to 2 so when you make a request, you should use the numeric value of 2 in your payload:
{ appState: 2 }
In the above case, the class-validator's NotEquals validator will then correctly deny the request with the response containing:
"constraints": {
"notEquals": "appState should not be equal to 2"
}
Related
I have an interface with
interface mathTest {
mathAction: MathActionEnum;
}
The reason for this is that I want this property to have just one of the specific values from the enum below.
enum MathActionEnum {
'byOne' = 1,
'byTwo' = 2,
'byFour' = 4,
'byEight' = 8,
}
Assume mathAction = 'byOne' -> received from an API response.
First scenario: doing an arithmetic operation, I need the number value: let result: number = amount / MathActionEnum[mathAction] but I get an error:
The right-hand side of an arithmetic operation must be of type 'any',
'number', 'bigint' or an enum type
It is a number but still I need to cast it with Number(MathActionEnum[mathAction]) for the error to go away.
Second scenario: equality check, I need the string value: if (mathAction === MathActionEnum[MathActionEnum.byOne]) but I get an error:
This condition will always return 'false' since the types
'MathActionEnum' and 'string' have no overlap
Which makes sense.
I'm a bit lost, is there a way to syntax it as I expect it to be? Maybe I need to define things differently?
Thanks
TypeScript enums are absolutely NOT suitable for any sort of key-value mapping. The intent is to have a grouping of uniquely identifiable labels, but labels are where it ends. While they may indeed have a number representation under the hood, they are not intended for use as a key-value store. You will have to cast it to "extract the number", and then the type is just number, so you effectively defeat the purpose of enums.
For all intents and purposes, think of them like keys with no useful values:
const MathActionEnum = Object.freeze({
byOne: Symbol(),
byTwo: Symbol(),
byFour: Symbol(),
byEight: Symbol(),
})
Consider the newer alternative, const assertion, instead. They'll provide you with type safety on both keys and values:
const MathActions = {
'byOne': 1,
'byTwo': 2,
'byFour': 4,
'byEight': 8,
} as const
type MathAction = keyof typeof MathActions
type MathActionValue = typeof MathActions[MathAction]
You get full type safety on both keys and values:
const example = (action: MathAction) => {
return 2 * MathActions[action]
}
example('byOne')
// compile error, not a valid key
example('foo')
// -------------
const example2 = (actionValue: MathActionValue) => {
return 2 * actionValue
}
example2(4)
// compile error, not a valid value
example2(19)
You can even add type assertions to check if arbitrary values are a key or value:
const isAction = (action: string): action is MathAction => {
return Object.keys(MathActions).includes(action)
}
isAction
const isActionValue = (actionValue: number): actionValue is MathActionValue => {
return Object.values(MathActions).includes(actionValue as any)
}
You'll even get IDE autocompletion for both keys and values:
Here's a Playground
I am debugging an app, there is an existing redux reducer which sets some data of store object. Now when i dispatch action for this reducer before the relevant object is initialised it still works and create an empty object. This works on our deployment server and do crash on my local machine with correct error that "map is undefined on null". Why is it creating an empty object and not crashing on deployment server and if it is creating an object why is it not assigning the data we pass to it. My reducer is
case ACTIONS.SET_LOCAL_WEIGHTS: {
const { weight } = action;
const drafts = fromJS(state.getIn(['draftData', 'rows']));
const setWeight = drafts.map((row: any) => {
row.data.weight = weight[row.id].weight;
return row;
});
return state
.setIn(['draftData', 'rows'], setWeight)
.setIn(['draftData', 'total'], setWeight.length);
}
It creates: draftData: {} when rows and total is also provided. I have tried it on node 15 and 12 for checking any anomaly on map function.
I get error Cannot read property 'map' of undefined on your code if the initial state doesn't have a property state.draftData.rows. I don't see anywhere where you would be creating an empty object.
The immutable.js fromJS method will create a List if called with an array from state.draftData.rows. But if it is called with undefined then it returns undefined instead of a collection with a .map() method.
I also don't think that you need to be calling fromJS if the rows object is never converted toJS, but it might depend on your initial state.
This code should work. It uses the existing List from state if it exists, or creates an empty List otherwise.
const drafts = state.getIn(["draftData", "rows"]) ?? fromJS([]);
The assignment in row.data.weight = weight[row.id].weight seems like a mutation of state.
I tried to rewrite this, but it seems strange to me that your code doesn't do anything with the weights in the payload unless their index/key matches one that's already in the state.
import { fromJS, List, Map } from "immutable";
interface Row {
data: {
weight: number;
};
id: number;
}
const reducer = (state = Map(), action) => {
switch (action.type) {
case ACTIONS.SET_LOCAL_WEIGHTS: {
const { weight } = action;
const drafts: List<Row> =
state.getIn(["draftData", "rows"]) ?? fromJS([]);
const setWeight = drafts.reduce(
(next, row, index) =>
next.setIn([index, "data", "weight"], weight[row.id]?.weight),
drafts
);
return state
.setIn(["draftData", "rows"], setWeight)
.setIn(["draftData", "total"], setWeight.size);
}
default:
return state;
}
};
I have a TypeScript server trying to read a JSON object using a Struct but it seems to be partially working only for objects containing a "fields" key which then expects an object as value. Nonetheless, a Struct should work with any JSON object.
Using BloomRPC I am trying the following message:
{
"payload": {
"fields": {
"Hello": {
"whatever": 0
}
}
}
}
The server reads:
{ fields: { Hello: {} } }
If I send:
{
"payload": {
"anotherfield": {
"HelloWorld": {
"whatever": 0
}
}
}
}
I get an empty object on the server.
The simplified protobuf file looks like this:
syntax = "proto3";
import "google/protobuf/struct.proto";
// The service definition.
service TestTicketService {
rpc UpdateTicket (UpdateTicketRequest) returns (UpdateTicketResponse);
}
// The request message containing the required ticket information.
message UpdateTicketRequest {
string ticketId = 1;
google.protobuf.Struct payload = 2;
}
// The response message containing any potential error message
message UpdateTicketResponse {
string error = 1;
}
Any idea why google/protobuf/struct.proto doesn't work as expected?
What really confused me is that I was trying to pass normal JSON objects and expecting to read them. The whole point is that from the client side, the JSON object needs to be encoded in a very specific way.
For example:
"payload": {
"fields": {
"name": {
"stringValue": "joe"
},
"age": {
"numberValue": 28
}
}
}
You can figure out the format of the message by looking at the Struct proto file here: https://googleapis.dev/nodejs/asset/latest/v1_doc_google_protobuf_doc_struct.js.html
The idea of a struct is that you can store arbitrary data - but only simple types: null, number, string, bool, array and object.
This maps perfectly to JSON, and this is not by accident.
The google.protobuf.Struct message has a special JSON representation:
The JSON representation for Struct is JSON object.
So you can parse any JSON string into a protobuf Struct, and when serializing to JSON again, you also get the same JSON string again.
It is important to note that the in-memory representation of the parsed Struct is not equal to a JSON object. Protobuf does not have dynamic fields and has to represent JSON data in a more complicated manner. That is why struct.proto defines some other types.
When you want to create a Struct in JavaScript, it is probably the easiest way to just create the JSON object you want:
var jsonObject = {foo: "bar"};
var jsonString = JSON.stringify(jsonObject);
Now you can parse you Struct from this jsonObject or jsonString and put set resulting Struct as a field value in another protobuf message.
Since you are already using TypeScript, it might be worth checking out one of the alternative TypeScript implementations for protobuf.
I am the author of protobuf-ts. Creating a Struct is pretty straight-forward:
let struct = Struct.fromJson({foo: "bar"});
First, install #types/google-protobuf and:
let rqm = new UpdateTicketRequest();
rqm.setTicketId("1");
rqm.setPayload(Struct.fromJavaScript({
Hello:{
whatever: 0,
}
});
//and call the api....
UpdateTicket(rqm);
The test is linked to this question here which I raised (& was resolved) a few days ago. My current test is:
// Helpers
function getObjectStructure(runners) {
const backStake = runners.back.stake || expect.any(Number).toBeGreaterThan(0)
const layStake = runners.lay.stake || expect.any(Number).toBeGreaterThan(0)
return {
netProfits: {
back: expect.any(Number).toBeGreaterThan(0),
lay: expect.any(Number).toBeGreaterThan(0)
},
grossProfits: {
back: (runners.back.price - 1) * backStake,
lay: layStake
},
stakes: {
back: backStake,
lay: layStake
}
}
}
// Mock
const funcB = jest.fn(pairs => {
return pairs[0]
})
// Test
test('Should call `funcB` with correct object structure', () => {
const params = JSON.parse(fs.readFileSync(paramsPath, 'utf8'))
const { arb } = params
const result = funcA(75)
expect(result).toBeInstanceOf(Object)
expect(funcB).toHaveBeenCalledWith(
Array(3910).fill(
expect.objectContaining(
getObjectStructure(arb.runners)
)
)
)
})
The object structure of arb.runners is this:
{
"back": {
"stake": 123,
"price": 1.23
},
"lay": {
"stake": 456,
"price": 4.56
}
}
There are many different tests around this function mainly dependent upon the argument that is passed into funcA. For this example, it's 75. There's a different length of array that is passed to funcB dependent upon this parameter. However, it's now also dependent on whether the runners (back and/or lay) have existing stake properties for them. I have a beforeAll in each test which manipulates the arb in the file where I hold the params. Hence, that's why the input for the runners is different every time. An outline of what I'm trying to achieve is:
Measure the array passed into funcB is of correct length
Measure the objects within the array are of the correct structure:
2.1 If there are stakes with the runners, that's fine & the test is straight forward
2.2 If not stakes are with the runners, I need to test that; netProfits, grossProfits, & stakes properties all have positive Numbers
2.2 is the one I'm struggling with. If I try with my attempt below, the test fails with the following error:
TypeError: expect.any(...).toBeGreaterThan is not a function
As with previous question, the problem is that expect.any(Number).toBeGreaterThan(0) is incorrect because expect.any(...) is not an assertion and doesn't have matcher methods. The result of expect.any(...) is just a special value that is recognized by Jest equality matchers. It cannot be used in an expression like (runners.back.price - 1) * backStake.
If the intention is to extend equality matcher with custom behaviour, this is the case for custom matcher. Since spy matchers use built-in equality matcher anyway, spy arguments need to be asserted explicitly with custom matcher.
Otherwise additional restrictions should be asserted manually. It should be:
function getObjectStructure() {
return {
netProfits: {
back: expect.any(Number),
lay: expect.any(Number)
},
grossProfits: {
back: expect.any(Number),
lay: expect.any(Number)
},
stakes: {
back: expect.any(Number),
lay: expect.any(Number)
}
}
}
and
expect(result).toBeInstanceOf(Object)
expect(funcB).toHaveBeenCalledTimes(1);
expect(funcB).toHaveBeenCalledWith(
Array(3910).fill(
expect.objectContaining(
getObjectStructure()
)
)
)
const funcBArg = funcB.mock.calls[0][0];
const nonPositiveNetProfitsBack = funcBArg
.map(({ netProfits: { back } }, i) => [i, back])
.filter(([, val] => !(val > 0))
.map(([i, val] => `${netProfits:back:${i}:${val}`);
expect(nonPositiveNetProfitsBack).toEqual([]);
const nonPositiveNetProfitsLay = ...
Where !(val > 0) is necessary to detect NaN. Without custom matcher failed assertion won't result in meaningful message but an index and nonPositiveNetProfitsBack temporary variable name can give enough feedback to spot the problem. An array can be additionally remapped to contain meaningful values like a string and occupy less space in errors.
I know this is a general question but I have exhausted google and tried many approaches.Any feedback is appreciated.
The HTTPClient is Angular 5+ so it returns an object created from the response JSON data. I get a massive JSON response from an endpoint I have no control over and I want to use about 20% of the response in my app and ignore the rest.
I am really trying hard to avoid using a series of templates or export objects or whatever and trying to force this massive untyped Observable into a typed object with hundreds of fields many being Arrays. All I need for the app is just a Array of very small objects with 3 fields per object. The 3 fields are all over within the JSON response and I want to map them to my object .map only seems to work when you are using the full response object and I can't find an example where .map does custom work besides in the case where you are mapping a few fields to 1 object and I am trying to map to an Array of my small objects.
UPDATED
Basically I want this service to return an object of Type DislayData to the module that subscribes to it but I get just an Object back. This is not what I ultimately need to do but if I can prove I can map the body of the response to my needed return type I can then start to break down the response body and return an Array of the Type I really need based on my silly DisplayData object. Thanks again!
export interface DislayData {
body: any;
}
...
export class DataService {
constructor(private http: HttpClient) { }
/** GET data from the black box */
getData(): Observable<DislayData> {
return this.http.get<HttpResponse<any>>(searchUrl, { observe: 'response' })
.pipe(
map(res => {
return res.body as DislayData;
}
tap(res => console.log(//do stuff with entire respoonse also)),
catchError(err => this.handleError(err)));
}
private handleError(error: HttpErrorResponse) {
...
Do you know the structure of the answering object?
If yes, you can do something like this:
item$ = new BehaviorSubject<any>({});
item = {
foo: 'a',
bar: 'b',
iton: [1, 2, 3],
boo: {
far: 'c'
}
};
logNewItem() {
this.item$
.pipe(
map(response => {
if (response.foo
&& response.iton
&& response.iton.length >= 3
&& response.boo
&& response.boo.far) {
let newItem = {
foo: response.foo,
iton2: response.iton[2],
far: response.boo.far
};
console.log(newItem); // output: Object { foo: "a", iton2: 3, far: "c" }
}
})
)
.subscribe();
this.item$.next(this.item);
}
Basically, you can simply make sure the properties exist, call them directly and map them to a better fitting object.
I heavily recommend creating an interface for the object you're receiving and an interface or class for the object you're mapping to. In that case you can also write the code more compact like this:
[...]
map(response: MyAPIResponse => {
let newItem = new NewItem(response);
console.log(newItem); // output: Object { foo: "a", iton2: 3, far: "c" }
}
})
[...]
class NewItem {
foo: string;
iton2: string;
far: string;
constructor(apiResponse: MyAPIResponse) {
//Validate parameter first
this.foo = apiResponse.foo;
this.iton2 = apiResponse.iton[2];
this.far = apiResponse.boo.far;
and make your code a lot more readable.