coverage/coverage.go (308 lines of code) (raw):
package coverage
import (
"fmt"
"strconv"
"github.com/sirupsen/logrus"
)
type Model struct {
Bool *map[string]bool `json:"Bool,omitempty"` // key is the Enum value, value is coverage status
BoolCoveredCount int `json:"BoolCoveredCount,omitempty"`
CoveredCount int `json:"CoveredCount,omitempty"`
Discriminator *string `json:"Discriminator,omitempty"`
Enum *map[string]bool `json:"Enum,omitempty"` // key is the Enum value, value is coverage status
EnumCoveredCount int `json:"EnumCoveredCount,omitempty"`
EnumTotalCount int `json:"EnumTotalCount,omitempty"`
Format *string `json:"Format,omitempty"`
HasAdditionalProperties bool `json:"HasAdditionalProperties,omitempty"`
Identifier string `json:"Identifier,omitempty"` // e.g., #.properties.accessPolicies[].permissions.certificates
IsAnyCovered bool `json:"IsAnyCovered"`
IsFullyCovered bool `json:"IsFullyCovered,omitempty"`
IsReadOnly bool `json:"IsReadOnly,omitempty"`
IsRequired bool `json:"IsRequired,omitempty"`
IsRoot bool `json:"IsRoot,omitempty"`
IsSecret bool `json:"IsSecret,omitempty"` // related to x-ms-secret
Item *Model `json:"Item,omitempty"`
ModelName string `json:"ModelName,omitempty"`
Properties *map[string]*Model `json:"Properties,omitempty"`
RootCoveredCount int `json:"RootCoveredCount,omitempty"` // only for root model, covered count plus all variant count if any
RootTotalCount int `json:"RootTotalCount,omitempty"` // only for root model, total count plus all variant count if any
SourceFile string `json:"SourceFile,omitempty"`
TotalCount int `json:"TotalCount,omitempty"`
Type *string `json:"Type,omitempty"`
Variants *map[string]*Model `json:"Variants,omitempty"` // variant model name is used as key, in case x-ms-discriminator-value is not available
VariantType *string `json:"VariantType,omitempty"` // the x-ms-discriminator-value of the variant model if exists, otherwise model name
}
// CredScan scans the input payload (root) and extract the secret field and value in the secrets map.
func (m *Model) CredScan(root interface{}, secrets map[string]string) {
if root == nil || m == nil || m.IsReadOnly {
return
}
// https://pkg.go.dev/encoding/json#Unmarshal
switch value := root.(type) {
case string:
case bool:
case float64:
case []interface{}:
if m.Item == nil {
logrus.Errorf("unexpected array in %s", m.Identifier)
}
for _, item := range value {
m.Item.CredScan(item, secrets)
}
case map[string]interface{}:
isMatchProperty := true
if m.Discriminator != nil && m.Variants != nil {
Loop:
for k, v := range value {
if k == *m.Discriminator {
if m.ModelName == v.(string) {
break
}
if m.VariantType != nil && *m.VariantType == v.(string) {
break
}
if variant, ok := (*m.Variants)[v.(string)]; ok {
isMatchProperty = false
variant.CredScan(value, secrets)
break
}
for _, variant := range *m.Variants {
if variant.VariantType != nil && *variant.VariantType == v.(string) {
isMatchProperty = false
variant.CredScan(value, secrets)
break Loop
}
}
logrus.Errorf("unexpected variant %s in %s", v.(string), m.Identifier)
}
}
}
if isMatchProperty {
for k, v := range value {
if m.Properties == nil {
// some objects has no properties defined
// https://github.com/Azure/azure-rest-api-specs/blob/3519c80fe510a268f6e59a29ccac8a53fdec15b6/specification/monitor/resource-manager/Microsoft.Insights/stable/2023-03-11/dataCollectionRules_API.json#L724
logrus.Warnf("unexpected key %s in %s", k, m.Identifier)
continue
}
if _, ok := (*m.Properties)[k]; !ok {
if !m.HasAdditionalProperties && m.Discriminator == nil {
logrus.Errorf("unexpected key %s in %s", k, m.Identifier)
continue
}
}
if (*m.Properties)[k].IsSecret {
secrets[fmt.Sprintf("%v.%v", m.Identifier, k)] = fmt.Sprintf("%v", v)
}
(*m.Properties)[k].CredScan(v, secrets)
}
}
case nil:
default:
logrus.Errorf("unexpect type %T for json unmarshaled value", value)
}
}
func (m *Model) MarkCovered(root interface{}) {
if root == nil || m == nil || m.IsReadOnly {
return
}
m.IsAnyCovered = true
// https://pkg.go.dev/encoding/json#Unmarshal
switch value := root.(type) {
case string:
if m.Enum != nil {
strValue := fmt.Sprintf("%v", value)
if _, ok := (*m.Enum)[strValue]; !ok {
logrus.Warningf("unexpected enum %s in %s", value, m.Identifier)
}
(*m.Enum)[strValue] = true
}
case bool:
if m.Bool == nil {
logrus.Errorf("unexpected bool %v in %v", value, m.Identifier)
}
(*m.Bool)[strconv.FormatBool(value)] = true
case float64:
case []interface{}:
if m.Item == nil {
logrus.Errorf("unexpected array in %s", m.Identifier)
}
for _, item := range value {
m.Item.MarkCovered(item)
}
case map[string]interface{}:
// decide to match property or variant
isMatchProperty := true
if m.Discriminator != nil && m.Variants != nil {
Loop:
for k, v := range value {
if k == *m.Discriminator {
// if model name or variant type is matched, then we match the property
if m.ModelName == v.(string) {
break
}
if m.VariantType != nil && *m.VariantType == v.(string) {
break
}
// either the discriminator value hit the variant model name or variant type, we match the variant
if variant, ok := (*m.Variants)[v.(string)]; ok {
isMatchProperty = true
variant.MarkCovered(value)
break
}
for _, variant := range *m.Variants {
if variant.VariantType != nil && *variant.VariantType == v.(string) {
isMatchProperty = true
variant.MarkCovered(value)
break Loop
}
}
logrus.Errorf("unexpected variant %s in %s", v.(string), m.Identifier)
}
}
}
if isMatchProperty {
for k, v := range value {
if m.Properties == nil {
// some objects has no properties defined
// https://github.com/Azure/azure-rest-api-specs/blob/3519c80fe510a268f6e59a29ccac8a53fdec15b6/specification/monitor/resource-manager/Microsoft.Insights/stable/2023-03-11/dataCollectionRules_API.json#L724
logrus.Warnf("unexpected key %s in %s", k, m.Identifier)
continue
}
if _, ok := (*m.Properties)[k]; !ok {
if !m.HasAdditionalProperties && m.Discriminator == nil {
logrus.Errorf("unexpected key %s in %s", k, m.Identifier)
continue
}
}
(*m.Properties)[k].MarkCovered(v)
}
}
case nil:
default:
logrus.Errorf("unexpect type %T for json unmarshaled value", value)
}
}
func (m *Model) CountCoverage() (int, int) {
if m == nil || m.IsReadOnly {
return 0, 0
}
m.CoveredCount = 0
m.TotalCount = 0
if m.Enum != nil {
m.EnumCoveredCount = 0
m.EnumTotalCount = len(*m.Enum)
for _, isCovered := range *m.Enum {
if isCovered {
m.EnumCoveredCount++
}
}
}
if m.Bool != nil {
m.BoolCoveredCount = 0
for _, isCovered := range *m.Bool {
if isCovered {
m.BoolCoveredCount++
}
}
}
if m.Item != nil {
covered, total := m.Item.CountCoverage()
m.CoveredCount = covered
m.TotalCount = total
}
if m.Properties != nil {
for _, v := range *m.Properties {
if v.IsReadOnly {
continue
}
if v.Item != nil && v.Item.IsReadOnly {
continue
}
covered, total := v.CountCoverage()
m.CoveredCount += covered
m.TotalCount += total
if v.Variants != nil {
for _, variant := range *v.Variants {
covered, total := variant.CountCoverage()
m.CoveredCount += covered
m.TotalCount += total
}
}
if v.Item != nil && v.Item.Variants != nil {
for _, variant := range *v.Item.Variants {
covered, total := variant.CountCoverage()
m.CoveredCount += covered
m.TotalCount += total
}
}
}
}
if m.IsRoot {
if m.Variants != nil {
for _, v := range *m.Variants {
v.CountCoverage()
}
}
if m.Item != nil && m.Item.Variants != nil {
for _, v := range *m.Item.Variants {
v.CountCoverage()
}
}
}
if m.TotalCount == 0 {
m.TotalCount = 1
}
if m.TotalCount == 1 && m.IsAnyCovered {
m.CoveredCount = 1
}
m.IsFullyCovered = m.TotalCount > 0 && m.CoveredCount == m.TotalCount
if m.IsRoot {
m.RootCoveredCount = m.CoveredCount
m.RootTotalCount = m.TotalCount
if m.Variants != nil {
for _, v := range *m.Variants {
m.RootCoveredCount += v.CoveredCount
m.RootTotalCount += v.TotalCount
}
}
if m.Item != nil && m.Item.Variants != nil {
for _, v := range *m.Item.Variants {
m.RootCoveredCount += v.CoveredCount
m.RootTotalCount += v.TotalCount
}
}
}
return m.CoveredCount, m.TotalCount
}
func (m *Model) SplitCovered(covered, uncovered *[]string) {
if m == nil || m.IsReadOnly {
return
}
if m.IsAnyCovered {
*covered = append(*covered, m.Identifier)
} else {
*uncovered = append(*uncovered, m.Identifier)
}
if m.Properties != nil {
for _, v := range *m.Properties {
v.SplitCovered(covered, uncovered)
}
}
if m.Variants != nil {
for _, v := range *m.Variants {
v.SplitCovered(covered, uncovered)
}
}
if m.Item != nil {
m.Item.SplitCovered(covered, uncovered)
}
if m.Enum != nil {
for k, isCovered := range *m.Enum {
if isCovered {
*covered = append(*covered, fmt.Sprintf("%s(%v)", m.Identifier, k))
} else {
*uncovered = append(*uncovered, fmt.Sprintf("%s(%v)", m.Identifier, k))
}
}
}
if m.Bool != nil {
for k, isCovered := range *m.Bool {
if isCovered {
*covered = append(*covered, fmt.Sprintf("%s(%v)", m.Identifier, k))
} else {
*uncovered = append(*uncovered, fmt.Sprintf("%s(%v)", m.Identifier, k))
}
}
}
}