I'm currently working on the CVSS v3.1 implementation in TypeScript. Here is the specification. The most interesting part for my question is probably Table 15: Base, Temporal and Environmental Vectors. Let's have a look at the Environmental metric group. Here we have the metrics Confidentiality Requirement, Integrity Requirement, etc. Each metric has possible values, e.g. Not Defined(X), High(H), Medium(M), and Low(L). I thought that's a pretty good use case for enums and records.
I looked at all examples I could find online but most examples are pretty simple and their value type is always the same. My value type depends on the key.
Here is what I currently have. I tried to keep it simple and use only two metrics.
enum EnvironmentalMetric { ConfidentialityRequirement = 'CR', IntegrityRequirement = 'IR', } // references EnvironmentalMetric.ConfidentialityRequirement (CR) enum ConfidentialityRequirement { NotDefined = 'X', Low = 'L', Medium = 'M', High = 'H', } // references EnvironmentalMetric.IntegrityRequirement (IR) enum IntegrityRequirement { NotDefined = 'X', Low = 'L', Medium = 'M', High = 'H', } In the end I'd like to have two loops, an outer loop over all metrics and an inner loop for each metric value. So I tried to define a nested record. For every metric value I need to have some information like a textual description and a numeric value. This numeric value is required afterwards to calculate a score. So accessing this value in a type safe manner is also required.
interface Information { text: string value: number } // can I improve this? interface Metrics { text: string metrics: | Record<ConfidentialityRequirement, Information> | Record<IntegrityRequirement, Information> } const foo: Record<EnvironmentalMetric, Metrics> = { [EnvironmentalMetric.ConfidentialityRequirement]: { text: 'Confidentiality Requirement', metrics: { [ConfidentialityRequirement.NotDefined]: { text: 'Not Defined', value: 1, }, [ConfidentialityRequirement.Low]: { text: 'Low', value: 1 }, [ConfidentialityRequirement.Medium]: { text: 'Medium', value: 1 }, [ConfidentialityRequirement.High]: { text: 'High', value: 1 }, }, }, [EnvironmentalMetric.IntegrityRequirement]: { text: 'Integrity Requirement', metrics: { [IntegrityRequirement.NotDefined]: { text: 'Not Defined', value: 1 }, [IntegrityRequirement.Low]: { text: 'Low', value: 1 }, [IntegrityRequirement.Medium]: { text: 'Medium', value: 1 }, [IntegrityRequirement.High]: { text: 'High', value: 1 }, } } } // this should have type "Record<ConfidentialityRequirement, Information>" // but has "Record<ConfidentialityRequirement, Information> | Record<IntegrityRequirement, Information>" const a = better.CR.metrics // this should have type "Record<IntegrityRequirement, Information>" // but has "Record<ConfidentialityRequirement, Information> | Record<IntegrityRequirement, Information>" const b = better.IR.metrics My questions are:
- Can I improve my
Metricsinterface so I don't have to use the union type? - Can I somehow make the "getters" type safe? So can I somehow say "whenever I get the value at key
EnvironmentalMetric.ConfidentialityRequirementTypeScript knows it is of typeRecord<ConfidentialityRequirement, Information>".
I also tried using native Map, which works, but accessing values is quite annoying because every value can be undefined.
Here is a link to the playground.
Any ideas? Thank you very much!