Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New centralised model for CALM (#790) #902

Merged
merged 1 commit into from
Feb 17, 2025

Conversation

LeighFinegold
Copy link
Member

@LeighFinegold LeighFinegold commented Feb 14, 2025

🔍 Summary

This PR introduces a centralized and structured data model for CALM. Per discussion on #899, The new model provides well-defined TypeScript classes and enums for core CALM components, ensuring seamless integration and validation for cli and visualizer componenets.

✨ Key Changes

  • Added types that match the current schema files.
  • Introduced CalmCore as the centralized entry point for nodes, relationships, metadata, controls, and flows.
  • Standardized structure for CalmNode, CalmRelationship, CalmFlow, CalmControl, and CalmMetadata.
  • Each model includes a fromJson() factory method to enable seamless conversion from JSON.
  • Ensures consistent handling of nested objects, such as relationships and interfaces.
  • Added unit tests for all models to ensure stability when we start to amend them to support potential new concepts like visitor pattern etc.

❌ Out of Scope for this PR

Observations around CALM Specification

  • We should look to move out metadata into separate json file as definition duplicated in flows.json
  • This PR corrects a spelling mistake on data-asset node-type
  • The current core.json doesn't set any requirements on any of the attributes e.g. nodes, relationships. Should it also specify type: object as part of the specification. (Note: generators have required that to be defined)
  • Should core.json actually have a collection of flowUrls instead of collection of flows? In the same way control detail has a requirementUrl which we expected to be linked to control-requirement.json

@@ -0,0 +1,17 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, it would be good to not have this linting suppression. What if we use unknown on line 10 instead of any.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes - that was a topic wanted to raise but forgot. In the end I went with status quo in other classes so let's reiterate this on next meet-up. Will go with unknown

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed this further with @aidanm3341 further and we agreed it would have been better to go with object than unknown. However, on further thought, I decided that actually it might be better to define types in the schema structures after all and have added this on latest commit.

expect(control.name).toBe('JSON Control');
expect(control.description).toBe('Description from JSON');
});
});
Copy link
Member

@aidanm3341 aidanm3341 Feb 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reckon these data classes be responsible for basic validation that the fields in the input data exist before using them. E.g. if name was not provided, an error should be thrown here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think its a good question - certainly on #790 we discussed the generator and visualizer would assume the document is validated already. The quesiton is how will the validator work with the model

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as we're consistent either way, e.g. I noticed that in the flow model you are doing some basic validations "// Ensure value is an object". If we're assuming that the input is valid, then we don't need that validation check there

public dataClassification?: CalmDataClassification,
public runAs?: string,
public instance?: string,
public interfaces?: CalmInterface[],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to make using these model objects a little nicer to use, we could get rid of the optional aspect of these lists and instead default them to an empty list. That would mean that any client code using these models doesn't have to worry about checking for undefined before iterating over it.

public interfaces: CalmInterface[] = []

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah thinkng about it, the optionality I put in because it matched the json-schema btu given we are parsing I like the idea of avoiding such checks.

export class CalmNodeDetails {
'detailed-architecture'?: string;
'required-pattern'?: string;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also this class doesn't have a constructor like the others? Not sure if I'm missing something with this particular class

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this class clearly got neglected :D

@LeighFinegold LeighFinegold force-pushed the template_manual_model branch 2 times, most recently from 0c4d0cf to 94e0ad9 Compare February 16, 2025 01:00
relationships?: CalmRelationshipSchema[];
metadata?: CalmMetadataSchema;
controls?: CalmControlsSchema;
flows?: CalmFlowSchema[];
Copy link
Member Author

@LeighFinegold LeighFinegold Feb 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I considered was stating that this could be an array of CalmFlowSchema[] | String[]. For now I kept it as this, but it requires the calm doc to be de-referenced before parsing which left as a TODO as requires @willosborne PR work on SchemaDirectory.

const logger = CalmParser.logger;
try {
const data = fs.readFileSync(coreCalmFilePath, 'utf8');
const dereferencedData: CalmCoreSchema = JSON.parse(data); // TODO: this needs to use SchemaDirectory to parse the other documents e.g. flows.
Copy link
Member Author

@LeighFinegold LeighFinegold Feb 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this needs to block the PR to get the models in, but we really do need to ensure that the document is dereferenced prior to trying to parse into a model. If we decide we don't want to do that then likely CalmCore.fromJson doesn't get called. I think the individual Calm method fromJson e.g. CalmFlow.fromJson would though.

A similar thing applies to the fact that a CalmControlDetail has reference to a requirementUrl but there is no official linkage to load the CalmRequirement. Added that to description of PR to consider CALM change to have a linkage of flowUrls.

//TODO: There is no required section.
export type CalmCoreSchema = {
nodes?: CalmNodeSchema[];
relationships?: CalmRelationshipSchema[];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aren't nodes and relationships required?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not atm on core.json which is why I put that an observation on this PR description

constructor(public uniqueId: string) {}

static fromJson(data: CalmInterfaceTypeSchema): CalmInterface {
if ('host' in data && 'port' in data) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bit is the only part I'm a little iffy about. Happy to proceed with this approach for now but we might need some more thought on this later


static fromJson(data: CalmMetadataSchema): CalmMetadata {
const flattenedData = data.reduce((acc, curr) => {
return { ...acc, ...curr };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nifty.

constructor(public data: Record<string, unknown>) {}

static fromJson(data: CalmMetadataSchema): CalmMetadata {
const flattenedData = data.reduce((acc, curr) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this flatten only the top level? We probably don't want it to recursively flatten all the nested objects

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be only first level, but can add a test case to confirm on subsequent commit.

@LeighFinegold LeighFinegold merged commit 9db5c1c into finos:main Feb 17, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants