github.com/CycloneDX/sbom-utility@v0.16.0/cmd/patch_format.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 package cmd 19 20 import ( 21 "encoding/json" 22 "fmt" 23 "io" 24 "os" 25 "path/filepath" 26 27 "github.com/CycloneDX/sbom-utility/utils" 28 ) 29 30 const ( 31 // Patch operations 32 IETF_RFC6902_OP_ADD = "add" 33 IETF_RFC6902_OP_REMOVE = "remove" 34 IETF_RFC6902_OP_REPLACE = "replace" 35 IETF_RFC6902_OP_MOVE = "move" 36 IETF_RFC6902_OP_COPY = "copy" 37 IETF_RFC6902_OP_TEST = "test" 38 ) 39 40 type IETF6902Document struct { 41 filename string 42 absFilename string 43 rawBytes []byte 44 Records []IETF6902Record 45 } 46 47 // Example of all opcodes: 48 // 49 // { "op": "test", "path": "/a/b/c", "value": "foo" }, 50 // { "op": "remove", "path": "/a/b/c" }, 51 // { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }, 52 // { "op": "replace", "path": "/a/b/c", "value": 42 }, 53 // { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }, 54 // { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" } 55 type IETF6902Record struct { 56 Operation string `json:"op"` 57 Path string `json:"path"` 58 Value interface{} `json:"value,omitempty"` 59 From string `json:"from,omitempty"` 60 } 61 62 func (record *IETF6902Record) String() string { 63 buffer, _ := utils.EncodeAnyToDefaultIndentedJSONStr(record) 64 return buffer.String() 65 } 66 67 func NewIETFRFC6902PatchDocument(patchFilename string) (document *IETF6902Document) { 68 temp := IETF6902Document{ 69 filename: patchFilename, 70 } 71 return &temp 72 } 73 74 func (document *IETF6902Document) ReadRawBytes() (err error) { 75 getLogger().Enter() 76 defer getLogger().Exit() 77 78 // only read once 79 if document.rawBytes == nil { 80 // validate filename 81 if len(document.filename) == 0 { 82 return fmt.Errorf("schema: invalid filename: `%s`", document.filename) 83 } 84 85 // Conditionally append working directory if no abs. path detected 86 if len(document.filename) > 0 && !filepath.IsAbs(document.filename) { 87 document.absFilename = filepath.Join(utils.GlobalFlags.WorkingDir, document.filename) 88 } else { 89 document.absFilename = document.filename 90 } 91 92 // Open our jsonFile 93 var jsonFile *os.File 94 if jsonFile, err = os.Open(document.absFilename); err != nil { 95 return 96 } 97 98 // defer the closing of our jsonFile 99 defer jsonFile.Close() 100 101 // read our opened jsonFile as a byte array. 102 if document.rawBytes, err = io.ReadAll(jsonFile); err != nil { 103 return 104 } 105 106 getLogger().Tracef("read data from: `%s`", document.filename) 107 getLogger().Tracef("\n >> rawBytes[:100]=[%s]", document.rawBytes[:100]) 108 } 109 return 110 } 111 112 func (document *IETF6902Document) UnmarshalRecords() (err error) { 113 getLogger().Enter() 114 defer getLogger().Exit() 115 116 // Read in the bytes of the patch file 117 if err = document.ReadRawBytes(); err != nil { 118 return 119 } 120 121 // optimistically, prepare the receiving structure and unmarshal 122 if err = json.Unmarshal(document.rawBytes, &document.Records); err != nil { 123 getLogger().Warningf("unmarshal failed: %v", err) 124 return 125 } 126 127 return 128 }