File size: 3,028 Bytes
da2e594
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
 * Utility functions for detecting and handling n8n expressions
 */

/**
 * Detects if a value is an n8n expression
 *
 * n8n expressions can be:
 * - Pure expression: `={{ $json.value }}`
 * - Mixed content: `=https://api.com/{{ $json.id }}/data`
 * - Prefix-only: `=$json.value`
 *
 * @param value - The value to check
 * @returns true if the value is an expression (starts with =)
 */
export function isExpression(value: unknown): value is string {
  return typeof value === 'string' && value.startsWith('=');
}

/**
 * Detects if a string contains n8n expression syntax {{ }}
 *
 * This checks for expression markers within the string,
 * regardless of whether it has the = prefix.
 *
 * @param value - The value to check
 * @returns true if the value contains {{ }} markers
 */
export function containsExpression(value: unknown): boolean {
  if (typeof value !== 'string') {
    return false;
  }
  // Use single regex for better performance than two includes()
  return /\{\{.*\}\}/s.test(value);
}

/**
 * Detects if a value should skip literal validation
 *
 * This is the main utility to use before validating values like URLs, JSON, etc.
 * It returns true if:
 * - The value is an expression (starts with =)
 * - OR the value contains expression markers {{ }}
 *
 * @param value - The value to check
 * @returns true if validation should be skipped
 */
export function shouldSkipLiteralValidation(value: unknown): boolean {
  return isExpression(value) || containsExpression(value);
}

/**
 * Extracts the expression content from a value
 *
 * If value is `={{ $json.value }}`, returns `$json.value`
 * If value is `=$json.value`, returns `$json.value`
 * If value is not an expression, returns the original value
 *
 * @param value - The value to extract from
 * @returns The expression content or original value
 */
export function extractExpressionContent(value: string): string {
  if (!isExpression(value)) {
    return value;
  }

  const withoutPrefix = value.substring(1); // Remove =

  // Check if it's wrapped in {{ }}
  const match = withoutPrefix.match(/^\{\{(.+)\}\}$/s);
  if (match) {
    return match[1].trim();
  }

  return withoutPrefix;
}

/**
 * Checks if a value is a mixed content expression
 *
 * Mixed content has both literal text and expressions:
 * - `Hello {{ $json.name }}!`
 * - `https://api.com/{{ $json.id }}/data`
 *
 * @param value - The value to check
 * @returns true if the value has mixed content
 */
export function hasMixedContent(value: unknown): boolean {
  // Type guard first to avoid calling containsExpression on non-strings
  if (typeof value !== 'string') {
    return false;
  }

  if (!containsExpression(value)) {
    return false;
  }

  // If it's wrapped entirely in {{ }}, it's not mixed
  const trimmed = value.trim();
  if (trimmed.startsWith('={{') && trimmed.endsWith('}}')) {
    // Check if there's only one pair of {{ }}
    const count = (trimmed.match(/\{\{/g) || []).length;
    if (count === 1) {
      return false;
    }
  }

  return true;
}