-
Notifications
You must be signed in to change notification settings - Fork 11
/
typed_resource.go
139 lines (110 loc) · 4.03 KB
/
typed_resource.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package typed generic based resource definition.
package typed
import (
"fmt"
"reflect"
"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/resource/meta/spec"
"github.com/cosi-project/runtime/pkg/resource/protobuf"
)
// DeepCopyable requires a spec to have DeepCopy method which will be used during Resource copy.
type DeepCopyable[T any] interface {
DeepCopy() T
}
// Extension is a phantom type which acts as info supplier for ResourceDefinition
// methods. It intantianed only during ResourceDefinition calls, so it should never contain any data which
// survives those calls. It can be used to provide additional method Make(*resource.Metadata, *T) any, which is used for
// custom interfaces. Look at LookupExtension and Maker for more details.
type Extension interface {
ResourceDefinition() spec.ResourceDefinitionSpec
}
// Resource provides a generic base implementation for resource.Resource.
type Resource[T DeepCopyable[T], E Extension] struct {
spec T
md resource.Metadata
}
// Metadata implements Resource.
func (t *Resource[T, E]) Metadata() *resource.Metadata {
return &t.md
}
// Spec implements resource.Resource.
func (t *Resource[T, E]) Spec() any {
return &t.spec
}
// TypedSpec returns a pointer to spec field.
func (t *Resource[T, E]) TypedSpec() *T {
return &t.spec
}
// DeepCopy returns a deep copy of Resource.
func (t *Resource[T, E]) DeepCopy() resource.Resource { //nolint:ireturn
return &Resource[T, E]{t.spec.DeepCopy(), t.md}
}
// ResourceDefinition implements spec.ResourceDefinitionProvider interface.
func (t *Resource[T, E]) ResourceDefinition() spec.ResourceDefinitionSpec {
var zero E
return zero.ResourceDefinition()
}
// Maker is an interface which can be implemented by resource extension to provide custom interfaces.
type Maker[T any] interface {
Make(*resource.Metadata, *T) any
}
func (t *Resource[T, E]) makeRD() (any, bool) {
var e E
maker, ok := any(e).(Maker[T])
if ok {
return maker.Make(t.Metadata(), t.TypedSpec()), true
}
return nil, false
}
// LookupExtension looks up for the [Maker] interface on the resource extension.
// It will call Make method on it, if it has one, passing [resource.Metadata] and typed spec as arguments,
// before returning the result of Make call and attempting to cast it to the provided type parameter I.
// Type Parameter I should be an interface with a single method.
//
// The common usage is to define `Make(...) any` on extension type, and return custom type which implements I.
func LookupExtension[I any](res resource.Resource) (I, bool) {
var zero I
typ := reflect.TypeOf((*I)(nil)).Elem()
if typ.Kind() != reflect.Interface {
panic("can only be used with interface types")
}
if typ.NumMethod() != 1 {
panic("can only be used with interfaces with a single method")
}
maker, ok := res.(interface{ makeRD() (any, bool) })
if !ok {
return zero, false
}
v, ok := maker.makeRD()
if !ok {
return zero, false
}
iface, ok := v.(I)
if !ok {
return zero, false
}
return iface, true
}
// UnmarshalProto implements protobuf.Unmarshaler interface in a generic way.
//
// UnmarshalProto requires that the spec implements the protobuf.ProtoUnmarshaller interface.
func (t *Resource[T, E]) UnmarshalProto(md *resource.Metadata, protoBytes []byte) error {
// Go doesn't allow to do type assertion on a generic type T, so use intermediate any value.
protoSpec, ok := any(&t.spec).(protobuf.ProtoUnmarshaler)
if !ok {
return fmt.Errorf("spec does not implement ProtoUnmarshaler")
}
if err := protoSpec.UnmarshalProto(protoBytes); err != nil {
return err
}
t.md = *md
return nil
}
// NewResource initializes and returns a new instance of Resource with typed spec field.
func NewResource[T DeepCopyable[T], E Extension](md resource.Metadata, spec T) *Resource[T, E] {
result := Resource[T, E]{md: md, spec: spec}
return &result
}