Skip to content

Commit

Permalink
patch(feat): octo | support for @Validate() decorator.
Browse files Browse the repository at this point in the history
  • Loading branch information
rash805115 committed Aug 8, 2024
1 parent 3682a5f commit c99868c
Show file tree
Hide file tree
Showing 23 changed files with 300 additions and 3 deletions.
1 change: 1 addition & 0 deletions dictionary.dic
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ unsynth
uri
usr
utf
validators
vetices
vpc
workspace
Expand Down
6 changes: 5 additions & 1 deletion packages/octo-aws-cdk/src/models/region/aws.region.model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Container, Model, OverlayService, Region } from '@quadnix/octo';
import { Container, Model, OverlayService, Region, Validate } from '@quadnix/octo';
import { RegionFilesystemAnchor } from '../../anchors/region-filesystem.anchor.js';
import { RegionFilesystemOverlay } from '../../overlays/region-filesystem/region-filesystem.overlay.js';
import type { IAwsRegion } from './aws.region.interface.js';
Expand Down Expand Up @@ -27,6 +27,10 @@ export class AwsRegion extends Region {

readonly awsRegionId: string;

@Validate({
destruct: (value: { filesystemName: string }[]): string[] => value.map((v) => v.filesystemName),
options: { maxLength: 32, minLength: 2, regex: /^[a-zA-Z][\w-]*[a-zA-Z0-9]$/ },
})
readonly filesystems: { filesystemAnchorName: string; filesystemName: string }[] = [];

override readonly regionId: RegionId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Container, Diff, DiffAction, Model, Service, StateManagementService } from '@quadnix/octo';
import { Container, Diff, DiffAction, Model, Service, StateManagementService, Validate } from '@quadnix/octo';
import { lstat, readdir } from 'fs/promises';
import { join, parse, resolve } from 'path';
import { FileUtility } from '../../../utilities/file/file.utility.js';
Expand All @@ -11,6 +11,7 @@ type IManifest = { [key: string]: { algorithm: 'sha1'; digest: string | 'deleted
export class S3StaticWebsiteService extends Service {
readonly awsRegionId: string;

@Validate({ options: { maxLength: 128, minLength: 2, regex: /^[a-zA-Z0-9][\w.-]*[a-zA-Z0-9]$/ } })
readonly bucketName: string;

readonly excludePaths: { directoryPath: string; subDirectoryOrFilePath: string }[] = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Container, Model, OverlayService, Service } from '@quadnix/octo';
import { Container, Model, OverlayService, Service, Validate } from '@quadnix/octo';
import { IamRoleAnchor } from '../../../anchors/iam-role.anchor.js';
import { S3DirectoryAnchor } from '../../../anchors/s3-directory.anchor.js';
import { CommonUtility } from '../../../utilities/common/common.utility.js';
Expand All @@ -17,6 +17,7 @@ export enum S3StorageAccess {
export class S3StorageService extends Service {
readonly awsRegionId: string;

@Validate({ options: { maxLength: 128, minLength: 2, regex: /^[a-zA-Z0-9][\w.-]*[a-zA-Z0-9]$/ } })
readonly bucketName: string;

readonly directories: { directoryAnchorName: string; remoteDirectoryPath: string }[] = [];
Expand Down
6 changes: 6 additions & 0 deletions packages/octo/src/app.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ export enum ModelType {
SHARED_RESOURCE = 'shared-resource',
}

export enum ValidationType {
MAX_LENGTH = 'maxLength',
MIN_LENGTH = 'minLength',
REGEX = 'regex',
}

export type ActionInputs = { [key: string]: string | UnknownResource };

export type ActionOutputs = { [key: string]: UnknownResource };
Expand Down
41 changes: 41 additions & 0 deletions packages/octo/src/decorators/validate.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { ValidationType } from '../app.type.js';
import { ValidationService } from '../services/validation/validation.service.js';

type ValidationOptions<T> = {
options: { [key in ValidationType]?: any };
destruct?: (value: any) => T[];
};

export function Validate<T>(
validators: ValidationOptions<T> | ValidationOptions<T>[],
): (target: any, propertyKey: string) => void {
return function (target: any, propertyKey: string) {
const symbol = Symbol();

Object.defineProperty(target, propertyKey, {
get: function () {
return this[symbol];
},
set: function (newValue: any) {
if (!Array.isArray(validators)) {
validators = [validators];
}

for (const validator of validators) {
for (const [type, constraint] of Object.entries(validator.options)) {
ValidationService.getInstance().addSubject(
type as ValidationType,
constraint,
target,
propertyKey,
newValue,
validator.destruct,
);
}
}

this[symbol] = newValue;
},
});
};
}
1 change: 1 addition & 0 deletions packages/octo/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { OnEvent } from './decorators/on-event.decorator.js';
export { Overlay } from './decorators/overlay.decorator.js';
export { Resource } from './decorators/resource.decorator.js';
export { IPackageMock, TestContainer } from './decorators/test-container.js';
export { Validate } from './decorators/validate.decorator.js';

export { DependencyRelationship } from './functions/dependency/dependency.js';
export { Diff, DiffAction } from './functions/diff/diff.js';
Expand Down
9 changes: 9 additions & 0 deletions packages/octo/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ResourceSerializationService } from './services/serialization/resource/
import { StateManagementService } from './services/state-management/state-management.service.js';
import { IStateProvider } from './services/state-management/state-provider.interface.js';
import { TransactionService } from './services/transaction/transaction.service.js';
import { ValidationService } from './services/validation/validation.service.js';

export class Octo {
private readonly modelStateFileName: string = 'models.json';
Expand All @@ -30,6 +31,7 @@ export class Octo {
private resourceSerializationService: ResourceSerializationService;
private stateManagementService: StateManagementService;
private transactionService: TransactionService;
private validationService: ValidationService;

async beginTransaction(
app: App,
Expand All @@ -55,6 +57,11 @@ export class Octo {

async compose(): Promise<void> {
await this.moduleContainer.apply();

const result = this.validationService.validate();
if (!result.pass) {
throw new Error('Validation error!');
}
}

getAllResources(): UnknownResource[] {
Expand Down Expand Up @@ -84,6 +91,7 @@ export class Octo {
this.resourceSerializationService,
this.stateManagementService,
this.transactionService,
this.validationService,
] = await Promise.all([
Container.get(CaptureService),
Container.get(InputService),
Expand All @@ -93,6 +101,7 @@ export class Octo {
Container.get(ResourceSerializationService),
Container.get(StateManagementService, { args: [stateProvider] }),
Container.get(TransactionService),
Container.get(ValidationService),
]);

for (const exclude of excludeInContainer) {
Expand Down
2 changes: 2 additions & 0 deletions packages/octo/src/models/app/app.model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { UnknownModel } from '../../app.type.js';
import { Model } from '../../decorators/model.decorator.js';
import { Validate } from '../../decorators/validate.decorator.js';
import type { Diff } from '../../functions/diff/diff.js';
import { Image } from '../image/image.model.js';
import { AModel } from '../model.abstract.js';
Expand Down Expand Up @@ -27,6 +28,7 @@ export class App extends AModel<IApp, App> {
/**
* The name of the app.
*/
@Validate({ options: { maxLength: 64, minLength: 2, regex: /^[a-zA-Z][\w-]*[a-zA-Z0-9]$/ } })
readonly name: string;

constructor(name: string) {
Expand Down
2 changes: 2 additions & 0 deletions packages/octo/src/models/deployment/deployment.model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { UnknownModel } from '../../app.type.js';
import { Model } from '../../decorators/model.decorator.js';
import { Validate } from '../../decorators/validate.decorator.js';
import type { Diff } from '../../functions/diff/diff.js';
import { AModel } from '../model.abstract.js';
import type { IDeployment } from './deployment.interface.js';
Expand All @@ -23,6 +24,7 @@ export class Deployment extends AModel<IDeployment, Deployment> {
* The identifying tag that can point to the server's code at a specific point in time.
* Could be a version number or a commit hash.
*/
@Validate({ options: { maxLength: 32, minLength: 2, regex: /^[a-zA-Z0-9][\w.-]*[a-zA-Z0-9]$/ } })
readonly deploymentTag: string;

constructor(deploymentTag: string) {
Expand Down
8 changes: 8 additions & 0 deletions packages/octo/src/models/environment/environment.model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { UnknownModel } from '../../app.type.js';
import { Model } from '../../decorators/model.decorator.js';
import { Validate } from '../../decorators/validate.decorator.js';
import { DiffUtility } from '../../functions/diff/diff.utility.js';
import type { Diff } from '../../functions/diff/diff.js';
import { AModel } from '../model.abstract.js';
Expand All @@ -24,11 +25,18 @@ export class Environment extends AModel<IEnvironment, Environment> {
* The name of the environment.
* An environment must be unique within a Region. But multiple Regions can share the same environment name.
*/
@Validate({ options: { maxLength: 32, minLength: 2, regex: /^[a-zA-Z][\w-]*[a-zA-Z0-9]$/ } })
readonly environmentName: string;

/**
* A set of environment variables to be passed to any {@link Execution} running in this environment.
*/
@Validate({
destruct: (value: Map<string, string>): string[] => {
return Array.from(value.keys());
},
options: { maxLength: 64, minLength: 2, regex: /^[a-zA-Z][\w-]*[a-zA-Z0-9]$/ },
})
readonly environmentVariables: Map<string, string> = new Map();

constructor(environmentName: string) {
Expand Down
7 changes: 7 additions & 0 deletions packages/octo/src/models/execution/execution.model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { UnknownModel } from '../../app.type.js';
import { Model } from '../../decorators/model.decorator.js';
import { Validate } from '../../decorators/validate.decorator.js';
import { DiffUtility } from '../../functions/diff/diff.utility.js';
import type { Diff } from '../../functions/diff/diff.js';
import { ArrayUtility } from '../../utilities/array/array.utility.js';
Expand Down Expand Up @@ -34,6 +35,12 @@ export class Execution extends AModel<IExecution, Execution> {
* It represents setting the [--env](https://docs.docker.com/compose/environment-variables/set-environment-variables/)
* option while running a Docker container.
*/
@Validate({
destruct: (value: Map<string, string>): string[] => {
return Array.from(value.keys());
},
options: { maxLength: 64, minLength: 2, regex: /^[a-zA-Z][\w-]*[a-zA-Z0-9]$/ },
})
readonly environmentVariables: Map<string, string> = new Map();

constructor(deployment: Deployment, environment: Environment, subnet: Subnet) {
Expand Down
3 changes: 3 additions & 0 deletions packages/octo/src/models/image/image.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { resolve } from 'path';
import type { UnknownModel } from '../../app.type.js';
import { Model } from '../../decorators/model.decorator.js';
import { Validate } from '../../decorators/validate.decorator.js';
import type { Diff } from '../../functions/diff/diff.js';
import { AModel } from '../model.abstract.js';
import type { IImage } from './image.interface.js';
Expand Down Expand Up @@ -52,12 +53,14 @@ export class Image extends AModel<IImage, Image> {
/**
* The name of the image, same as [namespace in Docker](https://docs.docker.com/reference/cli/docker/image/tag/).
*/
@Validate({ options: { maxLength: 32, minLength: 2, regex: /^[a-zA-Z@][\w\-\/]*[a-zA-Z0-9]$/ } })
readonly imageName: string;

/**
* The tag of the image, same as [tag in Docker](https://docs.docker.com/reference/cli/docker/image/tag/).
* - Unlike Docker, `imageTag` is mandatory in Octo.
*/
@Validate({ options: { maxLength: 32, minLength: 2, regex: /^[a-zA-Z0-9][\w.-]*[a-zA-Z0-9]$/ } })
readonly imageTag: string;

constructor(imageName: string, imageTag: string, options: IImageDockerOptions) {
Expand Down
2 changes: 2 additions & 0 deletions packages/octo/src/models/pipeline/pipeline.model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { UnknownModel } from '../../app.type.js';
import { Model } from '../../decorators/model.decorator.js';
import { Validate } from '../../decorators/validate.decorator.js';
import { DiffUtility } from '../../functions/diff/diff.utility.js';
import { AModel } from '../model.abstract.js';
import type { Diff } from '../../functions/diff/diff.js';
Expand All @@ -26,6 +27,7 @@ export class Pipeline extends AModel<IPipeline, Pipeline> {

readonly instructionSet: string[] = [];

@Validate({ options: { maxLength: 64, minLength: 2, regex: /^[a-zA-Z][\w-]*[a-zA-Z0-9]$/ } })
readonly pipelineName: string;

constructor(pipelineName: string) {
Expand Down
2 changes: 2 additions & 0 deletions packages/octo/src/models/region/region.model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { UnknownModel } from '../../app.type.js';
import { Model } from '../../decorators/model.decorator.js';
import { Validate } from '../../decorators/validate.decorator.js';
import type { Diff } from '../../functions/diff/diff.js';
import { Environment } from '../environment/environment.model.js';
import { AModel } from '../model.abstract.js';
Expand All @@ -22,6 +23,7 @@ import type { IRegion } from './region.interface.js';
export class Region extends AModel<IRegion, Region> {
readonly MODEL_NAME: string = 'region';

@Validate({ options: { maxLength: 32, minLength: 2, regex: /^[a-zA-Z][\w-]*[a-zA-Z0-9]$/ } })
readonly regionId: string;

constructor(regionId: string) {
Expand Down
2 changes: 2 additions & 0 deletions packages/octo/src/models/server/server.model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { UnknownModel } from '../../app.type.js';
import { Model } from '../../decorators/model.decorator.js';
import { Validate } from '../../decorators/validate.decorator.js';
import type { Diff } from '../../functions/diff/diff.js';
import { Deployment } from '../deployment/deployment.model.js';
import { AModel } from '../model.abstract.js';
Expand All @@ -23,6 +24,7 @@ export class Server extends AModel<IServer, Server> {
/**
* The name of the server.
*/
@Validate({ options: { maxLength: 64, minLength: 2, regex: /^[a-zA-Z][\w-]*[a-zA-Z0-9]$/ } })
readonly serverKey: string;

constructor(serverKey: string) {
Expand Down
2 changes: 2 additions & 0 deletions packages/octo/src/models/service/service.model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Model } from '../../decorators/model.decorator.js';
import { Validate } from '../../decorators/validate.decorator.js';
import type { Diff } from '../../functions/diff/diff.js';
import { AModel } from '../model.abstract.js';
import type { IService } from './service.interface.js';
Expand All @@ -22,6 +23,7 @@ export class Service extends AModel<IService, Service> {
/**
* The ID of the service.
*/
@Validate({ options: { maxLength: 64, minLength: 2, regex: /^[a-zA-Z][\w-]*[a-zA-Z0-9]$/ } })
readonly serviceId: string;

constructor(serviceId: string) {
Expand Down
2 changes: 2 additions & 0 deletions packages/octo/src/models/subnet/subnet.model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { UnknownModel } from '../../app.type.js';
import { Model } from '../../decorators/model.decorator.js';
import { Validate } from '../../decorators/validate.decorator.js';
import { Diff, DiffAction } from '../../functions/diff/diff.js';
import { AModel } from '../model.abstract.js';
import { Region } from '../region/region.model.js';
Expand Down Expand Up @@ -53,6 +54,7 @@ export class Subnet extends AModel<ISubnet, Subnet> {
/**
* The name of the subnet.
*/
@Validate({ options: { maxLength: 32, minLength: 2, regex: /^[a-zA-Z][\w-]*[a-zA-Z0-9]$/ } })
readonly subnetName: string;

constructor(region: Region, name: string) {
Expand Down
Loading

0 comments on commit c99868c

Please sign in to comment.