// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package helpers

import (
	"strings"
	"testing"
	"time"

	"github.com/hashicorp/consul/agent/structs"

	"github.com/stretchr/testify/require"

	"github.com/hashicorp/consul/api"
)

// TestParseConfigEntry is the 'api' mirror image of
// agent/structs/config_entry_test.go:TestDecodeConfigEntry
func TestParseConfigEntry(t *testing.T) {
	t.Parallel()
	for _, tc := range []struct {
		name             string
		camel, camelJSON string
		snake, snakeJSON string
		expect           api.ConfigEntry
		expectJSON       api.ConfigEntry
		expectErr        string
	}{
		{
			name: "proxy-defaults: extra fields or typo",
			snake: `
				kind = "proxy-defaults"
				name = "main"
				cornfig {
				  "foo" = 19
				}
			`,
			camel: `
				Kind = "proxy-defaults"
				Name = "main"
				Cornfig {
				  "foo" = 19
				}
			`,
			snakeJSON: `
			{
				"kind": "proxy-defaults",
				"name": "main",
				"cornfig": {
					"foo": 19
				}
			}
			`,
			camelJSON: `
			{
				"Kind": "proxy-defaults",
				"Name": "main",
				"Cornfig": {
					"foo": 19
				}
			}
			`,
			expectErr: `invalid config key "cornfig"`,
		},
		{
			name: "proxy-defaults",
			snake: `
				kind = "proxy-defaults"
				name = "main"
				meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				config {
				  "foo" = 19
				  "bar" = "abc"
				  "moreconfig" {
					"moar" = "config"
				  }
				}
				mesh_gateway {
					mode = "remote"
				}
				mode = "direct"
				transparent_proxy = {
					outbound_listener_port = 10101
					dialed_directly = true
				}
			`,
			camel: `
				Kind = "proxy-defaults"
				Name = "main"
				Meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				Config {
				  "foo" = 19
				  "bar" = "abc"
				  "moreconfig" {
					"moar" = "config"
				  }
				}
				MeshGateway {
					Mode = "remote"
				}
				Mode = "direct"
				TransparentProxy = {
					outbound_listener_port = 10101
					dialed_directly = true
				}
			`,
			snakeJSON: `
			{
				"kind": "proxy-defaults",
				"name": "main",
				"meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"config": {
					"foo": 19,
					"bar": "abc",
					"moreconfig": {
						"moar": "config"
					}
				},
				"mesh_gateway": {
					"mode": "remote"
				},
				"mode": "direct",
				"transparent_proxy": {
					"outbound_listener_port": 10101,
					"dialed_directly": true
				}
			}
			`,
			camelJSON: `
			{
				"Kind": "proxy-defaults",
				"Name": "main",
				"Meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"Config": {
					"foo": 19,
					"bar": "abc",
					"moreconfig": {
						"moar": "config"
					}
				},
				"MeshGateway": {
					"Mode": "remote"
				},
				"Mode": "direct",
				"TransparentProxy": {
					"OutboundListenerPort": 10101,
					"DialedDirectly": true
				}
			}
			`,
			expect: &api.ProxyConfigEntry{
				Kind: "proxy-defaults",
				Name: "main",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
				Config: map[string]interface{}{
					"foo": 19,
					"bar": "abc",
					"moreconfig": map[string]interface{}{
						"moar": "config",
					},
				},
				MeshGateway: api.MeshGatewayConfig{
					Mode: api.MeshGatewayModeRemote,
				},
				Mode: api.ProxyModeDirect,
				TransparentProxy: &api.TransparentProxyConfig{
					OutboundListenerPort: 10101,
					DialedDirectly:       true,
				},
			},
			expectJSON: &api.ProxyConfigEntry{
				Kind: "proxy-defaults",
				Name: "main",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
				Config: map[string]interface{}{
					"foo": float64(19), // json decoding gives float64 instead of int here
					"bar": "abc",
					"moreconfig": map[string]interface{}{
						"moar": "config",
					},
				},
				MeshGateway: api.MeshGatewayConfig{
					Mode: api.MeshGatewayModeRemote,
				},
				Mode: api.ProxyModeDirect,
				TransparentProxy: &api.TransparentProxyConfig{
					OutboundListenerPort: 10101,
					DialedDirectly:       true,
				},
			},
		},
		{
			name: "terminating-gateway",
			snake: `
				kind = "terminating-gateway"
				name = "terminating-gw-west"
				namespace = "default"
				meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				services = [
				  {
					name = "billing"
					namespace = "biz"
					ca_file = "/etc/ca.crt"
					cert_file = "/etc/client.crt"
					key_file = "/etc/tls.key"
					sni = "mydomain"
				  },
				  {
					name = "*"
					namespace = "ops"
				  }
				]
			`,
			camel: `
				Kind = "terminating-gateway"
				Name = "terminating-gw-west"
				Namespace = "default"
				Meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				Services = [
				  {
					Name = "billing"
					Namespace = "biz"
					CAFile = "/etc/ca.crt"
					CertFile = "/etc/client.crt"
					KeyFile = "/etc/tls.key"
					SNI = "mydomain"
				  },
				  {
					Name = "*"
					Namespace = "ops"
				  }
				]
			`,
			snakeJSON: `
			{
				"kind": "terminating-gateway",
				"name": "terminating-gw-west",
				"namespace": "default",
				"meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"services": [
				  {
					"name": "billing",
					"namespace": "biz",
					"ca_file": "/etc/ca.crt",
					"cert_file": "/etc/client.crt",
					"key_file": "/etc/tls.key",
					"sni": "mydomain"
				  },
				  {
					"name": "*",
					"namespace": "ops"
				  }
				]
			}
			`,
			camelJSON: `
			{
				"Kind": "terminating-gateway",
				"Name": "terminating-gw-west",
				"Namespace": "default",
				"Meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"Services": [
				  {
					"Name": "billing",
					"Namespace": "biz",
					"CAFile": "/etc/ca.crt",
					"CertFile": "/etc/client.crt",
					"KeyFile": "/etc/tls.key",
					"SNI": "mydomain"
				  },
				  {
					"Name": "*",
					"Namespace": "ops"
				  }
				]
			}
			`,
			expect: &api.TerminatingGatewayConfigEntry{
				Kind:      "terminating-gateway",
				Name:      "terminating-gw-west",
				Namespace: "default",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
				Services: []api.LinkedService{
					{
						Name:      "billing",
						Namespace: "biz",
						CAFile:    "/etc/ca.crt",
						CertFile:  "/etc/client.crt",
						KeyFile:   "/etc/tls.key",
						SNI:       "mydomain",
					},
					{
						Name:      "*",
						Namespace: "ops",
					},
				},
			},
			expectJSON: &api.TerminatingGatewayConfigEntry{
				Kind:      "terminating-gateway",
				Name:      "terminating-gw-west",
				Namespace: "default",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
				Services: []api.LinkedService{
					{
						Name:      "billing",
						Namespace: "biz",
						CAFile:    "/etc/ca.crt",
						CertFile:  "/etc/client.crt",
						KeyFile:   "/etc/tls.key",
						SNI:       "mydomain",
					},
					{
						Name:      "*",
						Namespace: "ops",
					},
				},
			},
		},
		{
			name: "service-defaults: kitchen sink (upstreams edition)",
			snake: `
				kind = "service-defaults"
				name = "main"
				meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				protocol = "http"
				external_sni = "abc-123"
				mesh_gateway {
					mode = "remote"
				}
				mode = "direct"
				transparent_proxy = {
					outbound_listener_port = 10101
					dialed_directly = true
				}
				upstream_config {
					overrides = [
						{
							name = "redis"
							passive_health_check {
								max_failures = 3
								interval = "2s"
							}
							envoy_listener_json = "{ \"listener-foo\": 5 }"
							envoy_cluster_json = "{ \"cluster-bar\": 5 }"
							protocol = "grpc"
							connect_timeout_ms = 6543
						},
						{
							name = "finance--billing"
							mesh_gateway {
								mode = "remote"
							}
							limits {
								max_connections = 1111
								max_pending_requests = 2222
								max_concurrent_requests = 3333
							}
						}
					]
					defaults {
						envoy_cluster_json = "zip"
						envoy_listener_json = "zop"
						connect_timeout_ms = 5000
						protocol = "http"
						limits {
							max_connections = 3
							max_pending_requests = 4
							max_concurrent_requests = 5
						}
						passive_health_check {
							max_failures = 5
							interval = "4s"
						}
					}
				}
			`,
			camel: `
				Kind = "service-defaults"
				Name = "main"
				Meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				Protocol = "http"
				ExternalSNI = "abc-123"
				MeshGateway {
					Mode = "remote"
				}
				Mode = "direct"
				TransparentProxy = {
					outbound_listener_port = 10101
					dialed_directly = true
				}
				UpstreamConfig {
					Overrides = [
						{
							Name = "redis"
							PassiveHealthCheck {
								MaxFailures = 3
								Interval = "2s"
							}
							EnvoyListenerJson = "{ \"listener-foo\": 5 }"
							EnvoyClusterJson = "{ \"cluster-bar\": 5 }"
							Protocol = "grpc"
							ConnectTimeoutMs = 6543
						},
						{
							Name = "finance--billing"
							MeshGateway {
								Mode = "remote"
							}
							Limits {
								MaxConnections = 1111
								MaxPendingRequests = 2222
								MaxConcurrentRequests = 3333
							}
						}
					]
					Defaults {
						EnvoyClusterJson = "zip"
						EnvoyListenerJson = "zop"
						ConnectTimeoutMs = 5000
						Protocol = "http"
						Limits {
							MaxConnections = 3
							MaxPendingRequests = 4
							MaxConcurrentRequests = 5
						}
						PassiveHealthCheck {
							MaxFailures = 5
							Interval = "4s"
						}
					}
				}
			`,
			snakeJSON: `
			{
				"kind": "service-defaults",
				"name": "main",
				"meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"protocol": "http",
				"external_sni": "abc-123",
				"mesh_gateway": {
					"mode": "remote"
				},
				"mode": "direct",
				"transparent_proxy": {
					"outbound_listener_port": 10101,
					"dialed_directly": true
				},
				"upstream_config": {
					"overrides": [
						{
							"name": "redis",
							"passive_health_check": {
								"max_failures": 3,
								"interval": "2s"
							},
							"envoy_listener_json": "{ \"listener-foo\": 5 }",
							"envoy_cluster_json": "{ \"cluster-bar\": 5 }",
							"protocol": "grpc",
							"connect_timeout_ms": 6543
						},
						{
							"name": "finance--billing",
							"mesh_gateway": {
								"mode": "remote"
							},
							"limits": {
								"max_connections": 1111,
								"max_pending_requests": 2222,
								"max_concurrent_requests": 3333
							}
						}
					],
					"defaults": {
						"envoy_cluster_json": "zip",
						"envoy_listener_json": "zop",
						"connect_timeout_ms": 5000,
						"protocol": "http",
						"limits": {
							"max_connections": 3,
							"max_pending_requests": 4,
							"max_concurrent_requests": 5
						},
						"passive_health_check": {
							"max_failures": 5,
							"interval": "4s"
						}
					}
				}
			}
			`,
			camelJSON: `
			{
				"Kind": "service-defaults",
				"Name": "main",
				"Meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"Protocol": "http",
				"ExternalSNI": "abc-123",
				"MeshGateway": {
					"Mode": "remote"
				},
				"Mode": "direct",
				"TransparentProxy": {
					"OutboundListenerPort": 10101,
					"DialedDirectly": true
				},
				"UpstreamConfig": {
					"Overrides": [
						{
							"Name": "redis",
							"PassiveHealthCheck": {
								"MaxFailures": 3,
								"Interval": "2s"
							},
							"EnvoyListenerJson": "{ \"listener-foo\": 5 }",
							"EnvoyClusterJson": "{ \"cluster-bar\": 5 }",
							"Protocol": "grpc",
							"ConnectTimeoutMs": 6543
						},
						{
							"Name": "finance--billing",
							"MeshGateway": {
								"Mode": "remote"
							},
							"Limits": {
								"MaxConnections": 1111,
								"MaxPendingRequests": 2222,
								"MaxConcurrentRequests": 3333
							}
						}
					],
					"Defaults": {
						"EnvoyClusterJson": "zip",
						"EnvoyListenerJson": "zop",
						"ConnectTimeoutMs": 5000,
						"Protocol": "http",
						"Limits": {
							"MaxConnections": 3,
							"MaxPendingRequests": 4,
							"MaxConcurrentRequests": 5
						},
						"PassiveHealthCheck": {
							"MaxFailures": 5,
							"Interval": "4s"
						}
					}
				}
			}
			`,
			expect: &api.ServiceConfigEntry{
				Kind: "service-defaults",
				Name: "main",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
				Protocol:    "http",
				ExternalSNI: "abc-123",
				MeshGateway: api.MeshGatewayConfig{
					Mode: api.MeshGatewayModeRemote,
				},
				Mode: api.ProxyModeDirect,
				TransparentProxy: &api.TransparentProxyConfig{
					OutboundListenerPort: 10101,
					DialedDirectly:       true,
				},
				UpstreamConfig: &api.UpstreamConfiguration{
					Overrides: []*api.UpstreamConfig{
						{
							Name: "redis",
							PassiveHealthCheck: &api.PassiveHealthCheck{
								MaxFailures: 3,
								Interval:    2 * time.Second,
							},
							EnvoyListenerJSON: `{ "listener-foo": 5 }`,
							EnvoyClusterJSON:  `{ "cluster-bar": 5 }`,
							Protocol:          "grpc",
							ConnectTimeoutMs:  6543,
						},
						{
							Name: "finance--billing",
							MeshGateway: api.MeshGatewayConfig{
								Mode: "remote",
							},
							Limits: &api.UpstreamLimits{
								MaxConnections:        intPointer(1111),
								MaxPendingRequests:    intPointer(2222),
								MaxConcurrentRequests: intPointer(3333),
							},
						},
					},
					Defaults: &api.UpstreamConfig{
						EnvoyClusterJSON:  "zip",
						EnvoyListenerJSON: "zop",
						Protocol:          "http",
						ConnectTimeoutMs:  5000,
						Limits: &api.UpstreamLimits{
							MaxConnections:        intPointer(3),
							MaxPendingRequests:    intPointer(4),
							MaxConcurrentRequests: intPointer(5),
						},
						PassiveHealthCheck: &api.PassiveHealthCheck{
							MaxFailures: 5,
							Interval:    4 * time.Second,
						},
					},
				},
			},
		},
		{
			name: "service-defaults: kitchen sink (destination edition)",
			snake: `
				kind = "service-defaults"
				name = "main"
				meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				protocol = "grpc"
				mesh_gateway {
					mode = "remote"
				}
				mode = "transparent"
				transparent_proxy = {
					outbound_listener_port = 10101
					dialed_directly = true
				}
				destination = {
					addresses = ["10.0.0.0", "10.0.0.1"]
					port = 443
				}
			`,
			camel: `
				Kind = "service-defaults"
				Name = "main"
				Meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				Protocol = "grpc"
				MeshGateway {
					Mode = "remote"
				}
				Mode = "transparent"
				TransparentProxy = {
					outbound_listener_port = 10101
					dialed_directly = true
				}
				Destination = {
					Addresses = ["10.0.0.0", "10.0.0.1"]
					Port = 443
				}
			`,
			snakeJSON: `
			{
				"kind": "service-defaults",
				"name": "main",
				"meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"protocol": "grpc",
				"mesh_gateway": {
					"mode": "remote"
				},
				"mode": "transparent",
				"transparent_proxy": {
					"outbound_listener_port": 10101,
					"dialed_directly": true
				},
				"destination": {
					"addresses": [
						"10.0.0.0",
						"10.0.0.1"
					],
					"port": 443
				}
			}
			`,
			camelJSON: `
			{
				"Kind": "service-defaults",
				"Name": "main",
				"Meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"Protocol": "grpc",
				"MeshGateway": {
					"Mode": "remote"
				},
				"Mode": "transparent",
				"TransparentProxy": {
					"OutboundListenerPort": 10101,
					"DialedDirectly": true
				},
				"Destination": {
					"Addresses": [
						"10.0.0.0",
						"10.0.0.1"
					],
					"Port": 443
				}
			}
			`,
			expect: &api.ServiceConfigEntry{
				Kind: "service-defaults",
				Name: "main",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
				Protocol: "grpc",
				MeshGateway: api.MeshGatewayConfig{
					Mode: api.MeshGatewayModeRemote,
				},
				Mode: api.ProxyModeTransparent,
				TransparentProxy: &api.TransparentProxyConfig{
					OutboundListenerPort: 10101,
					DialedDirectly:       true,
				},
				Destination: &api.DestinationConfig{
					Addresses: []string{"10.0.0.0", "10.0.0.1"},
					Port:      443,
				},
			},
		},
		{
			name: "service-router: kitchen sink",
			snake: `
				kind = "service-router"
				name = "main"
				partition = "pepper"
				meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				routes = [
					{
						match {
							http {
								path_exact = "/foo"
								header = [
									{
										name = "debug1"
										present = true
									},
									{
										name = "debug2"
										present = false
										invert = true
									},
									{
										name = "debug3"
										exact = "1"
									},
									{
										name = "debug4"
										prefix = "aaa"
									},
									{
										name = "debug5"
										suffix = "bbb"
									},
									{
										name = "debug6"
										regex = "a.*z"
									},
								]
							}
						}
						destination {
						  service                  = "carrot"
						  service_subset           = "kale"
						  namespace                = "leek"
						  partition                = "chard"
						  prefix_rewrite           = "/alternate"
						  request_timeout          = "99s"
						  idle_timeout             = "99s"
						  num_retries              = 12345
						  retry_on_connect_failure = true
						  retry_on_status_codes    = [401, 209]
						}
					},
					{
						match {
							http {
								path_prefix = "/foo"
								methods = [ "GET", "DELETE" ]
								query_param = [
									{
										name = "hack1"
										present = true
									},
									{
										name = "hack2"
										exact = "1"
									},
									{
										name = "hack3"
										regex = "a.*z"
									},
								]
							}
						}
					},
					{
						match {
							http {
								path_regex = "/foo"
							}
						}
					},
				]
			`,
			camel: `
				Kind = "service-router"
				Name = "main"
				Partition = "pepper"
				Meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				Routes = [
					{
						Match {
							HTTP {
								PathExact = "/foo"
								Header = [
									{
										Name = "debug1"
										Present = true
									},
									{
										Name = "debug2"
										Present = false
										Invert = true
									},
									{
										Name = "debug3"
										Exact = "1"
									},
									{
										Name = "debug4"
										Prefix = "aaa"
									},
									{
										Name = "debug5"
										Suffix = "bbb"
									},
									{
										Name = "debug6"
										Regex = "a.*z"
									},
								]
							}
						}
						Destination {
						  Service               = "carrot"
						  ServiceSubset         = "kale"
						  Namespace             = "leek"
						  Partition             = "chard"
						  PrefixRewrite         = "/alternate"
						  RequestTimeout        = "99s"
						  IdleTimeout           = "99s"
						  NumRetries            = 12345
						  RetryOnConnectFailure = true
						  RetryOnStatusCodes    = [401, 209]
						}
					},
					{
						Match {
							HTTP {
								PathPrefix = "/foo"
								Methods = [ "GET", "DELETE" ]
								QueryParam = [
									{
										Name = "hack1"
										Present = true
									},
									{
										Name = "hack2"
										Exact = "1"
									},
									{
										Name = "hack3"
										Regex = "a.*z"
									},
								]
							}
						}
					},
					{
						Match {
							HTTP {
								PathRegex = "/foo"
							}
						}
					},
				]
			`,
			snakeJSON: `
			{
				"kind": "service-router",
				"name": "main",
				"partition": "pepper",
				"meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"routes": [
					{
						"match": {
							"http": {
								"path_exact": "/foo",
								"header": [
									{
										"name": "debug1",
										"present": true
									},
									{
										"name": "debug2",
										"present": false,
										"invert": true
									},
									{
										"name": "debug3",
										"exact": "1"
									},
									{
										"name": "debug4",
										"prefix": "aaa"
									},
									{
										"name": "debug5",
										"suffix": "bbb"
									},
									{
										"name": "debug6",
										"regex": "a.*z"
									}
								]
							}
						},
						"destination": {
							"service": "carrot",
							"service_subset": "kale",
							"namespace": "leek",
							"partition": "chard",
							"prefix_rewrite": "/alternate",
							"request_timeout": "99s",
							"idle_timeout": "99s",
							"num_retries": 12345,
							"retry_on_connect_failure": true,
							"retry_on_status_codes": [
								401,
								209
							]
						}
					},
					{
						"match": {
							"http": {
								"path_prefix": "/foo",
								"methods": [
									"GET",
									"DELETE"
								],
								"query_param": [
									{
										"name": "hack1",
										"present": true
									},
									{
										"name": "hack2",
										"exact": "1"
									},
									{
										"name": "hack3",
										"regex": "a.*z"
									}
								]
							}
						}
					},
					{
						"match": {
							"http": {
								"path_regex": "/foo"
							}
						}
					}
				]
			}
			`,
			camelJSON: `
			{
				"Kind": "service-router",
				"Name": "main",
				"Partition": "pepper",
				"Meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"Routes": [
					{
						"Match": {
							"HTTP": {
								"PathExact": "/foo",
								"Header": [
									{
										"Name": "debug1",
										"Present": true
									},
									{
										"Name": "debug2",
										"Present": false,
										"Invert": true
									},
									{
										"Name": "debug3",
										"Exact": "1"
									},
									{
										"Name": "debug4",
										"Prefix": "aaa"
									},
									{
										"Name": "debug5",
										"Suffix": "bbb"
									},
									{
										"Name": "debug6",
										"Regex": "a.*z"
									}
								]
							}
						},
						"Destination": {
							"Service": "carrot",
							"ServiceSubset": "kale",
							"Namespace": "leek",
							"Partition": "chard",
							"PrefixRewrite": "/alternate",
							"RequestTimeout": "99s",
							"IdleTimeout": "99s",
							"NumRetries": 12345,
							"RetryOnConnectFailure": true,
							"RetryOnStatusCodes": [
								401,
								209
							]
						}
					},
					{
						"Match": {
							"HTTP": {
								"PathPrefix": "/foo",
								"Methods": [
									"GET",
									"DELETE"
								],
								"QueryParam": [
									{
										"Name": "hack1",
										"Present": true
									},
									{
										"Name": "hack2",
										"Exact": "1"
									},
									{
										"Name": "hack3",
										"Regex": "a.*z"
									}
								]
							}
						}
					},
					{
						"Match": {
							"HTTP": {
								"PathRegex": "/foo"
							}
						}
					}
				]
			}
			`,
			expect: &api.ServiceRouterConfigEntry{
				Kind:      "service-router",
				Name:      "main",
				Partition: "pepper",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
				Routes: []api.ServiceRoute{
					{
						Match: &api.ServiceRouteMatch{
							HTTP: &api.ServiceRouteHTTPMatch{
								PathExact: "/foo",
								Header: []api.ServiceRouteHTTPMatchHeader{
									{
										Name:    "debug1",
										Present: true,
									},
									{
										Name:    "debug2",
										Present: false,
										Invert:  true,
									},
									{
										Name:  "debug3",
										Exact: "1",
									},
									{
										Name:   "debug4",
										Prefix: "aaa",
									},
									{
										Name:   "debug5",
										Suffix: "bbb",
									},
									{
										Name:  "debug6",
										Regex: "a.*z",
									},
								},
							},
						},
						Destination: &api.ServiceRouteDestination{
							Service:               "carrot",
							ServiceSubset:         "kale",
							Namespace:             "leek",
							Partition:             "chard",
							PrefixRewrite:         "/alternate",
							RequestTimeout:        99 * time.Second,
							IdleTimeout:           99 * time.Second,
							NumRetries:            12345,
							RetryOnConnectFailure: true,
							RetryOnStatusCodes:    []uint32{401, 209},
						},
					},
					{
						Match: &api.ServiceRouteMatch{
							HTTP: &api.ServiceRouteHTTPMatch{
								PathPrefix: "/foo",
								Methods:    []string{"GET", "DELETE"},
								QueryParam: []api.ServiceRouteHTTPMatchQueryParam{
									{
										Name:    "hack1",
										Present: true,
									},
									{
										Name:  "hack2",
										Exact: "1",
									},
									{
										Name:  "hack3",
										Regex: "a.*z",
									},
								},
							},
						},
					},
					{
						Match: &api.ServiceRouteMatch{
							HTTP: &api.ServiceRouteHTTPMatch{
								PathRegex: "/foo",
							},
						},
					},
				},
			},
		},
		{
			name: "service-splitter: kitchen sink",
			snake: `
				kind = "service-splitter"
				name = "main"
				partition = "east"
				meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				splits = [
				  {
					weight        = 97.1
					service_subset = "v1"
				  },
				  {
					weight        = 2
					service_subset = "v2"
				  },
				  {
					weight    = 0.9
					service   = "other"
					namespace = "alt"
					partition = "west"
				  },
				]
			`,
			camel: `
				Kind = "service-splitter"
				Name = "main"
				Partition = "east"
				Meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				Splits = [
				  {
					Weight        = 97.1
					ServiceSubset = "v1"
				  },
				  {
					Weight        = 2,
					ServiceSubset = "v2"
				  },
				  {
					Weight    = 0.9
					Service   = "other"
					Namespace = "alt"
					Partition = "west"
				  },
				]
			`,
			snakeJSON: `
			{
				"kind": "service-splitter",
				"name": "main",
				"partition": "east",
				"meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"splits": [
					{
						"weight": 97.1,
						"service_subset": "v1"
					},
					{
						"weight": 2,
						"service_subset": "v2"
					},
					{
						"weight": 0.9,
						"service": "other",
						"namespace": "alt",
						"partition": "west"
					}
				]
			}
			`,
			camelJSON: `
			{
				"Kind": "service-splitter",
				"Name": "main",
				"Partition": "east",
				"Meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"Splits": [
					{
						"Weight": 97.1,
						"ServiceSubset": "v1"
					},
					{
						"Weight": 2,
						"ServiceSubset": "v2"
					},
					{
						"Weight": 0.9,
						"Service": "other",
						"Namespace": "alt",
						"Partition": "west"
					}
				]
			}
			`,
			expect: &api.ServiceSplitterConfigEntry{
				Kind:      api.ServiceSplitter,
				Name:      "main",
				Partition: "east",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
				Splits: []api.ServiceSplit{
					{
						Weight:        97.1,
						ServiceSubset: "v1",
					},
					{
						Weight:        2,
						ServiceSubset: "v2",
					},
					{
						Weight:    0.9,
						Service:   "other",
						Namespace: "alt",
						Partition: "west",
					},
				},
			},
		},
		{
			name: "service-resolver: subsets with failover",
			snake: `
				kind = "service-resolver"
				name = "main"
				meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				default_subset = "v1"
				connect_timeout = "15s"
				subsets = {
					"v1" = {
						filter = "Service.Meta.version == v1"
					},
					"v2" = {
						filter = "Service.Meta.version == v2"
						only_passing = true
					},
				}
				failover = {
					"v2" = {
						service = "failcopy"
						service_subset = "sure"
						namespace = "neighbor"
						datacenters = ["dc5", "dc14"]
					},
					"*" = {
						datacenters = ["dc7"]
					}
				}`,
			camel: `
				Kind = "service-resolver"
				Name = "main"
				Meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				DefaultSubset = "v1"
				ConnectTimeout = "15s"
				Subsets = {
					"v1" = {
						Filter = "Service.Meta.version == v1"
					},
					"v2" = {
						Filter = "Service.Meta.version == v2"
						OnlyPassing = true
					},
				}
				Failover = {
					"v2" = {
						Service = "failcopy"
						ServiceSubset = "sure"
						Namespace = "neighbor"
						Datacenters = ["dc5", "dc14"]
					},
					"*" = {
						Datacenters = ["dc7"]
					}
				}`,
			snakeJSON: `
			{
				"kind": "service-resolver",
				"name": "main",
				"meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"default_subset": "v1",
				"connect_timeout": "15s",
				"subsets": {
					"v1": {
						"filter": "Service.Meta.version == v1"
					},
					"v2": {
						"filter": "Service.Meta.version == v2",
						"only_passing": true
					}
				},
				"failover": {
					"v2": {
						"service": "failcopy",
						"service_subset": "sure",
						"namespace": "neighbor",
						"datacenters": [
							"dc5",
							"dc14"
						]
					},
					"*": {
						"datacenters": [
							"dc7"
						]
					}
				}
			}
			`,
			camelJSON: `
			{
				"Kind": "service-resolver",
				"Name": "main",
				"Meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"DefaultSubset": "v1",
				"ConnectTimeout": "15s",
				"Subsets": {
					"v1": {
						"Filter": "Service.Meta.version == v1"
					},
					"v2": {
						"Filter": "Service.Meta.version == v2",
						"OnlyPassing": true
					}
				},
				"Failover": {
					"v2": {
						"Service": "failcopy",
						"ServiceSubset": "sure",
						"Namespace": "neighbor",
						"Datacenters": [
							"dc5",
							"dc14"
						]
					},
					"*": {
						"Datacenters": [
							"dc7"
						]
					}
				}
			}
			`,
			expect: &api.ServiceResolverConfigEntry{
				Kind: "service-resolver",
				Name: "main",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
				DefaultSubset:  "v1",
				ConnectTimeout: 15 * time.Second,
				Subsets: map[string]api.ServiceResolverSubset{
					"v1": {
						Filter: "Service.Meta.version == v1",
					},
					"v2": {
						Filter:      "Service.Meta.version == v2",
						OnlyPassing: true,
					},
				},
				Failover: map[string]api.ServiceResolverFailover{
					"v2": {
						Service:       "failcopy",
						ServiceSubset: "sure",
						Namespace:     "neighbor",
						Datacenters:   []string{"dc5", "dc14"},
					},
					"*": {
						Datacenters: []string{"dc7"},
					},
				},
			},
		},
		{
			name: "service-resolver: redirect",
			snake: `
				kind = "service-resolver"
				name = "main"
				partition = "east"
				redirect {
					service = "other"
					service_subset = "backup"
					namespace = "alt"
					partition = "west"
					datacenter = "dc9"
				}
			`,
			camel: `
				Kind = "service-resolver"
				Name = "main"
				Partition = "east"
				Redirect {
					Service = "other"
					ServiceSubset = "backup"
					Namespace = "alt"
					Partition = "west"
					Datacenter = "dc9"
				}
			`,
			snakeJSON: `
			{
				"kind": "service-resolver",
				"name": "main",
				"partition": "east",
				"redirect": {
					"service": "other",
					"service_subset": "backup",
					"namespace": "alt",
					"partition": "west",
					"datacenter": "dc9"
				}
			}
			`,
			camelJSON: `
			{
				"Kind": "service-resolver",
				"Name": "main",
				"Partition": "east",
				"Redirect": {
					"Service": "other",
					"ServiceSubset": "backup",
					"Namespace": "alt",
					"Partition": "west",
					"Datacenter": "dc9"
				}
			}
			`,
			expect: &api.ServiceResolverConfigEntry{
				Kind:      "service-resolver",
				Name:      "main",
				Partition: "east",
				Redirect: &api.ServiceResolverRedirect{
					Service:       "other",
					ServiceSubset: "backup",
					Namespace:     "alt",
					Partition:     "west",
					Datacenter:    "dc9",
				},
			},
		},
		{
			name: "service-resolver: default",
			snake: `
				kind = "service-resolver"
				name = "main"
			`,
			camel: `
				Kind = "service-resolver"
				Name = "main"
			`,
			snakeJSON: `
			{
				"kind": "service-resolver",
				"name": "main"
			}
			`,
			camelJSON: `
			{
				"Kind": "service-resolver",
				"Name": "main"
			}
			`,
			expect: &api.ServiceResolverConfigEntry{
				Kind: "service-resolver",
				Name: "main",
			},
		},
		{
			name: "service-resolver: envoy hash lb kitchen sink",
			snake: `
				kind = "service-resolver"
				name = "main"
				load_balancer = {
					policy = "ring_hash"
					ring_hash_config = {
						minimum_ring_size = 1
						maximum_ring_size = 2
					}
					hash_policies = [
						{
							field = "cookie"
							field_value = "good-cookie"
							cookie_config = {
								ttl = "1s"
								path = "/oven"
							}
							terminal = true
						},
						{
							field = "cookie"
							field_value = "less-good-cookie"
							cookie_config = {
								session = true
								path = "/toaster"
							}
							terminal = true
						},
						{
							field = "header"
							field_value = "x-user-id"
						},
						{
							source_ip = true
						}
					]
				}
			`,
			camel: `
				Kind = "service-resolver"
				Name = "main"
				LoadBalancer = {
					Policy = "ring_hash"
					RingHashConfig = {
						MinimumRingSize = 1
						MaximumRingSize = 2
					}
					HashPolicies = [
						{
							Field = "cookie"
							FieldValue = "good-cookie"
							CookieConfig = {
								TTL = "1s"
								Path = "/oven"
							}
							Terminal = true
						},
						{
							Field = "cookie"
							FieldValue = "less-good-cookie"
							CookieConfig = {
								Session = true
								Path = "/toaster"
							}
							Terminal = true
						},
						{
							Field = "header"
							FieldValue = "x-user-id"
						},
						{
							SourceIP = true
						}
					]
				}
			`,
			snakeJSON: `
			{
				"kind": "service-resolver",
				"name": "main",
				"load_balancer": {
					"policy": "ring_hash",
					"ring_hash_config": {
						"minimum_ring_size": 1,
						"maximum_ring_size": 2
					},
					"hash_policies": [
						{
							"field": "cookie",
							"field_value": "good-cookie",
							"cookie_config": {
								"ttl": "1s",
								"path": "/oven"
							},
							"terminal": true
						},
						{
							"field": "cookie",
							"field_value": "less-good-cookie",
							"cookie_config": {
								"session": true,
								"path": "/toaster"
							},
							"terminal": true
						},
						{
							"field": "header",
							"field_value": "x-user-id"
						},
						{
							"source_ip": true
						}
					]
				}
			}
			`,
			camelJSON: `
			{
				"Kind": "service-resolver",
				"Name": "main",
				"LoadBalancer": {
					"Policy": "ring_hash",
					"RingHashConfig": {
						"MinimumRingSize": 1,
						"MaximumRingSize": 2
					},
					"HashPolicies": [
						{
							"Field": "cookie",
							"FieldValue": "good-cookie",
							"CookieConfig": {
								"TTL": "1s",
								"Path": "/oven"
							},
							"Terminal": true
						},
						{
							"Field": "cookie",
							"FieldValue": "less-good-cookie",
							"CookieConfig": {
								"Session": true,
								"Path": "/toaster"
							},
							"Terminal": true
						},
						{
							"Field": "header",
							"FieldValue": "x-user-id"
						},
						{
							"SourceIP": true
						}
					]
				}
			}
			`,
			expect: &api.ServiceResolverConfigEntry{
				Kind: "service-resolver",
				Name: "main",
				LoadBalancer: &api.LoadBalancer{
					Policy: structs.LBPolicyRingHash,
					RingHashConfig: &api.RingHashConfig{
						MinimumRingSize: 1,
						MaximumRingSize: 2,
					},
					HashPolicies: []api.HashPolicy{
						{
							Field:      structs.HashPolicyCookie,
							FieldValue: "good-cookie",
							CookieConfig: &api.CookieConfig{
								TTL:  1 * time.Second,
								Path: "/oven",
							},
							Terminal: true,
						},
						{
							Field:      structs.HashPolicyCookie,
							FieldValue: "less-good-cookie",
							CookieConfig: &api.CookieConfig{
								Session: true,
								Path:    "/toaster",
							},
							Terminal: true,
						},
						{
							Field:      structs.HashPolicyHeader,
							FieldValue: "x-user-id",
						},
						{
							SourceIP: true,
						},
					},
				},
			},
		},
		{
			name: "service-resolver: envoy least request kitchen sink",
			snake: `
				kind = "service-resolver"
				name = "main"
				load_balancer = {
					policy = "least_request"
					least_request_config = {
						choice_count = 2
					}
				}
			`,
			camel: `
				Kind = "service-resolver"
				Name = "main"
				LoadBalancer = {
					Policy = "least_request"
					LeastRequestConfig = {
						ChoiceCount = 2
					}
				}
			`,
			snakeJSON: `
			{
				"kind": "service-resolver",
				"name": "main",
				"load_balancer": {
					"policy": "least_request",
					"least_request_config": {
						"choice_count": 2
					}
				}
			}
			`,
			camelJSON: `
			{
				"Kind": "service-resolver",
				"Name": "main",
				"LoadBalancer": {
					"Policy": "least_request",
					"LeastRequestConfig": {
						"ChoiceCount": 2
					}
				}
			}
			`,
			expect: &api.ServiceResolverConfigEntry{
				Kind: "service-resolver",
				Name: "main",
				LoadBalancer: &api.LoadBalancer{
					Policy: structs.LBPolicyLeastRequest,
					LeastRequestConfig: &api.LeastRequestConfig{
						ChoiceCount: 2,
					},
				},
			},
		},
		{
			name: "expose paths: kitchen sink proxy defaults",
			snake: `
				kind = "proxy-defaults"
				name = "global"
				expose = {
					checks = true
					paths = [
						{
							local_path_port = 8080
							listener_port = 21500
							path = "/healthz"
							protocol = "http2"
						},
						{
							local_path_port = 8000
							listener_port = 21501
							path = "/metrics"
							protocol = "http"
						}
					]
				}`,
			camel: `
				Kind = "proxy-defaults"
				Name = "global"
				Expose = {
					Checks = true
					Paths = [
						{
							LocalPathPort = 8080
							ListenerPort = 21500
							Path = "/healthz"
							Protocol = "http2"
						},
						{
							LocalPathPort = 8000
							ListenerPort = 21501
							Path = "/metrics"
							Protocol = "http"
						}
					]
				}`,
			snakeJSON: `
			{
				"kind": "proxy-defaults",
				"name": "global",
				"expose": {
					"checks": true,
					"paths": [
						{
							"local_path_port": 8080,
							"listener_port": 21500,
							"path": "/healthz",
							"protocol": "http2"
						},
						{
							"local_path_port": 8000,
							"listener_port": 21501,
							"path": "/metrics",
							"protocol": "http"
						}
					]
				}
			}
			`,
			camelJSON: `
			{
				"Kind": "proxy-defaults",
				"Name": "global",
				"Expose": {
					"Checks": true,
					"Paths": [
						{
							"LocalPathPort": 8080,
							"ListenerPort": 21500,
							"Path": "/healthz",
							"Protocol": "http2"
						},
						{
							"LocalPathPort": 8000,
							"ListenerPort": 21501,
							"Path": "/metrics",
							"Protocol": "http"
						}
					]
				}
			}
			`,
			expect: &api.ProxyConfigEntry{
				Kind: "proxy-defaults",
				Name: "global",
				Expose: api.ExposeConfig{
					Checks: true,
					Paths: []api.ExposePath{
						{
							ListenerPort:  21500,
							Path:          "/healthz",
							LocalPathPort: 8080,
							Protocol:      "http2",
						},
						{
							ListenerPort:  21501,
							Path:          "/metrics",
							LocalPathPort: 8000,
							Protocol:      "http",
						},
					},
				},
			},
		},
		{
			name: "expose paths: kitchen sink service defaults",
			snake: `
				kind = "service-defaults"
				name = "web"
				expose = {
					checks = true
					paths = [
						{
							local_path_port = 8080
							listener_port = 21500
							path = "/healthz"
							protocol = "http2"
						},
						{
							local_path_port = 8000
							listener_port = 21501
							path = "/metrics"
							protocol = "http"
						}
					]
				}`,
			camel: `
				Kind = "service-defaults"
				Name = "web"
				Expose = {
					Checks = true
					Paths = [
						{
							LocalPathPort = 8080
							ListenerPort = 21500
							Path = "/healthz"
							Protocol = "http2"
						},
						{
							LocalPathPort = 8000
							ListenerPort = 21501
							Path = "/metrics"
							Protocol = "http"
						}
					]
				}`,
			snakeJSON: `
			{
				"kind": "service-defaults",
				"name": "web",
				"expose": {
					"checks": true,
					"paths": [
						{
							"local_path_port": 8080,
							"listener_port": 21500,
							"path": "/healthz",
							"protocol": "http2"
						},
						{
							"local_path_port": 8000,
							"listener_port": 21501,
							"path": "/metrics",
							"protocol": "http"
						}
					]
				}
			}
			`,
			camelJSON: `
			{
				"Kind": "service-defaults",
				"Name": "web",
				"Expose": {
					"Checks": true,
					"Paths": [
						{
							"LocalPathPort": 8080,
							"ListenerPort": 21500,
							"Path": "/healthz",
							"Protocol": "http2"
						},
						{
							"LocalPathPort": 8000,
							"ListenerPort": 21501,
							"Path": "/metrics",
							"Protocol": "http"
						}
					]
				}
			}
			`,
			expect: &api.ServiceConfigEntry{
				Kind: "service-defaults",
				Name: "web",
				Expose: api.ExposeConfig{
					Checks: true,
					Paths: []api.ExposePath{
						{
							ListenerPort:  21500,
							Path:          "/healthz",
							LocalPathPort: 8080,
							Protocol:      "http2",
						},
						{
							ListenerPort:  21501,
							Path:          "/metrics",
							LocalPathPort: 8000,
							Protocol:      "http",
						},
					},
				},
			},
		},
		{
			// TODO(rb): test SDS stuff here in both places (global/service)
			name: "ingress-gateway: kitchen sink",
			snake: `
				kind = "ingress-gateway"
				name = "ingress-web"
				meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				tls {
					enabled = true
					tls_min_version = "TLSv1_1"
					tls_max_version = "TLSv1_2"
					cipher_suites = [
						"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
						"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
					]
				}
				listeners = [
					{
						port = 8080
						protocol = "http"
						services = [
							{
								name = "web"
								hosts = ["test.example.com"]
							},
							{
								name = "db"
								namespace = "foo"
							}
						]
					}
				]
			`,
			camel: `
				Kind = "ingress-gateway"
				Name = "ingress-web"
				Meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				Tls {
					Enabled = true
					TLSMinVersion = "TLSv1_1"
					TLSMaxVersion = "TLSv1_2"
					CipherSuites = [
						"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
						"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
					]
				}
				Listeners = [
					{
						Port = 8080
						Protocol = "http"
						Services = [
							{
								Name = "web"
								Hosts = ["test.example.com"]
							},
							{
								Name = "db"
								Namespace = "foo"
							}
						]
					}
				]
			`,
			snakeJSON: `
			{
				"kind": "ingress-gateway",
				"name": "ingress-web",
				"meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"tls": {
					"enabled": true,
					"tls_min_version": "TLSv1_1",
					"tls_max_version": "TLSv1_2",
					"cipher_suites": [
						"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
						"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
					]
				},
				"listeners": [
					{
						"port": 8080,
						"protocol": "http",
						"services": [
							{
								"name": "web",
								"hosts": ["test.example.com"]
							},
							{
								"name": "db",
								"namespace": "foo"
							}
						]
					}
				]
			}
			`,
			camelJSON: `
			{
				"Kind": "ingress-gateway",
				"Name": "ingress-web",
				"Meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"TLS": {
					"Enabled": true,
					"TLSMinVersion": "TLSv1_1",
					"TLSMaxVersion": "TLSv1_2",
					"CipherSuites": [
						"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
						"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
					]
				},
				"Listeners": [
					{
						"Port": 8080,
						"Protocol": "http",
						"Services": [
							{
								"Name": "web",
								"Hosts": ["test.example.com"]
							},
							{
								"Name": "db",
								"Namespace": "foo"
							}
						]
					}
				]
			}
			`,
			expect: &api.IngressGatewayConfigEntry{
				Kind: "ingress-gateway",
				Name: "ingress-web",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
				TLS: api.GatewayTLSConfig{
					Enabled:       true,
					TLSMinVersion: "TLSv1_1",
					TLSMaxVersion: "TLSv1_2",
					CipherSuites: []string{
						"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
						"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
					},
				},
				Listeners: []api.IngressListener{
					{
						Port:     8080,
						Protocol: "http",
						Services: []api.IngressService{
							{
								Name:  "web",
								Hosts: []string{"test.example.com"},
							},
							{
								Name:      "db",
								Namespace: "foo",
							},
						},
					},
				},
			},
		},
		{
			name: "service-intentions: kitchen sink",
			snake: `
				kind = "service-intentions"
				name = "web"
				meta {
				  "foo" = "bar"
				  "gir" = "zim"
				}
				sources = [
				  {
					name        = "foo"
					action      = "deny"
					type        = "consul"
					description = "foo desc"
				  },
				  {
					name        = "bar"
					action      = "allow"
					description = "bar desc"
				  },
				  {
					name = "l7"
					permissions = [
					  {
						action = "deny"
						http {
						  path_exact = "/admin"
						  header = [
							{
							  name    = "hdr-present"
							  present = true
							},
							{
							  name  = "hdr-exact"
							  exact = "exact"
							},
							{
							  name   = "hdr-prefix"
							  prefix = "prefix"
							},
							{
							  name   = "hdr-suffix"
							  suffix = "suffix"
							},
							{
							  name   = "hdr-contains"
							  contains = "contains"
							},
							{
							  name  = "hdr-regex"
							  regex = "regex"
							},
							{
							  name    = "hdr-absent"
							  present = true
							  invert  = true
							},
							{
							  name  = "hdr-ignore-case"
							  exact = "exact"
							  ignore_case = true
							},
						  ]
						}
					  },
					  {
						action = "allow"
						http {
						  path_prefix = "/v3/"
						}
					  },
					  {
						action = "allow"
						http {
						  path_regex = "/v[12]/.*"
						  methods    = ["GET", "POST"]
						}
					  }
					]
				  }
				]
				sources {
				  name        = "*"
				  action      = "deny"
				  description = "wild desc"
				}
			`,
			camel: `
				Kind = "service-intentions"
				Name = "web"
				Meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				Sources = [
				  {
					Name        = "foo"
					Action      = "deny"
					Type        = "consul"
					Description = "foo desc"
				  },
				  {
					Name        = "bar"
					Action      = "allow"
					Description = "bar desc"
				  },
				  {
					Name = "l7"
					Permissions = [
					  {
						Action = "deny"
						HTTP {
						  PathExact = "/admin"
						  Header = [
							{
							  Name    = "hdr-present"
							  Present = true
							},
							{
							  Name  = "hdr-exact"
							  Exact = "exact"
							},
							{
							  Name   = "hdr-prefix"
							  Prefix = "prefix"
							},
							{
							  Name   = "hdr-suffix"
							  Suffix = "suffix"
							},
							{
							  Name   = "hdr-contains"
							  Contains = "contains"
							},
							{
							  Name  = "hdr-regex"
							  Regex = "regex"
							},
							{
							  Name    = "hdr-absent"
							  Present = true
							  Invert  = true
							},
							{
							  Name  = "hdr-ignore-case"
							  Exact = "exact"
							  IgnoreCase = true
							}
						  ]
						}
					  },
					  {
						Action = "allow"
						HTTP {
						  PathPrefix = "/v3/"
						}
					  },
					  {
						Action = "allow"
						HTTP {
						  PathRegex = "/v[12]/.*"
						  Methods   = ["GET", "POST"]
						}
					  }
					]
				  }
				]
				Sources {
				  Name        = "*"
				  Action      = "deny"
				  Description = "wild desc"
				}
			`,
			snakeJSON: `
			{
				"kind": "service-intentions",
				"name": "web",
				"meta": {
					"foo": "bar",
					"gir": "zim"
				},
				"sources": [
					{
						"name": "foo",
						"action": "deny",
						"type": "consul",
						"description": "foo desc"
					},
					{
						"name": "bar",
						"action": "allow",
						"description": "bar desc"
					},
					{
						"name": "l7",
						"permissions": [
							{
								"action": "deny",
								"http": {
									"path_exact": "/admin",
									"header": [
										{
											"name": "hdr-present",
											"present": true
										},
										{
											"name": "hdr-exact",
											"exact": "exact"
										},
										{
											"name": "hdr-prefix",
											"prefix": "prefix"
										},
										{
											"name": "hdr-suffix",
											"suffix": "suffix"
										},
										{
										    "name": "hdr-contains",
										    "contains": "contains"
										},
										{
											"name": "hdr-regex",
											"regex": "regex"
										},
										{
											"name": "hdr-absent",
											"present": true,
											"invert": true
										},
										{
											"name": "hdr-ignore-case",
											"exact": "exact",
											"ignore_case": true
										}
									]
								}
							},
							{
								"action": "allow",
								"http": {
									"path_prefix": "/v3/"
								}
							},
							{
								"action": "allow",
								"http": {
									"path_regex": "/v[12]/.*",
									"methods": [
										"GET",
										"POST"
									]
								}
							}
						]
					},
					{
						"name": "*",
						"action": "deny",
						"description": "wild desc"
					}
				]
			}
			`,
			camelJSON: `
			{
				"Kind": "service-intentions",
				"Name": "web",
				"Meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"Sources": [
					{
						"Name": "foo",
						"Action": "deny",
						"Type": "consul",
						"Description": "foo desc"
					},
					{
						"Name": "bar",
						"Action": "allow",
						"Description": "bar desc"
					},
					{
						"Name": "l7",
						"Permissions": [
							{
								"Action": "deny",
								"HTTP": {
									"PathExact": "/admin",
									"Header": [
										{
											"Name": "hdr-present",
											"Present": true
										},
										{
											"Name": "hdr-exact",
											"Exact": "exact"
										},
										{
											"Name": "hdr-prefix",
											"Prefix": "prefix"
										},
										{
											"Name": "hdr-suffix",
											"Suffix": "suffix"
										},
										{
										    "Name": "hdr-contains",
										    "Contains": "contains"
										},
										{
											"Name": "hdr-regex",
											"Regex": "regex"
										},
										{
											"Name": "hdr-absent",
											"Present": true,
											"Invert": true
										},
										{
											"Name": "hdr-ignore-case",
											"Exact": "exact",
											"IgnoreCase": true
										}
									]
								}
							},
							{
								"Action": "allow",
								"HTTP": {
									"PathPrefix": "/v3/"
								}
							},
							{
								"Action": "allow",
								"HTTP": {
									"PathRegex": "/v[12]/.*",
									"Methods": [
										"GET",
										"POST"
									]
								}
							}
						]
					},
					{
						"Name": "*",
						"Action": "deny",
						"Description": "wild desc"
					}
				]
			}
			`,
			expect: &api.ServiceIntentionsConfigEntry{
				Kind: "service-intentions",
				Name: "web",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
				Sources: []*api.SourceIntention{
					{
						Name:        "foo",
						Action:      "deny",
						Type:        "consul",
						Description: "foo desc",
					},
					{
						Name:        "bar",
						Action:      "allow",
						Description: "bar desc",
					},
					{
						Name: "l7",
						Permissions: []*api.IntentionPermission{
							{
								Action: "deny",
								HTTP: &api.IntentionHTTPPermission{
									PathExact: "/admin",
									Header: []api.IntentionHTTPHeaderPermission{
										{
											Name:    "hdr-present",
											Present: true,
										},
										{
											Name:  "hdr-exact",
											Exact: "exact",
										},
										{
											Name:   "hdr-prefix",
											Prefix: "prefix",
										},
										{
											Name:   "hdr-suffix",
											Suffix: "suffix",
										},
										{
											Name:     "hdr-contains",
											Contains: "contains",
										},
										{
											Name:  "hdr-regex",
											Regex: "regex",
										},
										{
											Name:    "hdr-absent",
											Present: true,
											Invert:  true,
										},
										{
											Name:       "hdr-ignore-case",
											Exact:      "exact",
											IgnoreCase: true,
										},
									},
								},
							},
							{
								Action: "allow",
								HTTP: &api.IntentionHTTPPermission{
									PathPrefix: "/v3/",
								},
							},
							{
								Action: "allow",
								HTTP: &api.IntentionHTTPPermission{
									PathRegex: "/v[12]/.*",
									Methods:   []string{"GET", "POST"},
								},
							},
						},
					},
					{
						Name:        "*",
						Action:      "deny",
						Description: "wild desc",
					},
				},
			},
		},
		{
			name: "service-intentions: wildcard destination",
			snake: `
				kind = "service-intentions"
				name = "*"
				sources {
				  name   = "foo"
				  action = "deny"
				  # should be parsed, but we'll ignore it later
				  precedence = 6
				}
			`,
			camel: `
				Kind = "service-intentions"
				Name = "*"
				Sources {
				  Name   = "foo"
				  Action = "deny"
				  # should be parsed, but we'll ignore it later
				  Precedence = 6
				}
			`,
			snakeJSON: `
			{
				"kind": "service-intentions",
				"name": "*",
				"sources": [
					{
						"name": "foo",
						"action": "deny",
						"precedence": 6
					}
				]
			}
			`,
			camelJSON: `
			{
				"Kind": "service-intentions",
				"Name": "*",
				"Sources": [
					{
						"Name": "foo",
						"Action": "deny",
						"Precedence": 6
					}
				]
			}
			`,
			expect: &api.ServiceIntentionsConfigEntry{
				Kind: "service-intentions",
				Name: "*",
				Sources: []*api.SourceIntention{
					{
						Name:       "foo",
						Action:     "deny",
						Precedence: 6,
					},
				},
			},
		},
		{
			name: "mesh: kitchen sink",
			snake: `
				kind = "mesh"
				meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				transparent_proxy {
					mesh_destinations_only = true
				}
				allow_enabling_permissive_mutual_tls = true
                validate_clusters = true
				tls {
					incoming {
						tls_min_version = "TLSv1_1"
						tls_max_version = "TLSv1_2"
						cipher_suites = [
							"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
							"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
						]
					}
					outgoing {
						tls_min_version = "TLSv1_1"
						tls_max_version = "TLSv1_2"
						cipher_suites = [
							"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
							"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
						]
					}
				}
                http {
                    sanitize_x_forwarded_client_cert = true
                    incoming {
                        request_normalization {
                        	insecure_disable_path_normalization = true
                            merge_slashes = true
                            path_with_escaped_slashes_action = "UNESCAPE_AND_FORWARD"
							headers_with_underscores_action = "DROP_HEADER"
                        }
                    }
                }
				peering {
					peer_through_mesh_gateways = true
				}
			`,
			camel: `
				Kind = "mesh"
				Meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				TransparentProxy {
					MeshDestinationsOnly = true
				}
				AllowEnablingPermissiveMutualTLS = true
                ValidateClusters = true
				TLS {
					Incoming {
						TLSMinVersion = "TLSv1_1"
						TLSMaxVersion = "TLSv1_2"
						CipherSuites = [
							"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
							"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
						]
					}
					Outgoing {
						TLSMinVersion = "TLSv1_1"
						TLSMaxVersion = "TLSv1_2"
						CipherSuites = [
							"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
							"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
						]
					}
				}
                HTTP {
                    SanitizeXForwardedClientCert = true
                    Incoming {
                        RequestNormalization {
                        	InsecureDisablePathNormalization = true
                            MergeSlashes = true
                            PathWithEscapedSlashesAction = "UNESCAPE_AND_FORWARD"
							HeadersWithUnderscoresAction = "DROP_HEADER"
                        }
                    }
                }
				Peering {
					PeerThroughMeshGateways = true
				}
			`,
			snakeJSON: `
			{
				"kind": "mesh",
				"meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"transparent_proxy": {
					"mesh_destinations_only": true
				},
				"allow_enabling_permissive_mutual_tls": true,
                "validate_clusters": true,
				"tls": {
					"incoming": {
						"tls_min_version": "TLSv1_1",
						"tls_max_version": "TLSv1_2",
						"cipher_suites": [
							"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
							"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
						]
					},
					"outgoing": {
						"tls_min_version": "TLSv1_1",
						"tls_max_version": "TLSv1_2",
						"cipher_suites": [
							"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
							"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
						]
					}
				},
                "http": {
                    "sanitize_x_forwarded_client_cert": true,
                    "incoming": {
						"request_normalization": {
							"insecure_disable_path_normalization": true,
							"merge_slashes": true,
							"path_with_escaped_slashes_action": "UNESCAPE_AND_FORWARD",
							"headers_with_underscores_action": "DROP_HEADER"	
						}
					}
                },
				"peering": {
					"peer_through_mesh_gateways": true
				}
			}
			`,
			camelJSON: `
			{
				"Kind": "mesh",
				"Meta" : {
					"foo": "bar",
					"gir": "zim"
				},
				"TransparentProxy": {
					"MeshDestinationsOnly": true
				},
				"AllowEnablingPermissiveMutualTLS": true,
				"ValidateClusters": true,
				"TLS": {
					"Incoming": {
						"TLSMinVersion": "TLSv1_1",
						"TLSMaxVersion": "TLSv1_2",
						"CipherSuites": [
							"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
							"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
						]
					},
					"Outgoing": {
						"TLSMinVersion": "TLSv1_1",
						"TLSMaxVersion": "TLSv1_2",
						"CipherSuites": [
							"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
							"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
						]
					}
				},
				"HTTP": {
					"SanitizeXForwardedClientCert": true,
					"Incoming": {
						"RequestNormalization": {
							"InsecureDisablePathNormalization": true,
							"MergeSlashes": true,
							"PathWithEscapedSlashesAction": "UNESCAPE_AND_FORWARD",
							"HeadersWithUnderscoresAction": "DROP_HEADER"
						}
					}
				},
				"Peering": {
					"PeerThroughMeshGateways": true
				}
			}
			`,
			expect: &api.MeshConfigEntry{
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
				TransparentProxy: api.TransparentProxyMeshConfig{
					MeshDestinationsOnly: true,
				},
				AllowEnablingPermissiveMutualTLS: true,
				ValidateClusters:                 true,
				TLS: &api.MeshTLSConfig{
					Incoming: &api.MeshDirectionalTLSConfig{
						TLSMinVersion: "TLSv1_1",
						TLSMaxVersion: "TLSv1_2",
						CipherSuites: []string{
							"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
							"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
						},
					},
					Outgoing: &api.MeshDirectionalTLSConfig{
						TLSMinVersion: "TLSv1_1",
						TLSMaxVersion: "TLSv1_2",
						CipherSuites: []string{
							"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
							"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
						},
					},
				},
				HTTP: &api.MeshHTTPConfig{
					SanitizeXForwardedClientCert: true,
					Incoming: &api.MeshDirectionalHTTPConfig{
						RequestNormalization: &api.RequestNormalizationMeshConfig{
							InsecureDisablePathNormalization: true,
							MergeSlashes:                     true,
							PathWithEscapedSlashesAction:     "UNESCAPE_AND_FORWARD",
							HeadersWithUnderscoresAction:     "DROP_HEADER",
						},
					},
				},
				Peering: &api.PeeringMeshConfig{
					PeerThroughMeshGateways: true,
				},
			},
		},
		{
			name: "exported-services",
			snake: `
				kind = "exported-services"
				name = "foo"
				meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				services = [
					{
						name = "web"
						namespace = "foo"
						consumers = [
							{
								partition = "bar"
							},
							{
								partition = "baz"
							},
							{
								peer_name = "flarm"
							}
						]
					},
					{
						name = "db"
						namespace = "bar"
						consumers = [
							{
								partition = "zoo"
							}
						]
					}
				]
			`,
			camel: `
				Kind = "exported-services"
				Name = "foo"
				Meta {
					"foo" = "bar"
					"gir" = "zim"
				}
				Services = [
					{
						Name = "web"
						Namespace = "foo"
						Consumers = [
							{
								Partition = "bar"
							},
							{
								Partition = "baz"
							},
							{
								Peer = "flarm"
							}
						]
					},
					{
						Name = "db"
						Namespace = "bar"
						Consumers = [
							{
								Partition = "zoo"
							}
						]
					}
				]
			`,
			snakeJSON: `
			{
				"kind": "exported-services",
				"name": "foo",
				"meta": {
					"foo": "bar",
					"gir": "zim"
				},
				"services": [
					{
						"name": "web",
						"namespace": "foo",
						"consumers": [
							{
								"partition": "bar"
							},
							{
								"partition": "baz"
							},
							{
								"peer_name": "flarm"
							}
						]
					},
					{
						"name": "db",
						"namespace": "bar",
						"consumers": [
							{
								"partition": "zoo"
							}
						]
					}
				]
			}
			`,
			camelJSON: `
			{
				"Kind": "exported-services",
				"Name": "foo",
				"Meta": {
					"foo": "bar",
					"gir": "zim"
				},
				"Services": [
					{
						"Name": "web",
						"Namespace": "foo",
						"Consumers": [
							{
								"Partition": "bar"
							},
							{
								"Partition": "baz"
							},
							{
								"Peer": "flarm"
							}
						]
					},
					{
						"Name": "db",
						"Namespace": "bar",
						"Consumers": [
							{
								"Partition": "zoo"
							}
						]
					}
				]
			}
			`,
			expect: &api.ExportedServicesConfigEntry{
				Name: "foo",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
				Services: []api.ExportedService{
					{
						Name:      "web",
						Namespace: "foo",
						Consumers: []api.ServiceConsumer{
							{
								Partition: "bar",
							},
							{
								Partition: "baz",
							},
							{
								Peer: "flarm",
							},
						},
					},
					{
						Name:      "db",
						Namespace: "bar",
						Consumers: []api.ServiceConsumer{
							{
								Partition: "zoo",
							},
						},
					},
				},
			},
		},
		{
			name: "api-gateway",
			snake: `
				kind = "api-gateway"
				name = "main"
				meta {
					"foo" = "bar"
					"gir" = "zim"
				}
			`,
			camel: `
				Kind = "api-gateway"
				Name = "main"
				Meta {
					"foo" = "bar"
					"gir" = "zim"
				}
			`,
			snakeJSON: `
			{
				"kind": "api-gateway",
				"name": "main",
				"meta": {
					"foo": "bar",
					"gir": "zim"
				}
			}
			`,
			camelJSON: `
			{
				"Kind": "api-gateway",
				"Name": "main",
				"Meta": {
					"foo": "bar",
					"gir": "zim"
				}
			}`,
			expect: &api.APIGatewayConfigEntry{
				Kind: "api-gateway",
				Name: "main",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
			},
		},
		{
			name: "inline-certificate",
			snake: `
				kind = "inline-certificate"
				name = "main"
				meta {
					"foo" = "bar"
					"gir" = "zim"
				}
			`,
			camel: `
				Kind = "inline-certificate"
				Name = "main"
				Meta {
					"foo" = "bar"
					"gir" = "zim"
				}
			`,
			snakeJSON: `
			{
				"kind": "inline-certificate",
				"name": "main",
				"meta": {
					"foo": "bar",
					"gir": "zim"
				}
			}
			`,
			camelJSON: `
			{
				"Kind": "inline-certificate",
				"Name": "main",
				"Meta": {
					"foo": "bar",
					"gir": "zim"
				}
			}`,
			expect: &api.InlineCertificateConfigEntry{
				Kind: "inline-certificate",
				Name: "main",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
			},
		},
		{
			name: "http-route",
			snake: `
				kind = "http-route"
				name = "main"
				meta {
					"foo" = "bar"
					"gir" = "zim"
				}
			`,
			camel: `
				Kind = "http-route"
				Name = "main"
				Meta {
					"foo" = "bar"
					"gir" = "zim"
				}
			`,
			snakeJSON: `
			{
				"kind": "http-route",
				"name": "main",
				"meta": {
					"foo": "bar",
					"gir": "zim"
				}
			}
			`,
			camelJSON: `
			{
				"Kind": "http-route",
				"Name": "main",
				"Meta": {
					"foo": "bar",
					"gir": "zim"
				}
			}`,
			expect: &api.HTTPRouteConfigEntry{
				Kind: "http-route",
				Name: "main",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
			},
		},
		{
			name: "tcp-route",
			snake: `
				kind = "tcp-route"
				name = "main"
				meta {
					"foo" = "bar"
					"gir" = "zim"
				}
			`,
			camel: `
				Kind = "tcp-route"
				Name = "main"
				Meta {
					"foo" = "bar"
					"gir" = "zim"
				}
			`,
			snakeJSON: `
			{
				"kind": "tcp-route",
				"name": "main",
				"meta": {
					"foo": "bar",
					"gir": "zim"
				}
			}
			`,
			camelJSON: `
			{
				"Kind": "tcp-route",
				"Name": "main",
				"Meta": {
					"foo": "bar",
					"gir": "zim"
				}
			}`,
			expect: &api.TCPRouteConfigEntry{
				Kind: "tcp-route",
				Name: "main",
				Meta: map[string]string{
					"foo": "bar",
					"gir": "zim",
				},
			},
		},
	} {
		tc := tc

		testbody := func(t *testing.T, body string, expect api.ConfigEntry) {
			t.Helper()
			got, err := ParseConfigEntry(body)
			if tc.expectErr != "" {
				require.Nil(t, got)
				require.Error(t, err)
				requireContainsLower(t, err.Error(), tc.expectErr)
			} else {
				require.NoError(t, err)
				require.Equal(t, expect, got)
			}
		}

		t.Run(tc.name+" (hcl snake case)", func(t *testing.T) {
			testbody(t, tc.snake, tc.expect)
		})
		t.Run(tc.name+" (hcl camel case)", func(t *testing.T) {
			testbody(t, tc.camel, tc.expect)
		})
		if tc.snakeJSON != "" {
			t.Run(tc.name+" (json snake case)", func(t *testing.T) {
				if tc.expectJSON != nil {
					testbody(t, tc.snakeJSON, tc.expectJSON)
				} else {
					testbody(t, tc.snakeJSON, tc.expect)
				}
			})
		}
		if tc.camelJSON != "" {
			t.Run(tc.name+" (json camel case)", func(t *testing.T) {
				if tc.expectJSON != nil {
					testbody(t, tc.camelJSON, tc.expectJSON)
				} else {
					testbody(t, tc.camelJSON, tc.expect)
				}
			})
		}
	}
}

func requireContainsLower(t *testing.T, haystack, needle string) {
	t.Helper()
	require.Contains(t, strings.ToLower(haystack), strings.ToLower(needle))
}

func intPointer(v int) *int {
	return &v
}
