All files / build-tools massage-spec.ts

35.59% Statements 21/59
22.72% Branches 5/22
45.45% Functions 5/11
37.5% Lines 21/56

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 1221x 1x 1x   1x 1x 1x 1x     1x             1x                                   1x 1x                                 1x 1x 1x 1x 2x 2x 1x           1x                                         1x                                       1x                              
import * as fastJsonPatch from 'fast-json-patch';
import { schema } from '../lib';
import { detectScrutinyTypes } from './scrutiny';
 
export function massageSpec(spec: schema.Specification) {
  detectScrutinyTypes(spec);
  replaceIncompleteTypes(spec);
  dropTypelessAttributes(spec);
}
 
export function forEachSection(spec: schema.Specification, data: any, cb: (spec: any, fragment: any, path: string[]) => void) {
  cb(spec.PropertyTypes, data.PropertyTypes, ['PropertyTypes']);
  cb(spec.ResourceTypes, data.ResourceTypes, ['ResourceTypes']);
  // Per-resource specs are keyed on ResourceType (singular), but we want it in ResourceTypes (plural)
  cb(spec.ResourceTypes, data.ResourceType, ['ResourceType']);
}
 
export function decorateResourceTypes(data: any) {
  const requiredTransform = data.ResourceSpecificationTransform as string | undefined;
  Iif (!requiredTransform) { return; }
  const resourceTypes = data.ResourceTypes || data.ResourceType;
  for (const name of Object.keys(resourceTypes)) {
    resourceTypes[name].RequiredTransform = requiredTransform;
  }
}
 
/**
 * Fix incomplete type definitions in PropertyTypes
 *
 * Some user-defined types are defined to not have any properties, and not
 * be a collection of other types either. They have no definition at all.
 *
 * Add a property object type with empty properties.
 */
function replaceIncompleteTypes(spec: schema.Specification) {
  for (const [name, definition] of Object.entries(spec.PropertyTypes)) {
    Iif (!schema.isRecordType(definition)
    && !schema.isCollectionProperty(definition)
    && !schema.isScalarProperty(definition)
    && !schema.isPrimitiveProperty(definition)) {
      // eslint-disable-next-line no-console
      console.log(`[${name}] Incomplete type, adding empty "Properties" field`);
 
      (definition as unknown as schema.RecordProperty).Properties = {};
    }
  }
}
 
/**
 * Drop Attributes specified with the different ResourceTypes that have
 * no type specified.
 */
function dropTypelessAttributes(spec: schema.Specification) {
  const resourceTypes = spec.ResourceTypes;
  Object.values(resourceTypes).forEach((resourceType) => {
    const attributes = resourceType.Attributes ?? {};
    Object.keys(attributes).forEach((attrKey) => {
      const attrVal = attributes[attrKey];
      if (Object.keys(attrVal).length === 0) {
        delete attributes[attrKey];
      }
    });
  });
}
 
export function merge(spec: any, fragment: any, jsonPath: string[]) {
  Iif (!fragment) { return; }
  for (const key of Object.keys(fragment)) {
    if (key in spec) {
      const specVal = spec[key];
      const fragVal = fragment[key];
      Iif (typeof specVal !== typeof fragVal) {
        // eslint-disable-next-line max-len
        throw new Error(`Attempted to merge ${JSON.stringify(fragVal)} into incompatible ${JSON.stringify(specVal)} at path ${jsonPath.join('/')}/${key}`);
      }
      Iif (typeof specVal !== 'object') {
        // eslint-disable-next-line max-len
        throw new Error(`Conflict when attempting to merge ${JSON.stringify(fragVal)} into ${JSON.stringify(specVal)} at path ${jsonPath.join('/')}/${key}`);
      }
      merge(specVal, fragVal, [...jsonPath, key]);
    } else {
      spec[key] = fragment[key];
    }
  }
}
 
export function patch(spec: any, fragment: any) {
  Iif (!fragment) { return; }
  if ('patch' in fragment) {
    // eslint-disable-next-line no-console
    console.log(`Applying patch: ${fragment.patch.description}`);
    fastJsonPatch.applyPatch(spec, fragment.patch.operations);
  } else {
    for (const key of Object.keys(fragment)) {
      patch(spec[key], fragment[key]);
    }
  }
}
 
/**
 * Modifies the provided specification so that ``ResourceTypes`` and ``PropertyTypes`` are listed in alphabetical order.
 *
 * @param spec an AWS CloudFormation Resource Specification document.
 *
 * @returns ``spec``, after having sorted the ``ResourceTypes`` and ``PropertyTypes`` sections alphabetically.
 */
export function normalize(spec: schema.Specification): schema.Specification {
  spec.ResourceTypes = normalizeSection(spec.ResourceTypes);
  Iif (spec.PropertyTypes) {
    spec.PropertyTypes = normalizeSection(spec.PropertyTypes);
  }
  return spec;
 
  function normalizeSection<T>(section: { [name: string]: T }): { [name: string]: T } {
    const result: { [name: string]: T } = {};
    for (const key of Object.keys(section).sort()) {
      result[key] = section[key];
    }
    return result;
  }
}