Skip to content

Commit

Permalink
Merge pull request #8445 from amplication/feat/jovu-schema-validation
Browse files Browse the repository at this point in the history
feat(server):generate args types for assistant functions and validate args schema
  • Loading branch information
mulygottlieb committed May 12, 2024
2 parents 1188056 + 3360553 commit 8d0ded5
Show file tree
Hide file tree
Showing 43 changed files with 1,412 additions and 215 deletions.
350 changes: 213 additions & 137 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@
"jest-environment-jsdom": "^29.7.0",
"jest-environment-node": "^29.7.0",
"jest-mock-extended": "^3.0.5",
"json-schema-to-typescript": "^10.1.5",
"json-schema-to-typescript": "^14.0.4",
"jsonc-eslint-parser": "^2.3.0",
"lint-staged": "^13.0.3",
"npm-run-all": "^4.1.5",
Expand Down
8 changes: 8 additions & 0 deletions packages/amplication-server/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@
"cwd": "packages/amplication-server"
}
},
"prebuild": {
"executor": "nx:run-commands",
"outputs": ["{projectRoot}/src/core/assistant/functions/types"],
"options": {
"command": "ts-node ./scripts/generate-assistant-functions-types",
"cwd": "packages/amplication-server"
}
},
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import fs from "fs";
import * as path from "path";
import fg from "fast-glob";
import { compile } from "json-schema-to-typescript";
import normalize from "normalize-path";

const SRC_DIRECTORY = path.join(__dirname, "..", "src");
const SCHEMAS_DIRECTORY = path.join(
SRC_DIRECTORY,
"core",
"assistant",
"functions"
);
const TYPES_DIRECTORY = path.join(SCHEMAS_DIRECTORY, "types");

if (require.main === module) {
generateTypes().catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
process.exit(1);
});
}

async function generateTypes() {
const schemaGrep = normalize(path.join(SCHEMAS_DIRECTORY, "**", "*.json"));
const schemaFiles = await fg(schemaGrep, {
objectMode: true,
});
if (schemaFiles.length === 0) {
throw new Error(`No schema files were found for ${schemaGrep}`);
}
await fs.promises.mkdir(TYPES_DIRECTORY, { recursive: true });
await Promise.all(
schemaFiles.map(async ({ name, path: filePath }) =>
generateTypeFile(filePath, name)
)
);
const code = schemaFiles
.map(({ name }) => `export * from "./${name.replace(".json", "")}"`)
.join("\n");
const indexPath = path.join(TYPES_DIRECTORY, "index.ts");
await fs.promises.writeFile(indexPath, code);
// eslint-disable-next-line no-console
console.info(
`Successfully written to ${path.relative(process.cwd(), TYPES_DIRECTORY)}`
);
}

async function generateTypeFile(filePath: string, name: string) {
//read file content as json and extract the parameters property
const value = await fs.promises.readFile(filePath, "utf-8");
const content = JSON.parse(value);

//generate the typescript code for the parameters property

const code = await compile(content.parameters, name.replace(".json", ""), {
additionalProperties: false,
});
const tsPath = path.join(TYPES_DIRECTORY, name.replace(".json", ".ts"));
await fs.promises.writeFile(tsPath, code);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ModuleActionModule } from "../moduleAction/moduleAction.module";
import { ModuleDtoModule } from "../moduleDto/moduleDto.module";
import { BillingModule } from "../billing/billing.module";
import { AssistantFunctionsService } from "./assistantFunctions.service";
import { JsonSchemaValidationModule } from "../../services/jsonSchemaValidation.module";

@Module({
imports: [
Expand All @@ -26,6 +27,7 @@ import { AssistantFunctionsService } from "./assistantFunctions.service";
ModuleActionModule,
ModuleDtoModule,
BillingModule,
JsonSchemaValidationModule,
],
providers: [
AssistantService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { PermissionsService } from "../permissions/permissions.service";
import { EnumBlockType } from "../../enums/EnumBlockType";
import { Module } from "../module/dto/Module";
import { AuthorizableOriginParameter } from "../../enums/AuthorizableOriginParameter";
import { JsonSchemaValidationModule } from "../../services/jsonSchemaValidation.module";

const EXAMPLE_CHAT_OPENAI_KEY = "EXAMPLE_CHAT_OPENAI_KEY";
const EXAMPLE_WORKSPACE_ID = "EXAMPLE_WORKSPACE_ID";
Expand Down Expand Up @@ -129,7 +130,9 @@ const resourceServiceResourcesMock = jest.fn();
const moduleServiceFindManyMock = jest.fn(() => {
return [EXAMPLE_MODULE];
});
const moduleDtoServiceCreateMock = jest.fn();
const moduleDtoServiceCreateMock = jest.fn(() => {
return { id: "exampleModuleDtoId" };
});
const moduleDtoServiceCreateEnumMock = jest.fn();
const moduleDtoServiceFindManyMock = jest.fn();

Expand All @@ -155,7 +158,7 @@ describe("AssistantFunctionsService", () => {

beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [],
imports: [JsonSchemaValidationModule],
providers: [
AssistantFunctionsService,

Expand Down Expand Up @@ -464,12 +467,18 @@ describe("AssistantFunctionsService", () => {
serviceId: "service123",
actionName: "NewAction",
actionDescription: "Action Description",
gqlOperation: "QUERY",
restVerb: "GET",
gqlOperation: "Query",
restVerb: "Get",
path: "/new-action",
inputType: {},
outputType: {},
restInputSource: "BODY",
inputType: {
type: "String",
isArray: false,
},
outputType: {
type: "String",
isArray: false,
},
restInputSource: "Body",
restInputParamsPropertyName: "params",
restInputBodyPropertyName: "body",
restInputQueryPropertyName: "query",
Expand Down Expand Up @@ -610,4 +619,42 @@ describe("AssistantFunctionsService", () => {
})
);
});

it("should return an error if module DTO types are invalid", async () => {
const EXAMPLE_SERVICE_ID = "exampleServiceId";

const functionName = EnumAssistantFunctions.CreateModuleDto;

const params = {
moduleId: EXAMPLE_MODULE_ID,
serviceId: EXAMPLE_SERVICE_ID,
dtoName: "NewDTO",
dtoDescription: "DTO Description",
properties: [
{
name: "property1",
propertyTypes: [
{
type: "InvalidType", //this is an invalid type
},
],
isOptional: false,
isArray: false,
},
],
};

const results = await service.executeFunction(
EXAMPLE_CALL_ID,
functionName,
JSON.stringify(params),
EXAMPLE_ASSISTANT_CONTEXT,
EXAMPLE_LOGGER_CONTEXT
);

expect(results).toEqual({
callId: EXAMPLE_CALL_ID,
results: expect.stringContaining("Invalid arguments:"),
});
});
});

0 comments on commit 8d0ded5

Please sign in to comment.