TL;DR: Consider Copy and Paste
You are dealing with two different interfaces in your scenario:
- An interface defined by an external source, which service A depends on
- An internal interface for the communication between A and B through RabbitMQ
Both interfaces might include message specifications and the message specifications might currently be exactly the same, but they are still separate interfaces and this is very important.
What will happen when the external message format changes?
If the internal message format automatically changes as well, as soon as the external message format changes, then it doesn't matter how you implement validation, your services will be tightly coupled anyways.
You achieve loose coupling by allowing both interfaces to change independently. Let the external API change, but keep in internal API as it is. Service A now needs to translate from one message format to another, but service B doesn't need to worry about such details, it doesn't care how service A got to its message.
The moment this happens, you will realise that using the same shared library for the validation of both messages won't work, since they are not the same interface at all.
At this point you might challenge whether the two interfaces should look the same to begin with. Are all data points in the external message relevant to service B? Is the data formatted in a way that is most convenient for B?
What would happen if you switch external data providers, would you still want to keep the same interface?
Consider designing the interface between A and B independent of your external provide, depending on your system's needs instead.
Assuming that you indeed start with two interfaces that are the same (but might diverge in the future), you still don't want to do the same implementation twice.
This is a scenario where copy and paste, despite its bad reputation, is a valid solution.