Skip to content

Commit ef958e8

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

File tree

2 files changed

+322
-23
lines changed

2 files changed

+322
-23
lines changed

src/serializer.ts

Lines changed: 137 additions & 21 deletions
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+
}
5461
}
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;
68+
}
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,105 @@ 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+
public static registerModel(gvk: GroupVersionKind, serializer: Serializer) {
152+
const gv = gvString(gvk);
153+
const kinds = (this.modelRegistry[gv] ??= {});
154+
if (kinds[gvk.kind]) {
155+
throw new Error(`Kind ${gvk.kind} of ${gv} is already defined`);
156+
}
157+
kinds[gvk.kind] = serializer;
158+
}
159+
160+
public static clearModelRegistry(): void {
161+
this.modelRegistry = {};
162+
}
163+
164+
private static getSerializerForObject(obj: unknown): undefined | Serializer {
165+
if (!isKubernetesObject(obj)) {
166+
return undefined;
167+
}
168+
const gvk = gvkFromObject(obj);
169+
return ObjectSerializer.modelRegistry[gvString(gvk)]?.[gvk.kind];
170+
}
171+
172+
public static serialize(data: any, type: string, format: string = ''): any {
173+
const serializer = ObjectSerializer.getSerializerForObject(data);
174+
if (serializer) {
175+
return serializer.serialize(data, type, format);
176+
}
177+
if (data instanceof KubernetesObject) {
178+
return data.serialize();
179+
}
180+
181+
const obj = InternalSerializer.serialize(data, type, format);
83182
if (obj !== data) {
84-
// the serializer knows the type and already deserialized it.
85183
return obj;
86184
}
87185

88186
if (!isKubernetesObject(data)) {
89187
return obj;
90188
}
91189

92-
const instance = new KubernetesObject();
190+
const instance: Record<string, any> = {};
93191
for (const attributeType of KubernetesObject.attributeTypeMap) {
94192
const value = data[attributeType.baseName];
95193
if (value !== undefined) {
96-
instance[attributeType.name] = InternalSerializer.deserialize(
194+
instance[attributeType.name] = InternalSerializer.serialize(
97195
data[attributeType.baseName],
98196
attributeType.type,
99197
attributeType.format,
@@ -109,4 +207,22 @@ export class ObjectSerializer extends InternalSerializer {
109207
}
110208
return instance;
111209
}
210+
211+
public static deserialize(data: any, type: string, format: string = ''): any {
212+
const serializer = ObjectSerializer.getSerializerForObject(data);
213+
if (serializer) {
214+
return serializer.deserialize(data, type, format);
215+
}
216+
const obj = InternalSerializer.deserialize(data, type, format);
217+
if (obj !== data) {
218+
// the serializer knows the type and already deserialized it.
219+
return obj;
220+
}
221+
222+
if (!isKubernetesObject(data)) {
223+
return obj;
224+
}
225+
226+
return KubernetesObject.fromUnknown(data);
227+
}
112228
}

0 commit comments

Comments
 (0)