2024-12-20 08:01:22 -08:00
import { json as generateSchemaFromExample , type SchemaObject } from 'generate-schema' ;
import type { JSONSchema7 } from 'json-schema' ;
import _ from 'lodash' ;
import type {
FieldValueOption ,
FieldType ,
IWorkflowNodeContext ,
INodeExecutionData ,
IDataObject ,
ResourceMapperField ,
ILocalLoadOptionsFunctions ,
ResourceMapperFields ,
ISupplyDataFunctions ,
} from 'n8n-workflow' ;
import { jsonParse , NodeOperationError , EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE } from 'n8n-workflow' ;
import {
JSON_EXAMPLE ,
INPUT_SOURCE ,
WORKFLOW_INPUTS ,
VALUES ,
TYPE_OPTIONS ,
PASSTHROUGH ,
} from './constants' ;
const SUPPORTED_TYPES = TYPE_OPTIONS . map ( ( x ) = > x . value ) ;
function parseJsonSchema ( schema : JSONSchema7 ) : FieldValueOption [ ] | string {
if ( ! schema ? . properties ) {
return 'Invalid JSON schema. Missing key `properties` in schema' ;
}
if ( typeof schema . properties !== 'object' ) {
return 'Invalid JSON schema. Key `properties` is not an object' ;
}
const result : FieldValueOption [ ] = [ ] ;
for ( const [ name , v ] of Object . entries ( schema . properties ) ) {
if ( typeof v !== 'object' ) {
return ` Invalid JSON schema. Value for property ' ${ name } ' is not an object ` ;
}
const type = v ? . type ;
if ( type === 'null' ) {
result . push ( { name , type : 'any' } ) ;
} else if ( Array . isArray ( type ) ) {
// Schema allows an array of types, but we don't
return ` Invalid JSON schema. Array of types for property ' ${ name } ' is not supported by n8n. Either provide a single type or use type 'any' to allow any type ` ;
} else if ( typeof type !== 'string' ) {
return ` Invalid JSON schema. Unexpected non-string type ${ type } for property ' ${ name } ' ` ;
} else if ( ! SUPPORTED_TYPES . includes ( type as never ) ) {
return ` Invalid JSON schema. Unsupported type ${ type } for property ' ${ name } '. Supported types are ${ JSON . stringify ( SUPPORTED_TYPES , null , 1 ) } ` ;
} else {
result . push ( { name , type : type as FieldType } ) ;
}
}
return result ;
}
function parseJsonExample ( context : IWorkflowNodeContext ) : JSONSchema7 {
const jsonString = context . getNodeParameter ( JSON_EXAMPLE , 0 , '' ) as string ;
const json = jsonParse < SchemaObject > ( jsonString ) ;
return generateSchemaFromExample ( json ) as JSONSchema7 ;
}
export function getFieldEntries ( context : IWorkflowNodeContext ) : FieldValueOption [ ] {
2025-01-07 00:32:51 -08:00
const inputSource = context . getNodeParameter ( INPUT_SOURCE , 0 , PASSTHROUGH ) ;
2024-12-20 08:01:22 -08:00
let result : FieldValueOption [ ] | string = 'Internal Error: Invalid input source' ;
try {
if ( inputSource === WORKFLOW_INPUTS ) {
result = context . getNodeParameter (
` ${ WORKFLOW_INPUTS } . ${ VALUES } ` ,
0 ,
[ ] ,
) as FieldValueOption [ ] ;
} else if ( inputSource === JSON_EXAMPLE ) {
const schema = parseJsonExample ( context ) ;
result = parseJsonSchema ( schema ) ;
} else if ( inputSource === PASSTHROUGH ) {
result = [ ] ;
}
} catch ( e : unknown ) {
result =
e && typeof e === 'object' && 'message' in e && typeof e . message === 'string'
? e . message
: ` Unknown error occurred: ${ JSON . stringify ( e ) } ` ;
}
if ( Array . isArray ( result ) ) {
return result ;
}
throw new NodeOperationError ( context . getNode ( ) , result ) ;
}
export function getWorkflowInputValues ( this : ISupplyDataFunctions ) : INodeExecutionData [ ] {
const inputData = this . getInputData ( ) ;
2025-01-16 04:04:06 -08:00
return inputData . map ( ( { json , binary } , itemIndex ) = > {
2024-12-20 08:01:22 -08:00
const itemFieldValues = this . getNodeParameter (
'workflowInputs.value' ,
itemIndex ,
{ } ,
) as IDataObject ;
return {
json : {
2025-01-16 04:04:06 -08:00
. . . json ,
2024-12-20 08:01:22 -08:00
. . . itemFieldValues ,
} ,
index : itemIndex ,
pairedItem : {
item : itemIndex ,
} ,
2025-01-16 04:04:06 -08:00
binary ,
2024-12-20 08:01:22 -08:00
} ;
} ) ;
}
export function getCurrentWorkflowInputData ( this : ISupplyDataFunctions ) {
const inputData : INodeExecutionData [ ] = getWorkflowInputValues . call ( this ) ;
const schema = this . getNodeParameter ( 'workflowInputs.schema' , 0 , [ ] ) as ResourceMapperField [ ] ;
if ( schema . length === 0 ) {
return inputData ;
} else {
const removedKeys = new Set ( schema . filter ( ( x ) = > x . removed ) . map ( ( x ) = > x . displayName ) ) ;
const filteredInputData : INodeExecutionData [ ] = inputData . map ( ( item , index ) = > ( {
index ,
pairedItem : { item : index } ,
json : _.pickBy ( item . json , ( _v , key ) = > ! removedKeys . has ( key ) ) ,
} ) ) ;
return filteredInputData ;
}
}
export async function loadWorkflowInputMappings (
this : ILocalLoadOptionsFunctions ,
) : Promise < ResourceMapperFields > {
const nodeLoadContext = await this . getWorkflowNodeContext ( EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE ) ;
let fields : ResourceMapperField [ ] = [ ] ;
if ( nodeLoadContext ) {
const fieldValues = getFieldEntries ( nodeLoadContext ) ;
fields = fieldValues . map ( ( currentWorkflowInput ) = > {
const field : ResourceMapperField = {
id : currentWorkflowInput.name ,
displayName : currentWorkflowInput.name ,
required : false ,
defaultMatch : false ,
display : true ,
canBeUsedToMatch : true ,
} ;
if ( currentWorkflowInput . type !== 'any' ) {
field . type = currentWorkflowInput . type ;
}
return field ;
} ) ;
}
return { fields } ;
}