io-ts icon indicating copy to clipboard operation
io-ts copied to clipboard

Validate array contains one of each object with specific properties and values

Open lance-p opened this issue 3 years ago • 2 comments

I looked at the docs but cant find the validation I need.

I have an array of object literals each with specific properties and values.

I want to validate that comboItems is not empty and contains a min and max of 3 objects AND that there is an itemType === 'Entree' AND an itemType==='Side' AND an itemType==='Beverage'


import * as t from "io-ts";
import { pipe } from 'fp-ts/function'
import { fold, isRight, isLeft } from 'fp-ts/Either'

enum ItemTypeEnum {'MEALS_GROUP'='MEALS_GROUP', 'ENTREES'='ENTREES', 'SIDES_GROUP'='SIDES_GROUP', 'BEVERAGES_GROUP'='BEVERAGES_GROUP'}

const Entree = t.type({
    itemType: t.literal(ItemTypeEnum.ENTREES),
})
const Side = t.type({
    itemType: t.literal(ItemTypeEnum.SIDES_GROUP),
})

type Entree = t.TypeOf<typeof Entree>
type Side = t.TypeOf<typeof Side>

export const Meal = t.type({
    comboItems: t.readonlyArray(t.union([Entree, Side]))
})

This shows as a success (which is expected):

console.log(pipe(
    Meal.decode({comboItems: [{itemType: ItemTypeEnum.ENTREES}, {itemType: ItemTypeEnum.SIDES_GROUP}]}),
    fold(
        // failure handler
        (errors) => `error: ${JSON.stringify(errors)}`,
        // success handler
        (a) => `success: ${JSON.stringify(a)}`
    )
    )
)

But this also shows as a success, which should not because it's missing {{itemType: ItemTypeEnum.SIDES_GROUP}

console.log(pipe(
    Meal.decode({comboItems: [{itemType: ItemTypeEnum.ENTREES}]}),
    fold(
        // failure handler
        (errors) => `error: ${JSON.stringify(errors)}`,
        // success handler
        (a) => `success: ${JSON.stringify(a)}`
    )
    )
)

Thanks for any help!

lance-p avatar Nov 09 '20 04:11 lance-p

comboItems is not empty and contains a min and max of 3 objects

So `comboItems should contain exactly 3 objects?

If possible go with a tuple(Entree, Side, Beverages). The disadvantage is that now the order of the elements is important.

If I misunderstood your first assumption and you mean at least 3 items. I would use a TypeScript Type that can describe your constraints then you are able to describe it in io-ts.

import { NonEmptyArray } from 'fp-ts/NonEmptyArray'

interface Meal {
  ENTREES: NonEmptyArray<Entree>
  SIDES_GROUP: NonEmptyArray<Side>
  BEVERAGES_GROUP: NonEmptyArray<Beverage>
}
function comboItems(meal: Meal): NonEmptyArray<Entree | Side | Beverage> {
  return [...meal.ENTREES, ...meal.SIDES_GROUP, ...meal.BEVERAGES_GROUP]
}

If you input is an array you can convert it to a record with groupBy from NonEmptyArray. Then I would check that your record is equal to the Meal interface e. g. by checking with an io-ts definition of Meal and the is method.

mlegenhausen avatar Nov 09 '20 06:11 mlegenhausen

I would use a branded complex type.

https://github.com/gcanti/fp-ts/issues/1340

steida avatar Nov 09 '20 21:11 steida