I would use generics like
class MyCustomError extends Error {}
abstract class Message {}
class Example1 extends Message {}
class Example2 extends Message {}
abstract class MessageFactory<T> {
abstract fromRawData(data: any): T;
}
class Example1Factory implements MessageFactory<Example1> {
fromRawData(data: any): Example1 {
if (! data?.property1) {
throw new MyCustomError();
} else {
return new Example1(); // You should build it based on data
}
}
}
class Example2Factory implements MessageFactory<Example2> {
fromRawData(data: any): Example2 {
if (! data?.property2) {
throw new MyCustomError();
} else {
return new Example2(); // You should build it based on data
}
}
}
abstract class MyRequest<T> {
private factory: MessageFactory<T>;
constructor(f: MessageFactory<T>) {
this.factory = f;
}
getRequest(data: any): T {
try {
return this.factory.fromRawData(data);
} catch (err) {
if (err instanceof MyCustomError) {
console.error("Message has not been parsed");
}
}
}
}
class Example1Request extends MyRequest<Example1> {}
class Example2Request extends MyRequest<Example2> {}
// Apply same logic for responses
const possible_requests: MyRequest<Message>[] = [new Example1Request(new Example1Factory()), new Example2Request(new Example2Factory)]
const data = {
property2: "yes"
}
possible_requests.forEach((req) => {
console.log(req.getRequest(data));
})
// Logs in error
// Message has not been parsed
// Logs in log
// undefined
// Example2 {}
So you will need a Factory to verify eligibility of your Message to your custom type (Example1 for instance).
Then you might build a Request which will use this factory and try to parse your message. It also handles errors in case message is not valid.
Finally you list each requests in an array and ask it to parse your message, you will then only have to filter undefined ones :)
Your Response should be built the same way, listing them and applying the requests to get appropriate response !
Please let me know if somehting is unclear, or if you don't understand how to use reponses based on Message's type
EDIT 1 @hhearts
abstract class MyRequest<T> {
private factory: MessageFactory<T>;
constructor(f: MessageFactory<T>) {
this.factory = f;
}
getRequest(data: any): MyRequest<T> {
try {
this.factory.fromRawData(data); // Checks factory might build request's message
return this; // We return request instead of message
} catch (err) {
if (err instanceof MyCustomError) {
//console.error("Message has not been parsed");
}
}
}
}
const message_types: string[] = possible_requests.map((req) => req.getRequest(data)).filter(r => !!r).map(s => s.constructor.name);
console.log(message_types);
// ["Example2Request"]
A Response<Example2> is easy to setup once you determine the Request's type (or Message's one)
For instance working with a singleton of Example2ResponseFactory in Example2Request enabling to build Example2Response from Example2Request based on Example2 (message) :)
EDIT 2
As types are not kept after compilation there is no way to get the "type" however you can access the typename through
variable.constructor.name
Class.name
And compare them, asthey are string you might compare it with an easy
typename.replace("Response/Request", ""); // Use it to compare request and response :)