All files / src/utils endpoint.ts

98.21% Statements 55/56
85.71% Branches 36/42
100% Functions 9/9
100% Lines 46/46

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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            14x 14x   14x 15x 15x 15x 15x 5x 5x 3x   10x     14x 41x 10x 4x   37x 15x 15x 14x 1x 1x 1x         13x 3x 1x   12x 12x   22x             27x 27x 18x 18x     27x 27x 27x 15x 3x 3x         12x     27x       27x     27x 15x 3x     27x 27x    
import {
  normalizeTangoAttributeInputs,
  toInputScalarString,
  type TangoAttributeInput
} from '@ska-octopus-widget-sdk/widget-sdk';
 
const DEFAULT_ENDPOINT = '1';
const URL_SCHEMES = new Set(['http', 'https', 'ws', 'wss']);
 
const parseRoutedEndpoint = (value: string): string => {
  const trimmed = String(value ?? '').trim();
  Iif (!trimmed) return '';
  const routed = /^(.+):\/\/.+$/.exec(trimmed);
  if (routed?.[1]) {
    const selector = routed[1].trim();
    if (URL_SCHEMES.has(selector.toLowerCase())) return '';
    return selector;
  }
  return trimmed.includes('/') ? '' : trimmed;
};
 
const collectLegacyEntries = (input: unknown, out: unknown[]): void => {
  if (Array.isArray(input)) {
    input.forEach((entry) => collectLegacyEntries(entry, out));
    return;
  }
  if (typeof input === 'string') {
    const raw = input.trim();
    if (!raw) return;
    if (raw.startsWith('[') && raw.endsWith(']')) {
      try {
        collectLegacyEntries(JSON.parse(raw), out);
        return;
      } catch {
        // Fall through and treat as regular string.
      }
    }
    if (raw.includes(',')) {
      raw.split(',').forEach((entry) => collectLegacyEntries(entry, out));
      return;
    }
    out.push(raw);
    return;
  }
  if (input != null) out.push(input);
};
 
export function normalizeEndpoints(
  input: unknown,
  opts?: { variables?: Record<string, unknown> | null }
): string[] {
  const uniq = new Set<string>();
  const add = (value: unknown) => {
    const endpoint = toInputScalarString(value);
    if (endpoint) uniq.add(endpoint);
  };
 
  const entries: unknown[] = [];
  collectLegacyEntries(input, entries);
  const tangoEntries: TangoAttributeInput[] = entries.map((entry) => {
    if (entry && typeof entry === 'object') {
      const candidate = entry as Record<string, unknown>;
      return {
        endpoint: candidate.endpoint,
        attribute: candidate.attribute ?? candidate.device ?? candidate.value ?? '__endpoint__'
      };
    }
    return toInputScalarString(entry);
  });
 
  const routed = normalizeTangoAttributeInputs({
    attributes: tangoEntries,
    variables: opts?.variables ?? undefined
  });
  routed.forEach((value) => add(parseRoutedEndpoint(value)));
 
  // Backward-compat: preserve endpoint values from object entries even when attribute is empty.
  entries.forEach((entry) => {
    if (!entry || typeof entry !== 'object') return;
    add((entry as Record<string, unknown>).endpoint);
  });
 
  if (uniq.size === 0) uniq.add(DEFAULT_ENDPOINT);
  return Array.from(uniq);
}