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
Metrics
interface 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.ConfidentialityRequirement
TypeScript 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!
CodePudding user response:
We can using a mapped type an a map of one enum to the other:
type MetricToRequirement = {
[EnvironmentalMetric.ConfidentialityRequirement]: ConfidentialityRequirement;
[EnvironmentalMetric.IntegrityRequirement]: IntegrityRequirement;
};
type Better = {
[K in EnvironmentalMetric]: {
text: string;
metrics: Record<MetricToRequirement[K], Information>;
};
};
You can liken this to a piece of (psuedo) code if it helps you understand:
let MetricToRequirement = {
[EnvironmentalMetric.ConfidentialityRequirement]: ConfidentialityRequirement;
[EnvironmentalMetric.IntegrityRequirement]: IntegrityRequirement;
};
let obj = {};
for each member of EnvironmentalMetric:
obj[K] = {
text: string;
metrics: Record<MetricToRequirement[member], Information>;
}