All files / src parser.ts

89.04% Statements 65/73
47.36% Branches 9/19
100% Functions 18/18
100% Lines 59/59

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 1161x     1x 1x 1x       1x 1x 1x 1x     1x         3x 3x   3x 3x   3x 3x 3x       3x 6x   6x 9x 9x           4x         5x       1x 4x 5x   4x         1x 4x 4x   4x 4x 4x 4x 4x 1x 1x 1x       4x 4x 4x 4x   4x 4x     4x 4x         1x 1x   1x 1x 4x 4x 4x   4x   4x     4x 1x 3x         1x      
import * as R from 'ramda'
import { OpenAPIV3 } from 'openapi-types'
import { ReferenceStorage } from './types/reference-storage'
import { isObject } from './utils/is-object'
import { JSONPath } from 'jsonpath-plus'
import { Reference } from './reference'
import { OpenapiReferenceParserResult } from './types/openapi-reference-parser-result'
 
 
export class OpenapiReferenceParser {
  private directDependenciesStorage: ReferenceStorage<Reference[]> = {}
  private transitiveDependenciesStorage: ReferenceStorage<Reference[]> = {}
  private dependenciesStorage: ReferenceStorage<Reference[]> = {}
 
  constructor(
    private document: OpenAPIV3.Document,
  ) {}
 
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  private forEach(fn: (ref: Reference, item: any) => void): void {
    for (const [pathname, pathItem] of Object.entries(this.document.paths || {})) {
      Iif (!isObject(this.document.paths)) continue
 
      for (const m in pathItem) {
        const method = m.toLowerCase() as OpenAPIV3.HttpMethods
 
        const operation = pathItem[method]
        Iif (R.isNil(operation)) continue
        fn(Reference.from(['paths', pathname, method]), operation)
      }
    }
 
    for (const [scopeName, scope] of Object.entries(this.document.components || {})) {
      Iif (!isObject(this.document.components)) continue
 
      for (const [componentName, component] of Object.entries(scope)) {
        Iif (R.isNil(component)) continue
        fn(Reference.from(['components', scopeName, componentName]), component)
      }
    }
  }
 
  private pickRefs(json: object): Reference[] {
    const arr = JSONPath({
      path: "$..*['$ref']",
      json,
    })
 
    return arr.map((ref: string) => Reference.from(ref))
  }
 
  private buildDirectDependenciesStorage(): void {
    this.forEach((ref, item) => {
      const refs = this.pickRefs(item)
        .filter((r) => !r.equals(ref))
 
      ref.set(this.directDependenciesStorage, R.uniqWith((a, b) => a.equals(b), refs))
    })
  }
 
  private buildTransitiveDependenciesStorage(): void {
    this.forEach((ref) => {
      const directDependencies = ref.get(this.directDependenciesStorage)
      Iif (!directDependencies) return
 
      const transitiveDependencies: Reference[] = []
      const dependencies = [...directDependencies]
      const isDuplicate = (r: Reference): boolean => ref.equals(r) || dependencies.some((d) => d.equals(r))
      const appendToDependencies = (refs: Reference[]): void => {
        for (const r of refs) {
          Iif (isDuplicate(r)) continue
          dependencies.push(r)
          transitiveDependencies.push(r)
        }
      }
 
      for (let i = 0; i < dependencies.length; i++) {
        const dep = dependencies[i]
        const depDirectDependencies = dep.get(this.directDependenciesStorage)
        const depTransitiveDependencies = dep.get(this.transitiveDependenciesStorage)
 
        Iif (depTransitiveDependencies) appendToDependencies(depTransitiveDependencies)
        else if (depDirectDependencies) appendToDependencies(depDirectDependencies)
      }
 
      ref.set(this.transitiveDependenciesStorage, transitiveDependencies)
      ref.set(this.dependenciesStorage, dependencies)
    })
  }
 
  parse(): OpenapiReferenceParserResult {
    this.buildDirectDependenciesStorage()
    this.buildTransitiveDependenciesStorage()
 
    const result: OpenapiReferenceParserResult = {}
    this.forEach((ref) => {
      const dependencies = ref.get(this.dependenciesStorage)
      const transitiveDependencies = ref.get(this.transitiveDependenciesStorage)
      const directDependencies = ref.get(this.directDependenciesStorage)
 
      Iif (!dependencies || !transitiveDependencies || !directDependencies) return
 
      ref.set(
        result,
        {
          dependencies: dependencies.map((r) => r.toString()),
          transitiveDependencies: transitiveDependencies.map((r) => r.toString()),
          directDependencies: directDependencies.map((r) => r.toString()),
        },
      )
    })
 
    return result
  }
}