From 1bf86d9eb1a7511483cdeac31c8423168a9de24b Mon Sep 17 00:00:00 2001 From: Dario Pranjic Date: Wed, 12 Nov 2025 21:27:30 +0100 Subject: [PATCH 01/15] Update graphql layer for new error system --- app/graphql/mutations/base_mutation.rb | 4 +-- .../types/errors/detailed_error_type.rb | 24 ++++++++++++++ app/graphql/types/errors/error_type.rb | 17 ++-------- app/services/service_response.rb | 33 +++++++++---------- .../mutation/applicationsettingsupdate.md | 2 +- docs/graphql/mutation/echo.md | 2 +- .../mutation/namespaceslicensescreate.md | 2 +- .../mutation/namespaceslicensesdelete.md | 2 +- .../mutation/namespacesmembersassignroles.md | 2 +- .../mutation/namespacesmembersdelete.md | 2 +- .../mutation/namespacesmembersinvite.md | 2 +- .../namespacesprojectsassignruntimes.md | 2 +- .../mutation/namespacesprojectscreate.md | 2 +- .../mutation/namespacesprojectsdelete.md | 2 +- .../mutation/namespacesprojectsflowscreate.md | 2 +- .../mutation/namespacesprojectsflowsdelete.md | 2 +- .../mutation/namespacesprojectsupdate.md | 2 +- .../namespacesrolesassignabilities.md | 2 +- .../mutation/namespacesrolesassignprojects.md | 2 +- .../graphql/mutation/namespacesrolescreate.md | 2 +- .../graphql/mutation/namespacesrolesdelete.md | 2 +- .../graphql/mutation/namespacesrolesupdate.md | 2 +- docs/graphql/mutation/organizationscreate.md | 2 +- docs/graphql/mutation/organizationsdelete.md | 2 +- docs/graphql/mutation/organizationsupdate.md | 2 +- docs/graphql/mutation/runtimescreate.md | 2 +- docs/graphql/mutation/runtimesdelete.md | 2 +- docs/graphql/mutation/runtimesrotatetoken.md | 2 +- docs/graphql/mutation/runtimesupdate.md | 2 +- .../mutation/usersemailverification.md | 2 +- docs/graphql/mutation/usersidentitylink.md | 2 +- docs/graphql/mutation/usersidentitylogin.md | 2 +- .../graphql/mutation/usersidentityregister.md | 2 +- docs/graphql/mutation/usersidentityunlink.md | 2 +- docs/graphql/mutation/userslogin.md | 2 +- docs/graphql/mutation/userslogout.md | 2 +- .../mutation/usersmfabackupcodesrotate.md | 2 +- .../mutation/usersmfatotpgeneratesecret.md | 2 +- .../mutation/usersmfatotpvalidatesecret.md | 2 +- docs/graphql/mutation/userspasswordreset.md | 2 +- .../mutation/userspasswordresetrequest.md | 2 +- docs/graphql/mutation/usersregister.md | 2 +- docs/graphql/mutation/usersupdate.md | 2 +- docs/graphql/object/error.md | 13 ++++++++ .../union/{error.md => detailederror.md} | 5 ++- 45 files changed, 98 insertions(+), 76 deletions(-) create mode 100644 app/graphql/types/errors/detailed_error_type.rb create mode 100644 docs/graphql/object/error.md rename docs/graphql/union/{error.md => detailederror.md} (57%) diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb index 089f6da1..020244ef 100644 --- a/app/graphql/mutations/base_mutation.rb +++ b/app/graphql/mutations/base_mutation.rb @@ -26,8 +26,8 @@ def current_authentication context[:current_authentication] end - def create_message_error(message) - Sagittarius::Graphql::ErrorMessageContainer.new(message: message) + def create_error(code, message) + { code: code, details: message } end end end diff --git a/app/graphql/types/errors/detailed_error_type.rb b/app/graphql/types/errors/detailed_error_type.rb new file mode 100644 index 00000000..840e43c1 --- /dev/null +++ b/app/graphql/types/errors/detailed_error_type.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Types + module Errors + # rubocop:disable GraphQL/GraphqlName -- we don't want the module prefix + class DetailedErrorType < Types::BaseUnion + graphql_name 'DetailedError' + # rubocop:enable GraphQL/GraphqlName + description 'Represents a detailed error with either a message or an active model error' + possible_types Types::Errors::ActiveModelErrorType, Types::Errors::MessageErrorType + + def self.resolve_type(object, _ctx) + case object + when ActiveModel::Error + Types::Errors::ActiveModelErrorType + when Sagittarius::Graphql::ErrorMessageContainer + Types::Errors::MessageErrorType + else + raise 'Unsupported DetailedErrorType' + end + end + end + end +end diff --git a/app/graphql/types/errors/error_type.rb b/app/graphql/types/errors/error_type.rb index 1e1b941a..f749ea5d 100644 --- a/app/graphql/types/errors/error_type.rb +++ b/app/graphql/types/errors/error_type.rb @@ -3,24 +3,13 @@ module Types module Errors # rubocop:disable GraphQL/GraphqlName -- we don't want the module prefix - class ErrorType < BaseUnion + class ErrorType < Types::BaseObject graphql_name 'Error' # rubocop:enable GraphQL/GraphqlName description 'Objects that can present an error' - possible_types Errors::ActiveModelErrorType, Errors::MessageErrorType, Errors::ErrorCodeType - def self.resolve_type(object, _ctx) - case object - when ActiveModel::Error - Errors::ActiveModelErrorType - when Sagittarius::Graphql::ErrorMessageContainer - Errors::MessageErrorType - when Sagittarius::Graphql::ServiceResponseErrorContainer - Errors::ErrorCodeType - else - raise 'Unsupported ErrorType' - end - end + field :code, Errors::ErrorCodeType, null: false, description: 'The code representing the error type' + field :details, [Errors::DetailedErrorType], null: true, description: 'Detailed validation errors if applicable' end end end diff --git a/app/services/service_response.rb b/app/services/service_response.rb index b2660373..6969644a 100644 --- a/app/services/service_response.rb +++ b/app/services/service_response.rb @@ -5,12 +5,12 @@ def self.success(message: nil, payload: nil) new(status: :success, message: message, payload: payload) end - def self.error(message: nil, payload: nil) - if payload.is_a?(Symbol) || payload.is_a?(Array) - Array.wrap(payload).each { |error_code| ErrorCode.validate_error_code!(error_code) } - end + def self.error(message: nil, error_code: nil, details: nil) + raise ArgumentError, 'error_code must be provided for error responses' if error_code.nil? + + ErrorCode.validate_error_code!(error_code) - new(status: :error, message: message, payload: payload) + new(status: :error, message: message, payload: { error_code: error_code, details: details }) end attr_reader :status, :message, :payload @@ -47,20 +47,17 @@ def to_h def to_mutation_response(success_key: :object) return { success_key => payload, errors: [] } if success? - - if payload.is_a?(ActiveModel::Errors) - { success_key => nil, errors: payload.errors } - else - errors = Array.wrap(payload).map do |message| - case message - when String - Sagittarius::Graphql::ErrorMessageContainer.new(message: message) - when Symbol - Sagittarius::Graphql::ServiceResponseErrorContainer.new(error_code: message) - end + return { success_key => nil, errors: payload } if payload&.details.is_a?(ActiveModel::Errors) + + payload.details = Array.wrap(payload&.details).map do |message| + case message + when String + Sagittarius::Graphql::ErrorMessageContainer.new(message: message) + when Symbol + Sagittarius::Graphql::ServiceResponseErrorContainer.new(error_code: message) end - - { success_key => nil, errors: errors } end + + { success_key => nil, errors: payload } end end diff --git a/docs/graphql/mutation/applicationsettingsupdate.md b/docs/graphql/mutation/applicationsettingsupdate.md index ced8de07..707b0a81 100644 --- a/docs/graphql/mutation/applicationsettingsupdate.md +++ b/docs/graphql/mutation/applicationsettingsupdate.md @@ -18,4 +18,4 @@ Update application settings. |------|------|-------------| | `applicationSettings` | [`ApplicationSettings`](../object/applicationsettings.md) | The updated application settings. | | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | diff --git a/docs/graphql/mutation/echo.md b/docs/graphql/mutation/echo.md index fec7679c..b6ad6399 100644 --- a/docs/graphql/mutation/echo.md +++ b/docs/graphql/mutation/echo.md @@ -20,5 +20,5 @@ that a user has mutation access. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `message` | [`String`](../scalar/string.md) | Message returned to the user. | diff --git a/docs/graphql/mutation/namespaceslicensescreate.md b/docs/graphql/mutation/namespaceslicensescreate.md index ee5db2e4..980faf89 100644 --- a/docs/graphql/mutation/namespaceslicensescreate.md +++ b/docs/graphql/mutation/namespaceslicensescreate.md @@ -17,5 +17,5 @@ title: namespacesLicensesCreate | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `namespaceLicense` | [`NamespaceLicense`](../object/namespacelicense.md) | The newly created license. | diff --git a/docs/graphql/mutation/namespaceslicensesdelete.md b/docs/graphql/mutation/namespaceslicensesdelete.md index 780eba3a..dfd85733 100644 --- a/docs/graphql/mutation/namespaceslicensesdelete.md +++ b/docs/graphql/mutation/namespaceslicensesdelete.md @@ -16,5 +16,5 @@ title: namespacesLicensesDelete | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `namespaceLicense` | [`NamespaceLicense`](../object/namespacelicense.md) | The deleted namespace license. | diff --git a/docs/graphql/mutation/namespacesmembersassignroles.md b/docs/graphql/mutation/namespacesmembersassignroles.md index 8560c7f2..51e7c7f0 100644 --- a/docs/graphql/mutation/namespacesmembersassignroles.md +++ b/docs/graphql/mutation/namespacesmembersassignroles.md @@ -17,5 +17,5 @@ Update the roles a member is assigned to. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `namespaceMemberRoles` | [`[NamespaceMemberRole!]`](../object/namespacememberrole.md) | The roles the member is now assigned to | diff --git a/docs/graphql/mutation/namespacesmembersdelete.md b/docs/graphql/mutation/namespacesmembersdelete.md index a81ec971..59d0e3f2 100644 --- a/docs/graphql/mutation/namespacesmembersdelete.md +++ b/docs/graphql/mutation/namespacesmembersdelete.md @@ -16,5 +16,5 @@ Remove a member from a namespace. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `namespaceMember` | [`NamespaceMember`](../object/namespacemember.md) | The removed namespace member | diff --git a/docs/graphql/mutation/namespacesmembersinvite.md b/docs/graphql/mutation/namespacesmembersinvite.md index 5e91bc89..48fd31dd 100644 --- a/docs/graphql/mutation/namespacesmembersinvite.md +++ b/docs/graphql/mutation/namespacesmembersinvite.md @@ -17,5 +17,5 @@ Invite a new member to a namespace. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `namespaceMember` | [`NamespaceMember`](../object/namespacemember.md) | The newly created namespace member | diff --git a/docs/graphql/mutation/namespacesprojectsassignruntimes.md b/docs/graphql/mutation/namespacesprojectsassignruntimes.md index 2375f273..c6200c9a 100644 --- a/docs/graphql/mutation/namespacesprojectsassignruntimes.md +++ b/docs/graphql/mutation/namespacesprojectsassignruntimes.md @@ -17,5 +17,5 @@ Assign runtimes to a project | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `namespaceProject` | [`NamespaceProject`](../object/namespaceproject.md) | The updated project with assigned runtimes | diff --git a/docs/graphql/mutation/namespacesprojectscreate.md b/docs/graphql/mutation/namespacesprojectscreate.md index c246420f..ee7c9cd3 100644 --- a/docs/graphql/mutation/namespacesprojectscreate.md +++ b/docs/graphql/mutation/namespacesprojectscreate.md @@ -18,5 +18,5 @@ Creates a new namespace project. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `namespaceProject` | [`NamespaceProject`](../object/namespaceproject.md) | The newly created project. | diff --git a/docs/graphql/mutation/namespacesprojectsdelete.md b/docs/graphql/mutation/namespacesprojectsdelete.md index a8c6292a..b3d32b60 100644 --- a/docs/graphql/mutation/namespacesprojectsdelete.md +++ b/docs/graphql/mutation/namespacesprojectsdelete.md @@ -16,5 +16,5 @@ Deletes a namespace project. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `namespaceProject` | [`NamespaceProject`](../object/namespaceproject.md) | The deleted project. | diff --git a/docs/graphql/mutation/namespacesprojectsflowscreate.md b/docs/graphql/mutation/namespacesprojectsflowscreate.md index 21d74795..31b0450c 100644 --- a/docs/graphql/mutation/namespacesprojectsflowscreate.md +++ b/docs/graphql/mutation/namespacesprojectsflowscreate.md @@ -17,5 +17,5 @@ Creates a new flow. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `flow` | [`Flow`](../object/flow.md) | The newly created flow. | diff --git a/docs/graphql/mutation/namespacesprojectsflowsdelete.md b/docs/graphql/mutation/namespacesprojectsflowsdelete.md index ed3cc1fb..7809386a 100644 --- a/docs/graphql/mutation/namespacesprojectsflowsdelete.md +++ b/docs/graphql/mutation/namespacesprojectsflowsdelete.md @@ -16,5 +16,5 @@ Deletes a namespace project. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `flow` | [`Flow`](../object/flow.md) | The deleted flow. | diff --git a/docs/graphql/mutation/namespacesprojectsupdate.md b/docs/graphql/mutation/namespacesprojectsupdate.md index 869a2424..e21483ff 100644 --- a/docs/graphql/mutation/namespacesprojectsupdate.md +++ b/docs/graphql/mutation/namespacesprojectsupdate.md @@ -19,5 +19,5 @@ Updates a namespace project. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `namespaceProject` | [`NamespaceProject`](../object/namespaceproject.md) | The updated project. | diff --git a/docs/graphql/mutation/namespacesrolesassignabilities.md b/docs/graphql/mutation/namespacesrolesassignabilities.md index 85b1154e..35a6cf60 100644 --- a/docs/graphql/mutation/namespacesrolesassignabilities.md +++ b/docs/graphql/mutation/namespacesrolesassignabilities.md @@ -18,4 +18,4 @@ Update the abilities a role is granted. |------|------|-------------| | `abilities` | [`[NamespaceRoleAbility!]`](../enum/namespaceroleability.md) | The now granted abilities | | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | diff --git a/docs/graphql/mutation/namespacesrolesassignprojects.md b/docs/graphql/mutation/namespacesrolesassignprojects.md index c5c3f168..b4cb98ad 100644 --- a/docs/graphql/mutation/namespacesrolesassignprojects.md +++ b/docs/graphql/mutation/namespacesrolesassignprojects.md @@ -17,5 +17,5 @@ Update the project a role is assigned to. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `projects` | [`[NamespaceProject!]`](../object/namespaceproject.md) | The now assigned projects | diff --git a/docs/graphql/mutation/namespacesrolescreate.md b/docs/graphql/mutation/namespacesrolescreate.md index 1a714302..cfb86a8f 100644 --- a/docs/graphql/mutation/namespacesrolescreate.md +++ b/docs/graphql/mutation/namespacesrolescreate.md @@ -17,5 +17,5 @@ Create a new role in a namespace. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `namespaceRole` | [`NamespaceRole`](../object/namespacerole.md) | The newly created namespace role | diff --git a/docs/graphql/mutation/namespacesrolesdelete.md b/docs/graphql/mutation/namespacesrolesdelete.md index 91494f7d..5cddfd5a 100644 --- a/docs/graphql/mutation/namespacesrolesdelete.md +++ b/docs/graphql/mutation/namespacesrolesdelete.md @@ -16,5 +16,5 @@ Delete an existing role in a namespace. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `namespaceRole` | [`NamespaceRole`](../object/namespacerole.md) | The deleted namespace role | diff --git a/docs/graphql/mutation/namespacesrolesupdate.md b/docs/graphql/mutation/namespacesrolesupdate.md index 70733083..e5b65fee 100644 --- a/docs/graphql/mutation/namespacesrolesupdate.md +++ b/docs/graphql/mutation/namespacesrolesupdate.md @@ -17,5 +17,5 @@ Update an existing namespace role. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `namespaceRole` | [`NamespaceRole`](../object/namespacerole.md) | The updated namespace role. | diff --git a/docs/graphql/mutation/organizationscreate.md b/docs/graphql/mutation/organizationscreate.md index f724d8c0..186560e1 100644 --- a/docs/graphql/mutation/organizationscreate.md +++ b/docs/graphql/mutation/organizationscreate.md @@ -16,5 +16,5 @@ Create a new organization. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `organization` | [`Organization`](../object/organization.md) | The newly created organization. | diff --git a/docs/graphql/mutation/organizationsdelete.md b/docs/graphql/mutation/organizationsdelete.md index 03cd7554..430a86ae 100644 --- a/docs/graphql/mutation/organizationsdelete.md +++ b/docs/graphql/mutation/organizationsdelete.md @@ -16,5 +16,5 @@ Delete an existing organization. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `organization` | [`Organization`](../object/organization.md) | The deleted organization. | diff --git a/docs/graphql/mutation/organizationsupdate.md b/docs/graphql/mutation/organizationsupdate.md index 4c940631..e2b71a7f 100644 --- a/docs/graphql/mutation/organizationsupdate.md +++ b/docs/graphql/mutation/organizationsupdate.md @@ -17,5 +17,5 @@ Update an existing organization. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `organization` | [`Organization`](../object/organization.md) | The updated organization. | diff --git a/docs/graphql/mutation/runtimescreate.md b/docs/graphql/mutation/runtimescreate.md index 41688cf3..e532da6b 100644 --- a/docs/graphql/mutation/runtimescreate.md +++ b/docs/graphql/mutation/runtimescreate.md @@ -18,5 +18,5 @@ Create a new runtime. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `runtime` | [`Runtime`](../object/runtime.md) | The newly created runtime. | diff --git a/docs/graphql/mutation/runtimesdelete.md b/docs/graphql/mutation/runtimesdelete.md index 7edb7de6..fe9eb829 100644 --- a/docs/graphql/mutation/runtimesdelete.md +++ b/docs/graphql/mutation/runtimesdelete.md @@ -16,5 +16,5 @@ Delete an existing runtime. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `runtime` | [`Runtime`](../object/runtime.md) | The updated organization. | diff --git a/docs/graphql/mutation/runtimesrotatetoken.md b/docs/graphql/mutation/runtimesrotatetoken.md index 5915d4fd..49c22a72 100644 --- a/docs/graphql/mutation/runtimesrotatetoken.md +++ b/docs/graphql/mutation/runtimesrotatetoken.md @@ -16,5 +16,5 @@ reloads the token of an existing runtime. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `runtime` | [`Runtime`](../object/runtime.md) | The updated runtime. | diff --git a/docs/graphql/mutation/runtimesupdate.md b/docs/graphql/mutation/runtimesupdate.md index ade866e7..80dace01 100644 --- a/docs/graphql/mutation/runtimesupdate.md +++ b/docs/graphql/mutation/runtimesupdate.md @@ -18,5 +18,5 @@ Update an existing runtime. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `runtime` | [`Runtime`](../object/runtime.md) | The updated runtime. | diff --git a/docs/graphql/mutation/usersemailverification.md b/docs/graphql/mutation/usersemailverification.md index 5c7331b0..946901cc 100644 --- a/docs/graphql/mutation/usersemailverification.md +++ b/docs/graphql/mutation/usersemailverification.md @@ -16,5 +16,5 @@ Verify your email when changing it or signing up | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `user` | [`User`](../object/user.md) | The user whose email was verified | diff --git a/docs/graphql/mutation/usersidentitylink.md b/docs/graphql/mutation/usersidentitylink.md index 1146fb5b..9ea0f1a4 100644 --- a/docs/graphql/mutation/usersidentitylink.md +++ b/docs/graphql/mutation/usersidentitylink.md @@ -17,5 +17,5 @@ Links an external identity to an existing user | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `userIdentity` | [`UserIdentity`](../object/useridentity.md) | The created user identity | diff --git a/docs/graphql/mutation/usersidentitylogin.md b/docs/graphql/mutation/usersidentitylogin.md index 24f057a9..b067a0d8 100644 --- a/docs/graphql/mutation/usersidentitylogin.md +++ b/docs/graphql/mutation/usersidentitylogin.md @@ -17,5 +17,5 @@ Login to an existing user via an external identity | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `userSession` | [`UserSession`](../object/usersession.md) | The created user session | diff --git a/docs/graphql/mutation/usersidentityregister.md b/docs/graphql/mutation/usersidentityregister.md index 2d12ad17..576ae01b 100644 --- a/docs/graphql/mutation/usersidentityregister.md +++ b/docs/graphql/mutation/usersidentityregister.md @@ -17,5 +17,5 @@ Register a new user via an external identity | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `userSession` | [`UserSession`](../object/usersession.md) | The created users session | diff --git a/docs/graphql/mutation/usersidentityunlink.md b/docs/graphql/mutation/usersidentityunlink.md index 4e7e71e2..d0b63484 100644 --- a/docs/graphql/mutation/usersidentityunlink.md +++ b/docs/graphql/mutation/usersidentityunlink.md @@ -16,5 +16,5 @@ Unlinks an external identity from an user | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `userIdentity` | [`UserIdentity`](../object/useridentity.md) | The removed identity | diff --git a/docs/graphql/mutation/userslogin.md b/docs/graphql/mutation/userslogin.md index 5850428b..bf175eba 100644 --- a/docs/graphql/mutation/userslogin.md +++ b/docs/graphql/mutation/userslogin.md @@ -19,5 +19,5 @@ Login to an existing user | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `userSession` | [`UserSession`](../object/usersession.md) | The created user session | diff --git a/docs/graphql/mutation/userslogout.md b/docs/graphql/mutation/userslogout.md index 5a13ed37..7bf351a7 100644 --- a/docs/graphql/mutation/userslogout.md +++ b/docs/graphql/mutation/userslogout.md @@ -16,5 +16,5 @@ Logout an existing user session | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `userSession` | [`UserSession`](../object/usersession.md) | The logged out user session | diff --git a/docs/graphql/mutation/usersmfabackupcodesrotate.md b/docs/graphql/mutation/usersmfabackupcodesrotate.md index 55082361..678a2e8b 100644 --- a/docs/graphql/mutation/usersmfabackupcodesrotate.md +++ b/docs/graphql/mutation/usersmfabackupcodesrotate.md @@ -16,4 +16,4 @@ Rotates the backup codes of a user. |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | | `codes` | [`[String!]`](../scalar/string.md) | The newly rotated backup codes. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | diff --git a/docs/graphql/mutation/usersmfatotpgeneratesecret.md b/docs/graphql/mutation/usersmfatotpgeneratesecret.md index a7f5f087..173b77e0 100644 --- a/docs/graphql/mutation/usersmfatotpgeneratesecret.md +++ b/docs/graphql/mutation/usersmfatotpgeneratesecret.md @@ -15,5 +15,5 @@ Generates an encrypted totp secret | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `secret` | [`String`](../scalar/string.md) | The created and signed secret | diff --git a/docs/graphql/mutation/usersmfatotpvalidatesecret.md b/docs/graphql/mutation/usersmfatotpvalidatesecret.md index cca8972b..73d802a7 100644 --- a/docs/graphql/mutation/usersmfatotpvalidatesecret.md +++ b/docs/graphql/mutation/usersmfatotpvalidatesecret.md @@ -18,5 +18,5 @@ Validates a TOTP value for the given secret and enables TOTP MFA for the user | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `user` | [`User`](../object/user.md) | The modified user | diff --git a/docs/graphql/mutation/userspasswordreset.md b/docs/graphql/mutation/userspasswordreset.md index 2a6f8d16..e096c843 100644 --- a/docs/graphql/mutation/userspasswordreset.md +++ b/docs/graphql/mutation/userspasswordreset.md @@ -18,5 +18,5 @@ Reset the password using a reset token | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `message` | [`String`](../scalar/string.md) | A message indicating the result of the password reset request | diff --git a/docs/graphql/mutation/userspasswordresetrequest.md b/docs/graphql/mutation/userspasswordresetrequest.md index d6f20f39..60ee1b57 100644 --- a/docs/graphql/mutation/userspasswordresetrequest.md +++ b/docs/graphql/mutation/userspasswordresetrequest.md @@ -16,5 +16,5 @@ Request an password reset | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `message` | [`String`](../scalar/string.md) | A message indicating the result of the password reset request | diff --git a/docs/graphql/mutation/usersregister.md b/docs/graphql/mutation/usersregister.md index 1e58392e..ff5cb181 100644 --- a/docs/graphql/mutation/usersregister.md +++ b/docs/graphql/mutation/usersregister.md @@ -19,5 +19,5 @@ Register a new user | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `userSession` | [`UserSession`](../object/usersession.md) | The created users session | diff --git a/docs/graphql/mutation/usersupdate.md b/docs/graphql/mutation/usersupdate.md index 388324fc..d27a05e0 100644 --- a/docs/graphql/mutation/usersupdate.md +++ b/docs/graphql/mutation/usersupdate.md @@ -24,5 +24,5 @@ Update an existing user. | Name | Type | Description | |------|------|-------------| | `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. | -| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. | +| `errors` | [`[Error!]!`](../object/error.md) | Errors encountered during execution of the mutation. | | `user` | [`User`](../object/user.md) | The updated user. | diff --git a/docs/graphql/object/error.md b/docs/graphql/object/error.md new file mode 100644 index 00000000..60250347 --- /dev/null +++ b/docs/graphql/object/error.md @@ -0,0 +1,13 @@ +--- +title: Error +--- + +Objects that can present an error + +## Fields without arguments + +| Name | Type | Description | +|------|------|-------------| +| `code` | [`ErrorCode!`](../object/errorcode.md) | The code representing the error type | +| `details` | [`[DetailedError!]`](../union/detailederror.md) | Detailed validation errors if applicable | + diff --git a/docs/graphql/union/error.md b/docs/graphql/union/detailederror.md similarity index 57% rename from docs/graphql/union/error.md rename to docs/graphql/union/detailederror.md index 524078f0..b4382a3f 100644 --- a/docs/graphql/union/error.md +++ b/docs/graphql/union/detailederror.md @@ -1,11 +1,10 @@ --- -title: Error +title: DetailedError --- -Objects that can present an error +Represents a detailed error with either a message or an active model error ## Possible types - [`ActiveModelError`](../object/activemodelerror.md) -- [`ErrorCode`](../object/errorcode.md) - [`MessageError`](../object/messageerror.md) From 87a46c9098a7cb90d094913ae8626321f6ac8ac2 Mon Sep 17 00:00:00 2001 From: Dario Pranjic Date: Thu, 13 Nov 2025 19:12:14 +0100 Subject: [PATCH 02/15] Fix error structure --- app/graphql/mutations/base_mutation.rb | 7 ++++- app/graphql/mutations/users/register.rb | 4 ++- .../types/errors/detailed_error_type.rb | 2 +- app/graphql/types/errors/error_code_type.rb | 14 --------- app/graphql/types/errors/error_type.rb | 3 +- app/services/error_code.rb | 3 +- app/services/service_response.rb | 26 ++++++++-------- .../users/identity/register_service.rb | 16 +++++----- app/services/users/register_service.rb | 12 +++++--- ...essage_container.rb => error_container.rb} | 2 +- .../service_response_error_container.rb | 7 ----- .../types/errors/detailed_error_type_spec.rb | 30 +++++++++++++++++++ spec/graphql/types/errors/error_type_spec.rb | 29 +++++------------- 13 files changed, 82 insertions(+), 73 deletions(-) delete mode 100644 app/graphql/types/errors/error_code_type.rb rename lib/sagittarius/graphql/{error_message_container.rb => error_container.rb} (58%) delete mode 100644 lib/sagittarius/graphql/service_response_error_container.rb create mode 100644 spec/graphql/types/errors/detailed_error_type_spec.rb diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb index 020244ef..ee2585db 100644 --- a/app/graphql/mutations/base_mutation.rb +++ b/app/graphql/mutations/base_mutation.rb @@ -27,7 +27,12 @@ def current_authentication end def create_error(code, message) - { code: code, details: message } + ErrorCode.validate_error_code!(code) + + Sagittarius::Graphql::ErrorContainer.new( + code, + [{ message: message }] + ) end end end diff --git a/app/graphql/mutations/users/register.rb b/app/graphql/mutations/users/register.rb index 491035a2..feea19ea 100644 --- a/app/graphql/mutations/users/register.rb +++ b/app/graphql/mutations/users/register.rb @@ -16,7 +16,9 @@ class Register < BaseMutation argument :username, String, required: true, description: 'Username of the user' def resolve(username:, email:, password:, password_repeat:) - return { user: nil, errors: [create_message_error('Invalid password repeat')] } if password != password_repeat + if password != password_repeat + return { user: nil, errors: [create_error(:invalid_password_repeat, 'Invalid password repeat')] } + end response = ::Users::RegisterService.new( username, diff --git a/app/graphql/types/errors/detailed_error_type.rb b/app/graphql/types/errors/detailed_error_type.rb index 840e43c1..191041f6 100644 --- a/app/graphql/types/errors/detailed_error_type.rb +++ b/app/graphql/types/errors/detailed_error_type.rb @@ -13,7 +13,7 @@ def self.resolve_type(object, _ctx) case object when ActiveModel::Error Types::Errors::ActiveModelErrorType - when Sagittarius::Graphql::ErrorMessageContainer + when Hash Types::Errors::MessageErrorType else raise 'Unsupported DetailedErrorType' diff --git a/app/graphql/types/errors/error_code_type.rb b/app/graphql/types/errors/error_code_type.rb deleted file mode 100644 index 64d3978b..00000000 --- a/app/graphql/types/errors/error_code_type.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Types - module Errors - # rubocop:disable GraphQL/GraphqlName -- we don't want the module prefix - class ErrorCodeType < Types::BaseObject - graphql_name 'ErrorCode' - # rubocop:enable GraphQL/GraphqlName - description 'Represents an error code' - - field :error_code, ErrorCodeEnum, null: false, description: 'The error code' - end - end -end diff --git a/app/graphql/types/errors/error_type.rb b/app/graphql/types/errors/error_type.rb index f749ea5d..bb7f0a9b 100644 --- a/app/graphql/types/errors/error_type.rb +++ b/app/graphql/types/errors/error_type.rb @@ -8,8 +8,9 @@ class ErrorType < Types::BaseObject # rubocop:enable GraphQL/GraphqlName description 'Objects that can present an error' - field :code, Errors::ErrorCodeType, null: false, description: 'The code representing the error type' field :details, [Errors::DetailedErrorType], null: true, description: 'Detailed validation errors if applicable' + field :error_code, ErrorCodeEnum, null: false, description: 'The code representing the error type' + end end end diff --git a/app/services/error_code.rb b/app/services/error_code.rb index 828a41f3..e5751375 100644 --- a/app/services/error_code.rb +++ b/app/services/error_code.rb @@ -36,7 +36,8 @@ def self.error_codes failed_to_invalidate_old_backup_codes: { description: 'The old backup codes could not be deleted' }, failed_to_save_valid_backup_code: { description: 'The new backup codes could not be saved' }, invalid_setting: { description: 'Invalid setting provided' }, - + invalid_user: { description: 'The user is invalid because of active model errors' }, + invalid_password_repeat: { description: 'The provided password repeat does not match the password' }, primary_level_not_found: { description: '', deprecation_reason: 'Outdated concept' }, secondary_level_not_found: { description: '', deprecation_reason: 'Outdated concept' }, tertiary_level_exceeds_parameters: { description: '', deprecation_reason: 'Outdated concept' }, diff --git a/app/services/service_response.rb b/app/services/service_response.rb index 6969644a..315925ec 100644 --- a/app/services/service_response.rb +++ b/app/services/service_response.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class ServiceResponse + include Code0::ZeroTrack::Loggable + def self.success(message: nil, payload: nil) new(status: :success, message: message, payload: payload) end @@ -10,7 +12,8 @@ def self.error(message: nil, error_code: nil, details: nil) ErrorCode.validate_error_code!(error_code) - new(status: :error, message: message, payload: { error_code: error_code, details: details }) + new(status: :error, message: message, + payload: { error_code: error_code, details: details }) end attr_reader :status, :message, :payload @@ -47,17 +50,16 @@ def to_h def to_mutation_response(success_key: :object) return { success_key => payload, errors: [] } if success? - return { success_key => nil, errors: payload } if payload&.details.is_a?(ActiveModel::Errors) - - payload.details = Array.wrap(payload&.details).map do |message| - case message - when String - Sagittarius::Graphql::ErrorMessageContainer.new(message: message) - when Symbol - Sagittarius::Graphql::ServiceResponseErrorContainer.new(error_code: message) - end - end - { success_key => nil, errors: payload } + payload[:details] = if payload[:details].is_a?(ActiveModel::Errors) + payload[:details].errors + else + Array.wrap(payload[:details]).map do |message| + { message: message } + end + end + + { success_key => nil, + errors: [Sagittarius::Graphql::ErrorContainer.new(payload[:error_code], payload[:details])] } end end diff --git a/app/services/users/identity/register_service.rb b/app/services/users/identity/register_service.rb index 972f5b12..00812541 100644 --- a/app/services/users/identity/register_service.rb +++ b/app/services/users/identity/register_service.rb @@ -16,14 +16,14 @@ def initialize(provider_id, args) def execute unless ApplicationSetting.current[:user_registration_enabled] - return ServiceResponse.error(message: 'User registration is disabled', payload: :registration_disabled) + return ServiceResponse.error(message: 'User registration is disabled', error_code: :registration_disabled) end begin identity = identity_provider.load_identity(provider_id, args) rescue Code0::Identities::Error => e logger.warn(message: 'Identity validation failed', exception: e) - return ServiceResponse.error(message: e.message, payload: :identity_validation_failed) + return ServiceResponse.error(message: e.message, error_code: :identity_validation_failed) end identifier = identity.identifier @@ -33,7 +33,7 @@ def execute lastname = identity.lastname password = SecureRandom.base58(50) - return ServiceResponse.error(message: 'No email given', payload: :missing_identity_data) if email.nil? + return ServiceResponse.error(error_code: :missing_identity_data) if email.nil? username = email.split('@').first if username.nil? @@ -48,17 +48,17 @@ def execute user = User.create(username: username, email: email, password: password, firstname: firstname, lastname: lastname) user.ensure_namespace - return ServiceResponse.error(message: 'User is invalid', payload: user.errors) unless user.persisted? + return ServiceResponse.error(error_code: :invalid_user, details: user.errors) unless user.persisted? user_identity = UserIdentity.create(user: user, provider_id: provider_id, identifier: identifier) unless user_identity.persisted? - t.rollback_and_return! ServiceResponse.error(message: 'UserIdentity is invalid', - payload: user_identity.errors) + t.rollback_and_return! ServiceResponse.error(error_code: :invalid_user_identity, + details: user_identity.errors) end user_session = UserSession.create(user: user) unless user_session.persisted? - t.rollback_and_return! ServiceResponse.error(message: 'UserSession is invalid', - payload: user_session.errors) + t.rollback_and_return! ServiceResponse.error(message: :invalid_user_session, + details: user_session.errors) end AuditService.audit( diff --git a/app/services/users/register_service.rb b/app/services/users/register_service.rb index a6ad501b..8f029623 100644 --- a/app/services/users/register_service.rb +++ b/app/services/users/register_service.rb @@ -15,18 +15,21 @@ def initialize(username, email, password) def execute unless ApplicationSetting.current[:user_registration_enabled] - return ServiceResponse.error(message: 'User registration is disabled', payload: :registration_disabled) + return ServiceResponse.error(message: 'User registration is disabled', error_code: :registration_disabled) end transactional do |t| user = User.create(username: username, email: email, password: password) user.ensure_namespace - return ServiceResponse.error(message: 'User is invalid', payload: user.errors) unless user.persisted? + unless user.persisted? + return ServiceResponse.error(message: 'User is invalid', error_code: :invalid_user, details: user.errors) + end user_session = UserSession.create(user: user) unless user_session.persisted? t.rollback_and_return! ServiceResponse.error(message: 'UserSession is invalid', - payload: user_session.errors) + error_code: :invalid_user_session, + details: user_session.errors) end email_verification_response = EmailVerificationSendService.new( @@ -36,7 +39,8 @@ def execute unless email_verification_response.success? t.rollback_and_return! ServiceResponse.error(message: 'Failed to send verification email', - payload: email_verification_response.payload) + error_code: :email_verification_send_failed, + details: email_verification_response.payload) end AuditService.audit( diff --git a/lib/sagittarius/graphql/error_message_container.rb b/lib/sagittarius/graphql/error_container.rb similarity index 58% rename from lib/sagittarius/graphql/error_message_container.rb rename to lib/sagittarius/graphql/error_container.rb index 34a8d03f..d2b4e6a2 100644 --- a/lib/sagittarius/graphql/error_message_container.rb +++ b/lib/sagittarius/graphql/error_container.rb @@ -2,6 +2,6 @@ module Sagittarius module Graphql - ErrorMessageContainer = Struct.new(:message) + ErrorContainer = Struct.new(:error_code, :details) end end diff --git a/lib/sagittarius/graphql/service_response_error_container.rb b/lib/sagittarius/graphql/service_response_error_container.rb deleted file mode 100644 index fd4ef263..00000000 --- a/lib/sagittarius/graphql/service_response_error_container.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -module Sagittarius - module Graphql - ServiceResponseErrorContainer = Struct.new(:error_code) - end -end diff --git a/spec/graphql/types/errors/detailed_error_type_spec.rb b/spec/graphql/types/errors/detailed_error_type_spec.rb new file mode 100644 index 00000000..0669832f --- /dev/null +++ b/spec/graphql/types/errors/detailed_error_type_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe SagittariusSchema.types['DetailedError'] do + it 'returns possible types' do + expect(described_class.possible_types).to include( + Types::Errors::ActiveModelErrorType, + Types::Errors::MessageErrorType + ) + end + + describe '.resolve_type' do + it 'resolves active model errors' do + expect( + described_class.resolve_type(ActiveModel::Error.new(nil, :test, :invalid), {}) + ).to eq(Types::Errors::ActiveModelErrorType) + end + + it 'resolves message errors' do + expect( + described_class.resolve_type({ message: 'message' }, {}) + ).to eq(Types::Errors::MessageErrorType) + end + + it 'raises an error for invalid types' do + expect { described_class.resolve_type(build(:user), {}) }.to raise_error 'Unsupported DetailedErrorType' + end + end +end diff --git a/spec/graphql/types/errors/error_type_spec.rb b/spec/graphql/types/errors/error_type_spec.rb index 964d8910..6b47517c 100644 --- a/spec/graphql/types/errors/error_type_spec.rb +++ b/spec/graphql/types/errors/error_type_spec.rb @@ -3,28 +3,13 @@ require 'rails_helper' RSpec.describe SagittariusSchema.types['Error'] do - it 'returns possible types' do - expect(described_class.possible_types).to include( - Types::Errors::ActiveModelErrorType, - Types::Errors::MessageErrorType - ) + let(:fields) do + %w[ + errorCode + details + ] end - describe '.resolve_type' do - it 'resolves active model errors' do - expect( - described_class.resolve_type(ActiveModel::Error.new(nil, :test, :invalid), {}) - ).to eq(Types::Errors::ActiveModelErrorType) - end - - it 'resolves message errors' do - expect( - described_class.resolve_type(Sagittarius::Graphql::ErrorMessageContainer.new(message: 'message'), {}) - ).to eq(Types::Errors::MessageErrorType) - end - - it 'raises an error for invalid types' do - expect { described_class.resolve_type(build(:user), {}) }.to raise_error 'Unsupported ErrorType' - end - end + it { expect(described_class.graphql_name).to eq('Error') } + it { expect(described_class).to have_graphql_fields(fields) } end From 749ceb5f8016f87b432859ed6c08c6cb1686d74c Mon Sep 17 00:00:00 2001 From: Dario Pranjic Date: Thu, 13 Nov 2025 21:46:03 +0100 Subject: [PATCH 03/15] Implement new error response code in all services and mutation --- .../namespaces/members/assign_roles.rb | 10 ++++- .../mutations/namespaces/members/delete.rb | 2 +- .../mutations/namespaces/members/invite.rb | 7 +++- .../namespaces/projects/assign_runtimes.rb | 10 ++++- .../mutations/namespaces/projects/create.rb | 2 +- .../mutations/namespaces/projects/delete.rb | 2 +- .../namespaces/projects/flows/create.rb | 21 +++++----- .../namespaces/projects/flows/delete.rb | 2 +- .../mutations/namespaces/projects/update.rb | 2 +- .../namespaces/roles/assign_abilities.rb | 2 +- .../namespaces/roles/assign_projects.rb | 8 +++- .../mutations/namespaces/roles/create.rb | 5 ++- .../mutations/namespaces/roles/delete.rb | 2 +- .../mutations/namespaces/roles/update.rb | 2 +- app/graphql/mutations/organizations/delete.rb | 2 +- app/graphql/mutations/organizations/update.rb | 5 ++- app/graphql/mutations/runtimes/create.rb | 2 +- app/graphql/mutations/runtimes/delete.rb | 2 +- .../mutations/runtimes/rotate_token.rb | 2 +- app/graphql/mutations/runtimes/update.rb | 2 +- .../mutations/users/identity/unlink.rb | 2 +- app/graphql/mutations/users/logout.rb | 5 ++- app/graphql/mutations/users/password_reset.rb | 2 +- app/graphql/mutations/users/update.rb | 4 +- .../types/errors/detailed_error_type.rb | 5 ++- app/graphql/types/errors/error_type.rb | 1 - .../errors/flow_validation_error_code_enum.rb | 18 +++++++++ .../errors/flow_validation_error_type.rb | 19 ++++++++++ .../errors/flow_validation_severity_enum.rb | 17 +++++++++ .../application_settings_update_service.rb | 7 ++-- app/services/error_code.rb | 26 +++++++++++++ app/services/files/upload_service.rb | 9 +++-- .../members/assign_roles_service.rb | 6 +-- .../namespaces/members/delete_service.rb | 7 ++-- .../namespaces/members/invite_service.rb | 5 ++- .../projects/assign_runtimes_service.rb | 5 ++- .../namespaces/projects/create_service.rb | 5 ++- .../namespaces/projects/delete_service.rb | 5 ++- .../projects/flows/create_service.rb | 5 ++- .../projects/flows/delete_service.rb | 5 ++- .../validation/flow_validation_error_code.rb | 35 +++++++++++++++++ .../flows/validation/validation_result.rb | 2 + .../flows/validation/validation_service.rb | 5 ++- .../namespaces/projects/update_service.rb | 5 ++- .../roles/assign_abilities_service.rb | 4 +- .../roles/assign_projects_service.rb | 2 +- .../namespaces/roles/create_service.rb | 5 ++- .../namespaces/roles/delete_service.rb | 7 ++-- .../namespaces/roles/update_service.rb | 5 ++- app/services/organizations/create_service.rb | 8 ++-- app/services/organizations/delete_service.rb | 5 ++- app/services/organizations/update_service.rb | 5 ++- app/services/runtimes/create_service.rb | 7 +++- app/services/runtimes/delete_service.rb | 5 ++- app/services/runtimes/rotate_token_service.rb | 5 ++- app/services/runtimes/update_service.rb | 5 ++- app/services/service_response.rb | 9 ++++- .../users/email_verification_service.rb | 5 ++- app/services/users/identity/link_service.rb | 10 +++-- app/services/users/identity/login_service.rb | 13 +++++-- app/services/users/login_service.rb | 9 +++-- app/services/users/logout_service.rb | 4 +- .../users/mfa/backup_codes/rotate_service.rb | 4 +- .../users/mfa/totp/generate_secret_service.rb | 4 +- .../users/mfa/totp/validate_secret_service.rb | 11 +++--- app/services/users/password_reset_service.rb | 5 ++- app/services/users/update_service.rb | 16 ++++---- docs/graphql/enum/errorcodeenum.md | 28 ++++++++++++++ .../enum/flowvalidationerrorcodeenum.md | 16 ++++++++ .../enum/flowvalidationseverityenum.md | 12 ++++++ docs/graphql/object/error.md | 2 +- docs/graphql/object/errorcode.md | 12 ------ docs/graphql/object/flowvalidationerror.md | 14 +++++++ docs/graphql/union/detailederror.md | 1 + .../mutations/namespaces/licenses/create.rb | 2 +- .../mutations/namespaces/licenses/delete.rb | 2 +- .../ee/namespaces/members/invite_service.rb | 2 +- .../namespaces/licenses/create_service.rb | 5 ++- .../namespaces/licenses/delete_service.rb | 5 ++- .../licenses/create_mutation_spec.rb | 6 +-- .../licenses/delete_mutation_spec.rb | 2 +- .../namespaces/members/invite_service_spec.rb | 2 +- .../update_mutation_spec.rb | 2 +- .../members/assign_roles_mutation_spec.rb | 4 +- .../namespace/members/delete_mutation_spec.rb | 2 +- .../namespace/members/invite_mutation_spec.rb | 6 +-- .../projects/assign_runtimes_mutation_spec.rb | 4 +- .../projects/create_mutation_spec.rb | 6 +-- .../projects/delete_mutation_spec.rb | 2 +- .../projects/flows/delete_mutation_spec.rb | 2 +- .../projects/update_mutation_spec.rb | 6 +-- .../roles/assign_abilities_mutation_spec.rb | 4 +- .../roles/assign_projects_mutation_spec.rb | 4 +- .../namespace/roles/create_mutation_spec.rb | 6 +-- .../namespace/roles/delete_mutation_spec.rb | 2 +- .../namespace/roles/update_mutation_spec.rb | 6 +-- .../organizations/create_mutation_spec.rb | 3 +- .../organizations/delete_mutation_spec.rb | 2 +- .../organizations/update_mutation_spec.rb | 3 +- .../mutation/runtimes/create_mutation_spec.rb | 3 +- .../mutation/runtimes/update_mutation_spec.rb | 3 +- .../graphql/mutation/users/logout_spec.rb | 3 +- .../mutation/users/password_reset_spec.rb | 6 +-- .../graphql/mutation/users/register_spec.rb | 38 +++++++++++-------- .../graphql/mutation/users/update_spec.rb | 8 ++-- spec/requests/graphql_spec.rb | 2 +- ...pplication_settings_update_service_spec.rb | 4 +- .../members/assign_roles_service_spec.rb | 8 ++-- .../namespaces/members/delete_service_spec.rb | 6 +-- .../namespaces/members/invite_service_spec.rb | 4 +- .../projects/assign_runtimes_service_spec.rb | 4 +- .../projects/delete_service_spec.rb | 4 +- .../validation/validation_service_spec.rb | 2 +- .../projects/update_service_spec.rb | 4 +- .../roles/assign_abilities_service_spec.rb | 6 +-- .../roles/assign_projects_service_spec.rb | 4 +- .../namespaces/roles/create_service_spec.rb | 4 +- .../namespaces/roles/delete_service_spec.rb | 4 +- .../namespaces/roles/update_service_spec.rb | 4 +- .../organizations/delete_service_spec.rb | 4 +- spec/services/runtimes/delete_service_spec.rb | 2 +- .../runtimes/rotate_token_service_spec.rb | 2 +- .../users/email_verification_service_spec.rb | 2 +- .../users/identity/link_service_spec.rb | 2 +- .../users/identity/login_service_spec.rb | 6 +-- .../users/identity/register_service_spec.rb | 18 ++++----- spec/services/users/login_service_spec.rb | 4 +- .../mfa/backup_codes/rotate_service_spec.rb | 2 +- .../mfa/totp/generate_secret_service_spec.rb | 4 +- .../mfa/totp/validate_secret_service_spec.rb | 4 +- .../users/password_reset_service_spec.rb | 2 +- spec/services/users/register_service_spec.rb | 12 +++--- spec/services/users/update_service_spec.rb | 4 +- spec/support/helpers/graphql_helpers.rb | 15 +++----- 134 files changed, 550 insertions(+), 279 deletions(-) create mode 100644 app/graphql/types/errors/flow_validation_error_code_enum.rb create mode 100644 app/graphql/types/errors/flow_validation_error_type.rb create mode 100644 app/graphql/types/errors/flow_validation_severity_enum.rb create mode 100644 app/services/namespaces/projects/flows/validation/flow_validation_error_code.rb create mode 100644 docs/graphql/enum/flowvalidationerrorcodeenum.md create mode 100644 docs/graphql/enum/flowvalidationseverityenum.md delete mode 100644 docs/graphql/object/errorcode.md create mode 100644 docs/graphql/object/flowvalidationerror.md diff --git a/app/graphql/mutations/namespaces/members/assign_roles.rb b/app/graphql/mutations/namespaces/members/assign_roles.rb index b9ae2856..8ef45716 100644 --- a/app/graphql/mutations/namespaces/members/assign_roles.rb +++ b/app/graphql/mutations/namespaces/members/assign_roles.rb @@ -18,8 +18,14 @@ def resolve(member_id:, role_ids:) member = SagittariusSchema.object_from_id(member_id) roles = role_ids.map { |id| SagittariusSchema.object_from_id(id) } - return { namespace_member_roles: nil, errors: [create_message_error('Invalid member')] } if member.nil? - return { namespace_member_roles: nil, errors: [create_message_error('Invalid role')] } if roles.any?(&:nil?) + if member.nil? + return { namespace_member_roles: nil, + errors: [create_error(:namespace_member_not_found, 'Invalid member')] } + end + if roles.any?(&:nil?) + return { namespace_member_roles: nil, + errors: [create_error(:namespace_role_not_found, 'Invalid role')] } + end ::Namespaces::Members::AssignRolesService.new( current_authentication, diff --git a/app/graphql/mutations/namespaces/members/delete.rb b/app/graphql/mutations/namespaces/members/delete.rb index c302e054..2b9ffc83 100644 --- a/app/graphql/mutations/namespaces/members/delete.rb +++ b/app/graphql/mutations/namespaces/members/delete.rb @@ -16,7 +16,7 @@ def resolve(namespace_member_id:) if namespace_member.nil? return { namespace_member: nil, - errors: [create_message_error('Invalid member')] } + errors: [create_error(:namespace_member_not_found, 'Invalid member')] } end ::Namespaces::Members::DeleteService.new( diff --git a/app/graphql/mutations/namespaces/members/invite.rb b/app/graphql/mutations/namespaces/members/invite.rb index 2f022a7f..a2627ee8 100644 --- a/app/graphql/mutations/namespaces/members/invite.rb +++ b/app/graphql/mutations/namespaces/members/invite.rb @@ -16,8 +16,11 @@ def resolve(namespace_id:, user_id:) namespace = SagittariusSchema.object_from_id(namespace_id) user = SagittariusSchema.object_from_id(user_id) - return { namespace_member: nil, errors: [create_message_error('Invalid namespace')] } if namespace.nil? - return { namespace_member: nil, errors: [create_message_error('Invalid user')] } if user.nil? + if namespace.nil? + return { namespace_member: nil, + errors: [create_error(:namespace_not_found, 'Invalid namespace')] } + end + return { namespace_member: nil, errors: [create_error(:user_not_found, 'Invalid user')] } if user.nil? ::Namespaces::Members::InviteService.new( current_authentication, diff --git a/app/graphql/mutations/namespaces/projects/assign_runtimes.rb b/app/graphql/mutations/namespaces/projects/assign_runtimes.rb index 89e91b47..d842b6c9 100644 --- a/app/graphql/mutations/namespaces/projects/assign_runtimes.rb +++ b/app/graphql/mutations/namespaces/projects/assign_runtimes.rb @@ -17,8 +17,14 @@ def resolve(namespace_project_id:, runtime_ids:) namespace_project = SagittariusSchema.object_from_id(namespace_project_id) runtimes = runtime_ids.map { |runtime_id| SagittariusSchema.object_from_id(runtime_id) } - return { namespace_project: nil, errors: [create_message_error('Invalid project')] } if namespace_project.nil? - return { namespace_project: nil, errors: [create_message_error('Invalid runtime')] } if runtimes.any?(&:nil?) + if namespace_project.nil? + return { namespace_project: nil, + errors: [create_error(:project_not_found, 'Invalid project')] } + end + if runtimes.any?(&:nil?) + return { namespace_project: nil, + errors: [create_error(:runtime_not_found, 'Invalid runtime')] } + end ::Namespaces::Projects::AssignRuntimesService.new( current_authentication, diff --git a/app/graphql/mutations/namespaces/projects/create.rb b/app/graphql/mutations/namespaces/projects/create.rb index e617a4af..7f7c69cf 100644 --- a/app/graphql/mutations/namespaces/projects/create.rb +++ b/app/graphql/mutations/namespaces/projects/create.rb @@ -19,7 +19,7 @@ def resolve(namespace_id:, **params) if namespace.nil? return { organization_project: nil, - errors: [create_message_error('Invalid namespace')] } + errors: [create_error(:namespace_not_found, 'Invalid namespace')] } end ::Namespaces::Projects::CreateService.new( diff --git a/app/graphql/mutations/namespaces/projects/delete.rb b/app/graphql/mutations/namespaces/projects/delete.rb index c54b1eb1..48dda124 100644 --- a/app/graphql/mutations/namespaces/projects/delete.rb +++ b/app/graphql/mutations/namespaces/projects/delete.rb @@ -16,7 +16,7 @@ def resolve(namespace_project_id:) if project.nil? return { organization_project: nil, - errors: [create_message_error('Invalid project')] } + errors: [create_error(:namespace_project_not_found, 'Invalid project')] } end ::Namespaces::Projects::DeleteService.new( diff --git a/app/graphql/mutations/namespaces/projects/flows/create.rb b/app/graphql/mutations/namespaces/projects/flows/create.rb index 9e3eb04d..3cf13e60 100644 --- a/app/graphql/mutations/namespaces/projects/flows/create.rb +++ b/app/graphql/mutations/namespaces/projects/flows/create.rb @@ -17,10 +17,20 @@ class Create < BaseMutation def resolve(project_id:, flow:, **_params) project = SagittariusSchema.object_from_id(project_id) - return error('Invalid project id') if project.nil? + if project.nil? + return { + flow: nil, + errors: [create_error(:namespace_project_not_found, 'Invalid project id')], + } + end flow_type = SagittariusSchema.object_from_id(flow.type) - return error('Invalid flow type id') if flow_type.nil? + if flow_type.nil? + return { + flow: nil, + errors: [create_error(:flow_type_not_found, 'Invalid flow type id')], + } + end ::Namespaces::Projects::Flows::CreateService.new( current_authentication, @@ -31,13 +41,6 @@ def resolve(project_id:, flow:, **_params) name: flow.name ).execute.to_mutation_response(success_key: :flow) end - - def error(message) - { - flow: nil, - errors: [create_message_error(message)], - } - end end end end diff --git a/app/graphql/mutations/namespaces/projects/flows/delete.rb b/app/graphql/mutations/namespaces/projects/flows/delete.rb index f39c3a46..ad6d2fb2 100644 --- a/app/graphql/mutations/namespaces/projects/flows/delete.rb +++ b/app/graphql/mutations/namespaces/projects/flows/delete.rb @@ -17,7 +17,7 @@ def resolve(flow_id:) if flow.nil? return { flow: nil, - errors: [create_message_error('Invalid flow')] } + errors: [create_error(:flow_not_found, 'Invalid flow')] } end ::Namespaces::Projects::Flows::DeleteService.new( diff --git a/app/graphql/mutations/namespaces/projects/update.rb b/app/graphql/mutations/namespaces/projects/update.rb index 0b8aff52..9845da61 100644 --- a/app/graphql/mutations/namespaces/projects/update.rb +++ b/app/graphql/mutations/namespaces/projects/update.rb @@ -21,7 +21,7 @@ def resolve(namespace_project_id:, **params) if project.nil? return { organization_project: nil, - errors: [create_message_error('Invalid project')] } + errors: [create_error(:project_not_found, 'Invalid project')] } end params[:primary_runtime_id] = params[:primary_runtime_id]&.model_id if params.key?(:primary_runtime_id) diff --git a/app/graphql/mutations/namespaces/roles/assign_abilities.rb b/app/graphql/mutations/namespaces/roles/assign_abilities.rb index 710f0df7..ecea819d 100644 --- a/app/graphql/mutations/namespaces/roles/assign_abilities.rb +++ b/app/graphql/mutations/namespaces/roles/assign_abilities.rb @@ -16,7 +16,7 @@ class AssignAbilities < BaseMutation def resolve(role_id:, abilities:) role = SagittariusSchema.object_from_id(role_id) - return { abilities: nil, errors: [create_message_error('Invalid role')] } if role.nil? + return { abilities: nil, errors: [create_error(:namespace_role_not_found, 'Invalid role')] } if role.nil? ::Namespaces::Roles::AssignAbilitiesService.new( current_authentication, diff --git a/app/graphql/mutations/namespaces/roles/assign_projects.rb b/app/graphql/mutations/namespaces/roles/assign_projects.rb index 16c295a3..e23b9148 100644 --- a/app/graphql/mutations/namespaces/roles/assign_projects.rb +++ b/app/graphql/mutations/namespaces/roles/assign_projects.rb @@ -17,8 +17,12 @@ def resolve(role_id:, project_ids:) role = SagittariusSchema.object_from_id(role_id) projects = project_ids.map { |id| SagittariusSchema.object_from_id(id) } - return { projects: nil, errors: [create_message_error('Invalid role')] } if role.nil? - return { projects: nil, errors: [create_message_error('Invalid project')] } if projects.any?(&:nil?) + return { projects: nil, errors: [create_error(:namespace_role_not_found, 'Invalid role')] } if role.nil? + + if projects.any?(&:nil?) + return { projects: nil, + errors: [create_error(:namespace_project_not_found, 'Invalid project')] } + end ::Namespaces::Roles::AssignProjectsService.new( current_authentication, diff --git a/app/graphql/mutations/namespaces/roles/create.rb b/app/graphql/mutations/namespaces/roles/create.rb index 5b0fea76..b6299522 100644 --- a/app/graphql/mutations/namespaces/roles/create.rb +++ b/app/graphql/mutations/namespaces/roles/create.rb @@ -15,7 +15,10 @@ class Create < BaseMutation def resolve(namespace_id:, **params) namespace = SagittariusSchema.object_from_id(namespace_id) - return { namespace_role: nil, errors: [create_message_error('Invalid namespace')] } if namespace.nil? + if namespace.nil? + return { namespace_role: nil, + errors: [create_error(:namespace_not_found, 'Invalid namespace')] } + end ::Namespaces::Roles::CreateService.new( current_authentication, diff --git a/app/graphql/mutations/namespaces/roles/delete.rb b/app/graphql/mutations/namespaces/roles/delete.rb index 97d7b910..10dc8fa8 100644 --- a/app/graphql/mutations/namespaces/roles/delete.rb +++ b/app/graphql/mutations/namespaces/roles/delete.rb @@ -16,7 +16,7 @@ def resolve(namespace_role_id:) if namespace_role.nil? return { namespace_role: nil, - errors: [create_message_error('Invalid namespace role')] } + errors: [create_error(:namespace_role_not_found, 'Invalid namespace role')] } end ::Namespaces::Roles::DeleteService.new( diff --git a/app/graphql/mutations/namespaces/roles/update.rb b/app/graphql/mutations/namespaces/roles/update.rb index 43e9c270..3c0e8bd8 100644 --- a/app/graphql/mutations/namespaces/roles/update.rb +++ b/app/graphql/mutations/namespaces/roles/update.rb @@ -18,7 +18,7 @@ def resolve(namespace_role_id:, **params) if namespace_role.nil? return { namespace_role: nil, - errors: [create_message_error('Invalid namespace role')] } + errors: [create_error(:namespace_role_not_found, 'Invalid namespace role')] } end ::Namespaces::Roles::UpdateService.new( diff --git a/app/graphql/mutations/organizations/delete.rb b/app/graphql/mutations/organizations/delete.rb index 63aaa0c5..f10f4260 100644 --- a/app/graphql/mutations/organizations/delete.rb +++ b/app/graphql/mutations/organizations/delete.rb @@ -17,7 +17,7 @@ def resolve(organization_id:) if organization.nil? return { organization_role: nil, - errors: [create_message_error('Invalid organization')] } + errors: [create_error(:organization_not_found, 'Invalid organization')] } end response = ::Organizations::DeleteService.new( diff --git a/app/graphql/mutations/organizations/update.rb b/app/graphql/mutations/organizations/update.rb index b26e8cb1..75c22c32 100644 --- a/app/graphql/mutations/organizations/update.rb +++ b/app/graphql/mutations/organizations/update.rb @@ -15,7 +15,10 @@ class Update < BaseMutation def resolve(organization_id:, **params) organization = SagittariusSchema.object_from_id(organization_id) - return { organization: nil, errors: [create_message_error('Invalid organization')] } if organization.nil? + if organization.nil? + return { organization: nil, + errors: [create_error(:organization_not_found, 'Invalid organization')] } + end ::Organizations::UpdateService.new( current_authentication, diff --git a/app/graphql/mutations/runtimes/create.rb b/app/graphql/mutations/runtimes/create.rb index 09ad2b7a..ffb0b24a 100644 --- a/app/graphql/mutations/runtimes/create.rb +++ b/app/graphql/mutations/runtimes/create.rb @@ -17,7 +17,7 @@ def resolve(name:, namespace_id: nil, description: '') if namespace.nil? && namespace_id.present? return { runtime: nil, - errors: [create_message_error('Invalid namespace')] } + errors: [create_error(:namespace_not_found, 'Invalid namespace')] } end ::Runtimes::CreateService.new( diff --git a/app/graphql/mutations/runtimes/delete.rb b/app/graphql/mutations/runtimes/delete.rb index db7a41ef..82dd5a67 100644 --- a/app/graphql/mutations/runtimes/delete.rb +++ b/app/graphql/mutations/runtimes/delete.rb @@ -15,7 +15,7 @@ def resolve(runtime_id:) if runtime.nil? return { runtime: nil, - errors: [create_message_error('Invalid runtime')] } + errors: [create_error(:runtime_not_found, 'Invalid runtime')] } end ::Runtimes::DeleteService.new( diff --git a/app/graphql/mutations/runtimes/rotate_token.rb b/app/graphql/mutations/runtimes/rotate_token.rb index c1572ebb..fd00b0f2 100644 --- a/app/graphql/mutations/runtimes/rotate_token.rb +++ b/app/graphql/mutations/runtimes/rotate_token.rb @@ -15,7 +15,7 @@ def resolve(runtime_id:) if runtime.nil? return { runtime: nil, - errors: [create_message_error('Invalid runtime')] } + errors: [create_error(:runtime_not_found, 'Invalid runtime')] } end ::Runtimes::RotateTokenService.new( diff --git a/app/graphql/mutations/runtimes/update.rb b/app/graphql/mutations/runtimes/update.rb index f5819c5a..2514da7d 100644 --- a/app/graphql/mutations/runtimes/update.rb +++ b/app/graphql/mutations/runtimes/update.rb @@ -17,7 +17,7 @@ class Update < BaseMutation def resolve(runtime_id:, **params) runtime = SagittariusSchema.object_from_id(runtime_id) - return { runtime: nil, errors: [create_message_error('Invalid runtime')] } if runtime.nil? + return { runtime: nil, errors: [create_error(:runtime_not_found, 'Invalid runtime')] } if runtime.nil? ::Runtimes::UpdateService.new( current_authentication, diff --git a/app/graphql/mutations/users/identity/unlink.rb b/app/graphql/mutations/users/identity/unlink.rb index ef059749..17ecd741 100644 --- a/app/graphql/mutations/users/identity/unlink.rb +++ b/app/graphql/mutations/users/identity/unlink.rb @@ -16,7 +16,7 @@ def resolve(identity_id:) if user_identity.nil? return { user_identity: nil, - errors: [create_message_error('Invalid identity')] } + errors: [create_error(:identity_not_found, 'Invalid identity')] } end ::Users::Identity::UnlinkService.new( diff --git a/app/graphql/mutations/users/logout.rb b/app/graphql/mutations/users/logout.rb index 0b334368..fb60193a 100644 --- a/app/graphql/mutations/users/logout.rb +++ b/app/graphql/mutations/users/logout.rb @@ -13,7 +13,10 @@ class Logout < BaseMutation def resolve(user_session_id:) user_session = SagittariusSchema.object_from_id(user_session_id) - return { user_session: nil, errors: [create_message_error('Invalid user session')] } if user_session.nil? + if user_session.nil? + return { user_session: nil, + errors: [create_error(:user_session_not_found, 'Invalid user session')] } + end ::Users::LogoutService.new( current_authentication, diff --git a/app/graphql/mutations/users/password_reset.rb b/app/graphql/mutations/users/password_reset.rb index b3c9802d..4d28bcc4 100644 --- a/app/graphql/mutations/users/password_reset.rb +++ b/app/graphql/mutations/users/password_reset.rb @@ -19,7 +19,7 @@ class PasswordReset < BaseMutation def resolve(reset_token:, new_password:, new_password_confirmation:) if new_password != new_password_confirmation return { user: nil, - errors: [create_message_error('Invalid password repeat')] } + errors: [create_error(:invalid_password_repeat, 'Invalid password repeat')] } end message = ::Users::PasswordResetService.new( diff --git a/app/graphql/mutations/users/update.rb b/app/graphql/mutations/users/update.rb index eb6df523..d4d1efc3 100644 --- a/app/graphql/mutations/users/update.rb +++ b/app/graphql/mutations/users/update.rb @@ -27,10 +27,10 @@ class Update < BaseMutation def resolve(user_id:, mfa: nil, **params) user = SagittariusSchema.object_from_id(user_id) - return { user: nil, errors: [create_message_error('Invalid user')] } if user.nil? + return { user: nil, errors: [create_error(:user_not_found, 'Invalid user with provided id')] } if user.nil? if params[:password] != params.delete(:password_repeat) - return { user: nil, errors: [create_message_error('Invalid password repeat')] } + return { user: nil, errors: [create_error(:invalid_password_repeat, 'Invalid password repeat')] } end ::Users::UpdateService.new( diff --git a/app/graphql/types/errors/detailed_error_type.rb b/app/graphql/types/errors/detailed_error_type.rb index 191041f6..2fe9916c 100644 --- a/app/graphql/types/errors/detailed_error_type.rb +++ b/app/graphql/types/errors/detailed_error_type.rb @@ -7,10 +7,13 @@ class DetailedErrorType < Types::BaseUnion graphql_name 'DetailedError' # rubocop:enable GraphQL/GraphqlName description 'Represents a detailed error with either a message or an active model error' - possible_types Types::Errors::ActiveModelErrorType, Types::Errors::MessageErrorType + possible_types Types::Errors::ActiveModelErrorType, Types::Errors::MessageErrorType, + Types::Errors::FlowValidationErrorType def self.resolve_type(object, _ctx) case object + when Namespaces::Projects::Flows::Validation::ValidationResult + Types::Errors::FlowValidationErrorType when ActiveModel::Error Types::Errors::ActiveModelErrorType when Hash diff --git a/app/graphql/types/errors/error_type.rb b/app/graphql/types/errors/error_type.rb index bb7f0a9b..ad523a6a 100644 --- a/app/graphql/types/errors/error_type.rb +++ b/app/graphql/types/errors/error_type.rb @@ -10,7 +10,6 @@ class ErrorType < Types::BaseObject field :details, [Errors::DetailedErrorType], null: true, description: 'Detailed validation errors if applicable' field :error_code, ErrorCodeEnum, null: false, description: 'The code representing the error type' - end end end diff --git a/app/graphql/types/errors/flow_validation_error_code_enum.rb b/app/graphql/types/errors/flow_validation_error_code_enum.rb new file mode 100644 index 00000000..a3f5bfcf --- /dev/null +++ b/app/graphql/types/errors/flow_validation_error_code_enum.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Types + module Errors + # rubocop:disable GraphQL/GraphqlName -- we don't want the module prefix + class FlowValidationErrorCodeEnum < Types::BaseEnum + graphql_name 'FlowValidationErrorCodeEnum' + # rubocop:enable GraphQL/GraphqlName + description 'Represents the available error responses' + + ::Namespaces::Projects::Flows::Validation::FlowValidationErrorCode.error_codes.each do |error_code, details| + value error_code.upcase, details[:description], + value: error_code, + deprecation_reason: details.fetch(:deprecation_reason, nil) + end + end + end +end diff --git a/app/graphql/types/errors/flow_validation_error_type.rb b/app/graphql/types/errors/flow_validation_error_type.rb new file mode 100644 index 00000000..4732064b --- /dev/null +++ b/app/graphql/types/errors/flow_validation_error_type.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Types + module Errors + # rubocop:disable GraphQL/GraphqlName -- we don't want the module prefix + class FlowValidationErrorType < Types::BaseObject + graphql_name 'FlowValidationError' + # rubocop:enable GraphQL/GraphqlName + description 'Represents an flow validation error' + + field :details, Errors::ActiveModelErrorType, null: true, + description: 'Additional details about the validation error' + field :error_code, Errors::FlowValidationErrorCodeEnum, + null: false, description: 'The code representing the validation error type' + field :severity, Errors::FlowValidationSeverityEnum, null: false, + description: 'The severity of the validation error' + end + end +end diff --git a/app/graphql/types/errors/flow_validation_severity_enum.rb b/app/graphql/types/errors/flow_validation_severity_enum.rb new file mode 100644 index 00000000..a04969ae --- /dev/null +++ b/app/graphql/types/errors/flow_validation_severity_enum.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Types + module Errors + # rubocop:disable GraphQL/GraphqlName -- we don't want the module prefix + class FlowValidationSeverityEnum < Types::BaseEnum + graphql_name 'FlowValidationSeverityEnum' + # rubocop:enable GraphQL/GraphqlName + description 'Represents the severity of a flow validation error' + + value 'WARNING', 'A non-blocking validation warning', value: :warning + value 'ERROR', 'A blocking validation error', value: :error + value 'WEAK', 'A weak validation issue that may not need to be addressed', value: :weak + value 'TYPO', 'A minor typographical issue can also be blocking', value: :typo + end + end +end diff --git a/app/services/application_settings_update_service.rb b/app/services/application_settings_update_service.rb index 2954531b..3b443f66 100644 --- a/app/services/application_settings_update_service.rb +++ b/app/services/application_settings_update_service.rb @@ -12,19 +12,20 @@ def initialize(current_authentication, params) def execute unless Ability.allowed?(current_authentication, :update_application_setting) - return ServiceResponse.error(message: 'Missing permissions', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permissions', error_code: :missing_permission) end transactional do |t| params.each do |param, value| setting = ApplicationSetting.find_by(setting: param) if setting.blank? - t.rollback_and_return! ServiceResponse.error(message: 'Invalid setting', payload: :invalid_setting) + t.rollback_and_return! ServiceResponse.error(message: 'Invalid setting', error_code: :invalid_setting) end setting.value = value unless setting.save - t.rollback_and_return! ServiceResponse.error(message: 'Failed to update setting', payload: setting.errors) + t.rollback_and_return! ServiceResponse.error(message: 'Failed to update setting', + error_code: :invalid_setting, details: setting.errors) end AuditService.audit( diff --git a/app/services/error_code.rb b/app/services/error_code.rb index e5751375..29c7c867 100644 --- a/app/services/error_code.rb +++ b/app/services/error_code.rb @@ -38,6 +38,32 @@ def self.error_codes invalid_setting: { description: 'Invalid setting provided' }, invalid_user: { description: 'The user is invalid because of active model errors' }, invalid_password_repeat: { description: 'The provided password repeat does not match the password' }, + cannot_modify_admin: { description: 'Only administrators can modify admin status of users' }, + cannot_modify_own_admin: { description: 'Users cannot modify their own admin status' }, + user_not_found: { description: 'The user with the given identifier was not found' }, + invalid_user_identity: { description: 'The user identity is invalid because of active model errors' }, + invalid_user_session: { description: 'The user session is invalid because of active model errors' }, + invalid_runtime: { description: 'The runtime is invalid because of active model errors' }, + invalid_organization: { description: 'The organization is invalid because of active model errors' }, + invalid_namespace_role: { description: 'The namespace role is invalid because of active model errors' }, + invalid_namespace_project: { description: 'The namespace project is invalid because of active model errors' }, + flow_validation_failed: { description: 'The flow validation has failed' }, + failed_to_reset_password: { description: 'Failed to reset the user password' }, + loading_identity_failed: { description: 'Failed to load user identity from external provider' }, + invalid_flow_setting: { description: 'The flow setting is invalid because of active model errors' }, + invalid_namespace_member: { description: 'The flow setting is invalid because of active model errors' }, + invalid_attachment: { description: 'The attachment is invalid because of active model errors' }, + invalid_namespace_license: { description: 'The namespace license is invalid because of active model errors' }, + project_not_found: { description: 'The namespace project with the given identifier was not found' }, + runtime_not_found: { description: 'The runtime with the given identifier was not found' }, + namespace_not_found: { description: 'The namespace with the given identifier was not found' }, + flow_not_found: { description: 'The flow with the given identifier was not found' }, + namespace_role_not_found: { description: 'The namespace role with the given identifier was not found' }, + identity_not_found: { description: 'The external identity with the given identifier was not found' }, + user_session_not_found: { description: 'The user session with the given identifier was not found' }, + namespace_project_not_found: { description: 'The namespace project with the given identifier was not found' }, + namespace_member_not_found: { description: 'The namespace member with the given identifier was not found' }, + license_not_found: { description: 'The namespace license with the given identifier was not found' }, primary_level_not_found: { description: '', deprecation_reason: 'Outdated concept' }, secondary_level_not_found: { description: '', deprecation_reason: 'Outdated concept' }, tertiary_level_exceeds_parameters: { description: '', deprecation_reason: 'Outdated concept' }, diff --git a/app/services/files/upload_service.rb b/app/services/files/upload_service.rb index bcfe24ff..df68acf7 100644 --- a/app/services/files/upload_service.rb +++ b/app/services/files/upload_service.rb @@ -15,18 +15,21 @@ def initialize(current_authentication, object:, attachment:, attachment_name:) def execute if object.nil? || current_authentication.nil? || attachment.nil? || attachment_name.nil? - return ServiceResponse.error(message: 'Missing parameter', payload: :missing_parameter) + return ServiceResponse.error(message: 'Missing parameter', error_code: :missing_parameter) end unless Ability.allowed?(current_authentication, :"update_attachment_#{attachment_name}", object) - return ServiceResponse.error(message: 'Missing permissions', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permissions', error_code: :missing_permission) end # No other check because the permission will fail object.send(attachment_name).attach attachment - ServiceResponse.success(message: 'Failed to save object', payload: object.errors) unless object.save + unless object.save + ServiceResponse.error(message: 'Failed to save object', error_code: :invalid_attachment, + details: object.errors) + end AuditService.audit( :attachment_updated, diff --git a/app/services/namespaces/members/assign_roles_service.rb b/app/services/namespaces/members/assign_roles_service.rb index de033b22..32268bcc 100644 --- a/app/services/namespaces/members/assign_roles_service.rb +++ b/app/services/namespaces/members/assign_roles_service.rb @@ -16,13 +16,13 @@ def initialize(current_authentication, member, roles) def execute namespace = member.namespace unless Ability.allowed?(current_authentication, :assign_member_roles, member) - return ServiceResponse.error(message: 'Missing permissions', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permissions', error_code: :missing_permission) end unless roles.map(&:namespace).all? { |t| t == namespace } return ServiceResponse.error( message: 'Roles and member belong to different namespaces', - payload: :inconsistent_namespace + error_code: :inconsistent_namespace ) end @@ -78,7 +78,7 @@ def check_last_admin_user(t) .exists?(abilities: { ability: :namespace_administrator }) t.rollback_and_return! ServiceResponse.error( message: 'Cannot remove last administrator from namespace', - payload: :cannot_remove_last_administrator + error_code: :cannot_remove_last_administrator ) end end diff --git a/app/services/namespaces/members/delete_service.rb b/app/services/namespaces/members/delete_service.rb index cc4aac2d..f89e9a2e 100644 --- a/app/services/namespaces/members/delete_service.rb +++ b/app/services/namespaces/members/delete_service.rb @@ -14,7 +14,7 @@ def initialize(current_authentication, namespace_member) def execute unless Ability.allowed?(current_authentication, :delete_member, namespace_member) - return ServiceResponse.error(message: 'Missing permissions', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permissions', error_code: :missing_permission) end transactional do |t| @@ -22,7 +22,8 @@ def execute if namespace_member.persisted? t.rollback_and_return! ServiceResponse.error(message: 'Failed to delete namespace member', - payload: namespace_member.errors) + error_code: :invalid_namespace_member, + details: namespace_member.errors) end check_last_administrator(t) @@ -49,7 +50,7 @@ def check_last_administrator(t) .exists?(abilities: { ability: :namespace_administrator }) t.rollback_and_return! ServiceResponse.error( message: 'Cannot remove last administrator from namespace', - payload: :cannot_remove_last_administrator + error_code: :cannot_remove_last_administrator ) end end diff --git a/app/services/namespaces/members/invite_service.rb b/app/services/namespaces/members/invite_service.rb index 30323929..af6be59d 100644 --- a/app/services/namespaces/members/invite_service.rb +++ b/app/services/namespaces/members/invite_service.rb @@ -15,7 +15,7 @@ def initialize(current_authentication, namespace, user) def execute unless Ability.allowed?(current_authentication, :invite_member, namespace) - return ServiceResponse.error(message: 'Missing permissions', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permissions', error_code: :missing_permission) end transactional do |t| @@ -25,7 +25,8 @@ def execute unless namespace_member.persisted? t.rollback_and_return! ServiceResponse.error(message: 'Failed to save namespace member', - payload: namespace_member.errors) + error_code: :invalid_namespace_member, + details: namespace_member.errors) end AuditService.audit( diff --git a/app/services/namespaces/projects/assign_runtimes_service.rb b/app/services/namespaces/projects/assign_runtimes_service.rb index dc0646d4..a37ec69e 100644 --- a/app/services/namespaces/projects/assign_runtimes_service.rb +++ b/app/services/namespaces/projects/assign_runtimes_service.rb @@ -15,7 +15,7 @@ def initialize(current_authentication, namespace_project, runtimes) def execute unless Ability.allowed?(current_authentication, :assign_project_runtimes, namespace_project) - return ServiceResponse.error(message: 'Missing permission', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission) end transactional do |t| @@ -27,7 +27,8 @@ def execute unless namespace_project.save t.rollback_and_return! ServiceResponse.error( message: 'Failed to assign runtimes to project', - payload: namespace_project.errors + error_code: :invalid_namespace_project, + details: namespace_project.errors ) end diff --git a/app/services/namespaces/projects/create_service.rb b/app/services/namespaces/projects/create_service.rb index 9cb88ce7..a378296f 100644 --- a/app/services/namespaces/projects/create_service.rb +++ b/app/services/namespaces/projects/create_service.rb @@ -16,7 +16,7 @@ def initialize(current_authentication, namespace:, name:, **params) def execute unless Ability.allowed?(current_authentication, :create_namespace_project, namespace) - return ServiceResponse.error(message: 'Missing permission', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission) end transactional do |t| @@ -24,7 +24,8 @@ def execute unless project.persisted? t.rollback_and_return! ServiceResponse.error( message: 'Failed to create project', - payload: project.errors + error_code: :invalid_namespace_project, + details: project.errors ) end diff --git a/app/services/namespaces/projects/delete_service.rb b/app/services/namespaces/projects/delete_service.rb index d5d29e97..056e63fe 100644 --- a/app/services/namespaces/projects/delete_service.rb +++ b/app/services/namespaces/projects/delete_service.rb @@ -14,7 +14,7 @@ def initialize(current_authentication, namespace_project) def execute unless Ability.allowed?(current_authentication, :delete_namespace_project, namespace_project) - return ServiceResponse.error(message: 'Missing permission', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission) end transactional do |t| @@ -23,7 +23,8 @@ def execute if namespace_project.persisted? t.rollback_and_return! ServiceResponse.error( message: 'Failed to delete project', - payload: namespace_project.errors + error_code: :invalid_namespace_project, + details: namespace_project.errors ) end diff --git a/app/services/namespaces/projects/flows/create_service.rb b/app/services/namespaces/projects/flows/create_service.rb index eb7df728..fd767319 100644 --- a/app/services/namespaces/projects/flows/create_service.rb +++ b/app/services/namespaces/projects/flows/create_service.rb @@ -16,7 +16,7 @@ def initialize(current_authentication, namespace_project:, **params) def execute unless Ability.allowed?(current_authentication, :create_flow, namespace_project) - return ServiceResponse.error(message: 'Missing permission', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission) end transactional do |t| @@ -28,7 +28,8 @@ def execute if setting.invalid? t.rollback_and_return! ServiceResponse.error( message: 'Invalid flow setting', - payload: setting.errors + error_code: :invalid_flow_setting, + details: setting.errors ) end diff --git a/app/services/namespaces/projects/flows/delete_service.rb b/app/services/namespaces/projects/flows/delete_service.rb index 1392db8d..423c7a41 100644 --- a/app/services/namespaces/projects/flows/delete_service.rb +++ b/app/services/namespaces/projects/flows/delete_service.rb @@ -15,7 +15,7 @@ def initialize(current_authentication, flow:) def execute unless Ability.allowed?(current_authentication, :delete_flow, flow) - return ServiceResponse.error(message: 'Missing permission', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission) end transactional do |t| @@ -24,7 +24,8 @@ def execute if flow.persisted? t.rollback_and_return! ServiceResponse.error( message: 'Failed to delete flow', - payload: flow.errors + error_code: :invalid_flow, + details: flow.errors ) end diff --git a/app/services/namespaces/projects/flows/validation/flow_validation_error_code.rb b/app/services/namespaces/projects/flows/validation/flow_validation_error_code.rb new file mode 100644 index 00000000..d6745ebf --- /dev/null +++ b/app/services/namespaces/projects/flows/validation/flow_validation_error_code.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Namespaces + module Projects + module Flows + module Validation + class FlowValidationErrorCode + InvalidErrorCode = Class.new(StandardError) + + def self.validate_error_code!(error_code) + return unless error_code.is_a?(Symbol) + return if Rails.env.production? + + raise InvalidErrorCode, error_code unless error_codes.include?(error_code) + end + + # rubocop:disable Layout/LineLength -- We want each description on a single line for readability + def self.error_codes + { + data_type_identifier_runtime_mismatch: { description: 'The data type identifier runtime does not match the flow type runtime.' }, + data_type_identifier_generic_key_not_found: { description: 'The generic key for the data type identifier was not found.' }, + data_type_rule_model_invalid: { description: 'The data type rule model is invalid.' }, + data_type_runtime_mismatch: { description: 'The data type runtime does not match the flow type runtime.' }, + flow_setting_model_invalid: { description: 'The flow setting model is invalid.' }, + flow_type_runtime_mismatch: { description: 'The flow type runtime does not match the project primary runtime.' }, + no_primary_runtime: { description: 'The project does not have a primary runtime set.' }, + node_function_runtime_mismatch: { description: 'The node function runtime does not match the project primary runtime.' }, + } + end + # rubocop:enable Layout/LineLength + end + end + end + end +end diff --git a/app/services/namespaces/projects/flows/validation/validation_result.rb b/app/services/namespaces/projects/flows/validation/validation_result.rb index 726ff6f8..c635706b 100644 --- a/app/services/namespaces/projects/flows/validation/validation_result.rb +++ b/app/services/namespaces/projects/flows/validation/validation_result.rb @@ -24,6 +24,8 @@ def self.warning(error_code, details = nil) attr_reader :type, :error_code, :details def initialize(type:, error_code:, details:) + FlowValidationErrorCode.validate_error_code!(error_code) + @type = type @error_code = error_code @details = details diff --git a/app/services/namespaces/projects/flows/validation/validation_service.rb b/app/services/namespaces/projects/flows/validation/validation_service.rb index ec719459..33d8cab5 100644 --- a/app/services/namespaces/projects/flows/validation/validation_service.rb +++ b/app/services/namespaces/projects/flows/validation/validation_service.rb @@ -73,7 +73,10 @@ def execute flow.flow_type ).execute - return ServiceResponse.error(message: 'Flow validation failed', payload: errors) if errors.any? + if errors.any? + return ServiceResponse.error(message: 'Flow validation failed', error_code: :flow_validation_failed, + details: errors) + end ServiceResponse.success(message: 'Validation service executed successfully', payload: flow) end diff --git a/app/services/namespaces/projects/update_service.rb b/app/services/namespaces/projects/update_service.rb index 4cfe346e..a68a4728 100644 --- a/app/services/namespaces/projects/update_service.rb +++ b/app/services/namespaces/projects/update_service.rb @@ -15,7 +15,7 @@ def initialize(current_authentication, namespace_project, **params) def execute unless Ability.allowed?(current_authentication, :update_namespace_project, namespace_project) - return ServiceResponse.error(message: 'Missing permission', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission) end params[:primary_runtime_id] = params.delete(:primary_runtime)&.id if params.key?(:primary_runtime) @@ -25,7 +25,8 @@ def execute unless success t.rollback_and_return! ServiceResponse.error( message: 'Failed to update namespace project', - payload: namespace_project.errors + error_code: :invalid_namespace_project, + details: namespace_project.errors ) end diff --git a/app/services/namespaces/roles/assign_abilities_service.rb b/app/services/namespaces/roles/assign_abilities_service.rb index 29dcd8ba..dc5d1460 100644 --- a/app/services/namespaces/roles/assign_abilities_service.rb +++ b/app/services/namespaces/roles/assign_abilities_service.rb @@ -16,7 +16,7 @@ def initialize(current_authentication, role, abilities) def execute namespace = role.namespace unless Ability.allowed?(current_authentication, :assign_role_abilities, role) - return ServiceResponse.error(message: 'Missing permissions', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permissions', error_code: :missing_permission) end transactional do |t| @@ -66,7 +66,7 @@ def check_admin_existing(t) abilities.include?(:namespace_administrator) t.rollback_and_return! ServiceResponse.error( message: 'Cannot remove the last administrator ability', - payload: :cannot_remove_last_admin_ability + error_code: :cannot_remove_last_admin_ability ) end end diff --git a/app/services/namespaces/roles/assign_projects_service.rb b/app/services/namespaces/roles/assign_projects_service.rb index cdf80068..ce8688d3 100644 --- a/app/services/namespaces/roles/assign_projects_service.rb +++ b/app/services/namespaces/roles/assign_projects_service.rb @@ -16,7 +16,7 @@ def initialize(current_authentication, role, projects) def execute namespace = role.namespace unless Ability.allowed?(current_authentication, :assign_role_projects, role) - return ServiceResponse.error(message: 'Missing permissions', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permissions', error_code: :missing_permission) end transactional do |t| diff --git a/app/services/namespaces/roles/create_service.rb b/app/services/namespaces/roles/create_service.rb index 61f3eefb..45449bcc 100644 --- a/app/services/namespaces/roles/create_service.rb +++ b/app/services/namespaces/roles/create_service.rb @@ -15,14 +15,15 @@ def initialize(current_authentication, namespace, params) def execute unless Ability.allowed?(current_authentication, :create_namespace_role, namespace) - return ServiceResponse.error(message: 'Missing permissions', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permissions', error_code: :missing_permission) end transactional do namespace_role = NamespaceRole.create(namespace: namespace, **params) unless namespace_role.persisted? - return ServiceResponse.error(message: 'Failed to save namespace role', payload: namespace_role.errors) + return ServiceResponse.error(message: 'Failed to save namespace role', error_code: :invalid_namespace_role, + details: namespace_role.errors) end AuditService.audit( diff --git a/app/services/namespaces/roles/delete_service.rb b/app/services/namespaces/roles/delete_service.rb index 4f8b0383..425c6175 100644 --- a/app/services/namespaces/roles/delete_service.rb +++ b/app/services/namespaces/roles/delete_service.rb @@ -14,7 +14,7 @@ def initialize(current_authentication, namespace_role) def execute unless Ability.allowed?(current_authentication, :delete_namespace_role, namespace_role) - return ServiceResponse.error(message: 'Missing permissions', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permissions', error_code: :missing_permission) end if !namespace_role.namespace.has_owner? && @@ -22,14 +22,15 @@ def execute .joins(:abilities) .exists?(abilities: { ability: :namespace_administrator }) return ServiceResponse.error(message: 'Cannot delete last administrator role', - payload: :cannot_delete_last_admin_role) + error_code: :cannot_delete_last_admin_role) end transactional do namespace_role.delete if namespace_role.persisted? - return ServiceResponse.error(message: 'Failed to delete namespace role', payload: namespace_role.errors) + return ServiceResponse.error(message: 'Failed to delete namespace role', + error_code: :invalid_namespace_role, details: namespace_role.errors) end AuditService.audit( diff --git a/app/services/namespaces/roles/update_service.rb b/app/services/namespaces/roles/update_service.rb index b4b2b7b9..9b6ca931 100644 --- a/app/services/namespaces/roles/update_service.rb +++ b/app/services/namespaces/roles/update_service.rb @@ -15,7 +15,7 @@ def initialize(current_authentication, namespace_role, params) def execute unless Ability.allowed?(current_authentication, :update_namespace_role, namespace_role) - return ServiceResponse.error(message: 'Missing permission', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission) end transactional do |t| @@ -23,7 +23,8 @@ def execute unless success t.rollback_and_return! ServiceResponse.error( message: 'Failed to update namespace role', - payload: namespace_role.errors + error_code: :invalid_namespace_role, + details: namespace_role.errors ) end diff --git a/app/services/organizations/create_service.rb b/app/services/organizations/create_service.rb index c4bf5923..7c1a7520 100644 --- a/app/services/organizations/create_service.rb +++ b/app/services/organizations/create_service.rb @@ -13,7 +13,7 @@ def initialize(current_authentication, name:) def execute unless Ability.allowed?(current_authentication, :create_organization) - return ServiceResponse.error(message: 'Missing permission', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission) end transactional do |t| @@ -21,7 +21,8 @@ def execute unless organization.persisted? t.rollback_and_return! ServiceResponse.error( message: 'Failed to create organization', - payload: organization.errors + error_code: :invalid_organization, + details: organization.errors ) end @@ -51,7 +52,8 @@ def create_object(t, model, **params) unless created_object.persisted? t.rollback_and_return! ServiceResponse.error(message: "Failed to create #{model}", - payload: created_object.errors) + error_code: :"invalid_#{model.name.underscore}", + details: created_object.errors) end created_object diff --git a/app/services/organizations/delete_service.rb b/app/services/organizations/delete_service.rb index 42425df1..219bde90 100644 --- a/app/services/organizations/delete_service.rb +++ b/app/services/organizations/delete_service.rb @@ -13,7 +13,7 @@ def initialize(current_authentication, organization) def execute unless Ability.allowed?(current_authentication, :delete_organization, organization) - return ServiceResponse.error(message: 'Missing permission', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission) end transactional do |t| @@ -21,7 +21,8 @@ def execute if organization.persisted? t.rollback_and_return! ServiceResponse.error(message: 'Failed to delete organization', - payload: organization.errors) + error_code: :invalid_organization, + details: organization.errors) end AuditService.audit( diff --git a/app/services/organizations/update_service.rb b/app/services/organizations/update_service.rb index 4008e0bd..00ac1d23 100644 --- a/app/services/organizations/update_service.rb +++ b/app/services/organizations/update_service.rb @@ -14,7 +14,7 @@ def initialize(current_authentication, organization, params) def execute unless Ability.allowed?(current_authentication, :update_organization, organization) - return ServiceResponse.error(message: 'Missing permission', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission) end transactional do |t| @@ -22,7 +22,8 @@ def execute unless success t.rollback_and_return! ServiceResponse.error( message: 'Failed to update organization', - payload: organization.errors + error_code: :invalid_organization, + details: organization.errors ) end diff --git a/app/services/runtimes/create_service.rb b/app/services/runtimes/create_service.rb index 7a033877..f98953cd 100644 --- a/app/services/runtimes/create_service.rb +++ b/app/services/runtimes/create_service.rb @@ -15,12 +15,15 @@ def initialize(current_authentication, namespace, name, **params) def execute unless Ability.allowed?(current_authentication, :create_runtime, namespace || :global) - return ServiceResponse.error(message: 'Missing permissions', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permissions', error_code: :missing_permission) end transactional do runtime = Runtime.create(namespace: namespace, name: name, **params) - return ServiceResponse.error(message: 'Runtime is invalid', payload: runtime.errors) unless runtime.persisted? + unless runtime.persisted? + return ServiceResponse.error(message: 'Runtime is invalid', error_code: :invalid_runtime, + details: runtime.errors) + end AuditService.audit( :runtime_created, diff --git a/app/services/runtimes/delete_service.rb b/app/services/runtimes/delete_service.rb index d55914c1..83aaf54a 100644 --- a/app/services/runtimes/delete_service.rb +++ b/app/services/runtimes/delete_service.rb @@ -13,7 +13,7 @@ def initialize(current_authentication, runtime) def execute unless Ability.allowed?(current_authentication, :delete_runtime, runtime || :global) - return ServiceResponse.error(message: 'Missing permissions', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permissions', error_code: :missing_permission) end transactional do |t| @@ -21,7 +21,8 @@ def execute if runtime.persisted? t.rollback_and_return! ServiceResponse.error(message: 'Failed to delete organization', - payload: runtime.errors) + error_code: :invalid_runtime, + details: runtime.errors) end AuditService.audit( diff --git a/app/services/runtimes/rotate_token_service.rb b/app/services/runtimes/rotate_token_service.rb index 6757179b..29fdc42b 100644 --- a/app/services/runtimes/rotate_token_service.rb +++ b/app/services/runtimes/rotate_token_service.rb @@ -13,14 +13,15 @@ def initialize(current_authentication, runtime) def execute unless Ability.allowed?(current_authentication, :rotate_runtime_token, runtime || :global) - return ServiceResponse.error(message: 'Missing permissions', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permissions', error_code: :missing_permission) end transactional do |t| runtime.regenerate_token! unless runtime.save t.rollback_and_return! ServiceResponse.error(message: 'Failed to rotate runtime token', - payload: runtime.errors) + error_code: :invalid_runtime, + details: runtime.errors) end AuditService.audit( :runtime_token_rotated, diff --git a/app/services/runtimes/update_service.rb b/app/services/runtimes/update_service.rb index 33985374..b49de03a 100644 --- a/app/services/runtimes/update_service.rb +++ b/app/services/runtimes/update_service.rb @@ -14,7 +14,7 @@ def initialize(current_authentication, runtime, params) def execute unless Ability.allowed?(current_authentication, :update_runtime, runtime || :global) - return ServiceResponse.error(message: 'Missing permissions', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permissions', error_code: :missing_permission) end transactional do |t| @@ -22,7 +22,8 @@ def execute unless success t.rollback_and_return! ServiceResponse.error( message: 'Failed to update runtime', - payload: runtime.errors + error_code: :invalid_runtime, + details: runtime.errors ) end diff --git a/app/services/service_response.rb b/app/services/service_response.rb index 315925ec..96bab5f7 100644 --- a/app/services/service_response.rb +++ b/app/services/service_response.rb @@ -55,7 +55,14 @@ def to_mutation_response(success_key: :object) payload[:details].errors else Array.wrap(payload[:details]).map do |message| - { message: message } + case message + when String + { message: message } + when Namespaces::Projects::Flows::Validation::Validation::ValidationResult + message + else + raise "Unsupported error detail type: #{message.class.name}" + end end end diff --git a/app/services/users/email_verification_service.rb b/app/services/users/email_verification_service.rb index f8a98c11..2a831fb3 100644 --- a/app/services/users/email_verification_service.rb +++ b/app/services/users/email_verification_service.rb @@ -15,13 +15,14 @@ def execute user = User.find_by_token_for(:email_verification, verification_code) unless Ability.allowed?(current_authentication, :verify_email, user) - return ServiceResponse.error(message: 'Missing permission', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission) end transactional do |t| user.email_verified_at = Time.zone.now unless user.save - t.rollback_and_return! ServiceResponse.error(message: 'Failed to set email to verified', payload: user.errors) + t.rollback_and_return! ServiceResponse.error(message: 'Failed to set email to verified', + error_code: :invalid_user, details: user.errors) end AuditService.audit( diff --git a/app/services/users/identity/link_service.rb b/app/services/users/identity/link_service.rb index c14891ae..74fd2d13 100644 --- a/app/services/users/identity/link_service.rb +++ b/app/services/users/identity/link_service.rb @@ -30,14 +30,18 @@ def execute provider_id: provider_id) unless user_identity.persisted? - t.rollback_and_return! ServiceResponse.error(payload: user_identity.errors, - message: 'An error occurred while creating user identity') + t.rollback_and_return! ServiceResponse.error( + error_code: :invalid_user_identity, + details: user_identity.errors, + message: 'An error occurred while creating user identity' + ) end current_user.user_identities << user_identity unless current_user.save - t.rollback_and_return! ServiceResponse.error(payload: current_user.errors, message: 'Failed to save user') + t.rollback_and_return! ServiceResponse.error(error_code: :invalid_user, details: current_user.errors, + message: 'Failed to save user') end AuditService.audit( diff --git a/app/services/users/identity/login_service.rb b/app/services/users/identity/login_service.rb index ea76ccf5..7f4ab4d5 100644 --- a/app/services/users/identity/login_service.rb +++ b/app/services/users/identity/login_service.rb @@ -4,6 +4,7 @@ module Users module Identity class LoginService < BaseService include Sagittarius::Database::Transactional + include Code0::ZeroTrack::Loggable attr_reader :provider_id, :args @@ -17,16 +18,19 @@ def execute begin identity = identity_provider.load_identity(provider_id, args) rescue Code0::Identities::Error => e - return ServiceResponse.error(payload: e, message: 'An error occurred while loading external identity') + logger.warn(message: 'Failed to load external identity', provider_id: provider_id, error: e.message, + backtrace: e.backtrace) + return ServiceResponse.error(error_code: :loading_identity_failed, + message: 'An error occurred while loading external identity') end if identity.nil? - return ServiceResponse.error(payload: :invalid_external_identity, message: 'External identity is nil') + return ServiceResponse.error(error_code: :invalid_external_identity, message: 'External identity is nil') end user_identity = UserIdentity.find_by(provider_id: identity.provider.to_s, identifier: identity.identifier) if user_identity.nil? - return ServiceResponse.error(payload: :external_identity_does_not_exist, + return ServiceResponse.error(error_code: :external_identity_does_not_exist, message: 'No user with that external identity exists, please register first') end @@ -36,7 +40,8 @@ def execute user_session = UserSession.create(user: user) unless user_session.persisted? t.rollback_and_return! ServiceResponse.error(message: 'UserSession is invalid', - payload: user_session.errors) + error_code: :invalid_user_session, + details: user_session.errors) end AuditService.audit( diff --git a/app/services/users/login_service.rb b/app/services/users/login_service.rb index be128a02..e3a7bfec 100644 --- a/app/services/users/login_service.rb +++ b/app/services/users/login_service.rb @@ -16,27 +16,28 @@ def execute user = User.authenticate_by(args) if user.nil? logger.info(message: 'Failed login', username: args[:username], email: args[:email]) - return ServiceResponse.error(message: 'Invalid login data', payload: :invalid_login_data) + return ServiceResponse.error(message: 'Invalid login data', error_code: :invalid_login_data) end transactional do |t| if mfa.present? && !user.mfa_enabled? t.rollback_and_return! ServiceResponse.error(message: 'Tried to login via MFA even if mfa is disabled', - payload: :mfa_failed) + error_code: :mfa_failed) end mfa_passed, mfa_type = user.validate_mfa!(mfa) if !mfa_passed && user.mfa_enabled? t.rollback_and_return! ServiceResponse.error(message: 'MFA failed', - payload: :mfa_failed) + error_code: :mfa_failed) end user_session = UserSession.create(user: user) unless user_session.persisted? logger.warn(message: 'Failed to create valid session for user', user_id: user.id, username: user.username) t.rollback_and_return! ServiceResponse.error(message: 'UserSession is invalid', - payload: user_session.errors) + error_code: :invalid_user_session, + details: user_session.errors) end AuditService.audit( diff --git a/app/services/users/logout_service.rb b/app/services/users/logout_service.rb index 1b2761bf..201b3804 100644 --- a/app/services/users/logout_service.rb +++ b/app/services/users/logout_service.rb @@ -13,7 +13,7 @@ def initialize(current_authentication, user_session) def execute unless Ability.allowed?(current_authentication, :logout_session, user_session) - return ServiceResponse.error(payload: :missing_permission) + return ServiceResponse.error(error_code: :missing_permission) end user_session.active = false @@ -23,7 +23,7 @@ def execute ServiceResponse.success(message: 'Logged out session', payload: user_session) else logger.warn(message: 'Failed to log out session', session_id: user_session.id, user_id: user_session.user_id) - ServiceResponse.error(payload: user_session.errors) + ServiceResponse.error(error_code: user_session.errors) end end end diff --git a/app/services/users/mfa/backup_codes/rotate_service.rb b/app/services/users/mfa/backup_codes/rotate_service.rb index 04f741ba..7db792d4 100644 --- a/app/services/users/mfa/backup_codes/rotate_service.rb +++ b/app/services/users/mfa/backup_codes/rotate_service.rb @@ -15,7 +15,7 @@ def initialize(current_authentication) def execute unless Ability.allowed?(current_authentication, :manage_mfa, current_user) - return ServiceResponse.error(payload: :missing_permission) + return ServiceResponse.error(error_code: :missing_permission) end transactional do |t| @@ -23,7 +23,7 @@ def execute old_codes.delete_all unless old_codes.count.zero? t.rollback_and_return! ServiceResponse.error(message: 'Failed to delete old backup codes', - payload: :failed_to_invalidate_old_backup_codes) + error_code: :failed_to_invalidate_old_backup_codes) end new_codes = generate_new_codes(t) diff --git a/app/services/users/mfa/totp/generate_secret_service.rb b/app/services/users/mfa/totp/generate_secret_service.rb index 93a3659b..3c7d7f29 100644 --- a/app/services/users/mfa/totp/generate_secret_service.rb +++ b/app/services/users/mfa/totp/generate_secret_service.rb @@ -12,11 +12,11 @@ def initialize(current_authentication) def execute unless Ability.allowed?(current_authentication, :manage_mfa, current_authentication.user) - return ServiceResponse.error(payload: :missing_permission) + return ServiceResponse.error(error_code: :missing_permission) end unless current_authentication.user.totp_secret.nil? - return ServiceResponse.error(payload: :totp_secret_already_set) + return ServiceResponse.error(error_code: :totp_secret_already_set) end totp_secret = ROTP::Base32.random diff --git a/app/services/users/mfa/totp/validate_secret_service.rb b/app/services/users/mfa/totp/validate_secret_service.rb index b02aea18..c823a5ca 100644 --- a/app/services/users/mfa/totp/validate_secret_service.rb +++ b/app/services/users/mfa/totp/validate_secret_service.rb @@ -17,23 +17,24 @@ def initialize(current_authentication, secret, current_totp) def execute unless Ability.allowed?(current_authentication, :manage_mfa, current_user) - return ServiceResponse.error(payload: :missing_permission) + return ServiceResponse.error(error_code: :missing_permission) end - return ServiceResponse.error(payload: :totp_secret_already_set) unless current_user.totp_secret.nil? + return ServiceResponse.error(error_code: :totp_secret_already_set) unless current_user.totp_secret.nil? totp_secret = Rails.application.message_verifier(:totp_secret).verified(secret) - return ServiceResponse.error(payload: :invalid_totp_secret) if totp_secret.nil? + return ServiceResponse.error(error_code: :invalid_totp_secret) if totp_secret.nil? totp = ROTP::TOTP.new(totp_secret) - return ServiceResponse.error(payload: :wrong_totp) unless totp.verify(current_totp) + return ServiceResponse.error(error_code: :wrong_totp) unless totp.verify(current_totp) transactional do current_user.totp_secret = totp_secret unless current_user.save - return ServiceResponse.error(message: 'Error while saving user', payload: current_user.errors) + return ServiceResponse.error(message: 'Error while saving user', error_code: :invalid_user, + details: current_user.errors) end AuditService.audit( diff --git a/app/services/users/password_reset_service.rb b/app/services/users/password_reset_service.rb index ee5ce660..28d60d81 100644 --- a/app/services/users/password_reset_service.rb +++ b/app/services/users/password_reset_service.rb @@ -16,13 +16,14 @@ def execute if user.nil? return ServiceResponse.error(message: 'Invalid or expired verification code', - payload: :invalid_verification_code) + error_code: :invalid_verification_code) end transactional do |t| user.password = new_password unless user.save - t.rollback_and_return! ServiceResponse.error(message: 'Failed to reset password', payload: user.errors) + t.rollback_and_return! ServiceResponse.error(message: 'Failed to reset password', + error_code: :failed_to_reset_password, details: user.errors) end AuditService.audit( diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb index a6abc2d2..4905f888 100644 --- a/app/services/users/update_service.rb +++ b/app/services/users/update_service.rb @@ -20,17 +20,17 @@ def initialize(current_authentication, user, mfa, params) def execute unless Ability.allowed?(current_authentication, :update_user, user) - return ServiceResponse.error(message: 'Missing permission', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission) end if params.key?(:admin) unless current_authentication.user.admin? return ServiceResponse.error(message: 'Cannot modify users admin status because user isn`t admin', - payload: :unmodifiable_field) + error_code: :cannot_modify_admin) end if current_authentication.user == user - return ServiceResponse.error(message: 'Cannot modify own admin status', payload: :unmodifiable_field) + return ServiceResponse.error(message: 'Cannot modify own admin status', error_code: :cannot_modify_own_admin) end end @@ -40,7 +40,7 @@ def execute if mfa.nil? return ServiceResponse.error( message: "MFA required for fields: #{params.keys.intersection(REQUIRES_MFA_FIELDS)}", - payload: :mfa_required + error_code: :mfa_required ) end @@ -48,7 +48,7 @@ def execute unless mfa_passed t.rollback_and_return! ServiceResponse.error(message: 'MFA failed', - payload: :mfa_failed) + error_code: :mfa_failed) end end @@ -56,7 +56,8 @@ def execute unless success t.rollback_and_return! ServiceResponse.error( message: 'Failed to update user', - payload: user.errors + error_code: :invalid_user, + details: user.errors ) end @@ -65,7 +66,8 @@ def execute unless response.success? t.rollback_and_return! ServiceResponse.error(message: 'Failed to send verification email', - payload: response.payload) + error_code: :email_verification_send_failed, + details: response.payload) end end diff --git a/docs/graphql/enum/errorcodeenum.md b/docs/graphql/enum/errorcodeenum.md index a02b4f7e..54d74027 100644 --- a/docs/graphql/enum/errorcodeenum.md +++ b/docs/graphql/enum/errorcodeenum.md @@ -7,30 +7,58 @@ Represents the available error responses | Value | Description | |-------|-------------| | `CANNOT_DELETE_LAST_ADMIN_ROLE` | This action would remove the last administrative role | +| `CANNOT_MODIFY_ADMIN` | Only administrators can modify admin status of users | +| `CANNOT_MODIFY_OWN_ADMIN` | Users cannot modify their own admin status | | `CANNOT_REMOVE_LAST_ADMINISTRATOR` | This action would remove the last administrator | | `CANNOT_REMOVE_LAST_ADMIN_ABILITY` | This action would remove the last administrative ability | | `EXTERNAL_IDENTITY_DOES_NOT_EXIST` | This external identity does not exist | | `FAILED_TO_INVALIDATE_OLD_BACKUP_CODES` | The old backup codes could not be deleted | +| `FAILED_TO_RESET_PASSWORD` | Failed to reset the user password | | `FAILED_TO_SAVE_VALID_BACKUP_CODE` | The new backup codes could not be saved | +| `FLOW_NOT_FOUND` | The flow with the given identifier was not found | +| `FLOW_VALIDATION_FAILED` | The flow validation has failed | | `GENERIC_KEY_NOT_FOUND` | The given key was not found in the data type | +| `IDENTITY_NOT_FOUND` | The external identity with the given identifier was not found | | `IDENTITY_VALIDATION_FAILED` | Failed to validate the external identity | | `INCONSISTENT_NAMESPACE` | Resources are from different namespaces | +| `INVALID_ATTACHMENT` | The attachment is invalid because of active model errors | | `INVALID_EXTERNAL_IDENTITY` | This external identity is invalid | +| `INVALID_FLOW_SETTING` | The flow setting is invalid because of active model errors | | `INVALID_LOGIN_DATA` | Invalid login data provided | +| `INVALID_NAMESPACE_LICENSE` | The namespace license is invalid because of active model errors | +| `INVALID_NAMESPACE_MEMBER` | The flow setting is invalid because of active model errors | +| `INVALID_NAMESPACE_PROJECT` | The namespace project is invalid because of active model errors | +| `INVALID_NAMESPACE_ROLE` | The namespace role is invalid because of active model errors | +| `INVALID_ORGANIZATION` | The organization is invalid because of active model errors | +| `INVALID_PASSWORD_REPEAT` | The provided password repeat does not match the password | +| `INVALID_RUNTIME` | The runtime is invalid because of active model errors | | `INVALID_SETTING` | Invalid setting provided | +| `INVALID_USER` | The user is invalid because of active model errors | +| `INVALID_USER_IDENTITY` | The user identity is invalid because of active model errors | +| `INVALID_USER_SESSION` | The user session is invalid because of active model errors | | `INVALID_VERIFICATION_CODE` | Invalid verification code provided | +| `LICENSE_NOT_FOUND` | The namespace license with the given identifier was not found | +| `LOADING_IDENTITY_FAILED` | Failed to load user identity from external provider | | `MFA_FAILED` | Invalid MFA data provided | | `MFA_REQUIRED` | MFA is required | | `MISSING_IDENTITY_DATA` | This external identity is missing data | | `MISSING_PARAMETER` | Not all required parameters are present | | `MISSING_PERMISSION` | The user is not permitted to perform this operation | +| `NAMESPACE_MEMBER_NOT_FOUND` | The namespace member with the given identifier was not found | +| `NAMESPACE_NOT_FOUND` | The namespace with the given identifier was not found | +| `NAMESPACE_PROJECT_NOT_FOUND` | The namespace project with the given identifier was not found | +| `NAMESPACE_ROLE_NOT_FOUND` | The namespace role with the given identifier was not found | | `NO_FREE_LICENSE_SEATS` | There are no free license seats to complete this operation | | `NO_PRIMARY_RUNTIME` | The project does not have a primary runtime | | `PRIMARY_LEVEL_NOT_FOUND` | **Deprecated:** Outdated concept | +| `PROJECT_NOT_FOUND` | The namespace project with the given identifier was not found | | `REGISTRATION_DISABLED` | Self-registration is disabled | | `RUNTIME_MISMATCH` | Resources are from different runtimes | +| `RUNTIME_NOT_FOUND` | The runtime with the given identifier was not found | | `SECONDARY_LEVEL_NOT_FOUND` | **Deprecated:** Outdated concept | | `TERTIARY_LEVEL_EXCEEDS_PARAMETERS` | **Deprecated:** Outdated concept | | `TOTP_SECRET_ALREADY_SET` | This user already has TOTP set up | | `UNMODIFIABLE_FIELD` | The user is not permitted to modify this field | +| `USER_NOT_FOUND` | The user with the given identifier was not found | +| `USER_SESSION_NOT_FOUND` | The user session with the given identifier was not found | | `WRONG_TOTP` | Invalid TOTP code provided | diff --git a/docs/graphql/enum/flowvalidationerrorcodeenum.md b/docs/graphql/enum/flowvalidationerrorcodeenum.md new file mode 100644 index 00000000..70b38f79 --- /dev/null +++ b/docs/graphql/enum/flowvalidationerrorcodeenum.md @@ -0,0 +1,16 @@ +--- +title: FlowValidationErrorCodeEnum +--- + +Represents the available error responses + +| Value | Description | +|-------|-------------| +| `DATA_TYPE_IDENTIFIER_GENERIC_KEY_NOT_FOUND` | The generic key for the data type identifier was not found. | +| `DATA_TYPE_IDENTIFIER_RUNTIME_MISMATCH` | The data type identifier runtime does not match the flow type runtime. | +| `DATA_TYPE_RULE_MODEL_INVALID` | The data type rule model is invalid. | +| `DATA_TYPE_RUNTIME_MISMATCH` | The data type runtime does not match the flow type runtime. | +| `FLOW_SETTING_MODEL_INVALID` | The flow setting model is invalid. | +| `FLOW_TYPE_RUNTIME_MISMATCH` | The flow type runtime does not match the project primary runtime. | +| `NODE_FUNCTION_RUNTIME_MISMATCH` | The node function runtime does not match the project primary runtime. | +| `NO_PRIMARY_RUNTIME` | The project does not have a primary runtime set. | diff --git a/docs/graphql/enum/flowvalidationseverityenum.md b/docs/graphql/enum/flowvalidationseverityenum.md new file mode 100644 index 00000000..9fcfe023 --- /dev/null +++ b/docs/graphql/enum/flowvalidationseverityenum.md @@ -0,0 +1,12 @@ +--- +title: FlowValidationSeverityEnum +--- + +Represents the severity of a flow validation error + +| Value | Description | +|-------|-------------| +| `ERROR` | A blocking validation error | +| `TYPO` | A minor typographical issue can also be blocking | +| `WARNING` | A non-blocking validation warning | +| `WEAK` | A weak validation issue that may not need to be addressed | diff --git a/docs/graphql/object/error.md b/docs/graphql/object/error.md index 60250347..195c8bb0 100644 --- a/docs/graphql/object/error.md +++ b/docs/graphql/object/error.md @@ -8,6 +8,6 @@ Objects that can present an error | Name | Type | Description | |------|------|-------------| -| `code` | [`ErrorCode!`](../object/errorcode.md) | The code representing the error type | | `details` | [`[DetailedError!]`](../union/detailederror.md) | Detailed validation errors if applicable | +| `errorCode` | [`ErrorCodeEnum!`](../enum/errorcodeenum.md) | The code representing the error type | diff --git a/docs/graphql/object/errorcode.md b/docs/graphql/object/errorcode.md deleted file mode 100644 index 091f1299..00000000 --- a/docs/graphql/object/errorcode.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: ErrorCode ---- - -Represents an error code - -## Fields without arguments - -| Name | Type | Description | -|------|------|-------------| -| `errorCode` | [`ErrorCodeEnum!`](../enum/errorcodeenum.md) | The error code | - diff --git a/docs/graphql/object/flowvalidationerror.md b/docs/graphql/object/flowvalidationerror.md new file mode 100644 index 00000000..ec6d28a2 --- /dev/null +++ b/docs/graphql/object/flowvalidationerror.md @@ -0,0 +1,14 @@ +--- +title: FlowValidationError +--- + +Represents an flow validation error + +## Fields without arguments + +| Name | Type | Description | +|------|------|-------------| +| `details` | [`ActiveModelError`](../object/activemodelerror.md) | Additional details about the validation error | +| `errorCode` | [`FlowValidationErrorCodeEnum!`](../enum/flowvalidationerrorcodeenum.md) | The code representing the validation error type | +| `severity` | [`FlowValidationSeverityEnum!`](../enum/flowvalidationseverityenum.md) | The severity of the validation error | + diff --git a/docs/graphql/union/detailederror.md b/docs/graphql/union/detailederror.md index b4382a3f..d5cd6d4c 100644 --- a/docs/graphql/union/detailederror.md +++ b/docs/graphql/union/detailederror.md @@ -7,4 +7,5 @@ Represents a detailed error with either a message or an active model error ## Possible types - [`ActiveModelError`](../object/activemodelerror.md) +- [`FlowValidationError`](../object/flowvalidationerror.md) - [`MessageError`](../object/messageerror.md) diff --git a/extensions/ee/app/graphql/mutations/namespaces/licenses/create.rb b/extensions/ee/app/graphql/mutations/namespaces/licenses/create.rb index ae071e82..f973130b 100644 --- a/extensions/ee/app/graphql/mutations/namespaces/licenses/create.rb +++ b/extensions/ee/app/graphql/mutations/namespaces/licenses/create.rb @@ -17,7 +17,7 @@ def resolve(namespace_id:, data:) if namespace.nil? return { namespace_license: nil, - errors: [create_message_error('Invalid namespace')] } + errors: [create_error(:namespace_not_found, 'Invalid namespace')] } end ::Namespaces::Licenses::CreateService.new( diff --git a/extensions/ee/app/graphql/mutations/namespaces/licenses/delete.rb b/extensions/ee/app/graphql/mutations/namespaces/licenses/delete.rb index f2c3329e..5f4b0206 100644 --- a/extensions/ee/app/graphql/mutations/namespaces/licenses/delete.rb +++ b/extensions/ee/app/graphql/mutations/namespaces/licenses/delete.rb @@ -18,7 +18,7 @@ def resolve(namespace_license_id:) if license.nil? return { organization_license: nil, - errors: [create_message_error('Invalid license')] } + errors: [create_error(:license_not_found, 'Invalid license')] } end ::Namespaces::Licenses::DeleteService.new( diff --git a/extensions/ee/app/services/ee/namespaces/members/invite_service.rb b/extensions/ee/app/services/ee/namespaces/members/invite_service.rb index 6d29e33c..4301564a 100644 --- a/extensions/ee/app/services/ee/namespaces/members/invite_service.rb +++ b/extensions/ee/app/services/ee/namespaces/members/invite_service.rb @@ -17,7 +17,7 @@ def validate_user_limit!(t) t.rollback_and_return! ServiceResponse.error( message: 'No free member seats in license', - payload: :no_free_license_seats + error_code: :no_free_license_seats ) end end diff --git a/extensions/ee/app/services/namespaces/licenses/create_service.rb b/extensions/ee/app/services/namespaces/licenses/create_service.rb index 718147e2..9d792f89 100644 --- a/extensions/ee/app/services/namespaces/licenses/create_service.rb +++ b/extensions/ee/app/services/namespaces/licenses/create_service.rb @@ -15,7 +15,7 @@ def initialize(current_authentication, namespace:, data:) def execute unless Ability.allowed?(current_authentication, :create_namespace_license, namespace) - return ServiceResponse.error(message: 'Missing permission', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission) end transactional do |t| @@ -23,7 +23,8 @@ def execute unless namespace_license.persisted? t.rollback_and_return! ServiceResponse.error( message: 'Failed to create namespace license', - payload: namespace_license.errors + error_code: :invalid_namespace_license, + details: namespace_license.errors ) end diff --git a/extensions/ee/app/services/namespaces/licenses/delete_service.rb b/extensions/ee/app/services/namespaces/licenses/delete_service.rb index 4a3a5476..f5e8a395 100644 --- a/extensions/ee/app/services/namespaces/licenses/delete_service.rb +++ b/extensions/ee/app/services/namespaces/licenses/delete_service.rb @@ -14,7 +14,7 @@ def initialize(current_authentication, namespace_license:) def execute unless Ability.allowed?(current_authentication, :delete_namespace_license, namespace_license) - return ServiceResponse.error(message: 'Missing permission', payload: :missing_permission) + return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission) end transactional do |t| @@ -22,7 +22,8 @@ def execute if namespace_license.persisted? t.rollback_and_return! ServiceResponse.error( message: 'Failed to delete namespace license', - payload: namespace_license.errors + error_code: :invalid_namespace_license, + details: namespace_license.errors ) end diff --git a/extensions/ee/spec/requests/graphql/mutations/namespaces/licenses/create_mutation_spec.rb b/extensions/ee/spec/requests/graphql/mutations/namespaces/licenses/create_mutation_spec.rb index 1458a613..23ffa8a1 100644 --- a/extensions/ee/spec/requests/graphql/mutations/namespaces/licenses/create_mutation_spec.rb +++ b/extensions/ee/spec/requests/graphql/mutations/namespaces/licenses/create_mutation_spec.rb @@ -72,8 +72,8 @@ expect(graphql_data_at(:namespaces_licenses_create, :namespace_license)).to be_nil expect( - graphql_data_at(:namespaces_licenses_create, :errors) - ).to include({ 'attribute' => 'data', 'type' => 'invalid' }) + graphql_data_at(:namespaces_licenses_create, :errors, :details) + ).to include([{ 'attribute' => 'data', 'type' => 'invalid' }]) end end end @@ -83,7 +83,7 @@ mutate! expect(graphql_data_at(:namespaces_licenses_create, :namespace_license)).to be_nil - expect(graphql_data_at(:namespaces_licenses_create, :errors)).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + expect(graphql_data_at(:namespaces_licenses_create, :errors, :error_code)).to include('MISSING_PERMISSION') end end end diff --git a/extensions/ee/spec/requests/graphql/mutations/namespaces/licenses/delete_mutation_spec.rb b/extensions/ee/spec/requests/graphql/mutations/namespaces/licenses/delete_mutation_spec.rb index 9a4efe47..8e443897 100644 --- a/extensions/ee/spec/requests/graphql/mutations/namespaces/licenses/delete_mutation_spec.rb +++ b/extensions/ee/spec/requests/graphql/mutations/namespaces/licenses/delete_mutation_spec.rb @@ -68,7 +68,7 @@ mutate! expect(graphql_data_at(:namespaces_licenses_delete, :namespace_license)).to be_nil - expect(graphql_data_at(:namespaces_licenses_delete, :errors)).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + expect(graphql_data_at(:namespaces_licenses_delete, :errors, :error_code)).to include('MISSING_PERMISSION') end end end diff --git a/extensions/ee/spec/services/ee/namespaces/members/invite_service_spec.rb b/extensions/ee/spec/services/ee/namespaces/members/invite_service_spec.rb index 9c4f71fd..6cacd15b 100644 --- a/extensions/ee/spec/services/ee/namespaces/members/invite_service_spec.rb +++ b/extensions/ee/spec/services/ee/namespaces/members/invite_service_spec.rb @@ -18,6 +18,6 @@ it { is_expected.not_to be_success } it { expect { service_response }.not_to change { NamespaceMember.count } } - it { expect(service_response.payload).to eq(:no_free_license_seats) } + it { expect(service_response.payload[:error_code]).to eq(:no_free_license_seats) } end end diff --git a/spec/requests/graphql/mutation/application_settings/update_mutation_spec.rb b/spec/requests/graphql/mutation/application_settings/update_mutation_spec.rb index d32da107..1757068b 100644 --- a/spec/requests/graphql/mutation/application_settings/update_mutation_spec.rb +++ b/spec/requests/graphql/mutation/application_settings/update_mutation_spec.rb @@ -44,7 +44,7 @@ it 'returns an error' do expect(graphql_data_at(:application_settings_update, :application_settings)).to be_nil - expect(graphql_data_at(:application_settings_update, :errors)).to include('errorCode' => 'MISSING_PERMISSION') + expect(graphql_data_at(:application_settings_update, :errors, :error_code)).to include('MISSING_PERMISSION') end end end diff --git a/spec/requests/graphql/mutation/namespace/members/assign_roles_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/members/assign_roles_mutation_spec.rb index 47c90399..adaa94ed 100644 --- a/spec/requests/graphql/mutation/namespace/members/assign_roles_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/members/assign_roles_mutation_spec.rb @@ -86,8 +86,8 @@ expect(graphql_data_at(:namespaces_members_assign_roles, :namespace_member_roles)).to be_nil expect( - graphql_data_at(:namespaces_members_assign_roles, :errors) - ).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + graphql_data_at(:namespaces_members_assign_roles, :errors, :error_code) + ).to include('MISSING_PERMISSION') end end end diff --git a/spec/requests/graphql/mutation/namespace/members/delete_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/members/delete_mutation_spec.rb index 1ee894d5..277c7352 100644 --- a/spec/requests/graphql/mutation/namespace/members/delete_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/members/delete_mutation_spec.rb @@ -81,7 +81,7 @@ mutate! expect(graphql_data_at(:namespaces_members_delete, :namespace_member)).to be_nil - expect(graphql_data_at(:namespaces_members_delete, :errors)).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + expect(graphql_data_at(:namespaces_members_delete, :errors, :error_code)).to include('MISSING_PERMISSION') end end end diff --git a/spec/requests/graphql/mutation/namespace/members/invite_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/members/invite_mutation_spec.rb index 0ed3e11b..c2a3f2f7 100644 --- a/spec/requests/graphql/mutation/namespace/members/invite_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/members/invite_mutation_spec.rb @@ -75,8 +75,8 @@ expect(graphql_data_at(:namespaces_members_invite, :namespace_member)).to be_nil expect( - graphql_data_at(:namespaces_members_invite, :errors) - ).to include({ 'attribute' => 'namespace', 'type' => 'taken' }) + graphql_data_at(:namespaces_members_invite, :errors, :details) + ).to include([{ 'attribute' => 'namespace', 'type' => 'taken' }]) end end end @@ -86,7 +86,7 @@ mutate! expect(graphql_data_at(:namespaces_members_invite, :namespace_member)).to be_nil - expect(graphql_data_at(:namespaces_members_invite, :errors)).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + expect(graphql_data_at(:namespaces_members_invite, :errors, :error_code)).to include('MISSING_PERMISSION') end end end diff --git a/spec/requests/graphql/mutation/namespace/projects/assign_runtimes_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/projects/assign_runtimes_mutation_spec.rb index 1cbf7389..de63a9f8 100644 --- a/spec/requests/graphql/mutation/namespace/projects/assign_runtimes_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/projects/assign_runtimes_mutation_spec.rb @@ -70,8 +70,8 @@ expect(graphql_data_at(:namespaces_projects_assign_runtimes, :runtimes)).to be_nil expect( - graphql_data_at(:namespaces_projects_assign_runtimes, :errors) - ).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + graphql_data_at(:namespaces_projects_assign_runtimes, :errors, :error_code) + ).to include('MISSING_PERMISSION') end end end diff --git a/spec/requests/graphql/mutation/namespace/projects/create_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/projects/create_mutation_spec.rb index 2b6ab8ea..58f27d5f 100644 --- a/spec/requests/graphql/mutation/namespace/projects/create_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/projects/create_mutation_spec.rb @@ -76,8 +76,8 @@ expect(graphql_data_at(:namespaces_projects_create, :namespace_project)).to be_nil expect( - graphql_data_at(:namespaces_projects_create, :errors) - ).to include({ 'attribute' => 'name', 'type' => 'taken' }) + graphql_data_at(:namespaces_projects_create, :errors, :details) + ).to include([{ 'attribute' => 'name', 'type' => 'taken' }]) end end @@ -117,7 +117,7 @@ mutate! expect(graphql_data_at(:namespaces_projects_create, :namespace_project)).to be_nil - expect(graphql_data_at(:namespaces_projects_create, :errors)).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + expect(graphql_data_at(:namespaces_projects_create, :errors, :error_code)).to include('MISSING_PERMISSION') end end end diff --git a/spec/requests/graphql/mutation/namespace/projects/delete_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/projects/delete_mutation_spec.rb index 7fd55204..966b4c2b 100644 --- a/spec/requests/graphql/mutation/namespace/projects/delete_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/projects/delete_mutation_spec.rb @@ -71,7 +71,7 @@ mutate! expect(graphql_data_at(:namespaces_projects_delete, :namespace_project)).to be_nil - expect(graphql_data_at(:namespaces_projects_delete, :errors)).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + expect(graphql_data_at(:namespaces_projects_delete, :errors, :error_code)).to include('MISSING_PERMISSION') end end end diff --git a/spec/requests/graphql/mutation/namespace/projects/flows/delete_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/projects/flows/delete_mutation_spec.rb index 4326f6ef..3017921e 100644 --- a/spec/requests/graphql/mutation/namespace/projects/flows/delete_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/projects/flows/delete_mutation_spec.rb @@ -74,7 +74,7 @@ expect(graphql_data_at(:namespaces_projects_flows_delete, :flow)).to be_nil expect(graphql_data_at(:namespaces_projects_flows_delete, - :errors)).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + :errors, :error_code)).to include('MISSING_PERMISSION') end end end diff --git a/spec/requests/graphql/mutation/namespace/projects/update_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/projects/update_mutation_spec.rb index 3a2fccb5..a29653c4 100644 --- a/spec/requests/graphql/mutation/namespace/projects/update_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/projects/update_mutation_spec.rb @@ -83,8 +83,8 @@ expect(graphql_data_at(:namespaces_projects_update, :namespace_project)).to be_nil expect( - graphql_data_at(:namespaces_projects_update, :errors) - ).to include({ 'attribute' => 'name', 'type' => 'taken' }) + graphql_data_at(:namespaces_projects_update, :errors, :details) + ).to include([{ 'attribute' => 'name', 'type' => 'taken' }]) end end @@ -123,7 +123,7 @@ mutate! expect(graphql_data_at(:namespaces_projects_update, :namespace_project)).to be_nil - expect(graphql_data_at(:namespaces_projects_update, :errors)).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + expect(graphql_data_at(:namespaces_projects_update, :errors, :error_code)).to include('MISSING_PERMISSION') end end end diff --git a/spec/requests/graphql/mutation/namespace/roles/assign_abilities_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/roles/assign_abilities_mutation_spec.rb index 62735055..237a1648 100644 --- a/spec/requests/graphql/mutation/namespace/roles/assign_abilities_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/roles/assign_abilities_mutation_spec.rb @@ -73,8 +73,8 @@ expect(graphql_data_at(:namespaces_roles_assign_abilities, :abilities)).to be_nil expect( - graphql_data_at(:namespaces_roles_assign_abilities, :errors) - ).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + graphql_data_at(:namespaces_roles_assign_abilities, :errors, :error_code) + ).to include('MISSING_PERMISSION') end end end diff --git a/spec/requests/graphql/mutation/namespace/roles/assign_projects_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/roles/assign_projects_mutation_spec.rb index 8551e69d..17bbab8d 100644 --- a/spec/requests/graphql/mutation/namespace/roles/assign_projects_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/roles/assign_projects_mutation_spec.rb @@ -73,8 +73,8 @@ expect(graphql_data_at(:namespaces_roles_assign_projects, :projects)).to be_nil expect( - graphql_data_at(:namespaces_roles_assign_projects, :errors) - ).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + graphql_data_at(:namespaces_roles_assign_projects, :errors, :error_code) + ).to include('MISSING_PERMISSION') end end end diff --git a/spec/requests/graphql/mutation/namespace/roles/create_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/roles/create_mutation_spec.rb index 9c54f604..959bda53 100644 --- a/spec/requests/graphql/mutation/namespace/roles/create_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/roles/create_mutation_spec.rb @@ -75,8 +75,8 @@ expect(graphql_data_at(:namespaces_roles_create, :namespace_role)).to be_nil expect( - graphql_data_at(:namespaces_roles_create, :errors) - ).to include({ 'attribute' => 'name', 'type' => 'taken' }) + graphql_data_at(:namespaces_roles_create, :errors, :details) + ).to include([{ 'attribute' => 'name', 'type' => 'taken' }]) end end @@ -115,7 +115,7 @@ mutate! expect(graphql_data_at(:namespaces_roles_create, :namespace_role)).to be_nil - expect(graphql_data_at(:namespaces_roles_create, :errors)).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + expect(graphql_data_at(:namespaces_roles_create, :errors, :error_code)).to include('MISSING_PERMISSION') end end end diff --git a/spec/requests/graphql/mutation/namespace/roles/delete_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/roles/delete_mutation_spec.rb index 637a0597..01afc875 100644 --- a/spec/requests/graphql/mutation/namespace/roles/delete_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/roles/delete_mutation_spec.rb @@ -75,7 +75,7 @@ mutate! expect(graphql_data_at(:namespaces_roles_delete, :namespace_role)).to be_nil - expect(graphql_data_at(:namespaces_roles_delete, :errors)).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + expect(graphql_data_at(:namespaces_roles_delete, :errors, :error_code)).to include('MISSING_PERMISSION') end end end diff --git a/spec/requests/graphql/mutation/namespace/roles/update_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/roles/update_mutation_spec.rb index 5f66bd9c..fe928a18 100644 --- a/spec/requests/graphql/mutation/namespace/roles/update_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/roles/update_mutation_spec.rb @@ -72,8 +72,8 @@ expect(graphql_data_at(:namespaces_roles_update, :namespace_role)).to be_nil expect( - graphql_data_at(:namespaces_roles_update, :errors) - ).to include({ 'attribute' => 'name', 'type' => 'taken' }) + graphql_data_at(:namespaces_roles_update, :errors, :details) + ).to include([{ 'attribute' => 'name', 'type' => 'taken' }]) end end @@ -111,7 +111,7 @@ mutate! expect(graphql_data_at(:namespaces_roles_update, :namespace_role)).to be_nil - expect(graphql_data_at(:namespaces_roles_update, :errors)).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + expect(graphql_data_at(:namespaces_roles_update, :errors, :error_code)).to include('MISSING_PERMISSION') end end end diff --git a/spec/requests/graphql/mutation/organizations/create_mutation_spec.rb b/spec/requests/graphql/mutation/organizations/create_mutation_spec.rb index 340d0197..145d7669 100644 --- a/spec/requests/graphql/mutation/organizations/create_mutation_spec.rb +++ b/spec/requests/graphql/mutation/organizations/create_mutation_spec.rb @@ -56,7 +56,8 @@ it 'returns an error' do expect(graphql_data_at(:organizations_create, :organization)).to be_nil - expect(graphql_data_at(:organizations_create, :errors)).to include({ 'attribute' => 'name', 'type' => 'taken' }) + expect(graphql_data_at(:organizations_create, :errors, + :details)).to include([{ 'attribute' => 'name', 'type' => 'taken' }]) end end end diff --git a/spec/requests/graphql/mutation/organizations/delete_mutation_spec.rb b/spec/requests/graphql/mutation/organizations/delete_mutation_spec.rb index db054d40..5a2989ce 100644 --- a/spec/requests/graphql/mutation/organizations/delete_mutation_spec.rb +++ b/spec/requests/graphql/mutation/organizations/delete_mutation_spec.rb @@ -64,7 +64,7 @@ mutate! expect(graphql_data_at(:organizations_delete, :organization)).to be_nil - expect(graphql_data_at(:organizations_delete, :errors)).to include({ 'errorCode' => 'MISSING_PERMISSION' }) + expect(graphql_data_at(:organizations_delete, :errors, :error_code)).to include('MISSING_PERMISSION') end end end diff --git a/spec/requests/graphql/mutation/organizations/update_mutation_spec.rb b/spec/requests/graphql/mutation/organizations/update_mutation_spec.rb index 161c8408..ba038ba9 100644 --- a/spec/requests/graphql/mutation/organizations/update_mutation_spec.rb +++ b/spec/requests/graphql/mutation/organizations/update_mutation_spec.rb @@ -68,7 +68,8 @@ it 'returns an error' do expect(graphql_data_at(:organizations_update, :organization)).to be_nil - expect(graphql_data_at(:organizations_update, :errors)).to include({ 'attribute' => 'name', 'type' => 'taken' }) + expect(graphql_data_at(:organizations_update, :errors, + :details)).to include([{ 'attribute' => 'name', 'type' => 'taken' }]) end end end diff --git a/spec/requests/graphql/mutation/runtimes/create_mutation_spec.rb b/spec/requests/graphql/mutation/runtimes/create_mutation_spec.rb index c83766af..afb1f1ad 100644 --- a/spec/requests/graphql/mutation/runtimes/create_mutation_spec.rb +++ b/spec/requests/graphql/mutation/runtimes/create_mutation_spec.rb @@ -96,7 +96,8 @@ it 'returns an error' do expect(graphql_data_at(:runtimes_create, :runtime)).to be_nil - expect(graphql_data_at(:runtimes_create, :errors)).to include({ 'attribute' => 'name', 'type' => 'taken' }) + expect(graphql_data_at(:runtimes_create, :errors, + :details)).to include([{ 'attribute' => 'name', 'type' => 'taken' }]) end end end diff --git a/spec/requests/graphql/mutation/runtimes/update_mutation_spec.rb b/spec/requests/graphql/mutation/runtimes/update_mutation_spec.rb index 637ffe56..78622057 100644 --- a/spec/requests/graphql/mutation/runtimes/update_mutation_spec.rb +++ b/spec/requests/graphql/mutation/runtimes/update_mutation_spec.rb @@ -103,7 +103,8 @@ it 'returns an error' do expect(graphql_data_at(:runtimes_update, :organization)).to be_nil - expect(graphql_data_at(:runtimes_update, :errors)).to include({ 'attribute' => 'name', 'type' => 'taken' }) + expect(graphql_data_at(:runtimes_update, :errors, + :details)).to include([{ 'attribute' => 'name', 'type' => 'taken' }]) end end end diff --git a/spec/requests/graphql/mutation/users/logout_spec.rb b/spec/requests/graphql/mutation/users/logout_spec.rb index 94a3c67a..8680f4b9 100644 --- a/spec/requests/graphql/mutation/users/logout_spec.rb +++ b/spec/requests/graphql/mutation/users/logout_spec.rb @@ -67,7 +67,8 @@ let(:user_session_id) { 'gid://sagittarius/UserSession/0' } it 'raises validation error' do - expect(graphql_data_at(:users_logout, :errors, :message)).to include('Invalid user session') + expect(graphql_data_at(:users_logout, :errors, :error_code)).to include('USER_SESSION_NOT_FOUND') + expect(graphql_data_at(:users_logout, :errors, :details)).to include([{ 'message' => 'Invalid user session' }]) end end end diff --git a/spec/requests/graphql/mutation/users/password_reset_spec.rb b/spec/requests/graphql/mutation/users/password_reset_spec.rb index 5bedde6b..bc97593c 100644 --- a/spec/requests/graphql/mutation/users/password_reset_spec.rb +++ b/spec/requests/graphql/mutation/users/password_reset_spec.rb @@ -61,9 +61,9 @@ let(:new_password_confirmation) { 'differentpassword' } it 'returns validation error' do - expect(graphql_data_at(:users_password_reset, :errors)).to include( - { 'message' => 'Invalid password repeat' } - ) + expect( + graphql_data_at(:users_password_reset, :errors, :details) + ).to include([{ 'message' => 'Invalid password repeat' }]) end it { is_expected.not_to create_audit_event(:password_reset) } diff --git a/spec/requests/graphql/mutation/users/register_spec.rb b/spec/requests/graphql/mutation/users/register_spec.rb index cd080ff1..a1545461 100644 --- a/spec/requests/graphql/mutation/users/register_spec.rb +++ b/spec/requests/graphql/mutation/users/register_spec.rb @@ -74,10 +74,12 @@ post_graphql mutation, variables: variables expect(graphql_data_at(:users_register, :user_session)).not_to be_present - expect(graphql_data_at(:users_register, :errors)).to include( - { 'attribute' => 'username', 'type' => 'taken' }, - { 'attribute' => 'email', 'type' => 'taken' } - ) + expect(graphql_data_at(:users_register, :errors, :details)).to include([ + { 'attribute' => 'username', + 'type' => 'taken' }, + { 'attribute' => 'email', + 'type' => 'taken' } + ]) end end @@ -94,9 +96,9 @@ it 'returns errors' do expect(graphql_data_at(:users_register, :user_session)).not_to be_present - expect(graphql_data_at(:users_register, :errors)).to include( - { 'message' => 'Invalid password repeat' } - ) + expect( + graphql_data_at(:users_register, :errors, :details) + ).to include([{ 'message' => 'Invalid password repeat' }]) end end @@ -113,14 +115,20 @@ it 'returns errors' do expect(graphql_data_at(:users_register, :user_session)).not_to be_present - expect(graphql_data_at(:users_register, :errors)).to include( - { 'attribute' => 'password', 'type' => 'blank' }, - { 'attribute' => 'username', 'type' => 'too_short' }, - { 'attribute' => 'username', 'type' => 'blank' }, - { 'attribute' => 'email', 'type' => 'too_short' }, - { 'attribute' => 'email', 'type' => 'invalid' }, - { 'attribute' => 'email', 'type' => 'blank' } - ) + expect(graphql_data_at(:users_register, :errors, :details)).to include([ + { 'attribute' => 'password', + 'type' => 'blank' }, + { 'attribute' => 'username', + 'type' => 'too_short' }, + { 'attribute' => 'username', + 'type' => 'blank' }, + { 'attribute' => 'email', + 'type' => 'too_short' }, + { 'attribute' => 'email', + 'type' => 'invalid' }, + { 'attribute' => 'email', + 'type' => 'blank' } + ]) end end end diff --git a/spec/requests/graphql/mutation/users/update_spec.rb b/spec/requests/graphql/mutation/users/update_spec.rb index 46e33124..8fdf21cc 100644 --- a/spec/requests/graphql/mutation/users/update_spec.rb +++ b/spec/requests/graphql/mutation/users/update_spec.rb @@ -65,7 +65,8 @@ it 'returns an error' do expect(graphql_data_at(:users_update, :user)).to be_nil - expect(graphql_data_at(:users_update, :errors)).to include({ 'message' => 'Invalid password repeat' }) + expect(graphql_data_at(:users_update, :errors, + :details)).to include([{ 'message' => 'Invalid password repeat' }]) end end @@ -110,7 +111,7 @@ it 'returns an error' do expect(graphql_data_at(:users_update, :user)).to be_nil - expect(graphql_data_at(:users_update, :errors)).to include({ 'errorCode' => 'MFA_REQUIRED' }) + expect(graphql_data_at(:users_update, :errors, :error_code)).to include('MFA_REQUIRED') end end @@ -157,7 +158,8 @@ it 'returns an error' do expect(graphql_data_at(:users_update, :user)).to be_nil - expect(graphql_data_at(:users_update, :errors)).to include({ 'attribute' => 'username', 'type' => 'taken' }) + expect(graphql_data_at(:users_update, :errors, + :details)).to include([{ 'attribute' => 'username', 'type' => 'taken' }]) end end end diff --git a/spec/requests/graphql_spec.rb b/spec/requests/graphql_spec.rb index ab2aa620..49b0ec17 100644 --- a/spec/requests/graphql_spec.rb +++ b/spec/requests/graphql_spec.rb @@ -62,7 +62,7 @@ expect(response).to have_http_status(:ok) expect_graphql_errors_to_be_empty - expect(graphql_data_at(:users_login, :errors, :message)).to be_present + expect(graphql_data_at(:users_login, :errors, :error_code)).to be_present end context 'when aliasing the mutation' do diff --git a/spec/services/application_settings_update_service_spec.rb b/spec/services/application_settings_update_service_spec.rb index 9eb03da9..3d92501a 100644 --- a/spec/services/application_settings_update_service_spec.rb +++ b/spec/services/application_settings_update_service_spec.rb @@ -10,7 +10,7 @@ let(:params) { { user_registration_enabled: false } } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it 'does not change any settings' do expect { service_response }.not_to change { ApplicationSetting.pluck(:value) } @@ -26,7 +26,7 @@ let(:params) { { user_registration_enabled: false } } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it 'does not change any settings' do expect { service_response }.not_to change { ApplicationSetting.pluck(:value) } diff --git a/spec/services/namespaces/members/assign_roles_service_spec.rb b/spec/services/namespaces/members/assign_roles_service_spec.rb index 5079b44a..f39d3ec8 100644 --- a/spec/services/namespaces/members/assign_roles_service_spec.rb +++ b/spec/services/namespaces/members/assign_roles_service_spec.rb @@ -20,7 +20,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceMemberRole.count } } it do @@ -30,7 +30,7 @@ context 'when user does not have permission' do it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceMemberRole.count } } it do @@ -65,7 +65,7 @@ end it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:cannot_remove_last_administrator) } + it { expect(service_response.payload[:error_code]).to eq(:cannot_remove_last_administrator) } it { expect { service_response }.not_to change { NamespaceMemberRole.count } } it do @@ -170,7 +170,7 @@ end it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:inconsistent_namespace) } + it { expect(service_response.payload[:error_code]).to eq(:inconsistent_namespace) } it { expect { service_response }.not_to change { NamespaceMemberRole.count } } it do diff --git a/spec/services/namespaces/members/delete_service_spec.rb b/spec/services/namespaces/members/delete_service_spec.rb index 12e91193..4771ebc0 100644 --- a/spec/services/namespaces/members/delete_service_spec.rb +++ b/spec/services/namespaces/members/delete_service_spec.rb @@ -23,7 +23,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceMember.count } } it do @@ -35,7 +35,7 @@ let(:current_user) { create(:user) } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceMember.count } } it do @@ -81,7 +81,7 @@ end it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:cannot_remove_last_administrator) } + it { expect(service_response.payload[:error_code]).to eq(:cannot_remove_last_administrator) } it { expect { service_response }.not_to change { NamespaceMember.count } } it do diff --git a/spec/services/namespaces/members/invite_service_spec.rb b/spec/services/namespaces/members/invite_service_spec.rb index 1324129c..4e07e624 100644 --- a/spec/services/namespaces/members/invite_service_spec.rb +++ b/spec/services/namespaces/members/invite_service_spec.rb @@ -12,7 +12,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceMember.count } } it do @@ -24,7 +24,7 @@ let(:current_user) { create(:user) } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceMember.count } } it do diff --git a/spec/services/namespaces/projects/assign_runtimes_service_spec.rb b/spec/services/namespaces/projects/assign_runtimes_service_spec.rb index 02c3332e..ece92755 100644 --- a/spec/services/namespaces/projects/assign_runtimes_service_spec.rb +++ b/spec/services/namespaces/projects/assign_runtimes_service_spec.rb @@ -13,7 +13,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceProjectRuntimeAssignment.count } } it do @@ -23,7 +23,7 @@ context 'when user does not have permission' do it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceProjectRuntimeAssignment.count } } it do diff --git a/spec/services/namespaces/projects/delete_service_spec.rb b/spec/services/namespaces/projects/delete_service_spec.rb index ca55afc4..df1c7147 100644 --- a/spec/services/namespaces/projects/delete_service_spec.rb +++ b/spec/services/namespaces/projects/delete_service_spec.rb @@ -12,7 +12,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceProject.count } } it do @@ -24,7 +24,7 @@ let(:current_user) { create(:user) } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceProject.count } } it do diff --git a/spec/services/namespaces/projects/flows/validation/validation_service_spec.rb b/spec/services/namespaces/projects/flows/validation/validation_service_spec.rb index cc28d22c..dbaa9a6e 100644 --- a/spec/services/namespaces/projects/flows/validation/validation_service_spec.rb +++ b/spec/services/namespaces/projects/flows/validation/validation_service_spec.rb @@ -69,7 +69,7 @@ end it 'returns an error' do - expect(service_response.payload).to include(have_attributes(error_code: :no_primary_runtime)) + expect(service_response.payload[:details]).to include(have_attributes(error_code: :no_primary_runtime)) end end diff --git a/spec/services/namespaces/projects/update_service_spec.rb b/spec/services/namespaces/projects/update_service_spec.rb index a809e035..119ffa4f 100644 --- a/spec/services/namespaces/projects/update_service_spec.rb +++ b/spec/services/namespaces/projects/update_service_spec.rb @@ -17,7 +17,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { namespace_project.reload.name } } it { expect { service_response }.not_to change { namespace_project.reload.primary_runtime } } @@ -30,7 +30,7 @@ let(:current_user) { create(:user) } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { namespace_project.reload.name } } it { expect { service_response }.not_to change { namespace_project.reload.primary_runtime } } diff --git a/spec/services/namespaces/roles/assign_abilities_service_spec.rb b/spec/services/namespaces/roles/assign_abilities_service_spec.rb index 762b3700..37872314 100644 --- a/spec/services/namespaces/roles/assign_abilities_service_spec.rb +++ b/spec/services/namespaces/roles/assign_abilities_service_spec.rb @@ -19,7 +19,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceRoleAbility.count } } it do @@ -29,7 +29,7 @@ context 'when user does not have permission' do it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceRoleAbility.count } } it do @@ -67,7 +67,7 @@ end it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:cannot_remove_last_admin_ability) } + it { expect(service_response.payload[:error_code]).to eq(:cannot_remove_last_admin_ability) } it { expect { service_response }.not_to change { NamespaceRoleAbility.count } } it do diff --git a/spec/services/namespaces/roles/assign_projects_service_spec.rb b/spec/services/namespaces/roles/assign_projects_service_spec.rb index 2cac6ed2..5177cb4f 100644 --- a/spec/services/namespaces/roles/assign_projects_service_spec.rb +++ b/spec/services/namespaces/roles/assign_projects_service_spec.rb @@ -13,7 +13,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceRoleProjectAssignment.count } } it do @@ -23,7 +23,7 @@ context 'when user does not have permission' do it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceRoleProjectAssignment.count } } it do diff --git a/spec/services/namespaces/roles/create_service_spec.rb b/spec/services/namespaces/roles/create_service_spec.rb index 9dc36be1..e822eeae 100644 --- a/spec/services/namespaces/roles/create_service_spec.rb +++ b/spec/services/namespaces/roles/create_service_spec.rb @@ -13,7 +13,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceRole.count } } it do @@ -25,7 +25,7 @@ let(:current_user) { create(:user) } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceRole.count } } it do diff --git a/spec/services/namespaces/roles/delete_service_spec.rb b/spec/services/namespaces/roles/delete_service_spec.rb index 11473ab4..28689843 100644 --- a/spec/services/namespaces/roles/delete_service_spec.rb +++ b/spec/services/namespaces/roles/delete_service_spec.rb @@ -16,7 +16,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceRole.count } } it do @@ -28,7 +28,7 @@ let(:current_user) { create(:user) } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { NamespaceRole.count } } it do diff --git a/spec/services/namespaces/roles/update_service_spec.rb b/spec/services/namespaces/roles/update_service_spec.rb index 33f9c295..39d7cba4 100644 --- a/spec/services/namespaces/roles/update_service_spec.rb +++ b/spec/services/namespaces/roles/update_service_spec.rb @@ -15,7 +15,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { namespace_role.reload.name } } it do @@ -27,7 +27,7 @@ let(:current_user) { create(:user) } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { namespace_role.reload.name } } it do diff --git a/spec/services/organizations/delete_service_spec.rb b/spec/services/organizations/delete_service_spec.rb index 445bae55..9c782e07 100644 --- a/spec/services/organizations/delete_service_spec.rb +++ b/spec/services/organizations/delete_service_spec.rb @@ -11,7 +11,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { Organization.count } } it do @@ -23,7 +23,7 @@ let(:current_user) { create(:user) } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { Organization.count } } it do diff --git a/spec/services/runtimes/delete_service_spec.rb b/spec/services/runtimes/delete_service_spec.rb index 4ef5650e..57ed8660 100644 --- a/spec/services/runtimes/delete_service_spec.rb +++ b/spec/services/runtimes/delete_service_spec.rb @@ -11,7 +11,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { Runtime.count } } it do diff --git a/spec/services/runtimes/rotate_token_service_spec.rb b/spec/services/runtimes/rotate_token_service_spec.rb index f7dd2992..3aa26511 100644 --- a/spec/services/runtimes/rotate_token_service_spec.rb +++ b/spec/services/runtimes/rotate_token_service_spec.rb @@ -11,7 +11,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it { expect { service_response }.not_to change { runtime.reload.token } } it do diff --git a/spec/services/users/email_verification_service_spec.rb b/spec/services/users/email_verification_service_spec.rb index fddac76a..4c19c9da 100644 --- a/spec/services/users/email_verification_service_spec.rb +++ b/spec/services/users/email_verification_service_spec.rb @@ -27,7 +27,7 @@ context 'when token is invalid' do let(:authentication_token) { 'invalidtoken' } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } it_behaves_like 'does not update' end diff --git a/spec/services/users/identity/link_service_spec.rb b/spec/services/users/identity/link_service_spec.rb index 85f38117..56e8e606 100644 --- a/spec/services/users/identity/link_service_spec.rb +++ b/spec/services/users/identity/link_service_spec.rb @@ -53,7 +53,7 @@ def setup_identity_provider(identity) it do expect(service_response).not_to be_success - expect(service_response.payload.full_messages).to include('Identifier has already been taken') + expect(service_response.payload[:details].full_messages).to include('Identifier has already been taken') end it 'does not create the audit event' do diff --git a/spec/services/users/identity/login_service_spec.rb b/spec/services/users/identity/login_service_spec.rb index 2bb5d44d..71d7e8ea 100644 --- a/spec/services/users/identity/login_service_spec.rb +++ b/spec/services/users/identity/login_service_spec.rb @@ -59,7 +59,7 @@ def setup_identity_provider(identity) it do is_expected.not_to create_audit_event expect(service_response).not_to be_success - expect(service_response.payload.message).to eq('Validation failed') + expect(service_response.payload[:error_code]).to eq(:loading_identity_failed) expect(service_response.message).to eq('An error occurred while loading external identity') end end @@ -75,7 +75,7 @@ def setup_identity_provider(identity) it do is_expected.not_to create_audit_event expect(service_response).not_to be_success - expect(service_response.payload).to eq(:invalid_external_identity) + expect(service_response.payload[:error_code]).to eq(:invalid_external_identity) expect(service_response.message).to eq('External identity is nil') end end @@ -92,7 +92,7 @@ def setup_identity_provider(identity) it do is_expected.not_to create_audit_event expect(service_response).not_to be_success - expect(service_response.payload).to eq(:external_identity_does_not_exist) + expect(service_response.payload[:error_code]).to eq(:external_identity_does_not_exist) expect(service_response.message).to eq('No user with that external identity exists, please register first') end end diff --git a/spec/services/users/identity/register_service_spec.rb b/spec/services/users/identity/register_service_spec.rb index 9057f114..8bab4f8d 100644 --- a/spec/services/users/identity/register_service_spec.rb +++ b/spec/services/users/identity/register_service_spec.rb @@ -52,13 +52,13 @@ def setup_identity_provider(identity) it { is_expected.not_to be_success } it { expect(service_response.message).to eq('User registration is disabled') } - it { expect(service_response.payload).to eq(:registration_disabled) } + it { expect(service_response.payload[:error_code]).to eq(:registration_disabled) } end end - shared_examples 'invalid user' do |error_message| + shared_examples 'invalid user' do |error_code| it { is_expected.not_to be_success } - it { expect(service_response.message).to eq(error_message) } + it { expect(service_response[:error_code]).to eq(error_code) } it { expect { service_response }.not_to create_audit_event } end @@ -79,10 +79,10 @@ def setup_identity_provider(identity) end it 'returns the right error' do - expect(service_response.payload).to eq(:missing_identity_data) + expect(service_response.payload[:error_code]).to eq(:missing_identity_data) end - it_behaves_like 'invalid user', 'No email given' + it_behaves_like 'invalid user', :missing_identity_data end context 'when username is missing' do @@ -163,7 +163,7 @@ def setup_identity_provider(identity) it 'catches the error' do expect { service_response }.not_to raise_error - expect(service_response.payload).to eq(:identity_validation_failed) + expect(service_response.payload[:error_code]).to eq(:identity_validation_failed) expect(service_response.message).to eq('Error message') end end @@ -183,9 +183,9 @@ def setup_identity_provider(identity) end it { is_expected.not_to be_success } - it { expect(service_response.message).to eq('UserIdentity is invalid') } + it { expect(service_response[:error_code]).to eq(:invalid_user_identity) } it { expect { service_response }.not_to create_audit_event } - it { expect(service_response.payload.full_messages).to include('Identifier has already been taken') } + it { expect(service_response.payload[:details].full_messages).to include('Identifier has already been taken') } end context 'when email is a duplicate' do @@ -198,6 +198,6 @@ def setup_identity_provider(identity) existing_user.email, 'firstname', 'lastname') end - it_behaves_like 'invalid user', 'User is invalid' + it_behaves_like 'invalid user', :invalid_user end end diff --git a/spec/services/users/login_service_spec.rb b/spec/services/users/login_service_spec.rb index 71a5de1b..4ec89b2e 100644 --- a/spec/services/users/login_service_spec.rb +++ b/spec/services/users/login_service_spec.rb @@ -61,7 +61,7 @@ it 'fails' do expect(service_response).not_to be_success - expect(service_response.payload).to eq(:mfa_failed) + expect(service_response.payload[:error_code]).to eq(:mfa_failed) is_expected.not_to create_audit_event end end @@ -112,7 +112,7 @@ it 'returns an error response' do expect(service_response).to be_error - expect(service_response.payload).to eq(:mfa_failed) + expect(service_response.payload[:error_code]).to eq(:mfa_failed) is_expected.not_to create_audit_event end end diff --git a/spec/services/users/mfa/backup_codes/rotate_service_spec.rb b/spec/services/users/mfa/backup_codes/rotate_service_spec.rb index 0c45b754..ac8e44ed 100644 --- a/spec/services/users/mfa/backup_codes/rotate_service_spec.rb +++ b/spec/services/users/mfa/backup_codes/rotate_service_spec.rb @@ -9,7 +9,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } end context 'when user is valid' do diff --git a/spec/services/users/mfa/totp/generate_secret_service_spec.rb b/spec/services/users/mfa/totp/generate_secret_service_spec.rb index 741df6a4..0a8fb960 100644 --- a/spec/services/users/mfa/totp/generate_secret_service_spec.rb +++ b/spec/services/users/mfa/totp/generate_secret_service_spec.rb @@ -9,7 +9,7 @@ let(:current_user) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } end context 'when user is valid' do @@ -17,7 +17,7 @@ let(:current_user) { create(:user, totp_secret: ROTP::Base32.random) } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:totp_secret_already_set) } + it { expect(service_response.payload[:error_code]).to eq(:totp_secret_already_set) } end context 'when totp secret is not set' do diff --git a/spec/services/users/mfa/totp/validate_secret_service_spec.rb b/spec/services/users/mfa/totp/validate_secret_service_spec.rb index 36ed93b0..045e935c 100644 --- a/spec/services/users/mfa/totp/validate_secret_service_spec.rb +++ b/spec/services/users/mfa/totp/validate_secret_service_spec.rb @@ -13,7 +13,7 @@ let(:signed_secret) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:missing_permission) } + it { expect(service_response.payload[:error_code]).to eq(:missing_permission) } end context 'when user is valid but totp secret is already set' do @@ -22,7 +22,7 @@ let(:signed_secret) { nil } it { is_expected.not_to be_success } - it { expect(service_response.payload).to eq(:totp_secret_already_set) } + it { expect(service_response.payload[:error_code]).to eq(:totp_secret_already_set) } end context 'when user and secret is valid but totp is not' do diff --git a/spec/services/users/password_reset_service_spec.rb b/spec/services/users/password_reset_service_spec.rb index c34f2afb..208a0669 100644 --- a/spec/services/users/password_reset_service_spec.rb +++ b/spec/services/users/password_reset_service_spec.rb @@ -28,7 +28,7 @@ context 'when token is invalid' do let(:authentication_token) { 'invalidtoken' } - it { expect(service_response.payload).to eq(:invalid_verification_code) } + it { expect(service_response.payload[:error_code]).to eq(:invalid_verification_code) } it_behaves_like 'user doesnt verify' end diff --git a/spec/services/users/register_service_spec.rb b/spec/services/users/register_service_spec.rb index a012b29f..9e5e66f3 100644 --- a/spec/services/users/register_service_spec.rb +++ b/spec/services/users/register_service_spec.rb @@ -52,7 +52,7 @@ it { is_expected.not_to be_success } it { expect(service_response.message).to eq('User registration is disabled') } - it { expect(service_response.payload).to eq(:registration_disabled) } + it { expect(service_response.payload[:error_code]).to eq(:registration_disabled) } end end @@ -85,7 +85,7 @@ let(:password) { generate(:password) } it_behaves_like 'invalid user' - it { expect(service_response.payload.full_messages).to include('Username has already been taken') } + it { expect(service_response.payload[:details].full_messages).to include('Username has already been taken') } end context 'when email is duplicated' do @@ -94,7 +94,7 @@ let(:password) { generate(:password) } it_behaves_like 'invalid user' - it { expect(service_response.payload.full_messages).to include('Email has already been taken') } + it { expect(service_response.payload[:details].full_messages).to include('Email has already been taken') } end end @@ -105,7 +105,7 @@ let(:password) { generate(:password) } it_behaves_like 'invalid user' - it { expect(service_response.payload.full_messages).to include("Username can't be blank") } + it { expect(service_response.payload[:details].full_messages).to include("Username can't be blank") } end context 'when email is nil' do @@ -114,7 +114,7 @@ let(:password) { generate(:password) } it_behaves_like 'invalid user' - it { expect(service_response.payload.full_messages).to include("Email can't be blank") } + it { expect(service_response.payload[:details].full_messages).to include("Email can't be blank") } end context 'when password is nil' do @@ -123,7 +123,7 @@ let(:password) { nil } it_behaves_like 'invalid user' - it { expect(service_response.payload.full_messages).to include("Password can't be blank") } + it { expect(service_response.payload[:details].full_messages).to include("Password can't be blank") } end end end diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb index 82433f30..8ef876a8 100644 --- a/spec/services/users/update_service_spec.rb +++ b/spec/services/users/update_service_spec.rb @@ -153,7 +153,7 @@ context 'when mfa is not provided' do it 'requires mfa' do - expect(service_response.payload).to eq(:mfa_required) + expect(service_response.payload[:error_code]).to eq(:mfa_required) end it { is_expected.not_to be_success } @@ -173,7 +173,7 @@ end it 'fails' do - expect(service_response.payload).to eq(:mfa_failed) + expect(service_response.payload[:error_code]).to eq(:mfa_failed) end it { is_expected.not_to be_success } diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index 36bbffb8..979f0fae 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -77,16 +77,11 @@ def expect_graphql_errors_to_be_empty def error_query %( errors { - ...on ActiveModelError { - attribute - type - } - ...on MessageError { - message - } - ...on ErrorCode { - errorCode - } + errorCode + details { + ...on ActiveModelError { attribute type } + ...on MessageError { message } + } } ) end From 15f4545d21f1b4e32b117f25b54e5751062139d9 Mon Sep 17 00:00:00 2001 From: Dario Pranjic Date: Thu, 13 Nov 2025 21:58:12 +0100 Subject: [PATCH 04/15] fix some suggestions from copilot --- app/services/error_code.rb | 1 + app/services/files/upload_service.rb | 4 ++-- app/services/users/identity/register_service.rb | 2 +- app/services/users/logout_service.rb | 2 +- .../users/identity/register_service_spec.rb | 14 +++++++------- spec/support/helpers/graphql_helpers.rb | 4 ++-- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/app/services/error_code.rb b/app/services/error_code.rb index 29c7c867..1dddcf7c 100644 --- a/app/services/error_code.rb +++ b/app/services/error_code.rb @@ -64,6 +64,7 @@ def self.error_codes namespace_project_not_found: { description: 'The namespace project with the given identifier was not found' }, namespace_member_not_found: { description: 'The namespace member with the given identifier was not found' }, license_not_found: { description: 'The namespace license with the given identifier was not found' }, + flow_type_not_found: { description: 'The flow type with the given identifier was not found' }, primary_level_not_found: { description: '', deprecation_reason: 'Outdated concept' }, secondary_level_not_found: { description: '', deprecation_reason: 'Outdated concept' }, tertiary_level_exceeds_parameters: { description: '', deprecation_reason: 'Outdated concept' }, diff --git a/app/services/files/upload_service.rb b/app/services/files/upload_service.rb index df68acf7..0cc400c3 100644 --- a/app/services/files/upload_service.rb +++ b/app/services/files/upload_service.rb @@ -27,8 +27,8 @@ def execute object.send(attachment_name).attach attachment unless object.save - ServiceResponse.error(message: 'Failed to save object', error_code: :invalid_attachment, - details: object.errors) + return ServiceResponse.error(message: 'Failed to save object', error_code: :invalid_attachment, + details: object.errors) end AuditService.audit( diff --git a/app/services/users/identity/register_service.rb b/app/services/users/identity/register_service.rb index 00812541..c6b592cb 100644 --- a/app/services/users/identity/register_service.rb +++ b/app/services/users/identity/register_service.rb @@ -57,7 +57,7 @@ def execute end user_session = UserSession.create(user: user) unless user_session.persisted? - t.rollback_and_return! ServiceResponse.error(message: :invalid_user_session, + t.rollback_and_return! ServiceResponse.error(error_code: :invalid_user_session, details: user_session.errors) end diff --git a/app/services/users/logout_service.rb b/app/services/users/logout_service.rb index 201b3804..5f4759c0 100644 --- a/app/services/users/logout_service.rb +++ b/app/services/users/logout_service.rb @@ -23,7 +23,7 @@ def execute ServiceResponse.success(message: 'Logged out session', payload: user_session) else logger.warn(message: 'Failed to log out session', session_id: user_session.id, user_id: user_session.user_id) - ServiceResponse.error(error_code: user_session.errors) + ServiceResponse.error(error_code: :invalid_user_session, details: user_session.errors) end end end diff --git a/spec/services/users/identity/register_service_spec.rb b/spec/services/users/identity/register_service_spec.rb index 8bab4f8d..ba6750f2 100644 --- a/spec/services/users/identity/register_service_spec.rb +++ b/spec/services/users/identity/register_service_spec.rb @@ -38,11 +38,11 @@ def setup_identity_provider(identity) it 'creates the audit event' do expect { service_response }.to create_audit_event( - :user_registered, - entity_type: 'User', - details: { 'provider_id' => provider_id.to_s, 'identifier' => 'identifier' }, - target_type: 'User' - ) + :user_registered, + entity_type: 'User', + details: { 'provider_id' => provider_id.to_s, 'identifier' => 'identifier' }, + target_type: 'User' + ) end context 'when user registration is disabled' do @@ -58,7 +58,7 @@ def setup_identity_provider(identity) shared_examples 'invalid user' do |error_code| it { is_expected.not_to be_success } - it { expect(service_response[:error_code]).to eq(error_code) } + it { expect(service_response.payload[:error_code]).to eq(error_code) } it { expect { service_response }.not_to create_audit_event } end @@ -183,7 +183,7 @@ def setup_identity_provider(identity) end it { is_expected.not_to be_success } - it { expect(service_response[:error_code]).to eq(:invalid_user_identity) } + it { expect(service_response.payload[:error_code]).to eq(:invalid_user_identity) } it { expect { service_response }.not_to create_audit_event } it { expect(service_response.payload[:details].full_messages).to include('Identifier has already been taken') } end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index 979f0fae..a48f537e 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -79,8 +79,8 @@ def error_query errors { errorCode details { - ...on ActiveModelError { attribute type } - ...on MessageError { message } + ...on ActiveModelError { attribute type } + ...on MessageError { message } } } ) From bc05b9985754373984d7ee9a46239d92d3c19180 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:22:19 +0000 Subject: [PATCH 05/15] Initial plan From 6c52f5f3bc63850bd4068f851f32dc46655131df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:22:22 +0000 Subject: [PATCH 06/15] Initial plan From dffc1af0ce2e2e4d23f575be299f375c75f81054 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:25:12 +0000 Subject: [PATCH 07/15] Fix grammar typo: change "an flow" to "a flow" Co-authored-by: Knerio <96529060+Knerio@users.noreply.github.com> --- app/graphql/types/errors/flow_validation_error_type.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/graphql/types/errors/flow_validation_error_type.rb b/app/graphql/types/errors/flow_validation_error_type.rb index 4732064b..0c99d508 100644 --- a/app/graphql/types/errors/flow_validation_error_type.rb +++ b/app/graphql/types/errors/flow_validation_error_type.rb @@ -6,7 +6,7 @@ module Errors class FlowValidationErrorType < Types::BaseObject graphql_name 'FlowValidationError' # rubocop:enable GraphQL/GraphqlName - description 'Represents an flow validation error' + description 'Represents a flow validation error' field :details, Errors::ActiveModelErrorType, null: true, description: 'Additional details about the validation error' From 5a06083f445b05dcbe1f60eca46db9726dd2fb0f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:25:30 +0000 Subject: [PATCH 08/15] Initial plan From 152e74e52eb31bbb8354b8348ee6cd99f1ca0771 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:25:37 +0000 Subject: [PATCH 09/15] Add missing invalid_totp_secret error code to error_codes hash Co-authored-by: Knerio <96529060+Knerio@users.noreply.github.com> --- app/services/error_code.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/error_code.rb b/app/services/error_code.rb index 1dddcf7c..d2813bf0 100644 --- a/app/services/error_code.rb +++ b/app/services/error_code.rb @@ -30,6 +30,7 @@ def self.error_codes mfa_required: { description: 'MFA is required' }, invalid_login_data: { description: 'Invalid login data provided' }, totp_secret_already_set: { description: 'This user already has TOTP set up' }, + invalid_totp_secret: { description: 'The TOTP secret is invalid or cannot be verified' }, wrong_totp: { description: 'Invalid TOTP code provided' }, invalid_verification_code: { description: 'Invalid verification code provided' }, unmodifiable_field: { description: 'The user is not permitted to modify this field' }, From 168e7a2f7eb27eff7d0c2b3a764c5759dfd85ca0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:25:55 +0000 Subject: [PATCH 10/15] Initial plan From 8e6c712f117a6bd06cf39a1198b3377c7c8549cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:28:36 +0000 Subject: [PATCH 11/15] Add organization_not_found error code to error_codes hash Co-authored-by: Knerio <96529060+Knerio@users.noreply.github.com> --- app/services/error_code.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/error_code.rb b/app/services/error_code.rb index 1dddcf7c..cbf45ae6 100644 --- a/app/services/error_code.rb +++ b/app/services/error_code.rb @@ -65,6 +65,7 @@ def self.error_codes namespace_member_not_found: { description: 'The namespace member with the given identifier was not found' }, license_not_found: { description: 'The namespace license with the given identifier was not found' }, flow_type_not_found: { description: 'The flow type with the given identifier was not found' }, + organization_not_found: { description: 'The organization with the given identifier was not found' }, primary_level_not_found: { description: '', deprecation_reason: 'Outdated concept' }, secondary_level_not_found: { description: '', deprecation_reason: 'Outdated concept' }, tertiary_level_exceeds_parameters: { description: '', deprecation_reason: 'Outdated concept' }, From b6a357ad6a85d668582464c18e620b40e6e5c062 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:30:01 +0000 Subject: [PATCH 12/15] Add missing email_verification_send_failed error code Co-authored-by: Knerio <96529060+Knerio@users.noreply.github.com> --- app/services/error_code.rb | 1 + docs/graphql/enum/errorcodeenum.md | 1 + 2 files changed, 2 insertions(+) diff --git a/app/services/error_code.rb b/app/services/error_code.rb index 1dddcf7c..887abf65 100644 --- a/app/services/error_code.rb +++ b/app/services/error_code.rb @@ -32,6 +32,7 @@ def self.error_codes totp_secret_already_set: { description: 'This user already has TOTP set up' }, wrong_totp: { description: 'Invalid TOTP code provided' }, invalid_verification_code: { description: 'Invalid verification code provided' }, + email_verification_send_failed: { description: 'Failed to send the email verification' }, unmodifiable_field: { description: 'The user is not permitted to modify this field' }, failed_to_invalidate_old_backup_codes: { description: 'The old backup codes could not be deleted' }, failed_to_save_valid_backup_code: { description: 'The new backup codes could not be saved' }, diff --git a/docs/graphql/enum/errorcodeenum.md b/docs/graphql/enum/errorcodeenum.md index 54d74027..8a501c14 100644 --- a/docs/graphql/enum/errorcodeenum.md +++ b/docs/graphql/enum/errorcodeenum.md @@ -11,6 +11,7 @@ Represents the available error responses | `CANNOT_MODIFY_OWN_ADMIN` | Users cannot modify their own admin status | | `CANNOT_REMOVE_LAST_ADMINISTRATOR` | This action would remove the last administrator | | `CANNOT_REMOVE_LAST_ADMIN_ABILITY` | This action would remove the last administrative ability | +| `EMAIL_VERIFICATION_SEND_FAILED` | Failed to send the email verification | | `EXTERNAL_IDENTITY_DOES_NOT_EXIST` | This external identity does not exist | | `FAILED_TO_INVALIDATE_OLD_BACKUP_CODES` | The old backup codes could not be deleted | | `FAILED_TO_RESET_PASSWORD` | Failed to reset the user password | From ca54ad16a0519f66060ab7a0ac38c2aa5afb8c60 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:43:51 +0000 Subject: [PATCH 13/15] Initial plan From 9589eb1329d892328b18281fef6a1957d305ab6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:47:35 +0000 Subject: [PATCH 14/15] Fix invalid_namespace_member error code description Co-authored-by: Knerio <96529060+Knerio@users.noreply.github.com> --- app/services/error_code.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/error_code.rb b/app/services/error_code.rb index 291038fb..c5256f6b 100644 --- a/app/services/error_code.rb +++ b/app/services/error_code.rb @@ -53,7 +53,7 @@ def self.error_codes failed_to_reset_password: { description: 'Failed to reset the user password' }, loading_identity_failed: { description: 'Failed to load user identity from external provider' }, invalid_flow_setting: { description: 'The flow setting is invalid because of active model errors' }, - invalid_namespace_member: { description: 'The flow setting is invalid because of active model errors' }, + invalid_namespace_member: { description: 'The namespace member is invalid because of active model errors' }, invalid_attachment: { description: 'The attachment is invalid because of active model errors' }, invalid_namespace_license: { description: 'The namespace license is invalid because of active model errors' }, project_not_found: { description: 'The namespace project with the given identifier was not found' }, From 3d7479b06326a13105dc015409074bd918aee374 Mon Sep 17 00:00:00 2001 From: Dario Pranjic Date: Sat, 15 Nov 2025 21:54:32 +0100 Subject: [PATCH 15/15] Fix some error codes --- app/graphql/mutations/namespaces/projects/delete.rb | 2 +- app/graphql/mutations/namespaces/projects/flows/create.rb | 2 +- app/graphql/mutations/namespaces/roles/assign_projects.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/graphql/mutations/namespaces/projects/delete.rb b/app/graphql/mutations/namespaces/projects/delete.rb index 48dda124..94f3fd57 100644 --- a/app/graphql/mutations/namespaces/projects/delete.rb +++ b/app/graphql/mutations/namespaces/projects/delete.rb @@ -16,7 +16,7 @@ def resolve(namespace_project_id:) if project.nil? return { organization_project: nil, - errors: [create_error(:namespace_project_not_found, 'Invalid project')] } + errors: [create_error(:project_not_found, 'Invalid project')] } end ::Namespaces::Projects::DeleteService.new( diff --git a/app/graphql/mutations/namespaces/projects/flows/create.rb b/app/graphql/mutations/namespaces/projects/flows/create.rb index 3cf13e60..44396e38 100644 --- a/app/graphql/mutations/namespaces/projects/flows/create.rb +++ b/app/graphql/mutations/namespaces/projects/flows/create.rb @@ -20,7 +20,7 @@ def resolve(project_id:, flow:, **_params) if project.nil? return { flow: nil, - errors: [create_error(:namespace_project_not_found, 'Invalid project id')], + errors: [create_error(:project_not_found, 'Invalid project id')], } end diff --git a/app/graphql/mutations/namespaces/roles/assign_projects.rb b/app/graphql/mutations/namespaces/roles/assign_projects.rb index e23b9148..1379f1ab 100644 --- a/app/graphql/mutations/namespaces/roles/assign_projects.rb +++ b/app/graphql/mutations/namespaces/roles/assign_projects.rb @@ -21,7 +21,7 @@ def resolve(role_id:, project_ids:) if projects.any?(&:nil?) return { projects: nil, - errors: [create_error(:namespace_project_not_found, 'Invalid project')] } + errors: [create_error(:project_not_found, 'Invalid project')] } end ::Namespaces::Roles::AssignProjectsService.new(