All files / src/codec/paths guard.ts

100% Statements 25/25
100% Branches 14/14
100% Functions 1/1
100% Lines 25/25

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  1x                       1x 1x 51x   51x 179x 179x 179x   179x 365x   365x 363x   363x 363x 363x 363x 365x   365x 691x 691x 97x 691x 332x   146x 146x  
import type {NonEmptyArray2} from '#Array'
import {Array, Equivalence, Predicate, pipe} from 'effect'
 
/**
 * Check that a path list is valid:
 *
 * 1. Every leaf, I.e.: a node that appears last in its path, is mentioned in
 *    only one path.
 * 2. All paths share the same root
 * 3. For any pair of appearances of any node the parent node is equal.
 * @category codec
 * @function
 */
export const isValidPathList =
  <A>(
    equals: Equivalence.Equivalence<A>,
  ): Predicate.Predicate<NonEmptyArray2<A>> =>
  paths => {
    const parents = new Map<A, A>(),
      leaves = new Set<A>(),
      root = paths[0][0]
 
    for (const path of paths) {
      const [head, leaf] = [Array.headNonEmpty(path), Array.lastNonEmpty(path)]
 
      if (!equals(head, root) || leaves.has(leaf)) return false
      leaves.add(leaf)
 
      const zipped = pipe(
        Array.tailNonEmpty(path),
        Array.zip(Array.initNonEmpty(path)),
      )
      if (Array.isEmptyArray(zipped)) return paths.length === 1
 
      for (const [child, parent] of zipped) {
        const maybeParent = parents.get(child)
        if (maybeParent === undefined) parents.set(child, parent)
        else if (!equals(maybeParent, parent)) return false
      }
    }
 
    return true
  }