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  }