github.com/CycloneDX/sbom-utility@v0.16.0/schema/cyclonedx_marshal.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 schema 20 21 import ( 22 "bytes" 23 "encoding/json" 24 "reflect" 25 ) 26 27 func IsInterfaceASlice(testValue interface{}) bool { 28 if testValue != nil { 29 if reflect.TypeOf(testValue).Kind() == reflect.Slice { 30 return true 31 } 32 } 33 return false 34 } 35 36 // -------------------------------------------------------------------------------- 37 // Custom marshallers 38 // -------------------------------------------------------------------------------- 39 // Objective: 40 // - Recreate a representation of the struct, but only include values in map 41 // that are not empty. Custom marshallers are needed as Golang does not 42 // check if child structs are empty or not. This is because they themselves 43 // are complex types that do not have a a single empty value (e.g., ["", 0,], etc.). 44 // Note: 45 // - Custom marshallers do NOT take into account validity of struct fields 46 // according to schema constraints (i.e., "OneOf", "AnyOf"). This means 47 // all struct fields are marshalled regardless of such constraints. 48 // -------------------------------------------------------------------------------- 49 50 var BYTE_ENCODED_ZERO_STRUCT = []byte("{}") 51 var ENCODED_EMPTY_SLICE_OF_STRUCT = []byte("[{}]") 52 53 // -------------------------- 54 // CDXLicenseChoice structs 55 // -------------------------- 56 57 func (value *CDXLicenseChoice) MarshalJSON() (marshalled []byte, err error) { 58 temp := map[string]interface{}{} 59 if value.Expression != "" { 60 temp["expression"] = value.Expression 61 } 62 63 if !reflect.ValueOf(value.License).IsZero() { 64 temp["license"] = value.License 65 } 66 67 return json.Marshal(temp) 68 } 69 70 // recreate a representation of the struct, but only include values in map that are not empty 71 func (value *CDXLicense) MarshalJSON() (bytes []byte, err error) { 72 temp := map[string]interface{}{} 73 if value.Id != "" { 74 temp["id"] = value.Id 75 } 76 77 if value.Name != "" { 78 temp["name"] = value.Name 79 } 80 81 if value.Url != "" { 82 temp["url"] = value.Url 83 } 84 85 // CDXAttachment 86 if !reflect.ValueOf(value.Text).IsZero() { 87 temp["text"] = value.Text 88 } 89 90 // v1.5 properties follow: 91 if value.BOMRef != nil && *value.BOMRef != "" { 92 temp["bom-ref"] = value.BOMRef 93 } 94 95 if value.Properties != nil && len(*value.Properties) > 0 { 96 temp["properties"] = value.Properties 97 } 98 99 if value.Licensing != nil { 100 temp["licensing"] = value.Licensing 101 } 102 103 return json.Marshal(temp) 104 } 105 106 // recreate a representation of the struct, but only include values in map that are not empty 107 func (value *CDXAttachment) MarshalJSON() ([]byte, error) { 108 temp := map[string]interface{}{} 109 if value.ContentType != "" { 110 temp["contentType"] = value.ContentType 111 } 112 113 if value.Encoding != "" { 114 temp["encoding"] = value.Encoding 115 } 116 117 if value.Content != "" { 118 temp["content"] = value.Content 119 } 120 // reuse built-in json encoder, which accepts a map primitive 121 return json.Marshal(temp) 122 } 123 124 // -------------------------- 125 // CDXVulnerability structs 126 // -------------------------- 127 128 // recreate a representation of the struct, but only include values in map that are not empty 129 func (value *CDXVulnerability) MarshalJSON() ([]byte, error) { 130 var testEmpty []byte 131 temp := map[string]interface{}{} 132 133 if value.BOMRef != nil && *value.BOMRef != "" { 134 temp["bom-ref"] = value.BOMRef 135 } 136 137 if value.Id != "" { 138 temp["id"] = value.Id 139 } 140 141 if value.Description != "" { 142 temp["description"] = value.Description 143 } 144 145 if value.Detail != "" { 146 temp["detail"] = value.Detail 147 } 148 149 if value.Recommendation != "" { 150 temp["recommendation"] = value.Recommendation 151 } 152 153 if value.Created != "" { 154 temp["created"] = value.Created 155 } 156 157 if value.Published != "" { 158 temp["published"] = value.Published 159 } 160 161 if value.Updated != "" { 162 temp["updated"] = value.Updated 163 } 164 165 // CDXVulnerabilitySource 166 if value.Source != nil { 167 testEmpty, _ = json.Marshal(value.Source) 168 if !bytes.Equal(testEmpty, BYTE_ENCODED_ZERO_STRUCT) { 169 temp["source"] = value.Source 170 } 171 } 172 173 // CDXCredit (anon. type) 174 if value.Credits != nil { 175 testEmpty, _ = json.Marshal(value.Credits) 176 if !bytes.Equal(testEmpty, BYTE_ENCODED_ZERO_STRUCT) { 177 temp["credits"] = value.Credits 178 } 179 } 180 181 // CDXAnalysis (anon. type) 182 if value.Analysis != nil { 183 testEmpty, _ = json.Marshal(value.Analysis) 184 if !bytes.Equal(testEmpty, BYTE_ENCODED_ZERO_STRUCT) { 185 temp["analysis"] = value.Analysis 186 } 187 } 188 189 // CDXAffects 190 if value.Affects != nil && len(*value.Affects) > 0 { 191 testEmpty, _ = json.Marshal(value.Affects) 192 if !bytes.Equal(testEmpty, ENCODED_EMPTY_SLICE_OF_STRUCT) { 193 temp["affects"] = value.Affects 194 } 195 } 196 197 if value.References != nil && len(*value.References) > 0 { 198 temp["references"] = value.References 199 } 200 201 if value.Ratings != nil && len(*value.Ratings) > 0 { 202 temp["ratings"] = value.Ratings 203 } 204 205 if value.Advisories != nil && len(*value.Advisories) > 0 { 206 temp["advisories"] = value.Advisories 207 } 208 209 if value.Cwes != nil && len(*value.Cwes) > 0 { 210 temp["cwes"] = value.Cwes 211 } 212 213 // v1.5 allows tools to be either an array of (legacy) tool object or a new tool object 214 // TODO: author test for legacy (array) object vs. new tool object 215 if IsInterfaceASlice(value.Tools) { 216 arrayTools, ok := value.Tools.([]CDXLegacyCreationTool) 217 if ok && len(arrayTools) > 0 { 218 temp["tools"] = arrayTools 219 } 220 } else { 221 tools, ok := value.Tools.(CDXCreationTools) 222 if ok { 223 temp["tools"] = tools 224 } 225 } 226 227 if value.Properties != nil && len(*value.Properties) > 0 { 228 temp["properties"] = value.Properties 229 } 230 231 // v1.5 properties follow 232 if value.Rejected != "" { 233 temp["rejected"] = value.Rejected 234 } 235 236 // reuse built-in json encoder, which accepts a map primitive 237 return json.Marshal(temp) 238 } 239 240 func (value *CDXVulnerabilityReference) MarshalJSON() ([]byte, error) { 241 temp := map[string]interface{}{} 242 if len(value.Id) > 0 { 243 temp["id"] = value.Id 244 } 245 if value.Source != nil && *value.Source != (CDXVulnerabilitySource{}) { 246 temp["source"] = value.Source 247 } 248 // reuse built-in json encoder, which accepts a map primitive 249 return json.Marshal(temp) 250 } 251 252 // type CDXVulnerabilitySource struct { 253 // Url string `json:"url,omitempty"` // v1.4 254 // Name string `json:"name,omitempty"` // v1.4 255 // } 256 func (value *CDXVulnerabilitySource) MarshalJSON() ([]byte, error) { 257 temp := map[string]interface{}{} 258 if len(value.Url) > 0 { 259 temp["url"] = value.Url 260 } 261 if len(value.Name) > 0 { 262 temp["name"] = value.Name 263 } 264 // reuse built-in json encoder, which accepts a map primitive 265 return json.Marshal(temp) 266 } 267 268 func (value *CDXCredit) MarshalJSON() ([]byte, error) { 269 temp := map[string]interface{}{} 270 if value.Individuals != nil && len(*value.Individuals) > 0 { 271 temp["individuals"] = value.Individuals 272 } 273 if value.Organizations != nil && len(*value.Organizations) > 0 { 274 temp["organizations"] = value.Organizations 275 } 276 if len(temp) == 0 { 277 return BYTE_ENCODED_ZERO_STRUCT, nil 278 } 279 // reuse built-in json encoder, which accepts a map primitive 280 return json.Marshal(temp) 281 } 282 283 // type CDXAffect struct { 284 // Versions *[]CDXVersionRange `json:"versions,omitempty"` // v1.4: anon. type 285 // Ref *CDXRefLinkType `json:"ref,omitempty"` // v1.5: added 286 // } 287 func (value *CDXAffect) MarshalJSON() ([]byte, error) { 288 temp := map[string]interface{}{} 289 if value.Versions != nil && len(*value.Versions) > 0 { 290 temp["versions"] = value.Versions 291 } 292 if value.Ref != nil && *value.Ref != "" { 293 temp["ref"] = value.Ref 294 } 295 if len(temp) == 0 { 296 return BYTE_ENCODED_ZERO_STRUCT, nil 297 } 298 // reuse built-in json encoder, which accepts a map primitive 299 return json.Marshal(temp) 300 } 301 302 // type CDXOrganizationalEntity struct { 303 // Name string `json:"name,omitempty"` 304 // Url []string `json:"url,omitempty"` 305 // Contact *[]CDXOrganizationalContact `json:"contact,omitempty"` 306 // BOMRef *CDXRefType `json:"bom-ref,omitempty"` // v1.5 added 307 // } 308 func (value *CDXOrganizationalEntity) MarshalJSON() ([]byte, error) { 309 temp := map[string]interface{}{} 310 if value.Name != "" { 311 temp["name"] = value.Name 312 } 313 if len(value.Url) > 0 { 314 temp["url"] = value.Url 315 } 316 if value.Contact != nil && len(*value.Contact) > 0 { 317 temp["contact"] = value.Contact 318 } 319 if value.BOMRef != nil && *value.BOMRef != "" { 320 temp["bom-ref"] = value.BOMRef 321 } 322 if len(temp) == 0 { 323 return BYTE_ENCODED_ZERO_STRUCT, nil 324 } 325 // reuse built-in json encoder, which accepts a map primitive 326 return json.Marshal(temp) 327 } 328 329 // type CDXOrganizationalContact struct { 330 // Name string `json:"name,omitempty"` 331 // Email string `json:"email,omitempty"` 332 // Phone string `json:"phone,omitempty"` 333 // BOMRef *CDXRefType `json:"bom-ref,omitempty"` // v1.5 added 334 // } 335 func (value *CDXOrganizationalContact) MarshalJSON() ([]byte, error) { 336 temp := map[string]interface{}{} 337 if value.Name != "" { 338 temp["name"] = value.Name 339 } 340 if value.Email != "" { 341 temp["email"] = value.Email 342 } 343 if value.Phone != "" { 344 temp["phone"] = value.Phone 345 } 346 if value.BOMRef != nil && *value.BOMRef != "" { 347 temp["bom-ref"] = value.BOMRef 348 } 349 if len(temp) == 0 { 350 return BYTE_ENCODED_ZERO_STRUCT, nil 351 } 352 // reuse built-in json encoder, which accepts a map primitive 353 return json.Marshal(temp) 354 }