From a3406f1da1be5c51336ebfdac2b5140038a379dc Mon Sep 17 00:00:00 2001 From: Dave New Date: Thu, 9 Nov 2023 04:46:51 +0200 Subject: [PATCH] fix: validation for unsupported job input types (#1289) --- integration/testdata/audit_logs/schema.keel | 4 +- node/codegen_test.go | 10 +- schema/testdata/proto_job_fields/proto.json | 313 ++++++++++++++++++ schema/testdata/proto_job_fields/schema.keel | 19 ++ .../errors.json | 106 ++++++ .../schema.keel | 18 + .../errors.json | 55 +++ .../schema.keel | 21 ++ .../errors.json | 21 -- .../schema.keel | 5 - schema/validation/jobs.go | 11 + 11 files changed, 550 insertions(+), 33 deletions(-) create mode 100644 schema/testdata/proto_job_fields/proto.json create mode 100644 schema/testdata/proto_job_fields/schema.keel create mode 100644 schema/testdata/validation_fields_invalid_types/errors.json create mode 100644 schema/testdata/validation_fields_invalid_types/schema.keel create mode 100644 schema/testdata/validation_jobs_invalid_input_types/errors.json create mode 100644 schema/testdata/validation_jobs_invalid_input_types/schema.keel delete mode 100644 schema/testdata/validation_unsupported_field_type/errors.json delete mode 100644 schema/testdata/validation_unsupported_field_type/schema.keel diff --git a/integration/testdata/audit_logs/schema.keel b/integration/testdata/audit_logs/schema.keel index 5fc31204c..4c9c1290e 100644 --- a/integration/testdata/audit_logs/schema.keel +++ b/integration/testdata/audit_logs/schema.keel @@ -53,7 +53,7 @@ model WeddingInvitee { job UpdateHeadCount { inputs { - weddingId Id + weddingId ID } @permission(expression: true) @@ -61,7 +61,7 @@ job UpdateHeadCount { job UpdateHeadCountWithKysely { inputs { - weddingId Id + weddingId ID } @permission(expression: true) diff --git a/node/codegen_test.go b/node/codegen_test.go index d0ba1d6fa..134d6b9b4 100644 --- a/node/codegen_test.go +++ b/node/codegen_test.go @@ -1931,7 +1931,7 @@ job JobWithoutInputs { job AdHocJobWithInputs { inputs { nameField Text - someBool Bool? + someBool Boolean? } @permission(roles: [Admin]) } @@ -1960,7 +1960,7 @@ job JobWithoutInputs { job AdHocJobWithInputs { inputs { nameField Text - someBool Bool? + someBool Boolean? } @permission(roles: [Admin]) } @@ -1972,7 +1972,7 @@ role Admin {}` expected := ` export interface AdHocJobWithInputsMessage { nameField: string; - someBool?: any; + someBool?: boolean; }` runWriterTest(t, schema, expected, func(s *proto.Schema, w *codegen.Writer) { @@ -2080,7 +2080,7 @@ job JobWithoutInputs { job AdHocJobWithInputs { inputs { nameField Text - someBool Bool? + someBool Boolean? } @permission(roles: [Admin]) } @@ -2096,7 +2096,7 @@ import "@teamkeel/testing-runtime"; export interface AdHocJobWithInputsMessage { nameField: string; - someBool?: any; + someBool?: boolean; } export interface EmailPasswordInput { email: string; diff --git a/schema/testdata/proto_job_fields/proto.json b/schema/testdata/proto_job_fields/proto.json new file mode 100644 index 000000000..82cef8709 --- /dev/null +++ b/schema/testdata/proto_job_fields/proto.json @@ -0,0 +1,313 @@ +{ + "models": [ + { + "name": "Identity", + "fields": [ + { + "modelName": "Identity", + "name": "email", + "type": { + "type": "TYPE_STRING" + }, + "optional": true, + "uniqueWith": [ + "issuer" + ] + }, + { + "modelName": "Identity", + "name": "emailVerified", + "type": { + "type": "TYPE_BOOL" + }, + "defaultValue": { + "expression": { + "source": "false" + } + } + }, + { + "modelName": "Identity", + "name": "password", + "type": { + "type": "TYPE_PASSWORD" + }, + "optional": true + }, + { + "modelName": "Identity", + "name": "externalId", + "type": { + "type": "TYPE_STRING" + }, + "optional": true + }, + { + "modelName": "Identity", + "name": "issuer", + "type": { + "type": "TYPE_STRING" + }, + "optional": true, + "uniqueWith": [ + "email" + ] + }, + { + "modelName": "Identity", + "name": "id", + "type": { + "type": "TYPE_ID" + }, + "primaryKey": true, + "defaultValue": { + "useZeroValue": true + } + }, + { + "modelName": "Identity", + "name": "createdAt", + "type": { + "type": "TYPE_DATETIME" + }, + "defaultValue": { + "useZeroValue": true + } + }, + { + "modelName": "Identity", + "name": "updatedAt", + "type": { + "type": "TYPE_DATETIME" + }, + "defaultValue": { + "useZeroValue": true + } + } + ], + "actions": [ + { + "modelName": "Identity", + "name": "authenticate", + "type": "ACTION_TYPE_WRITE", + "implementation": "ACTION_IMPLEMENTATION_RUNTIME", + "inputMessageName": "AuthenticateInput", + "responseMessageName": "AuthenticateResponse" + }, + { + "modelName": "Identity", + "name": "requestPasswordReset", + "type": "ACTION_TYPE_WRITE", + "implementation": "ACTION_IMPLEMENTATION_RUNTIME", + "inputMessageName": "RequestPasswordResetInput", + "responseMessageName": "RequestPasswordResetResponse" + }, + { + "modelName": "Identity", + "name": "resetPassword", + "type": "ACTION_TYPE_WRITE", + "implementation": "ACTION_IMPLEMENTATION_RUNTIME", + "inputMessageName": "ResetPasswordInput", + "responseMessageName": "ResetPasswordResponse" + } + ] + } + ], + "roles": [ + { + "name": "Admin" + } + ], + "apis": [ + { + "name": "Api", + "apiModels": [ + { + "modelName": "Identity" + } + ] + } + ], + "enums": [ + { + "name": "MyEnum", + "values": [ + { + "name": "One" + }, + { + "name": "Two" + } + ] + } + ], + "messages": [ + { + "name": "Any" + }, + { + "name": "MyJobMessage", + "fields": [ + { + "messageName": "MyJobMessage", + "name": "id", + "type": { + "type": "TYPE_ID" + } + }, + { + "messageName": "MyJobMessage", + "name": "text", + "type": { + "type": "TYPE_STRING" + } + }, + { + "messageName": "MyJobMessage", + "name": "number", + "type": { + "type": "TYPE_INT" + } + }, + { + "messageName": "MyJobMessage", + "name": "bool", + "type": { + "type": "TYPE_BOOL" + } + }, + { + "messageName": "MyJobMessage", + "name": "array", + "type": { + "type": "TYPE_STRING" + } + }, + { + "messageName": "MyJobMessage", + "name": "enum", + "type": { + "type": "TYPE_ENUM", + "enumName": "MyEnum" + } + } + ] + }, + { + "name": "EmailPasswordInput", + "fields": [ + { + "messageName": "EmailPasswordInput", + "name": "email", + "type": { + "type": "TYPE_STRING" + } + }, + { + "messageName": "EmailPasswordInput", + "name": "password", + "type": { + "type": "TYPE_STRING" + } + } + ] + }, + { + "name": "AuthenticateInput", + "fields": [ + { + "messageName": "AuthenticateInput", + "name": "createIfNotExists", + "type": { + "type": "TYPE_BOOL" + }, + "optional": true + }, + { + "messageName": "AuthenticateInput", + "name": "emailPassword", + "type": { + "type": "TYPE_MESSAGE", + "messageName": "EmailPasswordInput" + } + } + ] + }, + { + "name": "AuthenticateResponse", + "fields": [ + { + "messageName": "AuthenticateResponse", + "name": "identityCreated", + "type": { + "type": "TYPE_BOOL" + } + }, + { + "messageName": "AuthenticateResponse", + "name": "token", + "type": { + "type": "TYPE_STRING" + } + } + ] + }, + { + "name": "RequestPasswordResetInput", + "fields": [ + { + "messageName": "RequestPasswordResetInput", + "name": "email", + "type": { + "type": "TYPE_STRING" + } + }, + { + "messageName": "RequestPasswordResetInput", + "name": "redirectUrl", + "type": { + "type": "TYPE_STRING" + } + } + ] + }, + { + "name": "RequestPasswordResetResponse" + }, + { + "name": "ResetPasswordInput", + "fields": [ + { + "messageName": "ResetPasswordInput", + "name": "token", + "type": { + "type": "TYPE_STRING" + } + }, + { + "messageName": "ResetPasswordInput", + "name": "password", + "type": { + "type": "TYPE_STRING" + } + } + ] + }, + { + "name": "ResetPasswordResponse" + } + ], + "jobs": [ + { + "name": "MyJob", + "inputMessageName": "MyJobMessage", + "permissions": [ + { + "roleNames": [ + "Admin" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/schema/testdata/proto_job_fields/schema.keel b/schema/testdata/proto_job_fields/schema.keel new file mode 100644 index 000000000..fd831429c --- /dev/null +++ b/schema/testdata/proto_job_fields/schema.keel @@ -0,0 +1,19 @@ +job MyJob { + inputs { + id ID + text Text + number Number + bool Boolean + array Text[] + enum MyEnum + } + + @permission(roles: [Admin]) +} + +role Admin {} + +enum MyEnum { + One + Two +} \ No newline at end of file diff --git a/schema/testdata/validation_fields_invalid_types/errors.json b/schema/testdata/validation_fields_invalid_types/errors.json new file mode 100644 index 000000000..a17a61c18 --- /dev/null +++ b/schema/testdata/validation_fields_invalid_types/errors.json @@ -0,0 +1,106 @@ +{ + "errors": [ + { + "message": "field nope has an unsupported type Nope", + "hint": "Did you mean one of Boolean, Date, Foo, ID, Identity, Number, Password, Secret, Text, or Timestamp?", + "code": "E009", + "pos": { + "filename": "testdata/validation_fields_invalid_types/schema.keel", + "offset": 33, + "line": 3, + "column": 9 + }, + "endPos": { + "filename": "testdata/validation_fields_invalid_types/schema.keel", + "offset": 37, + "line": 3, + "column": 13 + } + }, + { + "message": "field nopeArray has an unsupported type Nope", + "hint": "Did you mean one of Boolean, Date, Foo, ID, Identity, Number, Password, Secret, Text, or Timestamp?", + "code": "E009", + "pos": { + "filename": "testdata/validation_fields_invalid_types/schema.keel", + "offset": 51, + "line": 4, + "column": 9 + }, + "endPos": { + "filename": "testdata/validation_fields_invalid_types/schema.keel", + "offset": 60, + "line": 4, + "column": 18 + } + }, + { + "message": "field nopeOptional has an unsupported type Nope", + "hint": "Did you mean one of Boolean, Date, Foo, ID, Identity, Number, Password, Secret, Text, or Timestamp?", + "code": "E009", + "pos": { + "filename": "testdata/validation_fields_invalid_types/schema.keel", + "offset": 76, + "line": 5, + "column": 9 + }, + "endPos": { + "filename": "testdata/validation_fields_invalid_types/schema.keel", + "offset": 88, + "line": 5, + "column": 21 + } + }, + { + "message": "field message has an unsupported type MyMessage", + "hint": "Did you mean one of Boolean, Date, Foo, ID, Identity, Number, Password, Secret, Text, or Timestamp?", + "code": "E009", + "pos": { + "filename": "testdata/validation_fields_invalid_types/schema.keel", + "offset": 124, + "line": 7, + "column": 9 + }, + "endPos": { + "filename": "testdata/validation_fields_invalid_types/schema.keel", + "offset": 131, + "line": 7, + "column": 16 + } + }, + { + "message": "field job has an unsupported type MyJob", + "hint": "Did you mean one of Boolean, Date, Foo, ID, Identity, Number, Password, Secret, Text, or Timestamp?", + "code": "E009", + "pos": { + "filename": "testdata/validation_fields_invalid_types/schema.keel", + "offset": 150, + "line": 8, + "column": 9 + }, + "endPos": { + "filename": "testdata/validation_fields_invalid_types/schema.keel", + "offset": 153, + "line": 8, + "column": 12 + } + }, + { + "message": "Repeated fields of type 'Text' are not supported", + "hint": "If this was a mistake, remove [] from 'Text[]'", + "code": "TypeError", + "pos": { + "filename": "testdata/validation_fields_invalid_types/schema.keel", + "offset": 109, + "line": 6, + "column": 15 + }, + "endPos": { + "filename": "testdata/validation_fields_invalid_types/schema.keel", + "offset": 113, + "line": 6, + "column": 19 + } + } + ] +} \ No newline at end of file diff --git a/schema/testdata/validation_fields_invalid_types/schema.keel b/schema/testdata/validation_fields_invalid_types/schema.keel new file mode 100644 index 000000000..67580360c --- /dev/null +++ b/schema/testdata/validation_fields_invalid_types/schema.keel @@ -0,0 +1,18 @@ +model Foo { + fields { + nope Nope + nopeArray Nope[] + nopeOptional Nope? + array Text[] + message MyMessage + job MyJob + } +} + +message MyMessage { + name Text +} + +job MyJob { + @schedule("* * * * *") +} \ No newline at end of file diff --git a/schema/testdata/validation_jobs_invalid_input_types/errors.json b/schema/testdata/validation_jobs_invalid_input_types/errors.json new file mode 100644 index 000000000..800d88f8d --- /dev/null +++ b/schema/testdata/validation_jobs_invalid_input_types/errors.json @@ -0,0 +1,55 @@ +{ + "errors": [ + { + "message": "Job input 'foo' is defined with unsupported type Foo", + "hint": "", + "code": "JobDefinitionError", + "pos": { + "filename": "testdata/validation_jobs_invalid_input_types/schema.keel", + "offset": 34, + "line": 3, + "column": 9 + }, + "endPos": { + "filename": "testdata/validation_jobs_invalid_input_types/schema.keel", + "offset": 37, + "line": 3, + "column": 12 + } + }, + { + "message": "Job input 'message' is defined with unsupported type MyMessage", + "hint": "", + "code": "JobDefinitionError", + "pos": { + "filename": "testdata/validation_jobs_invalid_input_types/schema.keel", + "offset": 50, + "line": 4, + "column": 9 + }, + "endPos": { + "filename": "testdata/validation_jobs_invalid_input_types/schema.keel", + "offset": 57, + "line": 4, + "column": 16 + } + }, + { + "message": "Job input 'model' is defined with unsupported type MyModel", + "hint": "", + "code": "JobDefinitionError", + "pos": { + "filename": "testdata/validation_jobs_invalid_input_types/schema.keel", + "offset": 76, + "line": 5, + "column": 9 + }, + "endPos": { + "filename": "testdata/validation_jobs_invalid_input_types/schema.keel", + "offset": 81, + "line": 5, + "column": 14 + } + } + ] +} \ No newline at end of file diff --git a/schema/testdata/validation_jobs_invalid_input_types/schema.keel b/schema/testdata/validation_jobs_invalid_input_types/schema.keel new file mode 100644 index 000000000..215ba2a96 --- /dev/null +++ b/schema/testdata/validation_jobs_invalid_input_types/schema.keel @@ -0,0 +1,21 @@ +job MyJob1 { + inputs { + foo Foo + message MyMessage + model MyModel + } + + @permission(roles: [Admin]) +} + +role Admin {} + +model MyModel { + fields { + name Text + } +} + +message MyMessage { + name Text +} \ No newline at end of file diff --git a/schema/testdata/validation_unsupported_field_type/errors.json b/schema/testdata/validation_unsupported_field_type/errors.json deleted file mode 100644 index ebe01186a..000000000 --- a/schema/testdata/validation_unsupported_field_type/errors.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "errors": [ - { - "message": "field something has an unsupported type Unknown", - "hint": "Did you mean one of Boolean, Date, ID, Identity, Number, Password, Post, Secret, Text, or Timestamp?", - "code": "E009", - "pos": { - "filename": "testdata/validation_unsupported_field_type/schema.keel", - "offset": 28, - "line": 3, - "column": 5 - }, - "endPos": { - "filename": "testdata/validation_unsupported_field_type/schema.keel", - "offset": 37, - "line": 3, - "column": 14 - } - } - ] -} \ No newline at end of file diff --git a/schema/testdata/validation_unsupported_field_type/schema.keel b/schema/testdata/validation_unsupported_field_type/schema.keel deleted file mode 100644 index d3fc1891f..000000000 --- a/schema/testdata/validation_unsupported_field_type/schema.keel +++ /dev/null @@ -1,5 +0,0 @@ -model Post { - fields { - something Unknown - } -} diff --git a/schema/validation/jobs.go b/schema/validation/jobs.go index 5cccec5bd..a05c0e1f1 100644 --- a/schema/validation/jobs.go +++ b/schema/validation/jobs.go @@ -5,6 +5,7 @@ import ( "github.com/samber/lo" "github.com/teamkeel/keel/schema/parser" + "github.com/teamkeel/keel/schema/query" "github.com/teamkeel/keel/schema/validation/errorhandling" ) @@ -41,6 +42,16 @@ func Jobs(asts []*parser.AST, errs *errorhandling.ValidationErrors) Visitor { } }, EnterJobInput: func(input *parser.JobInputNode) { + if !parser.IsBuiltInFieldType(input.Type.Value) && !query.IsEnum(asts, input.Type.Value) { + errs.AppendError(errorhandling.NewValidationErrorWithDetails( + errorhandling.JobDefinitionError, + errorhandling.ErrorDetails{ + Message: fmt.Sprintf("Job input '%s' is defined with unsupported type %s", input.Name.Value, input.Type.Value), + }, + input.Name, + )) + } + if lo.Contains(jobInputs, input.Name.Value) { errs.AppendError(errorhandling.NewValidationErrorWithDetails( errorhandling.DuplicateDefinitionError,