From 944ec206bfa90252ef074d32ded5b176343ea2fa Mon Sep 17 00:00:00 2001 From: Haim Tayrie Date: Sun, 26 Jul 2026 12:34:20 +0300 Subject: [PATCH 01/12] OSAC-3082: add test proto definitions for reference validation Assisted-by: Claude Code Signed-off-by: Haim Tayrie --- .../osac/tests/v1/reference_test_types.pb.go | 1284 +++++++++++++++++ .../v1/reference_test_types_protoopaque.pb.go | 1277 ++++++++++++++++ .../osac/tests/v1/reference_test_types.proto | 86 ++ 3 files changed, 2647 insertions(+) create mode 100644 internal/api/osac/tests/v1/reference_test_types.pb.go create mode 100644 internal/api/osac/tests/v1/reference_test_types_protoopaque.pb.go create mode 100644 proto/tests/osac/tests/v1/reference_test_types.proto diff --git a/internal/api/osac/tests/v1/reference_test_types.pb.go b/internal/api/osac/tests/v1/reference_test_types.pb.go new file mode 100644 index 000000000..0d20b9db2 --- /dev/null +++ b/internal/api/osac/tests/v1/reference_test_types.pb.go @@ -0,0 +1,1284 @@ +// +// Copyright (c) 2025 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc (unknown) +// source: osac/tests/v1/reference_test_types.proto + +//go:build !protoopaque + +package testsv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Full reference for cross-tenant/project lookups. +type TestTargetReference struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Project string `protobuf:"bytes,3,opt,name=project,proto3" json:"project,omitempty"` + Shared bool `protobuf:"varint,4,opt,name=shared,proto3" json:"shared,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestTargetReference) Reset() { + *x = TestTargetReference{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestTargetReference) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestTargetReference) ProtoMessage() {} + +func (x *TestTargetReference) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TestTargetReference) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *TestTargetReference) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *TestTargetReference) GetProject() string { + if x != nil { + return x.Project + } + return "" +} + +func (x *TestTargetReference) GetShared() bool { + if x != nil { + return x.Shared + } + return false +} + +func (x *TestTargetReference) SetId(v string) { + x.Id = v +} + +func (x *TestTargetReference) SetName(v string) { + x.Name = v +} + +func (x *TestTargetReference) SetProject(v string) { + x.Project = v +} + +func (x *TestTargetReference) SetShared(v bool) { + x.Shared = v +} + +type TestTargetReference_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Id string + Name string + Project string + Shared bool +} + +func (b0 TestTargetReference_builder) Build() *TestTargetReference { + m0 := &TestTargetReference{} + b, x := &b0, m0 + _, _ = b, x + x.Id = b.Id + x.Name = b.Name + x.Project = b.Project + x.Shared = b.Shared + return m0 +} + +// Local reference for same-tenant/project lookups. +type TestTargetLocalReference struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestTargetLocalReference) Reset() { + *x = TestTargetLocalReference{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestTargetLocalReference) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestTargetLocalReference) ProtoMessage() {} + +func (x *TestTargetLocalReference) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TestTargetLocalReference) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *TestTargetLocalReference) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *TestTargetLocalReference) SetId(v string) { + x.Id = v +} + +func (x *TestTargetLocalReference) SetName(v string) { + x.Name = v +} + +type TestTargetLocalReference_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Id string + Name string +} + +func (b0 TestTargetLocalReference_builder) Build() *TestTargetLocalReference { + m0 := &TestTargetLocalReference{} + b, x := &b0, m0 + _, _ = b, x + x.Id = b.Id + x.Name = b.Name + return m0 +} + +// Second local reference type for repeated and oneof testing. +type TestOtherTargetLocalReference struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestOtherTargetLocalReference) Reset() { + *x = TestOtherTargetLocalReference{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestOtherTargetLocalReference) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestOtherTargetLocalReference) ProtoMessage() {} + +func (x *TestOtherTargetLocalReference) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TestOtherTargetLocalReference) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *TestOtherTargetLocalReference) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *TestOtherTargetLocalReference) SetId(v string) { + x.Id = v +} + +func (x *TestOtherTargetLocalReference) SetName(v string) { + x.Name = v +} + +type TestOtherTargetLocalReference_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Id string + Name string +} + +func (b0 TestOtherTargetLocalReference_builder) Build() *TestOtherTargetLocalReference { + m0 := &TestOtherTargetLocalReference{} + b, x := &b0, m0 + _, _ = b, x + x.Id = b.Id + x.Name = b.Name + return m0 +} + +// Nested message containing reference fields (mirrors NetworkAttachment). +type TestRefAttachment struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Subnet *TestTargetLocalReference `protobuf:"bytes,1,opt,name=subnet,proto3" json:"subnet,omitempty"` + SecurityGroups []*TestOtherTargetLocalReference `protobuf:"bytes,2,rep,name=security_groups,json=securityGroups,proto3" json:"security_groups,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestRefAttachment) Reset() { + *x = TestRefAttachment{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestRefAttachment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestRefAttachment) ProtoMessage() {} + +func (x *TestRefAttachment) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TestRefAttachment) GetSubnet() *TestTargetLocalReference { + if x != nil { + return x.Subnet + } + return nil +} + +func (x *TestRefAttachment) GetSecurityGroups() []*TestOtherTargetLocalReference { + if x != nil { + return x.SecurityGroups + } + return nil +} + +func (x *TestRefAttachment) SetSubnet(v *TestTargetLocalReference) { + x.Subnet = v +} + +func (x *TestRefAttachment) SetSecurityGroups(v []*TestOtherTargetLocalReference) { + x.SecurityGroups = v +} + +func (x *TestRefAttachment) HasSubnet() bool { + if x == nil { + return false + } + return x.Subnet != nil +} + +func (x *TestRefAttachment) ClearSubnet() { + x.Subnet = nil +} + +type TestRefAttachment_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Subnet *TestTargetLocalReference + SecurityGroups []*TestOtherTargetLocalReference +} + +func (b0 TestRefAttachment_builder) Build() *TestRefAttachment { + m0 := &TestRefAttachment{} + b, x := &b0, m0 + _, _ = b, x + x.Subnet = b.Subnet + x.SecurityGroups = b.SecurityGroups + return m0 +} + +// Spec with various reference field patterns. +type TestRefSpec struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Target *TestTargetReference `protobuf:"bytes,1,opt,name=target,proto3" json:"target,omitempty"` + LocalTarget *TestTargetLocalReference `protobuf:"bytes,2,opt,name=local_target,json=localTarget,proto3" json:"local_target,omitempty"` + Attachment *TestRefAttachment `protobuf:"bytes,3,opt,name=attachment,proto3" json:"attachment,omitempty"` + OtherTargets []*TestOtherTargetLocalReference `protobuf:"bytes,4,rep,name=other_targets,json=otherTargets,proto3" json:"other_targets,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestRefSpec) Reset() { + *x = TestRefSpec{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestRefSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestRefSpec) ProtoMessage() {} + +func (x *TestRefSpec) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_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) +} + +func (x *TestRefSpec) GetTarget() *TestTargetReference { + if x != nil { + return x.Target + } + return nil +} + +func (x *TestRefSpec) GetLocalTarget() *TestTargetLocalReference { + if x != nil { + return x.LocalTarget + } + return nil +} + +func (x *TestRefSpec) GetAttachment() *TestRefAttachment { + if x != nil { + return x.Attachment + } + return nil +} + +func (x *TestRefSpec) GetOtherTargets() []*TestOtherTargetLocalReference { + if x != nil { + return x.OtherTargets + } + return nil +} + +func (x *TestRefSpec) SetTarget(v *TestTargetReference) { + x.Target = v +} + +func (x *TestRefSpec) SetLocalTarget(v *TestTargetLocalReference) { + x.LocalTarget = v +} + +func (x *TestRefSpec) SetAttachment(v *TestRefAttachment) { + x.Attachment = v +} + +func (x *TestRefSpec) SetOtherTargets(v []*TestOtherTargetLocalReference) { + x.OtherTargets = v +} + +func (x *TestRefSpec) HasTarget() bool { + if x == nil { + return false + } + return x.Target != nil +} + +func (x *TestRefSpec) HasLocalTarget() bool { + if x == nil { + return false + } + return x.LocalTarget != nil +} + +func (x *TestRefSpec) HasAttachment() bool { + if x == nil { + return false + } + return x.Attachment != nil +} + +func (x *TestRefSpec) ClearTarget() { + x.Target = nil +} + +func (x *TestRefSpec) ClearLocalTarget() { + x.LocalTarget = nil +} + +func (x *TestRefSpec) ClearAttachment() { + x.Attachment = nil +} + +type TestRefSpec_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Target *TestTargetReference + LocalTarget *TestTargetLocalReference + Attachment *TestRefAttachment + OtherTargets []*TestOtherTargetLocalReference +} + +func (b0 TestRefSpec_builder) Build() *TestRefSpec { + m0 := &TestRefSpec{} + b, x := &b0, m0 + _, _ = b, x + x.Target = b.Target + x.LocalTarget = b.LocalTarget + x.Attachment = b.Attachment + x.OtherTargets = b.OtherTargets + return m0 +} + +// Spec with oneof containing reference fields. +type TestRefOneofSpec struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // Types that are valid to be assigned to Target: + // + // *TestRefOneofSpec_ComputeInstance + // *TestRefOneofSpec_Cluster + Target isTestRefOneofSpec_Target `protobuf_oneof:"target"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestRefOneofSpec) Reset() { + *x = TestRefOneofSpec{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestRefOneofSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestRefOneofSpec) ProtoMessage() {} + +func (x *TestRefOneofSpec) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TestRefOneofSpec) GetTarget() isTestRefOneofSpec_Target { + if x != nil { + return x.Target + } + return nil +} + +func (x *TestRefOneofSpec) GetComputeInstance() *TestTargetLocalReference { + if x != nil { + if x, ok := x.Target.(*TestRefOneofSpec_ComputeInstance); ok { + return x.ComputeInstance + } + } + return nil +} + +func (x *TestRefOneofSpec) GetCluster() *TestOtherTargetLocalReference { + if x != nil { + if x, ok := x.Target.(*TestRefOneofSpec_Cluster); ok { + return x.Cluster + } + } + return nil +} + +func (x *TestRefOneofSpec) SetComputeInstance(v *TestTargetLocalReference) { + if v == nil { + x.Target = nil + return + } + x.Target = &TestRefOneofSpec_ComputeInstance{v} +} + +func (x *TestRefOneofSpec) SetCluster(v *TestOtherTargetLocalReference) { + if v == nil { + x.Target = nil + return + } + x.Target = &TestRefOneofSpec_Cluster{v} +} + +func (x *TestRefOneofSpec) HasTarget() bool { + if x == nil { + return false + } + return x.Target != nil +} + +func (x *TestRefOneofSpec) HasComputeInstance() bool { + if x == nil { + return false + } + _, ok := x.Target.(*TestRefOneofSpec_ComputeInstance) + return ok +} + +func (x *TestRefOneofSpec) HasCluster() bool { + if x == nil { + return false + } + _, ok := x.Target.(*TestRefOneofSpec_Cluster) + return ok +} + +func (x *TestRefOneofSpec) ClearTarget() { + x.Target = nil +} + +func (x *TestRefOneofSpec) ClearComputeInstance() { + if _, ok := x.Target.(*TestRefOneofSpec_ComputeInstance); ok { + x.Target = nil + } +} + +func (x *TestRefOneofSpec) ClearCluster() { + if _, ok := x.Target.(*TestRefOneofSpec_Cluster); ok { + x.Target = nil + } +} + +const TestRefOneofSpec_Target_not_set_case case_TestRefOneofSpec_Target = 0 +const TestRefOneofSpec_ComputeInstance_case case_TestRefOneofSpec_Target = 1 +const TestRefOneofSpec_Cluster_case case_TestRefOneofSpec_Target = 2 + +func (x *TestRefOneofSpec) WhichTarget() case_TestRefOneofSpec_Target { + if x == nil { + return TestRefOneofSpec_Target_not_set_case + } + switch x.Target.(type) { + case *TestRefOneofSpec_ComputeInstance: + return TestRefOneofSpec_ComputeInstance_case + case *TestRefOneofSpec_Cluster: + return TestRefOneofSpec_Cluster_case + default: + return TestRefOneofSpec_Target_not_set_case + } +} + +type TestRefOneofSpec_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // Fields of oneof Target: + ComputeInstance *TestTargetLocalReference + Cluster *TestOtherTargetLocalReference + // -- end of Target +} + +func (b0 TestRefOneofSpec_builder) Build() *TestRefOneofSpec { + m0 := &TestRefOneofSpec{} + b, x := &b0, m0 + _, _ = b, x + if b.ComputeInstance != nil { + x.Target = &TestRefOneofSpec_ComputeInstance{b.ComputeInstance} + } + if b.Cluster != nil { + x.Target = &TestRefOneofSpec_Cluster{b.Cluster} + } + return m0 +} + +type case_TestRefOneofSpec_Target protoreflect.FieldNumber + +func (x case_TestRefOneofSpec_Target) String() string { + md := file_osac_tests_v1_reference_test_types_proto_msgTypes[5].Descriptor() + if x == 0 { + return "not set" + } + return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x)) +} + +type isTestRefOneofSpec_Target interface { + isTestRefOneofSpec_Target() +} + +type TestRefOneofSpec_ComputeInstance struct { + ComputeInstance *TestTargetLocalReference `protobuf:"bytes,1,opt,name=compute_instance,json=computeInstance,proto3,oneof"` +} + +type TestRefOneofSpec_Cluster struct { + Cluster *TestOtherTargetLocalReference `protobuf:"bytes,2,opt,name=cluster,proto3,oneof"` +} + +func (*TestRefOneofSpec_ComputeInstance) isTestRefOneofSpec_Target() {} + +func (*TestRefOneofSpec_Cluster) isTestRefOneofSpec_Target() {} + +// buf:lint:ignore OSAC_OBJECT_SHAPE +type TestResourceWithRefs struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Metadata *Metadata `protobuf:"bytes,2,opt,name=metadata,proto3" json:"metadata,omitempty"` + Spec *TestRefSpec `protobuf:"bytes,3,opt,name=spec,proto3" json:"spec,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestResourceWithRefs) Reset() { + *x = TestResourceWithRefs{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestResourceWithRefs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestResourceWithRefs) ProtoMessage() {} + +func (x *TestResourceWithRefs) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TestResourceWithRefs) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *TestResourceWithRefs) GetMetadata() *Metadata { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *TestResourceWithRefs) GetSpec() *TestRefSpec { + if x != nil { + return x.Spec + } + return nil +} + +func (x *TestResourceWithRefs) SetId(v string) { + x.Id = v +} + +func (x *TestResourceWithRefs) SetMetadata(v *Metadata) { + x.Metadata = v +} + +func (x *TestResourceWithRefs) SetSpec(v *TestRefSpec) { + x.Spec = v +} + +func (x *TestResourceWithRefs) HasMetadata() bool { + if x == nil { + return false + } + return x.Metadata != nil +} + +func (x *TestResourceWithRefs) HasSpec() bool { + if x == nil { + return false + } + return x.Spec != nil +} + +func (x *TestResourceWithRefs) ClearMetadata() { + x.Metadata = nil +} + +func (x *TestResourceWithRefs) ClearSpec() { + x.Spec = nil +} + +type TestResourceWithRefs_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Id string + Metadata *Metadata + Spec *TestRefSpec +} + +func (b0 TestResourceWithRefs_builder) Build() *TestResourceWithRefs { + m0 := &TestResourceWithRefs{} + b, x := &b0, m0 + _, _ = b, x + x.Id = b.Id + x.Metadata = b.Metadata + x.Spec = b.Spec + return m0 +} + +// buf:lint:ignore OSAC_OBJECT_SHAPE +type TestResourceWithOneof struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Metadata *Metadata `protobuf:"bytes,2,opt,name=metadata,proto3" json:"metadata,omitempty"` + Spec *TestRefOneofSpec `protobuf:"bytes,3,opt,name=spec,proto3" json:"spec,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestResourceWithOneof) Reset() { + *x = TestResourceWithOneof{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestResourceWithOneof) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestResourceWithOneof) ProtoMessage() {} + +func (x *TestResourceWithOneof) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TestResourceWithOneof) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *TestResourceWithOneof) GetMetadata() *Metadata { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *TestResourceWithOneof) GetSpec() *TestRefOneofSpec { + if x != nil { + return x.Spec + } + return nil +} + +func (x *TestResourceWithOneof) SetId(v string) { + x.Id = v +} + +func (x *TestResourceWithOneof) SetMetadata(v *Metadata) { + x.Metadata = v +} + +func (x *TestResourceWithOneof) SetSpec(v *TestRefOneofSpec) { + x.Spec = v +} + +func (x *TestResourceWithOneof) HasMetadata() bool { + if x == nil { + return false + } + return x.Metadata != nil +} + +func (x *TestResourceWithOneof) HasSpec() bool { + if x == nil { + return false + } + return x.Spec != nil +} + +func (x *TestResourceWithOneof) ClearMetadata() { + x.Metadata = nil +} + +func (x *TestResourceWithOneof) ClearSpec() { + x.Spec = nil +} + +type TestResourceWithOneof_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Id string + Metadata *Metadata + Spec *TestRefOneofSpec +} + +func (b0 TestResourceWithOneof_builder) Build() *TestResourceWithOneof { + m0 := &TestResourceWithOneof{} + b, x := &b0, m0 + _, _ = b, x + x.Id = b.Id + x.Metadata = b.Metadata + x.Spec = b.Spec + return m0 +} + +type CreateTestResourceWithRefsRequest struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Object *TestResourceWithRefs `protobuf:"bytes,1,opt,name=object,proto3" json:"object,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateTestResourceWithRefsRequest) Reset() { + *x = CreateTestResourceWithRefsRequest{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateTestResourceWithRefsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTestResourceWithRefsRequest) ProtoMessage() {} + +func (x *CreateTestResourceWithRefsRequest) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *CreateTestResourceWithRefsRequest) GetObject() *TestResourceWithRefs { + if x != nil { + return x.Object + } + return nil +} + +func (x *CreateTestResourceWithRefsRequest) SetObject(v *TestResourceWithRefs) { + x.Object = v +} + +func (x *CreateTestResourceWithRefsRequest) HasObject() bool { + if x == nil { + return false + } + return x.Object != nil +} + +func (x *CreateTestResourceWithRefsRequest) ClearObject() { + x.Object = nil +} + +type CreateTestResourceWithRefsRequest_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Object *TestResourceWithRefs +} + +func (b0 CreateTestResourceWithRefsRequest_builder) Build() *CreateTestResourceWithRefsRequest { + m0 := &CreateTestResourceWithRefsRequest{} + b, x := &b0, m0 + _, _ = b, x + x.Object = b.Object + return m0 +} + +type UpdateTestResourceWithRefsRequest struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Object *TestResourceWithRefs `protobuf:"bytes,1,opt,name=object,proto3" json:"object,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateTestResourceWithRefsRequest) Reset() { + *x = UpdateTestResourceWithRefsRequest{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateTestResourceWithRefsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTestResourceWithRefsRequest) ProtoMessage() {} + +func (x *UpdateTestResourceWithRefsRequest) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *UpdateTestResourceWithRefsRequest) GetObject() *TestResourceWithRefs { + if x != nil { + return x.Object + } + return nil +} + +func (x *UpdateTestResourceWithRefsRequest) SetObject(v *TestResourceWithRefs) { + x.Object = v +} + +func (x *UpdateTestResourceWithRefsRequest) HasObject() bool { + if x == nil { + return false + } + return x.Object != nil +} + +func (x *UpdateTestResourceWithRefsRequest) ClearObject() { + x.Object = nil +} + +type UpdateTestResourceWithRefsRequest_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Object *TestResourceWithRefs +} + +func (b0 UpdateTestResourceWithRefsRequest_builder) Build() *UpdateTestResourceWithRefsRequest { + m0 := &UpdateTestResourceWithRefsRequest{} + b, x := &b0, m0 + _, _ = b, x + x.Object = b.Object + return m0 +} + +type CreateTestResourceWithOneofRequest struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Object *TestResourceWithOneof `protobuf:"bytes,1,opt,name=object,proto3" json:"object,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateTestResourceWithOneofRequest) Reset() { + *x = CreateTestResourceWithOneofRequest{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateTestResourceWithOneofRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTestResourceWithOneofRequest) ProtoMessage() {} + +func (x *CreateTestResourceWithOneofRequest) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *CreateTestResourceWithOneofRequest) GetObject() *TestResourceWithOneof { + if x != nil { + return x.Object + } + return nil +} + +func (x *CreateTestResourceWithOneofRequest) SetObject(v *TestResourceWithOneof) { + x.Object = v +} + +func (x *CreateTestResourceWithOneofRequest) HasObject() bool { + if x == nil { + return false + } + return x.Object != nil +} + +func (x *CreateTestResourceWithOneofRequest) ClearObject() { + x.Object = nil +} + +type CreateTestResourceWithOneofRequest_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Object *TestResourceWithOneof +} + +func (b0 CreateTestResourceWithOneofRequest_builder) Build() *CreateTestResourceWithOneofRequest { + m0 := &CreateTestResourceWithOneofRequest{} + b, x := &b0, m0 + _, _ = b, x + x.Object = b.Object + return m0 +} + +var File_osac_tests_v1_reference_test_types_proto protoreflect.FileDescriptor + +var file_osac_tests_v1_reference_test_types_proto_rawDesc = string([]byte{ + 0x0a, 0x28, 0x6f, 0x73, 0x61, 0x63, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x2f, + 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x6f, 0x73, 0x61, 0x63, + 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x6f, 0x73, 0x61, 0x63, 0x2f, + 0x74, 0x65, 0x73, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6b, 0x0a, 0x13, 0x54, 0x65, + 0x73, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x06, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x22, 0x3e, 0x0a, 0x18, 0x54, 0x65, 0x73, 0x74, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, + 0x6e, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x43, 0x0a, 0x1d, 0x54, 0x65, 0x73, 0x74, 0x4f, + 0x74, 0x68, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, + 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xab, 0x01, 0x0a, + 0x11, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x66, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, + 0x6e, 0x74, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, + 0x61, 0x6c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x06, 0x73, 0x75, 0x62, + 0x6e, 0x65, 0x74, 0x12, 0x55, 0x0a, 0x0f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6f, + 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, + 0x74, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, + 0x6c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0e, 0x73, 0x65, 0x63, 0x75, + 0x72, 0x69, 0x74, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x22, 0xaa, 0x02, 0x0a, 0x0b, 0x54, + 0x65, 0x73, 0x74, 0x52, 0x65, 0x66, 0x53, 0x70, 0x65, 0x63, 0x12, 0x3a, 0x0a, 0x06, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6f, 0x73, 0x61, + 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x06, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x4a, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, + 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, + 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x65, + 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x54, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x12, 0x40, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, + 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x66, 0x41, 0x74, + 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, + 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x51, 0x0a, 0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6f, 0x73, + 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, + 0x4f, 0x74, 0x68, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x6c, + 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0c, 0x6f, 0x74, 0x68, 0x65, 0x72, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x22, 0xbc, 0x01, 0x0a, 0x10, 0x54, 0x65, 0x73, 0x74, + 0x52, 0x65, 0x66, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x53, 0x70, 0x65, 0x63, 0x12, 0x54, 0x0a, 0x10, + 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, + 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x48, + 0x00, 0x52, 0x0f, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x12, 0x48, 0x0a, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x42, 0x08, 0x0a, 0x06, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x8b, 0x01, 0x0a, 0x14, 0x54, 0x65, 0x73, 0x74, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x69, 0x74, 0x68, 0x52, 0x65, 0x66, 0x73, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x2e, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x66, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, + 0x73, 0x70, 0x65, 0x63, 0x22, 0x91, 0x01, 0x0a, 0x15, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x69, 0x74, 0x68, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x33, + 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x17, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x12, 0x33, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1f, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x66, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x53, 0x70, + 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0x60, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x69, + 0x74, 0x68, 0x52, 0x65, 0x66, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, + 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, + 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x69, 0x74, 0x68, 0x52, 0x65, + 0x66, 0x73, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x60, 0x0a, 0x21, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x57, 0x69, 0x74, 0x68, 0x52, 0x65, 0x66, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x3b, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x23, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x69, 0x74, 0x68, + 0x52, 0x65, 0x66, 0x73, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x62, 0x0a, 0x22, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x57, 0x69, 0x74, 0x68, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, + 0x69, 0x74, 0x68, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x42, 0xd2, 0x01, 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, + 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x42, 0x17, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x54, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, + 0x01, 0x5a, 0x4e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x73, + 0x61, 0x63, 0x2d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x66, 0x75, 0x6c, 0x66, 0x69, + 0x6c, 0x6c, 0x6d, 0x65, 0x6e, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6f, 0x73, 0x61, 0x63, + 0x2f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x74, 0x65, 0x73, 0x74, 0x73, 0x76, + 0x31, 0xa2, 0x02, 0x03, 0x4f, 0x54, 0x58, 0xaa, 0x02, 0x0d, 0x4f, 0x73, 0x61, 0x63, 0x2e, 0x54, + 0x65, 0x73, 0x74, 0x73, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0d, 0x4f, 0x73, 0x61, 0x63, 0x5c, 0x54, + 0x65, 0x73, 0x74, 0x73, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x19, 0x4f, 0x73, 0x61, 0x63, 0x5c, 0x54, + 0x65, 0x73, 0x74, 0x73, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0xea, 0x02, 0x0f, 0x4f, 0x73, 0x61, 0x63, 0x3a, 0x3a, 0x54, 0x65, 0x73, 0x74, + 0x73, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +}) + +var file_osac_tests_v1_reference_test_types_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_osac_tests_v1_reference_test_types_proto_goTypes = []any{ + (*TestTargetReference)(nil), // 0: osac.tests.v1.TestTargetReference + (*TestTargetLocalReference)(nil), // 1: osac.tests.v1.TestTargetLocalReference + (*TestOtherTargetLocalReference)(nil), // 2: osac.tests.v1.TestOtherTargetLocalReference + (*TestRefAttachment)(nil), // 3: osac.tests.v1.TestRefAttachment + (*TestRefSpec)(nil), // 4: osac.tests.v1.TestRefSpec + (*TestRefOneofSpec)(nil), // 5: osac.tests.v1.TestRefOneofSpec + (*TestResourceWithRefs)(nil), // 6: osac.tests.v1.TestResourceWithRefs + (*TestResourceWithOneof)(nil), // 7: osac.tests.v1.TestResourceWithOneof + (*CreateTestResourceWithRefsRequest)(nil), // 8: osac.tests.v1.CreateTestResourceWithRefsRequest + (*UpdateTestResourceWithRefsRequest)(nil), // 9: osac.tests.v1.UpdateTestResourceWithRefsRequest + (*CreateTestResourceWithOneofRequest)(nil), // 10: osac.tests.v1.CreateTestResourceWithOneofRequest + (*Metadata)(nil), // 11: osac.tests.v1.Metadata +} +var file_osac_tests_v1_reference_test_types_proto_depIdxs = []int32{ + 1, // 0: osac.tests.v1.TestRefAttachment.subnet:type_name -> osac.tests.v1.TestTargetLocalReference + 2, // 1: osac.tests.v1.TestRefAttachment.security_groups:type_name -> osac.tests.v1.TestOtherTargetLocalReference + 0, // 2: osac.tests.v1.TestRefSpec.target:type_name -> osac.tests.v1.TestTargetReference + 1, // 3: osac.tests.v1.TestRefSpec.local_target:type_name -> osac.tests.v1.TestTargetLocalReference + 3, // 4: osac.tests.v1.TestRefSpec.attachment:type_name -> osac.tests.v1.TestRefAttachment + 2, // 5: osac.tests.v1.TestRefSpec.other_targets:type_name -> osac.tests.v1.TestOtherTargetLocalReference + 1, // 6: osac.tests.v1.TestRefOneofSpec.compute_instance:type_name -> osac.tests.v1.TestTargetLocalReference + 2, // 7: osac.tests.v1.TestRefOneofSpec.cluster:type_name -> osac.tests.v1.TestOtherTargetLocalReference + 11, // 8: osac.tests.v1.TestResourceWithRefs.metadata:type_name -> osac.tests.v1.Metadata + 4, // 9: osac.tests.v1.TestResourceWithRefs.spec:type_name -> osac.tests.v1.TestRefSpec + 11, // 10: osac.tests.v1.TestResourceWithOneof.metadata:type_name -> osac.tests.v1.Metadata + 5, // 11: osac.tests.v1.TestResourceWithOneof.spec:type_name -> osac.tests.v1.TestRefOneofSpec + 6, // 12: osac.tests.v1.CreateTestResourceWithRefsRequest.object:type_name -> osac.tests.v1.TestResourceWithRefs + 6, // 13: osac.tests.v1.UpdateTestResourceWithRefsRequest.object:type_name -> osac.tests.v1.TestResourceWithRefs + 7, // 14: osac.tests.v1.CreateTestResourceWithOneofRequest.object:type_name -> osac.tests.v1.TestResourceWithOneof + 15, // [15:15] is the sub-list for method output_type + 15, // [15:15] is the sub-list for method input_type + 15, // [15:15] is the sub-list for extension type_name + 15, // [15:15] is the sub-list for extension extendee + 0, // [0:15] is the sub-list for field type_name +} + +func init() { file_osac_tests_v1_reference_test_types_proto_init() } +func file_osac_tests_v1_reference_test_types_proto_init() { + if File_osac_tests_v1_reference_test_types_proto != nil { + return + } + file_osac_tests_v1_object_type_proto_init() + file_osac_tests_v1_reference_test_types_proto_msgTypes[5].OneofWrappers = []any{ + (*TestRefOneofSpec_ComputeInstance)(nil), + (*TestRefOneofSpec_Cluster)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_osac_tests_v1_reference_test_types_proto_rawDesc), len(file_osac_tests_v1_reference_test_types_proto_rawDesc)), + NumEnums: 0, + NumMessages: 11, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_osac_tests_v1_reference_test_types_proto_goTypes, + DependencyIndexes: file_osac_tests_v1_reference_test_types_proto_depIdxs, + MessageInfos: file_osac_tests_v1_reference_test_types_proto_msgTypes, + }.Build() + File_osac_tests_v1_reference_test_types_proto = out.File + file_osac_tests_v1_reference_test_types_proto_goTypes = nil + file_osac_tests_v1_reference_test_types_proto_depIdxs = nil +} diff --git a/internal/api/osac/tests/v1/reference_test_types_protoopaque.pb.go b/internal/api/osac/tests/v1/reference_test_types_protoopaque.pb.go new file mode 100644 index 000000000..fd3fb28be --- /dev/null +++ b/internal/api/osac/tests/v1/reference_test_types_protoopaque.pb.go @@ -0,0 +1,1277 @@ +// +// Copyright (c) 2025 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc (unknown) +// source: osac/tests/v1/reference_test_types.proto + +//go:build protoopaque + +package testsv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Full reference for cross-tenant/project lookups. +type TestTargetReference struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Id string `protobuf:"bytes,1,opt,name=id,proto3"` + xxx_hidden_Name string `protobuf:"bytes,2,opt,name=name,proto3"` + xxx_hidden_Project string `protobuf:"bytes,3,opt,name=project,proto3"` + xxx_hidden_Shared bool `protobuf:"varint,4,opt,name=shared,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestTargetReference) Reset() { + *x = TestTargetReference{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestTargetReference) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestTargetReference) ProtoMessage() {} + +func (x *TestTargetReference) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TestTargetReference) GetId() string { + if x != nil { + return x.xxx_hidden_Id + } + return "" +} + +func (x *TestTargetReference) GetName() string { + if x != nil { + return x.xxx_hidden_Name + } + return "" +} + +func (x *TestTargetReference) GetProject() string { + if x != nil { + return x.xxx_hidden_Project + } + return "" +} + +func (x *TestTargetReference) GetShared() bool { + if x != nil { + return x.xxx_hidden_Shared + } + return false +} + +func (x *TestTargetReference) SetId(v string) { + x.xxx_hidden_Id = v +} + +func (x *TestTargetReference) SetName(v string) { + x.xxx_hidden_Name = v +} + +func (x *TestTargetReference) SetProject(v string) { + x.xxx_hidden_Project = v +} + +func (x *TestTargetReference) SetShared(v bool) { + x.xxx_hidden_Shared = v +} + +type TestTargetReference_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Id string + Name string + Project string + Shared bool +} + +func (b0 TestTargetReference_builder) Build() *TestTargetReference { + m0 := &TestTargetReference{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Id = b.Id + x.xxx_hidden_Name = b.Name + x.xxx_hidden_Project = b.Project + x.xxx_hidden_Shared = b.Shared + return m0 +} + +// Local reference for same-tenant/project lookups. +type TestTargetLocalReference struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Id string `protobuf:"bytes,1,opt,name=id,proto3"` + xxx_hidden_Name string `protobuf:"bytes,2,opt,name=name,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestTargetLocalReference) Reset() { + *x = TestTargetLocalReference{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestTargetLocalReference) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestTargetLocalReference) ProtoMessage() {} + +func (x *TestTargetLocalReference) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TestTargetLocalReference) GetId() string { + if x != nil { + return x.xxx_hidden_Id + } + return "" +} + +func (x *TestTargetLocalReference) GetName() string { + if x != nil { + return x.xxx_hidden_Name + } + return "" +} + +func (x *TestTargetLocalReference) SetId(v string) { + x.xxx_hidden_Id = v +} + +func (x *TestTargetLocalReference) SetName(v string) { + x.xxx_hidden_Name = v +} + +type TestTargetLocalReference_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Id string + Name string +} + +func (b0 TestTargetLocalReference_builder) Build() *TestTargetLocalReference { + m0 := &TestTargetLocalReference{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Id = b.Id + x.xxx_hidden_Name = b.Name + return m0 +} + +// Second local reference type for repeated and oneof testing. +type TestOtherTargetLocalReference struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Id string `protobuf:"bytes,1,opt,name=id,proto3"` + xxx_hidden_Name string `protobuf:"bytes,2,opt,name=name,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestOtherTargetLocalReference) Reset() { + *x = TestOtherTargetLocalReference{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestOtherTargetLocalReference) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestOtherTargetLocalReference) ProtoMessage() {} + +func (x *TestOtherTargetLocalReference) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TestOtherTargetLocalReference) GetId() string { + if x != nil { + return x.xxx_hidden_Id + } + return "" +} + +func (x *TestOtherTargetLocalReference) GetName() string { + if x != nil { + return x.xxx_hidden_Name + } + return "" +} + +func (x *TestOtherTargetLocalReference) SetId(v string) { + x.xxx_hidden_Id = v +} + +func (x *TestOtherTargetLocalReference) SetName(v string) { + x.xxx_hidden_Name = v +} + +type TestOtherTargetLocalReference_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Id string + Name string +} + +func (b0 TestOtherTargetLocalReference_builder) Build() *TestOtherTargetLocalReference { + m0 := &TestOtherTargetLocalReference{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Id = b.Id + x.xxx_hidden_Name = b.Name + return m0 +} + +// Nested message containing reference fields (mirrors NetworkAttachment). +type TestRefAttachment struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Subnet *TestTargetLocalReference `protobuf:"bytes,1,opt,name=subnet,proto3"` + xxx_hidden_SecurityGroups *[]*TestOtherTargetLocalReference `protobuf:"bytes,2,rep,name=security_groups,json=securityGroups,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestRefAttachment) Reset() { + *x = TestRefAttachment{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestRefAttachment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestRefAttachment) ProtoMessage() {} + +func (x *TestRefAttachment) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TestRefAttachment) GetSubnet() *TestTargetLocalReference { + if x != nil { + return x.xxx_hidden_Subnet + } + return nil +} + +func (x *TestRefAttachment) GetSecurityGroups() []*TestOtherTargetLocalReference { + if x != nil { + if x.xxx_hidden_SecurityGroups != nil { + return *x.xxx_hidden_SecurityGroups + } + } + return nil +} + +func (x *TestRefAttachment) SetSubnet(v *TestTargetLocalReference) { + x.xxx_hidden_Subnet = v +} + +func (x *TestRefAttachment) SetSecurityGroups(v []*TestOtherTargetLocalReference) { + x.xxx_hidden_SecurityGroups = &v +} + +func (x *TestRefAttachment) HasSubnet() bool { + if x == nil { + return false + } + return x.xxx_hidden_Subnet != nil +} + +func (x *TestRefAttachment) ClearSubnet() { + x.xxx_hidden_Subnet = nil +} + +type TestRefAttachment_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Subnet *TestTargetLocalReference + SecurityGroups []*TestOtherTargetLocalReference +} + +func (b0 TestRefAttachment_builder) Build() *TestRefAttachment { + m0 := &TestRefAttachment{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Subnet = b.Subnet + x.xxx_hidden_SecurityGroups = &b.SecurityGroups + return m0 +} + +// Spec with various reference field patterns. +type TestRefSpec struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Target *TestTargetReference `protobuf:"bytes,1,opt,name=target,proto3"` + xxx_hidden_LocalTarget *TestTargetLocalReference `protobuf:"bytes,2,opt,name=local_target,json=localTarget,proto3"` + xxx_hidden_Attachment *TestRefAttachment `protobuf:"bytes,3,opt,name=attachment,proto3"` + xxx_hidden_OtherTargets *[]*TestOtherTargetLocalReference `protobuf:"bytes,4,rep,name=other_targets,json=otherTargets,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestRefSpec) Reset() { + *x = TestRefSpec{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestRefSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestRefSpec) ProtoMessage() {} + +func (x *TestRefSpec) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_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) +} + +func (x *TestRefSpec) GetTarget() *TestTargetReference { + if x != nil { + return x.xxx_hidden_Target + } + return nil +} + +func (x *TestRefSpec) GetLocalTarget() *TestTargetLocalReference { + if x != nil { + return x.xxx_hidden_LocalTarget + } + return nil +} + +func (x *TestRefSpec) GetAttachment() *TestRefAttachment { + if x != nil { + return x.xxx_hidden_Attachment + } + return nil +} + +func (x *TestRefSpec) GetOtherTargets() []*TestOtherTargetLocalReference { + if x != nil { + if x.xxx_hidden_OtherTargets != nil { + return *x.xxx_hidden_OtherTargets + } + } + return nil +} + +func (x *TestRefSpec) SetTarget(v *TestTargetReference) { + x.xxx_hidden_Target = v +} + +func (x *TestRefSpec) SetLocalTarget(v *TestTargetLocalReference) { + x.xxx_hidden_LocalTarget = v +} + +func (x *TestRefSpec) SetAttachment(v *TestRefAttachment) { + x.xxx_hidden_Attachment = v +} + +func (x *TestRefSpec) SetOtherTargets(v []*TestOtherTargetLocalReference) { + x.xxx_hidden_OtherTargets = &v +} + +func (x *TestRefSpec) HasTarget() bool { + if x == nil { + return false + } + return x.xxx_hidden_Target != nil +} + +func (x *TestRefSpec) HasLocalTarget() bool { + if x == nil { + return false + } + return x.xxx_hidden_LocalTarget != nil +} + +func (x *TestRefSpec) HasAttachment() bool { + if x == nil { + return false + } + return x.xxx_hidden_Attachment != nil +} + +func (x *TestRefSpec) ClearTarget() { + x.xxx_hidden_Target = nil +} + +func (x *TestRefSpec) ClearLocalTarget() { + x.xxx_hidden_LocalTarget = nil +} + +func (x *TestRefSpec) ClearAttachment() { + x.xxx_hidden_Attachment = nil +} + +type TestRefSpec_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Target *TestTargetReference + LocalTarget *TestTargetLocalReference + Attachment *TestRefAttachment + OtherTargets []*TestOtherTargetLocalReference +} + +func (b0 TestRefSpec_builder) Build() *TestRefSpec { + m0 := &TestRefSpec{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Target = b.Target + x.xxx_hidden_LocalTarget = b.LocalTarget + x.xxx_hidden_Attachment = b.Attachment + x.xxx_hidden_OtherTargets = &b.OtherTargets + return m0 +} + +// Spec with oneof containing reference fields. +type TestRefOneofSpec struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Target isTestRefOneofSpec_Target `protobuf_oneof:"target"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestRefOneofSpec) Reset() { + *x = TestRefOneofSpec{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestRefOneofSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestRefOneofSpec) ProtoMessage() {} + +func (x *TestRefOneofSpec) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TestRefOneofSpec) GetComputeInstance() *TestTargetLocalReference { + if x != nil { + if x, ok := x.xxx_hidden_Target.(*testRefOneofSpec_ComputeInstance); ok { + return x.ComputeInstance + } + } + return nil +} + +func (x *TestRefOneofSpec) GetCluster() *TestOtherTargetLocalReference { + if x != nil { + if x, ok := x.xxx_hidden_Target.(*testRefOneofSpec_Cluster); ok { + return x.Cluster + } + } + return nil +} + +func (x *TestRefOneofSpec) SetComputeInstance(v *TestTargetLocalReference) { + if v == nil { + x.xxx_hidden_Target = nil + return + } + x.xxx_hidden_Target = &testRefOneofSpec_ComputeInstance{v} +} + +func (x *TestRefOneofSpec) SetCluster(v *TestOtherTargetLocalReference) { + if v == nil { + x.xxx_hidden_Target = nil + return + } + x.xxx_hidden_Target = &testRefOneofSpec_Cluster{v} +} + +func (x *TestRefOneofSpec) HasTarget() bool { + if x == nil { + return false + } + return x.xxx_hidden_Target != nil +} + +func (x *TestRefOneofSpec) HasComputeInstance() bool { + if x == nil { + return false + } + _, ok := x.xxx_hidden_Target.(*testRefOneofSpec_ComputeInstance) + return ok +} + +func (x *TestRefOneofSpec) HasCluster() bool { + if x == nil { + return false + } + _, ok := x.xxx_hidden_Target.(*testRefOneofSpec_Cluster) + return ok +} + +func (x *TestRefOneofSpec) ClearTarget() { + x.xxx_hidden_Target = nil +} + +func (x *TestRefOneofSpec) ClearComputeInstance() { + if _, ok := x.xxx_hidden_Target.(*testRefOneofSpec_ComputeInstance); ok { + x.xxx_hidden_Target = nil + } +} + +func (x *TestRefOneofSpec) ClearCluster() { + if _, ok := x.xxx_hidden_Target.(*testRefOneofSpec_Cluster); ok { + x.xxx_hidden_Target = nil + } +} + +const TestRefOneofSpec_Target_not_set_case case_TestRefOneofSpec_Target = 0 +const TestRefOneofSpec_ComputeInstance_case case_TestRefOneofSpec_Target = 1 +const TestRefOneofSpec_Cluster_case case_TestRefOneofSpec_Target = 2 + +func (x *TestRefOneofSpec) WhichTarget() case_TestRefOneofSpec_Target { + if x == nil { + return TestRefOneofSpec_Target_not_set_case + } + switch x.xxx_hidden_Target.(type) { + case *testRefOneofSpec_ComputeInstance: + return TestRefOneofSpec_ComputeInstance_case + case *testRefOneofSpec_Cluster: + return TestRefOneofSpec_Cluster_case + default: + return TestRefOneofSpec_Target_not_set_case + } +} + +type TestRefOneofSpec_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // Fields of oneof xxx_hidden_Target: + ComputeInstance *TestTargetLocalReference + Cluster *TestOtherTargetLocalReference + // -- end of xxx_hidden_Target +} + +func (b0 TestRefOneofSpec_builder) Build() *TestRefOneofSpec { + m0 := &TestRefOneofSpec{} + b, x := &b0, m0 + _, _ = b, x + if b.ComputeInstance != nil { + x.xxx_hidden_Target = &testRefOneofSpec_ComputeInstance{b.ComputeInstance} + } + if b.Cluster != nil { + x.xxx_hidden_Target = &testRefOneofSpec_Cluster{b.Cluster} + } + return m0 +} + +type case_TestRefOneofSpec_Target protoreflect.FieldNumber + +func (x case_TestRefOneofSpec_Target) String() string { + md := file_osac_tests_v1_reference_test_types_proto_msgTypes[5].Descriptor() + if x == 0 { + return "not set" + } + return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x)) +} + +type isTestRefOneofSpec_Target interface { + isTestRefOneofSpec_Target() +} + +type testRefOneofSpec_ComputeInstance struct { + ComputeInstance *TestTargetLocalReference `protobuf:"bytes,1,opt,name=compute_instance,json=computeInstance,proto3,oneof"` +} + +type testRefOneofSpec_Cluster struct { + Cluster *TestOtherTargetLocalReference `protobuf:"bytes,2,opt,name=cluster,proto3,oneof"` +} + +func (*testRefOneofSpec_ComputeInstance) isTestRefOneofSpec_Target() {} + +func (*testRefOneofSpec_Cluster) isTestRefOneofSpec_Target() {} + +// buf:lint:ignore OSAC_OBJECT_SHAPE +type TestResourceWithRefs struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Id string `protobuf:"bytes,1,opt,name=id,proto3"` + xxx_hidden_Metadata *Metadata `protobuf:"bytes,2,opt,name=metadata,proto3"` + xxx_hidden_Spec *TestRefSpec `protobuf:"bytes,3,opt,name=spec,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestResourceWithRefs) Reset() { + *x = TestResourceWithRefs{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestResourceWithRefs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestResourceWithRefs) ProtoMessage() {} + +func (x *TestResourceWithRefs) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TestResourceWithRefs) GetId() string { + if x != nil { + return x.xxx_hidden_Id + } + return "" +} + +func (x *TestResourceWithRefs) GetMetadata() *Metadata { + if x != nil { + return x.xxx_hidden_Metadata + } + return nil +} + +func (x *TestResourceWithRefs) GetSpec() *TestRefSpec { + if x != nil { + return x.xxx_hidden_Spec + } + return nil +} + +func (x *TestResourceWithRefs) SetId(v string) { + x.xxx_hidden_Id = v +} + +func (x *TestResourceWithRefs) SetMetadata(v *Metadata) { + x.xxx_hidden_Metadata = v +} + +func (x *TestResourceWithRefs) SetSpec(v *TestRefSpec) { + x.xxx_hidden_Spec = v +} + +func (x *TestResourceWithRefs) HasMetadata() bool { + if x == nil { + return false + } + return x.xxx_hidden_Metadata != nil +} + +func (x *TestResourceWithRefs) HasSpec() bool { + if x == nil { + return false + } + return x.xxx_hidden_Spec != nil +} + +func (x *TestResourceWithRefs) ClearMetadata() { + x.xxx_hidden_Metadata = nil +} + +func (x *TestResourceWithRefs) ClearSpec() { + x.xxx_hidden_Spec = nil +} + +type TestResourceWithRefs_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Id string + Metadata *Metadata + Spec *TestRefSpec +} + +func (b0 TestResourceWithRefs_builder) Build() *TestResourceWithRefs { + m0 := &TestResourceWithRefs{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Id = b.Id + x.xxx_hidden_Metadata = b.Metadata + x.xxx_hidden_Spec = b.Spec + return m0 +} + +// buf:lint:ignore OSAC_OBJECT_SHAPE +type TestResourceWithOneof struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Id string `protobuf:"bytes,1,opt,name=id,proto3"` + xxx_hidden_Metadata *Metadata `protobuf:"bytes,2,opt,name=metadata,proto3"` + xxx_hidden_Spec *TestRefOneofSpec `protobuf:"bytes,3,opt,name=spec,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TestResourceWithOneof) Reset() { + *x = TestResourceWithOneof{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TestResourceWithOneof) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestResourceWithOneof) ProtoMessage() {} + +func (x *TestResourceWithOneof) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TestResourceWithOneof) GetId() string { + if x != nil { + return x.xxx_hidden_Id + } + return "" +} + +func (x *TestResourceWithOneof) GetMetadata() *Metadata { + if x != nil { + return x.xxx_hidden_Metadata + } + return nil +} + +func (x *TestResourceWithOneof) GetSpec() *TestRefOneofSpec { + if x != nil { + return x.xxx_hidden_Spec + } + return nil +} + +func (x *TestResourceWithOneof) SetId(v string) { + x.xxx_hidden_Id = v +} + +func (x *TestResourceWithOneof) SetMetadata(v *Metadata) { + x.xxx_hidden_Metadata = v +} + +func (x *TestResourceWithOneof) SetSpec(v *TestRefOneofSpec) { + x.xxx_hidden_Spec = v +} + +func (x *TestResourceWithOneof) HasMetadata() bool { + if x == nil { + return false + } + return x.xxx_hidden_Metadata != nil +} + +func (x *TestResourceWithOneof) HasSpec() bool { + if x == nil { + return false + } + return x.xxx_hidden_Spec != nil +} + +func (x *TestResourceWithOneof) ClearMetadata() { + x.xxx_hidden_Metadata = nil +} + +func (x *TestResourceWithOneof) ClearSpec() { + x.xxx_hidden_Spec = nil +} + +type TestResourceWithOneof_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Id string + Metadata *Metadata + Spec *TestRefOneofSpec +} + +func (b0 TestResourceWithOneof_builder) Build() *TestResourceWithOneof { + m0 := &TestResourceWithOneof{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Id = b.Id + x.xxx_hidden_Metadata = b.Metadata + x.xxx_hidden_Spec = b.Spec + return m0 +} + +type CreateTestResourceWithRefsRequest struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Object *TestResourceWithRefs `protobuf:"bytes,1,opt,name=object,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateTestResourceWithRefsRequest) Reset() { + *x = CreateTestResourceWithRefsRequest{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateTestResourceWithRefsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTestResourceWithRefsRequest) ProtoMessage() {} + +func (x *CreateTestResourceWithRefsRequest) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *CreateTestResourceWithRefsRequest) GetObject() *TestResourceWithRefs { + if x != nil { + return x.xxx_hidden_Object + } + return nil +} + +func (x *CreateTestResourceWithRefsRequest) SetObject(v *TestResourceWithRefs) { + x.xxx_hidden_Object = v +} + +func (x *CreateTestResourceWithRefsRequest) HasObject() bool { + if x == nil { + return false + } + return x.xxx_hidden_Object != nil +} + +func (x *CreateTestResourceWithRefsRequest) ClearObject() { + x.xxx_hidden_Object = nil +} + +type CreateTestResourceWithRefsRequest_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Object *TestResourceWithRefs +} + +func (b0 CreateTestResourceWithRefsRequest_builder) Build() *CreateTestResourceWithRefsRequest { + m0 := &CreateTestResourceWithRefsRequest{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Object = b.Object + return m0 +} + +type UpdateTestResourceWithRefsRequest struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Object *TestResourceWithRefs `protobuf:"bytes,1,opt,name=object,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateTestResourceWithRefsRequest) Reset() { + *x = UpdateTestResourceWithRefsRequest{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateTestResourceWithRefsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTestResourceWithRefsRequest) ProtoMessage() {} + +func (x *UpdateTestResourceWithRefsRequest) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *UpdateTestResourceWithRefsRequest) GetObject() *TestResourceWithRefs { + if x != nil { + return x.xxx_hidden_Object + } + return nil +} + +func (x *UpdateTestResourceWithRefsRequest) SetObject(v *TestResourceWithRefs) { + x.xxx_hidden_Object = v +} + +func (x *UpdateTestResourceWithRefsRequest) HasObject() bool { + if x == nil { + return false + } + return x.xxx_hidden_Object != nil +} + +func (x *UpdateTestResourceWithRefsRequest) ClearObject() { + x.xxx_hidden_Object = nil +} + +type UpdateTestResourceWithRefsRequest_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Object *TestResourceWithRefs +} + +func (b0 UpdateTestResourceWithRefsRequest_builder) Build() *UpdateTestResourceWithRefsRequest { + m0 := &UpdateTestResourceWithRefsRequest{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Object = b.Object + return m0 +} + +type CreateTestResourceWithOneofRequest struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Object *TestResourceWithOneof `protobuf:"bytes,1,opt,name=object,proto3"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateTestResourceWithOneofRequest) Reset() { + *x = CreateTestResourceWithOneofRequest{} + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateTestResourceWithOneofRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTestResourceWithOneofRequest) ProtoMessage() {} + +func (x *CreateTestResourceWithOneofRequest) ProtoReflect() protoreflect.Message { + mi := &file_osac_tests_v1_reference_test_types_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *CreateTestResourceWithOneofRequest) GetObject() *TestResourceWithOneof { + if x != nil { + return x.xxx_hidden_Object + } + return nil +} + +func (x *CreateTestResourceWithOneofRequest) SetObject(v *TestResourceWithOneof) { + x.xxx_hidden_Object = v +} + +func (x *CreateTestResourceWithOneofRequest) HasObject() bool { + if x == nil { + return false + } + return x.xxx_hidden_Object != nil +} + +func (x *CreateTestResourceWithOneofRequest) ClearObject() { + x.xxx_hidden_Object = nil +} + +type CreateTestResourceWithOneofRequest_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Object *TestResourceWithOneof +} + +func (b0 CreateTestResourceWithOneofRequest_builder) Build() *CreateTestResourceWithOneofRequest { + m0 := &CreateTestResourceWithOneofRequest{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Object = b.Object + return m0 +} + +var File_osac_tests_v1_reference_test_types_proto protoreflect.FileDescriptor + +var file_osac_tests_v1_reference_test_types_proto_rawDesc = string([]byte{ + 0x0a, 0x28, 0x6f, 0x73, 0x61, 0x63, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x2f, + 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x6f, 0x73, 0x61, 0x63, + 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x6f, 0x73, 0x61, 0x63, 0x2f, + 0x74, 0x65, 0x73, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6b, 0x0a, 0x13, 0x54, 0x65, + 0x73, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x06, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x22, 0x3e, 0x0a, 0x18, 0x54, 0x65, 0x73, 0x74, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, + 0x6e, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x43, 0x0a, 0x1d, 0x54, 0x65, 0x73, 0x74, 0x4f, + 0x74, 0x68, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, + 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xab, 0x01, 0x0a, + 0x11, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x66, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, + 0x6e, 0x74, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, + 0x61, 0x6c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x06, 0x73, 0x75, 0x62, + 0x6e, 0x65, 0x74, 0x12, 0x55, 0x0a, 0x0f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6f, + 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, + 0x74, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, + 0x6c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0e, 0x73, 0x65, 0x63, 0x75, + 0x72, 0x69, 0x74, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x22, 0xaa, 0x02, 0x0a, 0x0b, 0x54, + 0x65, 0x73, 0x74, 0x52, 0x65, 0x66, 0x53, 0x70, 0x65, 0x63, 0x12, 0x3a, 0x0a, 0x06, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6f, 0x73, 0x61, + 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x06, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x4a, 0x0a, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, + 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, + 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x65, + 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x54, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x12, 0x40, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, + 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x66, 0x41, 0x74, + 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, + 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x51, 0x0a, 0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6f, 0x73, + 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, + 0x4f, 0x74, 0x68, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x6c, + 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0c, 0x6f, 0x74, 0x68, 0x65, 0x72, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x22, 0xbc, 0x01, 0x0a, 0x10, 0x54, 0x65, 0x73, 0x74, + 0x52, 0x65, 0x66, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x53, 0x70, 0x65, 0x63, 0x12, 0x54, 0x0a, 0x10, + 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, + 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x48, + 0x00, 0x52, 0x0f, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x12, 0x48, 0x0a, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x42, 0x08, 0x0a, 0x06, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x8b, 0x01, 0x0a, 0x14, 0x54, 0x65, 0x73, 0x74, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x69, 0x74, 0x68, 0x52, 0x65, 0x66, 0x73, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x2e, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x66, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, + 0x73, 0x70, 0x65, 0x63, 0x22, 0x91, 0x01, 0x0a, 0x15, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x69, 0x74, 0x68, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x33, + 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x17, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x12, 0x33, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1f, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x66, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x53, 0x70, + 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0x60, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x69, + 0x74, 0x68, 0x52, 0x65, 0x66, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, + 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, + 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x69, 0x74, 0x68, 0x52, 0x65, + 0x66, 0x73, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x60, 0x0a, 0x21, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x57, 0x69, 0x74, 0x68, 0x52, 0x65, 0x66, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x3b, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x23, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x69, 0x74, 0x68, + 0x52, 0x65, 0x66, 0x73, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x62, 0x0a, 0x22, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x57, 0x69, 0x74, 0x68, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, + 0x69, 0x74, 0x68, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x42, 0xd2, 0x01, 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x2e, 0x6f, 0x73, 0x61, 0x63, 0x2e, 0x74, 0x65, + 0x73, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x42, 0x17, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x54, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, + 0x01, 0x5a, 0x4e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x73, + 0x61, 0x63, 0x2d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x66, 0x75, 0x6c, 0x66, 0x69, + 0x6c, 0x6c, 0x6d, 0x65, 0x6e, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6f, 0x73, 0x61, 0x63, + 0x2f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x74, 0x65, 0x73, 0x74, 0x73, 0x76, + 0x31, 0xa2, 0x02, 0x03, 0x4f, 0x54, 0x58, 0xaa, 0x02, 0x0d, 0x4f, 0x73, 0x61, 0x63, 0x2e, 0x54, + 0x65, 0x73, 0x74, 0x73, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0d, 0x4f, 0x73, 0x61, 0x63, 0x5c, 0x54, + 0x65, 0x73, 0x74, 0x73, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x19, 0x4f, 0x73, 0x61, 0x63, 0x5c, 0x54, + 0x65, 0x73, 0x74, 0x73, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0xea, 0x02, 0x0f, 0x4f, 0x73, 0x61, 0x63, 0x3a, 0x3a, 0x54, 0x65, 0x73, 0x74, + 0x73, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +}) + +var file_osac_tests_v1_reference_test_types_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_osac_tests_v1_reference_test_types_proto_goTypes = []any{ + (*TestTargetReference)(nil), // 0: osac.tests.v1.TestTargetReference + (*TestTargetLocalReference)(nil), // 1: osac.tests.v1.TestTargetLocalReference + (*TestOtherTargetLocalReference)(nil), // 2: osac.tests.v1.TestOtherTargetLocalReference + (*TestRefAttachment)(nil), // 3: osac.tests.v1.TestRefAttachment + (*TestRefSpec)(nil), // 4: osac.tests.v1.TestRefSpec + (*TestRefOneofSpec)(nil), // 5: osac.tests.v1.TestRefOneofSpec + (*TestResourceWithRefs)(nil), // 6: osac.tests.v1.TestResourceWithRefs + (*TestResourceWithOneof)(nil), // 7: osac.tests.v1.TestResourceWithOneof + (*CreateTestResourceWithRefsRequest)(nil), // 8: osac.tests.v1.CreateTestResourceWithRefsRequest + (*UpdateTestResourceWithRefsRequest)(nil), // 9: osac.tests.v1.UpdateTestResourceWithRefsRequest + (*CreateTestResourceWithOneofRequest)(nil), // 10: osac.tests.v1.CreateTestResourceWithOneofRequest + (*Metadata)(nil), // 11: osac.tests.v1.Metadata +} +var file_osac_tests_v1_reference_test_types_proto_depIdxs = []int32{ + 1, // 0: osac.tests.v1.TestRefAttachment.subnet:type_name -> osac.tests.v1.TestTargetLocalReference + 2, // 1: osac.tests.v1.TestRefAttachment.security_groups:type_name -> osac.tests.v1.TestOtherTargetLocalReference + 0, // 2: osac.tests.v1.TestRefSpec.target:type_name -> osac.tests.v1.TestTargetReference + 1, // 3: osac.tests.v1.TestRefSpec.local_target:type_name -> osac.tests.v1.TestTargetLocalReference + 3, // 4: osac.tests.v1.TestRefSpec.attachment:type_name -> osac.tests.v1.TestRefAttachment + 2, // 5: osac.tests.v1.TestRefSpec.other_targets:type_name -> osac.tests.v1.TestOtherTargetLocalReference + 1, // 6: osac.tests.v1.TestRefOneofSpec.compute_instance:type_name -> osac.tests.v1.TestTargetLocalReference + 2, // 7: osac.tests.v1.TestRefOneofSpec.cluster:type_name -> osac.tests.v1.TestOtherTargetLocalReference + 11, // 8: osac.tests.v1.TestResourceWithRefs.metadata:type_name -> osac.tests.v1.Metadata + 4, // 9: osac.tests.v1.TestResourceWithRefs.spec:type_name -> osac.tests.v1.TestRefSpec + 11, // 10: osac.tests.v1.TestResourceWithOneof.metadata:type_name -> osac.tests.v1.Metadata + 5, // 11: osac.tests.v1.TestResourceWithOneof.spec:type_name -> osac.tests.v1.TestRefOneofSpec + 6, // 12: osac.tests.v1.CreateTestResourceWithRefsRequest.object:type_name -> osac.tests.v1.TestResourceWithRefs + 6, // 13: osac.tests.v1.UpdateTestResourceWithRefsRequest.object:type_name -> osac.tests.v1.TestResourceWithRefs + 7, // 14: osac.tests.v1.CreateTestResourceWithOneofRequest.object:type_name -> osac.tests.v1.TestResourceWithOneof + 15, // [15:15] is the sub-list for method output_type + 15, // [15:15] is the sub-list for method input_type + 15, // [15:15] is the sub-list for extension type_name + 15, // [15:15] is the sub-list for extension extendee + 0, // [0:15] is the sub-list for field type_name +} + +func init() { file_osac_tests_v1_reference_test_types_proto_init() } +func file_osac_tests_v1_reference_test_types_proto_init() { + if File_osac_tests_v1_reference_test_types_proto != nil { + return + } + file_osac_tests_v1_object_type_proto_init() + file_osac_tests_v1_reference_test_types_proto_msgTypes[5].OneofWrappers = []any{ + (*testRefOneofSpec_ComputeInstance)(nil), + (*testRefOneofSpec_Cluster)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_osac_tests_v1_reference_test_types_proto_rawDesc), len(file_osac_tests_v1_reference_test_types_proto_rawDesc)), + NumEnums: 0, + NumMessages: 11, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_osac_tests_v1_reference_test_types_proto_goTypes, + DependencyIndexes: file_osac_tests_v1_reference_test_types_proto_depIdxs, + MessageInfos: file_osac_tests_v1_reference_test_types_proto_msgTypes, + }.Build() + File_osac_tests_v1_reference_test_types_proto = out.File + file_osac_tests_v1_reference_test_types_proto_goTypes = nil + file_osac_tests_v1_reference_test_types_proto_depIdxs = nil +} diff --git a/proto/tests/osac/tests/v1/reference_test_types.proto b/proto/tests/osac/tests/v1/reference_test_types.proto new file mode 100644 index 000000000..e6d7277f8 --- /dev/null +++ b/proto/tests/osac/tests/v1/reference_test_types.proto @@ -0,0 +1,86 @@ +// +// Copyright (c) 2025 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// + +syntax = "proto3"; + +package osac.tests.v1; + +import "osac/tests/v1/object_type.proto"; + +// Full reference for cross-tenant/project lookups. +message TestTargetReference { + string id = 1; + string name = 2; + string project = 3; + bool shared = 4; +} + +// Local reference for same-tenant/project lookups. +message TestTargetLocalReference { + string id = 1; + string name = 2; +} + +// Second local reference type for repeated and oneof testing. +message TestOtherTargetLocalReference { + string id = 1; + string name = 2; +} + +// Nested message containing reference fields (mirrors NetworkAttachment). +message TestRefAttachment { + TestTargetLocalReference subnet = 1; + repeated TestOtherTargetLocalReference security_groups = 2; +} + +// Spec with various reference field patterns. +message TestRefSpec { + TestTargetReference target = 1; + TestTargetLocalReference local_target = 2; + TestRefAttachment attachment = 3; + repeated TestOtherTargetLocalReference other_targets = 4; +} + +// Spec with oneof containing reference fields. +message TestRefOneofSpec { + oneof target { + TestTargetLocalReference compute_instance = 1; + TestOtherTargetLocalReference cluster = 2; + } +} + +// buf:lint:ignore OSAC_OBJECT_SHAPE +message TestResourceWithRefs { + string id = 1; + Metadata metadata = 2; + TestRefSpec spec = 3; +} + +// buf:lint:ignore OSAC_OBJECT_SHAPE +message TestResourceWithOneof { + string id = 1; + Metadata metadata = 2; + TestRefOneofSpec spec = 3; +} + +message CreateTestResourceWithRefsRequest { + TestResourceWithRefs object = 1; +} + +message UpdateTestResourceWithRefsRequest { + TestResourceWithRefs object = 1; +} + +message CreateTestResourceWithOneofRequest { + TestResourceWithOneof object = 1; +} From 7c057e5eed21c3818dccbb630af6b1c898e1c121 Mon Sep 17 00:00:00 2001 From: Haim Tayrie Date: Sun, 26 Jul 2026 12:46:23 +0300 Subject: [PATCH 02/12] OSAC-3082: implement reference validation interceptor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a gRPC unary server interceptor that validates resource references in Create/Update requests using protoreflect. The interceptor discovers reference-typed fields by naming convention (*Reference, *LocalReference), validates them against registered lookup functions, auto-populates missing id/name fields, and returns aggregated InvalidArgument errors with structured BadRequest FieldViolation details. Key behaviors: - Fail-closed: unregistered reference types produce Internal error - Tenant context: local refs use owning resource's metadata; full refs support explicit project and shared flag - Three resolution modes: name-only, id-only, both-provided (mismatch check) - Prometheus metrics: osac_reference_validation_total (counter), osac_reference_validation_duration_seconds (histogram) - Structured logging at DEBUG/WARN/ERROR levels The interceptor is registered at the end of the gRPC chain (after JIT provisioning). No lookup functions are registered yet — the interceptor is a no-op until the networking chunk (OSAC-3093) adds registrations. Assisted-by: Claude Code Signed-off-by: Haim Tayrie --- go.mod | 4 +- .../start/grpcserver/start_grpc_server_cmd.go | 13 + internal/references/reference_validator.go | 474 ++++++ .../references/reference_validator_test.go | 1377 +++++++++++++++++ internal/references/references_suite_test.go | 36 + 5 files changed, 1902 insertions(+), 2 deletions(-) create mode 100644 internal/references/reference_validator.go create mode 100644 internal/references/reference_validator_test.go create mode 100644 internal/references/references_suite_test.go diff --git a/go.mod b/go.mod index 2ed23ec90..68fb91298 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/osac-project/bare-metal-fulfillment-operator v0.0.10 github.com/osac-project/osac-operator/api v0.0.7 github.com/prometheus/client_golang v1.24.0 + github.com/prometheus/client_model v0.6.2 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/zalando/go-keyring v0.2.8 @@ -41,6 +42,7 @@ require ( golang.org/x/sync v0.22.0 golang.org/x/term v0.45.0 google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa google.golang.org/grpc v1.82.1 google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af gopkg.in/yaml.v3 v3.0.1 @@ -107,7 +109,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.70.0 // indirect github.com/prometheus/procfs v0.21.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect @@ -129,7 +130,6 @@ require ( golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.47.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect diff --git a/internal/cmd/service/start/grpcserver/start_grpc_server_cmd.go b/internal/cmd/service/start/grpcserver/start_grpc_server_cmd.go index 03c881300..129e1c6c2 100644 --- a/internal/cmd/service/start/grpcserver/start_grpc_server_cmd.go +++ b/internal/cmd/service/start/grpcserver/start_grpc_server_cmd.go @@ -50,6 +50,7 @@ import ( "github.com/osac-project/fulfillment-service/internal/network" "github.com/osac-project/fulfillment-service/internal/provisioners" "github.com/osac-project/fulfillment-service/internal/recovery" + "github.com/osac-project/fulfillment-service/internal/references" "github.com/osac-project/fulfillment-service/internal/servers" shtdwn "github.com/osac-project/fulfillment-service/internal/shutdown" "github.com/osac-project/fulfillment-service/internal/validation" @@ -430,6 +431,16 @@ func (c *runnerContext) run(cmd *cobra.Command, argv []string) error { //nolint: return fmt.Errorf("failed to create JIT provisioning interceptor: %w", err) } + // Prepare the reference validation interceptor: + c.logger.InfoContext(ctx, "Creating reference validation interceptor") + referenceValidator, err := references.NewReferenceValidator(). + SetLogger(c.logger). + SetMetricsRegisterer(metricsRegisterer). + Build() + if err != nil { + return fmt.Errorf("failed to create reference validation interceptor: %w", err) + } + // Prepare the transactions manager: c.logger.InfoContext(ctx, "Creating transactions manager") txManager, err := database.NewTxManager(). @@ -495,6 +506,7 @@ func (c *runnerContext) run(cmd *cobra.Command, argv []string) error { //nolint: authnInterceptor.UnaryServer, authzInterceptor.UnaryServer, jitProvisioningInterceptor.UnaryServer, + referenceValidator.UnaryServer, ), grpc.ChainStreamInterceptor( panicInterceptor.StreamServer, @@ -504,6 +516,7 @@ func (c *runnerContext) run(cmd *cobra.Command, argv []string) error { //nolint: authnInterceptor.StreamServer, authzInterceptor.StreamServer, jitProvisioningInterceptor.StreamServer, + referenceValidator.StreamServer, ), ) shutdown.AddGrpcServer(network.GrpcListenerName, 0, grpcServer) diff --git a/internal/references/reference_validator.go b/internal/references/reference_validator.go new file mode 100644 index 000000000..533f1e882 --- /dev/null +++ b/internal/references/reference_validator.go @@ -0,0 +1,474 @@ +/* +Copyright (c) 2025 Red Hat, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +package references + +import ( + "context" + "errors" + "fmt" + "log/slog" + "strings" + "time" + + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc" + grpccodes "google.golang.org/grpc/codes" + grpcstatus "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +// ResolvedRef is the result of resolving a reference to a known resource. +type ResolvedRef struct { + ID string + Tenant string + Project string + Name string +} + +// ReferenceLookupFunc resolves a resource by id, name, or both within a tenant/project scope. +// Returns the fully-resolved reference or an error. Errors that satisfy the IsNotFound() interface +// method are treated as invalid references; all other errors are treated as internal failures. +type ReferenceLookupFunc func( + ctx context.Context, + tenant, project, id, name string, +) (*ResolvedRef, error) + +// ReferenceValidatorBuilder configures and creates a ReferenceValidator. Don't create instances +// of this type directly, use the NewReferenceValidator function instead. +type ReferenceValidatorBuilder struct { + logger *slog.Logger + registerer prometheus.Registerer +} + +// ReferenceValidator is a gRPC interceptor that validates resource references in Create and Update +// requests using protoreflect. It discovers reference-typed fields by naming convention (message +// types ending with "Reference" or "LocalReference"), validates them against registered lookup +// functions, and mutates the request to auto-populate missing id or name fields. +type ReferenceValidator struct { + logger *slog.Logger + registry map[protoreflect.FullName]ReferenceLookupFunc + validationTotal *prometheus.CounterVec + validationDurati *prometheus.HistogramVec +} + +// NewReferenceValidator creates a builder that can then be used to configure and create a new +// reference validation interceptor. +func NewReferenceValidator() *ReferenceValidatorBuilder { + return &ReferenceValidatorBuilder{} +} + +// SetLogger sets the logger. This is mandatory. +func (b *ReferenceValidatorBuilder) SetLogger(value *slog.Logger) *ReferenceValidatorBuilder { + b.logger = value + return b +} + +// SetMetricsRegisterer sets the Prometheus registerer for metrics. This is optional. +func (b *ReferenceValidatorBuilder) SetMetricsRegisterer(value prometheus.Registerer) *ReferenceValidatorBuilder { + b.registerer = value + return b +} + +// Build uses the data stored in the builder to create and configure a new interceptor. +func (b *ReferenceValidatorBuilder) Build() (result *ReferenceValidator, err error) { + if b.logger == nil { + err = errors.New("logger is mandatory") + return + } + + result = &ReferenceValidator{ + logger: b.logger, + registry: make(map[protoreflect.FullName]ReferenceLookupFunc), + } + + if b.registerer != nil { + validationTotal := prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "osac_reference_validation_total", + Help: "Total number of reference validations performed.", + }, + []string{"resource_type", "result"}, + ) + err = b.registerer.Register(validationTotal) + if err != nil { + var registered prometheus.AlreadyRegisteredError + if errors.As(err, ®istered) { + validationTotal = registered.ExistingCollector.(*prometheus.CounterVec) + err = nil + } else { + return + } + } + result.validationTotal = validationTotal + + validationDuration := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "osac_reference_validation_duration_seconds", + Help: "Duration of individual reference validation lookups in seconds.", + Buckets: []float64{0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0}, + }, + []string{"resource_type"}, + ) + err = b.registerer.Register(validationDuration) + if err != nil { + var registered prometheus.AlreadyRegisteredError + if errors.As(err, ®istered) { + validationDuration = registered.ExistingCollector.(*prometheus.HistogramVec) + err = nil + } else { + return + } + } + result.validationDurati = validationDuration + } + + return +} + +// Register associates a reference message type with a lookup function. Must be called before the +// gRPC server starts accepting requests. +func (v *ReferenceValidator) Register(fullName protoreflect.FullName, lookupFunc ReferenceLookupFunc) { + v.registry[fullName] = lookupFunc +} + +// UnaryServer is the unary server interceptor function. +func (v *ReferenceValidator) UnaryServer(ctx context.Context, request any, info *grpc.UnaryServerInfo, + handler grpc.UnaryHandler) (response any, err error) { + if !isCreateOrUpdate(info.FullMethod) { + return handler(ctx, request) + } + + err = v.validate(ctx, request) + if err != nil { + return + } + + return handler(ctx, request) +} + +// StreamServer is the stream server interceptor function. Reference validation is not applicable +// to streams, so this is a pass-through. +func (v *ReferenceValidator) StreamServer(srv any, stream grpc.ServerStream, + info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + return handler(srv, stream) +} + +// validate walks the request message and validates all reference-typed fields. +func (v *ReferenceValidator) validate(ctx context.Context, request any) error { + message, ok := request.(proto.Message) + if !ok { + return nil + } + + tenant, project := extractTenantProject(message) + + var violations []*errdetails.BadRequest_FieldViolation + err := v.walkMessage(ctx, message.ProtoReflect(), nil, &violations, tenant, project) + if err != nil { + return err + } + + if len(violations) > 0 { + descriptions := make([]string, len(violations)) + for i, fv := range violations { + descriptions[i] = fmt.Sprintf("%s: %s", fv.GetField(), fv.GetDescription()) + } + + st, detailErr := grpcstatus.New( + grpccodes.InvalidArgument, + fmt.Sprintf("reference validation failed: %s", strings.Join(descriptions, "; ")), + ).WithDetails(&errdetails.BadRequest{FieldViolations: violations}) + if detailErr != nil { + return grpcstatus.Errorf(grpccodes.Internal, + "failed to attach error details: %v", detailErr) + } + return st.Err() + } + + return nil +} + +// walkMessage recursively walks a protoreflect.Message, discovering and validating reference-typed +// fields. Appends FieldViolation entries for invalid references. Mutates the message to fill in +// missing reference fields. +func (v *ReferenceValidator) walkMessage(ctx context.Context, msg protoreflect.Message, path []string, + violations *[]*errdetails.BadRequest_FieldViolation, tenant, project string) error { + var internalErr error + + msg.Range(func(fd protoreflect.FieldDescriptor, val protoreflect.Value) bool { + if fd.Kind() != protoreflect.MessageKind { + return true + } + + fieldPath := append(path, string(fd.Name())) + + if fd.IsList() { + list := val.List() + for i := 0; i < list.Len(); i++ { + elemMsg := list.Get(i).Message() + fullName := elemMsg.Descriptor().FullName() + indexedPath := append(append([]string{}, fieldPath[:len(fieldPath)-1]...), + fmt.Sprintf("%s[%d]", fd.Name(), i)) + + if isReferenceType(fullName) { + err := v.resolveAndMutate(ctx, elemMsg, fullName, indexedPath, + violations, tenant, project) + if err != nil { + internalErr = err + return false + } + } else { + err := v.walkMessage(ctx, elemMsg, indexedPath, violations, tenant, project) + if err != nil { + internalErr = err + return false + } + } + } + return true + } + + subMsg := val.Message() + fullName := subMsg.Descriptor().FullName() + + if isReferenceType(fullName) { + err := v.resolveAndMutate(ctx, subMsg, fullName, fieldPath, + violations, tenant, project) + if err != nil { + internalErr = err + return false + } + } else { + err := v.walkMessage(ctx, subMsg, fieldPath, violations, tenant, project) + if err != nil { + internalErr = err + return false + } + } + + return true + }) + + return internalErr +} + +// resolveAndMutate validates a single reference field against its registered lookup function and +// mutates the reference message to fill in missing fields. +func (v *ReferenceValidator) resolveAndMutate(ctx context.Context, refMsg protoreflect.Message, + fullName protoreflect.FullName, path []string, + violations *[]*errdetails.BadRequest_FieldViolation, + callerTenant, callerProject string) error { + + lookupFunc, ok := v.registry[fullName] + if !ok { + v.logger.Error("No lookup registered for reference type", + "reference_type", string(fullName), + "field_path", strings.Join(path, "."), + ) + return grpcstatus.Errorf(grpccodes.Internal, + "no lookup registered for reference type %q", fullName) + } + + refTenant, refProject := resolveTenantProject(refMsg, fullName, callerTenant, callerProject) + + idField := refMsg.Descriptor().Fields().ByName("id") + nameField := refMsg.Descriptor().Fields().ByName("name") + id := refMsg.Get(idField).String() + name := refMsg.Get(nameField).String() + + resourceType := string(fullName.Name()) + fieldPath := strings.Join(path, ".") + + start := time.Now() + resolved, err := lookupFunc(ctx, refTenant, refProject, id, name) + elapsed := time.Since(start) + + v.recordDuration(resourceType, elapsed) + + if err != nil { + if isNotFoundErr(err) { + desc := fmt.Sprintf("%s %q not found", shortName(fullName), identifier(id, name)) + *violations = append(*violations, &errdetails.BadRequest_FieldViolation{ + Field: fieldPath, + Description: desc, + }) + v.logger.Warn("Reference not found", + "field_path", fieldPath, + "reference_type", resourceType, + "identifier", identifier(id, name), + "tenant", refTenant, + "project", refProject, + ) + v.recordResult(resourceType, "invalid") + return nil + } + v.logger.Error("Reference lookup failed", + "field_path", fieldPath, + "reference_type", resourceType, + "error", err, + ) + v.recordResult(resourceType, "error") + return grpcstatus.Errorf(grpccodes.Internal, + "failed to resolve reference at %s: %v", fieldPath, err) + } + + if id == "" && resolved.ID != "" { + refMsg.Set(idField, protoreflect.ValueOfString(resolved.ID)) + } + if name == "" && resolved.Name != "" { + refMsg.Set(nameField, protoreflect.ValueOfString(resolved.Name)) + } + + if id != "" && name != "" && (resolved.ID != id || resolved.Name != name) { + desc := fmt.Sprintf("id %q and name %q do not refer to the same resource", id, name) + *violations = append(*violations, &errdetails.BadRequest_FieldViolation{ + Field: fieldPath, + Description: desc, + }) + v.logger.Warn("Reference id/name mismatch", + "field_path", fieldPath, + "reference_type", resourceType, + "id", id, + "name", name, + ) + v.recordResult(resourceType, "invalid") + return nil + } + + v.logger.Debug("Reference validated", + "field_path", fieldPath, + "reference_type", resourceType, + "resolved_id", resolved.ID, + "resolved_name", resolved.Name, + ) + v.recordResult(resourceType, "valid") + return nil +} + +func (v *ReferenceValidator) recordResult(resourceType, result string) { + if v.validationTotal != nil { + v.validationTotal.With(prometheus.Labels{ + "resource_type": resourceType, + "result": result, + }).Inc() + } +} + +func (v *ReferenceValidator) recordDuration(resourceType string, d time.Duration) { + if v.validationDurati != nil { + v.validationDurati.With(prometheus.Labels{ + "resource_type": resourceType, + }).Observe(d.Seconds()) + } +} + +// resolveTenantProject determines the tenant and project for a reference lookup based on the +// reference type and fields. +func resolveTenantProject(refMsg protoreflect.Message, fullName protoreflect.FullName, + callerTenant, callerProject string) (string, string) { + if isLocalReference(fullName) { + return callerTenant, callerProject + } + + tenant := callerTenant + project := callerProject + + sharedField := refMsg.Descriptor().Fields().ByName("shared") + if sharedField != nil && refMsg.Get(sharedField).Bool() { + tenant = "shared" + } + + projectField := refMsg.Descriptor().Fields().ByName("project") + if projectField != nil { + explicitProject := refMsg.Get(projectField).String() + if explicitProject != "" { + project = explicitProject + } + } + + return tenant, project +} + +// extractTenantProject extracts the tenant and project from the request's embedded object metadata +// using protoreflect (request → object → metadata → {tenant, project}). +func extractTenantProject(request proto.Message) (tenant, project string) { + msg := request.ProtoReflect() + + objectField := msg.Descriptor().Fields().ByName("object") + if objectField == nil { + return + } + objectMsg := msg.Get(objectField).Message() + + metadataField := objectMsg.Descriptor().Fields().ByName("metadata") + if metadataField == nil { + return + } + metadataMsg := objectMsg.Get(metadataField).Message() + + tenantField := metadataMsg.Descriptor().Fields().ByName("tenant") + if tenantField != nil { + tenant = metadataMsg.Get(tenantField).String() + } + + projectField := metadataMsg.Descriptor().Fields().ByName("project") + if projectField != nil { + project = metadataMsg.Get(projectField).String() + } + + return +} + +// isReferenceType checks if a message type is a reference type based on its name suffix. +func isReferenceType(fullName protoreflect.FullName) bool { + return strings.HasSuffix(string(fullName), "Reference") +} + +// isLocalReference checks if a reference type is a local reference. +func isLocalReference(fullName protoreflect.FullName) bool { + return strings.HasSuffix(string(fullName), "LocalReference") +} + +// isCreateOrUpdate checks if the gRPC method is a Create or Update operation. +func isCreateOrUpdate(method string) bool { + return strings.HasSuffix(method, "/Create") || strings.HasSuffix(method, "/Update") +} + +// isNotFoundErr checks whether an error represents a "not found" condition. +func isNotFoundErr(err error) bool { + var nf interface{ IsNotFound() bool } + if errors.As(err, &nf) { + return nf.IsNotFound() + } + return false +} + +// shortName extracts the unqualified message name from a fully-qualified protobuf name and strips +// common prefixes/suffixes for human-readable error messages. +func shortName(fullName protoreflect.FullName) string { + name := string(fullName.Name()) + name = strings.TrimSuffix(name, "LocalReference") + name = strings.TrimSuffix(name, "Reference") + return name +} + +// identifier returns a human-readable identifier string from id and/or name. +func identifier(id, name string) string { + if name != "" { + return name + } + return id +} diff --git a/internal/references/reference_validator_test.go b/internal/references/reference_validator_test.go new file mode 100644 index 000000000..11f014a2e --- /dev/null +++ b/internal/references/reference_validator_test.go @@ -0,0 +1,1377 @@ +/* +Copyright (c) 2025 Red Hat, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +package references + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc" + grpccodes "google.golang.org/grpc/codes" + grpcstatus "google.golang.org/grpc/status" + "google.golang.org/protobuf/reflect/protoreflect" + + testsv1 "github.com/osac-project/fulfillment-service/internal/api/osac/tests/v1" +) + +var _ = Describe("Reference validator", func() { + var validator *ReferenceValidator + + Describe("Builder", func() { + It("Succeeds when logger is set", func() { + result, err := NewReferenceValidator(). + SetLogger(logger). + Build() + Expect(err).ToNot(HaveOccurred()) + Expect(result).ToNot(BeNil()) + }) + + It("Fails when logger is not set", func() { + result, err := NewReferenceValidator(). + Build() + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError("logger is mandatory")) + Expect(result).To(BeNil()) + }) + }) + + Describe("Method filtering", func() { + BeforeEach(func() { + var err error + validator, err = NewReferenceValidator(). + SetLogger(logger). + Build() + Expect(err).ToNot(HaveOccurred()) + }) + + It("Validates Create requests", func() { + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{}.Build(), + }.Build(), + }.Build() + + handlerCalled := false + mockHandler := func(ctx context.Context, req any) (any, error) { + handlerCalled = true + return "response", nil + } + + response, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(handlerCalled).To(BeTrue()) + Expect(response).To(Equal("response")) + }) + + It("Validates Update requests", func() { + request := testsv1.UpdateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{}.Build(), + }.Build(), + }.Build() + + handlerCalled := false + mockHandler := func(ctx context.Context, req any) (any, error) { + handlerCalled = true + return "response", nil + } + + response, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Update"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(handlerCalled).To(BeTrue()) + Expect(response).To(Equal("response")) + }) + + It("Passes through Get requests without validation", func() { + handlerCalled := false + mockHandler := func(ctx context.Context, req any) (any, error) { + handlerCalled = true + return "response", nil + } + + response, err := validator.UnaryServer( + context.Background(), + "not-a-proto-message", + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Get"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(handlerCalled).To(BeTrue()) + Expect(response).To(Equal("response")) + }) + + It("Passes through List requests without validation", func() { + handlerCalled := false + mockHandler := func(ctx context.Context, req any) (any, error) { + handlerCalled = true + return "response", nil + } + + response, err := validator.UnaryServer( + context.Background(), + "anything", + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/List"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(handlerCalled).To(BeTrue()) + Expect(response).To(Equal("response")) + }) + + It("Passes through Delete requests without validation", func() { + handlerCalled := false + mockHandler := func(ctx context.Context, req any) (any, error) { + handlerCalled = true + return "response", nil + } + + response, err := validator.UnaryServer( + context.Background(), + "anything", + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Delete"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(handlerCalled).To(BeTrue()) + Expect(response).To(Equal("response")) + }) + + It("Passes through Signal requests without validation", func() { + handlerCalled := false + mockHandler := func(ctx context.Context, req any) (any, error) { + handlerCalled = true + return "response", nil + } + + response, err := validator.UnaryServer( + context.Background(), + "anything", + &grpc.UnaryServerInfo{FullMethod: "/osac.private.v1.TestService/Signal"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(handlerCalled).To(BeTrue()) + Expect(response).To(Equal("response")) + }) + }) + + Describe("Field discovery", func() { + BeforeEach(func() { + var err error + validator, err = NewReferenceValidator(). + SetLogger(logger). + Build() + Expect(err).ToNot(HaveOccurred()) + }) + + It("Discovers simple reference fields in spec", func() { + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + Target: testsv1.TestTargetReference_builder{ + Name: "my-target", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + refs := discoverReferenceFields(request) + + Expect(refs).To(ContainElement(HaveField("FullName", + protoreflect.FullName("osac.tests.v1.TestTargetReference")))) + }) + + It("Discovers local reference fields", func() { + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + LocalTarget: testsv1.TestTargetLocalReference_builder{ + Name: "my-local-target", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + refs := discoverReferenceFields(request) + + Expect(refs).To(ContainElement(HaveField("FullName", + protoreflect.FullName("osac.tests.v1.TestTargetLocalReference")))) + }) + + It("Discovers nested reference fields", func() { + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + Attachment: testsv1.TestRefAttachment_builder{ + Subnet: testsv1.TestTargetLocalReference_builder{ + Name: "my-subnet", + }.Build(), + }.Build(), + }.Build(), + }.Build(), + }.Build() + + refs := discoverReferenceFields(request) + + Expect(refs).To(ContainElement(HaveField("FullName", + protoreflect.FullName("osac.tests.v1.TestTargetLocalReference")))) + }) + + It("Discovers repeated reference fields", func() { + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + OtherTargets: []*testsv1.TestOtherTargetLocalReference{ + testsv1.TestOtherTargetLocalReference_builder{ + Name: "target-1", + }.Build(), + testsv1.TestOtherTargetLocalReference_builder{ + Name: "target-2", + }.Build(), + }, + }.Build(), + }.Build(), + }.Build() + + refs := discoverReferenceFields(request) + + otherTargetCount := 0 + for _, ref := range refs { + if ref.FullName == "osac.tests.v1.TestOtherTargetLocalReference" { + otherTargetCount++ + } + } + Expect(otherTargetCount).To(Equal(2)) + }) + + It("Discovers nested repeated reference fields", func() { + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + Attachment: testsv1.TestRefAttachment_builder{ + SecurityGroups: []*testsv1.TestOtherTargetLocalReference{ + testsv1.TestOtherTargetLocalReference_builder{ + Name: "sg-1", + }.Build(), + }, + }.Build(), + }.Build(), + }.Build(), + }.Build() + + refs := discoverReferenceFields(request) + + Expect(refs).To(ContainElement(HaveField("FullName", + protoreflect.FullName("osac.tests.v1.TestOtherTargetLocalReference")))) + }) + + It("Discovers oneof reference fields", func() { + request := testsv1.CreateTestResourceWithOneofRequest_builder{ + Object: testsv1.TestResourceWithOneof_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefOneofSpec_builder{ + ComputeInstance: testsv1.TestTargetLocalReference_builder{ + Name: "my-instance", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + refs := discoverReferenceFields(request) + + Expect(refs).To(ContainElement(HaveField("FullName", + protoreflect.FullName("osac.tests.v1.TestTargetLocalReference")))) + }) + + It("Discovers all reference field patterns in a complex message", func() { + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + Target: testsv1.TestTargetReference_builder{ + Name: "full-ref-target", + }.Build(), + LocalTarget: testsv1.TestTargetLocalReference_builder{ + Name: "local-target", + }.Build(), + Attachment: testsv1.TestRefAttachment_builder{ + Subnet: testsv1.TestTargetLocalReference_builder{ + Name: "subnet-1", + }.Build(), + SecurityGroups: []*testsv1.TestOtherTargetLocalReference{ + testsv1.TestOtherTargetLocalReference_builder{ + Name: "sg-1", + }.Build(), + }, + }.Build(), + OtherTargets: []*testsv1.TestOtherTargetLocalReference{ + testsv1.TestOtherTargetLocalReference_builder{ + Name: "other-1", + }.Build(), + }, + }.Build(), + }.Build(), + }.Build() + + refs := discoverReferenceFields(request) + + fullRefCount := 0 + localRefCount := 0 + otherRefCount := 0 + for _, ref := range refs { + switch ref.FullName { + case "osac.tests.v1.TestTargetReference": + fullRefCount++ + case "osac.tests.v1.TestTargetLocalReference": + localRefCount++ + case "osac.tests.v1.TestOtherTargetLocalReference": + otherRefCount++ + } + } + Expect(fullRefCount).To(Equal(1)) + Expect(localRefCount).To(Equal(2)) + Expect(otherRefCount).To(Equal(2)) + }) + + It("Returns no references when spec has no reference fields", func() { + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{}.Build(), + }.Build(), + }.Build() + + refs := discoverReferenceFields(request) + + Expect(refs).To(BeEmpty()) + }) + }) + + Describe("Stream pass-through", func() { + BeforeEach(func() { + var err error + validator, err = NewReferenceValidator(). + SetLogger(logger). + Build() + Expect(err).ToNot(HaveOccurred()) + }) + + It("Passes streams through without validation", func() { + handlerCalled := false + mockHandler := func(srv any, stream grpc.ServerStream) error { + handlerCalled = true + return nil + } + + err := validator.StreamServer( + nil, + nil, + &grpc.StreamServerInfo{FullMethod: "/osac.tests.v1.TestService/Watch"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(handlerCalled).To(BeTrue()) + }) + }) + + Describe("Reference type detection", func() { + It("Identifies Reference types", func() { + Expect(isReferenceType("osac.public.v1.VirtualNetworkReference")).To(BeTrue()) + Expect(isReferenceType("osac.tests.v1.TestTargetReference")).To(BeTrue()) + }) + + It("Identifies LocalReference types", func() { + Expect(isReferenceType("osac.public.v1.SubnetLocalReference")).To(BeTrue()) + Expect(isReferenceType("osac.tests.v1.TestTargetLocalReference")).To(BeTrue()) + }) + + It("Rejects non-reference types", func() { + Expect(isReferenceType("osac.public.v1.Metadata")).To(BeFalse()) + Expect(isReferenceType("osac.public.v1.VirtualNetworkSpec")).To(BeFalse()) + Expect(isReferenceType("osac.public.v1.VirtualNetwork")).To(BeFalse()) + }) + + It("Distinguishes local from full references", func() { + Expect(isLocalReference("osac.tests.v1.TestTargetLocalReference")).To(BeTrue()) + Expect(isLocalReference("osac.tests.v1.TestTargetReference")).To(BeFalse()) + }) + }) + + Describe("Create or Update detection", func() { + It("Matches Create methods", func() { + Expect(isCreateOrUpdate("/osac.public.v1.VirtualNetworks/Create")).To(BeTrue()) + }) + + It("Matches Update methods", func() { + Expect(isCreateOrUpdate("/osac.public.v1.VirtualNetworks/Update")).To(BeTrue()) + }) + + It("Does not match Get methods", func() { + Expect(isCreateOrUpdate("/osac.public.v1.VirtualNetworks/Get")).To(BeFalse()) + }) + + It("Does not match List methods", func() { + Expect(isCreateOrUpdate("/osac.public.v1.VirtualNetworks/List")).To(BeFalse()) + }) + + It("Does not match Delete methods", func() { + Expect(isCreateOrUpdate("/osac.public.v1.VirtualNetworks/Delete")).To(BeFalse()) + }) + + It("Does not match Signal methods", func() { + Expect(isCreateOrUpdate("/osac.private.v1.VirtualNetworks/Signal")).To(BeFalse()) + }) + + It("Does not match CreateSomethingElse (suffix match)", func() { + Expect(isCreateOrUpdate("/osac.public.v1.Service/CreateNotification")).To(BeFalse()) + }) + }) + + Describe("Unregistered reference types", func() { + BeforeEach(func() { + var err error + validator, err = NewReferenceValidator(). + SetLogger(logger). + Build() + Expect(err).ToNot(HaveOccurred()) + }) + + It("Returns Internal error for unregistered reference types (fail closed)", func() { + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + Target: testsv1.TestTargetReference_builder{ + Name: "my-target", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + Fail("Handler should not be called when reference type is unregistered") + return nil, nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).To(HaveOccurred()) + status, ok := grpcstatus.FromError(err) + Expect(ok).To(BeTrue()) + Expect(status.Code()).To(Equal(grpccodes.Internal)) + Expect(status.Message()).To(ContainSubstring("no lookup registered")) + }) + }) + + Describe("Lookup registration", func() { + BeforeEach(func() { + var err error + validator, err = NewReferenceValidator(). + SetLogger(logger). + Build() + Expect(err).ToNot(HaveOccurred()) + }) + + It("Calls registered lookup function with correct arguments", func() { + var capturedTenant, capturedProject, capturedID, capturedName string + validator.Register("osac.tests.v1.TestTargetReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + capturedTenant = tenant + capturedProject = project + capturedID = id + capturedName = name + return &ResolvedRef{ + ID: "resolved-id", + Tenant: tenant, + Project: project, + Name: name, + }, nil + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "project-b", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + Target: testsv1.TestTargetReference_builder{ + Name: "my-target", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + handlerCalled := false + mockHandler := func(ctx context.Context, req any) (any, error) { + handlerCalled = true + return "response", nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(handlerCalled).To(BeTrue()) + Expect(capturedTenant).To(Equal("tenant-a")) + Expect(capturedProject).To(Equal("project-b")) + Expect(capturedID).To(BeEmpty()) + Expect(capturedName).To(Equal("my-target")) + }) + + It("Auto-populates id when resolved by name", func() { + validator.Register("osac.tests.v1.TestTargetLocalReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return &ResolvedRef{ + ID: "auto-id-123", + Tenant: tenant, + Project: project, + Name: name, + }, nil + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + LocalTarget: testsv1.TestTargetLocalReference_builder{ + Name: "my-local-target", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + return "response", nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(request.GetObject().GetSpec().GetLocalTarget().GetId()).To(Equal("auto-id-123")) + }) + + It("Auto-populates name when resolved by id", func() { + validator.Register("osac.tests.v1.TestTargetLocalReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return &ResolvedRef{ + ID: id, + Tenant: tenant, + Project: project, + Name: "auto-name-from-id", + }, nil + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + LocalTarget: testsv1.TestTargetLocalReference_builder{ + Id: "target-id-456", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + return "response", nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(request.GetObject().GetSpec().GetLocalTarget().GetName()).To(Equal("auto-name-from-id")) + }) + }) + + Describe("Tenant context extraction", func() { + BeforeEach(func() { + var err error + validator, err = NewReferenceValidator(). + SetLogger(logger). + Build() + Expect(err).ToNot(HaveOccurred()) + }) + + It("Extracts tenant and project from request metadata for local references", func() { + var capturedTenant, capturedProject string + validator.Register("osac.tests.v1.TestTargetLocalReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + capturedTenant = tenant + capturedProject = project + return &ResolvedRef{ID: "id-1", Tenant: tenant, Project: project, Name: name}, nil + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "my-tenant", + Project: "my-project", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + LocalTarget: testsv1.TestTargetLocalReference_builder{ + Name: "target", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + return "response", nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(capturedTenant).To(Equal("my-tenant")) + Expect(capturedProject).To(Equal("my-project")) + }) + + It("Uses explicit project from full reference when present", func() { + var capturedProject string + validator.Register("osac.tests.v1.TestTargetReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + capturedProject = project + return &ResolvedRef{ID: "id-1", Tenant: tenant, Project: project, Name: name}, nil + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + Target: testsv1.TestTargetReference_builder{ + Name: "target-in-other-project", + Project: "other-project", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + return "response", nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(capturedProject).To(Equal("other-project")) + }) + + It("Uses shared tenant when shared flag is set on full reference", func() { + var capturedTenant string + validator.Register("osac.tests.v1.TestTargetReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + capturedTenant = tenant + return &ResolvedRef{ID: "id-1", Tenant: tenant, Project: project, Name: name}, nil + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + Target: testsv1.TestTargetReference_builder{ + Name: "shared-target", + Shared: true, + }.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + return "response", nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(capturedTenant).To(Equal("shared")) + }) + }) + + Describe("Error reporting", func() { + BeforeEach(func() { + var err error + validator, err = NewReferenceValidator(). + SetLogger(logger). + Build() + Expect(err).ToNot(HaveOccurred()) + }) + + It("Returns InvalidArgument with BadRequest FieldViolation for not-found reference", func() { + validator.Register("osac.tests.v1.TestTargetReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return nil, ¬FoundError{name: name} + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + Target: testsv1.TestTargetReference_builder{ + Name: "nonexistent", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + Fail("Handler should not be called for invalid reference") + return nil, nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).To(HaveOccurred()) + status, ok := grpcstatus.FromError(err) + Expect(ok).To(BeTrue()) + Expect(status.Code()).To(Equal(grpccodes.InvalidArgument)) + Expect(status.Message()).To(ContainSubstring("reference validation failed")) + + details := status.Details() + Expect(details).To(HaveLen(1)) + badRequest, ok := details[0].(*errdetails.BadRequest) + Expect(ok).To(BeTrue()) + Expect(badRequest.GetFieldViolations()).To(HaveLen(1)) + fv := badRequest.GetFieldViolations()[0] + Expect(fv.GetField()).To(Equal("object.spec.target")) + Expect(fv.GetDescription()).To(ContainSubstring("nonexistent")) + }) + + It("Aggregates multiple reference errors in a single BadRequest", func() { + validator.Register("osac.tests.v1.TestTargetReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return nil, ¬FoundError{name: name} + }) + validator.Register("osac.tests.v1.TestTargetLocalReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return nil, ¬FoundError{name: name} + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + Target: testsv1.TestTargetReference_builder{ + Name: "missing-full", + }.Build(), + LocalTarget: testsv1.TestTargetLocalReference_builder{ + Name: "missing-local", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + Fail("Handler should not be called for invalid references") + return nil, nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).To(HaveOccurred()) + status, ok := grpcstatus.FromError(err) + Expect(ok).To(BeTrue()) + Expect(status.Code()).To(Equal(grpccodes.InvalidArgument)) + + details := status.Details() + Expect(details).To(HaveLen(1)) + badRequest, ok := details[0].(*errdetails.BadRequest) + Expect(ok).To(BeTrue()) + Expect(badRequest.GetFieldViolations()).To(HaveLen(2)) + }) + + It("Returns Internal for DAO lookup errors (not InvalidArgument)", func() { + validator.Register("osac.tests.v1.TestTargetReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return nil, fmt.Errorf("database connection refused") + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + Target: testsv1.TestTargetReference_builder{ + Name: "some-target", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + Fail("Handler should not be called on internal error") + return nil, nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).To(HaveOccurred()) + status, ok := grpcstatus.FromError(err) + Expect(ok).To(BeTrue()) + Expect(status.Code()).To(Equal(grpccodes.Internal)) + Expect(status.Message()).To(ContainSubstring("database connection refused")) + }) + + It("Returns InvalidArgument for id/name mismatch", func() { + validator.Register("osac.tests.v1.TestTargetLocalReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return &ResolvedRef{ + ID: "different-id", + Name: "different-name", + }, nil + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + LocalTarget: testsv1.TestTargetLocalReference_builder{ + Id: "my-id", + Name: "my-name", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + Fail("Handler should not be called for mismatched references") + return nil, nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).To(HaveOccurred()) + status, ok := grpcstatus.FromError(err) + Expect(ok).To(BeTrue()) + Expect(status.Code()).To(Equal(grpccodes.InvalidArgument)) + Expect(status.Message()).To(ContainSubstring("do not refer to the same resource")) + }) + }) + + Describe("Pass-through for non-proto requests", func() { + BeforeEach(func() { + var err error + validator, err = NewReferenceValidator(). + SetLogger(logger). + Build() + Expect(err).ToNot(HaveOccurred()) + }) + + It("Passes through non-proto request on Create", func() { + handlerCalled := false + mockHandler := func(ctx context.Context, req any) (any, error) { + handlerCalled = true + return "response", nil + } + + response, err := validator.UnaryServer( + context.Background(), + "not-a-proto", + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(handlerCalled).To(BeTrue()) + Expect(response).To(Equal("response")) + }) + }) + + Describe("Prometheus metrics", func() { + var registry *prometheus.Registry + + BeforeEach(func() { + registry = prometheus.NewPedanticRegistry() + var err error + validator, err = NewReferenceValidator(). + SetLogger(logger). + SetMetricsRegisterer(registry). + Build() + Expect(err).ToNot(HaveOccurred()) + }) + + It("Increments valid counter on successful validation", func() { + validator.Register("osac.tests.v1.TestTargetLocalReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return &ResolvedRef{ID: "id-1", Tenant: tenant, Project: project, Name: name}, nil + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + LocalTarget: testsv1.TestTargetLocalReference_builder{ + Name: "target", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + return "response", nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + Expect(err).ToNot(HaveOccurred()) + + count := counterValue(registry, "osac_reference_validation_total", + "TestTargetLocalReference", "valid") + Expect(count).To(Equal(1.0)) + }) + + It("Increments invalid counter on not-found reference", func() { + validator.Register("osac.tests.v1.TestTargetReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return nil, ¬FoundError{name: name} + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + Target: testsv1.TestTargetReference_builder{ + Name: "missing", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + return nil, nil + } + + _, _ = validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + count := counterValue(registry, "osac_reference_validation_total", + "TestTargetReference", "invalid") + Expect(count).To(Equal(1.0)) + }) + + It("Increments error counter on DAO error", func() { + validator.Register("osac.tests.v1.TestTargetReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return nil, fmt.Errorf("connection refused") + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + Target: testsv1.TestTargetReference_builder{ + Name: "target", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + return nil, nil + } + + _, _ = validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + count := counterValue(registry, "osac_reference_validation_total", + "TestTargetReference", "error") + Expect(count).To(Equal(1.0)) + }) + + It("Records duration histogram", func() { + validator.Register("osac.tests.v1.TestTargetLocalReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return &ResolvedRef{ID: "id-1", Tenant: tenant, Project: project, Name: name}, nil + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + LocalTarget: testsv1.TestTargetLocalReference_builder{ + Name: "target", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + return "response", nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + Expect(err).ToNot(HaveOccurred()) + + families, err := registry.Gather() + Expect(err).ToNot(HaveOccurred()) + var found bool + for _, f := range families { + if f.GetName() == "osac_reference_validation_duration_seconds" { + found = true + Expect(f.GetMetric()).ToNot(BeEmpty()) + Expect(f.GetMetric()[0].GetHistogram().GetSampleCount()).To(BeNumerically(">", 0)) + } + } + Expect(found).To(BeTrue()) + }) + + It("Works without metrics registerer", func() { + noMetricsValidator, err := NewReferenceValidator(). + SetLogger(logger). + Build() + Expect(err).ToNot(HaveOccurred()) + + noMetricsValidator.Register("osac.tests.v1.TestTargetLocalReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return &ResolvedRef{ID: "id-1", Tenant: tenant, Project: project, Name: name}, nil + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + LocalTarget: testsv1.TestTargetLocalReference_builder{ + Name: "target", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + return "response", nil + } + + response, err := noMetricsValidator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(response).To(Equal("response")) + }) + }) +}) + +// discoveredRef holds information about a discovered reference field for test assertions. +type discoveredRef struct { + FullName protoreflect.FullName + Path string +} + +// discoverReferenceFields is a test helper that walks a request message and returns +// all discovered reference-typed fields. +func discoverReferenceFields(request any) []discoveredRef { + msg, ok := request.(interface{ ProtoReflect() protoreflect.Message }) + if !ok { + return nil + } + var refs []discoveredRef + collectRefs(msg.ProtoReflect(), "", &refs) + return refs +} + +func collectRefs(msg protoreflect.Message, path string, refs *[]discoveredRef) { + msg.Range(func(fd protoreflect.FieldDescriptor, val protoreflect.Value) bool { + if fd.Kind() != protoreflect.MessageKind { + return true + } + + fieldPath := fd.TextName() + if path != "" { + fieldPath = path + "." + fieldPath + } + + if fd.IsList() { + list := val.List() + for i := 0; i < list.Len(); i++ { + elemMsg := list.Get(i).Message() + fullName := elemMsg.Descriptor().FullName() + if isReferenceType(fullName) { + *refs = append(*refs, discoveredRef{FullName: fullName, Path: fieldPath}) + } else { + collectRefs(elemMsg, fieldPath, refs) + } + } + return true + } + + subMsg := val.Message() + fullName := subMsg.Descriptor().FullName() + if isReferenceType(fullName) { + *refs = append(*refs, discoveredRef{FullName: fullName, Path: fieldPath}) + } else { + collectRefs(subMsg, fieldPath, refs) + } + return true + }) +} + +// notFoundError implements the error interface for testing not-found scenarios. +type notFoundError struct { + name string +} + +func (e *notFoundError) Error() string { + return "not found: " + e.name +} + +func (e *notFoundError) IsNotFound() bool { + return true +} + +// counterValue retrieves the value of a counter metric from a Prometheus registry. +func counterValue(registry *prometheus.Registry, name, resourceType, result string) float64 { + families, err := registry.Gather() + ExpectWithOffset(1, err).ToNot(HaveOccurred()) + for _, f := range families { + if f.GetName() != name { + continue + } + for _, m := range f.GetMetric() { + labels := m.GetLabel() + if matchLabels(labels, resourceType, result) { + return m.GetCounter().GetValue() + } + } + } + return 0 +} + +func matchLabels(labels []*dto.LabelPair, resourceType, result string) bool { + var gotType, gotResult bool + for _, l := range labels { + if l.GetName() == "resource_type" && l.GetValue() == resourceType { + gotType = true + } + if l.GetName() == "result" && l.GetValue() == result { + gotResult = true + } + } + return gotType && gotResult +} diff --git a/internal/references/references_suite_test.go b/internal/references/references_suite_test.go new file mode 100644 index 000000000..815f56075 --- /dev/null +++ b/internal/references/references_suite_test.go @@ -0,0 +1,36 @@ +/* +Copyright (c) 2025 Red Hat, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +package references + +import ( + "log/slog" + "os" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var logger *slog.Logger + +func TestReferences(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "References Suite") +} + +var _ = BeforeSuite(func() { + logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelDebug, + })) +}) From aca291eb91de161c39c3675c960dbb1035851c8e Mon Sep 17 00:00:00 2001 From: Haim Tayrie Date: Sun, 26 Jul 2026 13:01:21 +0300 Subject: [PATCH 03/12] OSAC-3082: fix ineffassign lint warning in metrics registration Extract registerOrReuse generic helper to avoid assigning nil to err that is immediately overwritten by the next Register call. Assisted-by: Claude Code Signed-off-by: Haim Tayrie --- internal/references/reference_validator.go | 43 +++++++++++----------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/internal/references/reference_validator.go b/internal/references/reference_validator.go index 533f1e882..e1d130d28 100644 --- a/internal/references/reference_validator.go +++ b/internal/references/reference_validator.go @@ -95,44 +95,28 @@ func (b *ReferenceValidatorBuilder) Build() (result *ReferenceValidator, err err } if b.registerer != nil { - validationTotal := prometheus.NewCounterVec( + result.validationTotal, err = registerOrReuse(b.registerer, prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "osac_reference_validation_total", Help: "Total number of reference validations performed.", }, []string{"resource_type", "result"}, - ) - err = b.registerer.Register(validationTotal) + )) if err != nil { - var registered prometheus.AlreadyRegisteredError - if errors.As(err, ®istered) { - validationTotal = registered.ExistingCollector.(*prometheus.CounterVec) - err = nil - } else { - return - } + return } - result.validationTotal = validationTotal - validationDuration := prometheus.NewHistogramVec( + result.validationDurati, err = registerOrReuse(b.registerer, prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "osac_reference_validation_duration_seconds", Help: "Duration of individual reference validation lookups in seconds.", Buckets: []float64{0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0}, }, []string{"resource_type"}, - ) - err = b.registerer.Register(validationDuration) + )) if err != nil { - var registered prometheus.AlreadyRegisteredError - if errors.As(err, ®istered) { - validationDuration = registered.ExistingCollector.(*prometheus.HistogramVec) - err = nil - } else { - return - } + return } - result.validationDurati = validationDuration } return @@ -472,3 +456,18 @@ func identifier(id, name string) string { } return id } + +// registerOrReuse registers a Prometheus collector and returns it. If the collector is already +// registered, it returns the existing one. +func registerOrReuse[C prometheus.Collector](registerer prometheus.Registerer, c C) (C, error) { + err := registerer.Register(c) + if err != nil { + var registered prometheus.AlreadyRegisteredError + if errors.As(err, ®istered) { + return registered.ExistingCollector.(C), nil + } + var zero C + return zero, err + } + return c, nil +} From 8b8c252b9266d847f8c1e60f483a6da847209cbc Mon Sep 17 00:00:00 2001 From: Haim Tayrie Date: Sun, 26 Jul 2026 14:02:26 +0300 Subject: [PATCH 04/12] OSAC-3082: address validation review findings Add nil check for id/name fields in reference types, handle map fields defensively, fix slice aliasing in walkMessage, rename truncated field name, and improve empty identifier fallback. Assisted-by: Claude Code Signed-off-by: Haim Tayrie --- internal/references/reference_validator.go | 29 +++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/internal/references/reference_validator.go b/internal/references/reference_validator.go index e1d130d28..2ae940c50 100644 --- a/internal/references/reference_validator.go +++ b/internal/references/reference_validator.go @@ -58,10 +58,10 @@ type ReferenceValidatorBuilder struct { // types ending with "Reference" or "LocalReference"), validates them against registered lookup // functions, and mutates the request to auto-populate missing id or name fields. type ReferenceValidator struct { - logger *slog.Logger - registry map[protoreflect.FullName]ReferenceLookupFunc - validationTotal *prometheus.CounterVec - validationDurati *prometheus.HistogramVec + logger *slog.Logger + registry map[protoreflect.FullName]ReferenceLookupFunc + validationTotal *prometheus.CounterVec + validationDuration *prometheus.HistogramVec } // NewReferenceValidator creates a builder that can then be used to configure and create a new @@ -106,7 +106,7 @@ func (b *ReferenceValidatorBuilder) Build() (result *ReferenceValidator, err err return } - result.validationDurati, err = registerOrReuse(b.registerer, prometheus.NewHistogramVec( + result.validationDuration, err = registerOrReuse(b.registerer, prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "osac_reference_validation_duration_seconds", Help: "Duration of individual reference validation lookups in seconds.", @@ -197,7 +197,11 @@ func (v *ReferenceValidator) walkMessage(ctx context.Context, msg protoreflect.M return true } - fieldPath := append(path, string(fd.Name())) + fieldPath := append(append([]string{}, path...), string(fd.Name())) + + if fd.IsMap() { + return true + } if fd.IsList() { list := val.List() @@ -270,6 +274,10 @@ func (v *ReferenceValidator) resolveAndMutate(ctx context.Context, refMsg protor idField := refMsg.Descriptor().Fields().ByName("id") nameField := refMsg.Descriptor().Fields().ByName("name") + if idField == nil || nameField == nil { + return grpcstatus.Errorf(grpccodes.Internal, + "reference type %q does not have required id/name fields", fullName) + } id := refMsg.Get(idField).String() name := refMsg.Get(nameField).String() @@ -352,8 +360,8 @@ func (v *ReferenceValidator) recordResult(resourceType, result string) { } func (v *ReferenceValidator) recordDuration(resourceType string, d time.Duration) { - if v.validationDurati != nil { - v.validationDurati.With(prometheus.Labels{ + if v.validationDuration != nil { + v.validationDuration.With(prometheus.Labels{ "resource_type": resourceType, }).Observe(d.Seconds()) } @@ -454,7 +462,10 @@ func identifier(id, name string) string { if name != "" { return name } - return id + if id != "" { + return id + } + return "(empty reference)" } // registerOrReuse registers a Prometheus collector and returns it. If the collector is already From b9c403d16fa307ab4a5a957df4d7be495efe488a Mon Sep 17 00:00:00 2001 From: Haim Tayrie Date: Sun, 26 Jul 2026 16:06:10 +0300 Subject: [PATCH 05/12] OSAC-3082: address cross-cutting review findings Use context-aware logging (ErrorContext/WarnContext/DebugContext) to propagate request-scoped metadata. Restructure Build() to only assign result after all metric registrations succeed. Redact internal error details from gRPC client responses. Assisted-by: Claude Code Signed-off-by: Haim Tayrie --- internal/references/reference_validator.go | 28 +++++++++++-------- .../references/reference_validator_test.go | 2 +- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/internal/references/reference_validator.go b/internal/references/reference_validator.go index 2ae940c50..45dc94c82 100644 --- a/internal/references/reference_validator.go +++ b/internal/references/reference_validator.go @@ -89,13 +89,11 @@ func (b *ReferenceValidatorBuilder) Build() (result *ReferenceValidator, err err return } - result = &ReferenceValidator{ - logger: b.logger, - registry: make(map[protoreflect.FullName]ReferenceLookupFunc), - } + var validationTotal *prometheus.CounterVec + var validationDuration *prometheus.HistogramVec if b.registerer != nil { - result.validationTotal, err = registerOrReuse(b.registerer, prometheus.NewCounterVec( + validationTotal, err = registerOrReuse(b.registerer, prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "osac_reference_validation_total", Help: "Total number of reference validations performed.", @@ -106,7 +104,7 @@ func (b *ReferenceValidatorBuilder) Build() (result *ReferenceValidator, err err return } - result.validationDuration, err = registerOrReuse(b.registerer, prometheus.NewHistogramVec( + validationDuration, err = registerOrReuse(b.registerer, prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "osac_reference_validation_duration_seconds", Help: "Duration of individual reference validation lookups in seconds.", @@ -119,6 +117,12 @@ func (b *ReferenceValidatorBuilder) Build() (result *ReferenceValidator, err err } } + result = &ReferenceValidator{ + logger: b.logger, + registry: make(map[protoreflect.FullName]ReferenceLookupFunc), + validationTotal: validationTotal, + validationDuration: validationDuration, + } return } @@ -262,7 +266,7 @@ func (v *ReferenceValidator) resolveAndMutate(ctx context.Context, refMsg protor lookupFunc, ok := v.registry[fullName] if !ok { - v.logger.Error("No lookup registered for reference type", + v.logger.ErrorContext(ctx, "No lookup registered for reference type", "reference_type", string(fullName), "field_path", strings.Join(path, "."), ) @@ -297,7 +301,7 @@ func (v *ReferenceValidator) resolveAndMutate(ctx context.Context, refMsg protor Field: fieldPath, Description: desc, }) - v.logger.Warn("Reference not found", + v.logger.WarnContext(ctx, "Reference not found", "field_path", fieldPath, "reference_type", resourceType, "identifier", identifier(id, name), @@ -307,14 +311,14 @@ func (v *ReferenceValidator) resolveAndMutate(ctx context.Context, refMsg protor v.recordResult(resourceType, "invalid") return nil } - v.logger.Error("Reference lookup failed", + v.logger.ErrorContext(ctx, "Reference lookup failed", "field_path", fieldPath, "reference_type", resourceType, "error", err, ) v.recordResult(resourceType, "error") return grpcstatus.Errorf(grpccodes.Internal, - "failed to resolve reference at %s: %v", fieldPath, err) + "internal error resolving reference at %s", fieldPath) } if id == "" && resolved.ID != "" { @@ -330,7 +334,7 @@ func (v *ReferenceValidator) resolveAndMutate(ctx context.Context, refMsg protor Field: fieldPath, Description: desc, }) - v.logger.Warn("Reference id/name mismatch", + v.logger.WarnContext(ctx, "Reference id/name mismatch", "field_path", fieldPath, "reference_type", resourceType, "id", id, @@ -340,7 +344,7 @@ func (v *ReferenceValidator) resolveAndMutate(ctx context.Context, refMsg protor return nil } - v.logger.Debug("Reference validated", + v.logger.DebugContext(ctx, "Reference validated", "field_path", fieldPath, "reference_type", resourceType, "resolved_id", resolved.ID, diff --git a/internal/references/reference_validator_test.go b/internal/references/reference_validator_test.go index 11f014a2e..c4d43de31 100644 --- a/internal/references/reference_validator_test.go +++ b/internal/references/reference_validator_test.go @@ -981,7 +981,7 @@ var _ = Describe("Reference validator", func() { status, ok := grpcstatus.FromError(err) Expect(ok).To(BeTrue()) Expect(status.Code()).To(Equal(grpccodes.Internal)) - Expect(status.Message()).To(ContainSubstring("database connection refused")) + Expect(status.Message()).To(ContainSubstring("internal error resolving reference")) }) It("Returns InvalidArgument for id/name mismatch", func() { From ce657d1e129e00e9a0766536afb2d8be937ec187 Mon Sep 17 00:00:00 2001 From: Haim Tayrie Date: Sun, 26 Jul 2026 20:00:28 +0300 Subject: [PATCH 06/12] OSAC-3082: address review feedback - Sort violations by field path for deterministic error messages - Add warning log when skipping map fields with message values - Short-circuit empty references (both id and name blank) - Guard against nil resolved ref from lookup functions - Use ! prefix on customer-scoped slog keys for redaction - Use comma-ok type assertion in registerOrReuse - Add matching id+name happy path test - Assert status codes in metric test specs Assisted-by: Claude Code Signed-off-by: Haim Tayrie --- internal/references/reference_validator.go | 45 +++++++++++++-- .../references/reference_validator_test.go | 57 ++++++++++++++++++- 2 files changed, 94 insertions(+), 8 deletions(-) diff --git a/internal/references/reference_validator.go b/internal/references/reference_validator.go index 45dc94c82..727055eb3 100644 --- a/internal/references/reference_validator.go +++ b/internal/references/reference_validator.go @@ -18,6 +18,7 @@ import ( "errors" "fmt" "log/slog" + "sort" "strings" "time" @@ -170,6 +171,9 @@ func (v *ReferenceValidator) validate(ctx context.Context, request any) error { } if len(violations) > 0 { + sort.Slice(violations, func(i, j int) bool { + return violations[i].GetField() < violations[j].GetField() + }) descriptions := make([]string, len(violations)) for i, fv := range violations { descriptions[i] = fmt.Sprintf("%s: %s", fv.GetField(), fv.GetDescription()) @@ -204,6 +208,11 @@ func (v *ReferenceValidator) walkMessage(ctx context.Context, msg protoreflect.M fieldPath := append(append([]string{}, path...), string(fd.Name())) if fd.IsMap() { + if fd.MapValue().Kind() == protoreflect.MessageKind { + v.logger.WarnContext(ctx, "Skipping map field with message values — map reference validation not yet supported", + "field_path", strings.Join(fieldPath, "."), + ) + } return true } @@ -288,6 +297,15 @@ func (v *ReferenceValidator) resolveAndMutate(ctx context.Context, refMsg protor resourceType := string(fullName.Name()) fieldPath := strings.Join(path, ".") + if id == "" && name == "" { + *violations = append(*violations, &errdetails.BadRequest_FieldViolation{ + Field: fieldPath, + Description: fmt.Sprintf("%s reference must specify id or name", shortName(fullName)), + }) + v.recordResult(resourceType, "invalid") + return nil + } + start := time.Now() resolved, err := lookupFunc(ctx, refTenant, refProject, id, name) elapsed := time.Since(start) @@ -304,9 +322,9 @@ func (v *ReferenceValidator) resolveAndMutate(ctx context.Context, refMsg protor v.logger.WarnContext(ctx, "Reference not found", "field_path", fieldPath, "reference_type", resourceType, - "identifier", identifier(id, name), - "tenant", refTenant, - "project", refProject, + "!identifier", identifier(id, name), + "!tenant", refTenant, + "!project", refProject, ) v.recordResult(resourceType, "invalid") return nil @@ -321,6 +339,16 @@ func (v *ReferenceValidator) resolveAndMutate(ctx context.Context, refMsg protor "internal error resolving reference at %s", fieldPath) } + if resolved == nil { + v.logger.ErrorContext(ctx, "Reference lookup returned no result and no error", + "field_path", fieldPath, + "reference_type", resourceType, + ) + v.recordResult(resourceType, "error") + return grpcstatus.Errorf(grpccodes.Internal, + "internal error resolving reference at %s", fieldPath) + } + if id == "" && resolved.ID != "" { refMsg.Set(idField, protoreflect.ValueOfString(resolved.ID)) } @@ -337,8 +365,8 @@ func (v *ReferenceValidator) resolveAndMutate(ctx context.Context, refMsg protor v.logger.WarnContext(ctx, "Reference id/name mismatch", "field_path", fieldPath, "reference_type", resourceType, - "id", id, - "name", name, + "!id", id, + "!name", name, ) v.recordResult(resourceType, "invalid") return nil @@ -479,7 +507,12 @@ func registerOrReuse[C prometheus.Collector](registerer prometheus.Registerer, c if err != nil { var registered prometheus.AlreadyRegisteredError if errors.As(err, ®istered) { - return registered.ExistingCollector.(C), nil + existing, ok := registered.ExistingCollector.(C) + if !ok { + var zero C + return zero, fmt.Errorf("existing collector has unexpected type %T", registered.ExistingCollector) + } + return existing, nil } var zero C return zero, err diff --git a/internal/references/reference_validator_test.go b/internal/references/reference_validator_test.go index c4d43de31..97c816715 100644 --- a/internal/references/reference_validator_test.go +++ b/internal/references/reference_validator_test.go @@ -984,6 +984,49 @@ var _ = Describe("Reference validator", func() { Expect(status.Message()).To(ContainSubstring("internal error resolving reference")) }) + It("Passes through when id and name both match resolved values", func() { + validator.Register("osac.tests.v1.TestTargetLocalReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return &ResolvedRef{ + ID: "my-id", + Name: "my-name", + }, nil + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + LocalTarget: testsv1.TestTargetLocalReference_builder{ + Id: "my-id", + Name: "my-name", + }.Build(), + }.Build(), + }.Build(), + }.Build() + + handlerCalled := false + mockHandler := func(ctx context.Context, req any) (any, error) { + handlerCalled = true + return nil, nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(handlerCalled).To(BeTrue()) + }) + It("Returns InvalidArgument for id/name mismatch", func() { validator.Register("osac.tests.v1.TestTargetLocalReference", func( ctx context.Context, tenant, project, id, name string, @@ -1137,13 +1180,18 @@ var _ = Describe("Reference validator", func() { return nil, nil } - _, _ = validator.UnaryServer( + _, err := validator.UnaryServer( context.Background(), request, &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, mockHandler, ) + Expect(err).To(HaveOccurred()) + status, ok := grpcstatus.FromError(err) + Expect(ok).To(BeTrue()) + Expect(status.Code()).To(Equal(grpccodes.InvalidArgument)) + count := counterValue(registry, "osac_reference_validation_total", "TestTargetReference", "invalid") Expect(count).To(Equal(1.0)) @@ -1175,13 +1223,18 @@ var _ = Describe("Reference validator", func() { return nil, nil } - _, _ = validator.UnaryServer( + _, err := validator.UnaryServer( context.Background(), request, &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, mockHandler, ) + Expect(err).To(HaveOccurred()) + status, ok := grpcstatus.FromError(err) + Expect(ok).To(BeTrue()) + Expect(status.Code()).To(Equal(grpccodes.Internal)) + count := counterValue(registry, "osac_reference_validation_total", "TestTargetReference", "error") Expect(count).To(Equal(1.0)) From 009c0683e0a1df20cd7ca6165f7adc6ff591a2d6 Mon Sep 17 00:00:00 2001 From: Haim Tayrie Date: Mon, 27 Jul 2026 15:53:14 +0300 Subject: [PATCH 07/12] OSAC-3082: add empty reference test case Add test verifying that a reference with neither id nor name returns InvalidArgument with "must specify id or name" and never invokes the lookup function. Assisted-by: Claude Code Signed-off-by: Haim Tayrie --- .../references/reference_validator_test.go | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/internal/references/reference_validator_test.go b/internal/references/reference_validator_test.go index 97c816715..8aceb0fa2 100644 --- a/internal/references/reference_validator_test.go +++ b/internal/references/reference_validator_test.go @@ -839,6 +839,54 @@ var _ = Describe("Reference validator", func() { Expect(err).ToNot(HaveOccurred()) }) + It("Returns InvalidArgument when reference has neither id nor name", func() { + validator.Register("osac.tests.v1.TestTargetReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + Fail("Lookup should not be called for empty reference") + return nil, nil + }) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + Target: testsv1.TestTargetReference_builder{}.Build(), + }.Build(), + }.Build(), + }.Build() + + mockHandler := func(ctx context.Context, req any) (any, error) { + Fail("Handler should not be called for invalid reference") + return nil, nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).To(HaveOccurred()) + status, ok := grpcstatus.FromError(err) + Expect(ok).To(BeTrue()) + Expect(status.Code()).To(Equal(grpccodes.InvalidArgument)) + + details := status.Details() + Expect(details).To(HaveLen(1)) + badRequest, ok := details[0].(*errdetails.BadRequest) + Expect(ok).To(BeTrue()) + Expect(badRequest.GetFieldViolations()).To(HaveLen(1)) + fv := badRequest.GetFieldViolations()[0] + Expect(fv.GetField()).To(Equal("object.spec.target")) + Expect(fv.GetDescription()).To(ContainSubstring("must specify id or name")) + }) + It("Returns InvalidArgument with BadRequest FieldViolation for not-found reference", func() { validator.Register("osac.tests.v1.TestTargetReference", func( ctx context.Context, tenant, project, id, name string, From 48766e8873afdce80ad734647218bab0e900ca82 Mon Sep 17 00:00:00 2001 From: Haim Tayrie Date: Mon, 27 Jul 2026 17:01:49 +0300 Subject: [PATCH 08/12] OSAC-3082: eliminate unnecessary else blocks in walkMessage Replace if/else with early continue/return in walkMessage to reduce nesting and follow Go's happy-path-on-the-left convention. Assisted-by: Claude Code Signed-off-by: Haim Tayrie --- internal/references/reference_validator.go | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/references/reference_validator.go b/internal/references/reference_validator.go index 727055eb3..3cddb8fd2 100644 --- a/internal/references/reference_validator.go +++ b/internal/references/reference_validator.go @@ -231,12 +231,12 @@ func (v *ReferenceValidator) walkMessage(ctx context.Context, msg protoreflect.M internalErr = err return false } - } else { - err := v.walkMessage(ctx, elemMsg, indexedPath, violations, tenant, project) - if err != nil { - internalErr = err - return false - } + continue + } + err := v.walkMessage(ctx, elemMsg, indexedPath, violations, tenant, project) + if err != nil { + internalErr = err + return false } } return true @@ -252,12 +252,12 @@ func (v *ReferenceValidator) walkMessage(ctx context.Context, msg protoreflect.M internalErr = err return false } - } else { - err := v.walkMessage(ctx, subMsg, fieldPath, violations, tenant, project) - if err != nil { - internalErr = err - return false - } + return true + } + err := v.walkMessage(ctx, subMsg, fieldPath, violations, tenant, project) + if err != nil { + internalErr = err + return false } return true From 4096c706c0b8714a9a1f2bbc9afadc2142f8beb8 Mon Sep 17 00:00:00 2001 From: Haim Tayrie Date: Mon, 27 Jul 2026 17:02:59 +0300 Subject: [PATCH 09/12] OSAC-3082: eliminate unnecessary else blocks in test helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same pattern fix in collectRefs test helper — use continue/return instead of else. Assisted-by: Claude Code Signed-off-by: Haim Tayrie --- internal/references/reference_validator_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/references/reference_validator_test.go b/internal/references/reference_validator_test.go index 8aceb0fa2..b311bbf15 100644 --- a/internal/references/reference_validator_test.go +++ b/internal/references/reference_validator_test.go @@ -1415,9 +1415,9 @@ func collectRefs(msg protoreflect.Message, path string, refs *[]discoveredRef) { fullName := elemMsg.Descriptor().FullName() if isReferenceType(fullName) { *refs = append(*refs, discoveredRef{FullName: fullName, Path: fieldPath}) - } else { - collectRefs(elemMsg, fieldPath, refs) + continue } + collectRefs(elemMsg, fieldPath, refs) } return true } @@ -1426,9 +1426,9 @@ func collectRefs(msg protoreflect.Message, path string, refs *[]discoveredRef) { fullName := subMsg.Descriptor().FullName() if isReferenceType(fullName) { *refs = append(*refs, discoveredRef{FullName: fullName, Path: fieldPath}) - } else { - collectRefs(subMsg, fieldPath, refs) + return true } + collectRefs(subMsg, fieldPath, refs) return true }) } From d3b208cc740f3b533c99b12133b911cc2248038c Mon Sep 17 00:00:00 2001 From: Haim Tayrie Date: Mon, 27 Jul 2026 17:21:13 +0300 Subject: [PATCH 10/12] OSAC-3082: address reviewer feedback from danielerez and rccrdpccl - Redact resolved_id/resolved_name with ! prefix in debug log - Add sealed atomic.Bool to prevent Register after first request - Add end-to-end UnaryServer test with complex message exercising nested, repeated, and singular reference patterns Assisted-by: Claude Code Signed-off-by: Haim Tayrie --- internal/references/reference_validator.go | 10 +- .../references/reference_validator_test.go | 110 ++++++++++++++++++ 2 files changed, 118 insertions(+), 2 deletions(-) diff --git a/internal/references/reference_validator.go b/internal/references/reference_validator.go index 3cddb8fd2..406587b1c 100644 --- a/internal/references/reference_validator.go +++ b/internal/references/reference_validator.go @@ -20,6 +20,7 @@ import ( "log/slog" "sort" "strings" + "sync/atomic" "time" "github.com/prometheus/client_golang/prometheus" @@ -61,6 +62,7 @@ type ReferenceValidatorBuilder struct { type ReferenceValidator struct { logger *slog.Logger registry map[protoreflect.FullName]ReferenceLookupFunc + sealed atomic.Bool validationTotal *prometheus.CounterVec validationDuration *prometheus.HistogramVec } @@ -130,12 +132,16 @@ func (b *ReferenceValidatorBuilder) Build() (result *ReferenceValidator, err err // Register associates a reference message type with a lookup function. Must be called before the // gRPC server starts accepting requests. func (v *ReferenceValidator) Register(fullName protoreflect.FullName, lookupFunc ReferenceLookupFunc) { + if v.sealed.Load() { + panic(fmt.Sprintf("Register called after interceptor started serving: %s", fullName)) + } v.registry[fullName] = lookupFunc } // UnaryServer is the unary server interceptor function. func (v *ReferenceValidator) UnaryServer(ctx context.Context, request any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (response any, err error) { + v.sealed.Store(true) if !isCreateOrUpdate(info.FullMethod) { return handler(ctx, request) } @@ -375,8 +381,8 @@ func (v *ReferenceValidator) resolveAndMutate(ctx context.Context, refMsg protor v.logger.DebugContext(ctx, "Reference validated", "field_path", fieldPath, "reference_type", resourceType, - "resolved_id", resolved.ID, - "resolved_name", resolved.Name, + "!resolved_id", resolved.ID, + "!resolved_name", resolved.Name, ) v.recordResult(resourceType, "valid") return nil diff --git a/internal/references/reference_validator_test.go b/internal/references/reference_validator_test.go index b311bbf15..6fb775c06 100644 --- a/internal/references/reference_validator_test.go +++ b/internal/references/reference_validator_test.go @@ -51,6 +51,36 @@ var _ = Describe("Reference validator", func() { }) }) + Describe("Sealed after serving", func() { + It("Panics when Register is called after UnaryServer", func() { + validator, err := NewReferenceValidator(). + SetLogger(logger). + Build() + Expect(err).ToNot(HaveOccurred()) + + validator.Register("osac.tests.v1.TestTargetReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return &ResolvedRef{ID: "id-1", Name: name}, nil + }) + + _, _ = validator.UnaryServer( + context.Background(), + "not-a-proto", + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Get"}, + func(ctx context.Context, req any) (any, error) { return "ok", nil }, + ) + + Expect(func() { + validator.Register("osac.tests.v1.TestOtherTargetLocalReference", func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return nil, nil + }) + }).To(PanicWith(ContainSubstring("Register called after interceptor started serving"))) + }) + }) + Describe("Method filtering", func() { BeforeEach(func() { var err error @@ -1121,6 +1151,86 @@ var _ = Describe("Reference validator", func() { }) }) + Describe("End-to-end with complex message", func() { + BeforeEach(func() { + var err error + validator, err = NewReferenceValidator(). + SetLogger(logger). + Build() + Expect(err).ToNot(HaveOccurred()) + }) + + It("Resolves and mutates all reference patterns through UnaryServer", func() { + lookupFunc := func( + ctx context.Context, tenant, project, id, name string, + ) (*ResolvedRef, error) { + return &ResolvedRef{ + ID: "resolved-" + name, + Name: name, + }, nil + } + validator.Register("osac.tests.v1.TestTargetReference", lookupFunc) + validator.Register("osac.tests.v1.TestTargetLocalReference", lookupFunc) + validator.Register("osac.tests.v1.TestOtherTargetLocalReference", lookupFunc) + + request := testsv1.CreateTestResourceWithRefsRequest_builder{ + Object: testsv1.TestResourceWithRefs_builder{ + Id: "resource-1", + Metadata: testsv1.Metadata_builder{ + Tenant: "tenant-a", + Project: "default", + }.Build(), + Spec: testsv1.TestRefSpec_builder{ + Target: testsv1.TestTargetReference_builder{ + Name: "full-ref", + }.Build(), + LocalTarget: testsv1.TestTargetLocalReference_builder{ + Name: "local-ref", + }.Build(), + Attachment: testsv1.TestRefAttachment_builder{ + Subnet: testsv1.TestTargetLocalReference_builder{ + Name: "nested-subnet", + }.Build(), + SecurityGroups: []*testsv1.TestOtherTargetLocalReference{ + testsv1.TestOtherTargetLocalReference_builder{ + Name: "nested-sg", + }.Build(), + }, + }.Build(), + OtherTargets: []*testsv1.TestOtherTargetLocalReference{ + testsv1.TestOtherTargetLocalReference_builder{ + Name: "repeated-other", + }.Build(), + }, + }.Build(), + }.Build(), + }.Build() + + handlerCalled := false + mockHandler := func(ctx context.Context, req any) (any, error) { + handlerCalled = true + return "response", nil + } + + _, err := validator.UnaryServer( + context.Background(), + request, + &grpc.UnaryServerInfo{FullMethod: "/osac.tests.v1.TestService/Create"}, + mockHandler, + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(handlerCalled).To(BeTrue()) + + spec := request.GetObject().GetSpec() + Expect(spec.GetTarget().GetId()).To(Equal("resolved-full-ref")) + Expect(spec.GetLocalTarget().GetId()).To(Equal("resolved-local-ref")) + Expect(spec.GetAttachment().GetSubnet().GetId()).To(Equal("resolved-nested-subnet")) + Expect(spec.GetAttachment().GetSecurityGroups()[0].GetId()).To(Equal("resolved-nested-sg")) + Expect(spec.GetOtherTargets()[0].GetId()).To(Equal("resolved-repeated-other")) + }) + }) + Describe("Pass-through for non-proto requests", func() { BeforeEach(func() { var err error From eb33bad704796da7cd1b06c9628fad8da8378c77 Mon Sep 17 00:00:00 2001 From: Haim Tayrie Date: Mon, 27 Jul 2026 17:31:07 +0300 Subject: [PATCH 11/12] OSAC-3082: use CompareAndSwap for sealed flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoid redundant atomic writes on every request — CompareAndSwap only writes on the first call, then becomes a read. Assisted-by: Claude Code Signed-off-by: Haim Tayrie --- internal/references/reference_validator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/references/reference_validator.go b/internal/references/reference_validator.go index 406587b1c..65519a903 100644 --- a/internal/references/reference_validator.go +++ b/internal/references/reference_validator.go @@ -141,7 +141,7 @@ func (v *ReferenceValidator) Register(fullName protoreflect.FullName, lookupFunc // UnaryServer is the unary server interceptor function. func (v *ReferenceValidator) UnaryServer(ctx context.Context, request any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (response any, err error) { - v.sealed.Store(true) + v.sealed.CompareAndSwap(false, true) if !isCreateOrUpdate(info.FullMethod) { return handler(ctx, request) } From 663a780a6354803c4842c274ebda90007c613f6b Mon Sep 17 00:00:00 2001 From: Haim Tayrie Date: Tue, 28 Jul 2026 10:40:10 +0300 Subject: [PATCH 12/12] OSAC-3082: guard id/name field kinds in reference validation Check that id and name fields are StringKind via isStringField helper, not just present. Prevents a panic from ValueOfString on a non-string field. Assisted-by: Claude Code Signed-off-by: Haim Tayrie --- internal/references/reference_validator.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/references/reference_validator.go b/internal/references/reference_validator.go index 65519a903..54dbcf31b 100644 --- a/internal/references/reference_validator.go +++ b/internal/references/reference_validator.go @@ -293,9 +293,9 @@ func (v *ReferenceValidator) resolveAndMutate(ctx context.Context, refMsg protor idField := refMsg.Descriptor().Fields().ByName("id") nameField := refMsg.Descriptor().Fields().ByName("name") - if idField == nil || nameField == nil { + if !isStringField(idField) || !isStringField(nameField) { return grpcstatus.Errorf(grpccodes.Internal, - "reference type %q does not have required id/name fields", fullName) + "reference type %q does not have required string id/name fields", fullName) } id := refMsg.Get(idField).String() name := refMsg.Get(nameField).String() @@ -506,6 +506,10 @@ func identifier(id, name string) string { return "(empty reference)" } +func isStringField(fd protoreflect.FieldDescriptor) bool { + return fd != nil && fd.Kind() == protoreflect.StringKind +} + // registerOrReuse registers a Prometheus collector and returns it. If the collector is already // registered, it returns the existing one. func registerOrReuse[C prometheus.Collector](registerer prometheus.Registerer, c C) (C, error) {