diff --git a/controllers/ratelimitpolicy_controller_test.go b/controllers/ratelimitpolicy_controller_test.go index d0af4ca75..3028aae08 100644 --- a/controllers/ratelimitpolicy_controller_test.go +++ b/controllers/ratelimitpolicy_controller_test.go @@ -23,6 +23,7 @@ import ( kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" "github.com/kuadrant/kuadrant-operator/pkg/common" "github.com/kuadrant/kuadrant-operator/pkg/rlptools" + "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" ) @@ -272,35 +273,35 @@ var _ = Describe("RateLimitPolicy controller", func() { Expect(err).ToNot(HaveOccurred()) existingWASMConfig, err := rlptools.WASMPluginFromStruct(existingWasmPlugin.Spec.PluginConfig) Expect(err).ToNot(HaveOccurred()) - Expect(existingWASMConfig).To(Equal(&rlptools.WASMPlugin{ - FailureMode: rlptools.FailureModeDeny, - RateLimitPolicies: []rlptools.RateLimitPolicy{ + Expect(existingWASMConfig).To(Equal(&wasm.WASMPlugin{ + FailureMode: wasm.FailureModeDeny, + RateLimitPolicies: []wasm.RateLimitPolicy{ { Name: "*.example.com", Domain: common.MarshallNamespace(client.ObjectKeyFromObject(gateway), "*.example.com"), Service: common.KuadrantRateLimitClusterName, Hostnames: []string{"*.example.com"}, - Rules: []rlptools.Rule{ + Rules: []wasm.Rule{ { - Conditions: []rlptools.Condition{ + Conditions: []wasm.Condition{ { - AllOf: []rlptools.PatternExpression{ + AllOf: []wasm.PatternExpression{ { Selector: "request.url_path", - Operator: rlptools.PatternOperator(kuadrantv1beta2.StartsWithOperator), + Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator), Value: "/toy", }, { Selector: "request.method", - Operator: rlptools.PatternOperator(kuadrantv1beta2.EqualOperator), + Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator), Value: "GET", }, }, }, }, - Data: []rlptools.DataItem{ + Data: []wasm.DataItem{ { - Static: &rlptools.StaticSpec{ + Static: &wasm.StaticSpec{ Key: fmt.Sprintf("%s/%s/l1", testNamespace, rlpName), Value: "1", }, @@ -400,20 +401,20 @@ var _ = Describe("RateLimitPolicy controller", func() { Expect(err).ToNot(HaveOccurred()) existingWASMConfig, err := rlptools.WASMPluginFromStruct(existingWasmPlugin.Spec.PluginConfig) Expect(err).ToNot(HaveOccurred()) - Expect(existingWASMConfig).To(Equal(&rlptools.WASMPlugin{ - FailureMode: rlptools.FailureModeDeny, - RateLimitPolicies: []rlptools.RateLimitPolicy{ + Expect(existingWASMConfig).To(Equal(&wasm.WASMPlugin{ + FailureMode: wasm.FailureModeDeny, + RateLimitPolicies: []wasm.RateLimitPolicy{ { Name: "*", Domain: common.MarshallNamespace(client.ObjectKeyFromObject(gateway), "*"), Service: common.KuadrantRateLimitClusterName, Hostnames: []string{"*"}, - Rules: []rlptools.Rule{ + Rules: []wasm.Rule{ { Conditions: nil, - Data: []rlptools.DataItem{ + Data: []wasm.DataItem{ { - Static: &rlptools.StaticSpec{ + Static: &wasm.StaticSpec{ Key: fmt.Sprintf("%s/%s/l1", testNamespace, rlpName), Value: "1", }, diff --git a/controllers/ratelimitpolicy_wasm_plugins.go b/controllers/ratelimitpolicy_wasm_plugins.go index 45530fa1b..ef393cc19 100644 --- a/controllers/ratelimitpolicy_wasm_plugins.go +++ b/controllers/ratelimitpolicy_wasm_plugins.go @@ -15,6 +15,7 @@ import ( "github.com/kuadrant/kuadrant-operator/pkg/common" "github.com/kuadrant/kuadrant-operator/pkg/reconcilers" "github.com/kuadrant/kuadrant-operator/pkg/rlptools" + "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" ) func (r *RateLimitPolicyReconciler) reconcileWASMPluginConf(ctx context.Context, rlp *kuadrantv1beta2.RateLimitPolicy, gwDiffObj *reconcilers.GatewayDiff) error { @@ -120,7 +121,7 @@ func (r *RateLimitPolicyReconciler) gatewayWASMPlugin(ctx context.Context, gw co // returns nil when there is no rate limit policy to apply func (r *RateLimitPolicyReconciler) wasmPluginConfig(ctx context.Context, - gw common.GatewayWrapper, rlpRefs []client.ObjectKey) (*rlptools.WASMPlugin, error) { + gw common.GatewayWrapper, rlpRefs []client.ObjectKey) (*wasm.WASMPlugin, error) { logger, _ := logr.FromContext(ctx) routeRLPList := make([]*kuadrantv1beta2.RateLimitPolicy, 0) @@ -171,14 +172,14 @@ func (r *RateLimitPolicyReconciler) wasmPluginConfig(ctx context.Context, } } - wasmPlugin := &rlptools.WASMPlugin{ - FailureMode: rlptools.FailureModeDeny, - RateLimitPolicies: make([]rlptools.RateLimitPolicy, 0), + wasmPlugin := &wasm.WASMPlugin{ + FailureMode: wasm.FailureModeDeny, + RateLimitPolicies: make([]wasm.RateLimitPolicy, 0), } // One RateLimitPolicy per domain for domain, rules := range wasmRulesByDomain { - rateLimitPolicy := rlptools.RateLimitPolicy{ + rateLimitPolicy := wasm.RateLimitPolicy{ Name: domain, Domain: common.MarshallNamespace(gw.Key(), domain), Service: common.KuadrantRateLimitClusterName, @@ -192,7 +193,7 @@ func (r *RateLimitPolicyReconciler) wasmPluginConfig(ctx context.Context, } // merge operations currently implemented with list append operation -func mergeRules(routeRLP *kuadrantv1beta2.RateLimitPolicy, gwRLP *kuadrantv1beta2.RateLimitPolicy, route *gatewayapiv1beta1.HTTPRoute) []rlptools.Rule { +func mergeRules(routeRLP *kuadrantv1beta2.RateLimitPolicy, gwRLP *kuadrantv1beta2.RateLimitPolicy, route *gatewayapiv1beta1.HTTPRoute) []wasm.Rule { routeRules := rlptools.WasmRules(routeRLP, route) if gwRLP == nil { diff --git a/pkg/rlptools/wasm/types.go b/pkg/rlptools/wasm/types.go new file mode 100644 index 000000000..d971bba57 --- /dev/null +++ b/pkg/rlptools/wasm/types.go @@ -0,0 +1,124 @@ +package wasm + +import ( + "encoding/json" + + _struct "github.com/golang/protobuf/ptypes/struct" + gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" +) + +var ( + PathMatchTypeMap = map[gatewayapiv1beta1.PathMatchType]PatternOperator{ + gatewayapiv1beta1.PathMatchExact: PatternOperator(kuadrantv1beta2.EqualOperator), + gatewayapiv1beta1.PathMatchPathPrefix: PatternOperator(kuadrantv1beta2.StartsWithOperator), + gatewayapiv1beta1.PathMatchRegularExpression: PatternOperator(kuadrantv1beta2.MatchesOperator), + } +) + +type SelectorSpec struct { + // Selector of an attribute from the contextual properties provided by Envoy + // during request and connection processing + // https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes + // They are named by a dot-separated path (e.g. request.path) + // Examples: + // "request.path" -> The path portion of the URL + Selector string `json:"selector"` + + // If not set it defaults to `selector` field value as the descriptor key. + // +optional + Key *string `json:"key,omitempty"` + + // An optional value to use if the selector is not found in the context. + // If not set and the selector is not found in the context, then no descriptor is generated. + // +optional + Default *string `json:"default,omitempty"` +} + +type StaticSpec struct { + Value string `json:"value"` + Key string `json:"key"` +} + +// TODO implement one of constraint +// Precisely one of "static", "selector" must be set. +type DataItem struct { + // +optional + Static *StaticSpec `json:"static,omitempty"` + + // +optional + Selector *SelectorSpec `json:"selector,omitempty"` +} + +type PatternOperator kuadrantv1beta2.WhenConditionOperator + +type PatternExpression struct { + // Selector of an attribute from the contextual properties provided by Envoy + // during request and connection processing + // https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes + // They are named by a dot-separated path (e.g. request.path) + // Examples: + // "request.path" -> The path portion of the URL + Selector string `json:"selector"` + + // The binary operator to be applied to the content fetched from context, for comparison with "value". + // Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + // TODO build comprehensive list of operators + Operator PatternOperator `json:"operator"` + + // The value of reference for the comparison with the content fetched from the context. + Value string `json:"value"` +} + +// Condition defines traffic matching rules +type Condition struct { + // All of the expressions defined must match to match this condition + // +optional + AllOf []PatternExpression `json:"allOf,omitempty"` +} + +// Rule defines one rate limit configuration. When conditions are met, +// it uses `data` section to generate one RLS descriptor. +type Rule struct { + // +optional + Conditions []Condition `json:"conditions,omitempty"` + // +optional + Data []DataItem `json:"data,omitempty"` +} + +type RateLimitPolicy struct { + Name string `json:"name"` + Domain string `json:"domain"` + Service string `json:"service"` + Hostnames []string `json:"hostnames"` + + // +optional + Rules []Rule `json:"rules,omitempty"` +} + +// +kubebuilder:validation:Enum:=deny;allow +type FailureModeType string + +const ( + FailureModeDeny FailureModeType = "deny" + FailureModeAllow FailureModeType = "allow" +) + +type WASMPlugin struct { + FailureMode FailureModeType `json:"failureMode"` + RateLimitPolicies []RateLimitPolicy `json:"rateLimitPolicies"` +} + +func (w *WASMPlugin) ToStruct() (*_struct.Struct, error) { + wasmPluginJSON, err := json.Marshal(w) + if err != nil { + return nil, err + } + + pluginConfigStruct := &_struct.Struct{} + if err := pluginConfigStruct.UnmarshalJSON(wasmPluginJSON); err != nil { + return nil, err + } + return pluginConfigStruct, nil +} diff --git a/pkg/rlptools/wasm_utils.go b/pkg/rlptools/wasm_utils.go index 3c8364bf8..c4756ecb0 100644 --- a/pkg/rlptools/wasm_utils.go +++ b/pkg/rlptools/wasm_utils.go @@ -13,127 +13,16 @@ import ( kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" "github.com/kuadrant/kuadrant-operator/pkg/common" + "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" ) var ( WASMFilterImageURL = common.FetchEnv("RELATED_IMAGE_WASMSHIM", "oci://quay.io/kuadrant/wasm-shim:latest") - - PathMatchTypeMap = map[gatewayapiv1beta1.PathMatchType]PatternOperator{ - gatewayapiv1beta1.PathMatchExact: PatternOperator(kuadrantv1beta2.EqualOperator), - gatewayapiv1beta1.PathMatchPathPrefix: PatternOperator(kuadrantv1beta2.StartsWithOperator), - gatewayapiv1beta1.PathMatchRegularExpression: PatternOperator(kuadrantv1beta2.MatchesOperator), - } -) - -type SelectorSpec struct { - // Selector of an attribute from the contextual properties provided by Envoy - // during request and connection processing - // https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes - // They are named by a dot-separated path (e.g. request.path) - // Examples: - // "request.path" -> The path portion of the URL - Selector string `json:"selector"` - - // If not set it defaults to `selector` field value as the descriptor key. - // +optional - Key *string `json:"key,omitempty"` - - // An optional value to use if the selector is not found in the context. - // If not set and the selector is not found in the context, then no descriptor is generated. - // +optional - Default *string `json:"default,omitempty"` -} - -type StaticSpec struct { - Value string `json:"value"` - Key string `json:"key"` -} - -// TODO implement one of constraint -// Precisely one of "static", "selector" must be set. -type DataItem struct { - // +optional - Static *StaticSpec `json:"static,omitempty"` - - // +optional - Selector *SelectorSpec `json:"selector,omitempty"` -} - -type PatternOperator kuadrantv1beta2.WhenConditionOperator - -type PatternExpression struct { - // Selector of an attribute from the contextual properties provided by Envoy - // during request and connection processing - // https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes - // They are named by a dot-separated path (e.g. request.path) - // Examples: - // "request.path" -> The path portion of the URL - Selector string `json:"selector"` - - // The binary operator to be applied to the content fetched from context, for comparison with "value". - // Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - // TODO build comprehensive list of operators - Operator PatternOperator `json:"operator"` - - // The value of reference for the comparison with the content fetched from the context. - Value string `json:"value"` -} - -// Condition defines traffic matching rules -type Condition struct { - // All of the expressions defined must match to match this condition - // +optional - AllOf []PatternExpression `json:"allOf,omitempty"` -} - -// Rule defines one rate limit configuration. When conditions are met, -// it uses `data` section to generate one RLS descriptor. -type Rule struct { - // +optional - Conditions []Condition `json:"conditions,omitempty"` - // +optional - Data []DataItem `json:"data,omitempty"` -} - -type RateLimitPolicy struct { - Name string `json:"name"` - Domain string `json:"domain"` - Service string `json:"service"` - Hostnames []string `json:"hostnames"` - - // +optional - Rules []Rule `json:"rules,omitempty"` -} - -// +kubebuilder:validation:Enum:=deny;allow -type FailureModeType string - -const ( - FailureModeDeny FailureModeType = "deny" - FailureModeAllow FailureModeType = "allow" ) -type WASMPlugin struct { - FailureMode FailureModeType `json:"failureMode"` - RateLimitPolicies []RateLimitPolicy `json:"rateLimitPolicies"` -} - -func (w *WASMPlugin) ToStruct() (*_struct.Struct, error) { - wasmPluginJSON, err := json.Marshal(w) - if err != nil { - return nil, err - } - - pluginConfigStruct := &_struct.Struct{} - if err := pluginConfigStruct.UnmarshalJSON(wasmPluginJSON); err != nil { - return nil, err - } - return pluginConfigStruct, nil -} - // WasmRules computes WASM rules from the policy and the targeted Route (which can be nil when a gateway is targeted) -func WasmRules(rlp *kuadrantv1beta2.RateLimitPolicy, route *gatewayapiv1beta1.HTTPRoute) []Rule { - rules := make([]Rule, 0) +func WasmRules(rlp *kuadrantv1beta2.RateLimitPolicy, route *gatewayapiv1beta1.HTTPRoute) []wasm.Rule { + rules := make([]wasm.Rule, 0) if rlp == nil { return rules } @@ -149,31 +38,31 @@ func WasmRules(rlp *kuadrantv1beta2.RateLimitPolicy, route *gatewayapiv1beta1.HT return rules } -func ruleFromLimit(limitFullName string, limit *kuadrantv1beta2.Limit, route *gatewayapiv1beta1.HTTPRoute) Rule { +func ruleFromLimit(limitFullName string, limit *kuadrantv1beta2.Limit, route *gatewayapiv1beta1.HTTPRoute) wasm.Rule { if limit == nil { - return Rule{} + return wasm.Rule{} } - return Rule{ + return wasm.Rule{ Conditions: conditionsFromLimit(limit, route), Data: dataFromLimt(limitFullName, limit), } } -func conditionsFromLimit(limit *kuadrantv1beta2.Limit, route *gatewayapiv1beta1.HTTPRoute) []Condition { +func conditionsFromLimit(limit *kuadrantv1beta2.Limit, route *gatewayapiv1beta1.HTTPRoute) []wasm.Condition { if limit == nil { - return make([]Condition, 0) + return make([]wasm.Condition, 0) } // TODO(eastizle): review this implementation. This is a first naive implementation. // The conditions must always be a subset of the route's matching rules. - conditions := make([]Condition, 0) + conditions := make([]wasm.Condition, 0) for routeSelectorIdx := range limit.RouteSelectors { // TODO(eastizle): what if there are only Hostnames (i.e. empty "matches" list) for matchIdx := range limit.RouteSelectors[routeSelectorIdx].Matches { - condition := Condition{ + condition := wasm.Condition{ AllOf: patternExpresionsFromMatch(&limit.RouteSelectors[routeSelectorIdx].Matches[matchIdx]), } @@ -202,17 +91,17 @@ func conditionsFromLimit(limit *kuadrantv1beta2.Limit, route *gatewayapiv1beta1. return conditions } -func conditionsFromRoute(route *gatewayapiv1beta1.HTTPRoute) []Condition { +func conditionsFromRoute(route *gatewayapiv1beta1.HTTPRoute) []wasm.Condition { if route == nil { - return make([]Condition, 0) + return make([]wasm.Condition, 0) } - conditions := make([]Condition, 0) + conditions := make([]wasm.Condition, 0) for ruleIdx := range route.Spec.Rules { // One condition per match for matchIdx := range route.Spec.Rules[ruleIdx].Matches { - conditions = append(conditions, Condition{ + conditions = append(conditions, wasm.Condition{ AllOf: patternExpresionsFromMatch(&route.Spec.Rules[ruleIdx].Matches[matchIdx]), }) } @@ -221,14 +110,14 @@ func conditionsFromRoute(route *gatewayapiv1beta1.HTTPRoute) []Condition { return conditions } -func patternExpresionsFromMatch(match *gatewayapiv1beta1.HTTPRouteMatch) []PatternExpression { +func patternExpresionsFromMatch(match *gatewayapiv1beta1.HTTPRouteMatch) []wasm.PatternExpression { // TODO(eastizle): only paths and methods implemented if match == nil { - return make([]PatternExpression, 0) + return make([]wasm.PatternExpression, 0) } - expressions := make([]PatternExpression, 0) + expressions := make([]wasm.PatternExpression, 0) if match.Path != nil { expressions = append(expressions, patternExpresionFromPathMatch(*match.Path)) @@ -241,11 +130,11 @@ func patternExpresionsFromMatch(match *gatewayapiv1beta1.HTTPRouteMatch) []Patte return expressions } -func patternExpresionFromPathMatch(pathMatch gatewayapiv1beta1.HTTPPathMatch) PatternExpression { +func patternExpresionFromPathMatch(pathMatch gatewayapiv1beta1.HTTPPathMatch) wasm.PatternExpression { var ( - operator PatternOperator = PatternOperator(kuadrantv1beta2.StartsWithOperator) // default value - value string = "/" // default value + operator wasm.PatternOperator = wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator) // default value + value string = "/" // default value ) if pathMatch.Value != nil { @@ -253,60 +142,60 @@ func patternExpresionFromPathMatch(pathMatch gatewayapiv1beta1.HTTPPathMatch) Pa } if pathMatch.Type != nil { - if val, ok := PathMatchTypeMap[*pathMatch.Type]; ok { + if val, ok := wasm.PathMatchTypeMap[*pathMatch.Type]; ok { operator = val } } - return PatternExpression{ + return wasm.PatternExpression{ Selector: "request.url_path", Operator: operator, Value: value, } } -func patternExpresionFromMethod(method gatewayapiv1beta1.HTTPMethod) PatternExpression { - return PatternExpression{ +func patternExpresionFromMethod(method gatewayapiv1beta1.HTTPMethod) wasm.PatternExpression { + return wasm.PatternExpression{ Selector: "request.method", - Operator: PatternOperator(kuadrantv1beta2.EqualOperator), + Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator), Value: string(method), } } -func patternExpresionFromWhen(when kuadrantv1beta2.WhenCondition) PatternExpression { - return PatternExpression{ +func patternExpresionFromWhen(when kuadrantv1beta2.WhenCondition) wasm.PatternExpression { + return wasm.PatternExpression{ Selector: string(when.Selector), - Operator: PatternOperator(when.Operator), + Operator: wasm.PatternOperator(when.Operator), Value: when.Value, } } -func patternExpresionFromHostname(hostname gatewayapiv1beta1.Hostname) PatternExpression { - return PatternExpression{ +func patternExpresionFromHostname(hostname gatewayapiv1beta1.Hostname) wasm.PatternExpression { + return wasm.PatternExpression{ Selector: "request.host", Operator: "eq", Value: string(hostname), } } -func dataFromLimt(limitFullName string, limit *kuadrantv1beta2.Limit) []DataItem { +func dataFromLimt(limitFullName string, limit *kuadrantv1beta2.Limit) []wasm.DataItem { if limit == nil { - return make([]DataItem, 0) + return make([]wasm.DataItem, 0) } - data := make([]DataItem, 0) + data := make([]wasm.DataItem, 0) // static key representing the limit - data = append(data, DataItem{Static: &StaticSpec{Key: limitFullName, Value: "1"}}) + data = append(data, wasm.DataItem{Static: &wasm.StaticSpec{Key: limitFullName, Value: "1"}}) for _, counter := range limit.Counters { - data = append(data, DataItem{Selector: &SelectorSpec{Selector: string(counter)}}) + data = append(data, wasm.DataItem{Selector: &wasm.SelectorSpec{Selector: string(counter)}}) } return data } -func WASMPluginFromStruct(structure *_struct.Struct) (*WASMPlugin, error) { +func WASMPluginFromStruct(structure *_struct.Struct) (*wasm.WASMPlugin, error) { if structure == nil { return nil, errors.New("cannot desestructure WASMPlugin from nil") } @@ -316,7 +205,7 @@ func WASMPluginFromStruct(structure *_struct.Struct) (*WASMPlugin, error) { return nil, err } // Deserialize struct into PluginConfig struct - wasmPlugin := &WASMPlugin{} + wasmPlugin := &wasm.WASMPlugin{} if err := json.Unmarshal(configJSON, wasmPlugin); err != nil { return nil, err } @@ -324,7 +213,7 @@ func WASMPluginFromStruct(structure *_struct.Struct) (*WASMPlugin, error) { return wasmPlugin, nil } -type WasmRulesByDomain map[string][]Rule +type WasmRulesByDomain map[string][]wasm.Rule func WASMPluginMutator(existingObj, desiredObj client.Object) (bool, error) { update := false diff --git a/pkg/rlptools/wasm_utils_test.go b/pkg/rlptools/wasm_utils_test.go index 3157a96af..573308caf 100644 --- a/pkg/rlptools/wasm_utils_test.go +++ b/pkg/rlptools/wasm_utils_test.go @@ -11,6 +11,7 @@ import ( "github.com/google/go-cmp/cmp" kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" + "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" ) // TODO(eastizle): missing WASMPluginMutator tests @@ -53,26 +54,26 @@ func TestWasmRules(t *testing.T) { }, } - expectedRule := Rule{ - Conditions: []Condition{ + expectedRule := wasm.Rule{ + Conditions: []wasm.Condition{ { - AllOf: []PatternExpression{ + AllOf: []wasm.PatternExpression{ { Selector: "request.url_path", - Operator: PatternOperator(kuadrantv1beta2.StartsWithOperator), + Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator), Value: "/toy", }, { Selector: "request.method", - Operator: PatternOperator(kuadrantv1beta2.EqualOperator), + Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator), Value: "GET", }, }, }, }, - Data: []DataItem{ + Data: []wasm.DataItem{ { - Static: &StaticSpec{ + Static: &wasm.StaticSpec{ Key: "nsA/rlpA/l1", Value: "1", },