Worked on validation and errors

This commit is contained in:
Adina Totorean 2025-02-06 20:11:01 +02:00
parent 5780596cb1
commit 81ce5c69d6
3 changed files with 244 additions and 21 deletions

View file

@ -79,7 +79,6 @@ export class MicrosoftCosmosDbSharedKeyApi implements ICredentialType {
const url = new URL(requestOptions.baseURL + requestOptions.url);
const pathSegments = url.pathname.split('/').filter((segment) => segment);
console.log('Filtered Path Segments:', pathSegments);
let resourceType = '';
let resourceId = '';

View file

@ -11,7 +11,7 @@ import type {
INodeListSearchItems,
INodeListSearchResult,
} from 'n8n-workflow';
import { ApplicationError } from 'n8n-workflow';
import { ApplicationError, NodeApiError } from 'n8n-workflow';
export const HeaderConstants = {
// Required
@ -123,9 +123,11 @@ export async function handleErrorPostReceive(
data: INodeExecutionData[],
response: IN8nHttpFullResponse,
): Promise<INodeExecutionData[]> {
console.log('Status code❌', response.statusCode);
if (String(response.statusCode).startsWith('4') || String(response.statusCode).startsWith('5')) {
const responseBody = response.body as IDataObject;
console.log('Got here ❌', responseBody);
let errorMessage = 'Unknown error occurred';
if (typeof responseBody.message === 'string') {
@ -386,6 +388,7 @@ export async function validateOperations(
throw new ApplicationError('The "increment" operation must have a numeric value.');
}
//To-Do-check to not send properties it doesn't need
return {
op: operation.op,
path: operation.op === 'move' ? operation.toPath?.value : operation.path?.value,
@ -640,3 +643,156 @@ export async function getDynamicFields(
})),
};
}
export async function fetchPartitionKeyField(
this: ILoadOptionsFunctions,
): Promise<INodeListSearchResult> {
const collection = this.getNodeParameter('collId', '') as { mode: string; value: string };
if (!collection?.value) {
throw new ApplicationError('Collection ID is required to determine the partition key.');
}
const opts: IHttpRequestOptions = {
method: 'GET',
url: `/colls/${collection.value}`,
};
const responseData: IDataObject = await microsoftCosmosDbRequest.call(this, opts);
const partitionKey = responseData.partitionKey as
| {
paths: string[];
kind: string;
version: number;
}
| undefined;
const partitionKeyPaths = partitionKey?.paths ?? [];
if (partitionKeyPaths.length === 0) {
return { results: [] };
}
const partitionKeyField = partitionKeyPaths[0].replace('/', '');
return {
results: [
{
name: partitionKeyField,
value: partitionKeyField,
},
],
};
}
export async function validatePartitionKey(
this: IExecuteSingleFunctions,
requestOptions: IHttpRequestOptions,
): Promise<IHttpRequestOptions> {
const operation = this.getNodeParameter('operation') as string;
const customProperties = this.getNodeParameter('customProperties', {}) as IDataObject;
const partitionKeyResult = await fetchPartitionKeyField.call(
this as unknown as ILoadOptionsFunctions,
);
const partitionKeyField =
partitionKeyResult.results.length > 0 ? partitionKeyResult.results[0].value : '';
if (!partitionKeyField) {
throw new NodeApiError(
this.getNode(),
{},
{
message: 'Partition key not found',
description: 'Failed to determine the partition key for this collection.',
},
);
}
if (!(typeof partitionKeyField === 'string' || typeof partitionKeyField === 'number')) {
throw new NodeApiError(
this.getNode(),
{},
{
message: 'Invalid partition key',
description: `Partition key must be a string or number, but got ${typeof partitionKeyField}.`,
},
);
}
let parsedProperties: Record<string, unknown>;
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
parsedProperties =
typeof customProperties === 'string' ? JSON.parse(customProperties) : customProperties;
} catch (error) {
throw new NodeApiError(
this.getNode(),
{},
{
message: 'Invalid custom properties format',
description: 'Custom properties must be a valid JSON object.',
},
);
}
let id: string | undefined | { mode: string; value: string };
let partitionKeyValue: string | undefined;
if (operation === 'create') {
if (partitionKeyField === 'id') {
partitionKeyValue = this.getNodeParameter('newId', '') as string;
} else {
if (!Object.prototype.hasOwnProperty.call(parsedProperties, partitionKeyField)) {
throw new NodeApiError(
this.getNode(),
{},
{
message: 'Partition key not found in custom properties',
description: `Partition key "${partitionKeyField}" must be present and have a valid, non-empty value in custom properties.`,
},
);
}
partitionKeyValue = parsedProperties[partitionKeyField] as string;
}
} else {
if (partitionKeyField === 'id') {
id = this.getNodeParameter('id', {}) as { mode: string; value: string };
if (!id?.value) {
throw new NodeApiError(
this.getNode(),
{},
{
message: 'Item ID is missing or invalid',
description: "The item must have a valid value selected from 'Item'",
},
);
}
partitionKeyValue = id.value;
} else {
const additionalFields = this.getNodeParameter('additionalFields', {}) as IDataObject;
partitionKeyValue = additionalFields.partitionKey as string;
}
}
if (partitionKeyValue === undefined || partitionKeyValue === null || partitionKeyValue === '') {
throw new NodeApiError(
this.getNode(),
{},
{
message: 'Partition key value is missing or empty',
description: `Provide a value for partition key "${partitionKeyField}" in "Partition Key" field.`,
},
);
}
requestOptions.headers = {
...requestOptions.headers,
'x-ms-documentdb-partitionkey': `["${partitionKeyValue}"]`,
};
return requestOptions;
}

View file

@ -6,6 +6,7 @@ import {
handlePagination,
processResponseItems,
validateOperations,
validatePartitionKey,
validateQueryParameters,
} from '../GenericFunctions';
@ -27,18 +28,18 @@ export const itemOperations: INodeProperties[] = [
description: 'Create a new item',
routing: {
send: {
preSend: [formatCustomProperties],
preSend: [formatCustomProperties, validatePartitionKey],
},
request: {
ignoreHttpStatusErrors: true,
method: 'POST',
url: '=/colls/{{ $parameter["collId"] }}/docs',
//To-Do-do it based on the partition key of collection and only one
headers: {
'x-ms-documentdb-partitionkey': '=["{{$parameter["newId"]}}"]',
'x-ms-documentdb-is-upsert': 'True',
},
},
output: {
postReceive: [handleErrorPostReceive],
},
},
action: 'Create item',
},
@ -47,17 +48,16 @@ export const itemOperations: INodeProperties[] = [
value: 'delete',
description: 'Delete an existing item',
routing: {
send: {
preSend: [validatePartitionKey],
},
request: {
ignoreHttpStatusErrors: true,
method: 'DELETE',
url: '=/colls/{{ $parameter["collId"] }}/docs/{{ $parameter["id"] }}',
//To-Do-do it based on the partition key of collection and only one
headers: {
'x-ms-documentdb-partitionkey': '=["{{$parameter["id"]}}"]',
},
},
output: {
postReceive: [
handleErrorPostReceive,
{
type: 'set',
properties: {
@ -74,16 +74,19 @@ export const itemOperations: INodeProperties[] = [
value: 'get',
description: 'Retrieve an item',
routing: {
send: {
preSend: [validatePartitionKey],
},
request: {
ignoreHttpStatusErrors: true,
method: 'GET',
url: '=/colls/{{ $parameter["collId"]}}/docs/{{$parameter["id"]}}',
headers: {
//To-Do-do it based on the partition key of collection and only one
'x-ms-documentdb-partitionkey': '=["{{$parameter["id"]}}"]',
'x-ms-documentdb-is-upsert': 'True',
},
},
output: {
postReceive: [handleErrorPostReceive],
},
},
action: 'Get item',
},
@ -99,12 +102,11 @@ export const itemOperations: INodeProperties[] = [
pagination: handlePagination,
},
request: {
ignoreHttpStatusErrors: true,
method: 'GET',
url: '=/colls/{{ $parameter["collId"] }}/docs',
},
output: {
postReceive: [processResponseItems],
postReceive: [processResponseItems, handleErrorPostReceive],
},
},
action: 'Get many items',
@ -118,7 +120,6 @@ export const itemOperations: INodeProperties[] = [
preSend: [validateQueryParameters],
},
request: {
ignoreHttpStatusErrors: true,
method: 'POST',
url: '=/colls/{{ $parameter["collId"] }}/docs',
headers: {
@ -126,6 +127,9 @@ export const itemOperations: INodeProperties[] = [
'x-ms-documentdb-isquery': 'True',
},
},
output: {
postReceive: [handleErrorPostReceive],
},
},
action: 'Query items',
},
@ -135,15 +139,13 @@ export const itemOperations: INodeProperties[] = [
description: 'Update an existing item',
routing: {
send: {
preSend: [validateOperations],
preSend: [validateOperations, validatePartitionKey],
},
request: {
ignoreHttpStatusErrors: true,
method: 'PATCH',
url: '=/colls/{{ $parameter["collId"] }}/docs/{{ $parameter["id"] }}',
headers: {
'Content-Type': 'application/json-patch+json',
'x-ms-documentdb-partitionkey': '=["{{$parameter["id"]}}"]',
},
},
output: {
@ -330,6 +332,28 @@ export const deleteFields: INodeProperties[] = [
},
],
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Partition Key',
default: {},
displayOptions: {
show: {
resource: ['item'],
operation: ['delete'],
},
},
options: [
{
displayName: 'Partition Key',
name: 'partitionKey',
type: 'string',
default: '',
description: 'Specify the partition key for this item',
},
],
},
];
export const getFields: INodeProperties[] = [
@ -421,6 +445,28 @@ export const getFields: INodeProperties[] = [
},
],
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Partition Key',
default: {},
displayOptions: {
show: {
resource: ['item'],
operation: ['get'],
},
},
options: [
{
displayName: 'Partition Key',
name: 'partitionKey',
type: 'string',
default: '',
description: 'Specify the partition key for this item',
},
],
},
];
export const getAllFields: INodeProperties[] = [
@ -862,6 +908,28 @@ export const updateFields: INodeProperties[] = [
},
],
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Partition Key',
default: {},
displayOptions: {
show: {
resource: ['item'],
operation: ['update'],
},
},
options: [
{
displayName: 'Partition Key',
name: 'partitionKey',
type: 'string',
default: '',
description: 'Specify the partition key for this item',
},
],
},
];
export const itemFields: INodeProperties[] = [