kiesraad / abacus

Abacus, software voor verkiezingsuitslagen en zetelverdeling
https://kiesraad-abacus.pages.dev
European Union Public License 1.2
19 stars 6 forks source link

Discussion: what TypeScript type should a Rust enum have in our openapi.ts #489

Open oliver3 opened 1 month ago

oliver3 commented 1 month ago

Current implementation

In Abacus, we generate an OpenAPI specification from the Rust types, and an openapi.ts file with TypeScript types from that OpenAPI spec.

Currently, Rust enum types, such as

#[serde(rename_all = "snake_case")]
pub enum PollingStationStatus {
    FirstEntry,
    FirstEntryInProgress,
    Definitive,
}

are represented in OpenAPI as

"PollingStationStatus": {
  "type": "string",
  "enum": [
    "first_entry",
    "first_entry_in_progress",
    "definitive"
  ]
},

and the generated TypeScript type is a union type of string literals

export type PollingStationStatus = "first_entry" | "first_entry_in_progress" | "definitive";

Problem

With this solution, it seems to that there is no way to iterate over the possible values of this type, which would have been useful in a recent PR.

The generation of openapi.ts is done in gen_openapi_types.ts, and could be changed here to generate something else for these enums.

Possible other TypeScript types

A quick investigation leads to two other ways to represent this enum in the frontend, whose values can be iterated over:

TypeScript string enum

export enum PollingStationStatus {
  first_entry = "first_entry",
  first_entry_in_progress = "first_entry_in_progress",
  definitive = "definitive",
}

String array as const, and a type that is generated from its values

export const pollingStationStatusValues = ["first_entry", "first_entry_in_progress", "definitive"] as const;
export type PollingStationStatus = (typeof pollingStationStatusValues)[number];

Do we see the value of being able to iterate over the values, and which solution do we prefer?

lkleuver commented 1 month ago

another option: (least amount of refactor I think)

export const pollingStationStatusList: PollingStationStatus[] = [...];

But probably typescript enum is the most "proper", only reason string literal type is "nicer' is you don't need an extra import / less code.

jschuurk-kr commented 1 month ago

which would have been useful in a recent PR

@oliver3 Can you please add this issue to the epic of that PR? Because I think the most effective way forward is to take care of it now and use it to improve the code in that PR. An important goal of the zero bug policy is to make a developer's life easier. ;-)

oliver3 commented 1 month ago

Can you please add this issue to the epic of that PR? Because I think the most effective way forward is to take care of it now and use it to improve the code in that PR. An important goal of the zero bug policy is to make a developer's life easier. ;-)

Eventually we chose a different solution altogether so there is no need anymore in that epic, which was https://github.com/kiesraad/abacus/issues/399.

praseodym commented 3 weeks ago

In the TypeScript community there is some regarding the use of enums, as JavaScript lacks such functionality. Additionally, I found them inconvenient to use due to the necessity of importing the enum type.

oliver3 commented 3 days ago

One use case I just encountered is to be able to show radio buttons for each value of the PollingStationType.

@praseodym how would you feel about the other suggestions, e.g.

export const pollingStationStatusValues = ["first_entry", "first_entry_in_progress", "definitive"] as const;
export type PollingStationStatus = (typeof pollingStationStatusValues)[number];

or the other way around

export type PollingStationStatus = "first_entry" | "first_entry_in_progress" | "definitive";
export const pollingStationStatusValues: PollingStationStatus[] = ["first_entry", "first_entry_in_progress", "definitive"];
praseodym commented 3 days ago

First suggestion looks like the best option to me because the values are not repeated. To me it's a bit more clear without the parenthesis around typeof, i.e.:

export const pollingStationStatusValues = ["first_entry", "first_entry_in_progress", "definitive"] as const;
export type PollingStationStatus = typeof pollingStationStatusValues[number];