From 4c16202a99153280471c3010622437056073b8e8 Mon Sep 17 00:00:00 2001 From: Dominik Korittki <23359034+dkorittki@users.noreply.github.com> Date: Sun, 3 May 2026 16:02:52 +0200 Subject: [PATCH 01/15] feat: add ActiveGraphs mapper type to node.proto --- proto/wg/cosmo/node/v1/node.proto | 6 + router/gen/proto/wg/cosmo/node/v1/node.pb.go | 808 ++++++++++--------- 2 files changed, 437 insertions(+), 377 deletions(-) diff --git a/proto/wg/cosmo/node/v1/node.proto b/proto/wg/cosmo/node/v1/node.proto index af7bd79bb4..13df316675 100644 --- a/proto/wg/cosmo/node/v1/node.proto +++ b/proto/wg/cosmo/node/v1/node.proto @@ -29,6 +29,12 @@ message RouterConfig { string compatibility_version = 5; } +message ActiveGraphs { + // key = feature flag name or empty if base graph + // value = hash of engine config + map graph_configs = 1; +} + message Response { wg.cosmo.common.EnumStatusCode code = 1; // details is an optional field which can be used to provide more details about the error. diff --git a/router/gen/proto/wg/cosmo/node/v1/node.pb.go b/router/gen/proto/wg/cosmo/node/v1/node.pb.go index 0384fac5a6..2fa65342c4 100644 --- a/router/gen/proto/wg/cosmo/node/v1/node.pb.go +++ b/router/gen/proto/wg/cosmo/node/v1/node.pb.go @@ -669,6 +669,52 @@ func (x *RouterConfig) GetCompatibilityVersion() string { return "" } +type ActiveGraphs struct { + state protoimpl.MessageState `protogen:"open.v1"` + // key = feature flag name or empty if base graph + // value = hash of engine config + GraphConfigs map[string]string `protobuf:"bytes,1,rep,name=graph_configs,json=graphConfigs,proto3" json:"graph_configs,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ActiveGraphs) Reset() { + *x = ActiveGraphs{} + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ActiveGraphs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ActiveGraphs) ProtoMessage() {} + +func (x *ActiveGraphs) ProtoReflect() protoreflect.Message { + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ActiveGraphs.ProtoReflect.Descriptor instead. +func (*ActiveGraphs) Descriptor() ([]byte, []int) { + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{4} +} + +func (x *ActiveGraphs) GetGraphConfigs() map[string]string { + if x != nil { + return x.GraphConfigs + } + return nil +} + type Response struct { state protoimpl.MessageState `protogen:"open.v1"` Code common.EnumStatusCode `protobuf:"varint,1,opt,name=code,proto3,enum=wg.cosmo.common.EnumStatusCode" json:"code,omitempty"` @@ -680,7 +726,7 @@ type Response struct { func (x *Response) Reset() { *x = Response{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[4] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -692,7 +738,7 @@ func (x *Response) String() string { func (*Response) ProtoMessage() {} func (x *Response) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[4] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -705,7 +751,7 @@ func (x *Response) ProtoReflect() protoreflect.Message { // Deprecated: Use Response.ProtoReflect.Descriptor instead. func (*Response) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{4} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{5} } func (x *Response) GetCode() common.EnumStatusCode { @@ -732,7 +778,7 @@ type ResponseStatus struct { func (x *ResponseStatus) Reset() { *x = ResponseStatus{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[5] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -744,7 +790,7 @@ func (x *ResponseStatus) String() string { func (*ResponseStatus) ProtoMessage() {} func (x *ResponseStatus) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[5] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -757,7 +803,7 @@ func (x *ResponseStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ResponseStatus.ProtoReflect.Descriptor instead. func (*ResponseStatus) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{5} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{6} } func (x *ResponseStatus) GetCode() int32 { @@ -784,7 +830,7 @@ type RegistrationInfo struct { func (x *RegistrationInfo) Reset() { *x = RegistrationInfo{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[6] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -796,7 +842,7 @@ func (x *RegistrationInfo) String() string { func (*RegistrationInfo) ProtoMessage() {} func (x *RegistrationInfo) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[6] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -809,7 +855,7 @@ func (x *RegistrationInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use RegistrationInfo.ProtoReflect.Descriptor instead. func (*RegistrationInfo) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{6} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{7} } func (x *RegistrationInfo) GetAccountLimits() *AccountLimits { @@ -837,7 +883,7 @@ type AccountLimits struct { func (x *AccountLimits) Reset() { *x = AccountLimits{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[7] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -849,7 +895,7 @@ func (x *AccountLimits) String() string { func (*AccountLimits) ProtoMessage() {} func (x *AccountLimits) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[7] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -862,7 +908,7 @@ func (x *AccountLimits) ProtoReflect() protoreflect.Message { // Deprecated: Use AccountLimits.ProtoReflect.Descriptor instead. func (*AccountLimits) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{7} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{8} } func (x *AccountLimits) GetTraceSamplingRate() float32 { @@ -880,7 +926,7 @@ type SelfRegisterRequest struct { func (x *SelfRegisterRequest) Reset() { *x = SelfRegisterRequest{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[8] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -892,7 +938,7 @@ func (x *SelfRegisterRequest) String() string { func (*SelfRegisterRequest) ProtoMessage() {} func (x *SelfRegisterRequest) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[8] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -905,7 +951,7 @@ func (x *SelfRegisterRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SelfRegisterRequest.ProtoReflect.Descriptor instead. func (*SelfRegisterRequest) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{8} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{9} } type SelfRegisterResponse struct { @@ -918,7 +964,7 @@ type SelfRegisterResponse struct { func (x *SelfRegisterResponse) Reset() { *x = SelfRegisterResponse{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[9] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -930,7 +976,7 @@ func (x *SelfRegisterResponse) String() string { func (*SelfRegisterResponse) ProtoMessage() {} func (x *SelfRegisterResponse) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[9] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -943,7 +989,7 @@ func (x *SelfRegisterResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SelfRegisterResponse.ProtoReflect.Descriptor instead. func (*SelfRegisterResponse) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{9} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{10} } func (x *SelfRegisterResponse) GetResponse() *Response { @@ -975,7 +1021,7 @@ type EngineConfiguration struct { func (x *EngineConfiguration) Reset() { *x = EngineConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[10] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -987,7 +1033,7 @@ func (x *EngineConfiguration) String() string { func (*EngineConfiguration) ProtoMessage() {} func (x *EngineConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[10] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1000,7 +1046,7 @@ func (x *EngineConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use EngineConfiguration.ProtoReflect.Descriptor instead. func (*EngineConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{10} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{11} } func (x *EngineConfiguration) GetDefaultFlushInterval() int64 { @@ -1076,7 +1122,7 @@ type DataSourceConfiguration struct { func (x *DataSourceConfiguration) Reset() { *x = DataSourceConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[11] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1088,7 +1134,7 @@ func (x *DataSourceConfiguration) String() string { func (*DataSourceConfiguration) ProtoMessage() {} func (x *DataSourceConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[11] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1101,7 +1147,7 @@ func (x *DataSourceConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use DataSourceConfiguration.ProtoReflect.Descriptor instead. func (*DataSourceConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{11} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{12} } func (x *DataSourceConfiguration) GetKind() DataSourceKind { @@ -1228,7 +1274,7 @@ type CostConfiguration struct { func (x *CostConfiguration) Reset() { *x = CostConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[12] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1240,7 +1286,7 @@ func (x *CostConfiguration) String() string { func (*CostConfiguration) ProtoMessage() {} func (x *CostConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[12] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1253,7 +1299,7 @@ func (x *CostConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use CostConfiguration.ProtoReflect.Descriptor instead. func (*CostConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{12} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{13} } func (x *CostConfiguration) GetFieldWeights() []*FieldWeightConfiguration { @@ -1297,7 +1343,7 @@ type FieldWeightConfiguration struct { func (x *FieldWeightConfiguration) Reset() { *x = FieldWeightConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[13] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1309,7 +1355,7 @@ func (x *FieldWeightConfiguration) String() string { func (*FieldWeightConfiguration) ProtoMessage() {} func (x *FieldWeightConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[13] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1322,7 +1368,7 @@ func (x *FieldWeightConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldWeightConfiguration.ProtoReflect.Descriptor instead. func (*FieldWeightConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{13} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{14} } func (x *FieldWeightConfiguration) GetTypeName() string { @@ -1374,7 +1420,7 @@ type FieldListSizeConfiguration struct { func (x *FieldListSizeConfiguration) Reset() { *x = FieldListSizeConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[14] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1386,7 +1432,7 @@ func (x *FieldListSizeConfiguration) String() string { func (*FieldListSizeConfiguration) ProtoMessage() {} func (x *FieldListSizeConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[14] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1399,7 +1445,7 @@ func (x *FieldListSizeConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldListSizeConfiguration.ProtoReflect.Descriptor instead. func (*FieldListSizeConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{14} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{15} } func (x *FieldListSizeConfiguration) GetTypeName() string { @@ -1454,7 +1500,7 @@ type ArgumentConfiguration struct { func (x *ArgumentConfiguration) Reset() { *x = ArgumentConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[15] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1466,7 +1512,7 @@ func (x *ArgumentConfiguration) String() string { func (*ArgumentConfiguration) ProtoMessage() {} func (x *ArgumentConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[15] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1479,7 +1525,7 @@ func (x *ArgumentConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use ArgumentConfiguration.ProtoReflect.Descriptor instead. func (*ArgumentConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{15} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{16} } func (x *ArgumentConfiguration) GetName() string { @@ -1505,7 +1551,7 @@ type Scopes struct { func (x *Scopes) Reset() { *x = Scopes{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[16] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1517,7 +1563,7 @@ func (x *Scopes) String() string { func (*Scopes) ProtoMessage() {} func (x *Scopes) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[16] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1530,7 +1576,7 @@ func (x *Scopes) ProtoReflect() protoreflect.Message { // Deprecated: Use Scopes.ProtoReflect.Descriptor instead. func (*Scopes) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{16} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{17} } func (x *Scopes) GetRequiredAndScopes() []string { @@ -1551,7 +1597,7 @@ type AuthorizationConfiguration struct { func (x *AuthorizationConfiguration) Reset() { *x = AuthorizationConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[17] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1563,7 +1609,7 @@ func (x *AuthorizationConfiguration) String() string { func (*AuthorizationConfiguration) ProtoMessage() {} func (x *AuthorizationConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[17] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1576,7 +1622,7 @@ func (x *AuthorizationConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use AuthorizationConfiguration.ProtoReflect.Descriptor instead. func (*AuthorizationConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{17} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{18} } func (x *AuthorizationConfiguration) GetRequiresAuthentication() bool { @@ -1613,7 +1659,7 @@ type FieldConfiguration struct { func (x *FieldConfiguration) Reset() { *x = FieldConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[18] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1625,7 +1671,7 @@ func (x *FieldConfiguration) String() string { func (*FieldConfiguration) ProtoMessage() {} func (x *FieldConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[18] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1638,7 +1684,7 @@ func (x *FieldConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldConfiguration.ProtoReflect.Descriptor instead. func (*FieldConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{18} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{19} } func (x *FieldConfiguration) GetTypeName() string { @@ -1686,7 +1732,7 @@ type TypeConfiguration struct { func (x *TypeConfiguration) Reset() { *x = TypeConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[19] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1698,7 +1744,7 @@ func (x *TypeConfiguration) String() string { func (*TypeConfiguration) ProtoMessage() {} func (x *TypeConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[19] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1711,7 +1757,7 @@ func (x *TypeConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use TypeConfiguration.ProtoReflect.Descriptor instead. func (*TypeConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{19} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{20} } func (x *TypeConfiguration) GetTypeName() string { @@ -1740,7 +1786,7 @@ type TypeField struct { func (x *TypeField) Reset() { *x = TypeField{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[20] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1752,7 +1798,7 @@ func (x *TypeField) String() string { func (*TypeField) ProtoMessage() {} func (x *TypeField) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[20] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1765,7 +1811,7 @@ func (x *TypeField) ProtoReflect() protoreflect.Message { // Deprecated: Use TypeField.ProtoReflect.Descriptor instead. func (*TypeField) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{20} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{21} } func (x *TypeField) GetTypeName() string { @@ -1806,7 +1852,7 @@ type FieldCoordinates struct { func (x *FieldCoordinates) Reset() { *x = FieldCoordinates{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[21] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1818,7 +1864,7 @@ func (x *FieldCoordinates) String() string { func (*FieldCoordinates) ProtoMessage() {} func (x *FieldCoordinates) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[21] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1831,7 +1877,7 @@ func (x *FieldCoordinates) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldCoordinates.ProtoReflect.Descriptor instead. func (*FieldCoordinates) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{21} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{22} } func (x *FieldCoordinates) GetFieldName() string { @@ -1858,7 +1904,7 @@ type FieldSetCondition struct { func (x *FieldSetCondition) Reset() { *x = FieldSetCondition{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[22] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1870,7 +1916,7 @@ func (x *FieldSetCondition) String() string { func (*FieldSetCondition) ProtoMessage() {} func (x *FieldSetCondition) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[22] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1883,7 +1929,7 @@ func (x *FieldSetCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldSetCondition.ProtoReflect.Descriptor instead. func (*FieldSetCondition) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{22} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{23} } func (x *FieldSetCondition) GetFieldCoordinatesPath() []*FieldCoordinates { @@ -1913,7 +1959,7 @@ type RequiredField struct { func (x *RequiredField) Reset() { *x = RequiredField{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[23] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1925,7 +1971,7 @@ func (x *RequiredField) String() string { func (*RequiredField) ProtoMessage() {} func (x *RequiredField) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[23] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1938,7 +1984,7 @@ func (x *RequiredField) ProtoReflect() protoreflect.Message { // Deprecated: Use RequiredField.ProtoReflect.Descriptor instead. func (*RequiredField) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{23} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{24} } func (x *RequiredField) GetTypeName() string { @@ -1986,7 +2032,7 @@ type EntityInterfaceConfiguration struct { func (x *EntityInterfaceConfiguration) Reset() { *x = EntityInterfaceConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[24] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1998,7 +2044,7 @@ func (x *EntityInterfaceConfiguration) String() string { func (*EntityInterfaceConfiguration) ProtoMessage() {} func (x *EntityInterfaceConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[24] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2011,7 +2057,7 @@ func (x *EntityInterfaceConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use EntityInterfaceConfiguration.ProtoReflect.Descriptor instead. func (*EntityInterfaceConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{24} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{25} } func (x *EntityInterfaceConfiguration) GetInterfaceTypeName() string { @@ -2054,7 +2100,7 @@ type FetchConfiguration struct { func (x *FetchConfiguration) Reset() { *x = FetchConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[25] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2066,7 +2112,7 @@ func (x *FetchConfiguration) String() string { func (*FetchConfiguration) ProtoMessage() {} func (x *FetchConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[25] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2079,7 +2125,7 @@ func (x *FetchConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use FetchConfiguration.ProtoReflect.Descriptor instead. func (*FetchConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{25} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{26} } func (x *FetchConfiguration) GetUrl() *ConfigurationVariable { @@ -2163,7 +2209,7 @@ type StatusCodeTypeMapping struct { func (x *StatusCodeTypeMapping) Reset() { *x = StatusCodeTypeMapping{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[26] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2175,7 +2221,7 @@ func (x *StatusCodeTypeMapping) String() string { func (*StatusCodeTypeMapping) ProtoMessage() {} func (x *StatusCodeTypeMapping) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[26] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2188,7 +2234,7 @@ func (x *StatusCodeTypeMapping) ProtoReflect() protoreflect.Message { // Deprecated: Use StatusCodeTypeMapping.ProtoReflect.Descriptor instead. func (*StatusCodeTypeMapping) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{26} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{27} } func (x *StatusCodeTypeMapping) GetStatusCode() int64 { @@ -2226,7 +2272,7 @@ type DataSourceCustom_GraphQL struct { func (x *DataSourceCustom_GraphQL) Reset() { *x = DataSourceCustom_GraphQL{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[27] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2238,7 +2284,7 @@ func (x *DataSourceCustom_GraphQL) String() string { func (*DataSourceCustom_GraphQL) ProtoMessage() {} func (x *DataSourceCustom_GraphQL) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[27] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2251,7 +2297,7 @@ func (x *DataSourceCustom_GraphQL) ProtoReflect() protoreflect.Message { // Deprecated: Use DataSourceCustom_GraphQL.ProtoReflect.Descriptor instead. func (*DataSourceCustom_GraphQL) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{27} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{28} } func (x *DataSourceCustom_GraphQL) GetFetch() *FetchConfiguration { @@ -2307,7 +2353,7 @@ type GRPCConfiguration struct { func (x *GRPCConfiguration) Reset() { *x = GRPCConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[28] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2319,7 +2365,7 @@ func (x *GRPCConfiguration) String() string { func (*GRPCConfiguration) ProtoMessage() {} func (x *GRPCConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[28] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2332,7 +2378,7 @@ func (x *GRPCConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use GRPCConfiguration.ProtoReflect.Descriptor instead. func (*GRPCConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{28} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{29} } func (x *GRPCConfiguration) GetMapping() *GRPCMapping { @@ -2366,7 +2412,7 @@ type ImageReference struct { func (x *ImageReference) Reset() { *x = ImageReference{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[29] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2378,7 +2424,7 @@ func (x *ImageReference) String() string { func (*ImageReference) ProtoMessage() {} func (x *ImageReference) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[29] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2391,7 +2437,7 @@ func (x *ImageReference) ProtoReflect() protoreflect.Message { // Deprecated: Use ImageReference.ProtoReflect.Descriptor instead. func (*ImageReference) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{29} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{30} } func (x *ImageReference) GetRepository() string { @@ -2421,7 +2467,7 @@ type PluginConfiguration struct { func (x *PluginConfiguration) Reset() { *x = PluginConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[30] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2433,7 +2479,7 @@ func (x *PluginConfiguration) String() string { func (*PluginConfiguration) ProtoMessage() {} func (x *PluginConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[30] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2446,7 +2492,7 @@ func (x *PluginConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use PluginConfiguration.ProtoReflect.Descriptor instead. func (*PluginConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{30} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{31} } func (x *PluginConfiguration) GetName() string { @@ -2480,7 +2526,7 @@ type SSLConfiguration struct { func (x *SSLConfiguration) Reset() { *x = SSLConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[31] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2492,7 +2538,7 @@ func (x *SSLConfiguration) String() string { func (*SSLConfiguration) ProtoMessage() {} func (x *SSLConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[31] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2505,7 +2551,7 @@ func (x *SSLConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use SSLConfiguration.ProtoReflect.Descriptor instead. func (*SSLConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{31} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{32} } func (x *SSLConfiguration) GetEnabled() bool { @@ -2538,7 +2584,7 @@ type GRPCMapping struct { func (x *GRPCMapping) Reset() { *x = GRPCMapping{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[32] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2550,7 +2596,7 @@ func (x *GRPCMapping) String() string { func (*GRPCMapping) ProtoMessage() {} func (x *GRPCMapping) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[32] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2563,7 +2609,7 @@ func (x *GRPCMapping) ProtoReflect() protoreflect.Message { // Deprecated: Use GRPCMapping.ProtoReflect.Descriptor instead. func (*GRPCMapping) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{32} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{33} } func (x *GRPCMapping) GetVersion() int32 { @@ -2634,7 +2680,7 @@ type LookupMapping struct { func (x *LookupMapping) Reset() { *x = LookupMapping{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[33] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2646,7 +2692,7 @@ func (x *LookupMapping) String() string { func (*LookupMapping) ProtoMessage() {} func (x *LookupMapping) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[33] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2659,7 +2705,7 @@ func (x *LookupMapping) ProtoReflect() protoreflect.Message { // Deprecated: Use LookupMapping.ProtoReflect.Descriptor instead. func (*LookupMapping) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{33} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{34} } func (x *LookupMapping) GetType() LookupType { @@ -2710,7 +2756,7 @@ type LookupFieldMapping struct { func (x *LookupFieldMapping) Reset() { *x = LookupFieldMapping{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[34] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2722,7 +2768,7 @@ func (x *LookupFieldMapping) String() string { func (*LookupFieldMapping) ProtoMessage() {} func (x *LookupFieldMapping) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[34] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2735,7 +2781,7 @@ func (x *LookupFieldMapping) ProtoReflect() protoreflect.Message { // Deprecated: Use LookupFieldMapping.ProtoReflect.Descriptor instead. func (*LookupFieldMapping) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{34} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{35} } func (x *LookupFieldMapping) GetType() string { @@ -2771,7 +2817,7 @@ type OperationMapping struct { func (x *OperationMapping) Reset() { *x = OperationMapping{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[35] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2783,7 +2829,7 @@ func (x *OperationMapping) String() string { func (*OperationMapping) ProtoMessage() {} func (x *OperationMapping) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[35] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2796,7 +2842,7 @@ func (x *OperationMapping) ProtoReflect() protoreflect.Message { // Deprecated: Use OperationMapping.ProtoReflect.Descriptor instead. func (*OperationMapping) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{35} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{36} } func (x *OperationMapping) GetType() OperationType { @@ -2857,7 +2903,7 @@ type EntityMapping struct { func (x *EntityMapping) Reset() { *x = EntityMapping{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[36] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2869,7 +2915,7 @@ func (x *EntityMapping) String() string { func (*EntityMapping) ProtoMessage() {} func (x *EntityMapping) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[36] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2882,7 +2928,7 @@ func (x *EntityMapping) ProtoReflect() protoreflect.Message { // Deprecated: Use EntityMapping.ProtoReflect.Descriptor instead. func (*EntityMapping) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{36} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{37} } func (x *EntityMapping) GetTypeName() string { @@ -2950,7 +2996,7 @@ type RequiredFieldMapping struct { func (x *RequiredFieldMapping) Reset() { *x = RequiredFieldMapping{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[37] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2962,7 +3008,7 @@ func (x *RequiredFieldMapping) String() string { func (*RequiredFieldMapping) ProtoMessage() {} func (x *RequiredFieldMapping) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[37] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2975,7 +3021,7 @@ func (x *RequiredFieldMapping) ProtoReflect() protoreflect.Message { // Deprecated: Use RequiredFieldMapping.ProtoReflect.Descriptor instead. func (*RequiredFieldMapping) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{37} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{38} } func (x *RequiredFieldMapping) GetFieldMapping() *FieldMapping { @@ -3019,7 +3065,7 @@ type TypeFieldMapping struct { func (x *TypeFieldMapping) Reset() { *x = TypeFieldMapping{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[38] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3031,7 +3077,7 @@ func (x *TypeFieldMapping) String() string { func (*TypeFieldMapping) ProtoMessage() {} func (x *TypeFieldMapping) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[38] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3044,7 +3090,7 @@ func (x *TypeFieldMapping) ProtoReflect() protoreflect.Message { // Deprecated: Use TypeFieldMapping.ProtoReflect.Descriptor instead. func (*TypeFieldMapping) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{38} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{39} } func (x *TypeFieldMapping) GetType() string { @@ -3076,7 +3122,7 @@ type FieldMapping struct { func (x *FieldMapping) Reset() { *x = FieldMapping{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[39] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3088,7 +3134,7 @@ func (x *FieldMapping) String() string { func (*FieldMapping) ProtoMessage() {} func (x *FieldMapping) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[39] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3101,7 +3147,7 @@ func (x *FieldMapping) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldMapping.ProtoReflect.Descriptor instead. func (*FieldMapping) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{39} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{40} } func (x *FieldMapping) GetOriginal() string { @@ -3138,7 +3184,7 @@ type ArgumentMapping struct { func (x *ArgumentMapping) Reset() { *x = ArgumentMapping{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[40] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3150,7 +3196,7 @@ func (x *ArgumentMapping) String() string { func (*ArgumentMapping) ProtoMessage() {} func (x *ArgumentMapping) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[40] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3163,7 +3209,7 @@ func (x *ArgumentMapping) ProtoReflect() protoreflect.Message { // Deprecated: Use ArgumentMapping.ProtoReflect.Descriptor instead. func (*ArgumentMapping) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{40} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{41} } func (x *ArgumentMapping) GetOriginal() string { @@ -3190,7 +3236,7 @@ type EnumMapping struct { func (x *EnumMapping) Reset() { *x = EnumMapping{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[41] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3202,7 +3248,7 @@ func (x *EnumMapping) String() string { func (*EnumMapping) ProtoMessage() {} func (x *EnumMapping) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[41] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3215,7 +3261,7 @@ func (x *EnumMapping) ProtoReflect() protoreflect.Message { // Deprecated: Use EnumMapping.ProtoReflect.Descriptor instead. func (*EnumMapping) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{41} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{42} } func (x *EnumMapping) GetType() string { @@ -3242,7 +3288,7 @@ type EnumValueMapping struct { func (x *EnumValueMapping) Reset() { *x = EnumValueMapping{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[42] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3254,7 +3300,7 @@ func (x *EnumValueMapping) String() string { func (*EnumValueMapping) ProtoMessage() {} func (x *EnumValueMapping) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[42] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3267,7 +3313,7 @@ func (x *EnumValueMapping) ProtoReflect() protoreflect.Message { // Deprecated: Use EnumValueMapping.ProtoReflect.Descriptor instead. func (*EnumValueMapping) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{42} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{43} } func (x *EnumValueMapping) GetOriginal() string { @@ -3295,7 +3341,7 @@ type NatsStreamConfiguration struct { func (x *NatsStreamConfiguration) Reset() { *x = NatsStreamConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[43] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3307,7 +3353,7 @@ func (x *NatsStreamConfiguration) String() string { func (*NatsStreamConfiguration) ProtoMessage() {} func (x *NatsStreamConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[43] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3320,7 +3366,7 @@ func (x *NatsStreamConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use NatsStreamConfiguration.ProtoReflect.Descriptor instead. func (*NatsStreamConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{43} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{44} } func (x *NatsStreamConfiguration) GetConsumerName() string { @@ -3355,7 +3401,7 @@ type NatsEventConfiguration struct { func (x *NatsEventConfiguration) Reset() { *x = NatsEventConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[44] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3367,7 +3413,7 @@ func (x *NatsEventConfiguration) String() string { func (*NatsEventConfiguration) ProtoMessage() {} func (x *NatsEventConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[44] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3380,7 +3426,7 @@ func (x *NatsEventConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use NatsEventConfiguration.ProtoReflect.Descriptor instead. func (*NatsEventConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{44} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{45} } func (x *NatsEventConfiguration) GetEngineEventConfiguration() *EngineEventConfiguration { @@ -3414,7 +3460,7 @@ type KafkaEventConfiguration struct { func (x *KafkaEventConfiguration) Reset() { *x = KafkaEventConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[45] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3426,7 +3472,7 @@ func (x *KafkaEventConfiguration) String() string { func (*KafkaEventConfiguration) ProtoMessage() {} func (x *KafkaEventConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[45] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3439,7 +3485,7 @@ func (x *KafkaEventConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use KafkaEventConfiguration.ProtoReflect.Descriptor instead. func (*KafkaEventConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{45} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{46} } func (x *KafkaEventConfiguration) GetEngineEventConfiguration() *EngineEventConfiguration { @@ -3466,7 +3512,7 @@ type RedisEventConfiguration struct { func (x *RedisEventConfiguration) Reset() { *x = RedisEventConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[46] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3478,7 +3524,7 @@ func (x *RedisEventConfiguration) String() string { func (*RedisEventConfiguration) ProtoMessage() {} func (x *RedisEventConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[46] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3491,7 +3537,7 @@ func (x *RedisEventConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use RedisEventConfiguration.ProtoReflect.Descriptor instead. func (*RedisEventConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{46} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{47} } func (x *RedisEventConfiguration) GetEngineEventConfiguration() *EngineEventConfiguration { @@ -3520,7 +3566,7 @@ type EngineEventConfiguration struct { func (x *EngineEventConfiguration) Reset() { *x = EngineEventConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[47] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3532,7 +3578,7 @@ func (x *EngineEventConfiguration) String() string { func (*EngineEventConfiguration) ProtoMessage() {} func (x *EngineEventConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[47] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3545,7 +3591,7 @@ func (x *EngineEventConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use EngineEventConfiguration.ProtoReflect.Descriptor instead. func (*EngineEventConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{47} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{48} } func (x *EngineEventConfiguration) GetProviderId() string { @@ -3587,7 +3633,7 @@ type DataSourceCustomEvents struct { func (x *DataSourceCustomEvents) Reset() { *x = DataSourceCustomEvents{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[48] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3599,7 +3645,7 @@ func (x *DataSourceCustomEvents) String() string { func (*DataSourceCustomEvents) ProtoMessage() {} func (x *DataSourceCustomEvents) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[48] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3612,7 +3658,7 @@ func (x *DataSourceCustomEvents) ProtoReflect() protoreflect.Message { // Deprecated: Use DataSourceCustomEvents.ProtoReflect.Descriptor instead. func (*DataSourceCustomEvents) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{48} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{49} } func (x *DataSourceCustomEvents) GetNats() []*NatsEventConfiguration { @@ -3645,7 +3691,7 @@ type DataSourceCustom_Static struct { func (x *DataSourceCustom_Static) Reset() { *x = DataSourceCustom_Static{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[49] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3657,7 +3703,7 @@ func (x *DataSourceCustom_Static) String() string { func (*DataSourceCustom_Static) ProtoMessage() {} func (x *DataSourceCustom_Static) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[49] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3670,7 +3716,7 @@ func (x *DataSourceCustom_Static) ProtoReflect() protoreflect.Message { // Deprecated: Use DataSourceCustom_Static.ProtoReflect.Descriptor instead. func (*DataSourceCustom_Static) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{49} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{50} } func (x *DataSourceCustom_Static) GetData() *ConfigurationVariable { @@ -3693,7 +3739,7 @@ type ConfigurationVariable struct { func (x *ConfigurationVariable) Reset() { *x = ConfigurationVariable{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[50] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3705,7 +3751,7 @@ func (x *ConfigurationVariable) String() string { func (*ConfigurationVariable) ProtoMessage() {} func (x *ConfigurationVariable) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[50] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3718,7 +3764,7 @@ func (x *ConfigurationVariable) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigurationVariable.ProtoReflect.Descriptor instead. func (*ConfigurationVariable) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{50} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{51} } func (x *ConfigurationVariable) GetKind() ConfigurationVariableKind { @@ -3766,7 +3812,7 @@ type DirectiveConfiguration struct { func (x *DirectiveConfiguration) Reset() { *x = DirectiveConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[51] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3778,7 +3824,7 @@ func (x *DirectiveConfiguration) String() string { func (*DirectiveConfiguration) ProtoMessage() {} func (x *DirectiveConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[51] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3791,7 +3837,7 @@ func (x *DirectiveConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use DirectiveConfiguration.ProtoReflect.Descriptor instead. func (*DirectiveConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{51} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{52} } func (x *DirectiveConfiguration) GetDirectiveName() string { @@ -3818,7 +3864,7 @@ type URLQueryConfiguration struct { func (x *URLQueryConfiguration) Reset() { *x = URLQueryConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[52] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3830,7 +3876,7 @@ func (x *URLQueryConfiguration) String() string { func (*URLQueryConfiguration) ProtoMessage() {} func (x *URLQueryConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[52] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3843,7 +3889,7 @@ func (x *URLQueryConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use URLQueryConfiguration.ProtoReflect.Descriptor instead. func (*URLQueryConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{52} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{53} } func (x *URLQueryConfiguration) GetName() string { @@ -3869,7 +3915,7 @@ type HTTPHeader struct { func (x *HTTPHeader) Reset() { *x = HTTPHeader{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[53] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3881,7 +3927,7 @@ func (x *HTTPHeader) String() string { func (*HTTPHeader) ProtoMessage() {} func (x *HTTPHeader) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[53] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3894,7 +3940,7 @@ func (x *HTTPHeader) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPHeader.ProtoReflect.Descriptor instead. func (*HTTPHeader) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{53} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{54} } func (x *HTTPHeader) GetValues() []*ConfigurationVariable { @@ -3915,7 +3961,7 @@ type MTLSConfiguration struct { func (x *MTLSConfiguration) Reset() { *x = MTLSConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[54] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3927,7 +3973,7 @@ func (x *MTLSConfiguration) String() string { func (*MTLSConfiguration) ProtoMessage() {} func (x *MTLSConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[54] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3940,7 +3986,7 @@ func (x *MTLSConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use MTLSConfiguration.ProtoReflect.Descriptor instead. func (*MTLSConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{54} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{55} } func (x *MTLSConfiguration) GetKey() *ConfigurationVariable { @@ -3978,7 +4024,7 @@ type GraphQLSubscriptionConfiguration struct { func (x *GraphQLSubscriptionConfiguration) Reset() { *x = GraphQLSubscriptionConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[55] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3990,7 +4036,7 @@ func (x *GraphQLSubscriptionConfiguration) String() string { func (*GraphQLSubscriptionConfiguration) ProtoMessage() {} func (x *GraphQLSubscriptionConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[55] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4003,7 +4049,7 @@ func (x *GraphQLSubscriptionConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use GraphQLSubscriptionConfiguration.ProtoReflect.Descriptor instead. func (*GraphQLSubscriptionConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{55} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{56} } func (x *GraphQLSubscriptionConfiguration) GetEnabled() bool { @@ -4051,7 +4097,7 @@ type GraphQLFederationConfiguration struct { func (x *GraphQLFederationConfiguration) Reset() { *x = GraphQLFederationConfiguration{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[56] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4063,7 +4109,7 @@ func (x *GraphQLFederationConfiguration) String() string { func (*GraphQLFederationConfiguration) ProtoMessage() {} func (x *GraphQLFederationConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[56] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4076,7 +4122,7 @@ func (x *GraphQLFederationConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use GraphQLFederationConfiguration.ProtoReflect.Descriptor instead. func (*GraphQLFederationConfiguration) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{56} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{57} } func (x *GraphQLFederationConfiguration) GetEnabled() bool { @@ -4103,7 +4149,7 @@ type InternedString struct { func (x *InternedString) Reset() { *x = InternedString{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[57] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4115,7 +4161,7 @@ func (x *InternedString) String() string { func (*InternedString) ProtoMessage() {} func (x *InternedString) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[57] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4128,7 +4174,7 @@ func (x *InternedString) ProtoReflect() protoreflect.Message { // Deprecated: Use InternedString.ProtoReflect.Descriptor instead. func (*InternedString) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{57} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{58} } func (x *InternedString) GetKey() string { @@ -4148,7 +4194,7 @@ type SingleTypeField struct { func (x *SingleTypeField) Reset() { *x = SingleTypeField{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[58] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4160,7 +4206,7 @@ func (x *SingleTypeField) String() string { func (*SingleTypeField) ProtoMessage() {} func (x *SingleTypeField) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[58] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4173,7 +4219,7 @@ func (x *SingleTypeField) ProtoReflect() protoreflect.Message { // Deprecated: Use SingleTypeField.ProtoReflect.Descriptor instead. func (*SingleTypeField) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{58} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{59} } func (x *SingleTypeField) GetTypeName() string { @@ -4200,7 +4246,7 @@ type SubscriptionFieldCondition struct { func (x *SubscriptionFieldCondition) Reset() { *x = SubscriptionFieldCondition{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[59] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4212,7 +4258,7 @@ func (x *SubscriptionFieldCondition) String() string { func (*SubscriptionFieldCondition) ProtoMessage() {} func (x *SubscriptionFieldCondition) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[59] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4225,7 +4271,7 @@ func (x *SubscriptionFieldCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use SubscriptionFieldCondition.ProtoReflect.Descriptor instead. func (*SubscriptionFieldCondition) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{59} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{60} } func (x *SubscriptionFieldCondition) GetFieldPath() []string { @@ -4254,7 +4300,7 @@ type SubscriptionFilterCondition struct { func (x *SubscriptionFilterCondition) Reset() { *x = SubscriptionFilterCondition{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[60] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4266,7 +4312,7 @@ func (x *SubscriptionFilterCondition) String() string { func (*SubscriptionFilterCondition) ProtoMessage() {} func (x *SubscriptionFilterCondition) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[60] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4279,7 +4325,7 @@ func (x *SubscriptionFilterCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use SubscriptionFilterCondition.ProtoReflect.Descriptor instead. func (*SubscriptionFilterCondition) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{60} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{61} } func (x *SubscriptionFilterCondition) GetAnd() []*SubscriptionFilterCondition { @@ -4319,7 +4365,7 @@ type CacheWarmerOperations struct { func (x *CacheWarmerOperations) Reset() { *x = CacheWarmerOperations{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[61] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4331,7 +4377,7 @@ func (x *CacheWarmerOperations) String() string { func (*CacheWarmerOperations) ProtoMessage() {} func (x *CacheWarmerOperations) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[61] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4344,7 +4390,7 @@ func (x *CacheWarmerOperations) ProtoReflect() protoreflect.Message { // Deprecated: Use CacheWarmerOperations.ProtoReflect.Descriptor instead. func (*CacheWarmerOperations) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{61} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{62} } func (x *CacheWarmerOperations) GetOperations() []*Operation { @@ -4364,7 +4410,7 @@ type Operation struct { func (x *Operation) Reset() { *x = Operation{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[62] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4376,7 +4422,7 @@ func (x *Operation) String() string { func (*Operation) ProtoMessage() {} func (x *Operation) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[62] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4389,7 +4435,7 @@ func (x *Operation) ProtoReflect() protoreflect.Message { // Deprecated: Use Operation.ProtoReflect.Descriptor instead. func (*Operation) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{62} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{63} } func (x *Operation) GetRequest() *OperationRequest { @@ -4417,7 +4463,7 @@ type OperationRequest struct { func (x *OperationRequest) Reset() { *x = OperationRequest{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[63] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4429,7 +4475,7 @@ func (x *OperationRequest) String() string { func (*OperationRequest) ProtoMessage() {} func (x *OperationRequest) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[63] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4442,7 +4488,7 @@ func (x *OperationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use OperationRequest.ProtoReflect.Descriptor instead. func (*OperationRequest) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{63} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{64} } func (x *OperationRequest) GetOperationName() string { @@ -4475,7 +4521,7 @@ type Extension struct { func (x *Extension) Reset() { *x = Extension{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[64] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4487,7 +4533,7 @@ func (x *Extension) String() string { func (*Extension) ProtoMessage() {} func (x *Extension) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[64] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4500,7 +4546,7 @@ func (x *Extension) ProtoReflect() protoreflect.Message { // Deprecated: Use Extension.ProtoReflect.Descriptor instead. func (*Extension) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{64} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{65} } func (x *Extension) GetPersistedQuery() *PersistedQuery { @@ -4520,7 +4566,7 @@ type PersistedQuery struct { func (x *PersistedQuery) Reset() { *x = PersistedQuery{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[65] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4532,7 +4578,7 @@ func (x *PersistedQuery) String() string { func (*PersistedQuery) ProtoMessage() {} func (x *PersistedQuery) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[65] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4545,7 +4591,7 @@ func (x *PersistedQuery) ProtoReflect() protoreflect.Message { // Deprecated: Use PersistedQuery.ProtoReflect.Descriptor instead. func (*PersistedQuery) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{65} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{66} } func (x *PersistedQuery) GetSha256Hash() string { @@ -4572,7 +4618,7 @@ type ClientInfo struct { func (x *ClientInfo) Reset() { *x = ClientInfo{} - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[66] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4584,7 +4630,7 @@ func (x *ClientInfo) String() string { func (*ClientInfo) ProtoMessage() {} func (x *ClientInfo) ProtoReflect() protoreflect.Message { - mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[66] + mi := &file_wg_cosmo_node_v1_node_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4597,7 +4643,7 @@ func (x *ClientInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ClientInfo.ProtoReflect.Descriptor instead. func (*ClientInfo) Descriptor() ([]byte, []int) { - return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{66} + return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{67} } func (x *ClientInfo) GetName() string { @@ -4639,7 +4685,12 @@ const file_wg_cosmo_node_v1_node_proto_rawDesc = "" + "\tsubgraphs\x18\x03 \x03(\v2\x1a.wg.cosmo.node.v1.SubgraphR\tsubgraphs\x12j\n" + "\x14feature_flag_configs\x18\x04 \x01(\v23.wg.cosmo.node.v1.FeatureFlagRouterExecutionConfigsH\x00R\x12featureFlagConfigs\x88\x01\x01\x123\n" + "\x15compatibility_version\x18\x05 \x01(\tR\x14compatibilityVersionB\x17\n" + - "\x15_feature_flag_configs\"j\n" + + "\x15_feature_flag_configs\"\xa6\x01\n" + + "\fActiveGraphs\x12U\n" + + "\rgraph_configs\x18\x01 \x03(\v20.wg.cosmo.node.v1.ActiveGraphs.GraphConfigsEntryR\fgraphConfigs\x1a?\n" + + "\x11GraphConfigsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"j\n" + "\bResponse\x123\n" + "\x04code\x18\x01 \x01(\x0e2\x1f.wg.cosmo.common.EnumStatusCodeR\x04code\x12\x1d\n" + "\adetails\x18\x02 \x01(\tH\x00R\adetails\x88\x01\x01B\n" + @@ -5030,7 +5081,7 @@ func file_wg_cosmo_node_v1_node_proto_rawDescGZIP() []byte { } var file_wg_cosmo_node_v1_node_proto_enumTypes = make([]protoimpl.EnumInfo, 8) -var file_wg_cosmo_node_v1_node_proto_msgTypes = make([]protoimpl.MessageInfo, 74) +var file_wg_cosmo_node_v1_node_proto_msgTypes = make([]protoimpl.MessageInfo, 76) var file_wg_cosmo_node_v1_node_proto_goTypes = []any{ (ArgumentRenderConfiguration)(0), // 0: wg.cosmo.node.v1.ArgumentRenderConfiguration (ArgumentSource)(0), // 1: wg.cosmo.node.v1.ArgumentSource @@ -5044,188 +5095,191 @@ var file_wg_cosmo_node_v1_node_proto_goTypes = []any{ (*FeatureFlagRouterExecutionConfigs)(nil), // 9: wg.cosmo.node.v1.FeatureFlagRouterExecutionConfigs (*FeatureFlagRouterExecutionConfig)(nil), // 10: wg.cosmo.node.v1.FeatureFlagRouterExecutionConfig (*RouterConfig)(nil), // 11: wg.cosmo.node.v1.RouterConfig - (*Response)(nil), // 12: wg.cosmo.node.v1.Response - (*ResponseStatus)(nil), // 13: wg.cosmo.node.v1.ResponseStatus - (*RegistrationInfo)(nil), // 14: wg.cosmo.node.v1.RegistrationInfo - (*AccountLimits)(nil), // 15: wg.cosmo.node.v1.AccountLimits - (*SelfRegisterRequest)(nil), // 16: wg.cosmo.node.v1.SelfRegisterRequest - (*SelfRegisterResponse)(nil), // 17: wg.cosmo.node.v1.SelfRegisterResponse - (*EngineConfiguration)(nil), // 18: wg.cosmo.node.v1.EngineConfiguration - (*DataSourceConfiguration)(nil), // 19: wg.cosmo.node.v1.DataSourceConfiguration - (*CostConfiguration)(nil), // 20: wg.cosmo.node.v1.CostConfiguration - (*FieldWeightConfiguration)(nil), // 21: wg.cosmo.node.v1.FieldWeightConfiguration - (*FieldListSizeConfiguration)(nil), // 22: wg.cosmo.node.v1.FieldListSizeConfiguration - (*ArgumentConfiguration)(nil), // 23: wg.cosmo.node.v1.ArgumentConfiguration - (*Scopes)(nil), // 24: wg.cosmo.node.v1.Scopes - (*AuthorizationConfiguration)(nil), // 25: wg.cosmo.node.v1.AuthorizationConfiguration - (*FieldConfiguration)(nil), // 26: wg.cosmo.node.v1.FieldConfiguration - (*TypeConfiguration)(nil), // 27: wg.cosmo.node.v1.TypeConfiguration - (*TypeField)(nil), // 28: wg.cosmo.node.v1.TypeField - (*FieldCoordinates)(nil), // 29: wg.cosmo.node.v1.FieldCoordinates - (*FieldSetCondition)(nil), // 30: wg.cosmo.node.v1.FieldSetCondition - (*RequiredField)(nil), // 31: wg.cosmo.node.v1.RequiredField - (*EntityInterfaceConfiguration)(nil), // 32: wg.cosmo.node.v1.EntityInterfaceConfiguration - (*FetchConfiguration)(nil), // 33: wg.cosmo.node.v1.FetchConfiguration - (*StatusCodeTypeMapping)(nil), // 34: wg.cosmo.node.v1.StatusCodeTypeMapping - (*DataSourceCustom_GraphQL)(nil), // 35: wg.cosmo.node.v1.DataSourceCustom_GraphQL - (*GRPCConfiguration)(nil), // 36: wg.cosmo.node.v1.GRPCConfiguration - (*ImageReference)(nil), // 37: wg.cosmo.node.v1.ImageReference - (*PluginConfiguration)(nil), // 38: wg.cosmo.node.v1.PluginConfiguration - (*SSLConfiguration)(nil), // 39: wg.cosmo.node.v1.SSLConfiguration - (*GRPCMapping)(nil), // 40: wg.cosmo.node.v1.GRPCMapping - (*LookupMapping)(nil), // 41: wg.cosmo.node.v1.LookupMapping - (*LookupFieldMapping)(nil), // 42: wg.cosmo.node.v1.LookupFieldMapping - (*OperationMapping)(nil), // 43: wg.cosmo.node.v1.OperationMapping - (*EntityMapping)(nil), // 44: wg.cosmo.node.v1.EntityMapping - (*RequiredFieldMapping)(nil), // 45: wg.cosmo.node.v1.RequiredFieldMapping - (*TypeFieldMapping)(nil), // 46: wg.cosmo.node.v1.TypeFieldMapping - (*FieldMapping)(nil), // 47: wg.cosmo.node.v1.FieldMapping - (*ArgumentMapping)(nil), // 48: wg.cosmo.node.v1.ArgumentMapping - (*EnumMapping)(nil), // 49: wg.cosmo.node.v1.EnumMapping - (*EnumValueMapping)(nil), // 50: wg.cosmo.node.v1.EnumValueMapping - (*NatsStreamConfiguration)(nil), // 51: wg.cosmo.node.v1.NatsStreamConfiguration - (*NatsEventConfiguration)(nil), // 52: wg.cosmo.node.v1.NatsEventConfiguration - (*KafkaEventConfiguration)(nil), // 53: wg.cosmo.node.v1.KafkaEventConfiguration - (*RedisEventConfiguration)(nil), // 54: wg.cosmo.node.v1.RedisEventConfiguration - (*EngineEventConfiguration)(nil), // 55: wg.cosmo.node.v1.EngineEventConfiguration - (*DataSourceCustomEvents)(nil), // 56: wg.cosmo.node.v1.DataSourceCustomEvents - (*DataSourceCustom_Static)(nil), // 57: wg.cosmo.node.v1.DataSourceCustom_Static - (*ConfigurationVariable)(nil), // 58: wg.cosmo.node.v1.ConfigurationVariable - (*DirectiveConfiguration)(nil), // 59: wg.cosmo.node.v1.DirectiveConfiguration - (*URLQueryConfiguration)(nil), // 60: wg.cosmo.node.v1.URLQueryConfiguration - (*HTTPHeader)(nil), // 61: wg.cosmo.node.v1.HTTPHeader - (*MTLSConfiguration)(nil), // 62: wg.cosmo.node.v1.MTLSConfiguration - (*GraphQLSubscriptionConfiguration)(nil), // 63: wg.cosmo.node.v1.GraphQLSubscriptionConfiguration - (*GraphQLFederationConfiguration)(nil), // 64: wg.cosmo.node.v1.GraphQLFederationConfiguration - (*InternedString)(nil), // 65: wg.cosmo.node.v1.InternedString - (*SingleTypeField)(nil), // 66: wg.cosmo.node.v1.SingleTypeField - (*SubscriptionFieldCondition)(nil), // 67: wg.cosmo.node.v1.SubscriptionFieldCondition - (*SubscriptionFilterCondition)(nil), // 68: wg.cosmo.node.v1.SubscriptionFilterCondition - (*CacheWarmerOperations)(nil), // 69: wg.cosmo.node.v1.CacheWarmerOperations - (*Operation)(nil), // 70: wg.cosmo.node.v1.Operation - (*OperationRequest)(nil), // 71: wg.cosmo.node.v1.OperationRequest - (*Extension)(nil), // 72: wg.cosmo.node.v1.Extension - (*PersistedQuery)(nil), // 73: wg.cosmo.node.v1.PersistedQuery - (*ClientInfo)(nil), // 74: wg.cosmo.node.v1.ClientInfo - nil, // 75: wg.cosmo.node.v1.FeatureFlagRouterExecutionConfigs.ConfigByFeatureFlagNameEntry - nil, // 76: wg.cosmo.node.v1.EngineConfiguration.StringStorageEntry - nil, // 77: wg.cosmo.node.v1.CostConfiguration.TypeWeightsEntry - nil, // 78: wg.cosmo.node.v1.CostConfiguration.DirectiveArgumentWeightsEntry - nil, // 79: wg.cosmo.node.v1.FieldWeightConfiguration.ArgumentWeightsEntry - nil, // 80: wg.cosmo.node.v1.FieldWeightConfiguration.DirectiveArgumentWeightsEntry - nil, // 81: wg.cosmo.node.v1.FetchConfiguration.HeaderEntry - (common.EnumStatusCode)(0), // 82: wg.cosmo.common.EnumStatusCode - (common.GraphQLSubscriptionProtocol)(0), // 83: wg.cosmo.common.GraphQLSubscriptionProtocol - (common.GraphQLWebsocketSubprotocol)(0), // 84: wg.cosmo.common.GraphQLWebsocketSubprotocol + (*ActiveGraphs)(nil), // 12: wg.cosmo.node.v1.ActiveGraphs + (*Response)(nil), // 13: wg.cosmo.node.v1.Response + (*ResponseStatus)(nil), // 14: wg.cosmo.node.v1.ResponseStatus + (*RegistrationInfo)(nil), // 15: wg.cosmo.node.v1.RegistrationInfo + (*AccountLimits)(nil), // 16: wg.cosmo.node.v1.AccountLimits + (*SelfRegisterRequest)(nil), // 17: wg.cosmo.node.v1.SelfRegisterRequest + (*SelfRegisterResponse)(nil), // 18: wg.cosmo.node.v1.SelfRegisterResponse + (*EngineConfiguration)(nil), // 19: wg.cosmo.node.v1.EngineConfiguration + (*DataSourceConfiguration)(nil), // 20: wg.cosmo.node.v1.DataSourceConfiguration + (*CostConfiguration)(nil), // 21: wg.cosmo.node.v1.CostConfiguration + (*FieldWeightConfiguration)(nil), // 22: wg.cosmo.node.v1.FieldWeightConfiguration + (*FieldListSizeConfiguration)(nil), // 23: wg.cosmo.node.v1.FieldListSizeConfiguration + (*ArgumentConfiguration)(nil), // 24: wg.cosmo.node.v1.ArgumentConfiguration + (*Scopes)(nil), // 25: wg.cosmo.node.v1.Scopes + (*AuthorizationConfiguration)(nil), // 26: wg.cosmo.node.v1.AuthorizationConfiguration + (*FieldConfiguration)(nil), // 27: wg.cosmo.node.v1.FieldConfiguration + (*TypeConfiguration)(nil), // 28: wg.cosmo.node.v1.TypeConfiguration + (*TypeField)(nil), // 29: wg.cosmo.node.v1.TypeField + (*FieldCoordinates)(nil), // 30: wg.cosmo.node.v1.FieldCoordinates + (*FieldSetCondition)(nil), // 31: wg.cosmo.node.v1.FieldSetCondition + (*RequiredField)(nil), // 32: wg.cosmo.node.v1.RequiredField + (*EntityInterfaceConfiguration)(nil), // 33: wg.cosmo.node.v1.EntityInterfaceConfiguration + (*FetchConfiguration)(nil), // 34: wg.cosmo.node.v1.FetchConfiguration + (*StatusCodeTypeMapping)(nil), // 35: wg.cosmo.node.v1.StatusCodeTypeMapping + (*DataSourceCustom_GraphQL)(nil), // 36: wg.cosmo.node.v1.DataSourceCustom_GraphQL + (*GRPCConfiguration)(nil), // 37: wg.cosmo.node.v1.GRPCConfiguration + (*ImageReference)(nil), // 38: wg.cosmo.node.v1.ImageReference + (*PluginConfiguration)(nil), // 39: wg.cosmo.node.v1.PluginConfiguration + (*SSLConfiguration)(nil), // 40: wg.cosmo.node.v1.SSLConfiguration + (*GRPCMapping)(nil), // 41: wg.cosmo.node.v1.GRPCMapping + (*LookupMapping)(nil), // 42: wg.cosmo.node.v1.LookupMapping + (*LookupFieldMapping)(nil), // 43: wg.cosmo.node.v1.LookupFieldMapping + (*OperationMapping)(nil), // 44: wg.cosmo.node.v1.OperationMapping + (*EntityMapping)(nil), // 45: wg.cosmo.node.v1.EntityMapping + (*RequiredFieldMapping)(nil), // 46: wg.cosmo.node.v1.RequiredFieldMapping + (*TypeFieldMapping)(nil), // 47: wg.cosmo.node.v1.TypeFieldMapping + (*FieldMapping)(nil), // 48: wg.cosmo.node.v1.FieldMapping + (*ArgumentMapping)(nil), // 49: wg.cosmo.node.v1.ArgumentMapping + (*EnumMapping)(nil), // 50: wg.cosmo.node.v1.EnumMapping + (*EnumValueMapping)(nil), // 51: wg.cosmo.node.v1.EnumValueMapping + (*NatsStreamConfiguration)(nil), // 52: wg.cosmo.node.v1.NatsStreamConfiguration + (*NatsEventConfiguration)(nil), // 53: wg.cosmo.node.v1.NatsEventConfiguration + (*KafkaEventConfiguration)(nil), // 54: wg.cosmo.node.v1.KafkaEventConfiguration + (*RedisEventConfiguration)(nil), // 55: wg.cosmo.node.v1.RedisEventConfiguration + (*EngineEventConfiguration)(nil), // 56: wg.cosmo.node.v1.EngineEventConfiguration + (*DataSourceCustomEvents)(nil), // 57: wg.cosmo.node.v1.DataSourceCustomEvents + (*DataSourceCustom_Static)(nil), // 58: wg.cosmo.node.v1.DataSourceCustom_Static + (*ConfigurationVariable)(nil), // 59: wg.cosmo.node.v1.ConfigurationVariable + (*DirectiveConfiguration)(nil), // 60: wg.cosmo.node.v1.DirectiveConfiguration + (*URLQueryConfiguration)(nil), // 61: wg.cosmo.node.v1.URLQueryConfiguration + (*HTTPHeader)(nil), // 62: wg.cosmo.node.v1.HTTPHeader + (*MTLSConfiguration)(nil), // 63: wg.cosmo.node.v1.MTLSConfiguration + (*GraphQLSubscriptionConfiguration)(nil), // 64: wg.cosmo.node.v1.GraphQLSubscriptionConfiguration + (*GraphQLFederationConfiguration)(nil), // 65: wg.cosmo.node.v1.GraphQLFederationConfiguration + (*InternedString)(nil), // 66: wg.cosmo.node.v1.InternedString + (*SingleTypeField)(nil), // 67: wg.cosmo.node.v1.SingleTypeField + (*SubscriptionFieldCondition)(nil), // 68: wg.cosmo.node.v1.SubscriptionFieldCondition + (*SubscriptionFilterCondition)(nil), // 69: wg.cosmo.node.v1.SubscriptionFilterCondition + (*CacheWarmerOperations)(nil), // 70: wg.cosmo.node.v1.CacheWarmerOperations + (*Operation)(nil), // 71: wg.cosmo.node.v1.Operation + (*OperationRequest)(nil), // 72: wg.cosmo.node.v1.OperationRequest + (*Extension)(nil), // 73: wg.cosmo.node.v1.Extension + (*PersistedQuery)(nil), // 74: wg.cosmo.node.v1.PersistedQuery + (*ClientInfo)(nil), // 75: wg.cosmo.node.v1.ClientInfo + nil, // 76: wg.cosmo.node.v1.FeatureFlagRouterExecutionConfigs.ConfigByFeatureFlagNameEntry + nil, // 77: wg.cosmo.node.v1.ActiveGraphs.GraphConfigsEntry + nil, // 78: wg.cosmo.node.v1.EngineConfiguration.StringStorageEntry + nil, // 79: wg.cosmo.node.v1.CostConfiguration.TypeWeightsEntry + nil, // 80: wg.cosmo.node.v1.CostConfiguration.DirectiveArgumentWeightsEntry + nil, // 81: wg.cosmo.node.v1.FieldWeightConfiguration.ArgumentWeightsEntry + nil, // 82: wg.cosmo.node.v1.FieldWeightConfiguration.DirectiveArgumentWeightsEntry + nil, // 83: wg.cosmo.node.v1.FetchConfiguration.HeaderEntry + (common.EnumStatusCode)(0), // 84: wg.cosmo.common.EnumStatusCode + (common.GraphQLSubscriptionProtocol)(0), // 85: wg.cosmo.common.GraphQLSubscriptionProtocol + (common.GraphQLWebsocketSubprotocol)(0), // 86: wg.cosmo.common.GraphQLWebsocketSubprotocol } var file_wg_cosmo_node_v1_node_proto_depIdxs = []int32{ - 75, // 0: wg.cosmo.node.v1.FeatureFlagRouterExecutionConfigs.config_by_feature_flag_name:type_name -> wg.cosmo.node.v1.FeatureFlagRouterExecutionConfigs.ConfigByFeatureFlagNameEntry - 18, // 1: wg.cosmo.node.v1.FeatureFlagRouterExecutionConfig.engine_config:type_name -> wg.cosmo.node.v1.EngineConfiguration + 76, // 0: wg.cosmo.node.v1.FeatureFlagRouterExecutionConfigs.config_by_feature_flag_name:type_name -> wg.cosmo.node.v1.FeatureFlagRouterExecutionConfigs.ConfigByFeatureFlagNameEntry + 19, // 1: wg.cosmo.node.v1.FeatureFlagRouterExecutionConfig.engine_config:type_name -> wg.cosmo.node.v1.EngineConfiguration 8, // 2: wg.cosmo.node.v1.FeatureFlagRouterExecutionConfig.subgraphs:type_name -> wg.cosmo.node.v1.Subgraph - 18, // 3: wg.cosmo.node.v1.RouterConfig.engine_config:type_name -> wg.cosmo.node.v1.EngineConfiguration + 19, // 3: wg.cosmo.node.v1.RouterConfig.engine_config:type_name -> wg.cosmo.node.v1.EngineConfiguration 8, // 4: wg.cosmo.node.v1.RouterConfig.subgraphs:type_name -> wg.cosmo.node.v1.Subgraph 9, // 5: wg.cosmo.node.v1.RouterConfig.feature_flag_configs:type_name -> wg.cosmo.node.v1.FeatureFlagRouterExecutionConfigs - 82, // 6: wg.cosmo.node.v1.Response.code:type_name -> wg.cosmo.common.EnumStatusCode - 15, // 7: wg.cosmo.node.v1.RegistrationInfo.account_limits:type_name -> wg.cosmo.node.v1.AccountLimits - 12, // 8: wg.cosmo.node.v1.SelfRegisterResponse.response:type_name -> wg.cosmo.node.v1.Response - 14, // 9: wg.cosmo.node.v1.SelfRegisterResponse.registrationInfo:type_name -> wg.cosmo.node.v1.RegistrationInfo - 19, // 10: wg.cosmo.node.v1.EngineConfiguration.datasource_configurations:type_name -> wg.cosmo.node.v1.DataSourceConfiguration - 26, // 11: wg.cosmo.node.v1.EngineConfiguration.field_configurations:type_name -> wg.cosmo.node.v1.FieldConfiguration - 27, // 12: wg.cosmo.node.v1.EngineConfiguration.type_configurations:type_name -> wg.cosmo.node.v1.TypeConfiguration - 76, // 13: wg.cosmo.node.v1.EngineConfiguration.string_storage:type_name -> wg.cosmo.node.v1.EngineConfiguration.StringStorageEntry - 2, // 14: wg.cosmo.node.v1.DataSourceConfiguration.kind:type_name -> wg.cosmo.node.v1.DataSourceKind - 28, // 15: wg.cosmo.node.v1.DataSourceConfiguration.root_nodes:type_name -> wg.cosmo.node.v1.TypeField - 28, // 16: wg.cosmo.node.v1.DataSourceConfiguration.child_nodes:type_name -> wg.cosmo.node.v1.TypeField - 35, // 17: wg.cosmo.node.v1.DataSourceConfiguration.custom_graphql:type_name -> wg.cosmo.node.v1.DataSourceCustom_GraphQL - 57, // 18: wg.cosmo.node.v1.DataSourceConfiguration.custom_static:type_name -> wg.cosmo.node.v1.DataSourceCustom_Static - 59, // 19: wg.cosmo.node.v1.DataSourceConfiguration.directives:type_name -> wg.cosmo.node.v1.DirectiveConfiguration - 31, // 20: wg.cosmo.node.v1.DataSourceConfiguration.keys:type_name -> wg.cosmo.node.v1.RequiredField - 31, // 21: wg.cosmo.node.v1.DataSourceConfiguration.provides:type_name -> wg.cosmo.node.v1.RequiredField - 31, // 22: wg.cosmo.node.v1.DataSourceConfiguration.requires:type_name -> wg.cosmo.node.v1.RequiredField - 56, // 23: wg.cosmo.node.v1.DataSourceConfiguration.custom_events:type_name -> wg.cosmo.node.v1.DataSourceCustomEvents - 32, // 24: wg.cosmo.node.v1.DataSourceConfiguration.entity_interfaces:type_name -> wg.cosmo.node.v1.EntityInterfaceConfiguration - 32, // 25: wg.cosmo.node.v1.DataSourceConfiguration.interface_objects:type_name -> wg.cosmo.node.v1.EntityInterfaceConfiguration - 20, // 26: wg.cosmo.node.v1.DataSourceConfiguration.cost_configuration:type_name -> wg.cosmo.node.v1.CostConfiguration - 21, // 27: wg.cosmo.node.v1.CostConfiguration.field_weights:type_name -> wg.cosmo.node.v1.FieldWeightConfiguration - 22, // 28: wg.cosmo.node.v1.CostConfiguration.list_sizes:type_name -> wg.cosmo.node.v1.FieldListSizeConfiguration - 77, // 29: wg.cosmo.node.v1.CostConfiguration.type_weights:type_name -> wg.cosmo.node.v1.CostConfiguration.TypeWeightsEntry - 78, // 30: wg.cosmo.node.v1.CostConfiguration.directive_argument_weights:type_name -> wg.cosmo.node.v1.CostConfiguration.DirectiveArgumentWeightsEntry - 79, // 31: wg.cosmo.node.v1.FieldWeightConfiguration.argument_weights:type_name -> wg.cosmo.node.v1.FieldWeightConfiguration.ArgumentWeightsEntry - 80, // 32: wg.cosmo.node.v1.FieldWeightConfiguration.directive_argument_weights:type_name -> wg.cosmo.node.v1.FieldWeightConfiguration.DirectiveArgumentWeightsEntry - 1, // 33: wg.cosmo.node.v1.ArgumentConfiguration.source_type:type_name -> wg.cosmo.node.v1.ArgumentSource - 24, // 34: wg.cosmo.node.v1.AuthorizationConfiguration.required_or_scopes:type_name -> wg.cosmo.node.v1.Scopes - 24, // 35: wg.cosmo.node.v1.AuthorizationConfiguration.required_or_scopes_by_or:type_name -> wg.cosmo.node.v1.Scopes - 23, // 36: wg.cosmo.node.v1.FieldConfiguration.arguments_configuration:type_name -> wg.cosmo.node.v1.ArgumentConfiguration - 25, // 37: wg.cosmo.node.v1.FieldConfiguration.authorization_configuration:type_name -> wg.cosmo.node.v1.AuthorizationConfiguration - 68, // 38: wg.cosmo.node.v1.FieldConfiguration.subscription_filter_condition:type_name -> wg.cosmo.node.v1.SubscriptionFilterCondition - 29, // 39: wg.cosmo.node.v1.FieldSetCondition.field_coordinates_path:type_name -> wg.cosmo.node.v1.FieldCoordinates - 30, // 40: wg.cosmo.node.v1.RequiredField.conditions:type_name -> wg.cosmo.node.v1.FieldSetCondition - 58, // 41: wg.cosmo.node.v1.FetchConfiguration.url:type_name -> wg.cosmo.node.v1.ConfigurationVariable - 7, // 42: wg.cosmo.node.v1.FetchConfiguration.method:type_name -> wg.cosmo.node.v1.HTTPMethod - 81, // 43: wg.cosmo.node.v1.FetchConfiguration.header:type_name -> wg.cosmo.node.v1.FetchConfiguration.HeaderEntry - 58, // 44: wg.cosmo.node.v1.FetchConfiguration.body:type_name -> wg.cosmo.node.v1.ConfigurationVariable - 60, // 45: wg.cosmo.node.v1.FetchConfiguration.query:type_name -> wg.cosmo.node.v1.URLQueryConfiguration - 62, // 46: wg.cosmo.node.v1.FetchConfiguration.mtls:type_name -> wg.cosmo.node.v1.MTLSConfiguration - 58, // 47: wg.cosmo.node.v1.FetchConfiguration.base_url:type_name -> wg.cosmo.node.v1.ConfigurationVariable - 58, // 48: wg.cosmo.node.v1.FetchConfiguration.path:type_name -> wg.cosmo.node.v1.ConfigurationVariable - 58, // 49: wg.cosmo.node.v1.FetchConfiguration.http_proxy_url:type_name -> wg.cosmo.node.v1.ConfigurationVariable - 33, // 50: wg.cosmo.node.v1.DataSourceCustom_GraphQL.fetch:type_name -> wg.cosmo.node.v1.FetchConfiguration - 63, // 51: wg.cosmo.node.v1.DataSourceCustom_GraphQL.subscription:type_name -> wg.cosmo.node.v1.GraphQLSubscriptionConfiguration - 64, // 52: wg.cosmo.node.v1.DataSourceCustom_GraphQL.federation:type_name -> wg.cosmo.node.v1.GraphQLFederationConfiguration - 65, // 53: wg.cosmo.node.v1.DataSourceCustom_GraphQL.upstream_schema:type_name -> wg.cosmo.node.v1.InternedString - 66, // 54: wg.cosmo.node.v1.DataSourceCustom_GraphQL.custom_scalar_type_fields:type_name -> wg.cosmo.node.v1.SingleTypeField - 36, // 55: wg.cosmo.node.v1.DataSourceCustom_GraphQL.grpc:type_name -> wg.cosmo.node.v1.GRPCConfiguration - 40, // 56: wg.cosmo.node.v1.GRPCConfiguration.mapping:type_name -> wg.cosmo.node.v1.GRPCMapping - 38, // 57: wg.cosmo.node.v1.GRPCConfiguration.plugin:type_name -> wg.cosmo.node.v1.PluginConfiguration - 37, // 58: wg.cosmo.node.v1.PluginConfiguration.image_reference:type_name -> wg.cosmo.node.v1.ImageReference - 43, // 59: wg.cosmo.node.v1.GRPCMapping.operation_mappings:type_name -> wg.cosmo.node.v1.OperationMapping - 44, // 60: wg.cosmo.node.v1.GRPCMapping.entity_mappings:type_name -> wg.cosmo.node.v1.EntityMapping - 46, // 61: wg.cosmo.node.v1.GRPCMapping.type_field_mappings:type_name -> wg.cosmo.node.v1.TypeFieldMapping - 49, // 62: wg.cosmo.node.v1.GRPCMapping.enum_mappings:type_name -> wg.cosmo.node.v1.EnumMapping - 41, // 63: wg.cosmo.node.v1.GRPCMapping.resolve_mappings:type_name -> wg.cosmo.node.v1.LookupMapping - 3, // 64: wg.cosmo.node.v1.LookupMapping.type:type_name -> wg.cosmo.node.v1.LookupType - 42, // 65: wg.cosmo.node.v1.LookupMapping.lookup_mapping:type_name -> wg.cosmo.node.v1.LookupFieldMapping - 47, // 66: wg.cosmo.node.v1.LookupFieldMapping.field_mapping:type_name -> wg.cosmo.node.v1.FieldMapping - 4, // 67: wg.cosmo.node.v1.OperationMapping.type:type_name -> wg.cosmo.node.v1.OperationType - 45, // 68: wg.cosmo.node.v1.EntityMapping.required_field_mappings:type_name -> wg.cosmo.node.v1.RequiredFieldMapping - 47, // 69: wg.cosmo.node.v1.RequiredFieldMapping.field_mapping:type_name -> wg.cosmo.node.v1.FieldMapping - 47, // 70: wg.cosmo.node.v1.TypeFieldMapping.field_mappings:type_name -> wg.cosmo.node.v1.FieldMapping - 48, // 71: wg.cosmo.node.v1.FieldMapping.argument_mappings:type_name -> wg.cosmo.node.v1.ArgumentMapping - 50, // 72: wg.cosmo.node.v1.EnumMapping.values:type_name -> wg.cosmo.node.v1.EnumValueMapping - 55, // 73: wg.cosmo.node.v1.NatsEventConfiguration.engine_event_configuration:type_name -> wg.cosmo.node.v1.EngineEventConfiguration - 51, // 74: wg.cosmo.node.v1.NatsEventConfiguration.stream_configuration:type_name -> wg.cosmo.node.v1.NatsStreamConfiguration - 55, // 75: wg.cosmo.node.v1.KafkaEventConfiguration.engine_event_configuration:type_name -> wg.cosmo.node.v1.EngineEventConfiguration - 55, // 76: wg.cosmo.node.v1.RedisEventConfiguration.engine_event_configuration:type_name -> wg.cosmo.node.v1.EngineEventConfiguration - 5, // 77: wg.cosmo.node.v1.EngineEventConfiguration.type:type_name -> wg.cosmo.node.v1.EventType - 52, // 78: wg.cosmo.node.v1.DataSourceCustomEvents.nats:type_name -> wg.cosmo.node.v1.NatsEventConfiguration - 53, // 79: wg.cosmo.node.v1.DataSourceCustomEvents.kafka:type_name -> wg.cosmo.node.v1.KafkaEventConfiguration - 54, // 80: wg.cosmo.node.v1.DataSourceCustomEvents.redis:type_name -> wg.cosmo.node.v1.RedisEventConfiguration - 58, // 81: wg.cosmo.node.v1.DataSourceCustom_Static.data:type_name -> wg.cosmo.node.v1.ConfigurationVariable - 6, // 82: wg.cosmo.node.v1.ConfigurationVariable.kind:type_name -> wg.cosmo.node.v1.ConfigurationVariableKind - 58, // 83: wg.cosmo.node.v1.HTTPHeader.values:type_name -> wg.cosmo.node.v1.ConfigurationVariable - 58, // 84: wg.cosmo.node.v1.MTLSConfiguration.key:type_name -> wg.cosmo.node.v1.ConfigurationVariable - 58, // 85: wg.cosmo.node.v1.MTLSConfiguration.cert:type_name -> wg.cosmo.node.v1.ConfigurationVariable - 58, // 86: wg.cosmo.node.v1.GraphQLSubscriptionConfiguration.url:type_name -> wg.cosmo.node.v1.ConfigurationVariable - 83, // 87: wg.cosmo.node.v1.GraphQLSubscriptionConfiguration.protocol:type_name -> wg.cosmo.common.GraphQLSubscriptionProtocol - 84, // 88: wg.cosmo.node.v1.GraphQLSubscriptionConfiguration.websocketSubprotocol:type_name -> wg.cosmo.common.GraphQLWebsocketSubprotocol - 68, // 89: wg.cosmo.node.v1.SubscriptionFilterCondition.and:type_name -> wg.cosmo.node.v1.SubscriptionFilterCondition - 67, // 90: wg.cosmo.node.v1.SubscriptionFilterCondition.in:type_name -> wg.cosmo.node.v1.SubscriptionFieldCondition - 68, // 91: wg.cosmo.node.v1.SubscriptionFilterCondition.not:type_name -> wg.cosmo.node.v1.SubscriptionFilterCondition - 68, // 92: wg.cosmo.node.v1.SubscriptionFilterCondition.or:type_name -> wg.cosmo.node.v1.SubscriptionFilterCondition - 70, // 93: wg.cosmo.node.v1.CacheWarmerOperations.operations:type_name -> wg.cosmo.node.v1.Operation - 71, // 94: wg.cosmo.node.v1.Operation.request:type_name -> wg.cosmo.node.v1.OperationRequest - 74, // 95: wg.cosmo.node.v1.Operation.client:type_name -> wg.cosmo.node.v1.ClientInfo - 72, // 96: wg.cosmo.node.v1.OperationRequest.extensions:type_name -> wg.cosmo.node.v1.Extension - 73, // 97: wg.cosmo.node.v1.Extension.persisted_query:type_name -> wg.cosmo.node.v1.PersistedQuery - 10, // 98: wg.cosmo.node.v1.FeatureFlagRouterExecutionConfigs.ConfigByFeatureFlagNameEntry.value:type_name -> wg.cosmo.node.v1.FeatureFlagRouterExecutionConfig - 61, // 99: wg.cosmo.node.v1.FetchConfiguration.HeaderEntry.value:type_name -> wg.cosmo.node.v1.HTTPHeader - 16, // 100: wg.cosmo.node.v1.NodeService.SelfRegister:input_type -> wg.cosmo.node.v1.SelfRegisterRequest - 17, // 101: wg.cosmo.node.v1.NodeService.SelfRegister:output_type -> wg.cosmo.node.v1.SelfRegisterResponse - 101, // [101:102] is the sub-list for method output_type - 100, // [100:101] is the sub-list for method input_type - 100, // [100:100] is the sub-list for extension type_name - 100, // [100:100] is the sub-list for extension extendee - 0, // [0:100] is the sub-list for field type_name + 77, // 6: wg.cosmo.node.v1.ActiveGraphs.graph_configs:type_name -> wg.cosmo.node.v1.ActiveGraphs.GraphConfigsEntry + 84, // 7: wg.cosmo.node.v1.Response.code:type_name -> wg.cosmo.common.EnumStatusCode + 16, // 8: wg.cosmo.node.v1.RegistrationInfo.account_limits:type_name -> wg.cosmo.node.v1.AccountLimits + 13, // 9: wg.cosmo.node.v1.SelfRegisterResponse.response:type_name -> wg.cosmo.node.v1.Response + 15, // 10: wg.cosmo.node.v1.SelfRegisterResponse.registrationInfo:type_name -> wg.cosmo.node.v1.RegistrationInfo + 20, // 11: wg.cosmo.node.v1.EngineConfiguration.datasource_configurations:type_name -> wg.cosmo.node.v1.DataSourceConfiguration + 27, // 12: wg.cosmo.node.v1.EngineConfiguration.field_configurations:type_name -> wg.cosmo.node.v1.FieldConfiguration + 28, // 13: wg.cosmo.node.v1.EngineConfiguration.type_configurations:type_name -> wg.cosmo.node.v1.TypeConfiguration + 78, // 14: wg.cosmo.node.v1.EngineConfiguration.string_storage:type_name -> wg.cosmo.node.v1.EngineConfiguration.StringStorageEntry + 2, // 15: wg.cosmo.node.v1.DataSourceConfiguration.kind:type_name -> wg.cosmo.node.v1.DataSourceKind + 29, // 16: wg.cosmo.node.v1.DataSourceConfiguration.root_nodes:type_name -> wg.cosmo.node.v1.TypeField + 29, // 17: wg.cosmo.node.v1.DataSourceConfiguration.child_nodes:type_name -> wg.cosmo.node.v1.TypeField + 36, // 18: wg.cosmo.node.v1.DataSourceConfiguration.custom_graphql:type_name -> wg.cosmo.node.v1.DataSourceCustom_GraphQL + 58, // 19: wg.cosmo.node.v1.DataSourceConfiguration.custom_static:type_name -> wg.cosmo.node.v1.DataSourceCustom_Static + 60, // 20: wg.cosmo.node.v1.DataSourceConfiguration.directives:type_name -> wg.cosmo.node.v1.DirectiveConfiguration + 32, // 21: wg.cosmo.node.v1.DataSourceConfiguration.keys:type_name -> wg.cosmo.node.v1.RequiredField + 32, // 22: wg.cosmo.node.v1.DataSourceConfiguration.provides:type_name -> wg.cosmo.node.v1.RequiredField + 32, // 23: wg.cosmo.node.v1.DataSourceConfiguration.requires:type_name -> wg.cosmo.node.v1.RequiredField + 57, // 24: wg.cosmo.node.v1.DataSourceConfiguration.custom_events:type_name -> wg.cosmo.node.v1.DataSourceCustomEvents + 33, // 25: wg.cosmo.node.v1.DataSourceConfiguration.entity_interfaces:type_name -> wg.cosmo.node.v1.EntityInterfaceConfiguration + 33, // 26: wg.cosmo.node.v1.DataSourceConfiguration.interface_objects:type_name -> wg.cosmo.node.v1.EntityInterfaceConfiguration + 21, // 27: wg.cosmo.node.v1.DataSourceConfiguration.cost_configuration:type_name -> wg.cosmo.node.v1.CostConfiguration + 22, // 28: wg.cosmo.node.v1.CostConfiguration.field_weights:type_name -> wg.cosmo.node.v1.FieldWeightConfiguration + 23, // 29: wg.cosmo.node.v1.CostConfiguration.list_sizes:type_name -> wg.cosmo.node.v1.FieldListSizeConfiguration + 79, // 30: wg.cosmo.node.v1.CostConfiguration.type_weights:type_name -> wg.cosmo.node.v1.CostConfiguration.TypeWeightsEntry + 80, // 31: wg.cosmo.node.v1.CostConfiguration.directive_argument_weights:type_name -> wg.cosmo.node.v1.CostConfiguration.DirectiveArgumentWeightsEntry + 81, // 32: wg.cosmo.node.v1.FieldWeightConfiguration.argument_weights:type_name -> wg.cosmo.node.v1.FieldWeightConfiguration.ArgumentWeightsEntry + 82, // 33: wg.cosmo.node.v1.FieldWeightConfiguration.directive_argument_weights:type_name -> wg.cosmo.node.v1.FieldWeightConfiguration.DirectiveArgumentWeightsEntry + 1, // 34: wg.cosmo.node.v1.ArgumentConfiguration.source_type:type_name -> wg.cosmo.node.v1.ArgumentSource + 25, // 35: wg.cosmo.node.v1.AuthorizationConfiguration.required_or_scopes:type_name -> wg.cosmo.node.v1.Scopes + 25, // 36: wg.cosmo.node.v1.AuthorizationConfiguration.required_or_scopes_by_or:type_name -> wg.cosmo.node.v1.Scopes + 24, // 37: wg.cosmo.node.v1.FieldConfiguration.arguments_configuration:type_name -> wg.cosmo.node.v1.ArgumentConfiguration + 26, // 38: wg.cosmo.node.v1.FieldConfiguration.authorization_configuration:type_name -> wg.cosmo.node.v1.AuthorizationConfiguration + 69, // 39: wg.cosmo.node.v1.FieldConfiguration.subscription_filter_condition:type_name -> wg.cosmo.node.v1.SubscriptionFilterCondition + 30, // 40: wg.cosmo.node.v1.FieldSetCondition.field_coordinates_path:type_name -> wg.cosmo.node.v1.FieldCoordinates + 31, // 41: wg.cosmo.node.v1.RequiredField.conditions:type_name -> wg.cosmo.node.v1.FieldSetCondition + 59, // 42: wg.cosmo.node.v1.FetchConfiguration.url:type_name -> wg.cosmo.node.v1.ConfigurationVariable + 7, // 43: wg.cosmo.node.v1.FetchConfiguration.method:type_name -> wg.cosmo.node.v1.HTTPMethod + 83, // 44: wg.cosmo.node.v1.FetchConfiguration.header:type_name -> wg.cosmo.node.v1.FetchConfiguration.HeaderEntry + 59, // 45: wg.cosmo.node.v1.FetchConfiguration.body:type_name -> wg.cosmo.node.v1.ConfigurationVariable + 61, // 46: wg.cosmo.node.v1.FetchConfiguration.query:type_name -> wg.cosmo.node.v1.URLQueryConfiguration + 63, // 47: wg.cosmo.node.v1.FetchConfiguration.mtls:type_name -> wg.cosmo.node.v1.MTLSConfiguration + 59, // 48: wg.cosmo.node.v1.FetchConfiguration.base_url:type_name -> wg.cosmo.node.v1.ConfigurationVariable + 59, // 49: wg.cosmo.node.v1.FetchConfiguration.path:type_name -> wg.cosmo.node.v1.ConfigurationVariable + 59, // 50: wg.cosmo.node.v1.FetchConfiguration.http_proxy_url:type_name -> wg.cosmo.node.v1.ConfigurationVariable + 34, // 51: wg.cosmo.node.v1.DataSourceCustom_GraphQL.fetch:type_name -> wg.cosmo.node.v1.FetchConfiguration + 64, // 52: wg.cosmo.node.v1.DataSourceCustom_GraphQL.subscription:type_name -> wg.cosmo.node.v1.GraphQLSubscriptionConfiguration + 65, // 53: wg.cosmo.node.v1.DataSourceCustom_GraphQL.federation:type_name -> wg.cosmo.node.v1.GraphQLFederationConfiguration + 66, // 54: wg.cosmo.node.v1.DataSourceCustom_GraphQL.upstream_schema:type_name -> wg.cosmo.node.v1.InternedString + 67, // 55: wg.cosmo.node.v1.DataSourceCustom_GraphQL.custom_scalar_type_fields:type_name -> wg.cosmo.node.v1.SingleTypeField + 37, // 56: wg.cosmo.node.v1.DataSourceCustom_GraphQL.grpc:type_name -> wg.cosmo.node.v1.GRPCConfiguration + 41, // 57: wg.cosmo.node.v1.GRPCConfiguration.mapping:type_name -> wg.cosmo.node.v1.GRPCMapping + 39, // 58: wg.cosmo.node.v1.GRPCConfiguration.plugin:type_name -> wg.cosmo.node.v1.PluginConfiguration + 38, // 59: wg.cosmo.node.v1.PluginConfiguration.image_reference:type_name -> wg.cosmo.node.v1.ImageReference + 44, // 60: wg.cosmo.node.v1.GRPCMapping.operation_mappings:type_name -> wg.cosmo.node.v1.OperationMapping + 45, // 61: wg.cosmo.node.v1.GRPCMapping.entity_mappings:type_name -> wg.cosmo.node.v1.EntityMapping + 47, // 62: wg.cosmo.node.v1.GRPCMapping.type_field_mappings:type_name -> wg.cosmo.node.v1.TypeFieldMapping + 50, // 63: wg.cosmo.node.v1.GRPCMapping.enum_mappings:type_name -> wg.cosmo.node.v1.EnumMapping + 42, // 64: wg.cosmo.node.v1.GRPCMapping.resolve_mappings:type_name -> wg.cosmo.node.v1.LookupMapping + 3, // 65: wg.cosmo.node.v1.LookupMapping.type:type_name -> wg.cosmo.node.v1.LookupType + 43, // 66: wg.cosmo.node.v1.LookupMapping.lookup_mapping:type_name -> wg.cosmo.node.v1.LookupFieldMapping + 48, // 67: wg.cosmo.node.v1.LookupFieldMapping.field_mapping:type_name -> wg.cosmo.node.v1.FieldMapping + 4, // 68: wg.cosmo.node.v1.OperationMapping.type:type_name -> wg.cosmo.node.v1.OperationType + 46, // 69: wg.cosmo.node.v1.EntityMapping.required_field_mappings:type_name -> wg.cosmo.node.v1.RequiredFieldMapping + 48, // 70: wg.cosmo.node.v1.RequiredFieldMapping.field_mapping:type_name -> wg.cosmo.node.v1.FieldMapping + 48, // 71: wg.cosmo.node.v1.TypeFieldMapping.field_mappings:type_name -> wg.cosmo.node.v1.FieldMapping + 49, // 72: wg.cosmo.node.v1.FieldMapping.argument_mappings:type_name -> wg.cosmo.node.v1.ArgumentMapping + 51, // 73: wg.cosmo.node.v1.EnumMapping.values:type_name -> wg.cosmo.node.v1.EnumValueMapping + 56, // 74: wg.cosmo.node.v1.NatsEventConfiguration.engine_event_configuration:type_name -> wg.cosmo.node.v1.EngineEventConfiguration + 52, // 75: wg.cosmo.node.v1.NatsEventConfiguration.stream_configuration:type_name -> wg.cosmo.node.v1.NatsStreamConfiguration + 56, // 76: wg.cosmo.node.v1.KafkaEventConfiguration.engine_event_configuration:type_name -> wg.cosmo.node.v1.EngineEventConfiguration + 56, // 77: wg.cosmo.node.v1.RedisEventConfiguration.engine_event_configuration:type_name -> wg.cosmo.node.v1.EngineEventConfiguration + 5, // 78: wg.cosmo.node.v1.EngineEventConfiguration.type:type_name -> wg.cosmo.node.v1.EventType + 53, // 79: wg.cosmo.node.v1.DataSourceCustomEvents.nats:type_name -> wg.cosmo.node.v1.NatsEventConfiguration + 54, // 80: wg.cosmo.node.v1.DataSourceCustomEvents.kafka:type_name -> wg.cosmo.node.v1.KafkaEventConfiguration + 55, // 81: wg.cosmo.node.v1.DataSourceCustomEvents.redis:type_name -> wg.cosmo.node.v1.RedisEventConfiguration + 59, // 82: wg.cosmo.node.v1.DataSourceCustom_Static.data:type_name -> wg.cosmo.node.v1.ConfigurationVariable + 6, // 83: wg.cosmo.node.v1.ConfigurationVariable.kind:type_name -> wg.cosmo.node.v1.ConfigurationVariableKind + 59, // 84: wg.cosmo.node.v1.HTTPHeader.values:type_name -> wg.cosmo.node.v1.ConfigurationVariable + 59, // 85: wg.cosmo.node.v1.MTLSConfiguration.key:type_name -> wg.cosmo.node.v1.ConfigurationVariable + 59, // 86: wg.cosmo.node.v1.MTLSConfiguration.cert:type_name -> wg.cosmo.node.v1.ConfigurationVariable + 59, // 87: wg.cosmo.node.v1.GraphQLSubscriptionConfiguration.url:type_name -> wg.cosmo.node.v1.ConfigurationVariable + 85, // 88: wg.cosmo.node.v1.GraphQLSubscriptionConfiguration.protocol:type_name -> wg.cosmo.common.GraphQLSubscriptionProtocol + 86, // 89: wg.cosmo.node.v1.GraphQLSubscriptionConfiguration.websocketSubprotocol:type_name -> wg.cosmo.common.GraphQLWebsocketSubprotocol + 69, // 90: wg.cosmo.node.v1.SubscriptionFilterCondition.and:type_name -> wg.cosmo.node.v1.SubscriptionFilterCondition + 68, // 91: wg.cosmo.node.v1.SubscriptionFilterCondition.in:type_name -> wg.cosmo.node.v1.SubscriptionFieldCondition + 69, // 92: wg.cosmo.node.v1.SubscriptionFilterCondition.not:type_name -> wg.cosmo.node.v1.SubscriptionFilterCondition + 69, // 93: wg.cosmo.node.v1.SubscriptionFilterCondition.or:type_name -> wg.cosmo.node.v1.SubscriptionFilterCondition + 71, // 94: wg.cosmo.node.v1.CacheWarmerOperations.operations:type_name -> wg.cosmo.node.v1.Operation + 72, // 95: wg.cosmo.node.v1.Operation.request:type_name -> wg.cosmo.node.v1.OperationRequest + 75, // 96: wg.cosmo.node.v1.Operation.client:type_name -> wg.cosmo.node.v1.ClientInfo + 73, // 97: wg.cosmo.node.v1.OperationRequest.extensions:type_name -> wg.cosmo.node.v1.Extension + 74, // 98: wg.cosmo.node.v1.Extension.persisted_query:type_name -> wg.cosmo.node.v1.PersistedQuery + 10, // 99: wg.cosmo.node.v1.FeatureFlagRouterExecutionConfigs.ConfigByFeatureFlagNameEntry.value:type_name -> wg.cosmo.node.v1.FeatureFlagRouterExecutionConfig + 62, // 100: wg.cosmo.node.v1.FetchConfiguration.HeaderEntry.value:type_name -> wg.cosmo.node.v1.HTTPHeader + 17, // 101: wg.cosmo.node.v1.NodeService.SelfRegister:input_type -> wg.cosmo.node.v1.SelfRegisterRequest + 18, // 102: wg.cosmo.node.v1.NodeService.SelfRegister:output_type -> wg.cosmo.node.v1.SelfRegisterResponse + 102, // [102:103] is the sub-list for method output_type + 101, // [101:102] is the sub-list for method input_type + 101, // [101:101] is the sub-list for extension type_name + 101, // [101:101] is the sub-list for extension extendee + 0, // [0:101] is the sub-list for field type_name } func init() { file_wg_cosmo_node_v1_node_proto_init() } @@ -5234,23 +5288,23 @@ func file_wg_cosmo_node_v1_node_proto_init() { return } file_wg_cosmo_node_v1_node_proto_msgTypes[3].OneofWrappers = []any{} - file_wg_cosmo_node_v1_node_proto_msgTypes[4].OneofWrappers = []any{} - file_wg_cosmo_node_v1_node_proto_msgTypes[9].OneofWrappers = []any{} + file_wg_cosmo_node_v1_node_proto_msgTypes[5].OneofWrappers = []any{} file_wg_cosmo_node_v1_node_proto_msgTypes[10].OneofWrappers = []any{} - file_wg_cosmo_node_v1_node_proto_msgTypes[13].OneofWrappers = []any{} + file_wg_cosmo_node_v1_node_proto_msgTypes[11].OneofWrappers = []any{} file_wg_cosmo_node_v1_node_proto_msgTypes[14].OneofWrappers = []any{} - file_wg_cosmo_node_v1_node_proto_msgTypes[18].OneofWrappers = []any{} - file_wg_cosmo_node_v1_node_proto_msgTypes[25].OneofWrappers = []any{} - file_wg_cosmo_node_v1_node_proto_msgTypes[30].OneofWrappers = []any{} - file_wg_cosmo_node_v1_node_proto_msgTypes[55].OneofWrappers = []any{} - file_wg_cosmo_node_v1_node_proto_msgTypes[60].OneofWrappers = []any{} + file_wg_cosmo_node_v1_node_proto_msgTypes[15].OneofWrappers = []any{} + file_wg_cosmo_node_v1_node_proto_msgTypes[19].OneofWrappers = []any{} + file_wg_cosmo_node_v1_node_proto_msgTypes[26].OneofWrappers = []any{} + file_wg_cosmo_node_v1_node_proto_msgTypes[31].OneofWrappers = []any{} + file_wg_cosmo_node_v1_node_proto_msgTypes[56].OneofWrappers = []any{} + file_wg_cosmo_node_v1_node_proto_msgTypes[61].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_wg_cosmo_node_v1_node_proto_rawDesc), len(file_wg_cosmo_node_v1_node_proto_rawDesc)), NumEnums: 8, - NumMessages: 74, + NumMessages: 76, NumExtensions: 0, NumServices: 1, }, From 24b3add4155caedbe6ed7d91684e5b1434aa90a5 Mon Sep 17 00:00:00 2001 From: Dominik Korittki <23359034+dkorittki@users.noreply.github.com> Date: Sun, 3 May 2026 16:36:10 +0200 Subject: [PATCH 02/15] feat: read feature claim from router jwt --- router/core/graph_server.go | 5 +++++ router/internal/jwt/claims.go | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/router/core/graph_server.go b/router/core/graph_server.go index 23812b8fe4..5063d2b66e 100644 --- a/router/core/graph_server.go +++ b/router/core/graph_server.go @@ -208,6 +208,11 @@ func newGraphServer(ctx context.Context, r *Router, routerConfig *nodev1.RouterC return nil, err } baseOtelAttributes = append(baseOtelAttributes, otel.WgFederatedGraphID.String(claims.FederatedGraphID)) + + if claims.HasFeature(rjwt.FeatureSplitConfigLoading) { + // TODO: implement split-config-loading path + s.logger.Info("split-config-loading feature enabled") + } } s.baseOtelAttributes = baseOtelAttributes diff --git a/router/internal/jwt/claims.go b/router/internal/jwt/claims.go index b6b60f832b..441a2b56f3 100644 --- a/router/internal/jwt/claims.go +++ b/router/internal/jwt/claims.go @@ -2,17 +2,28 @@ package jwt import ( "fmt" + "slices" + "github.com/golang-jwt/jwt/v5" ) const ( FederatedGraphIDClaim = "federated_graph_id" OrganizationIDClaim = "organization_id" + FeaturesClaim = "features" + + FeatureSplitConfigLoading = "split-config-loading" ) type FederatedGraphTokenClaims struct { FederatedGraphID string OrganizationID string + Features []string +} + +// HasFeature reports whether the given feature flag is present in the token claims. +func (c *FederatedGraphTokenClaims) HasFeature(feature string) bool { + return slices.Contains(c.Features, feature) } func ExtractFederatedGraphTokenClaims(token string) (*FederatedGraphTokenClaims, error) { @@ -44,8 +55,24 @@ func ExtractFederatedGraphTokenClaims(token string) (*FederatedGraphTokenClaims, return nil, fmt.Errorf("invalid token claims, %q is not a string, it's %T", OrganizationIDClaim, organizationIDValue) } + var features []string + if featuresValue := claims[FeaturesClaim]; featuresValue != nil { + featuresSlice, ok := featuresValue.([]any) + if !ok { + return nil, fmt.Errorf("invalid token claims, %q is not an array, it's %T", FeaturesClaim, featuresValue) + } + for _, v := range featuresSlice { + s, ok := v.(string) + if !ok { + return nil, fmt.Errorf("invalid token claims, %q contains a non-string element %T", FeaturesClaim, v) + } + features = append(features, s) + } + } + return &FederatedGraphTokenClaims{ FederatedGraphID: federatedGraphID, OrganizationID: organizationID, + Features: features, }, nil } From 519a4f34ebf03449ba84530b140b8230810e833d Mon Sep 17 00:00:00 2001 From: Dominik Korittki <23359034+dkorittki@users.noreply.github.com> Date: Mon, 4 May 2026 10:12:35 +0200 Subject: [PATCH 03/15] feat: use split config polling if enabled via router token --- router/core/graph_server.go | 4 - router/core/init_config_poller.go | 53 ++ .../configpoller/split_config_poller.go | 272 +++++++++++ .../configpoller/split_config_poller_test.go | 452 ++++++++++++++++++ router/pkg/routerconfig/cdn/split_fetcher.go | 212 ++++++++ 5 files changed, 989 insertions(+), 4 deletions(-) create mode 100644 router/pkg/controlplane/configpoller/split_config_poller.go create mode 100644 router/pkg/controlplane/configpoller/split_config_poller_test.go create mode 100644 router/pkg/routerconfig/cdn/split_fetcher.go diff --git a/router/core/graph_server.go b/router/core/graph_server.go index 5063d2b66e..7ccc932b27 100644 --- a/router/core/graph_server.go +++ b/router/core/graph_server.go @@ -209,10 +209,6 @@ func newGraphServer(ctx context.Context, r *Router, routerConfig *nodev1.RouterC } baseOtelAttributes = append(baseOtelAttributes, otel.WgFederatedGraphID.String(claims.FederatedGraphID)) - if claims.HasFeature(rjwt.FeatureSplitConfigLoading) { - // TODO: implement split-config-loading path - s.logger.Info("split-config-loading feature enabled") - } } s.baseOtelAttributes = baseOtelAttributes diff --git a/router/core/init_config_poller.go b/router/core/init_config_poller.go index b8358ccae0..ecbae4ca77 100644 --- a/router/core/init_config_poller.go +++ b/router/core/init_config_poller.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + rjwt "github.com/wundergraph/cosmo/router/internal/jwt" "github.com/wundergraph/cosmo/router/pkg/controlplane/configpoller" "github.com/wundergraph/cosmo/router/pkg/execution_config" "github.com/wundergraph/cosmo/router/pkg/routerconfig" @@ -125,6 +126,24 @@ func InitializeConfigPoller(r *Router, registry *ProviderRegistry) (*configpolle return nil, nil } + // Check whether the router JWT requests the split-config-loading strategy. + // Split config is only supported with the default Cosmo CDN (no custom storage provider). + hasSplitCfgFeature, err := hasSplitConfigFeature(r) + if err != nil { + return nil, err + } + + if hasSplitCfgFeature { + providerID := r.routerConfigPollerConfig.Storage.ProviderID + if providerID != "" { + r.logger.Info("split-config-loading feature is enabled but a custom storage provider is configured; falling back to regular config polling", + zap.String("provider_id", providerID), + ) + } else { + return newSplitConfigPoller(r) + } + } + primaryClient, err := getConfigClient(r, registry, r.routerConfigPollerConfig.Storage.ProviderID, false) if err != nil { return nil, err @@ -159,3 +178,37 @@ func InitializeConfigPoller(r *Router, registry *ProviderRegistry) (*configpolle return &configPoller, nil } + +func hasSplitConfigFeature(r *Router) (bool, error) { + if r.graphApiToken == "" { + return false, nil + } + + claims, err := rjwt.ExtractFederatedGraphTokenClaims(r.graphApiToken) + if err != nil { + return false, fmt.Errorf("failed to parse graph API token: %w", err) + } + + return claims.HasFeature(rjwt.FeatureSplitConfigLoading), nil +} + +func newSplitConfigPoller(r *Router) (*configpoller.ConfigPoller, error) { + fetcher, err := configCDNProvider.NewSplitFetcher( + r.cdnConfig.URL, + r.graphApiToken, + &configCDNProvider.Options{ + Logger: r.logger, + SignatureKey: r.routerConfigPollerConfig.GraphSignKey, + }, + ) + if err != nil { + return nil, fmt.Errorf("failed to create split config fetcher: %w", err) + } + + splitPoller := configpoller.NewSplitConfigPoller( + fetcher, + configpoller.WithSplitLogger(r.logger), + configpoller.WithSplitPolling(r.routerConfigPollerConfig.PollInterval, r.routerConfigPollerConfig.PollJitter), + ) + return &splitPoller, nil +} diff --git a/router/pkg/controlplane/configpoller/split_config_poller.go b/router/pkg/controlplane/configpoller/split_config_poller.go new file mode 100644 index 0000000000..87f751ca9b --- /dev/null +++ b/router/pkg/controlplane/configpoller/split_config_poller.go @@ -0,0 +1,272 @@ +package configpoller + +import ( + "context" + "fmt" + "slices" + "time" + + "github.com/cespare/xxhash/v2" + nodev1 "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/node/v1" + "github.com/wundergraph/cosmo/router/pkg/controlplane" + "github.com/wundergraph/cosmo/router/pkg/routerconfig" + "go.uber.org/zap" + "google.golang.org/protobuf/proto" +) + +// SplitConfigFetcher fetches individual graph configs from CDN using the split-config strategy. +type SplitConfigFetcher interface { + // FetchMapper fetches the mapper file listing all active graphs and their hashes. + FetchMapper(ctx context.Context) (*nodev1.ActiveGraphs, error) + // FetchConfig fetches the router config for the given feature flag name. + // featureFlagName="" fetches the base graph (manifest/latest.json). + // featureFlagName="X" fetches the feature flag config (manifest/feature-flags/X.json). + FetchConfig(ctx context.Context, featureFlagName string) (*nodev1.RouterConfig, error) +} + +// SplitConfigPollerOption configures a splitConfigPoller. +type SplitConfigPollerOption func(*splitConfigPoller) + +type splitConfigPoller struct { + logger *zap.Logger + poller controlplane.Poller + pollInterval time.Duration + pollJitter time.Duration + fetcher SplitConfigFetcher + + // Internal state – not safe for concurrent access. + knownHashes map[string]string // name -> hash from last successful mapper fetch ("" = base) + currentConfig *nodev1.RouterConfig // last successfully assembled full config + latestVersion string // composite hash used for change detection +} + +// NewSplitConfigPoller creates a ConfigPoller that uses the split-config strategy. +func NewSplitConfigPoller(fetcher SplitConfigFetcher, opts ...SplitConfigPollerOption) ConfigPoller { + p := &splitConfigPoller{ + fetcher: fetcher, + knownHashes: make(map[string]string), + } + for _, opt := range opts { + opt(p) + } + if p.logger == nil { + p.logger = zap.NewNop() + } + p.poller = controlplane.NewPoll(p.pollInterval, p.pollJitter) + return p +} + +// WithSplitLogger sets the logger for the split config poller. +func WithSplitLogger(logger *zap.Logger) SplitConfigPollerOption { + return func(p *splitConfigPoller) { + p.logger = logger + } +} + +// WithSplitPolling sets the polling interval and jitter for the split config poller. +func WithSplitPolling(interval time.Duration, jitter time.Duration) SplitConfigPollerOption { + return func(p *splitConfigPoller) { + p.pollInterval = interval + p.pollJitter = jitter + } +} + +// computeCompositeVersion returns a deterministic version string derived from all mapper entries. +func computeCompositeVersion(graphConfigs map[string]string) string { + keys := make([]string, 0, len(graphConfigs)) + for k := range graphConfigs { + keys = append(keys, k) + } + slices.Sort(keys) + + h := xxhash.New() + for _, k := range keys { + _, _ = h.Write([]byte(k + ":" + graphConfigs[k] + ";")) + } + return fmt.Sprintf("split-%x", h.Sum64()) +} + +// fetchAndAssembleAll fetches every config listed in activeGraphs and assembles a full RouterConfig. +func (p *splitConfigPoller) fetchAndAssembleAll(ctx context.Context, activeGraphs *nodev1.ActiveGraphs) (*nodev1.RouterConfig, error) { + // Fetch base graph. + baseConfig, err := p.fetcher.FetchConfig(ctx, "") + if err != nil { + return nil, fmt.Errorf("failed to fetch base config: %w", err) + } + + assembled := &nodev1.RouterConfig{ + EngineConfig: baseConfig.EngineConfig, + Version: baseConfig.Version, + Subgraphs: baseConfig.Subgraphs, + CompatibilityVersion: baseConfig.CompatibilityVersion, + } + + // Fetch feature flag configs. + for name := range activeGraphs.GetGraphConfigs() { + if name == "" { + continue // base graph already handled above + } + ffConfig, err := p.fetcher.FetchConfig(ctx, name) + if err != nil { + return nil, fmt.Errorf("failed to fetch config for feature flag %q: %w", name, err) + } + if assembled.FeatureFlagConfigs == nil { + assembled.FeatureFlagConfigs = &nodev1.FeatureFlagRouterExecutionConfigs{ + ConfigByFeatureFlagName: make(map[string]*nodev1.FeatureFlagRouterExecutionConfig), + } + } + assembled.FeatureFlagConfigs.ConfigByFeatureFlagName[name] = &nodev1.FeatureFlagRouterExecutionConfig{ + EngineConfig: ffConfig.EngineConfig, + Version: ffConfig.Version, + Subgraphs: ffConfig.Subgraphs, + } + } + + return assembled, nil +} + +// GetRouterConfig performs the initial fetch: mapper + all individual configs. +func (p *splitConfigPoller) GetRouterConfig(ctx context.Context) (*routerconfig.Response, error) { + activeGraphs, err := p.fetcher.FetchMapper(ctx) + if err != nil { + return nil, fmt.Errorf("failed to fetch mapper: %w", err) + } + + graphConfigs := activeGraphs.GetGraphConfigs() + if len(graphConfigs) == 0 { + return nil, fmt.Errorf("mapper returned empty graph configs") + } + + config, err := p.fetchAndAssembleAll(ctx, activeGraphs) + if err != nil { + return nil, err + } + + p.knownHashes = graphConfigs + p.currentConfig = config + p.latestVersion = computeCompositeVersion(graphConfigs) + + return &routerconfig.Response{Config: config}, nil +} + +// Subscribe starts the polling loop and calls handler whenever the assembled config changes. +func (p *splitConfigPoller) Subscribe(ctx context.Context, handler func(newConfig *nodev1.RouterConfig, oldVersion string) error) { + p.poller.Subscribe(ctx, func() { + fetchStart := time.Now() + + activeGraphs, err := p.fetcher.FetchMapper(ctx) + if err != nil { + p.logger.Error("Failed to fetch mapper during poll, keeping existing config", zap.Error(err)) + return + } + + graphConfigs := activeGraphs.GetGraphConfigs() + if len(graphConfigs) == 0 { + p.logger.Warn("Mapper returned empty graph configs, keeping existing config") + return + } + + newVersion := computeCompositeVersion(graphConfigs) + if newVersion == p.latestVersion { + p.logger.Debug("No changes detected in engine config, keeping existing config") + return + } + + p.logger.Info("Router execution config has changed, hot reloading server", + zap.String("old_version", p.latestVersion), + zap.String("new_version", newVersion), + zap.String("fetch_time", time.Since(fetchStart).String()), + ) + + // Determine what changed, was added, or was removed. + changed := make(map[string]bool) + added := make(map[string]bool) + removed := make(map[string]bool) + + for name, hash := range graphConfigs { + if oldHash, exists := p.knownHashes[name]; !exists { + added[name] = true + } else if oldHash != hash { + changed[name] = true + } + } + for name := range p.knownHashes { + if _, exists := graphConfigs[name]; !exists { + removed[name] = true + } + } + + // Clone the in-use config before mutating. + patched := proto.Clone(p.currentConfig).(*nodev1.RouterConfig) + + // Apply changes and additions. + toFetch := make(map[string]bool, len(changed)+len(added)) + for name := range changed { + toFetch[name] = true + } + for name := range added { + toFetch[name] = true + } + + for name := range toFetch { + fetchedConfig, err := p.fetcher.FetchConfig(ctx, name) + if err != nil { + p.logger.Error("Failed to fetch config, skipping entire update", + zap.String("name", name), + zap.Error(err), + ) + return + } + + if name == "" { + // Base graph update. + patched.EngineConfig = fetchedConfig.EngineConfig + patched.Version = fetchedConfig.Version + patched.Subgraphs = fetchedConfig.Subgraphs + patched.CompatibilityVersion = fetchedConfig.CompatibilityVersion + } else { + if patched.FeatureFlagConfigs == nil { + patched.FeatureFlagConfigs = &nodev1.FeatureFlagRouterExecutionConfigs{ + ConfigByFeatureFlagName: make(map[string]*nodev1.FeatureFlagRouterExecutionConfig), + } + } + patched.FeatureFlagConfigs.ConfigByFeatureFlagName[name] = &nodev1.FeatureFlagRouterExecutionConfig{ + EngineConfig: fetchedConfig.EngineConfig, + Version: fetchedConfig.Version, + Subgraphs: fetchedConfig.Subgraphs, + } + } + } + + // Remove deleted feature flags. + for name := range removed { + if name == "" { + continue // base graph cannot be removed + } + if patched.FeatureFlagConfigs != nil { + delete(patched.FeatureFlagConfigs.ConfigByFeatureFlagName, name) + if len(patched.FeatureFlagConfigs.ConfigByFeatureFlagName) == 0 { + patched.FeatureFlagConfigs = nil + } + } + } + + oldVersion := p.latestVersion + + handlerStart := time.Now() + if err := handler(patched, oldVersion); err != nil { + p.logger.Error("Error invoking config poll handler", zap.Error(err)) + return + } + + p.logger.Debug("New graph server swapped", + zap.String("duration", time.Since(handlerStart).String()), + zap.String("config_version", newVersion), + ) + + // Only update internal state after the handler succeeds. + p.knownHashes = graphConfigs + p.currentConfig = patched + p.latestVersion = newVersion + }) +} diff --git a/router/pkg/controlplane/configpoller/split_config_poller_test.go b/router/pkg/controlplane/configpoller/split_config_poller_test.go new file mode 100644 index 0000000000..36ee6640dd --- /dev/null +++ b/router/pkg/controlplane/configpoller/split_config_poller_test.go @@ -0,0 +1,452 @@ +package configpoller + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + nodev1 "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/node/v1" + "go.uber.org/zap" +) + +// mockSplitFetcher is a controllable implementation of SplitConfigFetcher for tests. +type mockSplitFetcher struct { + mapperResult *nodev1.ActiveGraphs + mapperErr error + // configResults maps feature flag name -> result (use "" for base graph) + configResults map[string]*nodev1.RouterConfig + configErrors map[string]error + // tracks calls for assertion + fetchConfigCalls []string + fetchMapperCalls int +} + +func (m *mockSplitFetcher) FetchMapper(_ context.Context) (*nodev1.ActiveGraphs, error) { + m.fetchMapperCalls++ + return m.mapperResult, m.mapperErr +} + +func (m *mockSplitFetcher) FetchConfig(_ context.Context, featureFlagName string) (*nodev1.RouterConfig, error) { + m.fetchConfigCalls = append(m.fetchConfigCalls, featureFlagName) + if err, ok := m.configErrors[featureFlagName]; ok { + return nil, err + } + if cfg, ok := m.configResults[featureFlagName]; ok { + return cfg, nil + } + return nil, errors.New("no config registered for: " + featureFlagName) +} + +func makeRouterConfig(version string) *nodev1.RouterConfig { + return &nodev1.RouterConfig{ + Version: version, + EngineConfig: &nodev1.EngineConfiguration{ + DefaultFlushInterval: 500, + }, + CompatibilityVersion: "1", + } +} + +func makeActiveGraphs(entries map[string]string) *nodev1.ActiveGraphs { + return &nodev1.ActiveGraphs{GraphConfigs: entries} +} + +// newTestPoller builds a splitConfigPoller wired to the given mock fetcher. +// The polling interval is set long enough that Subscribe won't fire automatically in tests. +func newTestPoller(fetcher SplitConfigFetcher) *splitConfigPoller { + p := &splitConfigPoller{ + fetcher: fetcher, + knownHashes: make(map[string]string), + pollInterval: 24 * time.Hour, + pollJitter: 0, + logger: zap.NewNop(), + } + return p +} + +// ---- GetRouterConfig tests ---- + +func TestSplitGetRouterConfig_BaseOnly(t *testing.T) { + baseCfg := makeRouterConfig("v1") + mock := &mockSplitFetcher{ + mapperResult: makeActiveGraphs(map[string]string{"": "hash-base"}), + configResults: map[string]*nodev1.RouterConfig{"": baseCfg}, + } + + p := newTestPoller(mock) + resp, err := p.GetRouterConfig(context.Background()) + require.NoError(t, err) + require.NotNil(t, resp) + + assert.Equal(t, "v1", resp.Config.Version) + assert.Nil(t, resp.Config.FeatureFlagConfigs) + assert.Equal(t, "hash-base", p.knownHashes[""]) + assert.Contains(t, p.latestVersion, "split-") +} + +func TestSplitGetRouterConfig_WithFeatureFlags(t *testing.T) { + baseCfg := makeRouterConfig("v1") + ffCfg := makeRouterConfig("ff-v1") + mock := &mockSplitFetcher{ + mapperResult: makeActiveGraphs(map[string]string{ + "": "hash-base", + "ff1": "hash-ff1", + }), + configResults: map[string]*nodev1.RouterConfig{ + "": baseCfg, + "ff1": ffCfg, + }, + } + + p := newTestPoller(mock) + resp, err := p.GetRouterConfig(context.Background()) + require.NoError(t, err) + + assert.Equal(t, "v1", resp.Config.Version) + require.NotNil(t, resp.Config.FeatureFlagConfigs) + ff := resp.Config.FeatureFlagConfigs.ConfigByFeatureFlagName["ff1"] + require.NotNil(t, ff) + assert.Equal(t, "ff-v1", ff.Version) + + assert.Equal(t, "hash-base", p.knownHashes[""]) + assert.Equal(t, "hash-ff1", p.knownHashes["ff1"]) +} + +func TestSplitGetRouterConfig_MapperError(t *testing.T) { + mock := &mockSplitFetcher{ + mapperErr: errors.New("network error"), + } + p := newTestPoller(mock) + _, err := p.GetRouterConfig(context.Background()) + require.Error(t, err) + assert.Contains(t, err.Error(), "network error") +} + +func TestSplitGetRouterConfig_EmptyMapper(t *testing.T) { + mock := &mockSplitFetcher{ + mapperResult: makeActiveGraphs(map[string]string{}), + } + p := newTestPoller(mock) + _, err := p.GetRouterConfig(context.Background()) + require.Error(t, err) + assert.Contains(t, err.Error(), "empty graph configs") +} + +func TestSplitGetRouterConfig_ConfigFetchError(t *testing.T) { + mock := &mockSplitFetcher{ + mapperResult: makeActiveGraphs(map[string]string{"": "hash-base"}), + configErrors: map[string]error{"": errors.New("CDN unavailable")}, + } + p := newTestPoller(mock) + _, err := p.GetRouterConfig(context.Background()) + require.Error(t, err) + assert.Contains(t, err.Error(), "CDN unavailable") +} + +// ---- Subscribe / polling tests ---- + +// pollOnce manually executes one poll iteration using the poller's internal logic. +// It extracts the subscribe callback by using a fake controlplane.Poller. +func pollOnce(p *splitConfigPoller, handler func(*nodev1.RouterConfig, string) error) { + var tickFn func() + + // Temporarily replace the poller with one that captures the tick function. + p.poller = &capturingPoller{capture: &tickFn} + p.Subscribe(context.Background(), handler) + if tickFn != nil { + tickFn() + } +} + +// capturingPoller captures the handler passed to Subscribe so tests can invoke it manually. +type capturingPoller struct { + capture *func() +} + +func (c *capturingPoller) Subscribe(_ context.Context, fn func()) { + *c.capture = fn +} + +func TestSplitSubscribe_NoChanges(t *testing.T) { + baseCfg := makeRouterConfig("v1") + mock := &mockSplitFetcher{ + mapperResult: makeActiveGraphs(map[string]string{"": "hash-base"}), + configResults: map[string]*nodev1.RouterConfig{"": baseCfg}, + } + + p := newTestPoller(mock) + // Seed state as if GetRouterConfig was already called. + p.knownHashes = map[string]string{"": "hash-base"} + p.currentConfig = baseCfg + p.latestVersion = computeCompositeVersion(p.knownHashes) + + handlerCalled := false + pollOnce(p, func(_ *nodev1.RouterConfig, _ string) error { + handlerCalled = true + return nil + }) + + assert.False(t, handlerCalled, "handler must not be called when nothing changed") + // Only FetchMapper should have been called, no FetchConfig calls. + assert.Equal(t, 0, len(mock.fetchConfigCalls)) +} + +func TestSplitSubscribe_BaseGraphChanged(t *testing.T) { + oldBase := makeRouterConfig("v1") + newBase := makeRouterConfig("v2") + mock := &mockSplitFetcher{ + // Mapper now reports a new hash for the base graph. + mapperResult: makeActiveGraphs(map[string]string{"": "hash-base-new"}), + configResults: map[string]*nodev1.RouterConfig{"": newBase}, + } + + p := newTestPoller(mock) + p.knownHashes = map[string]string{"": "hash-base-old"} + p.currentConfig = oldBase + p.latestVersion = computeCompositeVersion(p.knownHashes) + + var received *nodev1.RouterConfig + pollOnce(p, func(cfg *nodev1.RouterConfig, _ string) error { + received = cfg + return nil + }) + + require.NotNil(t, received) + assert.Equal(t, "v2", received.Version) + // Only base config should have been fetched. + assert.Equal(t, []string{""}, mock.fetchConfigCalls) + // State updated. + assert.Equal(t, "hash-base-new", p.knownHashes[""]) +} + +func TestSplitSubscribe_SingleFFChanged(t *testing.T) { + baseCfg := makeRouterConfig("v1") + oldFF := makeRouterConfig("ff-v1") + newFF := makeRouterConfig("ff-v2") + mock := &mockSplitFetcher{ + mapperResult: makeActiveGraphs(map[string]string{ + "": "hash-base", + "ff1": "hash-ff1-new", + }), + configResults: map[string]*nodev1.RouterConfig{"ff1": newFF}, + } + + p := newTestPoller(mock) + p.knownHashes = map[string]string{"": "hash-base", "ff1": "hash-ff1-old"} + p.currentConfig = &nodev1.RouterConfig{ + Version: "v1", + EngineConfig: baseCfg.EngineConfig, + FeatureFlagConfigs: &nodev1.FeatureFlagRouterExecutionConfigs{ + ConfigByFeatureFlagName: map[string]*nodev1.FeatureFlagRouterExecutionConfig{ + "ff1": {Version: oldFF.Version, EngineConfig: oldFF.EngineConfig}, + }, + }, + } + p.latestVersion = computeCompositeVersion(p.knownHashes) + + var received *nodev1.RouterConfig + pollOnce(p, func(cfg *nodev1.RouterConfig, _ string) error { + received = cfg + return nil + }) + + require.NotNil(t, received) + // Base unchanged. + assert.Equal(t, "v1", received.Version) + // FF updated. + require.NotNil(t, received.FeatureFlagConfigs) + assert.Equal(t, "ff-v2", received.FeatureFlagConfigs.ConfigByFeatureFlagName["ff1"].Version) + // Only ff1 config should have been re-fetched (not base). + assert.Equal(t, []string{"ff1"}, mock.fetchConfigCalls) +} + +func TestSplitSubscribe_FFAdded(t *testing.T) { + baseCfg := makeRouterConfig("v1") + newFF := makeRouterConfig("ff-v1") + mock := &mockSplitFetcher{ + mapperResult: makeActiveGraphs(map[string]string{ + "": "hash-base", + "ff1": "hash-ff1", + }), + configResults: map[string]*nodev1.RouterConfig{"ff1": newFF}, + } + + p := newTestPoller(mock) + p.knownHashes = map[string]string{"": "hash-base"} + p.currentConfig = baseCfg + p.latestVersion = computeCompositeVersion(p.knownHashes) + + var received *nodev1.RouterConfig + pollOnce(p, func(cfg *nodev1.RouterConfig, _ string) error { + received = cfg + return nil + }) + + require.NotNil(t, received) + require.NotNil(t, received.FeatureFlagConfigs) + assert.Equal(t, "ff-v1", received.FeatureFlagConfigs.ConfigByFeatureFlagName["ff1"].Version) + assert.Equal(t, []string{"ff1"}, mock.fetchConfigCalls) +} + +func TestSplitSubscribe_FFRemoved(t *testing.T) { + baseCfg := makeRouterConfig("v1") + oldFF := makeRouterConfig("ff-v1") + mock := &mockSplitFetcher{ + // Mapper no longer contains ff1. + mapperResult: makeActiveGraphs(map[string]string{"": "hash-base"}), + } + + p := newTestPoller(mock) + p.knownHashes = map[string]string{"": "hash-base", "ff1": "hash-ff1"} + p.currentConfig = &nodev1.RouterConfig{ + Version: "v1", + EngineConfig: baseCfg.EngineConfig, + FeatureFlagConfigs: &nodev1.FeatureFlagRouterExecutionConfigs{ + ConfigByFeatureFlagName: map[string]*nodev1.FeatureFlagRouterExecutionConfig{ + "ff1": {Version: oldFF.Version, EngineConfig: oldFF.EngineConfig}, + }, + }, + } + p.latestVersion = computeCompositeVersion(p.knownHashes) + + var received *nodev1.RouterConfig + pollOnce(p, func(cfg *nodev1.RouterConfig, _ string) error { + received = cfg + return nil + }) + + require.NotNil(t, received) + assert.Nil(t, received.FeatureFlagConfigs, "FeatureFlagConfigs should be nil when last FF is removed") + // No FetchConfig calls needed for removal. + assert.Empty(t, mock.fetchConfigCalls) +} + +func TestSplitSubscribe_MultipleChanges(t *testing.T) { + baseCfg := makeRouterConfig("v1") + newBase := makeRouterConfig("v2") + newFF2 := makeRouterConfig("ff2-v1") + oldFF1 := makeRouterConfig("ff1-v1") + mock := &mockSplitFetcher{ + // base changed, ff2 added, ff1 removed. + mapperResult: makeActiveGraphs(map[string]string{ + "": "hash-base-new", + "ff2": "hash-ff2", + }), + configResults: map[string]*nodev1.RouterConfig{ + "": newBase, + "ff2": newFF2, + }, + } + + p := newTestPoller(mock) + p.knownHashes = map[string]string{"": "hash-base-old", "ff1": "hash-ff1"} + p.currentConfig = &nodev1.RouterConfig{ + Version: "v1", + EngineConfig: baseCfg.EngineConfig, + FeatureFlagConfigs: &nodev1.FeatureFlagRouterExecutionConfigs{ + ConfigByFeatureFlagName: map[string]*nodev1.FeatureFlagRouterExecutionConfig{ + "ff1": {Version: oldFF1.Version}, + }, + }, + } + p.latestVersion = computeCompositeVersion(p.knownHashes) + + var received *nodev1.RouterConfig + pollOnce(p, func(cfg *nodev1.RouterConfig, _ string) error { + received = cfg + return nil + }) + + require.NotNil(t, received) + assert.Equal(t, "v2", received.Version) + require.NotNil(t, received.FeatureFlagConfigs) + assert.Equal(t, "ff2-v1", received.FeatureFlagConfigs.ConfigByFeatureFlagName["ff2"].Version) + _, hasFF1 := received.FeatureFlagConfigs.ConfigByFeatureFlagName["ff1"] + assert.False(t, hasFF1, "ff1 should have been removed") +} + +func TestSplitSubscribe_MapperFetchFailure(t *testing.T) { + baseCfg := makeRouterConfig("v1") + mock := &mockSplitFetcher{ + mapperErr: errors.New("mapper fetch failed"), + } + + p := newTestPoller(mock) + p.knownHashes = map[string]string{"": "hash-base"} + p.currentConfig = baseCfg + initialVersion := computeCompositeVersion(p.knownHashes) + p.latestVersion = initialVersion + + handlerCalled := false + pollOnce(p, func(_ *nodev1.RouterConfig, _ string) error { + handlerCalled = true + return nil + }) + + assert.False(t, handlerCalled) + // State unchanged. + assert.Equal(t, initialVersion, p.latestVersion) +} + +func TestSplitSubscribe_ConfigFetchFailure(t *testing.T) { + baseCfg := makeRouterConfig("v1") + mock := &mockSplitFetcher{ + mapperResult: makeActiveGraphs(map[string]string{"": "hash-base-new"}), + configErrors: map[string]error{"": errors.New("config fetch failed")}, + } + + p := newTestPoller(mock) + p.knownHashes = map[string]string{"": "hash-base-old"} + p.currentConfig = baseCfg + initialVersion := computeCompositeVersion(p.knownHashes) + p.latestVersion = initialVersion + + handlerCalled := false + pollOnce(p, func(_ *nodev1.RouterConfig, _ string) error { + handlerCalled = true + return nil + }) + + assert.False(t, handlerCalled) + // State must remain unchanged after a failed fetch. + assert.Equal(t, initialVersion, p.latestVersion) + assert.Equal(t, "hash-base-old", p.knownHashes[""]) +} + +func TestSplitSubscribe_HandlerError_StateNotUpdated(t *testing.T) { + baseCfg := makeRouterConfig("v1") + newBase := makeRouterConfig("v2") + mock := &mockSplitFetcher{ + mapperResult: makeActiveGraphs(map[string]string{"": "hash-base-new"}), + configResults: map[string]*nodev1.RouterConfig{"": newBase}, + } + + p := newTestPoller(mock) + p.knownHashes = map[string]string{"": "hash-base-old"} + p.currentConfig = baseCfg + initialVersion := computeCompositeVersion(p.knownHashes) + p.latestVersion = initialVersion + + pollOnce(p, func(_ *nodev1.RouterConfig, _ string) error { + return errors.New("handler failed") + }) + + // State must remain unchanged after a handler failure. + assert.Equal(t, initialVersion, p.latestVersion) + assert.Equal(t, "hash-base-old", p.knownHashes[""]) +} + +func TestComputeCompositeVersion_Deterministic(t *testing.T) { + m1 := map[string]string{"a": "1", "b": "2", "c": "3"} + m2 := map[string]string{"c": "3", "a": "1", "b": "2"} + assert.Equal(t, computeCompositeVersion(m1), computeCompositeVersion(m2)) +} + +func TestComputeCompositeVersion_DifferentInputsDifferentOutput(t *testing.T) { + m1 := map[string]string{"a": "1"} + m2 := map[string]string{"a": "2"} + assert.NotEqual(t, computeCompositeVersion(m1), computeCompositeVersion(m2)) +} diff --git a/router/pkg/routerconfig/cdn/split_fetcher.go b/router/pkg/routerconfig/cdn/split_fetcher.go new file mode 100644 index 0000000000..1d66fdaca0 --- /dev/null +++ b/router/pkg/routerconfig/cdn/split_fetcher.go @@ -0,0 +1,212 @@ +package cdn + +import ( + "compress/gzip" + "context" + "crypto/hmac" + "crypto/sha256" + "crypto/subtle" + "encoding/base64" + "errors" + "fmt" + "hash" + "io" + "net/http" + "net/url" + + nodev1 "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/node/v1" + "github.com/wundergraph/cosmo/router/internal/httpclient" + "github.com/wundergraph/cosmo/router/internal/jwt" + "github.com/wundergraph/cosmo/router/pkg/execution_config" + "go.uber.org/zap" + "google.golang.org/protobuf/encoding/protojson" +) + +// SplitFetcher fetches mapper and individual router configs from the CDN +// using simple GET requests (no version negotiation). +type SplitFetcher struct { + cdnURL *url.URL + authenticationToken string + // federatedGraphID is url-escaped and extracted from the router JWT. + federatedGraphID string + // organizationID is url-escaped and extracted from the router JWT. + organizationID string + httpClient *http.Client + logger *zap.Logger + hash hash.Hash // HMAC-SHA256 for signature validation; nil if no key configured +} + +// NewSplitFetcher creates a SplitFetcher for the given CDN endpoint and router token. +// It follows the same setup pattern as NewClient. +func NewSplitFetcher(endpoint string, token string, opts *Options) (*SplitFetcher, error) { + if token == "" { + return nil, errors.New("token is required for split config fetcher") + } + + u, err := url.Parse(endpoint) + if err != nil { + return nil, fmt.Errorf("invalid CDN URL %q: %w", endpoint, err) + } + + if opts.Logger == nil { + opts.Logger = zap.NewNop() + } + + claims, err := jwt.ExtractFederatedGraphTokenClaims(token) + if err != nil { + return nil, err + } + + logger := opts.Logger.With(zap.String("component", "split_config_fetcher")) + + f := &SplitFetcher{ + cdnURL: u, + authenticationToken: token, + federatedGraphID: url.PathEscape(claims.FederatedGraphID), + organizationID: url.PathEscape(claims.OrganizationID), + httpClient: httpclient.NewRetryableHTTPClient(logger), + logger: logger, + } + + if opts.SignatureKey != "" { + f.hash = hmac.New(sha256.New, []byte(opts.SignatureKey)) + } + + return f, nil +} + +// doGET performs an authenticated GET request to the given CDN path, validates the +// optional HMAC signature, and returns the (decompressed) response body. +func (f *SplitFetcher) doGET(ctx context.Context, path string) ([]byte, error) { + target := f.cdnURL.ResolveReference(&url.URL{Path: path}) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, target.String(), nil) + if err != nil { + return nil, err + } + req.Header.Add("Authorization", "Bearer "+f.authenticationToken) + req.Header.Set("Accept-Encoding", "gzip") + + resp, err := f.httpClient.Do(req) + if err != nil { + return nil, err + } + defer func() { + _ = resp.Body.Close() + }() + + switch resp.StatusCode { + case http.StatusOK: + // handled below + case http.StatusNotFound: + return nil, ErrConfigNotFound + case http.StatusUnauthorized: + return nil, errors.New("could not authenticate against CDN") + case http.StatusBadRequest: + return nil, errors.New("bad request") + default: + return nil, fmt.Errorf("unexpected status code when loading split config, statusCode: %d", resp.StatusCode) + } + + var reader io.Reader = resp.Body + if resp.Header.Get("Content-Encoding") == "gzip" { + r, err := gzip.NewReader(resp.Body) + if err != nil { + return nil, fmt.Errorf("could not create gzip reader: %w", err) + } + defer func() { + _ = r.Close() + }() + reader = r + } + + body, err := io.ReadAll(reader) + if err != nil { + return nil, fmt.Errorf("could not read response body: %w", err) + } + if len(body) == 0 { + return nil, errors.New("empty response body") + } + + // Validate HMAC signature when a key is configured. + if f.hash != nil { + configSignature := resp.Header.Get(sigResponseHeaderName) + if configSignature == "" { + f.logger.Error( + "Signature header not found in CDN response. Ensure that your Admission Controller was able to sign the config.", + zap.Error(ErrMissingSignatureHeader), + ) + return nil, ErrMissingSignatureHeader + } + + if _, err := f.hash.Write(body); err != nil { + return nil, fmt.Errorf("could not write config body to hmac: %w", err) + } + dataHmac := f.hash.Sum(nil) + f.hash.Reset() + + rawSignature, err := base64.StdEncoding.DecodeString(configSignature) + if err != nil { + return nil, fmt.Errorf("could not decode signature: %w", err) + } + + if subtle.ConstantTimeCompare(rawSignature, dataHmac) != 1 { + f.logger.Error( + "Invalid config signature, potential tampering detected.", + zap.Error(ErrInvalidSignature), + ) + return nil, ErrInvalidSignature + } + + f.logger.Info("Config signature validation successful", + zap.String("federatedGraphID", f.federatedGraphID), + zap.String("signature", configSignature), + ) + } + + return body, nil +} + +// FetchMapper fetches the mapper file and returns the active graph configs and their hashes. +func (f *SplitFetcher) FetchMapper(ctx context.Context) (*nodev1.ActiveGraphs, error) { + path := fmt.Sprintf("/%s/%s/manifest/mapper.json", f.organizationID, f.federatedGraphID) + body, err := f.doGET(ctx, path) + if err != nil { + return nil, fmt.Errorf("could not fetch mapper: %w", err) + } + + var activeGraphs nodev1.ActiveGraphs + ms := protojson.UnmarshalOptions{DiscardUnknown: true} + if err := ms.Unmarshal(body, &activeGraphs); err != nil { + return nil, fmt.Errorf("could not unmarshal mapper: %w", err) + } + + return &activeGraphs, nil +} + +// FetchConfig fetches a single router config from CDN. +// featureFlagName="" returns the base graph; any other value returns the named feature flag config. +func (f *SplitFetcher) FetchConfig(ctx context.Context, featureFlagName string) (*nodev1.RouterConfig, error) { + var path string + if featureFlagName == "" { + path = fmt.Sprintf("/%s/%s/manifest/latest.json", f.organizationID, f.federatedGraphID) + } else { + path = fmt.Sprintf("/%s/%s/manifest/feature-flags/%s.json", + f.organizationID, + f.federatedGraphID, + url.PathEscape(featureFlagName), + ) + } + + body, err := f.doGET(ctx, path) + if err != nil { + return nil, fmt.Errorf("could not fetch config for %q: %w", featureFlagName, err) + } + + cfg, err := execution_config.UnmarshalConfig(body) + if err != nil { + return nil, fmt.Errorf("could not unmarshal router config for %q: %w", featureFlagName, err) + } + + return cfg, nil +} From 8d675ead40d5bcb32a38214994bbdf75829d203a Mon Sep 17 00:00:00 2001 From: Dominik Korittki <23359034+dkorittki@users.noreply.github.com> Date: Mon, 4 May 2026 13:19:11 +0200 Subject: [PATCH 04/15] fix: guard against nil variable --- router/pkg/routerconfig/cdn/split_fetcher.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/router/pkg/routerconfig/cdn/split_fetcher.go b/router/pkg/routerconfig/cdn/split_fetcher.go index 1d66fdaca0..9f3f9d5906 100644 --- a/router/pkg/routerconfig/cdn/split_fetcher.go +++ b/router/pkg/routerconfig/cdn/split_fetcher.go @@ -43,6 +43,10 @@ func NewSplitFetcher(endpoint string, token string, opts *Options) (*SplitFetche return nil, errors.New("token is required for split config fetcher") } + if opts == nil { + opts = &Options{} + } + u, err := url.Parse(endpoint) if err != nil { return nil, fmt.Errorf("invalid CDN URL %q: %w", endpoint, err) From 7cd3a63f9985d372c0720756fc49efaa1608c9e4 Mon Sep 17 00:00:00 2001 From: Dominik Korittki <23359034+dkorittki@users.noreply.github.com> Date: Mon, 4 May 2026 13:24:10 +0200 Subject: [PATCH 05/15] chore: remove blank line --- router/core/graph_server.go | 1 - router/internal/jwt/claims.go | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/router/core/graph_server.go b/router/core/graph_server.go index 7ccc932b27..23812b8fe4 100644 --- a/router/core/graph_server.go +++ b/router/core/graph_server.go @@ -208,7 +208,6 @@ func newGraphServer(ctx context.Context, r *Router, routerConfig *nodev1.RouterC return nil, err } baseOtelAttributes = append(baseOtelAttributes, otel.WgFederatedGraphID.String(claims.FederatedGraphID)) - } s.baseOtelAttributes = baseOtelAttributes diff --git a/router/internal/jwt/claims.go b/router/internal/jwt/claims.go index 441a2b56f3..cdf8f78a00 100644 --- a/router/internal/jwt/claims.go +++ b/router/internal/jwt/claims.go @@ -8,10 +8,9 @@ import ( ) const ( - FederatedGraphIDClaim = "federated_graph_id" - OrganizationIDClaim = "organization_id" - FeaturesClaim = "features" - + FederatedGraphIDClaim = "federated_graph_id" + OrganizationIDClaim = "organization_id" + FeaturesClaim = "features" FeatureSplitConfigLoading = "split-config-loading" ) From 8c3e2df39bb353798da9b0ba8e12ef545a6d5988 Mon Sep 17 00:00:00 2001 From: Dominik Korittki <23359034+dkorittki@users.noreply.github.com> Date: Mon, 4 May 2026 13:30:13 +0200 Subject: [PATCH 06/15] chore: rename doGet to get --- router/pkg/routerconfig/cdn/split_fetcher.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/router/pkg/routerconfig/cdn/split_fetcher.go b/router/pkg/routerconfig/cdn/split_fetcher.go index 9f3f9d5906..6af41aac22 100644 --- a/router/pkg/routerconfig/cdn/split_fetcher.go +++ b/router/pkg/routerconfig/cdn/split_fetcher.go @@ -79,9 +79,9 @@ func NewSplitFetcher(endpoint string, token string, opts *Options) (*SplitFetche return f, nil } -// doGET performs an authenticated GET request to the given CDN path, validates the +// get performs an authenticated GET request to the given CDN path, validates the // optional HMAC signature, and returns the (decompressed) response body. -func (f *SplitFetcher) doGET(ctx context.Context, path string) ([]byte, error) { +func (f *SplitFetcher) get(ctx context.Context, path string) ([]byte, error) { target := f.cdnURL.ResolveReference(&url.URL{Path: path}) req, err := http.NewRequestWithContext(ctx, http.MethodGet, target.String(), nil) @@ -174,7 +174,7 @@ func (f *SplitFetcher) doGET(ctx context.Context, path string) ([]byte, error) { // FetchMapper fetches the mapper file and returns the active graph configs and their hashes. func (f *SplitFetcher) FetchMapper(ctx context.Context) (*nodev1.ActiveGraphs, error) { path := fmt.Sprintf("/%s/%s/manifest/mapper.json", f.organizationID, f.federatedGraphID) - body, err := f.doGET(ctx, path) + body, err := f.get(ctx, path) if err != nil { return nil, fmt.Errorf("could not fetch mapper: %w", err) } @@ -202,7 +202,7 @@ func (f *SplitFetcher) FetchConfig(ctx context.Context, featureFlagName string) ) } - body, err := f.doGET(ctx, path) + body, err := f.get(ctx, path) if err != nil { return nil, fmt.Errorf("could not fetch config for %q: %w", featureFlagName, err) } From d0675ddf4a218463ec9e411acb378caf429d50f6 Mon Sep 17 00:00:00 2001 From: Dominik Korittki <23359034+dkorittki@users.noreply.github.com> Date: Mon, 4 May 2026 13:43:25 +0200 Subject: [PATCH 07/15] chore: outsource hmac verification into own function --- router/pkg/routerconfig/cdn/client.go | 41 +---------------- router/pkg/routerconfig/cdn/split_fetcher.go | 37 ++------------- router/pkg/routerconfig/cdn/utils.go | 48 ++++++++++++++++++++ 3 files changed, 53 insertions(+), 73 deletions(-) create mode 100644 router/pkg/routerconfig/cdn/utils.go diff --git a/router/pkg/routerconfig/cdn/client.go b/router/pkg/routerconfig/cdn/client.go index 3bd8289aba..b22bce09f8 100644 --- a/router/pkg/routerconfig/cdn/client.go +++ b/router/pkg/routerconfig/cdn/client.go @@ -6,8 +6,6 @@ import ( "context" "crypto/hmac" "crypto/sha256" - "crypto/subtle" - "encoding/base64" "encoding/json" "errors" "fmt" @@ -184,45 +182,10 @@ func (cdn *Client) getRouterConfig(ctx context.Context, version string, _ time.T return nil, errors.New("empty response body") } - /** - * If a signature key is set, we need to validate the signature of the received config - */ - if cdn.hash != nil { - configSignature := resp.Header.Get(sigResponseHeaderName) - if configSignature == "" { - cdn.logger.Error( - "Signature header not found in CDN response. Ensure that your Admission Controller was able to sign the config. Open the compositions page in the Studio to check the status of the last deployment", - zap.Error(ErrMissingSignatureHeader), - ) - return nil, ErrMissingSignatureHeader - } - - // create a signature of the received config body - if _, err := cdn.hash.Write(body); err != nil { - return nil, fmt.Errorf("could not write config body to hmac: %w", err) - } - dataHmac := cdn.hash.Sum(nil) - cdn.hash.Reset() - - // compare received signature with the one we calculated with the private signature key - rawSignature, err := base64.StdEncoding.DecodeString(configSignature) - if err != nil { - return nil, fmt.Errorf("could not hex decode signature key: %w", err) + if err := validateHMACSignature(cdn.hash, body, resp.Header.Get(sigResponseHeaderName), cdn.logger, cdn.federatedGraphID); err != nil { + return nil, err } - - if subtle.ConstantTimeCompare(rawSignature, dataHmac) != 1 { - cdn.logger.Error( - "Invalid config signature, potential tampering detected. Ensure that your Admission Controller has signed the config correctly. Open the compositions page in the Studio to check the status of the last deployment", - zap.Error(ErrInvalidSignature), - ) - return nil, ErrInvalidSignature - } - - cdn.logger.Info("Config signature validation successful", - zap.String("federatedGraphID", cdn.federatedGraphID), - zap.String("signature", configSignature), - ) } return body, nil diff --git a/router/pkg/routerconfig/cdn/split_fetcher.go b/router/pkg/routerconfig/cdn/split_fetcher.go index 6af41aac22..2abd34a7b6 100644 --- a/router/pkg/routerconfig/cdn/split_fetcher.go +++ b/router/pkg/routerconfig/cdn/split_fetcher.go @@ -5,8 +5,6 @@ import ( "context" "crypto/hmac" "crypto/sha256" - "crypto/subtle" - "encoding/base64" "errors" "fmt" "hash" @@ -79,7 +77,7 @@ func NewSplitFetcher(endpoint string, token string, opts *Options) (*SplitFetche return f, nil } -// get performs an authenticated GET request to the given CDN path, validates the +// get performs an authenticated HTTP GET request to the given CDN path, validates the // optional HMAC signature, and returns the (decompressed) response body. func (f *SplitFetcher) get(ctx context.Context, path string) ([]byte, error) { target := f.cdnURL.ResolveReference(&url.URL{Path: path}) @@ -134,38 +132,9 @@ func (f *SplitFetcher) get(ctx context.Context, path string) ([]byte, error) { // Validate HMAC signature when a key is configured. if f.hash != nil { - configSignature := resp.Header.Get(sigResponseHeaderName) - if configSignature == "" { - f.logger.Error( - "Signature header not found in CDN response. Ensure that your Admission Controller was able to sign the config.", - zap.Error(ErrMissingSignatureHeader), - ) - return nil, ErrMissingSignatureHeader + if err := validateHMACSignature(f.hash, body, resp.Header.Get(sigResponseHeaderName), f.logger, f.federatedGraphID); err != nil { + return nil, err } - - if _, err := f.hash.Write(body); err != nil { - return nil, fmt.Errorf("could not write config body to hmac: %w", err) - } - dataHmac := f.hash.Sum(nil) - f.hash.Reset() - - rawSignature, err := base64.StdEncoding.DecodeString(configSignature) - if err != nil { - return nil, fmt.Errorf("could not decode signature: %w", err) - } - - if subtle.ConstantTimeCompare(rawSignature, dataHmac) != 1 { - f.logger.Error( - "Invalid config signature, potential tampering detected.", - zap.Error(ErrInvalidSignature), - ) - return nil, ErrInvalidSignature - } - - f.logger.Info("Config signature validation successful", - zap.String("federatedGraphID", f.federatedGraphID), - zap.String("signature", configSignature), - ) } return body, nil diff --git a/router/pkg/routerconfig/cdn/utils.go b/router/pkg/routerconfig/cdn/utils.go new file mode 100644 index 0000000000..b388b18f06 --- /dev/null +++ b/router/pkg/routerconfig/cdn/utils.go @@ -0,0 +1,48 @@ +package cdn + +import ( + "crypto/subtle" + "encoding/base64" + "fmt" + "hash" + + "go.uber.org/zap" +) + +// validateHMACSignature verifies the HMAC-SHA256 signature of body against encodedSig +// (the base64-encoded value of the X-Signature-SHA256 response header). +// h is reset after use so it can be reused for the next request. +func validateHMACSignature(h hash.Hash, body []byte, encodedSig string, logger *zap.Logger, federatedGraphID string) error { + if encodedSig == "" { + logger.Error( + "Signature header not found in CDN response. Ensure that your Admission Controller was able to sign the config. Open the compositions page in the Studio to check the status of the last deployment", + zap.Error(ErrMissingSignatureHeader), + ) + return ErrMissingSignatureHeader + } + + if _, err := h.Write(body); err != nil { + return fmt.Errorf("could not write config body to hmac: %w", err) + } + dataHmac := h.Sum(nil) + h.Reset() + + rawSignature, err := base64.StdEncoding.DecodeString(encodedSig) + if err != nil { + return fmt.Errorf("could not decode signature: %w", err) + } + + if subtle.ConstantTimeCompare(rawSignature, dataHmac) != 1 { + logger.Error( + "Invalid config signature, potential tampering detected. Ensure that your Admission Controller has signed the config correctly. Open the compositions page in the Studio to check the status of the last deployment", + zap.Error(ErrInvalidSignature), + ) + return ErrInvalidSignature + } + + logger.Info("Config signature validation successful", + zap.String("federatedGraphID", federatedGraphID), + zap.String("signature", encodedSig), + ) + return nil +} From 1dceabed63a907b42a847368d8506ad651a080ba Mon Sep 17 00:00:00 2001 From: Dominik Korittki <23359034+dkorittki@users.noreply.github.com> Date: Mon, 4 May 2026 13:56:08 +0200 Subject: [PATCH 08/15] chore: use empty struct as type for map used as set --- .../configpoller/split_config_poller.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/router/pkg/controlplane/configpoller/split_config_poller.go b/router/pkg/controlplane/configpoller/split_config_poller.go index 87f751ca9b..796b8bd7b6 100644 --- a/router/pkg/controlplane/configpoller/split_config_poller.go +++ b/router/pkg/controlplane/configpoller/split_config_poller.go @@ -179,20 +179,20 @@ func (p *splitConfigPoller) Subscribe(ctx context.Context, handler func(newConfi ) // Determine what changed, was added, or was removed. - changed := make(map[string]bool) - added := make(map[string]bool) - removed := make(map[string]bool) + changed := make(map[string]struct{}) + added := make(map[string]struct{}) + removed := make(map[string]struct{}) for name, hash := range graphConfigs { if oldHash, exists := p.knownHashes[name]; !exists { - added[name] = true + added[name] = struct{}{} } else if oldHash != hash { - changed[name] = true + changed[name] = struct{}{} } } for name := range p.knownHashes { if _, exists := graphConfigs[name]; !exists { - removed[name] = true + removed[name] = struct{}{} } } @@ -200,12 +200,12 @@ func (p *splitConfigPoller) Subscribe(ctx context.Context, handler func(newConfi patched := proto.Clone(p.currentConfig).(*nodev1.RouterConfig) // Apply changes and additions. - toFetch := make(map[string]bool, len(changed)+len(added)) + toFetch := make(map[string]struct{}, len(changed)+len(added)) for name := range changed { - toFetch[name] = true + toFetch[name] = struct{}{} } for name := range added { - toFetch[name] = true + toFetch[name] = struct{}{} } for name := range toFetch { From 88953e537e4970b1e5b884bf42f45c325fbb9137 Mon Sep 17 00:00:00 2001 From: Dominik Korittki <23359034+dkorittki@users.noreply.github.com> Date: Mon, 4 May 2026 14:26:43 +0200 Subject: [PATCH 09/15] fix: detect missing base graph entry --- router/pkg/controlplane/configpoller/split_config_poller.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/router/pkg/controlplane/configpoller/split_config_poller.go b/router/pkg/controlplane/configpoller/split_config_poller.go index 796b8bd7b6..ed661e4efe 100644 --- a/router/pkg/controlplane/configpoller/split_config_poller.go +++ b/router/pkg/controlplane/configpoller/split_config_poller.go @@ -166,6 +166,11 @@ func (p *splitConfigPoller) Subscribe(ctx context.Context, handler func(newConfi return } + if _, ok := graphConfigs[""]; !ok { + p.logger.Warn("Mapper missing base graph entry, keeping existing config") + return + } + newVersion := computeCompositeVersion(graphConfigs) if newVersion == p.latestVersion { p.logger.Debug("No changes detected in engine config, keeping existing config") From 22840710b2319a903f8cf698107f16abacd7f3c6 Mon Sep 17 00:00:00 2001 From: Dominik Korittki <23359034+dkorittki@users.noreply.github.com> Date: Mon, 4 May 2026 16:42:20 +0200 Subject: [PATCH 10/15] chore: small refactors --- router/core/init_config_poller.go | 8 +++----- .../controlplane/configpoller/split_config_poller_test.go | 2 -- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/router/core/init_config_poller.go b/router/core/init_config_poller.go index ecbae4ca77..2748be71f5 100644 --- a/router/core/init_config_poller.go +++ b/router/core/init_config_poller.go @@ -135,13 +135,11 @@ func InitializeConfigPoller(r *Router, registry *ProviderRegistry) (*configpolle if hasSplitCfgFeature { providerID := r.routerConfigPollerConfig.Storage.ProviderID - if providerID != "" { - r.logger.Info("split-config-loading feature is enabled but a custom storage provider is configured; falling back to regular config polling", - zap.String("provider_id", providerID), - ) - } else { + if providerID == "" { return newSplitConfigPoller(r) } + r.logger.Info("split-config-loading feature is enabled but a custom storage provider is configured; falling back to regular config polling", + zap.String("provider_id", providerID)) } primaryClient, err := getConfigClient(r, registry, r.routerConfigPollerConfig.Storage.ProviderID, false) diff --git a/router/pkg/controlplane/configpoller/split_config_poller_test.go b/router/pkg/controlplane/configpoller/split_config_poller_test.go index 36ee6640dd..07a63286ab 100644 --- a/router/pkg/controlplane/configpoller/split_config_poller_test.go +++ b/router/pkg/controlplane/configpoller/split_config_poller_test.go @@ -152,8 +152,6 @@ func TestSplitGetRouterConfig_ConfigFetchError(t *testing.T) { // It extracts the subscribe callback by using a fake controlplane.Poller. func pollOnce(p *splitConfigPoller, handler func(*nodev1.RouterConfig, string) error) { var tickFn func() - - // Temporarily replace the poller with one that captures the tick function. p.poller = &capturingPoller{capture: &tickFn} p.Subscribe(context.Background(), handler) if tickFn != nil { From 88ef61fde8d051363a782f4809b5b5ab0a8b0f48 Mon Sep 17 00:00:00 2001 From: Dominik Korittki <23359034+dkorittki@users.noreply.github.com> Date: Mon, 4 May 2026 16:42:25 +0200 Subject: [PATCH 11/15] Revert "chore: outsource hmac verification into own function" This reverts commit d0675ddf4a218463ec9e411acb378caf429d50f6. --- router/pkg/routerconfig/cdn/client.go | 41 ++++++++++++++++- router/pkg/routerconfig/cdn/split_fetcher.go | 37 +++++++++++++-- router/pkg/routerconfig/cdn/utils.go | 48 -------------------- 3 files changed, 73 insertions(+), 53 deletions(-) delete mode 100644 router/pkg/routerconfig/cdn/utils.go diff --git a/router/pkg/routerconfig/cdn/client.go b/router/pkg/routerconfig/cdn/client.go index b22bce09f8..3bd8289aba 100644 --- a/router/pkg/routerconfig/cdn/client.go +++ b/router/pkg/routerconfig/cdn/client.go @@ -6,6 +6,8 @@ import ( "context" "crypto/hmac" "crypto/sha256" + "crypto/subtle" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -182,10 +184,45 @@ func (cdn *Client) getRouterConfig(ctx context.Context, version string, _ time.T return nil, errors.New("empty response body") } + /** + * If a signature key is set, we need to validate the signature of the received config + */ + if cdn.hash != nil { - if err := validateHMACSignature(cdn.hash, body, resp.Header.Get(sigResponseHeaderName), cdn.logger, cdn.federatedGraphID); err != nil { - return nil, err + configSignature := resp.Header.Get(sigResponseHeaderName) + if configSignature == "" { + cdn.logger.Error( + "Signature header not found in CDN response. Ensure that your Admission Controller was able to sign the config. Open the compositions page in the Studio to check the status of the last deployment", + zap.Error(ErrMissingSignatureHeader), + ) + return nil, ErrMissingSignatureHeader + } + + // create a signature of the received config body + if _, err := cdn.hash.Write(body); err != nil { + return nil, fmt.Errorf("could not write config body to hmac: %w", err) + } + dataHmac := cdn.hash.Sum(nil) + cdn.hash.Reset() + + // compare received signature with the one we calculated with the private signature key + rawSignature, err := base64.StdEncoding.DecodeString(configSignature) + if err != nil { + return nil, fmt.Errorf("could not hex decode signature key: %w", err) } + + if subtle.ConstantTimeCompare(rawSignature, dataHmac) != 1 { + cdn.logger.Error( + "Invalid config signature, potential tampering detected. Ensure that your Admission Controller has signed the config correctly. Open the compositions page in the Studio to check the status of the last deployment", + zap.Error(ErrInvalidSignature), + ) + return nil, ErrInvalidSignature + } + + cdn.logger.Info("Config signature validation successful", + zap.String("federatedGraphID", cdn.federatedGraphID), + zap.String("signature", configSignature), + ) } return body, nil diff --git a/router/pkg/routerconfig/cdn/split_fetcher.go b/router/pkg/routerconfig/cdn/split_fetcher.go index 2abd34a7b6..6af41aac22 100644 --- a/router/pkg/routerconfig/cdn/split_fetcher.go +++ b/router/pkg/routerconfig/cdn/split_fetcher.go @@ -5,6 +5,8 @@ import ( "context" "crypto/hmac" "crypto/sha256" + "crypto/subtle" + "encoding/base64" "errors" "fmt" "hash" @@ -77,7 +79,7 @@ func NewSplitFetcher(endpoint string, token string, opts *Options) (*SplitFetche return f, nil } -// get performs an authenticated HTTP GET request to the given CDN path, validates the +// get performs an authenticated GET request to the given CDN path, validates the // optional HMAC signature, and returns the (decompressed) response body. func (f *SplitFetcher) get(ctx context.Context, path string) ([]byte, error) { target := f.cdnURL.ResolveReference(&url.URL{Path: path}) @@ -132,9 +134,38 @@ func (f *SplitFetcher) get(ctx context.Context, path string) ([]byte, error) { // Validate HMAC signature when a key is configured. if f.hash != nil { - if err := validateHMACSignature(f.hash, body, resp.Header.Get(sigResponseHeaderName), f.logger, f.federatedGraphID); err != nil { - return nil, err + configSignature := resp.Header.Get(sigResponseHeaderName) + if configSignature == "" { + f.logger.Error( + "Signature header not found in CDN response. Ensure that your Admission Controller was able to sign the config.", + zap.Error(ErrMissingSignatureHeader), + ) + return nil, ErrMissingSignatureHeader } + + if _, err := f.hash.Write(body); err != nil { + return nil, fmt.Errorf("could not write config body to hmac: %w", err) + } + dataHmac := f.hash.Sum(nil) + f.hash.Reset() + + rawSignature, err := base64.StdEncoding.DecodeString(configSignature) + if err != nil { + return nil, fmt.Errorf("could not decode signature: %w", err) + } + + if subtle.ConstantTimeCompare(rawSignature, dataHmac) != 1 { + f.logger.Error( + "Invalid config signature, potential tampering detected.", + zap.Error(ErrInvalidSignature), + ) + return nil, ErrInvalidSignature + } + + f.logger.Info("Config signature validation successful", + zap.String("federatedGraphID", f.federatedGraphID), + zap.String("signature", configSignature), + ) } return body, nil diff --git a/router/pkg/routerconfig/cdn/utils.go b/router/pkg/routerconfig/cdn/utils.go deleted file mode 100644 index b388b18f06..0000000000 --- a/router/pkg/routerconfig/cdn/utils.go +++ /dev/null @@ -1,48 +0,0 @@ -package cdn - -import ( - "crypto/subtle" - "encoding/base64" - "fmt" - "hash" - - "go.uber.org/zap" -) - -// validateHMACSignature verifies the HMAC-SHA256 signature of body against encodedSig -// (the base64-encoded value of the X-Signature-SHA256 response header). -// h is reset after use so it can be reused for the next request. -func validateHMACSignature(h hash.Hash, body []byte, encodedSig string, logger *zap.Logger, federatedGraphID string) error { - if encodedSig == "" { - logger.Error( - "Signature header not found in CDN response. Ensure that your Admission Controller was able to sign the config. Open the compositions page in the Studio to check the status of the last deployment", - zap.Error(ErrMissingSignatureHeader), - ) - return ErrMissingSignatureHeader - } - - if _, err := h.Write(body); err != nil { - return fmt.Errorf("could not write config body to hmac: %w", err) - } - dataHmac := h.Sum(nil) - h.Reset() - - rawSignature, err := base64.StdEncoding.DecodeString(encodedSig) - if err != nil { - return fmt.Errorf("could not decode signature: %w", err) - } - - if subtle.ConstantTimeCompare(rawSignature, dataHmac) != 1 { - logger.Error( - "Invalid config signature, potential tampering detected. Ensure that your Admission Controller has signed the config correctly. Open the compositions page in the Studio to check the status of the last deployment", - zap.Error(ErrInvalidSignature), - ) - return ErrInvalidSignature - } - - logger.Info("Config signature validation successful", - zap.String("federatedGraphID", federatedGraphID), - zap.String("signature", encodedSig), - ) - return nil -} From 8159cb40768a4ec5ba549d1442ea6de725b8548e Mon Sep 17 00:00:00 2001 From: Dominik Korittki <23359034+dkorittki@users.noreply.github.com> Date: Mon, 4 May 2026 17:03:44 +0200 Subject: [PATCH 12/15] chore: use url.JoinPath instead of fmt.Sprintf --- router/pkg/routerconfig/cdn/split_fetcher.go | 40 +++++++++++--------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/router/pkg/routerconfig/cdn/split_fetcher.go b/router/pkg/routerconfig/cdn/split_fetcher.go index 6af41aac22..c525140ef2 100644 --- a/router/pkg/routerconfig/cdn/split_fetcher.go +++ b/router/pkg/routerconfig/cdn/split_fetcher.go @@ -27,13 +27,11 @@ import ( type SplitFetcher struct { cdnURL *url.URL authenticationToken string - // federatedGraphID is url-escaped and extracted from the router JWT. - federatedGraphID string - // organizationID is url-escaped and extracted from the router JWT. - organizationID string - httpClient *http.Client - logger *zap.Logger - hash hash.Hash // HMAC-SHA256 for signature validation; nil if no key configured + federatedGraphID string + organizationID string + httpClient *http.Client + logger *zap.Logger + hash hash.Hash // HMAC-SHA256 for signature validation; nil if no key configured } // NewSplitFetcher creates a SplitFetcher for the given CDN endpoint and router token. @@ -66,8 +64,8 @@ func NewSplitFetcher(endpoint string, token string, opts *Options) (*SplitFetche f := &SplitFetcher{ cdnURL: u, authenticationToken: token, - federatedGraphID: url.PathEscape(claims.FederatedGraphID), - organizationID: url.PathEscape(claims.OrganizationID), + federatedGraphID: claims.FederatedGraphID, + organizationID: claims.OrganizationID, httpClient: httpclient.NewRetryableHTTPClient(logger), logger: logger, } @@ -173,7 +171,10 @@ func (f *SplitFetcher) get(ctx context.Context, path string) ([]byte, error) { // FetchMapper fetches the mapper file and returns the active graph configs and their hashes. func (f *SplitFetcher) FetchMapper(ctx context.Context) (*nodev1.ActiveGraphs, error) { - path := fmt.Sprintf("/%s/%s/manifest/mapper.json", f.organizationID, f.federatedGraphID) + path, err := url.JoinPath("/", f.organizationID, f.federatedGraphID, "manifest/mapper.json") + if err != nil { + return nil, fmt.Errorf("could not join path: %w", err) + } body, err := f.get(ctx, path) if err != nil { return nil, fmt.Errorf("could not fetch mapper: %w", err) @@ -191,15 +192,20 @@ func (f *SplitFetcher) FetchMapper(ctx context.Context) (*nodev1.ActiveGraphs, e // FetchConfig fetches a single router config from CDN. // featureFlagName="" returns the base graph; any other value returns the named feature flag config. func (f *SplitFetcher) FetchConfig(ctx context.Context, featureFlagName string) (*nodev1.RouterConfig, error) { - var path string + var ( + path string + err error + ) + if featureFlagName == "" { - path = fmt.Sprintf("/%s/%s/manifest/latest.json", f.organizationID, f.federatedGraphID) + path, err = url.JoinPath("/", f.organizationID, f.federatedGraphID, "manifest/latest.json") } else { - path = fmt.Sprintf("/%s/%s/manifest/feature-flags/%s.json", - f.organizationID, - f.federatedGraphID, - url.PathEscape(featureFlagName), - ) + path, err = url.JoinPath("/", f.organizationID, f.federatedGraphID, "manifest/feature-flags", + featureFlagName+".json") + } + + if err != nil { + return nil, fmt.Errorf("could not join path: %w", err) } body, err := f.get(ctx, path) From fa6678689b8d6ffa46fe9762f64d90eb497f8dac Mon Sep 17 00:00:00 2001 From: Dominik Korittki <23359034+dkorittki@users.noreply.github.com> Date: Mon, 4 May 2026 18:18:07 +0200 Subject: [PATCH 13/15] fix: use post request to CDN instead of GET --- router/pkg/routerconfig/cdn/split_fetcher.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/router/pkg/routerconfig/cdn/split_fetcher.go b/router/pkg/routerconfig/cdn/split_fetcher.go index c525140ef2..97242c720a 100644 --- a/router/pkg/routerconfig/cdn/split_fetcher.go +++ b/router/pkg/routerconfig/cdn/split_fetcher.go @@ -1,6 +1,7 @@ package cdn import ( + "bytes" "compress/gzip" "context" "crypto/hmac" @@ -77,15 +78,21 @@ func NewSplitFetcher(endpoint string, token string, opts *Options) (*SplitFetche return f, nil } -// get performs an authenticated GET request to the given CDN path, validates the +// post performs an authenticated POST request to the given CDN path, validates the // optional HMAC signature, and returns the (decompressed) response body. -func (f *SplitFetcher) get(ctx context.Context, path string) ([]byte, error) { +func (f *SplitFetcher) post(ctx context.Context, path string) ([]byte, error) { target := f.cdnURL.ResolveReference(&url.URL{Path: path}) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, target.String(), nil) + // This payload tells the cdn to always return a 200 OK instead + // 304 Not Modified (in case the version did not change). The split config poller + // already does config change detection, so no need for the CDN to do the same. + reqBody := []byte(`{"version": ""}`) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, target.String(), bytes.NewReader(reqBody)) if err != nil { return nil, err } + req.Header.Set("Content-Type", "application/json; charset=UTF-8") req.Header.Add("Authorization", "Bearer "+f.authenticationToken) req.Header.Set("Accept-Encoding", "gzip") @@ -175,7 +182,7 @@ func (f *SplitFetcher) FetchMapper(ctx context.Context) (*nodev1.ActiveGraphs, e if err != nil { return nil, fmt.Errorf("could not join path: %w", err) } - body, err := f.get(ctx, path) + body, err := f.post(ctx, path) if err != nil { return nil, fmt.Errorf("could not fetch mapper: %w", err) } @@ -208,7 +215,7 @@ func (f *SplitFetcher) FetchConfig(ctx context.Context, featureFlagName string) return nil, fmt.Errorf("could not join path: %w", err) } - body, err := f.get(ctx, path) + body, err := f.post(ctx, path) if err != nil { return nil, fmt.Errorf("could not fetch config for %q: %w", featureFlagName, err) } From 579de46828b4918a68911d2c8eb7c06228199023 Mon Sep 17 00:00:00 2001 From: Dominik Korittki <23359034+dkorittki@users.noreply.github.com> Date: Tue, 5 May 2026 11:42:57 +0200 Subject: [PATCH 14/15] fix: path escape variables --- router/pkg/routerconfig/cdn/split_fetcher.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/router/pkg/routerconfig/cdn/split_fetcher.go b/router/pkg/routerconfig/cdn/split_fetcher.go index 97242c720a..1f39fbe85f 100644 --- a/router/pkg/routerconfig/cdn/split_fetcher.go +++ b/router/pkg/routerconfig/cdn/split_fetcher.go @@ -65,8 +65,8 @@ func NewSplitFetcher(endpoint string, token string, opts *Options) (*SplitFetche f := &SplitFetcher{ cdnURL: u, authenticationToken: token, - federatedGraphID: claims.FederatedGraphID, - organizationID: claims.OrganizationID, + federatedGraphID: url.PathEscape(claims.FederatedGraphID), + organizationID: url.PathEscape(claims.OrganizationID), httpClient: httpclient.NewRetryableHTTPClient(logger), logger: logger, } From adf6a89cb23cd35315b8a52f3698d0261eb4a8ea Mon Sep 17 00:00:00 2001 From: Dominik Korittki <23359034+dkorittki@users.noreply.github.com> Date: Tue, 5 May 2026 12:15:21 +0200 Subject: [PATCH 15/15] chore: add split fetcher tests --- .../configpoller/split_config_poller_test.go | 2 +- .../routerconfig/cdn/split_fetcher_test.go | 439 ++++++++++++++++++ 2 files changed, 440 insertions(+), 1 deletion(-) create mode 100644 router/pkg/routerconfig/cdn/split_fetcher_test.go diff --git a/router/pkg/controlplane/configpoller/split_config_poller_test.go b/router/pkg/controlplane/configpoller/split_config_poller_test.go index 07a63286ab..6760ce89c9 100644 --- a/router/pkg/controlplane/configpoller/split_config_poller_test.go +++ b/router/pkg/controlplane/configpoller/split_config_poller_test.go @@ -153,7 +153,7 @@ func TestSplitGetRouterConfig_ConfigFetchError(t *testing.T) { func pollOnce(p *splitConfigPoller, handler func(*nodev1.RouterConfig, string) error) { var tickFn func() p.poller = &capturingPoller{capture: &tickFn} - p.Subscribe(context.Background(), handler) + p.Subscribe(context.Background(), handler) // sets tickFn if tickFn != nil { tickFn() } diff --git a/router/pkg/routerconfig/cdn/split_fetcher_test.go b/router/pkg/routerconfig/cdn/split_fetcher_test.go new file mode 100644 index 0000000000..19eb06aca0 --- /dev/null +++ b/router/pkg/routerconfig/cdn/split_fetcher_test.go @@ -0,0 +1,439 @@ +package cdn_test + +import ( + "bytes" + "compress/gzip" + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "io" + "net/http" + "net/http/httptest" + "testing" + + jwt "github.com/golang-jwt/jwt/v5" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + nodev1 "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/node/v1" + "github.com/wundergraph/cosmo/router/pkg/routerconfig/cdn" + "google.golang.org/protobuf/encoding/protojson" +) + +const ( + testGraphID = "871e5543-60f6-4ffc-a302-4f15277de4e7" + testOrgID = "5f718753-77d6-4e28-a3a3-52b24c812802" +) + +// testToken is a HS256-signed JWT with testGraphID / testOrgID claims. +// NewSplitFetcher calls ParseUnverified internally, so the signing key is irrelevant. +var testToken = func() string { + claims := jwt.MapClaims{ + "federated_graph_id": testGraphID, + "organization_id": testOrgID, + "features": []string{"split-config-loading"}, + } + tok := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + str, err := tok.SignedString([]byte("test-secret")) + if err != nil { + panic("failed to create test token: " + err.Error()) + } + return str +}() + +// newFetcher creates a SplitFetcher pointed at serverURL using testToken. +func newFetcher(t *testing.T, serverURL string, opts *cdn.Options) *cdn.SplitFetcher { + t.Helper() + f, err := cdn.NewSplitFetcher(serverURL, testToken, opts) + require.NoError(t, err) + return f +} + +func marshalActiveGraphs(t *testing.T, configs map[string]string) []byte { + t.Helper() + b, err := protojson.Marshal(&nodev1.ActiveGraphs{GraphConfigs: configs}) + require.NoError(t, err) + return b +} + +func marshalRouterConfig(t *testing.T, cfg *nodev1.RouterConfig) []byte { + t.Helper() + b, err := protojson.Marshal(cfg) + require.NoError(t, err) + return b +} + +func gzipData(t *testing.T, data []byte) []byte { + t.Helper() + var buf bytes.Buffer + w := gzip.NewWriter(&buf) + _, err := w.Write(data) + require.NoError(t, err) + require.NoError(t, w.Close()) + return buf.Bytes() +} + +func computeHMAC(key, body []byte) string { + h := hmac.New(sha256.New, key) + h.Write(body) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} + +// ─── NewSplitFetcher ────────────────────────────────────────────────────────── + +func TestNewSplitFetcher_EmptyToken(t *testing.T) { + _, err := cdn.NewSplitFetcher("http://cdn.example.com", "", nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "token is required") +} + +func TestNewSplitFetcher_InvalidToken(t *testing.T) { + _, err := cdn.NewSplitFetcher("http://cdn.example.com", "not-a-jwt", nil) + require.Error(t, err) +} + +func TestNewSplitFetcher_InvalidURL(t *testing.T) { + _, err := cdn.NewSplitFetcher("://invalid-url", testToken, nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid CDN URL") +} + +func TestNewSplitFetcher_NilOpts(t *testing.T) { + f, err := cdn.NewSplitFetcher("http://cdn.example.com", testToken, nil) + require.NoError(t, err) + require.NotNil(t, f) +} + +func TestNewSplitFetcher_WithSignatureKey(t *testing.T) { + f, err := cdn.NewSplitFetcher("http://cdn.example.com", testToken, &cdn.Options{SignatureKey: "my-secret"}) + require.NoError(t, err) + require.NotNil(t, f) +} + +// ─── HTTP error status codes ────────────────────────────────────────────────── + +func TestFetchMapper_HTTPErrors(t *testing.T) { + tests := []struct { + name string + statusCode int + wantErr error // checked with errors.Is when non-nil + wantErrMsg string // checked with assert.Contains when non-empty + }{ + { + name: "not found", + statusCode: http.StatusNotFound, + wantErr: cdn.ErrConfigNotFound, + }, + { + name: "unauthorized", + statusCode: http.StatusUnauthorized, + wantErrMsg: "could not authenticate against CDN", + }, + { + name: "bad request", + statusCode: http.StatusBadRequest, + wantErrMsg: "bad request", + }, + { + // Real unexpected CDN codes (5xx) would be retried; 418 exercises the + // default branch without triggering the retryable client's retry loop. + name: "unexpected status", + statusCode: http.StatusTeapot, + wantErrMsg: "unexpected status code when loading split config, statusCode: 418", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(tt.statusCode) + })) + defer srv.Close() + + _, err := newFetcher(t, srv.URL, nil).FetchMapper(context.Background()) + require.Error(t, err) + if tt.wantErr != nil { + assert.ErrorIs(t, err, tt.wantErr) + } + if tt.wantErrMsg != "" { + assert.Contains(t, err.Error(), tt.wantErrMsg) + } + }) + } +} + +// ─── Request details ────────────────────────────────────────────────────────── + +func TestFetchMapper_RequestMethodAndBody(t *testing.T) { + var gotMethod string + var gotBody []byte + body := marshalActiveGraphs(t, map[string]string{"": "h1"}) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotMethod = r.Method + gotBody, _ = io.ReadAll(r.Body) + w.WriteHeader(http.StatusOK) + _, _ = w.Write(body) + })) + defer srv.Close() + + _, err := newFetcher(t, srv.URL, nil).FetchMapper(context.Background()) + require.NoError(t, err) + + assert.Equal(t, http.MethodPost, gotMethod) + assert.JSONEq(t, `{"version":""}`, string(gotBody)) +} + +func TestFetchMapper_RequestHeaders(t *testing.T) { + body := marshalActiveGraphs(t, map[string]string{"": "h1"}) + var gotAuth, gotCT, gotAE string + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotAuth = r.Header.Get("Authorization") + gotCT = r.Header.Get("Content-Type") + gotAE = r.Header.Get("Accept-Encoding") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(body) + })) + defer srv.Close() + + _, err := newFetcher(t, srv.URL, nil).FetchMapper(context.Background()) + require.NoError(t, err) + + assert.Equal(t, "Bearer "+testToken, gotAuth) + assert.Equal(t, "application/json; charset=UTF-8", gotCT) + assert.Equal(t, "gzip", gotAE) +} + +// ─── Response body handling ─────────────────────────────────────────────────── + +func TestFetchMapper_EmptyBody(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + // intentionally write nothing + })) + defer srv.Close() + + _, err := newFetcher(t, srv.URL, nil).FetchMapper(context.Background()) + require.Error(t, err) + assert.Contains(t, err.Error(), "empty response body") +} + +func TestFetchMapper_GzipEncoded(t *testing.T) { + // The fetcher sets Accept-Encoding: gzip explicitly, which disables Go's + // transparent decompression, so the manual gzip path in post() is exercised. + raw := marshalActiveGraphs(t, map[string]string{"": "hash-base", "ff1": "hash-ff1"}) + compressed := gzipData(t, raw) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Encoding", "gzip") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(compressed) + })) + defer srv.Close() + + result, err := newFetcher(t, srv.URL, nil).FetchMapper(context.Background()) + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, "hash-base", result.GraphConfigs[""]) + assert.Equal(t, "hash-ff1", result.GraphConfigs["ff1"]) +} + +// ─── FetchMapper: parsing & URL path ───────────────────────────────────────── + +func TestFetchMapper_Success(t *testing.T) { + configs := map[string]string{"": "hash-base", "ff1": "hash-ff1"} + body := marshalActiveGraphs(t, configs) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(body) + })) + defer srv.Close() + + result, err := newFetcher(t, srv.URL, nil).FetchMapper(context.Background()) + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, "hash-base", result.GraphConfigs[""]) + assert.Equal(t, "hash-ff1", result.GraphConfigs["ff1"]) +} + +func TestFetchMapper_InvalidJSON(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("not-valid-proto-json")) + })) + defer srv.Close() + + _, err := newFetcher(t, srv.URL, nil).FetchMapper(context.Background()) + require.Error(t, err) + assert.Contains(t, err.Error(), "could not unmarshal mapper") +} + +func TestFetchMapper_URLPath(t *testing.T) { + var gotPath string + body := marshalActiveGraphs(t, map[string]string{"": "h1"}) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotPath = r.URL.Path + w.WriteHeader(http.StatusOK) + _, _ = w.Write(body) + })) + defer srv.Close() + + _, err := newFetcher(t, srv.URL, nil).FetchMapper(context.Background()) + require.NoError(t, err) + + assert.Equal(t, "/"+testOrgID+"/"+testGraphID+"/manifest/mapper.json", gotPath) +} + +// ─── HMAC signature validation ──────────────────────────────────────────────── + +func TestFetchMapper_Signature(t *testing.T) { + const sigKey = "my-secret" + body := marshalActiveGraphs(t, map[string]string{"": "h1"}) + validSig := computeHMAC([]byte(sigKey), body) + wrongSig := computeHMAC([]byte("wrong-key"), body) + + tests := []struct { + name string + sigKey string // empty = no HMAC configured + respSig string // value of X-Signature-SHA256 sent by server; empty = omit header + wantErr error + wantErrMsg string + }{ + { + name: "no key – signature header ignored", + sigKey: "", + // no header; succeeds because HMAC is disabled + }, + { + name: "valid signature", + sigKey: sigKey, + respSig: validSig, + }, + { + name: "missing signature header", + sigKey: sigKey, + wantErr: cdn.ErrMissingSignatureHeader, + }, + { + name: "invalid base64 in signature", + sigKey: sigKey, + respSig: "!!!not-base64!!!", + wantErrMsg: "could not decode signature", + }, + { + name: "signature mismatch", + sigKey: sigKey, + respSig: wrongSig, + wantErr: cdn.ErrInvalidSignature, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + if tt.respSig != "" { + w.Header().Set("X-Signature-SHA256", tt.respSig) + } + w.WriteHeader(http.StatusOK) + _, _ = w.Write(body) + })) + defer srv.Close() + + var opts *cdn.Options + if tt.sigKey != "" { + opts = &cdn.Options{SignatureKey: tt.sigKey} + } + _, err := newFetcher(t, srv.URL, opts).FetchMapper(context.Background()) + + if tt.wantErr == nil && tt.wantErrMsg == "" { + require.NoError(t, err) + return + } + require.Error(t, err) + if tt.wantErr != nil { + assert.ErrorIs(t, err, tt.wantErr) + } + if tt.wantErrMsg != "" { + assert.Contains(t, err.Error(), tt.wantErrMsg) + } + }) + } +} + +// ─── FetchConfig ────────────────────────────────────────────────────────────── + +func TestFetchConfig_URLPaths(t *testing.T) { + tests := []struct { + name string + featureFlagName string + wantPath string + }{ + { + name: "base graph uses latest.json", + featureFlagName: "", + wantPath: "/" + testOrgID + "/" + testGraphID + "/manifest/latest.json", + }, + { + name: "feature flag uses feature-flags sub-path", + featureFlagName: "my-flag", + wantPath: "/" + testOrgID + "/" + testGraphID + "/manifest/feature-flags/my-flag.json", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var gotPath string + body := marshalRouterConfig(t, &nodev1.RouterConfig{ + Version: "v1", + EngineConfig: &nodev1.EngineConfiguration{DefaultFlushInterval: 500}, + CompatibilityVersion: "1", + }) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotPath = r.URL.Path + w.WriteHeader(http.StatusOK) + _, _ = w.Write(body) + })) + defer srv.Close() + + _, err := newFetcher(t, srv.URL, nil).FetchConfig(context.Background(), tt.featureFlagName) + require.NoError(t, err) + assert.Equal(t, tt.wantPath, gotPath) + }) + } +} + +func TestFetchConfig_Success(t *testing.T) { + expected := &nodev1.RouterConfig{ + Version: "v42", + EngineConfig: &nodev1.EngineConfiguration{DefaultFlushInterval: 500}, + CompatibilityVersion: "1", + } + body := marshalRouterConfig(t, expected) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(body) + })) + defer srv.Close() + + result, err := newFetcher(t, srv.URL, nil).FetchConfig(context.Background(), "") + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, "v42", result.Version) +} + +func TestFetchConfig_InvalidJSON(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("not-valid-proto-json")) + })) + defer srv.Close() + + _, err := newFetcher(t, srv.URL, nil).FetchConfig(context.Background(), "") + require.Error(t, err) + assert.Contains(t, err.Error(), "could not unmarshal router config") +}