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

APP-14990 Refactor to make registering AJV schemas and formats more modular #197

Merged
merged 5 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ and this project adheres to

## [Unreleased]

## 0.56.0 - 2024-04-04

### Added

- Modular AJV instance construction with `registerSchemas` and
`registerFormats`.

## 0.55.0 - 2024-03-26

### Added
Expand Down
6 changes: 6 additions & 0 deletions external/resolvedSchemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -9506,10 +9506,16 @@
"description": "A listing of all IP addresses associated with this Host",
"anyOf": [
{ "type": "string", "format": "ip" },
{ "type": "string", "format": "ipCidr" },
{
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "format": "ip" }
},
{
"type": "array",
"uniqueItems": true,
"items": { "type": "string", "format": "ipCidr" }
}
]
},
Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module.exports = {
testEnvironment: 'node',
collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts', '!src/index.ts'],
coveragePathIgnorePatterns: ['src/registerSchemas'],
coverageThreshold: {
global: {
statements: 100,
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@jupiterone/data-model",
"version": "0.55.0",
"version": "0.56.0",
"description": "Automatically generated package.json, please edit manually",
"repository": {
"type": "git",
Expand All @@ -18,7 +18,7 @@
"scripts": {
"prebuild": "rm -rf dist/*",
"build": "yarn tsc --project tsconfig.dist.json --declaration",
"generate-schema-imports": "./tools/generate-schema-imports.sh",
"generate-schema-imports": "./tools/generate-schema-imports.sh && ./tools/generate-register-schemas-function.sh",
"generate-resolved-schemas": "node tools/generate-resolved-schemas/index.js src/schemas external/resolvedSchemas.json && prettier --write external/resolvedSchemas.json",
"test": "jest",
"lint": "eslint src --ext .ts,.js",
Expand Down
14 changes: 2 additions & 12 deletions src/IntegrationSchema.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import Ajv from 'ajv';
import addFormats from 'ajv-formats';

const ipv4 = addFormats.get('ipv4') as RegExp;
const ipv6 = addFormats.get('ipv6') as RegExp;
const ipv4CidrRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|[12]?[0-9])$/;
const ipv6CidrRegex = /^([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$|^([0-9a-fA-F]{1,4}:){1,7}:\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$|^::\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$|^([0-9a-fA-F]{1,4}:){1,7}[0-9a-fA-F]{1,4}$/;
import { registerFormats } from './registerFormats';

// JSON Schema allows an object to contain properties that are not specified by
// the schema. This can be disabled with `additionalProperties: false`. Ajv then
Expand All @@ -24,14 +19,9 @@ const ipv6CidrRegex = /^([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}\/(12[0-8]|1[01]
export const IntegrationSchema = new Ajv({
// Ignore "excludes", "multiple"; used in JupiterOne UI?
strictSchema: false,
formats: {
ip: (x) => ipv4.test(x) || ipv6.test(x),
ipCidr: (x) => ipv4CidrRegex.test(x) || ipv6CidrRegex.test(x),
},
});

// Install ajv-formats
addFormats(IntegrationSchema);
registerFormats(IntegrationSchema);

// Schema Imports : generated by tools/generate-schema-imports.sh
import WorkloadJson from './schemas/Workload.json';
Copy link
Contributor Author

@eyadmba eyadmba Apr 4, 2024

Choose a reason for hiding this comment

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

I could have replaced all the code from this point until the end of the file with just a simple call to the new function: registerSchemas(IntegrationSchema), but I noticed that there are exposed members from this module in those lines that I didn't know if they're being used elsewhere or not, so I had to leave them.

Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export { registerSchemas } from './registerSchemas';
export { registerFormats } from './registerFormats';

export { EVERYONE, INTERNET } from './globalEntities';

export { RelationshipClass } from './RelationshipClass';
Expand Down
24 changes: 24 additions & 0 deletions src/registerFormats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import addFormats from 'ajv-formats';
import Ajv from 'ajv';

const ipv4 = addFormats.get('ipv4') as RegExp;
const ipv6 = addFormats.get('ipv6') as RegExp;
const ipv4CidrRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|[12]?[0-9])$/;
const ipv6CidrRegex = /^([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$|^([0-9a-fA-F]{1,4}:){1,7}:\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$|^::\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$|^([0-9a-fA-F]{1,4}:){1,7}[0-9a-fA-F]{1,4}$/;

const isValidIpFormat = {
name: 'ip',
fn: (x) => ipv4.test(x) || ipv6.test(x),
};

const isValidIpCidrFormat = {
name: 'ipCidr',
fn: (x) => ipv4CidrRegex.test(x) || ipv6CidrRegex.test(x),
};

export function registerFormats(ajvInstance: Ajv): void {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Similar to registerSchemas, here's registerFormats().

ajvInstance.addFormat(isValidIpFormat.name, isValidIpFormat.fn);
ajvInstance.addFormat(isValidIpCidrFormat.name, isValidIpCidrFormat.fn);

addFormats(ajvInstance);
}
207 changes: 207 additions & 0 deletions src/registerSchemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// THIS FILE IS AUTOMATICALLY GENERATED!
// using the script tools/generate-register-schemas-function.sh

import Ajv from 'ajv';

// Dynamically imported schemas
import AccessKeyJson from './schemas/AccessKey.json';
import AccessPolicyJson from './schemas/AccessPolicy.json';
import AccessRoleJson from './schemas/AccessRole.json';
import AccountJson from './schemas/Account.json';
import AlertJson from './schemas/Alert.json';
import ApplicationJson from './schemas/Application.json';
import ApplicationEndpointJson from './schemas/ApplicationEndpoint.json';
import AssessmentJson from './schemas/Assessment.json';
import AttackerJson from './schemas/Attacker.json';
import BackupJson from './schemas/Backup.json';
import CertificateJson from './schemas/Certificate.json';
import ChannelJson from './schemas/Channel.json';
import ClusterJson from './schemas/Cluster.json';
import CodeCommitJson from './schemas/CodeCommit.json';
import CodeDeployJson from './schemas/CodeDeploy.json';
import CodeModuleJson from './schemas/CodeModule.json';
import CodeRepoJson from './schemas/CodeRepo.json';
import CodeReviewJson from './schemas/CodeReview.json';
import ConfigurationJson from './schemas/Configuration.json';
import ContainerJson from './schemas/Container.json';
import ControlJson from './schemas/Control.json';
import ControlPolicyJson from './schemas/ControlPolicy.json';
import CryptoKeyJson from './schemas/CryptoKey.json';
import DataCollectionJson from './schemas/DataCollection.json';
import DataObjectJson from './schemas/DataObject.json';
import DataStoreJson from './schemas/DataStore.json';
import DatabaseJson from './schemas/Database.json';
import DeploymentJson from './schemas/Deployment.json';
import DeviceJson from './schemas/Device.json';
import DirectoryJson from './schemas/Directory.json';
import DiskJson from './schemas/Disk.json';
import DocumentJson from './schemas/Document.json';
import DomainJson from './schemas/Domain.json';
import DomainRecordJson from './schemas/DomainRecord.json';
import DomainZoneJson from './schemas/DomainZone.json';
import EntityJson from './schemas/Entity.json';
import FindingJson from './schemas/Finding.json';
import FirewallJson from './schemas/Firewall.json';
import FrameworkJson from './schemas/Framework.json';
import FunctionJson from './schemas/Function.json';
import GatewayJson from './schemas/Gateway.json';
import GraphObjectJson from './schemas/GraphObject.json';
import GroupJson from './schemas/Group.json';
import HostJson from './schemas/Host.json';
import HostAgentJson from './schemas/HostAgent.json';
import ImageJson from './schemas/Image.json';
import IncidentJson from './schemas/Incident.json';
import InternetJson from './schemas/Internet.json';
import IpAddressJson from './schemas/IpAddress.json';
import IssueJson from './schemas/Issue.json';
import KeyJson from './schemas/Key.json';
import LogsJson from './schemas/Logs.json';
import ModelJson from './schemas/Model.json';
import ModuleJson from './schemas/Module.json';
import NetworkJson from './schemas/Network.json';
import NetworkEndpointJson from './schemas/NetworkEndpoint.json';
import NetworkInterfaceJson from './schemas/NetworkInterface.json';
import OrganizationJson from './schemas/Organization.json';
import PRJson from './schemas/PR.json';
import PasswordPolicyJson from './schemas/PasswordPolicy.json';
import PersonJson from './schemas/Person.json';
import PolicyJson from './schemas/Policy.json';
import ProblemJson from './schemas/Problem.json';
import ProcedureJson from './schemas/Procedure.json';
import ProcessJson from './schemas/Process.json';
import ProductJson from './schemas/Product.json';
import ProgramJson from './schemas/Program.json';
import ProjectJson from './schemas/Project.json';
import QuestionJson from './schemas/Question.json';
import QueueJson from './schemas/Queue.json';
import RecordJson from './schemas/Record.json';
import RecordEntityJson from './schemas/RecordEntity.json';
import RepositoryJson from './schemas/Repository.json';
import RequirementJson from './schemas/Requirement.json';
import ResourceJson from './schemas/Resource.json';
import ReviewJson from './schemas/Review.json';
import RiskJson from './schemas/Risk.json';
import RootJson from './schemas/Root.json';
import RuleJson from './schemas/Rule.json';
import RulesetJson from './schemas/Ruleset.json';
import ScannerJson from './schemas/Scanner.json';
import SecretJson from './schemas/Secret.json';
import SectionJson from './schemas/Section.json';
import ServiceJson from './schemas/Service.json';
import SiteJson from './schemas/Site.json';
import StandardJson from './schemas/Standard.json';
import SubscriptionJson from './schemas/Subscription.json';
import TaskJson from './schemas/Task.json';
import TeamJson from './schemas/Team.json';
import ThreatIntelJson from './schemas/ThreatIntel.json';
import TrainingJson from './schemas/Training.json';
import UserJson from './schemas/User.json';
import UserGroupJson from './schemas/UserGroup.json';
import VaultJson from './schemas/Vault.json';
import VendorJson from './schemas/Vendor.json';
import VulnerabilityJson from './schemas/Vulnerability.json';
import WeaknessJson from './schemas/Weakness.json';
import WorkflowJson from './schemas/Workflow.json';
import WorkloadJson from './schemas/Workload.json';

export function registerSchemas(ajvInstance: Ajv): void {
Copy link
Contributor Author

@eyadmba eyadmba Apr 4, 2024

Choose a reason for hiding this comment

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

As explained in the PR's description, this is the function that takes in an AJV instance and adds all the data model's schemas to it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Similar to src/IntegrationSchema.ts, this file gets auto generated entirely using the script tools/generate-register-schemas-function.sh.

ajvInstance.addSchema(AccessKeyJson);
ajvInstance.addSchema(AccessPolicyJson);
ajvInstance.addSchema(AccessRoleJson);
ajvInstance.addSchema(AccountJson);
ajvInstance.addSchema(AlertJson);
ajvInstance.addSchema(ApplicationJson);
ajvInstance.addSchema(ApplicationEndpointJson);
ajvInstance.addSchema(AssessmentJson);
ajvInstance.addSchema(AttackerJson);
ajvInstance.addSchema(BackupJson);
ajvInstance.addSchema(CertificateJson);
ajvInstance.addSchema(ChannelJson);
ajvInstance.addSchema(ClusterJson);
ajvInstance.addSchema(CodeCommitJson);
ajvInstance.addSchema(CodeDeployJson);
ajvInstance.addSchema(CodeModuleJson);
ajvInstance.addSchema(CodeRepoJson);
ajvInstance.addSchema(CodeReviewJson);
ajvInstance.addSchema(ConfigurationJson);
ajvInstance.addSchema(ContainerJson);
ajvInstance.addSchema(ControlJson);
ajvInstance.addSchema(ControlPolicyJson);
ajvInstance.addSchema(CryptoKeyJson);
ajvInstance.addSchema(DataCollectionJson);
ajvInstance.addSchema(DataObjectJson);
ajvInstance.addSchema(DataStoreJson);
ajvInstance.addSchema(DatabaseJson);
ajvInstance.addSchema(DeploymentJson);
ajvInstance.addSchema(DeviceJson);
ajvInstance.addSchema(DirectoryJson);
ajvInstance.addSchema(DiskJson);
ajvInstance.addSchema(DocumentJson);
ajvInstance.addSchema(DomainJson);
ajvInstance.addSchema(DomainRecordJson);
ajvInstance.addSchema(DomainZoneJson);
ajvInstance.addSchema(EntityJson);
ajvInstance.addSchema(FindingJson);
ajvInstance.addSchema(FirewallJson);
ajvInstance.addSchema(FrameworkJson);
ajvInstance.addSchema(FunctionJson);
ajvInstance.addSchema(GatewayJson);
ajvInstance.addSchema(GraphObjectJson);
ajvInstance.addSchema(GroupJson);
ajvInstance.addSchema(HostJson);
ajvInstance.addSchema(HostAgentJson);
ajvInstance.addSchema(ImageJson);
ajvInstance.addSchema(IncidentJson);
ajvInstance.addSchema(InternetJson);
ajvInstance.addSchema(IpAddressJson);
ajvInstance.addSchema(IssueJson);
ajvInstance.addSchema(KeyJson);
ajvInstance.addSchema(LogsJson);
ajvInstance.addSchema(ModelJson);
ajvInstance.addSchema(ModuleJson);
ajvInstance.addSchema(NetworkJson);
ajvInstance.addSchema(NetworkEndpointJson);
ajvInstance.addSchema(NetworkInterfaceJson);
ajvInstance.addSchema(OrganizationJson);
ajvInstance.addSchema(PRJson);
ajvInstance.addSchema(PasswordPolicyJson);
ajvInstance.addSchema(PersonJson);
ajvInstance.addSchema(PolicyJson);
ajvInstance.addSchema(ProblemJson);
ajvInstance.addSchema(ProcedureJson);
ajvInstance.addSchema(ProcessJson);
ajvInstance.addSchema(ProductJson);
ajvInstance.addSchema(ProgramJson);
ajvInstance.addSchema(ProjectJson);
ajvInstance.addSchema(QuestionJson);
ajvInstance.addSchema(QueueJson);
ajvInstance.addSchema(RecordJson);
ajvInstance.addSchema(RecordEntityJson);
ajvInstance.addSchema(RepositoryJson);
ajvInstance.addSchema(RequirementJson);
ajvInstance.addSchema(ResourceJson);
ajvInstance.addSchema(ReviewJson);
ajvInstance.addSchema(RiskJson);
ajvInstance.addSchema(RootJson);
ajvInstance.addSchema(RuleJson);
ajvInstance.addSchema(RulesetJson);
ajvInstance.addSchema(ScannerJson);
ajvInstance.addSchema(SecretJson);
ajvInstance.addSchema(SectionJson);
ajvInstance.addSchema(ServiceJson);
ajvInstance.addSchema(SiteJson);
ajvInstance.addSchema(StandardJson);
ajvInstance.addSchema(SubscriptionJson);
ajvInstance.addSchema(TaskJson);
ajvInstance.addSchema(TeamJson);
ajvInstance.addSchema(ThreatIntelJson);
ajvInstance.addSchema(TrainingJson);
ajvInstance.addSchema(UserJson);
ajvInstance.addSchema(UserGroupJson);
ajvInstance.addSchema(VaultJson);
ajvInstance.addSchema(VendorJson);
ajvInstance.addSchema(VulnerabilityJson);
ajvInstance.addSchema(WeaknessJson);
ajvInstance.addSchema(WorkflowJson);
ajvInstance.addSchema(WorkloadJson);
}
48 changes: 48 additions & 0 deletions tools/generate-register-schemas-function.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/bin/bash

# absolute path to where this script is located
here=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )

cd $here;

# Define the path to the directory containing the JSON schemas
schemaDir="../src/schemas"

# Define the output TypeScript file
outputFile="../src/registerSchemas.ts"

# Write the fixed part of the template to the outputFile
echo "// THIS FILE IS AUTOMATICALLY GENERATED!" > "$outputFile"
echo "// using the script tools/generate-register-schemas-function.sh" >> "$outputFile"
echo "" >> "$outputFile"
echo "import Ajv from 'ajv';" >> "$outputFile"
echo "" >> "$outputFile"

# Dynamically generate import statements and ajvInstance.addSchema calls
importStatements=""
schemaCalls=""

# Loop over the JSON files in the schema directory without changing the current directory
for file in "$schemaDir"/*.json; do
# Extract just the filename from the path
filename=$(basename -- "$file")

# Remove the file extension to get the schema name
schemaName=$(echo "$filename" | sed 's/.json$//')

# Create the import statement
importStatements+="import ${schemaName}Json from './schemas/${filename}';"$'\n'

# Create the ajvInstance.addSchema call
schemaCalls+=" ajvInstance.addSchema(${schemaName}Json);"$'\n'
done

# trim the trailing new line
schemaCalls=$(echo "$schemaCalls" | sed '$d')

# Write the dynamic parts to the outputFile
echo "// Dynamically imported schemas" >> "$outputFile"
echo "$importStatements" >> "$outputFile"
echo "export function registerSchemas(ajvInstance: Ajv): void {" >> "$outputFile"
echo "$schemaCalls" >> "$outputFile"
echo "}" >> "$outputFile"
Loading