gcanti / io-ts

Runtime type system for IO decoding/encoding
https://gcanti.github.io/io-ts/
MIT License
6.68k stars 330 forks source link

Cannot Enforce Readonly on String Type #634

Open craigmiller160 opened 2 years ago

craigmiller160 commented 2 years ago

šŸ› Bug report

Current Behavior

import * as ioType from 'io-ts';

export enum MarketInvestmentType {
    USA_ETF = 'USA_ETF',
    INTERNATIONAL_ETF = 'INTERNATIONAL_ETF',
    CRYPTO = 'CRYPTO'
}

const marketInvestmentTypeV = ioType.keyof({
    [MarketInvestmentType.USA_ETF]: null,
    [MarketInvestmentType.INTERNATIONAL_ETF]: null,
    [MarketInvestmentType.CRYPTO]: null
});

const marketInvestmentInfoV = ioType.type({
    symbol: ioType.readonly(ioType.string),
    name: ioType.readonly(ioType.string),
    type: ioType.readonly(marketInvestmentTypeV)
});
export type MarketInvestmentInfo = ioType.TypeOf<typeof marketInvestmentInfoV>;

const foo: MarketInvestmentInfo = {
    symbol: '',
    name: '',
    type: MarketInvestmentType.USA_ETF
};
foo.symbol = 'abc'; // TypeScript is allowing this, it shouldn't

Expected behavior

I would expect using the "readonly" combinator would result in a type that enforces readonly restrictions on that field. As is shown above, this does not happen. I will point out that the "type" field (the enum) in the above example does have readonly working.

Reproducible example

See above.

Suggested solution(s)

Either the readonly combinator should work for simple types like strings, or there should be another way to enforce readonly constraints on such fields.

Additional context

Your environment

Which versions of io-ts are affected by this issue? Did this work in previous versions of io-ts?

Software Version(s)
io-ts 2.2.16
fp-ts 2.11.7
TypeScript 4.5.4
mlegenhausen commented 2 years ago

You are not mutating the string you are mutating the object. You need to make your t.type readonly. This can be done by the following changes to your code:

import * as ioType from 'io-ts'

export enum MarketInvestmentType {
  USA_ETF = 'USA_ETF',
  INTERNATIONAL_ETF = 'INTERNATIONAL_ETF',
  CRYPTO = 'CRYPTO'
}

const marketInvestmentTypeV = ioType.keyof({
  [MarketInvestmentType.USA_ETF]: null,
  [MarketInvestmentType.INTERNATIONAL_ETF]: null,
  [MarketInvestmentType.CRYPTO]: null
})

const marketInvestmentInfoV = ioType.readonly(
  ioType.type({
    symbol: ioType.string,
    name: ioType.string,
    type: marketInvestmentTypeV
  })
)
export type MarketInvestmentInfo = ioType.TypeOf<typeof marketInvestmentInfoV>

const foo: MarketInvestmentInfo = {
  symbol: '',
  name: '',
  type: MarketInvestmentType.USA_ETF
}
foo.symbol = 'abc' // throws an error