Repository: kubernetes-sigs/ingress2gateway (go)

Issue:
[kong] Convert `ImplementationSpecific` path type
<!-- Please only use this template for submitting enhancement requests -->

**What would you like to be added**:

Support `ImplementationSpecific` path conversion for Kong provider. The `ImplementationSpecific` path type should be converted into `RegularExpression` within Kong provider conversion functions.


Pull request:
feat: kong implementationSpecific pathType
**What type of PR is this?**
<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind test

Optionally add one or more of the following kinds if applicable:
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->

/kind feature

**What this PR does / why we need it**:

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #88

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, please enter a release note below:
-->
```release-note
Kong supports `ImplementationSpecific` as `PathType` and converts it into `RegularExpression.`
```


Patch:
diff --git a/PROVIDER.md b/PROVIDER.md
index ee867ac..4340e87 100644
--- a/PROVIDER.md
+++ b/PROVIDER.md
@@ -61,7 +61,10 @@ func (r *resourceReader) ReadResourcesFromFiles(ctx context.Context, customResou
 ```
 3. Create a struct named `converter` which implements the `ResourceConverter` interface in a file named `converter.go`.
 The implemented `ToGatewayAPI` function should simply call every registered `featureParser` function, one by one.
-Take a look at `ingressnginx/converter.go` for example.
+Take a look at `ingressnginx/converter.go` for an example.
+The `ImplementationSpecificOptions` struct contains the handlers to customize native ingress implementation-specific fields.
+Take a look at `kong/converter.go` for an example.
+
 ```go
 package examplegateway
 
@@ -74,6 +77,7 @@ type converter struct {
 	conf *i2gw.ProviderConf
 
 	featureParsers []i2gw.FeatureParser
+	implementationSpecificOptions i2gw.ProviderImplementationSpecificOptions
 }
 
 // newConverter returns an ingress-nginx converter instance.
@@ -83,6 +87,9 @@ func newConverter(conf *i2gw.ProviderConf) *converter {
 		featureParsers: []i2gw.FeatureParser{
 			// The list of feature parsers comes here.
 		},
+		implementationSpecificOptions: i2gw.ProviderImplementationSpecificOptions{
+			// The list of the implementationSpecific ingress fields options comes here.
+		},
 	}
 }
 ```
diff --git a/pkg/i2gw/provider.go b/pkg/i2gw/provider.go
index 6dcf9e8..215b04c 100644
--- a/pkg/i2gw/provider.go
+++ b/pkg/i2gw/provider.go
@@ -73,6 +73,17 @@ type ResourceConverter interface {
 	ToGatewayAPI(resources InputResources) (GatewayResources, field.ErrorList)
 }
 
+// ImplementationSpecificHTTPPathTypeMatchConverter is an option to customize the ingress implementationSpecific
+// match type conversion.
+type ImplementationSpecificHTTPPathTypeMatchConverter func(*gatewayv1beta1.HTTPPathMatch)
+
+// ProviderImplementationSpecificOptions contains customized implementation-specific fields and functions.
+// These will be used by the common package to customize the provider-specific behavior for all the
+// implementation-specific fields of the ingress API.
+type ProviderImplementationSpecificOptions struct {
+	ToImplementationSpecificHTTPPathTypeMatch ImplementationSpecificHTTPPathTypeMatchConverter
+}
+
 // InputResources contains all Ingress objects, and Provider specific
 // custom resources.
 type InputResources struct {
diff --git a/pkg/i2gw/providers/common/converter.go b/pkg/i2gw/providers/common/converter.go
index 6ce814e..f3ffe35 100644
--- a/pkg/i2gw/providers/common/converter.go
+++ b/pkg/i2gw/providers/common/converter.go
@@ -32,7 +32,7 @@ import (
 
 // ToGateway converts the received ingresses to i2gw.GatewayResources,
 // without taking into consideration any provider specific logic.
-func ToGateway(ingresses []networkingv1.Ingress) (i2gw.GatewayResources, field.ErrorList) {
+func ToGateway(ingresses []networkingv1.Ingress, options i2gw.ProviderImplementationSpecificOptions) (i2gw.GatewayResources, field.ErrorList) {
 	aggregator := ingressAggregator{ruleGroups: map[ruleGroupKey]*ingressRuleGroup{}}
 
 	var errs field.ErrorList
@@ -43,7 +43,7 @@ func ToGateway(ingresses []networkingv1.Ingress) (i2gw.GatewayResources, field.E
 		return i2gw.GatewayResources{}, errs
 	}
 
-	routes, gateways, errs := aggregator.toHTTPRoutesAndGateways()
+	routes, gateways, errs := aggregator.toHTTPRoutesAndGateways(options)
 	if len(errs) > 0 {
 		return i2gw.GatewayResources{}, errs
 	}
@@ -157,7 +157,7 @@ func (a *ingressAggregator) addIngressRule(namespace, name, ingressClass string,
 	rg.rules = append(rg.rules, ingressRule{rule: rule})
 }
 
-func (a *ingressAggregator) toHTTPRoutesAndGateways() ([]gatewayv1beta1.HTTPRoute, []gatewayv1beta1.Gateway, field.ErrorList) {
+func (a *ingressAggregator) toHTTPRoutesAndGateways(options i2gw.ProviderImplementationSpecificOptions) ([]gatewayv1beta1.HTTPRoute, []gatewayv1beta1.Gateway, field.ErrorList) {
 	var httpRoutes []gatewayv1beta1.HTTPRoute
 	var errors field.ErrorList
 	listenersByNamespacedGateway := map[string][]gatewayv1beta1.Listener{}
@@ -178,7 +178,7 @@ func (a *ingressAggregator) toHTTPRoutesAndGateways() ([]gatewayv1beta1.HTTPRout
 		}
 		gwKey := fmt.Sprintf("%s/%s", rg.namespace, rg.ingressClass)
 		listenersByNamespacedGateway[gwKey] = append(listenersByNamespacedGateway[gwKey], listener)
-		httpRoute, errs := rg.toHTTPRoute()
+		httpRoute, errs := rg.toHTTPRoute(options)
 		httpRoutes = append(httpRoutes, httpRoute)
 		errors = append(errors, errs...)
 	}
@@ -269,7 +269,7 @@ func (a *ingressAggregator) toHTTPRoutesAndGateways() ([]gatewayv1beta1.HTTPRout
 	return httpRoutes, gateways, errors
 }
 
-func (rg *ingressRuleGroup) toHTTPRoute() (gatewayv1beta1.HTTPRoute, field.ErrorList) {
+func (rg *ingressRuleGroup) toHTTPRoute(options i2gw.ProviderImplementationSpecificOptions) (gatewayv1beta1.HTTPRoute, field.ErrorList) {
 	pathsByMatchGroup := map[pathMatchKey][]ingressPath{}
 	var errors field.ErrorList
 
@@ -305,7 +305,7 @@ func (rg *ingressRuleGroup) toHTTPRoute() (gatewayv1beta1.HTTPRoute, field.Error
 	for _, paths := range pathsByMatchGroup {
 		path := paths[0]
 		fieldPath := field.NewPath("spec", "rules").Index(path.ruleIdx).Child(path.ruleType).Child("paths").Index(path.pathIdx)
-		match, err := toHTTPRouteMatch(path, fieldPath)
+		match, err := toHTTPRouteMatch(path.path, fieldPath, options.ToImplementationSpecificHTTPPathTypeMatch)
 		if err != nil {
 			errors = append(errors, err)
 			continue
@@ -348,20 +348,30 @@ func getPathMatchKey(ip ingressPath) pathMatchKey {
 	return pathMatchKey(fmt.Sprintf("%s/%s", pathType, ip.path.Path))
 }
 
-func toHTTPRouteMatch(ip ingressPath, path *field.Path) (*gatewayv1beta1.HTTPRouteMatch, *field.Error) {
+func toHTTPRouteMatch(routePath networkingv1.HTTPIngressPath, path *field.Path, toImplementationSpecificPathMatch i2gw.ImplementationSpecificHTTPPathTypeMatchConverter) (*gatewayv1beta1.HTTPRouteMatch, *field.Error) {
 	pmPrefix := gatewayv1beta1.PathMatchPathPrefix
 	pmExact := gatewayv1beta1.PathMatchExact
 
-	match := &gatewayv1beta1.HTTPRouteMatch{Path: &gatewayv1beta1.HTTPPathMatch{Value: &ip.path.Path}}
-	//exhaustive:ignore -explicit-exhaustive-switch
-	// networkingv1.PathTypeImplementationSpecific is not supported here, hence it goes into default case.
-	switch *ip.path.PathType {
+	match := &gatewayv1beta1.HTTPRouteMatch{Path: &gatewayv1beta1.HTTPPathMatch{Value: &routePath.Path}}
+	switch *routePath.PathType {
 	case networkingv1.PathTypePrefix:
 		match.Path.Type = &pmPrefix
 	case networkingv1.PathTypeExact:
 		match.Path.Type = &pmExact
+	// In case the path type is ImplementationSpecific, the path value and type
+	// will be set by the provider-specific customization function. If such function
+	// is not given by the provider, an error is returned.
+	case networkingv1.PathTypeImplementationSpecific:
+		if toImplementationSpecificPathMatch != nil {
+			toImplementationSpecificPathMatch(match.Path)
+		} else {
+			return nil, field.Invalid(path.Child("pathType"), routePath.PathType, "implementationSpecific path type is not supported in generic translation, and your provider does not provide custom support to translate it")
+		}
 	default:
-		return nil, field.Invalid(path.Child("pathType"), ip.path.PathType, fmt.Sprintf("unsupported path match type: %s", *ip.path.PathType))
+		// default should never hit, as all the possible cases are already checked
+		// via proper switch cases.
+		return nil, field.Invalid(path.Child("pathType"), match.Path.Type, fmt.Sprintf("unsupported path match type: %s", *match.Path.Type))
+
 	}
 
 	return match, nil
diff --git a/pkg/i2gw/providers/common/utils.go b/pkg/i2gw/providers/common/utils.go
index ef472fe..dcc9130 100644
--- a/pkg/i2gw/providers/common/utils.go
+++ b/pkg/i2gw/providers/common/utils.go
@@ -126,3 +126,7 @@ func ToBackendRef(ib networkingv1.IngressBackend, path *field.Path) (*gatewayv1b
 		},
 	}, nil
 }
+
+func PtrTo[T any](a T) *T {
+	return &a
+}
diff --git a/pkg/i2gw/providers/ingressnginx/converter.go b/pkg/i2gw/providers/ingressnginx/converter.go
index 6216e5e..8bb1678 100644
--- a/pkg/i2gw/providers/ingressnginx/converter.go
+++ b/pkg/i2gw/providers/ingressnginx/converter.go
@@ -45,7 +45,7 @@ func (c *converter) ToGatewayAPI(resources i2gw.InputResources) (i2gw.GatewayRes
 
 	// Convert plain ingress resources to gateway resources, ignoring all
 	// provider-specific features.
-	gatewayResources, errs := common.ToGateway(resources.Ingresses)
+	gatewayResources, errs := common.ToGateway(resources.Ingresses, i2gw.ProviderImplementationSpecificOptions{})
 	if len(errs) > 0 {
 		return i2gw.GatewayResources{}, errs
 	}
diff --git a/pkg/i2gw/providers/kong/README.md b/pkg/i2gw/providers/kong/README.md
index 16055d1..f9e6c84 100644
--- a/pkg/i2gw/providers/kong/README.md
+++ b/pkg/i2gw/providers/kong/README.md
@@ -1,6 +1,8 @@
-# Ingress Kong Provider
+# Kong Provider
 
-The project supports translating kong specific annotations.
+## Annotations supported
+
+The project supports translating Kong-specific annotations.
 
 Current supported annotations:
 
@@ -9,7 +11,7 @@ Current supported annotations:
   be specified by separating values with commas. Example: `konghq.com/methods: "POST,GET"`.
 - `konghq.com/headers.*`: If specified, the values of this annotation are used to
   perform header matching on the associated ingress rules. The header name is specified
-  in the annotation key after `.`, and the annotations value can contain multiple
+  in the annotation key after `.`, and the annotation value can contain multiple
   header values separated by commas. All the header values for a specific header
   name are intended to be ORed. Example: `konghq.com/headers.x-routing: "alpha,bravo"`.
 - `konghq.com/plugins`: If specified, the values of this annotation are used to
@@ -17,3 +19,11 @@ Current supported annotations:
   by separating values with commas. Example: `konghq.com/plugins: "plugin1,plugin2"`.
 
 If you are reliant on any annotations not listed above, please open an issue.
+
+## Implementation-specific features
+
+The following implementation-specific features are supported:
+
+- The ingress `ImplementationSpecific` match type is properly converted to
+  - `RegularExpression` HTTPRoute match type when the path has the prefix `/~`.
+  - `PathPrefix` HTTPRoute match type when there is no `/~` prefix.
diff --git a/pkg/i2gw/providers/kong/converter.go b/pkg/i2gw/providers/kong/converter.go
index c683af2..d628d21 100644
--- a/pkg/i2gw/providers/kong/converter.go
+++ b/pkg/i2gw/providers/kong/converter.go
@@ -26,7 +26,8 @@ import (
 type converter struct {
 	conf *i2gw.ProviderConf
 
-	featureParsers []i2gw.FeatureParser
+	featureParsers                []i2gw.FeatureParser
+	implementationSpecificOptions i2gw.ProviderImplementationSpecificOptions
 }
 
 // newConverter returns an kong converter instance.
@@ -38,6 +39,9 @@ func newConverter(conf *i2gw.ProviderConf) *converter {
 			methodMatchingFeature,
 			pluginsFeature,
 		},
+		implementationSpecificOptions: i2gw.ProviderImplementationSpecificOptions{
+			ToImplementationSpecificHTTPPathTypeMatch: implementationSpecificHTTPPathTypeMatch,
+		},
 	}
 }
 
@@ -47,7 +51,7 @@ func (c *converter) ToGatewayAPI(resources i2gw.InputResources) (i2gw.GatewayRes
 
 	// Convert plain ingress resources to gateway resources, ignoring all
 	// provider-specific features.
-	gatewayResources, errs := common.ToGateway(resources.Ingresses)
+	gatewayResources, errs := common.ToGateway(resources.Ingresses, c.implementationSpecificOptions)
 	if len(errs) > 0 {
 		return i2gw.GatewayResources{}, errs
 	}
diff --git a/pkg/i2gw/providers/kong/implementation_specific.go b/pkg/i2gw/providers/kong/implementation_specific.go
new file mode 100644
index 0000000..8d5a0de
--- /dev/null
+++ b/pkg/i2gw/providers/kong/implementation_specific.go
@@ -0,0 +1,35 @@
+/*
+Copyright 2023 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package kong
+
+import (
+	"strings"
+
+	"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common"
+	gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
+)
+
+func implementationSpecificHTTPPathTypeMatch(path *gatewayv1beta1.HTTPPathMatch) {
+	pmPrefix := gatewayv1beta1.PathMatchPathPrefix
+	pmRegex := gatewayv1beta1.PathMatchRegularExpression
+	if strings.HasPrefix(*path.Value, "/~") {
+		path.Type = &pmRegex
+		path.Value = common.PtrTo(strings.TrimPrefix(*path.Value, "/~"))
+	} else {
+		path.Type = &pmPrefix
+	}
+}


Test patch:
diff --git a/pkg/i2gw/providers/common/converter_test.go b/pkg/i2gw/providers/common/converter_test.go
index c41f802..da85891 100644
--- a/pkg/i2gw/providers/common/converter_test.go
+++ b/pkg/i2gw/providers/common/converter_test.go
@@ -85,7 +85,7 @@ func Test_ingresses2GatewaysAndHttpRoutes(t *testing.T) {
 								Name:     "example-com-http",
 								Port:     80,
 								Protocol: gatewayv1beta1.HTTPProtocolType,
-								Hostname: ptrTo(gatewayv1beta1.Hostname("example.com")),
+								Hostname: PtrTo(gatewayv1beta1.Hostname("example.com")),
 							}},
 						},
 					},
@@ -104,14 +104,14 @@ func Test_ingresses2GatewaysAndHttpRoutes(t *testing.T) {
 								Matches: []gatewayv1beta1.HTTPRouteMatch{{
 									Path: &gatewayv1beta1.HTTPPathMatch{
 										Type:  &gPathPrefix,
-										Value: ptrTo("/foo"),
+										Value: PtrTo("/foo"),
 									},
 								}},
 								BackendRefs: []gatewayv1beta1.HTTPBackendRef{{
 									BackendRef: gatewayv1beta1.BackendRef{
 										BackendObjectReference: gatewayv1beta1.BackendObjectReference{
 											Name: "example",
-											Port: ptrTo(gatewayv1beta1.PortNumber(3000)),
+											Port: PtrTo(gatewayv1beta1.PortNumber(3000)),
 										},
 									},
 								}},
@@ -162,12 +162,12 @@ func Test_ingresses2GatewaysAndHttpRoutes(t *testing.T) {
 								Name:     "example-com-http",
 								Port:     80,
 								Protocol: gatewayv1beta1.HTTPProtocolType,
-								Hostname: ptrTo(gatewayv1beta1.Hostname("example.com")),
+								Hostname: PtrTo(gatewayv1beta1.Hostname("example.com")),
 							}, {
 								Name:     "example-com-https",
 								Port:     443,
 								Protocol: gatewayv1beta1.HTTPSProtocolType,
-								Hostname: ptrTo(gatewayv1beta1.Hostname("example.com")),
+								Hostname: PtrTo(gatewayv1beta1.Hostname("example.com")),
 								TLS: &gatewayv1beta1.GatewayTLSConfig{
 									CertificateRefs: []gatewayv1beta1.SecretObjectReference{{
 										Name: "example-cert",
@@ -191,14 +191,14 @@ func Test_ingresses2GatewaysAndHttpRoutes(t *testing.T) {
 								Matches: []gatewayv1beta1.HTTPRouteMatch{{
 									Path: &gatewayv1beta1.HTTPPathMatch{
 										Type:  &gPathPrefix,
-										Value: ptrTo("/foo"),
+										Value: PtrTo("/foo"),
 									},
 								}},
 								BackendRefs: []gatewayv1beta1.HTTPBackendRef{{
 									BackendRef: gatewayv1beta1.BackendRef{
 										BackendObjectReference: gatewayv1beta1.BackendObjectReference{
 											Name: "example",
-											Port: ptrTo(gatewayv1beta1.PortNumber(3000)),
+											Port: PtrTo(gatewayv1beta1.PortNumber(3000)),
 										},
 									},
 								}},
@@ -214,7 +214,7 @@ func Test_ingresses2GatewaysAndHttpRoutes(t *testing.T) {
 			ingresses: []networkingv1.Ingress{{
 				ObjectMeta: metav1.ObjectMeta{Name: "net", Namespace: "different"},
 				Spec: networkingv1.IngressSpec{
-					IngressClassName: ptrTo("example-proxy"),
+					IngressClassName: PtrTo("example-proxy"),
 					Rules: []networkingv1.IngressRule{{
 						Host: "example.net",
 						IngressRuleValue: networkingv1.IngressRuleValue{
@@ -226,7 +226,7 @@ func Test_ingresses2GatewaysAndHttpRoutes(t *testing.T) {
 										Resource: &corev1.TypedLocalObjectReference{
 											Name:     "custom",
 											Kind:     "StorageBucket",
-											APIGroup: ptrTo("vendor.example.com"),
+											APIGroup: PtrTo("vendor.example.com"),
 										},
 									},
 								}},
@@ -253,7 +253,7 @@ func Test_ingresses2GatewaysAndHttpRoutes(t *testing.T) {
 								Name:     "example-net-http",
 								Port:     80,
 								Protocol: gatewayv1beta1.HTTPProtocolType,
-								Hostname: ptrTo(gatewayv1beta1.Hostname("example.net")),
+								Hostname: PtrTo(gatewayv1beta1.Hostname("example.net")),
 							}},
 						},
 					},
@@ -272,15 +272,15 @@ func Test_ingresses2GatewaysAndHttpRoutes(t *testing.T) {
 								Matches: []gatewayv1beta1.HTTPRouteMatch{{
 									Path: &gatewayv1beta1.HTTPPathMatch{
 										Type:  &gExact,
-										Value: ptrTo("/bar"),
+										Value: PtrTo("/bar"),
 									},
 								}},
 								BackendRefs: []gatewayv1beta1.HTTPBackendRef{{
 									BackendRef: gatewayv1beta1.BackendRef{
 										BackendObjectReference: gatewayv1beta1.BackendObjectReference{
 											Name:  "custom",
-											Group: ptrTo(gatewayv1beta1.Group("vendor.example.com")),
-											Kind:  ptrTo(gatewayv1beta1.Kind("StorageBucket")),
+											Group: PtrTo(gatewayv1beta1.Group("vendor.example.com")),
+											Kind:  PtrTo(gatewayv1beta1.Kind("StorageBucket")),
 										},
 									},
 								}},
@@ -300,7 +300,7 @@ func Test_ingresses2GatewaysAndHttpRoutes(t *testing.T) {
 									BackendRef: gatewayv1beta1.BackendRef{
 										BackendObjectReference: gatewayv1beta1.BackendObjectReference{
 											Name: "default",
-											Port: ptrTo(gatewayv1beta1.PortNumber(8080)),
+											Port: PtrTo(gatewayv1beta1.PortNumber(8080)),
 										},
 									}},
 								}},
@@ -316,7 +316,7 @@ func Test_ingresses2GatewaysAndHttpRoutes(t *testing.T) {
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
 
-			gatewayResources, errs := ToGateway(tc.ingresses)
+			gatewayResources, errs := ToGateway(tc.ingresses, i2gw.ProviderImplementationSpecificOptions{})
 
 			if len(gatewayResources.HTTPRoutes) != len(tc.expectedGatewayResources.HTTPRoutes) {
 				t.Errorf("Expected %d HTTPRoutes, got %d: %+v",
@@ -358,7 +358,3 @@ func Test_ingresses2GatewaysAndHttpRoutes(t *testing.T) {
 		})
 	}
 }
-
-func ptrTo[T any](a T) *T {
-	return &a
-}
diff --git a/pkg/i2gw/providers/ingressnginx/converter_test.go b/pkg/i2gw/providers/ingressnginx/converter_test.go
index 745cf17..e2bbfa6 100644
--- a/pkg/i2gw/providers/ingressnginx/converter_test.go
+++ b/pkg/i2gw/providers/ingressnginx/converter_test.go
@@ -29,6 +29,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/validation/field"
+	"k8s.io/utils/pointer"
 	gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
 )
 
@@ -36,6 +37,7 @@ func Test_ToGateway(t *testing.T) {
 	iPrefix := networkingv1.PathTypePrefix
 	//iExact := networkingv1.PathTypeExact
 	gPathPrefix := gatewayv1beta1.PathMatchPathPrefix
+	isPathType := networkingv1.PathTypeImplementationSpecific
 	//gExact := gatewayv1beta1.PathMatchExact
 
 	testCases := []struct {
@@ -164,6 +166,48 @@ func Test_ToGateway(t *testing.T) {
 			},
 			expectedErrors: field.ErrorList{},
 		},
+		{
+			name: "ImplementationSpecific HTTPRouteMatching",
+			ingresses: []networkingv1.Ingress{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "implementation-specific-regex",
+						Namespace: "default",
+					},
+					Spec: networkingv1.IngressSpec{
+						IngressClassName: ptrTo("ingress-nginx"),
+						Rules: []networkingv1.IngressRule{{
+							Host: "test.mydomain.com",
+							IngressRuleValue: networkingv1.IngressRuleValue{
+								HTTP: &networkingv1.HTTPIngressRuleValue{
+									Paths: []networkingv1.HTTPIngressPath{{
+										Path:     "/~/echo/**/test",
+										PathType: &isPathType,
+										Backend: networkingv1.IngressBackend{
+											Service: &networkingv1.IngressServiceBackend{
+												Name: "test",
+												Port: networkingv1.ServiceBackendPort{
+													Number: 80,
+												},
+											},
+										},
+									}},
+								},
+							},
+						}},
+					},
+				},
+			},
+			expectedGatewayResources: i2gw.GatewayResources{},
+			expectedErrors: field.ErrorList{
+				{
+					Type:     field.ErrorTypeInvalid,
+					Field:    "spec.rules[0].http.paths[0].pathType",
+					BadValue: pointer.String("ImplementationSpecific"),
+					Detail:   "implementationSpecific path type is not supported in generic translation, and your provider does not provide custom support to translate it",
+				},
+			},
+		},
 	}
 
 	for _, tc := range testCases {
diff --git a/pkg/i2gw/providers/kong/converter_test.go b/pkg/i2gw/providers/kong/converter_test.go
index 6ff0131..ad381ca 100644
--- a/pkg/i2gw/providers/kong/converter_test.go
+++ b/pkg/i2gw/providers/kong/converter_test.go
@@ -33,7 +33,9 @@ import (
 
 func Test_ToGateway(t *testing.T) {
 	iPrefix := networkingv1.PathTypePrefix
+	ImplSpecificPathType := networkingv1.PathTypeImplementationSpecific
 	gPathPrefix := gatewayv1beta1.PathMatchPathPrefix
+	gPathRegex := gatewayv1beta1.PathMatchRegularExpression
 
 	testCases := []struct {
 		name                     string
@@ -322,6 +324,176 @@ func Test_ToGateway(t *testing.T) {
 			},
 			expectedErrors: field.ErrorList{},
 		},
+		{
+			name: "ImplementationSpecific HTTPRouteMatching with regex",
+			ingresses: []networkingv1.Ingress{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "implementation-specific-regex",
+						Namespace: "default",
+					},
+					Spec: networkingv1.IngressSpec{
+						IngressClassName: ptrTo("ingress-kong"),
+						Rules: []networkingv1.IngressRule{{
+							Host: "test.mydomain.com",
+							IngressRuleValue: networkingv1.IngressRuleValue{
+								HTTP: &networkingv1.HTTPIngressRuleValue{
+									Paths: []networkingv1.HTTPIngressPath{{
+										Path:     "/~/echo/**/test",
+										PathType: &ImplSpecificPathType,
+										Backend: networkingv1.IngressBackend{
+											Service: &networkingv1.IngressServiceBackend{
+												Name: "test",
+												Port: networkingv1.ServiceBackendPort{
+													Number: 80,
+												},
+											},
+										},
+									}},
+								},
+							},
+						}},
+					},
+				},
+			},
+			expectedGatewayResources: i2gw.GatewayResources{
+				Gateways: map[types.NamespacedName]gatewayv1beta1.Gateway{
+					{Namespace: "default", Name: "ingress-kong"}: {
+						ObjectMeta: metav1.ObjectMeta{Name: "ingress-kong", Namespace: "default"},
+						Spec: gatewayv1beta1.GatewaySpec{
+							GatewayClassName: "ingress-kong",
+							Listeners: []gatewayv1beta1.Listener{{
+								Name:     "test-mydomain-com-http",
+								Port:     80,
+								Protocol: gatewayv1beta1.HTTPProtocolType,
+								Hostname: ptrTo(gatewayv1beta1.Hostname("test.mydomain.com")),
+							}},
+						},
+					},
+				},
+				HTTPRoutes: map[types.NamespacedName]gatewayv1beta1.HTTPRoute{
+					{Namespace: "default", Name: "implementation-specific-regex-test-mydomain-com"}: {
+						ObjectMeta: metav1.ObjectMeta{Name: "implementation-specific-regex-test-mydomain-com", Namespace: "default"},
+						Spec: gatewayv1beta1.HTTPRouteSpec{
+							CommonRouteSpec: gatewayv1beta1.CommonRouteSpec{
+								ParentRefs: []gatewayv1beta1.ParentReference{{
+									Name: "ingress-kong",
+								}},
+							},
+							Hostnames: []gatewayv1beta1.Hostname{"test.mydomain.com"},
+							Rules: []gatewayv1beta1.HTTPRouteRule{
+								{
+									Matches: []gatewayv1beta1.HTTPRouteMatch{
+										{
+											Path: &gatewayv1beta1.HTTPPathMatch{
+												Type:  &gPathRegex,
+												Value: ptrTo("/echo/**/test"),
+											},
+										},
+									},
+									BackendRefs: []gatewayv1beta1.HTTPBackendRef{
+										{
+											BackendRef: gatewayv1beta1.BackendRef{
+												BackendObjectReference: gatewayv1beta1.BackendObjectReference{
+													Name: "test",
+													Port: ptrTo(gatewayv1beta1.PortNumber(80)),
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedErrors: field.ErrorList{},
+		},
+		{
+			name: "ImplementationSpecific HTTPRouteMatching without regex",
+			ingresses: []networkingv1.Ingress{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "implementation-specific-no-regex",
+						Namespace: "default",
+					},
+					Spec: networkingv1.IngressSpec{
+						IngressClassName: ptrTo("ingress-kong"),
+						Rules: []networkingv1.IngressRule{{
+							Host: "test.mydomain.com",
+							IngressRuleValue: networkingv1.IngressRuleValue{
+								HTTP: &networkingv1.HTTPIngressRuleValue{
+									Paths: []networkingv1.HTTPIngressPath{{
+										Path:     "/echo",
+										PathType: &ImplSpecificPathType,
+										Backend: networkingv1.IngressBackend{
+											Service: &networkingv1.IngressServiceBackend{
+												Name: "test",
+												Port: networkingv1.ServiceBackendPort{
+													Number: 80,
+												},
+											},
+										},
+									}},
+								},
+							},
+						}},
+					},
+				},
+			},
+			expectedGatewayResources: i2gw.GatewayResources{
+				Gateways: map[types.NamespacedName]gatewayv1beta1.Gateway{
+					{Namespace: "default", Name: "ingress-kong"}: {
+						ObjectMeta: metav1.ObjectMeta{Name: "ingress-kong", Namespace: "default"},
+						Spec: gatewayv1beta1.GatewaySpec{
+							GatewayClassName: "ingress-kong",
+							Listeners: []gatewayv1beta1.Listener{{
+								Name:     "test-mydomain-com-http",
+								Port:     80,
+								Protocol: gatewayv1beta1.HTTPProtocolType,
+								Hostname: ptrTo(gatewayv1beta1.Hostname("test.mydomain.com")),
+							}},
+						},
+					},
+				},
+				HTTPRoutes: map[types.NamespacedName]gatewayv1beta1.HTTPRoute{
+					{Namespace: "default", Name: "implementation-specific-no-regex-test-mydomain-com"}: {
+						ObjectMeta: metav1.ObjectMeta{Name: "implementation-specific-no-regex-test-mydomain-com", Namespace: "default"},
+						Spec: gatewayv1beta1.HTTPRouteSpec{
+							CommonRouteSpec: gatewayv1beta1.CommonRouteSpec{
+								ParentRefs: []gatewayv1beta1.ParentReference{{
+									Name: "ingress-kong",
+								}},
+							},
+							Hostnames: []gatewayv1beta1.Hostname{"test.mydomain.com"},
+							Rules: []gatewayv1beta1.HTTPRouteRule{
+								{
+									Matches: []gatewayv1beta1.HTTPRouteMatch{
+										{
+											Path: &gatewayv1beta1.HTTPPathMatch{
+												Type:  &gPathPrefix,
+												Value: ptrTo("/echo"),
+											},
+										},
+									},
+									BackendRefs: []gatewayv1beta1.HTTPBackendRef{
+										{
+											BackendRef: gatewayv1beta1.BackendRef{
+												BackendObjectReference: gatewayv1beta1.BackendObjectReference{
+													Name: "test",
+													Port: ptrTo(gatewayv1beta1.PortNumber(80)),
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedErrors: field.ErrorList{},
+		},
 	}
 
 	for _, tc := range testCases {
diff --git a/pkg/i2gw/providers/kong/header_matching_test.go b/pkg/i2gw/providers/kong/header_matching_test.go
index 6337972..6e79f74 100644
--- a/pkg/i2gw/providers/kong/header_matching_test.go
+++ b/pkg/i2gw/providers/kong/header_matching_test.go
@@ -246,7 +246,9 @@ func TestHeaderMatchingFeature(t *testing.T) {
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			gatewayResources, errs := common.ToGateway(tc.inputResources.Ingresses)
+			gatewayResources, errs := common.ToGateway(tc.inputResources.Ingresses, i2gw.ProviderImplementationSpecificOptions{
+				ToImplementationSpecificHTTPPathTypeMatch: implementationSpecificHTTPPathTypeMatch,
+			})
 			if len(errs) != 0 {
 				t.Errorf("Expected no errors, got %d: %+v", len(errs), errs)
 			}
diff --git a/pkg/i2gw/providers/kong/method_matching_test.go b/pkg/i2gw/providers/kong/method_matching_test.go
index aaa461d..c26173a 100644
--- a/pkg/i2gw/providers/kong/method_matching_test.go
+++ b/pkg/i2gw/providers/kong/method_matching_test.go
@@ -189,7 +189,9 @@ func TestMethodMatchingFeature(t *testing.T) {
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			gatewayResources, errs := common.ToGateway(tc.inputResources.Ingresses)
+			gatewayResources, errs := common.ToGateway(tc.inputResources.Ingresses, i2gw.ProviderImplementationSpecificOptions{
+				ToImplementationSpecificHTTPPathTypeMatch: implementationSpecificHTTPPathTypeMatch,
+			})
 			if len(errs) != 0 {
 				t.Errorf("Expected no errors, got %d: %+v", len(errs), errs)
 			}