import _ from "lodash" ; import { Delta , DiffPatcher } from "jsondiffpatch" ; import { ramlToolLogger } from "../common/logger" ; import { NodeChanges } from "./changes/nodeChanges" ; import * as AmfGraphTypes from "./amfGraphTypes" ; import * as AmfGraphDeltaTypes from "./amfGraphDeltaTypes" ; /**
 * Delta types that can be identified by jsondiffpath
 */ export enum DeltaType { ADDED , REMOVED , MODIFIED , TEXT_DIFF , MOVED } /**
 * Since context is also treated as a node and type is required for node, we will use a custom type for context
 **/ export const CONTEXT_TYPE = [ "context" ] ; param baseGraph param newGraph returns export function findGraphChanges ( baseGraph , newGraph ) { try { validateAmfGraph ( baseGraph ) ; } catch ( error ) { error . message = `Error validating base AMF graph: ${ error . message }` ; throw error ; } try { validateAmfGraph ( newGraph ) ; } catch ( error ) { error . message = `Error validating new AMF graph: ${ error . message }` ; throw error ; } const jsonDiff = new DiffPatcher ( { objectHash : ( obj ) : string => obj [ AmfGraphTypes . KEY_NODE_ID ] , arrays : { detectMove : true , includeValueOnMove : true , } , textDiff : { minLength : 6000 , } , } ) ; jsonDiff . processor . pipes . diff . before ( "collectChildren" , addNodeInfo ) ; ramlToolLogger . debug ( `Added plugin to include node ID in the node delta: ${ jsonDiff . processor . pipes . diff . list ( ) }` ) ; /**
     * Refer to jsondiffpath documentation for the format of the diffs that are returned
     * https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md
     */ const delta = jsonDiff . diff ( baseGraph , newGraph ) ; return parseDelta ( delta ) ; } param flattenedJson function validateAmfGraph ( flattenedJson ) { if ( ! flattenedJson ) { throw new Error ( `Invalid object` ) ; } const graph = flattenedJson [ AmfGraphTypes . KEY_GRAPH ] ; if ( graph == null ) { throw new Error ( `${ AmfGraphTypes . KEY_GRAPH } property is missing` ) ; } if ( graph . length == 0 ) { throw new Error ( `${ AmfGraphTypes . KEY_GRAPH } property must be an array of json nodes` ) ; } } param delta returns function parseDelta ( delta ) { const nodeChanges = [ ] ; if ( _ . isEmpty ( delta ) ) { ramlToolLogger . info ( "No delta reported by jsondiffpatch" ) ; return nodeChanges ; } ramlToolLogger . debug ( "Parsing delta reported by jsondiffpatch" ) ; const graphDelta = delta [ AmfGraphTypes . KEY_GRAPH ] ; if ( graphDelta != null ) { ramlToolLogger . debug ( "Parsing delta of graph nodes" ) ; nodeChanges . push ( ... parseGraphDelta ( graphDelta ) ) ; } const contextDelta = delta [ AmfGraphTypes . KEY_CONTEXT ] ; if ( contextDelta != null ) { ramlToolLogger . debug ( "Parsing delta of context node" ) ; const contextNodeChanges = parseNodePropDelta ( AmfGraphTypes . KEY_CONTEXT , CONTEXT_TYPE , contextDelta ) ; if ( contextNodeChanges != null ) { nodeChanges . push ( contextNodeChanges ) ; } } return nodeChanges ; } param graphDelta returns function parseGraphDelta ( graphDelta ) { const graphChanges = [ ] ; const keys = Object . keys ( graphDelta ) . filter ( ( key ) => key !== AmfGraphDeltaTypes . ARRAY_KEY ) ; keys . forEach ( ( key ) => { const nodeDelta = graphDelta [ key ] ; let nodeChanges ; if ( _ . isArray ( nodeDelta ) ) { nodeChanges = parseNodeDelta ( nodeDelta , getDeltaType ( nodeDelta ) ) ; } else if ( _ . isObject ( nodeDelta ) ) { nodeChanges = parseNodePropDelta ( nodeDelta [ AmfGraphTypes . KEY_NODE_ID ] , nodeDelta [ AmfGraphTypes . KEY_NODE_TYPE ] , nodeDelta ) ; } if ( nodeChanges != null ) { graphChanges . push ( nodeChanges ) ; } } ) ; return graphChanges ; } param nodeType function isNodeTypeDefined ( nodeType ) { return nodeType ?. length > 0 ; } param nodeId param nodeType param nodeDelta returns function parseNodePropDelta ( nodeId , nodeType , nodeDelta ) { if ( ! isNodeTypeDefined ( nodeType ) ) { ramlToolLogger . debug ( `Ignoring changes to the node with no type: ${ nodeId }` ) ; return undefined ; } let nodeChanges = new NodeChanges ( nodeId , nodeType ) ; const keys = Object . keys ( nodeDelta ) . filter ( ( key ) => key !== AmfGraphTypes . KEY_NODE_ID && key !== AmfGraphTypes . KEY_NODE_TYPE ) ; keys . forEach ( ( key ) => { const value = nodeDelta [ key ] ; if ( _ . isArray ( value ) ) { parsePropertyDelta ( key , value as AmfGraphDeltaTypes . PropertyDelta < unknown > , getDeltaType ( value ) , nodeChanges ) ; } else if ( _ . isObject ( value ) ) { if ( value [ AmfGraphDeltaTypes . ARRAY_KEY ] === AmfGraphDeltaTypes . ARRAY_VALUE ) { parseArrayDelta ( key , value as AmfGraphDeltaTypes . ArrayDelta < unknown > , nodeChanges ) ; } else if ( value [ AmfGraphTypes . KEY_NODE_ID ] ) { parseReferenceNodeDelta ( key , value as AmfGraphDeltaTypes . ReferenceNodeDelta , nodeChanges ) ; } } } ) ; if ( _ . isEmpty ( nodeChanges . added ) && _ . isEmpty ( nodeChanges . removed ) ) { nodeChanges = undefined ; } return nodeChanges ; } param delta return export function getDeltaType < T > ( delta ) { const deltaLength = delta . length ; if ( deltaLength === 1 ) { return DeltaType . ADDED ; } if ( deltaLength === 2 ) { return DeltaType . MODIFIED ; } if ( deltaLength === 3 && delta [ 2 ] === 0 ) { return DeltaType . REMOVED ; } if ( deltaLength === 3 && delta [ 2 ] === 2 ) { return DeltaType . TEXT_DIFF ; } if ( deltaLength === 3 && delta [ 2 ] === 3 ) { return DeltaType . MOVED ; } throw new Error ( `Invalid delta structure found: ${ JSON . stringify ( delta , null , 2 ) }` ) ; } param delta param deltaType returns export function parseNodeDelta ( delta , deltaType ) { const node = delta [ 0 ] ; const id = node [ AmfGraphTypes . KEY_NODE_ID ] ; const type = node [ AmfGraphTypes . KEY_NODE_TYPE ] ; if ( ! isNodeTypeDefined ( type ) ) { ramlToolLogger . debug ( `Ignoring changes to the node with no type: ${ id }` ) ; return undefined ; } ramlToolLogger . debug ( `Parsing delta of node: ${ id }, delta type: ${ DeltaType [ deltaType ] }` ) ; let nodeChanges ; const nodeProps = _ . omit ( node , AmfGraphTypes . KEY_NODE_ID , AmfGraphTypes . KEY_NODE_TYPE ) ; switch ( deltaType ) { case DeltaType . ADDED : nodeChanges = new NodeChanges ( id , type ) ; nodeChanges . added = nodeProps ; break ; case DeltaType . REMOVED : nodeChanges = new NodeChanges ( id , type ) ; nodeChanges . removed = nodeProps ; break ; case DeltaType . MOVED : ramlToolLogger . debug ( `Ignoring the move of node: ${ id }` ) ; break ; default : /**
             *  Note: MODIFIED, TEXT_DIFF are not valid for node as delta is by node id
             */ throw new Error ( `Invalid delta type for node: ${ DeltaType [ deltaType ] }, node: ${ id }` ) ; } return nodeChanges ; } param nodeProperty param delta param deltaType param nodeChanges export function parsePropertyDelta ( nodeProperty , delta , deltaType , nodeChanges ) { ramlToolLogger . debug ( `Adding delta of node: ${ nodeChanges . id }, property: ${ nodeProperty }, delta type: ${ DeltaType [ deltaType ] }` ) ; switch ( deltaType ) { case DeltaType . ADDED : nodeChanges . added [ nodeProperty ] = delta [ 0 ] ; break ; case DeltaType . MODIFIED : nodeChanges . removed [ nodeProperty ] = delta [ 0 ] ; nodeChanges . added [ nodeProperty ] = delta [ 1 ] ; break ; case DeltaType . REMOVED : nodeChanges . removed [ nodeProperty ] = delta [ 0 ] ; break ; case DeltaType . TEXT_DIFF : break ; default : throw new Error ( `Invalid delta type for node property: ${ DeltaType [ deltaType ] },  node: ${ nodeChanges . id }, property: ${ nodeProperty }` ) ; } } param nodeProperty param delta param nodeChanges export function parseArrayDelta ( nodeProperty , delta , nodeChanges ) { const indexKeys = Object . keys ( delta ) . filter ( ( key ) => key !== AmfGraphDeltaTypes . ARRAY_KEY ) ; indexKeys . forEach ( ( k ) => { const arrValue = delta [ k ] ; const deltaType = getDeltaType ( arrValue ) ; ramlToolLogger . debug ( `Adding delta of node: ${ nodeChanges . id }, array property: ${ nodeProperty }, array key: ${ k }, delta type: ${ DeltaType [ deltaType ] }` ) ; addArrayDelta ( nodeProperty , arrValue , deltaType , nodeChanges ) ; } ) ; } param key param delta param deltaType param nodeChanges export function addArrayDelta ( key , delta , deltaType , nodeChanges ) { switch ( deltaType ) { case DeltaType . ADDED : if ( nodeChanges . added [ key ] == null ) { nodeChanges . added [ key ] = [ ] ; } ( nodeChanges . added [ key ] as Array < unknown > ) . push ( delta [ 0 ] ) ; break ; case DeltaType . REMOVED : if ( nodeChanges . removed [ key ] == null ) { nodeChanges . removed [ key ] = [ ] ; } ( nodeChanges . removed [ key ] as Array < unknown > ) . push ( delta [ 0 ] ) ; break ; case DeltaType . MOVED : ramlToolLogger . debug ( `Ignoring the moves of node: ${ nodeChanges . id }, property: ${ key }` ) ; break ; default : throw new Error ( `Invalid delta type for node array property value: ${ DeltaType [ deltaType ] },  node: ${ nodeChanges . id }, property: ${ key }` ) ; } } param nodeProperty param delta param nodeChanges function parseReferenceNodeDelta ( nodeProperty , delta , nodeChanges ) { const deltaValues = delta [ AmfGraphTypes . KEY_NODE_ID ] ; const deltaType = getDeltaType ( deltaValues ) ; ramlToolLogger . debug ( `Adding delta of node: ${ nodeChanges . id }, reference property: ${ nodeProperty }, delta type: ${ DeltaType [ deltaType ] }` ) ; switch ( deltaType ) { case DeltaType . MODIFIED : nodeChanges . removed [ nodeProperty ] = { [ AmfGraphTypes . KEY_NODE_ID ] : deltaValues [ 0 ] , } ; nodeChanges . added [ nodeProperty ] = { [ AmfGraphTypes . KEY_NODE_ID ] : deltaValues [ 1 ] , } ; break ; case DeltaType . TEXT_DIFF : break ; default : throw new Error ( `Invalid delta type for node reference property: ${ DeltaType [ deltaType ] },  node: ${ nodeChanges . id }, property: ${ nodeProperty }` ) ; } } /**
 * jsondiffpatch plugin to add node ID and type to the node delta
 */ const addNodeInfo = function ( context ) : void { if ( context . leftType === "object" && typeof context . left [ AmfGraphTypes . KEY_NODE_ID ] !== "undefined" ) { const changed = ! ! _ . find ( context . children , ( childContext ) => childContext . result ) ; if ( changed ) { context . setResult ( { id type [ AmfGraphTypes . KEY_NODE_ID ] : context . right [ AmfGraphTypes . KEY_NODE_ID ] , [ AmfGraphTypes . KEY_NODE_TYPE ] : context . right [ AmfGraphTypes . KEY_NODE_TYPE ] , } ) ; } } } ; addNodeInfo . filterName = "addNodeInfo" ;