diff --git a/packages/nodes-base/nodes/Aws/Cognito/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/Cognito/GenericFunctions.ts index 60c45dc539..bc447cf911 100644 --- a/packages/nodes-base/nodes/Aws/Cognito/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/Cognito/GenericFunctions.ts @@ -14,6 +14,14 @@ import type { } from 'n8n-workflow'; import { ApplicationError, jsonParse, NodeApiError, NodeOperationError } from 'n8n-workflow'; +export async function presendTest( + this: IExecuteSingleFunctions, + requestOptions: IHttpRequestOptions, +): Promise { + console.log('requestOptions', requestOptions); + return requestOptions; +} + /* * Helper function which stringifies the body before sending the request. * It is added to the routing property in the "resource" parameter thus for all requests. @@ -33,14 +41,20 @@ export async function presendFilter( requestOptions: IHttpRequestOptions, ): Promise { const additionalFields = this.getNodeParameter('additionalFields', {}) as IDataObject; - const filterAttribute = additionalFields.filterAttribute as string; + const filterAttribute = additionalFields.filters as string; let filterType = additionalFields.filterType as string; const filterValue = additionalFields.filterValue as string; - if (!filterAttribute || !filterType || !filterValue) { + const hasAnyFilter = filterAttribute || filterType || filterValue; + + if (!hasAnyFilter) { + return requestOptions; + } + + if (hasAnyFilter && (!filterAttribute || !filterType || !filterValue)) { throw new NodeOperationError( this.getNode(), - 'Please provide Filter Attribute, Filter Type, and Filter Value to use filtering.', + 'If filtering is used, please provide Filter Attribute, Filter Type, and Filter Value.', ); } @@ -69,25 +83,25 @@ export async function presendFilter( return requestOptions; } -export async function presendOptions( +export async function presendAdditionalFields( this: IExecuteSingleFunctions, requestOptions: IHttpRequestOptions, ): Promise { - const options = this.getNodeParameter('options', {}) as IDataObject; + const additionalFields = this.getNodeParameter('additionalFields', {}) as IDataObject; - const hasOptions = options.Description || options.Precedence || options.Path || options.RoleArn; + const hasOptions = Object.keys(additionalFields).length > 0; if (!hasOptions) { throw new NodeOperationError( this.getNode(), - 'At least one of the options (Description, Precedence, Path, or RoleArn) must be provided to update the group.', + 'At least one of the additional fields must be provided to update the group.', ); } return requestOptions; } -export async function presendPath( +export async function presendVerifyPath( this: IExecuteSingleFunctions, requestOptions: IHttpRequestOptions, ): Promise { @@ -325,7 +339,7 @@ export async function handleErrorPostReceive( /* Helper function used in listSearch methods */ export async function awsRequest( - this: ILoadOptionsFunctions | IPollFunctions, + this: ILoadOptionsFunctions | IPollFunctions | IExecuteSingleFunctions, opts: IHttpRequestOptions, ): Promise { const region = (await this.getCredentials('aws')).region as string; @@ -376,7 +390,6 @@ export async function awsRequest( } } -/* listSearch methods */ export async function searchUserPools( this: ILoadOptionsFunctions, filter?: string, @@ -448,7 +461,6 @@ export async function searchUsers( const users = responseData.Users as IDataObject[] | undefined; if (!users) { - console.warn('No users found in the response'); return { results: [] }; } @@ -529,3 +541,283 @@ export async function searchGroups( return { results, paginationToken: responseData.NextToken }; } + +export async function simplifyData( + this: IExecuteSingleFunctions, + items: INodeExecutionData[], + response: IN8nHttpFullResponse, +): Promise { + const simple = this.getNodeParameter('simple') as boolean; + + type UserPool = { + Arn: string; + CreationDate: number; + DeletionProtection: string; + Domain: string; + EstimatedNumberOfUsers: number; + Id: string; + LastModifiedDate: number; + MfaConfiguration: string; + Name: string; + }; + + type User = { + Enabled: boolean; + UserAttributes?: Array<{ Name: string; Value: string }>; + Attributes?: Array<{ Name: string; Value: string }>; + UserCreateDate: number; + UserLastModifiedDate: number; + UserStatus: string; + Username: string; + }; + + function mapUserAttributes(userAttributes: Array<{ Name: string; Value: string }>): { + [key: string]: string; + } { + return userAttributes?.reduce( + (acc, { Name, Value }) => { + if (Name !== 'sub') { + acc[Name] = Value; + } + return acc; + }, + {} as { [key: string]: string }, + ); + } + + if (!simple) { + return items; + } + + const resource = this.getNodeParameter('resource'); + const operation = this.getNodeParameter('operation'); + + const simplifiedItems = items + .map((item) => { + const data = item.json?.UserPool as UserPool | undefined; + const userData = item.json as User | undefined; + const users = item.json?.Users as User[] | undefined; + + switch (resource) { + case 'userPool': + if (data) { + return { + json: { + UserPool: { + Arn: data.Arn, + CreationDate: data.CreationDate, + DeletionProtection: data.DeletionProtection, + Domain: data.Domain, + EstimatedNumberOfUsers: data.EstimatedNumberOfUsers, + Id: data.Id, + LastModifiedDate: data.LastModifiedDate, + MfaConfiguration: data.MfaConfiguration, + Name: data.Name, + }, + }, + }; + } + break; + + case 'user': + if (userData) { + if (operation === 'get') { + const userAttributes = userData.UserAttributes + ? mapUserAttributes(userData.UserAttributes) + : {}; + return { + json: { + User: { + Enabled: userData.Enabled, + ...Object.fromEntries(Object.entries(userAttributes).slice(0, 6)), + UserCreateDate: userData.UserCreateDate, + UserLastModifiedDate: userData.UserLastModifiedDate, + UserStatus: userData.UserStatus, + Username: userData.Username, + }, + }, + }; + } else if (operation === 'getAll') { + if (users && Array.isArray(users)) { + const processedUsers: User[] = []; + users.forEach((user) => { + const userAttributes = user.Attributes ? mapUserAttributes(user.Attributes) : {}; + processedUsers.push({ + Enabled: userData.Enabled, + ...Object.fromEntries(Object.entries(userAttributes).slice(0, 6)), + UserCreateDate: userData.UserCreateDate, + UserLastModifiedDate: userData.UserLastModifiedDate, + UserStatus: userData.UserStatus, + Username: userData.Username, + }); + }); + return { + json: { + Users: processedUsers, + }, + }; + } + } + } + break; + } + + return undefined; + }) + .filter((item) => item !== undefined) + .flat(); + + return simplifiedItems; +} + +export async function listUsersInGroup( + this: IExecuteSingleFunctions, + groupName: string, + userPoolId: string, +): Promise { + if (!userPoolId) { + throw new ApplicationError('User Pool ID is required'); + } + + const opts: IHttpRequestOptions = { + url: '', + method: 'POST', + headers: { + 'X-Amz-Target': 'AWSCognitoIdentityProviderService.ListUsersInGroup', + }, + body: JSON.stringify({ + UserPoolId: userPoolId, + GroupName: groupName, + MaxResults: 60, + }), + }; + + const responseData: IDataObject = await awsRequest.call(this, opts); + + const users = responseData.Users as IDataObject[] | undefined; + + if (!users) { + return { results: [] }; + } + + const results: INodeListSearchItems[] = users + .map((user) => { + const attributes = user.Attributes as Array<{ Name: string; Value: string }> | undefined; + + const email = attributes?.find((attr) => attr.Name === 'email')?.Value; + const sub = attributes?.find((attr) => attr.Name === 'sub')?.Value; + const username = user.Username as string; + + const name = email || sub || username; + const value = username; + + return { name, value }; + }) + .sort((a, b) => { + return a.name.toLowerCase().localeCompare(b.name.toLowerCase()); + }); + + return { results, paginationToken: responseData.NextToken as string | undefined }; +} + +export async function getUsersForGroup( + this: IExecuteSingleFunctions, + groupName: string, + userPoolId: string, +): Promise { + const users = await listUsersInGroup.call(this, groupName, userPoolId); + + if (users && users.results && Array.isArray(users.results) && users.results.length > 0) { + return users.results as IDataObject[]; + } + + return [] as IDataObject[]; +} + +export async function processUsersForGroups( + this: IExecuteSingleFunctions, + items: INodeExecutionData[], + response: IN8nHttpFullResponse, +): Promise { + const userPoolIdRaw = this.getNodeParameter('userPoolId') as IDataObject; + const userPoolId = userPoolIdRaw.value as string; + const include = this.getNodeParameter('includeUsers', 0) as boolean; + + if (!include) { + return items; + } + + const processedGroups: IDataObject[] = []; + + if (response.body && typeof response.body === 'object') { + if ('Group' in response.body) { + const group = (response.body as { Group: IDataObject }).Group; + const usersResponse = await getUsersForGroup.call( + this, + group.GroupName as string, + userPoolId, + ); + + if (usersResponse.length > 0) { + return items.map((item) => ({ + json: { ...item.json, Users: usersResponse }, + })); + } else { + return items.map((item) => ({ + json: { ...item.json }, + })); + } + } else { + const groups = (response.body as { Groups: IDataObject[] }).Groups; + + for (const group of groups) { + const usersResponse = await getUsersForGroup.call( + this, + group.GroupName as string, + userPoolId, + ); + + if (usersResponse.length > 0) { + processedGroups.push({ + ...group, + Users: usersResponse, + }); + } else { + processedGroups.push(group); + } + } + } + } + + return items.map((item) => ({ + json: { ...item.json, Groups: processedGroups }, + })); +} + +//Check if needed +export async function fetchUserPoolConfig( + this: IExecuteSingleFunctions, + requestOptions: IHttpRequestOptions, +): Promise { + const userPoolIdRaw = this.getNodeParameter('userPoolId') as IDataObject; + const userPoolId = userPoolIdRaw.value as string; + + if (!userPoolId) { + throw new ApplicationError('User Pool ID is required'); + } + + const opts: IHttpRequestOptions = { + url: '', + method: 'POST', + body: JSON.stringify({ + UserPoolId: userPoolId, + }), + headers: { + 'X-Amz-Target': 'AWSCognitoIdentityProviderService.DescribeUserPool', + }, + }; + + const responseData: IDataObject = await awsRequest.call(this, opts); + + return requestOptions; +} diff --git a/packages/nodes-base/nodes/Aws/Cognito/descriptions/GroupDescription.ts b/packages/nodes-base/nodes/Aws/Cognito/descriptions/GroupDescription.ts index 8119b48587..c68cdb3369 100644 --- a/packages/nodes-base/nodes/Aws/Cognito/descriptions/GroupDescription.ts +++ b/packages/nodes-base/nodes/Aws/Cognito/descriptions/GroupDescription.ts @@ -3,8 +3,9 @@ import type { INodeProperties } from 'n8n-workflow'; import { handleErrorPostReceive, handlePagination, - presendOptions, - presendPath, + presendAdditionalFields, + presendVerifyPath, + processUsersForGroups, } from '../GenericFunctions'; export const groupOperations: INodeProperties[] = [ @@ -77,7 +78,7 @@ export const groupOperations: INodeProperties[] = [ ignoreHttpStatusErrors: true, }, output: { - postReceive: [handleErrorPostReceive], + postReceive: [processUsersForGroups, handleErrorPostReceive], }, }, action: 'Get group', @@ -101,7 +102,7 @@ export const groupOperations: INodeProperties[] = [ ignoreHttpStatusErrors: true, }, output: { - postReceive: [handleErrorPostReceive], + postReceive: [processUsersForGroups, handleErrorPostReceive], }, }, action: 'Get many groups', @@ -112,7 +113,7 @@ export const groupOperations: INodeProperties[] = [ description: 'Update an existing group', routing: { send: { - preSend: [presendOptions], + preSend: [presendAdditionalFields], }, request: { method: 'POST', @@ -264,7 +265,7 @@ const createFields: INodeProperties[] = [ send: { property: 'Path', type: 'body', - preSend: [presendPath], + preSend: [presendVerifyPath], }, }, }, @@ -492,6 +493,19 @@ const getFields: INodeProperties[] = [ }, ], }, + { + displayName: 'Include User List', + name: 'includeUsers', + type: 'boolean', + displayOptions: { + show: { + resource: ['group'], + operation: ['get'], + }, + }, + default: true, + description: 'Whether to include a list of users in the group', + }, ]; const getAllFields: INodeProperties[] = [ @@ -553,6 +567,19 @@ const getAllFields: INodeProperties[] = [ displayOptions: { show: { resource: ['group'], operation: ['getAll'], returnAll: [false] } }, routing: { send: { type: 'body', property: 'Limit' } }, }, + { + displayName: 'Include User List', + name: 'includeUsers', + type: 'boolean', + displayOptions: { + show: { + resource: ['group'], + operation: ['getAll'], + }, + }, + default: true, + description: 'Whether to include a list of users in the group', + }, ]; const updateFields: INodeProperties[] = [ @@ -657,8 +684,8 @@ const updateFields: INodeProperties[] = [ type: 'resourceLocator', }, { - displayName: 'Options', - name: 'options', + displayName: 'Additional Fields', + name: 'additionalFields', default: {}, displayOptions: { show: { @@ -708,7 +735,7 @@ const updateFields: INodeProperties[] = [ send: { property: 'Path', type: 'body', - preSend: [presendPath], + preSend: [presendVerifyPath], }, }, }, diff --git a/packages/nodes-base/nodes/Aws/Cognito/descriptions/UserDescription.ts b/packages/nodes-base/nodes/Aws/Cognito/descriptions/UserDescription.ts index 0048e050ef..f07e811381 100644 --- a/packages/nodes-base/nodes/Aws/Cognito/descriptions/UserDescription.ts +++ b/packages/nodes-base/nodes/Aws/Cognito/descriptions/UserDescription.ts @@ -1,10 +1,13 @@ import type { INodeProperties } from 'n8n-workflow'; import { + fetchUserPoolConfig, handleErrorPostReceive, handlePagination, presendFilter, + presendTest, processAttributes, + simplifyData, } from '../GenericFunctions'; export const userOperations: INodeProperties[] = [ @@ -52,6 +55,9 @@ export const userOperations: INodeProperties[] = [ description: 'Create a new user', action: 'Create user', routing: { + send: { + preSend: [presendTest, fetchUserPoolConfig], + }, request: { method: 'POST', headers: { @@ -104,7 +110,7 @@ export const userOperations: INodeProperties[] = [ ignoreHttpStatusErrors: true, }, output: { - postReceive: [handleErrorPostReceive], + postReceive: [simplifyData, handleErrorPostReceive], }, }, }, @@ -130,7 +136,7 @@ export const userOperations: INodeProperties[] = [ ignoreHttpStatusErrors: true, }, output: { - postReceive: [handleErrorPostReceive], + postReceive: [simplifyData, handleErrorPostReceive], }, }, action: 'Get many users', @@ -167,6 +173,7 @@ export const userOperations: INodeProperties[] = [ description: 'Update a user', action: 'Update user', routing: { + send: { preSend: [presendTest] }, request: { method: 'POST', headers: { @@ -246,7 +253,7 @@ const createFields: INodeProperties[] = [ displayName: 'User Name', name: 'Username', default: '', - description: 'The username of the new user to create', + description: 'The username of the new user to create. No whitespace is allowed.', placeholder: 'e.g. JohnSmith', displayOptions: { show: { @@ -277,61 +284,51 @@ const createFields: INodeProperties[] = [ }, }, options: [ + //doesn't work { - displayName: 'Client Metadata', - name: 'clientMetadata', + displayName: 'User Attributes', + name: 'UserAttributes', type: 'fixedCollection', - placeholder: 'Add Metadata', - default: { metadata: [] }, - description: 'A map of custom key-value pairs for workflows triggered by this action', + placeholder: 'Add Attribute', + default: { + attributes: [], + }, + description: 'Attributes to add for the user', typeOptions: { multipleValues: true, }, options: [ { - displayName: 'Metadata', - name: 'metadata', + displayName: 'Attributes', + name: 'attributes', values: [ { - displayName: 'Key', - name: 'key', + displayName: 'Name', + name: 'Name', type: 'string', default: '', - description: 'The key of the metadata attribute', + description: 'The name of the attribute (e.g., custom:deliverables)', }, { displayName: 'Value', - name: 'value', + name: 'Value', type: 'string', default: '', - description: 'The value of the metadata attribute', + description: 'The value of the attribute', }, ], }, ], routing: { send: { + preSend: [processAttributes], type: 'body', - property: 'ClientMetadata', + property: 'UserAttributes', value: - '={{ $value.metadata && $value.metadata.length > 0 ? Object.fromEntries($value.metadata.map(attribute => [attribute.Name, attribute.Value])) : {} }}', + '={{ $value.attributes?.map(attribute => ({ Name: attribute.Name, Value: attribute.Value })) || [] }}', }, }, }, - { - displayName: 'Temporary Password', - name: 'TemporaryPassword', - default: '', - description: "The user's temporary password", - routing: { - send: { - property: 'TemporaryPassword', - type: 'body', - }, - }, - type: 'string', - typeOptions: { password: true }, - }, { displayName: 'Message Action', name: 'MessageAction', @@ -396,88 +393,45 @@ const createFields: INodeProperties[] = [ }, }, { - displayName: 'User Attributes', - name: 'UserAttributes', - type: 'fixedCollection', - placeholder: 'Add Attribute', - default: { - attributes: [], - }, - description: 'Attributes to add for the user', - typeOptions: { - multipleValues: true, - }, + displayName: 'Temporary Password', + name: 'temporaryPasswordOptions', + type: 'options', + default: 'generatePassword', + description: 'Choose to set a password manually or one will be automatically generated', options: [ { - displayName: 'Attributes', - name: 'attributes', - values: [ - { - displayName: 'Name', - name: 'Name', - type: 'string', - default: '', - description: 'The name of the attribute (e.g., custom:deliverables)', - }, - { - displayName: 'Value', - name: 'Value', - type: 'string', - default: '', - description: 'The value of the attribute', - }, - ], + name: 'Set a Password', + value: 'setPassword', + }, + { + name: 'Generate a Password', + value: 'generatePassword', }, ], routing: { send: { - preSend: [processAttributes], + property: 'TemporaryPassword', type: 'body', - property: 'UserAttributes', - value: - '={{ $value.attributes?.map(attribute => ({ Name: attribute.Name, Value: attribute.Value })) || [] }}', }, }, }, { - displayName: 'Validation Data', - name: 'ValidationData', - type: 'fixedCollection', - placeholder: 'Add Attribute', - default: { - attributes: [], - }, - description: 'Validation data to add for the user', - typeOptions: { - multipleValues: true, - }, - options: [ - { - displayName: 'Data', - name: 'data', - values: [ - { - displayName: 'Key', - name: 'Key', - type: 'string', - default: '', - description: 'The name of the data (e.g., custom:deliverables)', - }, - { - displayName: 'Value', - name: 'Value', - type: 'string', - default: '', - description: 'The value of the data', - }, - ], + displayName: 'Password', + name: 'password', + type: 'string', + typeOptions: { password: true }, + default: '', + placeholder: 'Enter a temporary password', + description: "The user's temporary password", + displayOptions: { + show: { + temporaryPasswordOptions: ['setPassword'], }, - ], + }, routing: { send: { + property: 'TemporaryPassword', type: 'body', - property: 'ValidationData', - value: '={{ $value.data?.map(data => ({ Name: data.Key, Value: data.Value })) || [] }}', }, }, }, @@ -586,6 +540,19 @@ const getFields: INodeProperties[] = [ required: true, type: 'resourceLocator', }, + { + displayName: 'Simplify', + name: 'simple', + type: 'boolean', + displayOptions: { + show: { + resource: ['user'], + operation: ['get'], + }, + }, + default: true, + description: 'Whether to return a simplified version of the response instead of the raw data', + }, ]; const getAllFields: INodeProperties[] = [ @@ -660,48 +627,35 @@ const getAllFields: INodeProperties[] = [ displayOptions: { show: { resource: ['user'], operation: ['getAll'], returnAll: [false] } }, routing: { send: { type: 'body', property: 'Limit' } }, }, + { + displayName: 'Simplify', + name: 'simple', + type: 'boolean', + displayOptions: { + show: { + resource: ['user'], + operation: ['getAll'], + }, + }, + default: true, + description: 'Whether to return a simplified version of the response instead of the raw data', + }, { displayName: 'Additional Fields', name: 'additionalFields', type: 'collection', placeholder: 'Add Field', default: {}, - displayOptions: { show: { resource: ['user'], operation: ['getAll'] } }, + displayOptions: { + show: { + resource: ['user'], + operation: ['getAll'], + }, + }, options: [ { - displayName: 'Attributes To Get', - name: 'attributesToGet', - type: 'fixedCollection', - typeOptions: { multipleValues: true }, - default: {}, - placeholder: 'Add Attribute', - description: 'The attributes to return in the response', - options: [ - { - name: 'metadataValues', - displayName: 'Metadata', - values: [ - { - displayName: 'Attribute', - name: 'attribute', - type: 'string', - default: '', - description: 'The attribute name to return', - }, - ], - }, - ], - routing: { - send: { - type: 'body', - property: 'AttributesToGet', - value: '={{ $value.metadataValues.map(attribute => attribute.attribute) }}', - }, - }, - }, - { - displayName: 'Filter Attribute', - name: 'filterAttribute', + displayName: 'Filters', + name: 'filters', type: 'options', default: 'username', hint: 'Make sure to select an attribute, type, and provide a value before submitting.', @@ -896,7 +850,7 @@ const updateFields: INodeProperties[] = [ ], }, { - displayName: 'User', + displayName: 'User Name', name: 'Username', default: { mode: 'list', @@ -988,6 +942,7 @@ const updateFields: INodeProperties[] = [ ], routing: { send: { + preSend: [processAttributes], type: 'body', property: 'UserAttributes', value: @@ -995,62 +950,6 @@ const updateFields: INodeProperties[] = [ }, }, }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - default: {}, - displayOptions: { - show: { - resource: ['user'], - operation: ['update'], - }, - }, - options: [ - { - displayName: 'Client Metadata', - name: 'clientMetadata', - type: 'fixedCollection', - placeholder: 'Add Metadata Pair', - default: { metadata: [] }, - description: 'A map of custom key-value pairs for workflows triggered by this action', - typeOptions: { - multipleValues: true, - }, - options: [ - { - displayName: 'Metadata', - name: 'metadata', - values: [ - { - displayName: 'Key', - name: 'key', - type: 'string', - default: '', - description: 'The key of the metadata attribute', - }, - { - displayName: 'Value', - name: 'value', - type: 'string', - default: '', - description: 'The value of the metadata attribute', - }, - ], - }, - ], - routing: { - send: { - type: 'body', - property: 'ClientMetadata', - value: - '={{ $value.metadata && $value.metadata.length > 0 ? Object.fromEntries($value.metadata.map(attribute => [attribute.Name, attribute.Value])) : {} }}', - }, - }, - }, - ], - }, ]; const addToGroupFields: INodeProperties[] = [ diff --git a/packages/nodes-base/nodes/Aws/Cognito/descriptions/UserPoolDescription.ts b/packages/nodes-base/nodes/Aws/Cognito/descriptions/UserPoolDescription.ts index 8127aad3a6..0576612a0c 100644 --- a/packages/nodes-base/nodes/Aws/Cognito/descriptions/UserPoolDescription.ts +++ b/packages/nodes-base/nodes/Aws/Cognito/descriptions/UserPoolDescription.ts @@ -1,4 +1,5 @@ import type { INodeProperties } from 'n8n-workflow'; +import { presendTest, simplifyData } from '../GenericFunctions'; export const userPoolOperations: INodeProperties[] = [ { @@ -13,12 +14,26 @@ export const userPoolOperations: INodeProperties[] = [ value: 'get', action: 'Describe the configuration of a user pool', routing: { + send: { + preSend: [presendTest], + }, request: { method: 'POST', headers: { 'X-Amz-Target': 'AWSCognitoIdentityProviderService.DescribeUserPool', }, }, + output: { + postReceive: [ + simplifyData, + { + type: 'rootProperty', + properties: { + property: 'UserPool', + }, + }, + ], + }, }, }, ], @@ -28,7 +43,7 @@ export const userPoolOperations: INodeProperties[] = [ export const userPoolFields: INodeProperties[] = [ { - displayName: 'User Pool ID', + displayName: 'User Pool', name: 'userPoolId', required: true, type: 'resourceLocator', @@ -69,4 +84,17 @@ export const userPoolFields: INodeProperties[] = [ }, ], }, + { + displayName: 'Simplify', + name: 'simple', + type: 'boolean', + displayOptions: { + show: { + resource: ['userPool'], + operation: ['get'], + }, + }, + default: true, + description: 'Whether to return a simplified version of the response instead of the raw data', + }, ];