8000 feat: add model registry to object serializer · kubernetes-client/javascript@ee60f29 · GitHub
[go: up one dir, main page]

Skip to content

Commit ee60f29

Browse files
committed
feat: add model registry to object serializer
1 parent 0b6e716 commit ee60f29

File tree

2 files changed

+331
-23
lines changed

2 files changed

+331
-23
lines changed

src/serializer.ts

+146-21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import { ObjectSerializer as InternalSerializer, V1ObjectMeta } from './gen/models/ObjectSerializer.js';
22

3+
type KubernetesObjectHeader = {
4+
apiVersion: string;
5+
kind: string;
6+
};
7+
8+
const isKubernetesObject = (data: unknown): data is KubernetesObjectHeader =>
9+
!!data && typeof data === 'object' && 'apiVersion' in data && 'kind' in data;
10+
311
type AttributeType = {
412
name: string;
513
baseName: string;
@@ -38,30 +46,38 @@ class KubernetesObject {
3846
format: '',
3947
},
4048
];
41-
}
42-
43-
const isKubernetesObject = (data: unknown): boolean =>
44-
!!data && typeof data === 'object' && 'apiVersion' in data && 'kind' in data;
4549

46-
/**
47-
* Wraps the ObjectSerializer to support custom resources and generic Kubernetes objects.
48-
*/
49-
export class ObjectSerializer extends InternalSerializer {
50-
public static serialize(data: any, type: string, format: string = ''): any {
51-
const obj = InternalSerializer.serialize(data, type, format);
52-
if (obj !== data) {
53-
return obj;
50+
public serialize(): any {
51+
const instance: Record<string, any> = {};
52+
for (const attributeType of KubernetesObject.attributeTypeMap) {
53+
const value = this[attributeType.baseName];
54+
if (value !== undefined) {
55+
instance[attributeType.name] = InternalSerializer.serialize(
56+
this[attributeType.baseName],
57+
attributeType.type,
58+
attributeType.format,
59+
);
60+
}
61+
}
62+
// add all unknown properties as is.
63+
for (const [key, value] of Object.entries(this)) {
64+
if (KubernetesObject.attributeTypeMap.find((t) => t.name === key)) {
65+
continue;
66+
}
67+
instance[key] = value;
5468
}
69+
return instance;
70+
}
5571

72+
public static fromUnknown(data: unknown): KubernetesObject {
5673
if (!isKubernetesObject(data)) {
57-
return obj;
74+
throw new Error(`Unable to deseriliaze non-Kubernetes object ${data}.`);
5875
}
59-
60-
const instance: Record<string, any> = {};
76+
const instance = new KubernetesObject();
6177
for (const attributeType of KubernetesObject.attributeTypeMap) {
6278
const value = data[attributeType.baseName];
6379
if (value !== undefined) {
64-
instance[attributeType.name] = InternalSerializer.serialize(
80+
instance[attributeType.name] = InternalSerializer.deserialize(
6581
data[attributeType.baseName],
6682
attributeType.type,
6783
attributeType.format,
@@ -77,23 +93,114 @@ export class ObjectSerializer extends InternalSerializer {
7793
}
7894
return instance;
7995
}
96+
}
8097

81-
public static deserialize(data: any, type: string, format: string = ''): any {
82-
const obj = InternalSerializer.deserialize(data, type, format);
98+
export interface Serializer {
99+
serialize(data: any, type: string, format?: string): any;
100+
deserialize(data: any, type: string, format?): any;
101+
}
102+
103+
export type GroupVersionKind = {
104+
group: string;
105+
version: string;
106+
kind: string;
107+
};
108+
109+
type ModelRegistry = {
110+
[gv: string]: {
111+
[kind: string]: Serializer;
112+
};
113+
};
114+
115+
const gvString = ({ group, version }: GroupVersionKind): string => [group, version].join('/');
116+
117+
const gvkFromObject = (obj: KubernetesObjectHeader): GroupVersionKind => {
118+
const [g, v] = obj.apiVersion.split('/');
119+
return {
120+
kind: obj.kind,
121+
group: v ? g : '',
122+
version: v ? v : g,
123+
};
124+
};
125+
126+
/**
127+
* Default serializer that uses the KubernetesObject to serialize and deserialize
128+
* any object using only the minimum required attributes.
129+
*/
130+
export const defaultSerializer: Serializer = {
131+
serialize: (data: any, type: string, format?: string): any => {
132+
if (data instanceof KubernetesObject) {
133+
return data.serialize();
134+
}
135+
return KubernetesObject.fromUnknown(data).serialize();
136+
},
137+
deserialize: (data: any, type: string, format?): any => {
138+
return KubernetesObject.fromUnknown(data);
139+
},
140+
};
141+
142+
/**
143+
* Wraps the ObjectSerializer to support custom resources and generic Kubernetes objects.
144+
*
145+
* CustomResources that are unknown to the ObjectSerializer can be registered
146+
* by using ObjectSerializer.registerModel().
147+
*/
148+
export class ObjectSerializer extends InternalSerializer {
149+
private static modelRegistry: ModelRegistry = {};
150+
151+
/**
152+
* Adds a dedicated seriliazer for a Kubernetes resource.
153+
* Every resource is uniquly identified using its group, version and kind.
154+
* @param gvk
155+
* @param serializer
156+
*/
157+
public static registerModel(gvk: GroupVersionKind, serializer: Serializer) {
158+
const gv = gvString(gvk);
159+
const kinds = (this.modelRegistry[gv] ??= {});
160+
if (kinds[gvk.kind]) {
161+
throw new Error(`Kind ${gvk.kind} of ${gv} is already defined`);
162+
}
163+
kinds[gvk.kind] = serializer;
164+
}
165+
166+
/**
167+
* Removes all registered models from the registry.
168+
*/
169+
public static clearModelRegistry(): void {
170+
this.modelRegistry = {};
171+
}
172+
173+
private static getSerializerForObject(obj: unknown): undefined | Serializer {
174+
if (!isKubernetesObject(obj)) {
175+
return undefined;
176+
}
177+
const gvk = gvkFromObject(obj);
178+
return ObjectSerializer.modelRegistry[gvString(gvk)]?.[gvk.kind];
179+
}
180+
181+
public static serialize(data: any, type: string, format: string = ''): any {
182+
const serializer = ObjectSerializer.getSerializerForObject(data);
183+
if (serializer) {
184+
return serializer.serialize(data, type, format);
185+
}
186+
if (data instanceof KubernetesObject) {
187+
return data.serialize();
188+
}
189+
190+
const obj = InternalSerializer.serialize(data, type, format);
83191
if (obj !== data) {
84-
// the serializer knows the type and already deserialized it.
85192
return obj;
86193
}
87194

88195
if (!isKubernetesObject(data)) {
89196
return obj;
90197
}
91198

92-
const instance = new KubernetesObject();
199+
const instance: Record<string, any> = {};
93200
for (const attributeType of KubernetesObject.attributeTypeMap) {
94201
const value = data[attributeType.baseName];
95202
if (value !== undefined) {
96-
instance[attributeType.name] = InternalSerializer.deserialize(
203+
instance[attributeType.name] = InternalSerializer.serialize(
97204
data[attributeType.baseName],
98205
attributeType.type,
99206
attributeType.format,
@@ -109,4 +216,22 @@ export class ObjectSerializer extends InternalSerializer {
109216
}
110217
return instance;
111218
}
219+
220+
public static deserialize(data: any, type: string, format: string = ''): any {
221+
const serializer = ObjectSerializer.getSerializerForObject(data);
222+
if (serializer) {
223+
return serializer.deserialize(data, type, format);
224+
}
225+
const obj = InternalSerializer.deserialize(data, type, format);
226+
if (obj !== data) {
227+
// the serializer knows the type and already deserialized it.
228+
return obj;
229+
}
230+
231+
if (!isKubernetesObject(data)) {
232+
return obj;
233+
}
234+
235+
return KubernetesObject.fromUnknown(data);
236+
}
112237
}

0 commit comments

Comments
 (0)
0