gcanti / io-ts-types

A collection of codecs and combinators for use with io-ts
https://gcanti.github.io/io-ts-types/
MIT License
311 stars 40 forks source link

Add case insensitivity for string type literals #134

Open piersmacdonald opened 6 years ago

piersmacdonald commented 6 years ago

it's helpful to define an in put as being t.keyof({ firstName: null, preferredName: null }); but it would be nice if they could send firstName, firstname, fIrStNaMe`

sledorze commented 6 years ago

@piersmacdonald we strive keeping io-ts mininal and lean. Io-ts-types repo is where such additional Types may be defined.

piersmacdonald commented 6 years ago

Fair enough, thanks for the quick response.

kpritam commented 4 years ago

@piersmacdonald Did you come up with this type? I couldn't find it in io-ts-types.

@gcanti is there easy way to achieve this with current Decoder?

One way to do this is to have all literals in lowercase and then convert incoming string to lowercase and then forward it to D.literals decoder

But this forces me to define all literal types in lowercase.

 const lowerCase = pipe(
    D.string,
    D.map((s) => s.toLowerCase())
  )

  const ComponentType = pipe(lowerCase, D.compose(D.literal('hcd', 'assembly')))
  type ComponentType = D.TypeOf<typeof ComponentType>

  console.log(ComponentType.decode('HCD'))
gcanti commented 4 years ago

@kpritam I would return the original input

import * as E from 'fp-ts/lib/Either'
import { flow } from 'fp-ts/lib/function'
import * as D from 'io-ts/lib/Decoder'

const decoder: D.Decoder<unknown, string> = {
  decode: flow(
    D.string.decode,
    E.chainFirst((s) => D.literal('hcd', 'assembly').decode(s.toLowerCase()))
  )
}

console.log(decoder.decode('HCD'))
// => { _tag: 'Right', right: 'HCD' }
kpritam commented 4 years ago

@gcanti but this forces to define all the literals in lowercase, that might not what you always want.

Basically is it possible to make decoding of following literals case insensitive?

const compType = D.literal('Hcd', 'Assembly')

// all the following cases should pass and all should return **Hcd**
compType.decode('hcd')
compType.decode('Hcd')
compType.decode('HCD')
gcanti commented 4 years ago

all the following cases should pass and all should return Hcd

Ah ok, I misinterpreted the requirements, I thought you always wanted the original input back

gcanti commented 4 years ago
import * as E from 'fp-ts/lib/Either'
import { flow, pipe } from 'fp-ts/lib/function'
import * as O from 'fp-ts/lib/Option'
import * as A from 'fp-ts/lib/ReadonlyArray'
import * as D from 'io-ts/lib/Decoder'

export const iliteral = <A extends readonly [string, ...Array<string>]>(
  ...values: A
): D.Decoder<unknown, A[number]> => {
  const message = values.map((value) => JSON.stringify(value)).join(' | ')
  return {
    decode: flow(
      D.string.decode,
      E.chain((s) =>
        pipe(
          values,
          A.findIndex((value) => value.toLowerCase() === s.toLowerCase()),
          O.fold(
            () => D.failure(s, message),
            (i) => D.success(values[i])
          )
        )
      )
    )
  }
}
kpritam commented 4 years ago

@gcanti great, thanks.

Does it make sense for this to go into main library or io-ts-types?

It is pretty common use case that these literal types are defined as case insensitive enums in other languages and then its upto codec library to decide how to encode/decode them.

piersmacdonald commented 4 years ago

@kpritam ended up switching libraries for the job. This question was asked in the context of validating user requests for a REST service. So I switched to class-validator which is more specific to my use case. Still make some use of io-ts but not in that context.

gcanti commented 4 years ago

Does it make sense for this to go into main library or io-ts-types?

:+1: once Decoder becomes official, moving to io-ts-types as a reminder