github.com/CycloneDX/sbom-utility@v0.16.0/cmd/patch.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  /*
     3   * Licensed to the Apache Software Foundation (ASF) under one or more
     4   * contributor license agreements.  See the NOTICE file distributed with
     5   * this work for additional information regarding copyright ownership.
     6   * The ASF licenses this file to You under the Apache License, Version 2.0
     7   * (the "License"); you may not use this file except in compliance with
     8   * the License.  You may obtain a copy of the License at
     9   *
    10   *     http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing, software
    13   * distributed under the License is distributed on an "AS IS" BASIS,
    14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15   * See the License for the specific language governing permissions and
    16   * limitations under the License.
    17   */
    18  
    19  package cmd
    20  
    21  import (
    22  	"fmt"
    23  	"io"
    24  	"os"
    25  	"reflect"
    26  	"strconv"
    27  	"strings"
    28  
    29  	"github.com/CycloneDX/sbom-utility/schema"
    30  	"github.com/CycloneDX/sbom-utility/utils"
    31  	"github.com/spf13/cobra"
    32  )
    33  
    34  // flags (do not translate)
    35  const (
    36  	FLAG_PATCH_FILE = "patch-file"
    37  )
    38  
    39  // flag help (translate)
    40  const (
    41  	MSG_PATCH_FILE = "patch filename"
    42  )
    43  
    44  const (
    45  	ERR_PATCH_REPLACE_PATH_EXISTS = "invalid path. Path does not exist to replace value"
    46  )
    47  
    48  // The "-" character is used to index the end of the array (see [RFC6901])
    49  const (
    50  	RFC6901_END_OF_ARRAY = "-"
    51  )
    52  
    53  var PATCH_OUTPUT_SUPPORTED_FORMATS = MSG_SUPPORTED_OUTPUT_FORMATS_HELP +
    54  	strings.Join([]string{FORMAT_JSON}, ", ")
    55  
    56  // Command PreRunE helper function to test for patch file
    57  func preRunTestForPatchFile(args []string) error {
    58  	getLogger().Enter()
    59  	defer getLogger().Exit()
    60  	getLogger().Tracef("args: %v", args)
    61  
    62  	// Make sure the input filename is present and exists
    63  	patchFilename := utils.GlobalFlags.PatchFlags.PatchFile
    64  	if patchFilename == "" {
    65  		return getLogger().Errorf("Missing required argument(s): %s", FLAG_PATCH_FILE)
    66  	} else if _, err := os.Stat(patchFilename); err != nil {
    67  		return getLogger().Errorf("File not found: `%s`", patchFilename)
    68  	}
    69  	return nil
    70  }
    71  
    72  func NewCommandPatch() *cobra.Command {
    73  	var command = new(cobra.Command)
    74  	command.Use = CMD_USAGE_PATCH
    75  	command.Short = "Apply an IETF RFC 6902 patch file to a JSON BOM file"
    76  	command.Long = "Apply an IETF RFC 6902 patch file to a JSON BOM file"
    77  	command.RunE = patchCmdImpl
    78  	command.PreRunE = func(cmd *cobra.Command, args []string) (err error) {
    79  		// Test for required flags (parameters)
    80  		err = preRunTestForInputFile(args)
    81  		if err != nil {
    82  			return
    83  		}
    84  		err = preRunTestForPatchFile(args)
    85  		if err != nil {
    86  			return
    87  		}
    88  		return
    89  	}
    90  	initCommandPatchFlags(command)
    91  
    92  	return command
    93  }
    94  
    95  func initCommandPatchFlags(command *cobra.Command) (err error) {
    96  	getLogger().Enter()
    97  	defer getLogger().Exit()
    98  
    99  	// NOTE: Cobra commands that use the same variable with different "default" values (i.e., "txt", "json")
   100  	// will overwrite each other during initialization... we must use a unique variable for each command/conflict
   101  	command.PersistentFlags().StringVar(&utils.GlobalFlags.PatchFlags.OutputFormat, FLAG_OUTPUT_FORMAT, FORMAT_JSON,
   102  		MSG_FLAG_OUTPUT_FORMAT+PATCH_OUTPUT_SUPPORTED_FORMATS)
   103  	command.Flags().StringVarP(&utils.GlobalFlags.PatchFlags.PatchFile, FLAG_PATCH_FILE, "", "", MSG_PATCH_FILE)
   104  	err = command.MarkFlagRequired(FLAG_PATCH_FILE)
   105  	if err != nil {
   106  		err = getLogger().Errorf("unable to mark flag `%s` as required: %s", FLAG_PATCH_FILE, err)
   107  	}
   108  	return
   109  }
   110  
   111  func patchCmdImpl(cmd *cobra.Command, args []string) (err error) {
   112  	getLogger().Enter(args)
   113  	defer getLogger().Exit()
   114  
   115  	// Create output writer
   116  	outputFilename := utils.GlobalFlags.PersistentFlags.OutputFile
   117  	outputFile, writer, err := createOutputFile(outputFilename)
   118  	getLogger().Tracef("outputFile: `%v`; writer: `%v`", outputFilename, writer)
   119  
   120  	// Overcome Cobra limitation in variable reuse between diff. commands
   121  	// That is, as soon as ANY command sets a default value, it cannot be changed
   122  	utils.GlobalFlags.PersistentFlags.OutputFormat = utils.GlobalFlags.PatchFlags.OutputFormat
   123  
   124  	// use function closure to assure consistent error output based upon error type
   125  	defer func() {
   126  		// always close the output file
   127  		if outputFile != nil {
   128  			outputFile.Close()
   129  			getLogger().Infof("Closed output file: `%s`", outputFilename)
   130  		}
   131  	}()
   132  
   133  	if err == nil {
   134  		err = Patch(writer, utils.GlobalFlags.PersistentFlags, utils.GlobalFlags.PatchFlags)
   135  	}
   136  
   137  	return
   138  }
   139  
   140  // Assure all errors are logged
   141  func processPatchResults(err error) {
   142  	if err != nil {
   143  		// No special processing at this time
   144  		getLogger().Error(err)
   145  	}
   146  }
   147  
   148  // NOTE: resourceType has already been validated
   149  func Patch(writer io.Writer, persistentFlags utils.PersistentCommandFlags, patchFlags utils.PatchCommandFlags) (err error) {
   150  	getLogger().Enter()
   151  	defer getLogger().Exit()
   152  
   153  	// use function closure to assure consistent error output based upon error type
   154  	defer func() {
   155  		if err != nil {
   156  			processPatchResults(err)
   157  		}
   158  	}()
   159  
   160  	// Note: returns error if either file load or unmarshal to JSON map fails
   161  	var document *schema.BOM
   162  	if document, err = LoadInputBOMFileAndDetectSchema(); err != nil {
   163  		return
   164  	}
   165  
   166  	// At this time, fail SPDX format SBOMs as "unsupported" (for "any" format)
   167  	if !document.FormatInfo.IsCycloneDx() {
   168  		err = schema.NewUnsupportedFormatForCommandError(
   169  			document.FormatInfo.CanonicalName,
   170  			document.GetFilename(),
   171  			CMD_LICENSE, FORMAT_ANY)
   172  		return
   173  	}
   174  
   175  	// validate parameters
   176  	patchFile := utils.GlobalFlags.PatchFlags.PatchFile
   177  	if patchFile == "" {
   178  		err = fmt.Errorf("invalid patch file: %s", patchFile)
   179  		return
   180  	}
   181  
   182  	patchDocument := NewIETFRFC6902PatchDocument(patchFile)
   183  	if err = patchDocument.UnmarshalRecords(); err != nil {
   184  		return
   185  	}
   186  
   187  	if err = processPatchRecords(document, patchDocument); err != nil {
   188  		return
   189  	}
   190  
   191  	// After patch records are applied to the JSON map;
   192  	// update the corresponding "CdxBom" using the "unmarshal" wrapper.
   193  	// NOTE: If any JSON keys that are NOT part of the CycloneDX spec.
   194  	// have been added via a patch "add" operation, they will be removed
   195  	// during the unmarshal process.
   196  	if document.CdxBom, err = schema.UnMarshalDocument(document.JsonMap); err != nil {
   197  		return
   198  	}
   199  
   200  	// Output the "patched" version of the Input BOM
   201  	format := persistentFlags.OutputFormat
   202  	getLogger().Infof("Writing patched BOM (`%s` format)...", format)
   203  	switch format {
   204  	case FORMAT_JSON:
   205  		err = document.WriteAsEncodedJSONInt(writer, utils.GlobalFlags.PersistentFlags.GetOutputIndentInt())
   206  	default:
   207  		// Default to Text output for anything else (set as flag default)
   208  		getLogger().Warningf("Patch not supported for `%s` format; defaulting to `%s` format...",
   209  			format, FORMAT_JSON)
   210  		err = document.WriteAsEncodedJSONInt(writer, utils.GlobalFlags.PersistentFlags.GetOutputIndentInt())
   211  	}
   212  
   213  	return
   214  }
   215  
   216  func innerPatch(document *schema.BOM) (err error) {
   217  	// validate parameters
   218  	patchFile := utils.GlobalFlags.PatchFlags.PatchFile
   219  	if patchFile == "" {
   220  		err = fmt.Errorf("invalid patch file: %s", patchFile)
   221  		return
   222  	}
   223  
   224  	patchDocument := NewIETFRFC6902PatchDocument(patchFile)
   225  	if err = patchDocument.UnmarshalRecords(); err != nil {
   226  		return
   227  	}
   228  
   229  	if err = processPatchRecords(document, patchDocument); err != nil {
   230  		return
   231  	}
   232  	return
   233  }
   234  
   235  func processPatchRecords(bomDocument *schema.BOM, patchDocument *IETF6902Document) (err error) {
   236  	getLogger().Enter()
   237  	defer getLogger().Exit()
   238  
   239  	for _, record := range patchDocument.Records {
   240  		getLogger().Tracef("patch: %s\n", record.String())
   241  
   242  		// operation objects MUST have exactly one "path" member.
   243  		// That member's value is a string containing a JSON-Pointer value
   244  		// [RFC6901] that references a location within the target document
   245  		// (the "target location") where the operation is performed.
   246  		// NOTE: RFC 6901 indicates an "empty" path means a pointer to the
   247  		// entire document which effectively mean patch the entire document
   248  		// which does not make sense...
   249  		if record.Path == "" {
   250  			// TODO: make this a declared error type that can be tested
   251  			return fmt.Errorf("invalid IETF RFC 6902 patch operation. \"path\" is empty")
   252  		}
   253  
   254  		var keys []string
   255  		jsonMap := bomDocument.GetJSONMap()
   256  
   257  		if jsonMap == nil {
   258  			return fmt.Errorf("invalid json document (nil)")
   259  		}
   260  
   261  		if keys, err = parseMapKeysFromPath(record.Path); err != nil {
   262  			return
   263  		}
   264  
   265  		lengthKeys := len(keys)
   266  		if lengthKeys == 0 {
   267  			return fmt.Errorf("invalid document path (nil)")
   268  		}
   269  
   270  		switch record.Operation {
   271  		case IETF_RFC6902_OP_ADD:
   272  			if record.Value == nil {
   273  				// TODO: make this a declared error type that can be tested
   274  				return fmt.Errorf("invalid IETF RFC 6902 patch operation. \"value\" missing")
   275  			}
   276  			if err = addOrReplaceValue(jsonMap, keys, record.Value, false); err != nil {
   277  				return
   278  			}
   279  		case IETF_RFC6902_OP_REPLACE:
   280  			// NOTE: Replace logic is identical to "add" operation except that
   281  			// the target "key" MUST exist...
   282  			if record.Value == nil {
   283  				// TODO: make this a declared error type that can be tested
   284  				return fmt.Errorf("invalid IETF RFC 6902 patch operation. \"value\" missing")
   285  			}
   286  			if err = addOrReplaceValue(jsonMap, keys, record.Value, true); err != nil {
   287  				return
   288  			}
   289  		case IETF_RFC6902_OP_REMOVE:
   290  			if err = removeValue(jsonMap, keys, record.Value); err != nil {
   291  				return
   292  			}
   293  		case IETF_RFC6902_OP_TEST:
   294  			// NOTE: "test" operations do not change the input JSON.  They either
   295  			// will report (via INFO messages) "success" of a data match or return
   296  			// a (typed) error that indicates a non-match and terminates all
   297  			// patch record processing.
   298  			var equal bool
   299  			var actualValue interface{}
   300  			if equal, actualValue, err = testValue(jsonMap, keys, record.Value); err != nil {
   301  				return
   302  			}
   303  			// The RFC6902 spec. requires returning an "error" if the test values does not match
   304  			// the value found in the document...
   305  			if !equal {
   306  				err = NewIETFRFC6902TestError(record.String(), actualValue)
   307  				return
   308  			}
   309  			successMessage := fmt.Sprintf("%s. test record: %s, actual value: %v\n", MSG_IETF_RFC6902_OPERATION_SUCCESS, record.String(), actualValue)
   310  			getLogger().Info(successMessage)
   311  		case IETF_RFC6902_OP_MOVE:
   312  			fallthrough
   313  		case IETF_RFC6902_OP_COPY:
   314  			return NewUnsupportedError(record.Operation, "IETF RFC 6902 operation not currently supported")
   315  		default:
   316  			return NewUnsupportedError(record.Operation, "invalid IETF RFC 6902 operation")
   317  		}
   318  	}
   319  
   320  	return
   321  }
   322  
   323  func parseMapKeysFromPath(path string) (keys []string, err error) {
   324  	// first char SHOULD be a forward slash, if not error
   325  	if path == "" || path[0] != '/' {
   326  		err = fmt.Errorf("invalid path. Path must begin with forward slash")
   327  		return
   328  	}
   329  	// parse out paths ignoring leading forward slash character
   330  	keys = strings.Split(path[1:], "/")
   331  	return
   332  }
   333  
   334  func parseArrayIndex(indexPath string) (arrayIndex int, err error) {
   335  	// Check for RFC6901 end-of-array character
   336  	if indexPath == RFC6901_END_OF_ARRAY {
   337  		arrayIndex = -1
   338  		return
   339  	}
   340  	// otherwise, the path should be convertible to an integer
   341  	arrayIndex, err = strconv.Atoi(indexPath)
   342  	return
   343  }
   344  
   345  // func parseArrayIndexFromPath(path string) (arrayIndex int, err error) {
   346  // 	var keys []string
   347  // 	keys, err = parseMapKeysFromPath(path)
   348  // 	if err != nil {
   349  // 		return
   350  // 	}
   351  
   352  // 	lengthKeys := len(keys)
   353  // 	if lengthKeys <= 0 {
   354  // 		err = fmt.Errorf("invalid path. Path: %s", path)
   355  // 		return
   356  // 	}
   357  // 	return parseArrayIndex(keys[lengthKeys-1])
   358  // }
   359  
   360  // The "test" operation tests that a value at the target location is
   361  // equal to a specified value.
   362  //   - The operation object MUST contain a "value" member that conveys the
   363  //     value to be compared to the target location's value.
   364  //   - The target location MUST be equal to the "value" value for the
   365  //     operation to be considered successful.
   366  //
   367  // Here, "equal" means that the value at the target location and the
   368  // value conveyed by "value" are of the same JSON type, and that they
   369  // are considered equal by the following rules for that type:
   370  //
   371  //   - strings: are considered equal if they contain the same number of
   372  //     Unicode characters and their code points are byte-by-byte equal.
   373  //
   374  //   - numbers: are considered equal if their values are numerically
   375  //     equal.
   376  //
   377  //   - arrays: are considered equal if they contain the same number of
   378  //
   379  //     values, and if each value can be considered equal to the value at
   380  //     the corresponding position in the other array, using this list of
   381  //     type-specific rules.
   382  //
   383  //   - objects: are considered equal if they contain the same number of
   384  //     members, and if each member can be considered equal to a member in
   385  //     the other object, by comparing their keys (as strings) and their
   386  //     values (using this list of type-specific rules).
   387  //
   388  //   - literals (false, true, and null): are considered equal if they are
   389  //     the same.
   390  func testValue(parentMap map[string]interface{}, keys []string, value interface{}) (equal bool, actualValue interface{}, err error) {
   391  	var nextNodeKey string   // := keys[0]
   392  	var nextNode interface{} // := parentMap[nextNodeKey]
   393  	lengthKeys := len(keys)
   394  
   395  	switch lengthKeys {
   396  	case 0:
   397  		err = fmt.Errorf("invalid map key (nil)")
   398  		return
   399  	case 1: // special case of adding new key/value to document root
   400  		nextNode = parentMap
   401  	default: // adding keys/values along document path
   402  		nextNodeKey = keys[0]
   403  		nextNode = parentMap[nextNodeKey]
   404  	}
   405  
   406  	switch typedNode := nextNode.(type) {
   407  	case map[string]interface{}:
   408  		// If the resulting value is indeed another map type, we expect for a Json Map
   409  		// we preserve that pointer for the next iteration
   410  		if lengthKeys > 2 {
   411  			// if the next node is a map AND there is more than one path following it,
   412  			// it would mean we have not yet reached the final map or slice to add
   413  			// a value to
   414  			equal, actualValue, err = testValue(typedNode, keys[1:], value)
   415  			return
   416  		} else {
   417  			// if the next node is a map AND only 1 path remains after it,
   418  			// it would mean that last path is a new key to be added
   419  			// to the next node's map with the provided value
   420  			actualValue = typedNode[keys[0]]
   421  			equal, err = isValueEqual(value, actualValue)
   422  			if !equal || err != nil {
   423  				return
   424  			}
   425  		}
   426  	case []interface{}:
   427  		if lengthKeys != 2 {
   428  			// TODO: create a formal error type for this
   429  			err = fmt.Errorf("invalid path. IETF RFC 6901 does not permit paths after array indices")
   430  			return
   431  		}
   432  		var arrayIndex int
   433  		indexPath := keys[1]
   434  		arrayIndex, err = parseArrayIndex(indexPath)
   435  		if err != nil {
   436  			return
   437  		}
   438  		actualValue = typedNode[arrayIndex]
   439  		equal, err = isValueEqual(value, actualValue)
   440  		if !equal || err != nil {
   441  			return
   442  		}
   443  	default:
   444  		// Optimistically, assign the value and emit a warning of the unexpected JSON type
   445  		getLogger().Warningf("Invalid document node type: (%T)", nextNode)
   446  		return
   447  	}
   448  	return
   449  }
   450  
   451  func isValueEqual(value1 interface{}, value2 interface{}) (equal bool, err error) {
   452  	// We want to assure type match before actual value comparison
   453  	switch value1.(type) {
   454  	case bool:
   455  		if _, ok := value2.(bool); !ok {
   456  			err = fmt.Errorf("invalid type comparison. value1: %v (%T), value2: %v (%T)", value1, value1, value2, value2)
   457  			return
   458  		}
   459  		equal = (value1 == value2)
   460  	case float64:
   461  		if _, ok := value2.(float64); !ok {
   462  			err = fmt.Errorf("invalid type comparison. value1: %v (%T), value2: %v (%T)", value1, value1, value2, value2)
   463  			return
   464  		}
   465  		equal = (value1 == value2)
   466  	case string:
   467  		if _, ok := value2.(string); !ok {
   468  			err = fmt.Errorf("invalid type comparison. value1: %v (%T), value2: %v (%T)", value1, value1, value2, value2)
   469  			return
   470  		}
   471  		equal = (value1 == value2)
   472  		return
   473  	case []interface{}:
   474  		if _, ok := value2.([]interface{}); !ok {
   475  			err = fmt.Errorf("invalid type comparison. value1: %v (%T), value2: %v (%T)", value1, value1, value2, value2)
   476  			return
   477  		}
   478  		equal = reflect.DeepEqual(value1, value2)
   479  		return
   480  	case map[string]interface{}:
   481  		if _, ok := value2.(map[string]interface{}); !ok {
   482  			err = fmt.Errorf("invalid type comparison. value1: %v (%T), value2: %v (%T)", value1, value1, value2, value2)
   483  			return
   484  		}
   485  		equal = reflect.DeepEqual(value1, value2)
   486  		return
   487  	default:
   488  		err = fmt.Errorf("invalid type comparison. Unexpected type for value: %v (%T)", value1, value1)
   489  		return
   490  	}
   491  
   492  	return
   493  }
   494  
   495  // The "remove" operation removes the value at the target location.
   496  //
   497  // - The target location MUST exist for the operation to be successful.
   498  // - If removing an element from an array, any elements above the
   499  // specified index are shifted one position to the left.
   500  func removeValue(parentMap map[string]interface{}, keys []string, value interface{}) (err error) {
   501  	var nextNodeKey string   // := keys[0]
   502  	var nextNode interface{} // := parentMap[nextNodeKey]
   503  	lengthKeys := len(keys)
   504  
   505  	switch lengthKeys {
   506  	case 0:
   507  		return fmt.Errorf("invalid map key (nil)")
   508  	case 1: // special case of adding new key/value to document root
   509  		nextNode = parentMap
   510  	default: // adding keys/values along document path
   511  		nextNodeKey = keys[0]
   512  		nextNode = parentMap[nextNodeKey]
   513  	}
   514  
   515  	switch typedNode := nextNode.(type) {
   516  	case map[string]interface{}:
   517  		// If the resulting value is indeed another map type, we expect for a Json Map
   518  		// we preserve that pointer for the next iteration
   519  		if lengthKeys > 2 {
   520  			// if the next node is a map AND there is more than one path following it,
   521  			// it would mean we have not yet reached the final map or slice to add
   522  			// a value to
   523  			err = removeValue(typedNode, keys[1:], value)
   524  			return
   525  		} else {
   526  			// if the next node is a map AND only 1 path remains after it,
   527  			// it would mean that last path is a new key to be added
   528  			// to the next node's map with the provided value
   529  			delete(typedNode, keys[0])
   530  		}
   531  	case []interface{}:
   532  		if lengthKeys != 2 {
   533  			err = fmt.Errorf("invalid path. IETF RFC 6901 does not permit paths after array indices")
   534  			return
   535  		}
   536  
   537  		var arrayIndex int
   538  		indexPath := keys[1]
   539  		arrayIndex, err = parseArrayIndex(indexPath)
   540  		if err != nil {
   541  			return
   542  		}
   543  		var newSlice []interface{}
   544  		newSlice, err = removeValueFromSliceAtIndex(typedNode, arrayIndex)
   545  		parentMap[nextNodeKey] = newSlice
   546  	case float64:
   547  		// NOTE: It is a conscious decision of tbe encoding/json package to
   548  		// decode all Number values to float64
   549  		parentMap[nextNodeKey] = value
   550  	case bool:
   551  		parentMap[nextNodeKey] = value
   552  	default:
   553  		// Optimistically, assign the value and emit a warning of the unexpected JSON type
   554  		parentMap[nextNodeKey] = value
   555  		getLogger().Warningf("Invalid document node type: (%T)", nextNode)
   556  		return
   557  	}
   558  	return
   559  }
   560  
   561  // The "add" operation performs one of the following functions,
   562  // depending upon what the target location references:
   563  //
   564  //   - If the target location specifies an array index, a new value is
   565  //     inserted into the array at the specified index.
   566  //
   567  //   - If the target location specifies an object member that does not
   568  //     already exist, a new member is added to the object.
   569  //
   570  //   - If the target location specifies an object member that does exist,
   571  //     that member's value is replaced.
   572  //
   573  // The operation object MUST contain a "value" member whose content
   574  // specifies the value to be added.
   575  //
   576  // The "replace" operation replaces the value at the target location
   577  // with a new value.  The operation object MUST contain a "value" member
   578  // whose content specifies the replacement value.
   579  func addOrReplaceValue(parentMap map[string]interface{}, keys []string, value interface{}, replace bool) (err error) {
   580  	var nextNodeKey string   // := keys[0]
   581  	var nextNode interface{} // := parentMap[nextNodeKey]
   582  	lengthKeys := len(keys)
   583  
   584  	switch lengthKeys {
   585  	case 0:
   586  		return fmt.Errorf("invalid map key (nil)")
   587  	case 1: // special case of adding new key/value to document root
   588  		nextNode = parentMap
   589  	default: // adding keys/values along document path
   590  		nextNodeKey = keys[0]
   591  		nextNode = parentMap[nextNodeKey]
   592  	}
   593  
   594  	switch typedNode := nextNode.(type) {
   595  	case map[string]interface{}:
   596  		// If the resulting value is indeed another map type, we expect for a Json Map
   597  		// we preserve that pointer for the next iteration
   598  		if lengthKeys > 2 {
   599  			// if the next node is a map AND there is more than one path following it,
   600  			// it would mean we have not yet reached the final map or slice to add
   601  			// a value to
   602  			err = addOrReplaceValue(typedNode, keys[1:], value, replace)
   603  			return
   604  		} else {
   605  			// if the next node is a map AND only 1 path remains after it,
   606  			// it would mean that last path is a new key to be added
   607  			// to the next node's map with the provided value
   608  			currentKey := keys[lengthKeys-1]
   609  			if _, exists := typedNode[currentKey]; !exists && replace {
   610  				err = fmt.Errorf(ERR_PATCH_REPLACE_PATH_EXISTS)
   611  				return
   612  			}
   613  			typedNode[currentKey] = value
   614  		}
   615  	case []interface{}:
   616  		if lengthKeys != 2 {
   617  			err = fmt.Errorf("invalid path. IETF RFC 6901 does not permit paths after array indices")
   618  			return
   619  		}
   620  
   621  		var arrayIndex int
   622  		indexPath := keys[1]
   623  		arrayIndex, err = parseArrayIndex(indexPath)
   624  		if err != nil {
   625  			return
   626  		}
   627  		newSlice := insertValueIntoSlice(nextNode.([]interface{}), arrayIndex, value)
   628  		parentMap[nextNodeKey] = newSlice
   629  	case float64:
   630  		// NOTE: It is a conscious decision of tbe encoding/json package to
   631  		// decode all Number values to float64
   632  		parentMap[nextNodeKey] = value
   633  	case bool:
   634  		parentMap[nextNodeKey] = value
   635  	default:
   636  		// Optimistically, assign the value and emit a warning of the unexpected JSON type
   637  		parentMap[nextNodeKey] = value
   638  		getLogger().Warningf("Invalid document node type: (%T)", nextNode)
   639  		return
   640  	}
   641  	return
   642  }
   643  
   644  func insertValueIntoSlice(slice []interface{}, index int, value interface{}) []interface{} {
   645  	if index == -1 || index >= len(slice) {
   646  		return append(slice, value)
   647  	}
   648  	slice = append(slice[:index+1], slice[index:]...)
   649  	slice[index] = value
   650  	return slice
   651  }
   652  
   653  func removeValueFromSliceAtIndex(slice []interface{}, index int) (newSlice []interface{}, err error) {
   654  	if index < 0 || index >= len(slice) {
   655  		err = fmt.Errorf("remove array element failed. Index (%v) out of range for array (length: %v). ", index, len(slice))
   656  		return
   657  	}
   658  	// unpack elements from the slice subsets (i.e. using ... notation)
   659  	return append(slice[:index], slice[index+1:]...), nil
   660  }