All files / metadata/src/displayset metadataContext.ts

69.23% Statements 9/13
59.09% Branches 13/22
33.33% Functions 2/6
69.23% Lines 9/13

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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122                                            137x                                 137x                                         137x                                   137x 137x   137x                       137x 137x     137x                                                
/**
 * The **active metadata context**, and the read of one context name for one
 * instance with no split rules.
 *
 * A host sets the context that it gives to `createDisplaySetSplitRules` also
 * here (OHIF: the `metadataContext` customization), so that `isImageInstance`
 * and the other code tests give the same answer as the rules.
 *
 * @module displayset/metadataContext
 */
 
import { createSite, safeFunctionCompilers } from '../safeFunctions';
import { ContextProgram, defaultMetadataContext } from './displaySetContext';
import type { DisplaySetContext } from './displaySetContext';
import { BUILT_IN_SERIES_FUNCTIONS } from './seriesFunctions';
import { splitRuleSchema } from './splitRuleSchema';
import type {
  NaturalizedInstance,
  SeriesFunction,
  SeriesFunctionDefinition,
} from './types';
 
let activeContext: DisplaySetContext = defaultMetadataContext;
 
/**
 * Sets the active metadata context: the entries over `defaultMetadataContext`
 * that the code tests read when no context is given. `undefined` goes back to
 * the default. A new object gives a new compile at the next read.
 */
export function setMetadataContext(context?: DisplaySetContext): void {
  activeContext = context ?? defaultMetadataContext;
}
 
/** The active metadata context (see {@link setMetadataContext}). */
export function getMetadataContext(): DisplaySetContext {
  return activeContext;
}
 
/** The compiled program of each context object, keyed by that object. */
const programs = new WeakMap<object, ContextProgram>();
 
/** Options of {@link compileMetadataContext}. */
export type CompileMetadataContextOptions = {
  seriesFunctions?: Readonly<
    Record<string, SeriesFunction | SeriesFunctionDefinition>
  >;
  deniedNames?: readonly string[];
  /** What the messages call the context. */
  definition?: string;
};
 
/**
 * Compiles `context` over `defaultMetadataContext`. With no series functions
 * and no deny list, the program goes into the cache under the `context` object
 * (`defaultMetadataContext` when absent).
 */
export function compileMetadataContext(
  context: DisplaySetContext | undefined,
  options: CompileMetadataContextOptions = {}
): ContextProgram {
  const program = new ContextProgram(
    { ...defaultMetadataContext, ...context },
    {
      seriesFunctions: {
        ...BUILT_IN_SERIES_FUNCTIONS,
        ...options.seriesFunctions,
      },
      deniedNames: options.deniedNames,
      site: createSite(
        {
          definition: options.definition ?? 'metadata context',
          path: 'context',
        },
        splitRuleSchema,
        safeFunctionCompilers
      ),
    }
  );
  Eif (!options.seriesFunctions && !options.deniedNames) {
    programs.set(context ?? defaultMetadataContext, program);
  }
  return program;
}
 
/**
 * The program of `context`: from the cache, else compiled. A split rule set
 * with no series functions and no deny list reads the same cache, so it and
 * the code tests share one compile of the same context object.
 */
export function getMetadataContextProgram(
  context: DisplaySetContext | undefined,
  options: CompileMetadataContextOptions = {}
): ContextProgram {
  const shareable = !options.seriesFunctions && !options.deniedNames;
  const cached = shareable
    ? programs.get(context ?? defaultMetadataContext)
    : undefined;
  return cached ?? compileMetadataContext(context, options);
}
 
/** The cached program of `context`, compiled at the first read. */
function programOf(context: DisplaySetContext): ContextProgram {
  return getMetadataContextProgram(context);
}
 
/**
 * Reads the context name `name` for one instance, with no split rules: the
 * value of the entry of `context`, else of the active context. The entry must
 * have the instance scope (`isImage`, `isVideo`, ...); a series-scope entry is
 * an error, because one instance is not a series.
 *
 * @throws when the context has no entry `name`, or the entry has the series
 *   scope.
 */
export function getContextValue(
  name: string,
  instance: NaturalizedInstance,
  context?: DisplaySetContext
): unknown {
  return programOf(context ?? activeContext).instanceValue(name, instance);
}