From 009b180c5a1869f66bbeac460fd80fe601911306 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Tue, 17 Oct 2023 12:24:27 +0100 Subject: [PATCH] fix up URL/URI validation --- .../application_registration_resource.go | 12 +- .../applications/application_resource.go | 150 +++++++++--------- .../invitations/invitation_resource.go | 10 +- .../service_principal_resource.go | 8 +- internal/tf/validation/uri.go | 103 ++++-------- internal/tf/validation/uri_test.go | 39 +++-- 6 files changed, 143 insertions(+), 179 deletions(-) diff --git a/internal/services/applications/application_registration_resource.go b/internal/services/applications/application_registration_resource.go index 09ba450c5..42b34c3a1 100644 --- a/internal/services/applications/application_registration_resource.go +++ b/internal/services/applications/application_registration_resource.go @@ -76,21 +76,21 @@ func (r ApplicationRegistrationResource) Arguments() map[string]*pluginsdk.Schem Description: "URL of the home page for the application", Type: pluginsdk.TypeString, Optional: true, - ValidateFunc: validation.StringIsNotEmpty, + ValidateFunc: validation.IsHttpOrHttpsUrl, }, "logout_url": { Description: "URL of the logout page for the application, where the session is cleared for single sign-out", Type: pluginsdk.TypeString, Optional: true, - ValidateFunc: validation.StringIsNotEmpty, + ValidateFunc: validation.IsLogoutUrl, }, "marketing_url": { Description: "URL of the marketing page for the application", Type: pluginsdk.TypeString, Optional: true, - ValidateFunc: validation.StringIsNotEmpty, + ValidateFunc: validation.IsHttpOrHttpsUrl, }, "notes": { @@ -104,7 +104,7 @@ func (r ApplicationRegistrationResource) Arguments() map[string]*pluginsdk.Schem Description: "URL of the privacy statement for the application", Type: pluginsdk.TypeString, Optional: true, - ValidateFunc: validation.StringIsNotEmpty, + ValidateFunc: validation.IsHttpOrHttpsUrl, }, "requested_access_token_version": { @@ -149,14 +149,14 @@ func (r ApplicationRegistrationResource) Arguments() map[string]*pluginsdk.Schem Description: "URL of the support page for the application", Type: pluginsdk.TypeString, Optional: true, - ValidateFunc: validation.StringIsNotEmpty, + ValidateFunc: validation.IsHttpOrHttpsUrl, }, "terms_of_service_url": { Description: "URL of the terms of service statement for the application", Type: pluginsdk.TypeString, Optional: true, - ValidateFunc: validation.StringIsNotEmpty, + ValidateFunc: validation.IsHttpOrHttpsUrl, }, } } diff --git a/internal/services/applications/application_resource.go b/internal/services/applications/application_resource.go index 1ffcf8eb5..5192a183b 100644 --- a/internal/services/applications/application_resource.go +++ b/internal/services/applications/application_resource.go @@ -61,10 +61,10 @@ func applicationResource() *pluginsdk.Resource { Schema: map[string]*pluginsdk.Schema{ "display_name": { - Description: "The display name for the application", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + Description: "The display name for the application", + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, }, "api": { @@ -79,8 +79,8 @@ func applicationResource() *pluginsdk.Resource { Type: pluginsdk.TypeSet, Optional: true, Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsUUID, }, }, @@ -97,24 +97,24 @@ func applicationResource() *pluginsdk.Resource { Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ "id": { - Description: "The unique identifier of the delegated permission", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + Description: "The unique identifier of the delegated permission", + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.IsUUID, }, "admin_consent_description": { - Description: "Delegated permission description that appears in all tenant-wide admin consent experiences, intended to be read by an administrator granting the permission on behalf of all users", - Type: pluginsdk.TypeString, - Optional: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + Description: "Delegated permission description that appears in all tenant-wide admin consent experiences, intended to be read by an administrator granting the permission on behalf of all users", + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, }, "admin_consent_display_name": { - Description: "Display name for the delegated permission, intended to be read by an administrator granting the permission on behalf of all users", - Type: pluginsdk.TypeString, - Optional: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + Description: "Display name for the delegated permission, intended to be read by an administrator granting the permission on behalf of all users", + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, }, "enabled": { @@ -136,17 +136,17 @@ func applicationResource() *pluginsdk.Resource { }, "user_consent_description": { - Description: "Delegated permission description that appears in the end user consent experience, intended to be read by a user consenting on their own behalf", - Type: pluginsdk.TypeString, - Optional: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + Description: "Delegated permission description that appears in the end user consent experience, intended to be read by a user consenting on their own behalf", + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, }, "user_consent_display_name": { - Description: "Display name for the delegated permission that appears in the end user consent experience", - Type: pluginsdk.TypeString, - Optional: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + Description: "Display name for the delegated permission that appears in the end user consent experience", + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, }, "value": { @@ -194,10 +194,10 @@ func applicationResource() *pluginsdk.Resource { Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ "id": { - Description: "The unique identifier of the app role", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + Description: "The unique identifier of the app role", + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.IsUUID, }, "allowed_member_types": { @@ -217,17 +217,17 @@ func applicationResource() *pluginsdk.Resource { }, "description": { - Description: "Description of the app role that appears when the role is being assigned and, if the role functions as an application permissions, during the consent experiences", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + Description: "Description of the app role that appears when the role is being assigned and, if the role functions as an application permissions, during the consent experiences", + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, }, "display_name": { - Description: "Display name for the app role that appears during app role assignment and in consent experiences", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + Description: "Display name for the app role that appears during app role assignment and in consent experiences", + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, }, "enabled": { @@ -257,10 +257,10 @@ func applicationResource() *pluginsdk.Resource { }, "description": { - Description: "Description of the application as shown to end users", - Type: pluginsdk.TypeString, - Optional: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringLenBetween(0, 1024)), + Description: "Description of the application as shown to end users", + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 1024), }, "device_only_auth_enabled": { @@ -331,8 +331,8 @@ func applicationResource() *pluginsdk.Resource { Type: pluginsdk.TypeSet, Optional: true, Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - ValidateDiagFunc: validation.IsAppUri, + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsAppUri, }, }, @@ -350,10 +350,10 @@ func applicationResource() *pluginsdk.Resource { }, "notes": { - Description: "User-specified notes relevant for the management of the application", - Type: pluginsdk.TypeString, - Optional: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + Description: "User-specified notes relevant for the management of the application", + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, }, // This is a top level attribute because d.SetNewComputed() doesn't work inside a block @@ -393,8 +393,8 @@ func applicationResource() *pluginsdk.Resource { Set: pluginsdk.HashString, MaxItems: 100, Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsUUID, }, }, @@ -417,8 +417,8 @@ func applicationResource() *pluginsdk.Resource { Optional: true, MaxItems: 256, Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - ValidateDiagFunc: validation.IsRedirectUriFunc(true, true), + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsRedirectUriFunc(true, true), }, }, }, @@ -443,10 +443,10 @@ func applicationResource() *pluginsdk.Resource { Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ "id": { - Description: "", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + Description: "", + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.IsUUID, }, "type": { @@ -500,8 +500,8 @@ func applicationResource() *pluginsdk.Resource { Optional: true, MaxItems: 256, Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - ValidateDiagFunc: validation.IsRedirectUriFunc(false, false), + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsRedirectUriFunc(false, false), }, }, }, @@ -527,12 +527,12 @@ func applicationResource() *pluginsdk.Resource { }, "template_id": { - Description: "Unique ID of the application template from which this application is created", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + Description: "Unique ID of the application template from which this application is created", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.IsUUID, }, "terms_of_service_url": { @@ -549,17 +549,17 @@ func applicationResource() *pluginsdk.Resource { Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ "homepage_url": { - Description: "Home page or landing page of the application", - Type: pluginsdk.TypeString, - Optional: true, - ValidateDiagFunc: validation.IsHttpOrHttpsUrl, + Description: "Home page or landing page of the application", + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.IsHttpOrHttpsUrl, }, "logout_url": { - Description: "The URL that will be used by Microsoft's authorization service to sign out a user using front-channel, back-channel or SAML logout protocols", - Type: pluginsdk.TypeString, - Optional: true, - ValidateDiagFunc: validation.IsLogoutUrl, + Description: "The URL that will be used by Microsoft's authorization service to sign out a user using front-channel, back-channel or SAML logout protocols", + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.IsLogoutUrl, }, "redirect_uris": { @@ -568,8 +568,8 @@ func applicationResource() *pluginsdk.Resource { Optional: true, MaxItems: 256, Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - ValidateDiagFunc: validation.IsRedirectUriFunc(true, false), + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsRedirectUriFunc(true, false), }, }, @@ -730,7 +730,7 @@ func applicationResourceCustomizeDiff(ctx context.Context, diff *pluginsdk.Resou } // urn scheme not supported with personal account sign-ins for _, v := range identifierUris { - if diags := validation.IsUriFunc([]string{"http", "https", "api", "ms-appx"}, false, false, false)(v, cty.Path{}); diags.HasError() { + if _, errs := validation.IsUriFunc([]string{"http", "https", "api", "ms-appx"}, false, false, false)(v, "identifier_uris"); len(errs) > 0 { return fmt.Errorf("`identifier_uris` is invalid. The URN scheme is not supported when `sign_in_audience` is %q or %q", msgraph.SignInAudienceAzureADandPersonalMicrosoftAccount, msgraph.SignInAudiencePersonalMicrosoftAccount) } diff --git a/internal/services/invitations/invitation_resource.go b/internal/services/invitations/invitation_resource.go index 7d3975ad8..786a4ed54 100644 --- a/internal/services/invitations/invitation_resource.go +++ b/internal/services/invitations/invitation_resource.go @@ -36,11 +36,11 @@ func invitationResource() *pluginsdk.Resource { Schema: map[string]*pluginsdk.Schema{ "redirect_url": { - Description: "The URL that the user should be redirected to once the invitation is redeemed", - Type: pluginsdk.TypeString, - Required: true, - ForceNew: true, - ValidateDiagFunc: validation.IsHttpOrHttpsUrl, + Description: "The URL that the user should be redirected to once the invitation is redeemed", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.IsHttpOrHttpsUrl, }, "user_email_address": { diff --git a/internal/services/serviceprincipals/service_principal_resource.go b/internal/services/serviceprincipals/service_principal_resource.go index 2a6d1e0e4..d2270d887 100644 --- a/internal/services/serviceprincipals/service_principal_resource.go +++ b/internal/services/serviceprincipals/service_principal_resource.go @@ -158,10 +158,10 @@ func servicePrincipalResource() *pluginsdk.Resource { }, "login_url": { - Description: "The URL where the service provider redirects the user to Azure AD to authenticate. Azure AD uses the URL to launch the application from Microsoft 365 or the Azure AD My Apps. When blank, Azure AD performs IdP-initiated sign-on for applications configured with SAML-based single sign-on", - Type: pluginsdk.TypeString, - Optional: true, - ValidateDiagFunc: validation.IsHttpOrHttpsUrl, + Description: "The URL where the service provider redirects the user to Azure AD to authenticate. Azure AD uses the URL to launch the application from Microsoft 365 or the Azure AD My Apps. When blank, Azure AD performs IdP-initiated sign-on for applications configured with SAML-based single sign-on", + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.IsHttpOrHttpsUrl, }, "notes": { diff --git a/internal/tf/validation/uri.go b/internal/tf/validation/uri.go index be59b9ad3..f45bdbb46 100644 --- a/internal/tf/validation/uri.go +++ b/internal/tf/validation/uri.go @@ -8,147 +8,100 @@ import ( "net/url" "strings" - "github.com/hashicorp/go-cty/cty" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" ) -func IsAppUri(i interface{}, path cty.Path) diag.Diagnostics { - return IsUriFunc([]string{"http", "https", "api", "ms-appx"}, true, false, false)(i, path) +func IsAppUri(i interface{}, k string) (warnings []string, errors []error) { + return IsUriFunc([]string{"http", "https", "api", "ms-appx"}, true, false, false)(i, k) } -func IsHttpOrHttpsUrl(i interface{}, path cty.Path) diag.Diagnostics { - return IsUriFunc([]string{"http", "https"}, false, true, false)(i, path) +func IsHttpOrHttpsUrl(i interface{}, k string) (warnings []string, errors []error) { + return IsUriFunc([]string{"http", "https"}, false, true, false)(i, k) } -func IsHttpsUrl(i interface{}, path cty.Path) diag.Diagnostics { - return IsUriFunc([]string{"https"}, false, true, false)(i, path) +func IsHttpsUrl(i interface{}, k string) (warnings []string, errors []error) { + return IsUriFunc([]string{"https"}, false, true, false)(i, k) } -func IsLogoutUrl(i interface{}, path cty.Path) (ret diag.Diagnostics) { - ret = IsUriFunc([]string{"http", "https"}, false, true, false)(i, path) - if len(ret) > 0 { +func IsLogoutUrl(i interface{}, k string) (warnings []string, errors []error) { + warnings, errors = IsUriFunc([]string{"http", "https"}, false, true, false)(i, k) + if len(errors) > 0 { return } if len(i.(string)) > 255 { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: "URL must be 255 characters or less", - AttributePath: path, - }) + errors = append(errors, fmt.Errorf("URL must be 255 characters or less for %q", k)) } return } -func IsRedirectUriFunc(urnAllowed bool, publicClient bool) schema.SchemaValidateDiagFunc { - return func(i interface{}, path cty.Path) (ret diag.Diagnostics) { +func IsRedirectUriFunc(urnAllowed bool, publicClient bool) pluginsdk.SchemaValidateFunc { + return func(i interface{}, k string) (warnings []string, errors []error) { // See https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#register-the-proxyidentityexperienceframework-application var allowedSchemes []string if !publicClient { allowedSchemes = []string{"http", "https", "ms-appx-web"} } - ret = IsUriFunc(allowedSchemes, urnAllowed, true, true)(i, path) - if len(ret) > 0 { + warnings, errors = IsUriFunc(allowedSchemes, urnAllowed, true, true)(i, k) + if len(errors) > 0 { return } if len(i.(string)) > 256 { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: "URI must be 256 characters or less", - AttributePath: path, - }) + errors = append(errors, fmt.Errorf("URI must be 256 characters or less for %q", k)) } return } } -func IsUriFunc(validURLSchemes []string, urnAllowed bool, allowTrailingSlash bool, forceTrailingSlash bool) schema.SchemaValidateDiagFunc { - return func(i interface{}, path cty.Path) (ret diag.Diagnostics) { +func IsUriFunc(validURLSchemes []string, urnAllowed bool, allowTrailingSlash bool, forceTrailingSlash bool) pluginsdk.SchemaValidateFunc { + return func(i interface{}, k string) ([]string, []error) { v, ok := i.(string) if !ok { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Expected a string value", - AttributePath: path, - }) - return + return nil, []error{fmt.Errorf("expected a string value for %q", k)} } if v == "" { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: "URI must not be empty", - AttributePath: path, - }) - return + return nil, []error{fmt.Errorf("URI must not be empty for %q", k)} } if urnAllowed { parts := strings.Split(v, ":") if len(parts) >= 3 && parts[0] == "urn" { - return + return nil, nil } } u, err := url.Parse(v) if err != nil { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: "URI is in an invalid format", - Detail: err.Error(), - AttributePath: path, - }) - return + return nil, []error{fmt.Errorf("URI is in an invalid format for %q", k)} } if !allowTrailingSlash && u.Path == "/" { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: "URI must not have a trailing slash when there is no path segment", - AttributePath: path, - }) - return + return nil, []error{fmt.Errorf("URI must not have a trailing slash when there is no path segment for %q", k)} } if u.Host == "" { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: "URI has no host", - AttributePath: path, - }) - return + return nil, []error{fmt.Errorf("URI has no host for %q", k)} } if validURLSchemes == nil { - return + return nil, nil } if forceTrailingSlash && u.Path == "" { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: "URI must have a trailing slash when there is no path segment", - AttributePath: path, - }) - return + return nil, []error{fmt.Errorf("URI must have a trailing slash when there is no path segment for %q", k)} } for _, s := range validURLSchemes { if u.Scheme == s { - return + return nil, nil } } - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("Expected URI to have a scheme of: %s", strings.Join(validURLSchemes, ", ")), - AttributePath: path, - }) - - return + return nil, []error{fmt.Errorf("unexpected URI scheme for %q, expected one of: %s", k, strings.Join(validURLSchemes, ", "))} } } diff --git a/internal/tf/validation/uri_test.go b/internal/tf/validation/uri_test.go index 8d1b1c5e9..ed44e4d3d 100644 --- a/internal/tf/validation/uri_test.go +++ b/internal/tf/validation/uri_test.go @@ -5,8 +5,6 @@ package validation import ( "testing" - - "github.com/hashicorp/go-cty/cty" ) func TestIsHTTPSURL(t *testing.T) { @@ -42,10 +40,13 @@ func TestIsHTTPSURL(t *testing.T) { for _, tc := range cases { t.Run(tc.Url, func(t *testing.T) { - diags := IsHttpsUrl(tc.Url, cty.Path{}) + warnings, errors := IsHttpsUrl(tc.Url, "test") - if len(diags) != tc.Errors { - t.Fatalf("Expected URLIsHTTPS to have %d not %d errors for %q", tc.Errors, len(diags), tc.Url) + if len(warnings) > 0 { + t.Fatalf("Expected URLIsHTTPS to have 0 not %d warnings for %q", len(warnings), tc.Url) + } + if len(errors) != tc.Errors { + t.Fatalf("Expected URLIsHTTPS to have %d not %d errors for %q", tc.Errors, len(errors), tc.Url) } }) } @@ -84,10 +85,13 @@ func TestIsHTTPOrHTTPSURL(t *testing.T) { for _, tc := range cases { t.Run(tc.Url, func(t *testing.T) { - diags := IsHttpOrHttpsUrl(tc.Url, cty.Path{}) + warnings, errors := IsHttpOrHttpsUrl(tc.Url, "test") - if len(diags) != tc.Errors { - t.Fatalf("Expected URLIsHTTPOrHTTPS to have %d not %d errors for %q", tc.Errors, len(diags), tc.Url) + if len(warnings) > 0 { + t.Fatalf("Expected URLIsHTTPOrHTTPS to have 0 not %d warnings for %q", len(warnings), tc.Url) + } + if len(errors) != tc.Errors { + t.Fatalf("Expected URLIsHTTPOrHTTPS to have %d not %d errors for %q", tc.Errors, len(errors), tc.Url) } }) } @@ -142,10 +146,13 @@ func TestIsAppURI(t *testing.T) { for _, tc := range cases { t.Run(tc.Url, func(t *testing.T) { - diags := IsAppUri(tc.Url, cty.Path{}) + warnings, errors := IsAppUri(tc.Url, "test") - if len(diags) != tc.Errors { - t.Fatalf("Expected URLIsAppURI to have %d not %d errors for %q", tc.Errors, len(diags), tc.Url) + if len(warnings) > 0 { + t.Fatalf("Expected URLIsAppURI to have 0 not %d warnings for %q", len(warnings), tc.Url) + } + if len(errors) != tc.Errors { + t.Fatalf("Expected URLIsAppURI to have %d not %d errors for %q", tc.Errors, len(errors), tc.Url) } }) } @@ -201,9 +208,13 @@ func TestIsUriFunc(t *testing.T) { for _, tc := range cases { t.Run(tc.TestName, func(t *testing.T) { - diags := IsUriFunc(tc.Schemes, tc.UrnAllowed, tc.AllowTrailingSlash, tc.ForceTrailingSlash) - if len(diags(tc.Url, cty.Path{})) != tc.Errors { - t.Fatalf("Expected IsUriFunc to have %d errors for %v", tc.Errors, tc.Url) + warnings, errors := IsUriFunc(tc.Schemes, tc.UrnAllowed, tc.AllowTrailingSlash, tc.ForceTrailingSlash)(tc.Url, "test") + + if len(warnings) > 0 { + t.Fatalf("Expected IsUriFunc() to have 0 not %d warnings for %q", len(warnings), tc.Url) + } + if len(errors) != tc.Errors { + t.Fatalf("Expected IsUriFunc() to have %d not %d errors for %v", tc.Errors, len(errors), tc.Url) } }) }