github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/open_resource_discovery/validation.go (about)

     1  package ord
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"regexp"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/mitchellh/hashstructure/v2"
    12  
    13  	"golang.org/x/mod/semver"
    14  
    15  	"github.com/google/go-cmp/cmp"
    16  	"github.com/kyma-incubator/compass/components/director/pkg/str"
    17  
    18  	validation "github.com/go-ozzo/ozzo-validation/v4"
    19  	"github.com/go-ozzo/ozzo-validation/v4/is"
    20  	"github.com/kyma-incubator/compass/components/director/internal/model"
    21  
    22  	"github.com/pkg/errors"
    23  	"github.com/tidwall/gjson"
    24  )
    25  
    26  // Disclaimer: All regexes below are provided by the ORD spec itself.
    27  const (
    28  	// SemVerRegex represents the valid structure of the field
    29  	SemVerRegex = "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"
    30  	// PackageOrdIDRegex represents the valid structure of the ordID of the Package
    31  	PackageOrdIDRegex = "^([a-z0-9-]+(?:[.][a-z0-9-]+)*):(package):([a-zA-Z0-9._\\-]+):(alpha|beta|v[0-9]+)$"
    32  	// VendorOrdIDRegex represents the valid structure of the ordID of the Vendor
    33  	VendorOrdIDRegex = "^([a-z0-9-]+(?:[.][a-z0-9-]+)*):(vendor):([a-zA-Z0-9._\\-]+):()$"
    34  	// ProductOrdIDRegex represents the valid structure of the ordID of the Product
    35  	ProductOrdIDRegex = "^([a-z0-9-]+(?:[.][a-z0-9-]+)*):(product):([a-zA-Z0-9._\\-]+):()$"
    36  	// BundleOrdIDRegex represents the valid structure of the ordID of the ConsumptionBundle
    37  	BundleOrdIDRegex = "^([a-z0-9-]+(?:[.][a-z0-9-]+)*):(consumptionBundle):([a-zA-Z0-9._\\-]+):(alpha|beta|v[0-9]+)$"
    38  	// TombstoneOrdIDRegex represents the valid structure of the ordID of the Tombstone
    39  	TombstoneOrdIDRegex = "^([a-z0-9-]+(?:[.][a-z0-9-]+)*):(package|consumptionBundle|product|vendor|apiResource|eventResource):([a-zA-Z0-9._\\-]+):(alpha|beta|v[0-9]+|)$"
    40  	// SystemInstanceBaseURLRegex represents the valid structure of the field
    41  	SystemInstanceBaseURLRegex = "^http[s]?:\\/\\/[^:\\/\\s]+\\.[^:\\/\\s\\.]+(:\\d+)?(\\/[a-zA-Z0-9-\\._~]+)?$"
    42  	// ConfigBaseURLRegex represents the valid structure of the field
    43  	ConfigBaseURLRegex = "^http[s]?:\\/\\/[^:\\/\\s]+\\.[^:\\/\\s\\.]+(:\\d+)?(\\/[a-zA-Z0-9-\\._~]+)?$"
    44  	// StringArrayElementRegex represents the valid structure of the field
    45  	StringArrayElementRegex = "^[a-zA-Z0-9-_.\\/ ]*$"
    46  	// CountryRegex represents the valid structure of the field
    47  	CountryRegex = "^[A-Z]{2}$"
    48  	// APIOrdIDRegex represents the valid structure of the ordID of the API
    49  	APIOrdIDRegex = "^([a-z0-9-]+(?:[.][a-z0-9-]+)*):(apiResource):([a-zA-Z0-9._\\-]+):(alpha|beta|v[0-9]+)$"
    50  	// EventOrdIDRegex represents the valid structure of the ordID of the Event
    51  	EventOrdIDRegex = "^([a-z0-9-]+(?:[.][a-z0-9-]+)*):(eventResource):([a-zA-Z0-9._\\-]+):(alpha|beta|v[0-9]+)$"
    52  	// CorrelationIDsRegex represents the valid structure of the field
    53  	CorrelationIDsRegex = "^([a-z0-9-]+(?:[.][a-z0-9-]+)*):([a-zA-Z0-9._\\-\\/]+):([a-zA-Z0-9._\\-\\/]+)$"
    54  	// LabelsKeyRegex represents the valid structure of the field
    55  	LabelsKeyRegex = "^[a-zA-Z0-9-_.]*$"
    56  	// NoNewLineRegex represents the valid structure of the field
    57  	NoNewLineRegex = "^[^\\n]*$"
    58  	// CustomImplementationStandardRegex represents the valid structure of the field
    59  	CustomImplementationStandardRegex = "^([a-z0-9-]+(?:[.][a-z0-9-]+)*):([a-zA-Z0-9._\\-]+):v([0-9]+)$"
    60  	// VendorPartnersRegex represents the valid structure of the field
    61  	VendorPartnersRegex = "^([a-z0-9-]+(?:[.][a-z0-9-]+)*):(vendor):([a-zA-Z0-9._\\-]+):()$"
    62  	// CustomPolicyLevelRegex represents the valid structure of the field
    63  	CustomPolicyLevelRegex = "^([a-z0-9-]+(?:[.][a-z0-9-]+)*):([a-zA-Z0-9._\\-]+):v([0-9]+)$"
    64  	// CustomTypeCredentialExchangeStrategyRegex represents the valid structure of the field
    65  	CustomTypeCredentialExchangeStrategyRegex = "^([a-z0-9-]+(?:[.][a-z0-9-]+)*):([a-zA-Z0-9._\\-]+):v([0-9]+)$"
    66  	// SAPProductOrdIDNamespaceRegex represents the valid structure of a SAP Product OrdID Namespace part
    67  	SAPProductOrdIDNamespaceRegex = "^(sap)((\\.)([a-z0-9-]+(?:[.][a-z0-9-]+)*))*$"
    68  	// OrdNamespaceRegex represents the valid structure of a Ord Namespace
    69  	OrdNamespaceRegex = "^[a-z0-9]+(?:[.][a-z0-9]+)*$"
    70  
    71  	// MinDescriptionLength represents the minimal accepted length of the Description field
    72  	MinDescriptionLength = 1
    73  	// MaxDescriptionLength represents the minimal accepted length of the Description field
    74  	MaxDescriptionLength = 5000
    75  	// MinLocalTenantIDLength represents the minimal accepted length of the LocalID field
    76  	MinLocalTenantIDLength = 1
    77  	// MaxLocalTenantIDLength represents the minimal accepted length of the LocalID field
    78  	MaxLocalTenantIDLength = 255
    79  	// MinSystemVersionTitleLength represents the minimal accepted length of the LocalID field
    80  	MinSystemVersionTitleLength = 1
    81  	// MaxSystemVersionTitleLength represents the minimal accepted length of the LocalID field
    82  	MaxSystemVersionTitleLength = 255
    83  )
    84  
    85  const (
    86  	custom string = "custom"
    87  	none   string = "none"
    88  
    89  	// PolicyLevelSap is one of the available policy options
    90  	PolicyLevelSap string = "sap:core:v1"
    91  	// PolicyLevelSapPartner is one of the available policy options
    92  	PolicyLevelSapPartner string = "sap:partner:v1"
    93  	// PolicyLevelCustom is one of the available policy options
    94  	PolicyLevelCustom = custom
    95  	// PolicyLevelNone is one of the available policy options
    96  	PolicyLevelNone string = none
    97  
    98  	// ReleaseStatusBeta is one of the available release status options
    99  	ReleaseStatusBeta string = "beta"
   100  	// ReleaseStatusActive is one of the available release status options
   101  	ReleaseStatusActive string = "active"
   102  	// ReleaseStatusDeprecated is one of the available release status options
   103  	ReleaseStatusDeprecated string = "deprecated"
   104  
   105  	// APIProtocolODataV2 is one of the available api protocol options
   106  	APIProtocolODataV2 string = "odata-v2"
   107  	// APIProtocolODataV4 is one of the available api protocol options
   108  	APIProtocolODataV4 string = "odata-v4"
   109  	// APIProtocolSoapInbound is one of the available api protocol options
   110  	APIProtocolSoapInbound string = "soap-inbound"
   111  	// APIProtocolSoapOutbound is one of the available api protocol options
   112  	APIProtocolSoapOutbound string = "soap-outbound"
   113  	// APIProtocolRest is one of the available api protocol options
   114  	APIProtocolRest string = "rest"
   115  	// APIProtocolSapRfc is one of the available api protocol options
   116  	APIProtocolSapRfc string = "sap-rfc"
   117  	// APIProtocolWebsocket is one of the available api protocol options
   118  	APIProtocolWebsocket string = "websocket"
   119  	// APIProtocolSAPSQLAPIV1 is one of the available api protocol options
   120  	APIProtocolSAPSQLAPIV1 string = "sap-sql-api-v1"
   121  
   122  	// APIVisibilityPublic is one of the available api visibility options
   123  	APIVisibilityPublic string = "public"
   124  	// APIVisibilityPrivate is one of the available api visibility options
   125  	APIVisibilityPrivate string = "private"
   126  	// APIVisibilityInternal is one of the available api visibility options
   127  	APIVisibilityInternal string = "internal"
   128  
   129  	// APIImplementationStandardDocumentAPI is one of the available api implementation standard options
   130  	APIImplementationStandardDocumentAPI string = "sap:ord-document-api:v1"
   131  	// APIImplementationStandardServiceBroker is one of the available api implementation standard options
   132  	APIImplementationStandardServiceBroker string = "cff:open-service-broker:v2"
   133  	// APIImplementationStandardCsnExposure is one of the available api implementation standard options
   134  	APIImplementationStandardCsnExposure string = "sap:csn-exposure:v1"
   135  	// APIImplementationStandardApeAPI is one of the available api implementation standard options
   136  	APIImplementationStandardApeAPI string = "sap:ape-api:v1"
   137  	// APIImplementationStandardCdiAPI is one of the available api implementation standard options
   138  	APIImplementationStandardCdiAPI string = "sap:cdi-api:v1"
   139  	// APIImplementationStandardCustom is one of the available api implementation standard options
   140  	APIImplementationStandardCustom = custom
   141  
   142  	// SapVendor is a valid Vendor ordID
   143  	SapVendor = "sap:vendor:SAP:"
   144  	// PartnerVendor is a valid partner Vendor ordID
   145  	PartnerVendor = "partner:vendor:SAP:"
   146  )
   147  
   148  var (
   149  	// LineOfBusinesses contain all valid values for this field from the spec
   150  	LineOfBusinesses = map[string]bool{
   151  		"Asset Management":                 true,
   152  		"Commerce":                         true,
   153  		"Finance":                          true,
   154  		"Human Resources":                  true,
   155  		"Manufacturing":                    true,
   156  		"Marketing":                        true,
   157  		"R&D Engineering":                  true,
   158  		"Sales":                            true,
   159  		"Service":                          true,
   160  		"Sourcing and Procurement":         true,
   161  		"Supply Chain":                     true,
   162  		"Sustainability":                   true,
   163  		"Metering":                         true,
   164  		"Grid Operations and Maintenance":  true,
   165  		"Plant Operations and Maintenance": true,
   166  		"Maintenance and Engineering":      true,
   167  	}
   168  	// Industries contain all valid values for this field from the spec
   169  	Industries = map[string]bool{
   170  		"Aerospace and Defense": true,
   171  		"Automotive":            true,
   172  		"Banking":               true,
   173  		"Chemicals":             true,
   174  		"Consumer Products":     true,
   175  		"Defense and Security":  true,
   176  		"Engineering Construction and Operations": true,
   177  		"Healthcare":                          true,
   178  		"Higher Education and Research":       true,
   179  		"High Tech":                           true,
   180  		"Industrial Machinery and Components": true,
   181  		"Insurance":                           true,
   182  		"Life Sciences":                       true,
   183  		"Media":                               true,
   184  		"Mill Products":                       true,
   185  		"Mining":                              true,
   186  		"Oil and Gas":                         true,
   187  		"Professional Services":               true,
   188  		"Public Sector":                       true,
   189  		"Retail":                              true,
   190  		"Sports and Entertainment":            true,
   191  		"Telecommunications":                  true,
   192  		"Travel and Transportation":           true,
   193  		"Utilities":                           true,
   194  		"Wholesale Distribution":              true,
   195  	}
   196  	// SupportedUseCases contain all valid values for this field from the spec
   197  	SupportedUseCases = map[string]bool{
   198  		"mass-extraction": true,
   199  		// "mass-import":     true, // will be added later in spec
   200  	}
   201  )
   202  
   203  var shortDescriptionRules = []validation.Rule{
   204  	validation.Required, validation.RuneLength(1, 256), validation.NewStringRule(noNewLines, "short description should not contain line breaks"),
   205  }
   206  
   207  var optionalShortDescriptionRules = []validation.Rule{
   208  	validation.NilOrNotEmpty, validation.RuneLength(1, 256), validation.NewStringRule(noNewLines, "short description should not contain line breaks"),
   209  }
   210  
   211  // ORDDocumentValidationError contains the validation errors when aggregating ord documents
   212  type ORDDocumentValidationError struct {
   213  	Err error
   214  }
   215  
   216  func (e *ORDDocumentValidationError) Error() string {
   217  	return e.Err.Error()
   218  }
   219  
   220  // ValidateSystemInstanceInput validates the given SystemInstance
   221  func ValidateSystemInstanceInput(app *model.Application) error {
   222  	return validation.ValidateStruct(app,
   223  		validation.Field(&app.CorrelationIDs, validation.By(func(value interface{}) error {
   224  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(CorrelationIDsRegex))
   225  		})),
   226  		validation.Field(&app.LocalTenantID, validation.NilOrNotEmpty, validation.Length(MinLocalTenantIDLength, MaxLocalTenantIDLength)),
   227  		validation.Field(&app.ApplicationNamespace, validation.Match(regexp.MustCompile(OrdNamespaceRegex))),
   228  		validation.Field(&app.BaseURL, is.RequestURI, validation.Match(regexp.MustCompile(SystemInstanceBaseURLRegex))),
   229  		validation.Field(&app.OrdLabels, validation.By(validateORDLabels)),
   230  		validation.Field(&app.Tags, validation.By(func(value interface{}) error {
   231  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(StringArrayElementRegex))
   232  		})),
   233  		validation.Field(&app.DocumentationLabels, validation.By(validateDocumentationLabels)),
   234  	)
   235  }
   236  
   237  // ValidateSystemVersionInput validates the given SystemVersion
   238  func ValidateSystemVersionInput(appTemplateVersion *model.ApplicationTemplateVersionInput) error {
   239  	return validation.ValidateStruct(appTemplateVersion,
   240  		validation.Field(&appTemplateVersion.Title, validation.NilOrNotEmpty, validation.Length(MinSystemVersionTitleLength, MaxSystemVersionTitleLength), validation.Match(regexp.MustCompile(NoNewLineRegex))),
   241  		validation.Field(&appTemplateVersion.ReleaseDate, validation.Required),
   242  	)
   243  }
   244  
   245  func validateDocumentInput(doc *Document) error {
   246  	return validation.ValidateStruct(doc, validation.Field(&doc.OpenResourceDiscovery, validation.Required, validation.Match(regexp.MustCompile("^1.*$"))))
   247  }
   248  
   249  func validatePackageInput(pkg *model.PackageInput, packagesFromDB map[string]*model.Package, resourceHashes map[string]uint64) error {
   250  	return validation.ValidateStruct(pkg,
   251  		validation.Field(&pkg.OrdID, validation.Required, validation.Match(regexp.MustCompile(PackageOrdIDRegex))),
   252  		validation.Field(&pkg.Title, validation.Length(1, 255), validation.Required),
   253  		validation.Field(&pkg.ShortDescription, shortDescriptionRules...),
   254  		validation.Field(&pkg.Description, validation.Required, validation.Length(MinDescriptionLength, MaxDescriptionLength)),
   255  		validation.Field(&pkg.SupportInfo, validation.NilOrNotEmpty),
   256  		validation.Field(&pkg.Version, validation.Required, validation.Match(regexp.MustCompile(SemVerRegex)), validation.By(func(value interface{}) error {
   257  			return validatePackageVersionInput(value, *pkg, packagesFromDB, resourceHashes)
   258  		})),
   259  		validation.Field(&pkg.PolicyLevel, validation.Required, validation.In(PolicyLevelSap, PolicyLevelSapPartner, PolicyLevelCustom, PolicyLevelNone), validation.When(pkg.CustomPolicyLevel != nil, validation.In(PolicyLevelCustom))),
   260  		validation.Field(&pkg.CustomPolicyLevel, validation.When(pkg.PolicyLevel != PolicyLevelCustom, validation.Empty), validation.Match(regexp.MustCompile(CustomPolicyLevelRegex))),
   261  		validation.Field(&pkg.PackageLinks, validation.By(validatePackageLinks)),
   262  		validation.Field(&pkg.Links, validation.By(validateORDLinks)),
   263  		validation.Field(&pkg.Vendor, validation.Required, validation.When(pkg.PolicyLevel == PolicyLevelSap, validation.In(SapVendor)), validation.When(pkg.PolicyLevel == PolicyLevelSapPartner, validation.NotIn(SapVendor)), validation.Match(regexp.MustCompile(VendorOrdIDRegex))),
   264  		validation.Field(&pkg.PartOfProducts, validation.Required, validation.By(func(value interface{}) error {
   265  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(ProductOrdIDRegex))
   266  		})),
   267  		validation.Field(&pkg.Tags, validation.By(func(value interface{}) error {
   268  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(StringArrayElementRegex))
   269  		})),
   270  		validation.Field(&pkg.Labels, validation.By(validateORDLabels)),
   271  		validation.Field(&pkg.Countries, validation.By(func(value interface{}) error {
   272  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(CountryRegex))
   273  		})),
   274  		validation.Field(&pkg.LineOfBusiness,
   275  			validation.By(func(value interface{}) error {
   276  				if pkg.PolicyLevel != PolicyLevelSap {
   277  					return nil
   278  				}
   279  				return validateJSONArrayOfStringsContainsInMap(value, LineOfBusinesses)
   280  			}),
   281  			validation.By(func(value interface{}) error {
   282  				return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(StringArrayElementRegex))
   283  			}),
   284  		),
   285  		validation.Field(&pkg.Industry,
   286  			validation.By(func(value interface{}) error {
   287  				if pkg.PolicyLevel != PolicyLevelSap {
   288  					return nil
   289  				}
   290  				return validateJSONArrayOfStringsContainsInMap(value, Industries)
   291  			}),
   292  			validation.By(func(value interface{}) error {
   293  				return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(StringArrayElementRegex))
   294  			}),
   295  		),
   296  		validation.Field(&pkg.DocumentationLabels, validation.By(validateDocumentationLabels)),
   297  	)
   298  }
   299  
   300  func validateBundleInput(bndl *model.BundleCreateInput, bundlesFromDB map[string]*model.Bundle, resourceHashes map[string]uint64, credentialExchangeStrategyTenantMappings map[string]CredentialExchangeStrategyTenantMapping) error {
   301  	return validation.ValidateStruct(bndl,
   302  		validation.Field(&bndl.OrdID, validation.Required, validation.Match(regexp.MustCompile(BundleOrdIDRegex))),
   303  		validation.Field(&bndl.LocalTenantID, validation.NilOrNotEmpty, validation.Length(MinLocalTenantIDLength, MaxLocalTenantIDLength)),
   304  		validation.Field(&bndl.Name, validation.Required),
   305  		validation.Field(&bndl.ShortDescription, optionalShortDescriptionRules...),
   306  		validation.Field(&bndl.Description, validation.NilOrNotEmpty, validation.Length(MinDescriptionLength, MaxDescriptionLength)),
   307  		validation.Field(&bndl.Version, validation.Match(regexp.MustCompile(SemVerRegex)), validation.By(func(value interface{}) error {
   308  			return validateBundleVersionInput(value, *bndl, bundlesFromDB, resourceHashes)
   309  		})),
   310  		validation.Field(&bndl.Links, validation.By(validateORDLinks)),
   311  		validation.Field(&bndl.Labels, validation.By(validateORDLabels)),
   312  		validation.Field(&bndl.CredentialExchangeStrategies, validation.By(func(value interface{}) error {
   313  			return validateJSONArrayOfObjects(value, map[string][]validation.Rule{
   314  				"type": {
   315  					validation.Required,
   316  					validation.In(custom),
   317  				},
   318  				"callbackUrl": {
   319  					is.RequestURI,
   320  				},
   321  			}, validateCustomType(credentialExchangeStrategyTenantMappings), validateCustomDescription)
   322  		})),
   323  		validation.Field(&bndl.CorrelationIDs, validation.By(func(value interface{}) error {
   324  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(CorrelationIDsRegex))
   325  		})),
   326  		validation.Field(&bndl.Tags, validation.By(func(value interface{}) error {
   327  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(StringArrayElementRegex))
   328  		})),
   329  		validation.Field(&bndl.DocumentationLabels, validation.By(validateDocumentationLabels)),
   330  	)
   331  }
   332  
   333  func validateAPIInput(api *model.APIDefinitionInput, packagePolicyLevels map[string]string, apisFromDB map[string]*model.APIDefinition, apiHashes map[string]uint64) error {
   334  	return validation.ValidateStruct(api,
   335  		validation.Field(&api.OrdID, validation.Required, validation.Match(regexp.MustCompile(APIOrdIDRegex))),
   336  		validation.Field(&api.LocalTenantID, validation.NilOrNotEmpty, validation.Length(MinLocalTenantIDLength, MaxLocalTenantIDLength)),
   337  		validation.Field(&api.Name, validation.Required),
   338  		validation.Field(&api.ShortDescription, shortDescriptionRules...),
   339  		validation.Field(&api.Description, validation.Required, validation.Length(MinDescriptionLength, MaxDescriptionLength)),
   340  		validation.Field(&api.PolicyLevel, validation.In(PolicyLevelSap, PolicyLevelSapPartner, PolicyLevelCustom, PolicyLevelNone), validation.When(api.CustomPolicyLevel != nil, validation.In(PolicyLevelCustom))),
   341  		validation.Field(&api.CustomPolicyLevel, validation.When(api.PolicyLevel != nil && *api.PolicyLevel != PolicyLevelCustom, validation.Empty), validation.Match(regexp.MustCompile(CustomPolicyLevelRegex))),
   342  		validation.Field(&api.VersionInput.Value, validation.Required, validation.Match(regexp.MustCompile(SemVerRegex)), validation.By(func(value interface{}) error {
   343  			return validateAPIDefinitionVersionInput(value, *api, apisFromDB, apiHashes)
   344  		})),
   345  		validation.Field(&api.OrdPackageID, validation.Required, validation.Match(regexp.MustCompile(PackageOrdIDRegex))),
   346  		validation.Field(&api.APIProtocol, validation.Required, validation.In(APIProtocolODataV2, APIProtocolODataV4, APIProtocolSoapInbound, APIProtocolSoapOutbound, APIProtocolRest, APIProtocolSapRfc, APIProtocolWebsocket, APIProtocolSAPSQLAPIV1)),
   347  		validation.Field(&api.Visibility, validation.Required, validation.In(APIVisibilityPublic, APIVisibilityInternal, APIVisibilityPrivate)),
   348  		validation.Field(&api.PartOfProducts, validation.By(func(value interface{}) error {
   349  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(ProductOrdIDRegex))
   350  		})),
   351  		validation.Field(&api.SupportedUseCases,
   352  			validation.By(func(value interface{}) error {
   353  				return validateJSONArrayOfStringsContainsInMap(value, SupportedUseCases)
   354  			}),
   355  		),
   356  		validation.Field(&api.Hierarchy, validation.By(func(value interface{}) error {
   357  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(StringArrayElementRegex))
   358  		})),
   359  		validation.Field(&api.Tags, validation.By(func(value interface{}) error {
   360  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(StringArrayElementRegex))
   361  		})),
   362  		validation.Field(&api.Countries, validation.By(func(value interface{}) error {
   363  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(CountryRegex))
   364  		})),
   365  		validation.Field(&api.LineOfBusiness,
   366  			validation.By(func(value interface{}) error {
   367  				return validateWhenPolicyLevelIsSAP(api.OrdPackageID, packagePolicyLevels, func() error {
   368  					return validateJSONArrayOfStringsContainsInMap(value, LineOfBusinesses)
   369  				})
   370  			}),
   371  			validation.By(func(value interface{}) error {
   372  				return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(StringArrayElementRegex))
   373  			}),
   374  		),
   375  		validation.Field(&api.Industry,
   376  			validation.By(func(value interface{}) error {
   377  				return validateWhenPolicyLevelIsSAP(api.OrdPackageID, packagePolicyLevels, func() error {
   378  					return validateJSONArrayOfStringsContainsInMap(value, Industries)
   379  				})
   380  			}),
   381  			validation.By(func(value interface{}) error {
   382  				return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(StringArrayElementRegex))
   383  			}),
   384  		),
   385  		validation.Field(&api.ResourceDefinitions, validation.By(func(value interface{}) error {
   386  			return validateAPIResourceDefinitions(value, *api, packagePolicyLevels)
   387  		})),
   388  		validation.Field(&api.APIResourceLinks, validation.By(validateAPILinks)),
   389  		validation.Field(&api.Links, validation.By(validateORDLinks)),
   390  		validation.Field(&api.ReleaseStatus, validation.Required, validation.In(ReleaseStatusBeta, ReleaseStatusActive, ReleaseStatusDeprecated)),
   391  		validation.Field(&api.SunsetDate, validation.When(*api.ReleaseStatus == ReleaseStatusDeprecated, validation.Required), validation.When(api.SunsetDate != nil, validation.By(isValidDate))),
   392  		validation.Field(&api.ChangeLogEntries, validation.By(validateORDChangeLogEntries)),
   393  		validation.Field(&api.TargetURLs, validation.By(validateEntryPoints), validation.When(api.TargetURLs == nil, validation.By(notPartOfConsumptionBundles(api.PartOfConsumptionBundles)))),
   394  		validation.Field(&api.Labels, validation.By(validateORDLabels)),
   395  		validation.Field(&api.ImplementationStandard, validation.In(APIImplementationStandardDocumentAPI, APIImplementationStandardServiceBroker, APIImplementationStandardCsnExposure, APIImplementationStandardApeAPI, APIImplementationStandardCdiAPI, APIImplementationStandardCustom)),
   396  		validation.Field(&api.CustomImplementationStandard, validation.When(api.ImplementationStandard != nil && *api.ImplementationStandard == APIImplementationStandardCustom, validation.Required, validation.Match(regexp.MustCompile(CustomImplementationStandardRegex))).Else(validation.Empty)),
   397  		validation.Field(&api.CustomImplementationStandardDescription, validation.When(api.ImplementationStandard != nil && *api.ImplementationStandard == APIImplementationStandardCustom, validation.Required).Else(validation.Empty)),
   398  		validation.Field(&api.PartOfConsumptionBundles, validation.By(func(value interface{}) error {
   399  			return validateAPIPartOfConsumptionBundles(value, api.TargetURLs, regexp.MustCompile(BundleOrdIDRegex))
   400  		})),
   401  		validation.Field(&api.DefaultConsumptionBundle, validation.Match(regexp.MustCompile(BundleOrdIDRegex)), validation.By(func(value interface{}) error {
   402  			return validateDefaultConsumptionBundle(value, api.PartOfConsumptionBundles)
   403  		})),
   404  		validation.Field(&api.Extensible, validation.By(func(value interface{}) error {
   405  			return validateExtensibleField(value, api.OrdPackageID, packagePolicyLevels)
   406  		})),
   407  		validation.Field(&api.DocumentationLabels, validation.By(validateDocumentationLabels)),
   408  	)
   409  }
   410  
   411  func validateEventInput(event *model.EventDefinitionInput, packagePolicyLevels map[string]string, eventsFromDB map[string]*model.EventDefinition, eventHashes map[string]uint64) error {
   412  	return validation.ValidateStruct(event,
   413  		validation.Field(&event.OrdID, validation.Required, validation.Match(regexp.MustCompile(EventOrdIDRegex))),
   414  		validation.Field(&event.LocalTenantID, validation.NilOrNotEmpty, validation.Length(MinLocalTenantIDLength, MaxLocalTenantIDLength)),
   415  		validation.Field(&event.Name, validation.Required),
   416  		validation.Field(&event.ShortDescription, shortDescriptionRules...),
   417  		validation.Field(&event.Description, validation.Required, validation.Length(MinDescriptionLength, MaxDescriptionLength)),
   418  		validation.Field(&event.PolicyLevel, validation.In(PolicyLevelSap, PolicyLevelSapPartner, PolicyLevelCustom, PolicyLevelNone), validation.When(event.CustomPolicyLevel != nil, validation.In(PolicyLevelCustom))),
   419  		validation.Field(&event.CustomPolicyLevel, validation.When(event.PolicyLevel != nil && *event.PolicyLevel != PolicyLevelCustom, validation.Empty), validation.Match(regexp.MustCompile(CustomPolicyLevelRegex))),
   420  		validation.Field(&event.VersionInput.Value, validation.Required, validation.Match(regexp.MustCompile(SemVerRegex)), validation.By(func(value interface{}) error {
   421  			return validateEventDefinitionVersionInput(value, *event, eventsFromDB, eventHashes)
   422  		})),
   423  		validation.Field(&event.OrdPackageID, validation.Required, validation.Match(regexp.MustCompile(PackageOrdIDRegex))),
   424  		validation.Field(&event.Visibility, validation.Required, validation.In(APIVisibilityPublic, APIVisibilityInternal, APIVisibilityPrivate)),
   425  		validation.Field(&event.PartOfProducts, validation.By(func(value interface{}) error {
   426  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(ProductOrdIDRegex))
   427  		})),
   428  		validation.Field(&event.Hierarchy, validation.By(func(value interface{}) error {
   429  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(StringArrayElementRegex))
   430  		})),
   431  		validation.Field(&event.Tags, validation.By(func(value interface{}) error {
   432  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(StringArrayElementRegex))
   433  		})),
   434  		validation.Field(&event.Countries, validation.By(func(value interface{}) error {
   435  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(CountryRegex))
   436  		})),
   437  		validation.Field(&event.LineOfBusiness,
   438  			validation.By(func(value interface{}) error {
   439  				return validateWhenPolicyLevelIsSAP(event.OrdPackageID, packagePolicyLevels, func() error {
   440  					return validateJSONArrayOfStringsContainsInMap(value, LineOfBusinesses)
   441  				})
   442  			}),
   443  			validation.By(func(value interface{}) error {
   444  				return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(StringArrayElementRegex))
   445  			}),
   446  		),
   447  		validation.Field(&event.Industry,
   448  			validation.By(func(value interface{}) error {
   449  				return validateWhenPolicyLevelIsSAP(event.OrdPackageID, packagePolicyLevels, func() error {
   450  					return validateJSONArrayOfStringsContainsInMap(value, Industries)
   451  				})
   452  			}),
   453  			validation.By(func(value interface{}) error {
   454  				return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(StringArrayElementRegex))
   455  			}),
   456  		),
   457  		validation.Field(&event.ResourceDefinitions, validation.By(func(value interface{}) error {
   458  			return validateEventResourceDefinition(value, *event, packagePolicyLevels)
   459  		})),
   460  		validation.Field(&event.Links, validation.By(validateORDLinks)),
   461  		validation.Field(&event.ReleaseStatus, validation.Required, validation.In(ReleaseStatusBeta, ReleaseStatusActive, ReleaseStatusDeprecated)),
   462  		validation.Field(&event.SunsetDate, validation.When(*event.ReleaseStatus == ReleaseStatusDeprecated, validation.Required), validation.When(event.SunsetDate != nil, validation.By(isValidDate))),
   463  		validation.Field(&event.ChangeLogEntries, validation.By(validateORDChangeLogEntries)),
   464  		validation.Field(&event.Labels, validation.By(validateORDLabels)),
   465  		validation.Field(&event.PartOfConsumptionBundles, validation.By(func(value interface{}) error {
   466  			return validateEventPartOfConsumptionBundles(value, regexp.MustCompile(BundleOrdIDRegex))
   467  		})),
   468  		validation.Field(&event.DefaultConsumptionBundle, validation.Match(regexp.MustCompile(BundleOrdIDRegex)), validation.By(func(value interface{}) error {
   469  			return validateDefaultConsumptionBundle(value, event.PartOfConsumptionBundles)
   470  		})),
   471  		validation.Field(&event.Extensible, validation.By(func(value interface{}) error {
   472  			return validateExtensibleField(value, event.OrdPackageID, packagePolicyLevels)
   473  		})),
   474  		validation.Field(&event.DocumentationLabels, validation.By(validateDocumentationLabels)),
   475  	)
   476  }
   477  
   478  func validateProductInput(product *model.ProductInput) error {
   479  	productOrdIDNamespace := strings.Split(product.OrdID, ":")[0]
   480  
   481  	return validation.ValidateStruct(product,
   482  		validation.Field(&product.OrdID, validation.Required, validation.Match(regexp.MustCompile(ProductOrdIDRegex))),
   483  		validation.Field(&product.Title, validation.Length(1, 255), validation.Required),
   484  		validation.Field(&product.ShortDescription, shortDescriptionRules...),
   485  		validation.Field(&product.Vendor, validation.Required,
   486  			validation.Match(regexp.MustCompile(VendorOrdIDRegex)),
   487  			validation.When(regexp.MustCompile(SAPProductOrdIDNamespaceRegex).MatchString(productOrdIDNamespace), validation.In(SapVendor)).Else(validation.NotIn(SapVendor)),
   488  		),
   489  		validation.Field(&product.Parent, validation.When(product.Parent != nil, validation.Match(regexp.MustCompile(ProductOrdIDRegex)))),
   490  		validation.Field(&product.CorrelationIDs, validation.By(func(value interface{}) error {
   491  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(CorrelationIDsRegex))
   492  		})),
   493  		validation.Field(&product.Labels, validation.By(validateORDLabels)),
   494  		validation.Field(&product.Tags, validation.By(func(value interface{}) error {
   495  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(StringArrayElementRegex))
   496  		})),
   497  		validation.Field(&product.DocumentationLabels, validation.By(validateDocumentationLabels)),
   498  	)
   499  }
   500  
   501  func validateVendorInput(vendor *model.VendorInput) error {
   502  	return validation.ValidateStruct(vendor,
   503  		validation.Field(&vendor.OrdID, validation.Required, validation.Match(regexp.MustCompile(VendorOrdIDRegex))),
   504  		validation.Field(&vendor.Title, validation.Length(1, 255), validation.Required),
   505  		validation.Field(&vendor.Labels, validation.By(validateORDLabels)),
   506  		validation.Field(&vendor.Partners, validation.By(func(value interface{}) error {
   507  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(VendorPartnersRegex))
   508  		})),
   509  		validation.Field(&vendor.Tags, validation.By(func(value interface{}) error {
   510  			return validateJSONArrayOfStringsMatchPattern(value, regexp.MustCompile(StringArrayElementRegex))
   511  		})),
   512  		validation.Field(&vendor.DocumentationLabels, validation.By(validateDocumentationLabels)),
   513  	)
   514  }
   515  
   516  func validateTombstoneInput(tombstone *model.TombstoneInput) error {
   517  	return validation.ValidateStruct(tombstone,
   518  		validation.Field(&tombstone.OrdID, validation.Required, validation.Match(regexp.MustCompile(TombstoneOrdIDRegex))),
   519  		validation.Field(&tombstone.RemovalDate, validation.Required, validation.By(isValidDate)))
   520  }
   521  
   522  func validateORDLabels(val interface{}) error {
   523  	return validateLabels(val, LabelsKeyRegex)
   524  }
   525  
   526  func validateDocumentationLabels(val interface{}) error {
   527  	return validateLabels(val, NoNewLineRegex)
   528  }
   529  
   530  func validateLabels(val interface{}, regex string) error {
   531  	if val == nil {
   532  		return nil
   533  	}
   534  
   535  	labels, ok := val.(json.RawMessage)
   536  	if !ok {
   537  		return errors.New("labels should be json")
   538  	}
   539  
   540  	if len(labels) == 0 {
   541  		return nil
   542  	}
   543  
   544  	if !gjson.ValidBytes(labels) {
   545  		return errors.New("labels should be valid json")
   546  	}
   547  
   548  	parsedLabels := gjson.ParseBytes(labels)
   549  	if !parsedLabels.IsObject() {
   550  		return errors.New("labels should be json object")
   551  	}
   552  
   553  	var err error
   554  	parsedLabels.ForEach(func(key, value gjson.Result) bool {
   555  		if err = validation.Validate(key.String(), validation.Match(regexp.MustCompile(regex))); err != nil {
   556  			return false
   557  		}
   558  		if !value.IsArray() {
   559  			err = errors.New("label value should be array")
   560  			return false
   561  		}
   562  		for _, el := range value.Array() {
   563  			if el.Type != gjson.String {
   564  				err = errors.New("label value should be array of strings")
   565  				return false
   566  			}
   567  		}
   568  		return true
   569  	})
   570  	return err
   571  }
   572  
   573  func validateEntryPoints(val interface{}) error {
   574  	if val == nil {
   575  		return nil
   576  	}
   577  
   578  	entryPoints, ok := val.(json.RawMessage)
   579  	if !ok {
   580  		return errors.New("entryPoints should be json")
   581  	}
   582  
   583  	if len(entryPoints) == 0 {
   584  		return nil
   585  	}
   586  
   587  	if !gjson.ValidBytes(entryPoints) {
   588  		return errors.New("entryPoints should be valid json")
   589  	}
   590  
   591  	parsedArr := gjson.ParseBytes(entryPoints)
   592  	if !parsedArr.IsArray() {
   593  		return errors.New("should be json array")
   594  	}
   595  
   596  	if len(parsedArr.Array()) == 0 {
   597  		return errors.New("entryPoints should not be empty if present")
   598  	}
   599  
   600  	if areThereEntryPointDuplicates(parsedArr.Array()) {
   601  		return errors.New("entryPoints should not contain duplicates")
   602  	}
   603  
   604  	for _, el := range parsedArr.Array() {
   605  		if el.Type != gjson.String {
   606  			return errors.New("should be array of strings")
   607  		}
   608  
   609  		err := validation.Validate(el.String(), is.RequestURI)
   610  		if err != nil {
   611  			return errors.New("entryPoint should be a valid URI")
   612  		}
   613  	}
   614  	return nil
   615  }
   616  
   617  func validateORDChangeLogEntries(value interface{}) error {
   618  	return validateJSONArrayOfObjects(value, map[string][]validation.Rule{
   619  		"version": {
   620  			validation.Required,
   621  			validation.Match(regexp.MustCompile(SemVerRegex)),
   622  		},
   623  		"releaseStatus": {
   624  			validation.Required,
   625  			validation.In(ReleaseStatusBeta, ReleaseStatusActive, ReleaseStatusDeprecated),
   626  		},
   627  		"date": {
   628  			validation.Required,
   629  			validation.By(isValidDate),
   630  		},
   631  		"description": {
   632  			validation.NilOrNotEmpty,
   633  			validation.Length(MinDescriptionLength, MaxDescriptionLength),
   634  		},
   635  		"url": {
   636  			is.RequestURI,
   637  		},
   638  	})
   639  }
   640  
   641  func validateORDLinks(value interface{}) error {
   642  	return validateJSONArrayOfObjects(value, map[string][]validation.Rule{
   643  		"title": {
   644  			validation.Length(1, 255),
   645  			validation.Required,
   646  		},
   647  		"url": {
   648  			validation.Required,
   649  			is.RequestURI,
   650  		},
   651  		"description": {
   652  			validation.NilOrNotEmpty,
   653  			validation.Length(MinDescriptionLength, MaxDescriptionLength),
   654  		},
   655  	})
   656  }
   657  
   658  func validatePackageLinks(value interface{}) error {
   659  	return validateJSONArrayOfObjects(value, map[string][]validation.Rule{
   660  		"type": {
   661  			validation.Required,
   662  			validation.In("terms-of-service", "license", "client-registration", "payment", "sandbox", "service-level-agreement", "support", "custom"),
   663  		},
   664  		"url": {
   665  			validation.Required,
   666  			is.RequestURI,
   667  		},
   668  	}, func(el gjson.Result) error {
   669  		if el.Get("customType").Exists() {
   670  			if el.Get("type").String() != custom {
   671  				return errors.New("if customType is provided, type should be set to 'custom'")
   672  			} else {
   673  				return validation.Validate(el.Get("customType").String(), validation.Match(regexp.MustCompile(CustomImplementationStandardRegex)))
   674  			}
   675  		}
   676  		return nil
   677  	})
   678  }
   679  
   680  func validateAPILinks(value interface{}) error {
   681  	return validateJSONArrayOfObjects(value, map[string][]validation.Rule{
   682  		"type": {
   683  			validation.Required,
   684  			validation.In("api-documentation", "authentication", "client-registration", "console", "payment", "service-level-agreement", "support", "custom"),
   685  		},
   686  		"url": {
   687  			validation.Required,
   688  			is.RequestURI,
   689  		},
   690  	}, func(el gjson.Result) error {
   691  		if el.Get("customType").Exists() {
   692  			if el.Get("type").String() != custom {
   693  				return errors.New("if customType is provided, type should be set to 'custom'")
   694  			} else {
   695  				return validation.Validate(el.Get("customType").String(), validation.Match(regexp.MustCompile(CustomImplementationStandardRegex)))
   696  			}
   697  		}
   698  		return nil
   699  	})
   700  }
   701  
   702  func validateAPIResourceDefinitions(value interface{}, api model.APIDefinitionInput, packagePolicyLevels map[string]string) error {
   703  	if value == nil {
   704  		return nil
   705  	}
   706  
   707  	pkgOrdID := str.PtrStrToStr(api.OrdPackageID)
   708  	policyLevel := packagePolicyLevels[pkgOrdID]
   709  	apiVisibility := str.PtrStrToStr(api.Visibility)
   710  	apiProtocol := str.PtrStrToStr(api.APIProtocol)
   711  	resourceDefinitions := api.ResourceDefinitions
   712  
   713  	isResourceDefinitionMandatory := !(policyLevel == PolicyLevelSap && apiVisibility == APIVisibilityPrivate)
   714  	if len(resourceDefinitions) == 0 && isResourceDefinitionMandatory {
   715  		return errors.New("when api resource visibility is public or internal, resource definitions must be provided")
   716  	}
   717  
   718  	if len(resourceDefinitions) == 0 && !isResourceDefinitionMandatory {
   719  		return nil
   720  	}
   721  
   722  	resourceDefinitionTypes := make(map[model.APISpecType]bool)
   723  
   724  	for _, rd := range resourceDefinitions {
   725  		resourceDefinitionType := rd.Type
   726  		resourceDefinitionTypes[resourceDefinitionType] = true
   727  	}
   728  
   729  	isPolicyCoreOrPartner := policyLevel == PolicyLevelSap || policyLevel == PolicyLevelSapPartner
   730  	wsdlTypeExists := resourceDefinitionTypes[model.APISpecTypeWsdlV1] || resourceDefinitionTypes[model.APISpecTypeWsdlV2]
   731  	if isPolicyCoreOrPartner && (apiProtocol == APIProtocolSoapInbound || apiProtocol == APIProtocolSoapOutbound) && !wsdlTypeExists {
   732  		return errors.New("for APIResources of policyLevel='sap' or 'sap-partner' and with apiProtocol='soap-inbound' or 'soap-outbound' it is mandatory to provide either WSDL V2 or WSDL V1 definitions")
   733  	}
   734  
   735  	edmxTypeExists := resourceDefinitionTypes[model.APISpecTypeEDMX]
   736  	openAPITypeExists := resourceDefinitionTypes[model.APISpecTypeOpenAPIV2] || resourceDefinitionTypes[model.APISpecTypeOpenAPIV3]
   737  	if isPolicyCoreOrPartner && (apiProtocol == APIProtocolODataV2 || apiProtocol == APIProtocolODataV4) && !(edmxTypeExists && openAPITypeExists) {
   738  		return errors.New("for APIResources of policyLevel='sap' or 'sap-partner' and with apiProtocol='odata-v2' or 'odata-v4' it is mandatory to not only provide edmx definitions, but also OpenAPI definitions")
   739  	}
   740  
   741  	if isPolicyCoreOrPartner && apiProtocol == APIProtocolRest && !openAPITypeExists {
   742  		return errors.New("for APIResources of policyLevel='sap' or 'sap-partner' and with apiProtocol='rest' it is mandatory to provide either OpenAPI 3 or OpenAPI 2 definitions")
   743  	}
   744  
   745  	rfcMetadataTypeExists := resourceDefinitionTypes[model.APISpecTypeRfcMetadata]
   746  	if isPolicyCoreOrPartner && apiProtocol == APIProtocolSapRfc && !rfcMetadataTypeExists {
   747  		return errors.New("for APIResources of policyLevel='sap' or 'sap-partner' and with apiProtocol='sap-rfc' it is mandatory to provide SAP RFC definitions")
   748  	}
   749  
   750  	if apiProtocol == APIProtocolWebsocket && (api.ImplementationStandard == nil || !resourceDefinitionTypes[model.APISpecTypeCustom]) {
   751  		return errors.New("for APIResources with apiProtocol='websocket' it is mandatory to provide implementationStandard definition and type to be set to custom")
   752  	}
   753  
   754  	if apiProtocol == APIProtocolSAPSQLAPIV1 && !(resourceDefinitionTypes[model.APISpecTypeCustom] || resourceDefinitionTypes[model.APISpecTypeSQLAPIDefinitionV1]) {
   755  		return errors.New("for APIResources with apiProtocol='sap-sql-api-v1' it is mandatory type to be set either to sap-sql-api-definition-v1 or custom")
   756  	}
   757  
   758  	return nil
   759  }
   760  
   761  func validateEventResourceDefinition(value interface{}, event model.EventDefinitionInput, packagePolicyLevels map[string]string) error {
   762  	if value == nil {
   763  		return nil
   764  	}
   765  
   766  	pkgOrdID := str.PtrStrToStr(event.OrdPackageID)
   767  	policyLevel := packagePolicyLevels[pkgOrdID]
   768  	apiVisibility := str.PtrStrToStr(event.Visibility)
   769  
   770  	if policyLevel == PolicyLevelSap && apiVisibility == APIVisibilityPrivate {
   771  		return nil
   772  	}
   773  
   774  	eventResourceDef, ok := value.([]*model.EventResourceDefinition)
   775  	if !ok {
   776  		return errors.New("error while casting to EventResourceDefinition")
   777  	}
   778  
   779  	if len(eventResourceDef) == 0 {
   780  		return errors.New("when event resource visibility is public or internal, resource definitions must be provided")
   781  	}
   782  
   783  	return nil
   784  }
   785  
   786  func validatePackageVersionInput(value interface{}, pkg model.PackageInput, pkgsFromDB map[string]*model.Package, resourceHashes map[string]uint64) error {
   787  	if value == nil {
   788  		return nil
   789  	}
   790  
   791  	if len(pkgsFromDB) == 0 {
   792  		return nil
   793  	}
   794  
   795  	pkgFromDB, ok := pkgsFromDB[pkg.OrdID]
   796  	if !ok || isResourceHashMissing(pkgFromDB.ResourceHash) {
   797  		return nil
   798  	}
   799  
   800  	hashDB := str.PtrStrToStr(pkgFromDB.ResourceHash)
   801  	hashDoc := strconv.FormatUint(resourceHashes[pkg.OrdID], 10)
   802  
   803  	return checkHashEquality(pkgFromDB.Version, pkg.Version, hashDB, hashDoc)
   804  }
   805  
   806  func validateBundleVersionInput(value interface{}, bndl model.BundleCreateInput, bndlsFromDB map[string]*model.Bundle, resourceHashes map[string]uint64) error {
   807  	if value == nil {
   808  		return nil
   809  	}
   810  
   811  	if len(bndlsFromDB) == 0 {
   812  		return nil
   813  	}
   814  
   815  	bndlOrdID := str.PtrStrToStr(bndl.OrdID)
   816  	bndlFromDB, ok := bndlsFromDB[bndlOrdID]
   817  	if !ok || isResourceHashMissing(bndlFromDB.ResourceHash) {
   818  		return nil
   819  	}
   820  
   821  	hashDB := str.PtrStrToStr(bndlFromDB.ResourceHash)
   822  	hashDoc := strconv.FormatUint(resourceHashes[str.PtrStrToStr(bndl.OrdID)], 10)
   823  
   824  	if bndlFromDB.Version != nil && bndl.Version != nil {
   825  		return checkHashEquality(*bndlFromDB.Version, *bndl.Version, hashDB, hashDoc)
   826  	}
   827  	if bndlFromDB.Version != nil && bndl.Version == nil {
   828  		return errors.New("bundle version is present in the DB, but is missing from the document")
   829  	}
   830  	return nil
   831  }
   832  
   833  func validateEventDefinitionVersionInput(value interface{}, event model.EventDefinitionInput, eventsFromDB map[string]*model.EventDefinition, eventHashes map[string]uint64) error {
   834  	if value == nil {
   835  		return nil
   836  	}
   837  
   838  	if len(eventsFromDB) == 0 {
   839  		return nil
   840  	}
   841  
   842  	eventFromDB, ok := eventsFromDB[str.PtrStrToStr(event.OrdID)]
   843  	if !ok || isResourceHashMissing(eventFromDB.ResourceHash) {
   844  		return nil
   845  	}
   846  
   847  	hashDB := str.PtrStrToStr(eventFromDB.ResourceHash)
   848  	hashDoc := strconv.FormatUint(eventHashes[str.PtrStrToStr(event.OrdID)], 10)
   849  
   850  	return checkHashEquality(eventFromDB.Version.Value, event.VersionInput.Value, hashDB, hashDoc)
   851  }
   852  
   853  func validateAPIDefinitionVersionInput(value interface{}, api model.APIDefinitionInput, apisFromDB map[string]*model.APIDefinition, apiHashes map[string]uint64) error {
   854  	if value == nil {
   855  		return nil
   856  	}
   857  
   858  	if len(apisFromDB) == 0 {
   859  		return nil
   860  	}
   861  
   862  	apiFromDB, ok := apisFromDB[str.PtrStrToStr(api.OrdID)]
   863  	if !ok || isResourceHashMissing(apiFromDB.ResourceHash) {
   864  		return nil
   865  	}
   866  
   867  	hashDB := str.PtrStrToStr(apiFromDB.ResourceHash)
   868  	hashDoc := strconv.FormatUint(apiHashes[str.PtrStrToStr(api.OrdID)], 10)
   869  
   870  	return checkHashEquality(apiFromDB.Version.Value, api.VersionInput.Value, hashDB, hashDoc)
   871  }
   872  
   873  func normalizeAPIDefinition(api *model.APIDefinitionInput) (model.APIDefinitionInput, error) {
   874  	bytes, err := json.Marshal(api)
   875  	if err != nil {
   876  		return model.APIDefinitionInput{}, errors.Wrapf(err, "error while marshalling api definition with ID %s", str.PtrStrToStr(api.OrdID))
   877  	}
   878  
   879  	var normalizedAPIDefinition model.APIDefinitionInput
   880  	if err := json.Unmarshal(bytes, &normalizedAPIDefinition); err != nil {
   881  		return model.APIDefinitionInput{}, errors.Wrapf(err, "error while unmarshalling api definition with ID %s", str.PtrStrToStr(api.OrdID))
   882  	}
   883  
   884  	return normalizedAPIDefinition, nil
   885  }
   886  
   887  func normalizeEventDefinition(event *model.EventDefinitionInput) (model.EventDefinitionInput, error) {
   888  	bytes, err := json.Marshal(event)
   889  	if err != nil {
   890  		return model.EventDefinitionInput{}, errors.Wrapf(err, "error while marshalling event definition with ID %s", str.PtrStrToStr(event.OrdID))
   891  	}
   892  
   893  	var normalizedEventDefinition model.EventDefinitionInput
   894  	if err := json.Unmarshal(bytes, &normalizedEventDefinition); err != nil {
   895  		return model.EventDefinitionInput{}, errors.Wrapf(err, "error while unmarshalling event definition with ID %s", str.PtrStrToStr(event.OrdID))
   896  	}
   897  
   898  	return normalizedEventDefinition, nil
   899  }
   900  
   901  func normalizePackage(pkg *model.PackageInput) (model.PackageInput, error) {
   902  	bytes, err := json.Marshal(pkg)
   903  	if err != nil {
   904  		return model.PackageInput{}, errors.Wrapf(err, "error while marshalling package definition with ID %s", pkg.OrdID)
   905  	}
   906  
   907  	var normalizedPkgDefinition model.PackageInput
   908  	if err := json.Unmarshal(bytes, &normalizedPkgDefinition); err != nil {
   909  		return model.PackageInput{}, errors.Wrapf(err, "error while unmarshalling package definition with ID %s", pkg.OrdID)
   910  	}
   911  
   912  	return normalizedPkgDefinition, nil
   913  }
   914  
   915  func normalizeBundle(bndl *model.BundleCreateInput) (model.BundleCreateInput, error) {
   916  	bytes, err := json.Marshal(bndl)
   917  	if err != nil {
   918  		return model.BundleCreateInput{}, errors.Wrapf(err, "error while marshalling bundle definition with ID %v", bndl.OrdID)
   919  	}
   920  
   921  	var normalizedBndlDefinition model.BundleCreateInput
   922  	if err := json.Unmarshal(bytes, &normalizedBndlDefinition); err != nil {
   923  		return model.BundleCreateInput{}, errors.Wrapf(err, "error while unmarshalling bundle definition with ID %v", bndl.OrdID)
   924  	}
   925  
   926  	return normalizedBndlDefinition, nil
   927  }
   928  
   929  func isResourceHashMissing(hash *string) bool {
   930  	hashStr := str.PtrStrToStr(hash)
   931  	return hashStr == ""
   932  }
   933  
   934  func noNewLines(s string) bool {
   935  	return !strings.Contains(s, "\\n")
   936  }
   937  
   938  func validateWhenPolicyLevelIsSAP(packageOrdID *string, packagePolicyLevels map[string]string, validationFunc func() error) error {
   939  	pkgOrdID := str.PtrStrToStr(packageOrdID)
   940  	policyLevel := packagePolicyLevels[pkgOrdID]
   941  
   942  	if policyLevel != PolicyLevelSap {
   943  		return nil
   944  	}
   945  
   946  	return validationFunc()
   947  }
   948  
   949  func validateJSONArrayOfStringsContainsInMap(arr interface{}, validValues map[string]bool) error {
   950  	if arr == nil {
   951  		return nil
   952  	}
   953  
   954  	jsonArr, ok := arr.(json.RawMessage)
   955  	if !ok {
   956  		return errors.New("should be json")
   957  	}
   958  
   959  	if len(jsonArr) == 0 {
   960  		return nil
   961  	}
   962  
   963  	if !gjson.ValidBytes(jsonArr) {
   964  		return errors.New("should be valid json")
   965  	}
   966  
   967  	parsedArr := gjson.ParseBytes(jsonArr)
   968  	if !parsedArr.IsArray() {
   969  		return errors.New("should be json array")
   970  	}
   971  
   972  	for _, el := range parsedArr.Array() {
   973  		if el.Type != gjson.String {
   974  			return errors.New("should be array of strings")
   975  		}
   976  
   977  		exists, ok := validValues[el.String()]
   978  
   979  		if !exists || !ok {
   980  			return errors.New("array element is not in the list of valid values")
   981  		}
   982  	}
   983  
   984  	return nil
   985  }
   986  
   987  func validateJSONArrayOfStringsMatchPattern(arr interface{}, regexPattern *regexp.Regexp) error {
   988  	if arr == nil {
   989  		return nil
   990  	}
   991  
   992  	jsonArr, ok := arr.(json.RawMessage)
   993  	if !ok {
   994  		return errors.New("should be json")
   995  	}
   996  
   997  	if len(jsonArr) == 0 {
   998  		return nil
   999  	}
  1000  
  1001  	if !gjson.ValidBytes(jsonArr) {
  1002  		return errors.New("should be valid json")
  1003  	}
  1004  	parsedArr := gjson.ParseBytes(jsonArr)
  1005  
  1006  	if !parsedArr.IsArray() {
  1007  		return errors.New("should be json array")
  1008  	}
  1009  
  1010  	if len(parsedArr.Array()) == 0 {
  1011  		return nil
  1012  	}
  1013  
  1014  	for _, el := range parsedArr.Array() {
  1015  		if el.Type != gjson.String {
  1016  			return errors.New("should be array of strings")
  1017  		}
  1018  		if !regexPattern.MatchString(el.String()) {
  1019  			return errors.Errorf("elements should match %q", regexPattern.String())
  1020  		}
  1021  	}
  1022  	return nil
  1023  }
  1024  
  1025  func validateJSONArrayOfObjects(arr interface{}, elementFieldRules map[string][]validation.Rule, crossFieldRules ...func(gjson.Result) error) error {
  1026  	if arr == nil {
  1027  		return nil
  1028  	}
  1029  
  1030  	jsonArr, ok := arr.(json.RawMessage)
  1031  	if !ok {
  1032  		return errors.New("should be json")
  1033  	}
  1034  
  1035  	if len(jsonArr) == 0 {
  1036  		return nil
  1037  	}
  1038  
  1039  	if !gjson.ValidBytes(jsonArr) {
  1040  		return errors.New("should be valid json")
  1041  	}
  1042  
  1043  	parsedArr := gjson.ParseBytes(jsonArr)
  1044  	if !parsedArr.IsArray() {
  1045  		return errors.New("should be json array")
  1046  	}
  1047  
  1048  	if len(parsedArr.Array()) == 0 {
  1049  		return nil
  1050  	}
  1051  
  1052  	for _, el := range parsedArr.Array() {
  1053  		for field, rules := range elementFieldRules {
  1054  			if err := validation.Validate(el.Get(field).Value(), rules...); err != nil {
  1055  				return errors.Wrapf(err, "error validating field %s", field)
  1056  			}
  1057  			for _, f := range crossFieldRules {
  1058  				if err := f(el); err != nil {
  1059  					return err
  1060  				}
  1061  			}
  1062  		}
  1063  	}
  1064  
  1065  	return nil
  1066  }
  1067  
  1068  func validateJSONObjects(obj interface{}, elementFieldRules map[string][]validation.Rule, crossFieldRules ...func(gjson.Result) error) error {
  1069  	if obj == nil {
  1070  		return nil
  1071  	}
  1072  
  1073  	jsonObj, ok := obj.(json.RawMessage)
  1074  	if !ok {
  1075  		return errors.New("should be json")
  1076  	}
  1077  
  1078  	if len(jsonObj) == 0 {
  1079  		return nil
  1080  	}
  1081  
  1082  	if !gjson.ValidBytes(jsonObj) {
  1083  		return errors.New("should be valid json")
  1084  	}
  1085  
  1086  	parsedObj := gjson.ParseBytes(jsonObj)
  1087  	if !parsedObj.IsObject() {
  1088  		return errors.New("should be json object")
  1089  	}
  1090  
  1091  	for field, rules := range elementFieldRules {
  1092  		if err := validation.Validate(parsedObj.Get(field).Value(), rules...); err != nil {
  1093  			return errors.Wrapf(err, "error validating field %s", field)
  1094  		}
  1095  		for _, f := range crossFieldRules {
  1096  			if err := f(parsedObj); err != nil {
  1097  				return err
  1098  			}
  1099  		}
  1100  	}
  1101  
  1102  	return nil
  1103  }
  1104  
  1105  func validateCustomType(credentialExchangeStrategyTenantMappings map[string]CredentialExchangeStrategyTenantMapping) func(el gjson.Result) error {
  1106  	return func(el gjson.Result) error {
  1107  		if el.Get("customType").Exists() && el.Get("type").String() != custom {
  1108  			return errors.New("if customType is provided, type should be set to 'custom'")
  1109  		}
  1110  
  1111  		customType := el.Get("customType").String()
  1112  		if _, ok := credentialExchangeStrategyTenantMappings[customType]; strings.Contains(customType, TenantMappingCustomTypeIdentifier) && !ok {
  1113  			return errors.New("credential exchange strategy's tenant mapping customType is not valid")
  1114  		}
  1115  
  1116  		return validation.Validate(customType, validation.Match(regexp.MustCompile(CustomTypeCredentialExchangeStrategyRegex)))
  1117  	}
  1118  }
  1119  
  1120  func validateCustomDescription(el gjson.Result) error {
  1121  	if el.Get("customDescription").Exists() && el.Get("type").String() != custom {
  1122  		return errors.New("if customDescription is provided, type should be set to 'custom'")
  1123  	}
  1124  	if el.Get("customDescription").Exists() && el.Get("type").String() == custom && (len(el.Get("customDescription").String()) < MinDescriptionLength || len(el.Get("customDescription").String()) > MaxDescriptionLength) {
  1125  		return errors.New(fmt.Sprintf("if customDescription is provided and type is 'custom', then the accepted length of customDescription is between %d - %d characters", MinDescriptionLength, MaxDescriptionLength))
  1126  	}
  1127  	return nil
  1128  }
  1129  
  1130  func validateEventPartOfConsumptionBundles(value interface{}, regexPattern *regexp.Regexp) error {
  1131  	bundleReferences, ok := value.([]*model.ConsumptionBundleReference)
  1132  	if !ok {
  1133  		return errors.New("error while casting to ConsumptionBundleReference")
  1134  	}
  1135  
  1136  	if bundleReferences != nil && len(bundleReferences) == 0 {
  1137  		return errors.New("bundleReference should not be empty if present")
  1138  	}
  1139  
  1140  	bundleIDsPerEvent := make(map[string]bool)
  1141  	for _, br := range bundleReferences {
  1142  		if br.BundleOrdID == "" {
  1143  			return errors.New("bundleReference ordId is mandatory field")
  1144  		}
  1145  
  1146  		if !regexPattern.MatchString(br.BundleOrdID) {
  1147  			return errors.Errorf("ordId should match %q", regexPattern.String())
  1148  		}
  1149  
  1150  		if isPresent := bundleIDsPerEvent[br.BundleOrdID]; !isPresent {
  1151  			bundleIDsPerEvent[br.BundleOrdID] = true
  1152  		} else {
  1153  			return errors.Errorf("event can not reference the same bundle with ordId %q more than once", br.BundleOrdID)
  1154  		}
  1155  
  1156  		if br.DefaultTargetURL != "" {
  1157  			return errors.New("events are not supposed to have defaultEntryPoint")
  1158  		}
  1159  	}
  1160  	return nil
  1161  }
  1162  
  1163  func validateAPIPartOfConsumptionBundles(value interface{}, targetURLs json.RawMessage, regexPattern *regexp.Regexp) error {
  1164  	bundleReferences, ok := value.([]*model.ConsumptionBundleReference)
  1165  	if !ok {
  1166  		return errors.New("error while casting to ConsumptionBundleReference")
  1167  	}
  1168  
  1169  	if bundleReferences != nil && len(bundleReferences) == 0 {
  1170  		return errors.New("bundleReference should not be empty if present")
  1171  	}
  1172  
  1173  	bundleIDsPerAPI := make(map[string]bool)
  1174  	for _, br := range bundleReferences {
  1175  		if br.BundleOrdID == "" {
  1176  			return errors.New("bundleReference ordId is mandatory field")
  1177  		}
  1178  
  1179  		if !regexPattern.MatchString(br.BundleOrdID) {
  1180  			return errors.Errorf("ordId should match %q", regexPattern.String())
  1181  		}
  1182  
  1183  		if isPresent := bundleIDsPerAPI[br.BundleOrdID]; !isPresent {
  1184  			bundleIDsPerAPI[br.BundleOrdID] = true
  1185  		} else {
  1186  			return errors.Errorf("api can not reference the same bundle with ordId %q more than once", br.BundleOrdID)
  1187  		}
  1188  
  1189  		err := validation.Validate(br.DefaultTargetURL, is.RequestURI)
  1190  		if err != nil {
  1191  			return errors.New("defaultEntryPoint should be a valid URI")
  1192  		}
  1193  
  1194  		lenTargetURLs := len(gjson.ParseBytes(targetURLs).Array())
  1195  		if br.DefaultTargetURL != "" && lenTargetURLs <= 1 {
  1196  			return errors.New("defaultEntryPoint must only be provided if an API has more than one entry point")
  1197  		}
  1198  
  1199  		if br.DefaultTargetURL != "" && lenTargetURLs > 1 {
  1200  			if isDefaultTargetURLMissingFromTargetURLs(br.DefaultTargetURL, targetURLs) {
  1201  				return errors.New("defaultEntryPoint must be in the list of entryPoints for the given API")
  1202  			}
  1203  		}
  1204  	}
  1205  
  1206  	return nil
  1207  }
  1208  
  1209  func validateDefaultConsumptionBundle(value interface{}, partOfConsumptionBundles []*model.ConsumptionBundleReference) error {
  1210  	defaultConsumptionBundle, ok := value.(*string)
  1211  	if !ok {
  1212  		return errors.New(fmt.Sprintf("expected string value for defaultConsumptionBundle, found %T", value))
  1213  	}
  1214  
  1215  	if defaultConsumptionBundle == nil {
  1216  		return nil
  1217  	}
  1218  
  1219  	var isFound bool
  1220  	for _, bundleRef := range partOfConsumptionBundles {
  1221  		if *defaultConsumptionBundle == bundleRef.BundleOrdID {
  1222  			isFound = true
  1223  			break
  1224  		}
  1225  	}
  1226  
  1227  	if !isFound {
  1228  		return errors.New("defaultConsumptionBundle must be an existing option in the corresponding partOfConsumptionBundles array")
  1229  	}
  1230  	return nil
  1231  }
  1232  
  1233  func isDefaultTargetURLMissingFromTargetURLs(defaultTargetURL string, targetURLs json.RawMessage) bool {
  1234  	for _, targetURL := range gjson.ParseBytes(targetURLs).Array() {
  1235  		if targetURL.String() == defaultTargetURL {
  1236  			return false
  1237  		}
  1238  	}
  1239  	return true
  1240  }
  1241  
  1242  func areThereEntryPointDuplicates(entryPoints []gjson.Result) bool {
  1243  	if len(entryPoints) <= 1 {
  1244  		return false
  1245  	}
  1246  
  1247  	seen := make(map[string]bool)
  1248  	for _, val := range entryPoints {
  1249  		if seen[val.String()] {
  1250  			return true
  1251  		}
  1252  		seen[val.String()] = true
  1253  	}
  1254  	return false
  1255  }
  1256  
  1257  func isValidDate(d interface{}) error {
  1258  	var err error
  1259  	date, err := castDate(d)
  1260  	if err != nil {
  1261  		return err
  1262  	}
  1263  
  1264  	if _, err = time.Parse(time.RFC3339, date); err == nil { // RFC3339 -> "2006-01-02T15:04:05Z" or "2006-01-02T15:04:05+07:00"
  1265  		return nil
  1266  	} else if _, err = time.Parse("2006-01-02", date); err == nil { // RFC3339 date without time extension
  1267  		return nil
  1268  	} else if _, err = time.Parse("2006-01-02T15:04:05", date); err == nil { // ISO8601 without Z/00+00
  1269  		return nil
  1270  	} else if _, err = time.Parse("2006-01-02T15:04:05Z0700", date); err == nil { // ISO8601 with skipped ':' in offset (e.g.: 2006-01-02T15:04:05+0700)
  1271  		return nil
  1272  	}
  1273  	return errors.New("invalid date")
  1274  }
  1275  
  1276  func castDate(d interface{}) (string, error) {
  1277  	datePtr, ok := d.(*string)
  1278  	if ok {
  1279  		return *datePtr, nil
  1280  	}
  1281  
  1282  	date, ok := d.(string)
  1283  	if ok {
  1284  		return date, nil
  1285  	}
  1286  
  1287  	return "", errors.New(fmt.Sprintf("expected string or *string value for date, found %T", d))
  1288  }
  1289  
  1290  func notPartOfConsumptionBundles(partOfConsumptionBundles []*model.ConsumptionBundleReference) validation.RuleFunc {
  1291  	return func(value interface{}) error {
  1292  		if len(partOfConsumptionBundles) > 0 {
  1293  			return errors.New("api without entry points can not be part of consumption bundle")
  1294  		}
  1295  		return nil
  1296  	}
  1297  }
  1298  
  1299  func validateExtensibleField(value interface{}, ordPackageID *string, packagePolicyLevels map[string]string) error {
  1300  	pkgOrdID := str.PtrStrToStr(ordPackageID)
  1301  	policyLevel := packagePolicyLevels[pkgOrdID]
  1302  
  1303  	if (policyLevel == PolicyLevelSap || policyLevel == PolicyLevelSapPartner) && (value == nil || value.(json.RawMessage) == nil) {
  1304  		return errors.Errorf("`extensible` field must be provided when `policyLevel` is either `%s` or `%s`", PolicyLevelSap, PolicyLevelSapPartner)
  1305  	}
  1306  
  1307  	return validateJSONObjects(value, map[string][]validation.Rule{
  1308  		"supported": {
  1309  			validation.Required,
  1310  			validation.In("no", "manual", "automatic"),
  1311  		},
  1312  		"description": {},
  1313  	}, validateExtensibleInnerFields)
  1314  }
  1315  
  1316  func validateExtensibleInnerFields(el gjson.Result) error {
  1317  	supportedProperty := el.Get("supported")
  1318  	supportedValue, ok := supportedProperty.Value().(string)
  1319  	if !ok {
  1320  		return errors.New("`supported` value not provided")
  1321  	}
  1322  
  1323  	descriptionProperty := el.Get("description")
  1324  	descriptionValue, ok := descriptionProperty.Value().(string)
  1325  	validLength := len(descriptionValue) >= MinDescriptionLength && len(descriptionValue) <= MaxDescriptionLength
  1326  
  1327  	if supportedProperty.Exists() && (supportedValue == "manual" || supportedValue == "automatic") && (!validLength || !ok) {
  1328  		return errors.New(fmt.Sprintf("if supported field is either 'manual' or 'automatic', description should be provided with length of %d - %d characters", MinDescriptionLength, MaxDescriptionLength))
  1329  	}
  1330  	return nil
  1331  }
  1332  
  1333  // HashObject hashes the given object
  1334  func HashObject(obj interface{}) (uint64, error) {
  1335  	hash, err := hashstructure.Hash(obj, hashstructure.FormatV2, &hashstructure.HashOptions{SlicesAsSets: true})
  1336  	if err != nil {
  1337  		return 0, errors.New("failed to hash the given object")
  1338  	}
  1339  
  1340  	return hash, nil
  1341  }
  1342  
  1343  func checkHashEquality(rdFromDBVersion, rdFromDocVersion, hashFromDB, hashFromDoc string) error {
  1344  	rdFromDBVersion = fmt.Sprintf("v%s", rdFromDBVersion)
  1345  	rdFromDocVersion = fmt.Sprintf("v%s", rdFromDocVersion)
  1346  
  1347  	areVersionsEqual := semver.Compare(rdFromDocVersion, rdFromDBVersion)
  1348  	if areHashesEqual := cmp.Equal(hashFromDB, hashFromDoc); !areHashesEqual && areVersionsEqual <= 0 {
  1349  		return errors.New("there is a change in the resource; version value should be incremented")
  1350  	}
  1351  
  1352  	return nil
  1353  }