diff --git a/src/main/java/org/eclipse/uprotocol/transport/builder/UMessageBuilder.java b/src/main/java/org/eclipse/uprotocol/transport/builder/UMessageBuilder.java index 5f73da0d..27e81d4c 100644 --- a/src/main/java/org/eclipse/uprotocol/transport/builder/UMessageBuilder.java +++ b/src/main/java/org/eclipse/uprotocol/transport/builder/UMessageBuilder.java @@ -25,10 +25,9 @@ import com.google.protobuf.ByteString; import org.eclipse.uprotocol.communication.UPayload; -import org.eclipse.uprotocol.transport.validate.UAttributesValidator; -import org.eclipse.uprotocol.uri.validator.UriValidator; +import org.eclipse.uprotocol.transport.validator.UAttributesValidator; import org.eclipse.uprotocol.uuid.factory.UuidFactory; -import org.eclipse.uprotocol.uuid.validate.UuidValidator; +import org.eclipse.uprotocol.uuid.factory.UuidUtils; import java.util.Objects; import java.util.Optional; @@ -36,11 +35,12 @@ /** * Builder for easy construction of the UAttributes object. */ -public class UMessageBuilder { +public final class UMessageBuilder { private final UUri source; - private final UUID id; private final UMessageType type; + + private UUID id; private UPriority priority; private Integer ttl; private String token; @@ -54,109 +54,132 @@ public class UMessageBuilder { private ByteString payload; /** - * Construct a UMessageBuilder for a publish message. - * - * @param source The topic the message is published to (a.k.a Source address). - * @return Returns the UMessageBuilder with the configured priority. + * Gets a builder for a publish message. + * + * A publish message is used to notify all interested consumers of an event that has occurred. + * Consumers usually indicate their interest by subscribing to a particular topic. + * + * @param source The topic to publish the message to. + * @return The builder. + * @throws NullPointerException if the source is {@code null}. */ public static UMessageBuilder publish(UUri source) { Objects.requireNonNull(source, "source cannot be null."); - // Validate the source - if (!UriValidator.isTopic(source)) { - throw new IllegalArgumentException("source must be a topic."); - } - return new UMessageBuilder(source, UuidFactory.Factories.UPROTOCOL.factory().create(), - UMessageType.UMESSAGE_TYPE_PUBLISH); + return new UMessageBuilder( + source, + UuidFactory.Factories.UPROTOCOL.factory().create(), + UMessageType.UMESSAGE_TYPE_PUBLISH); } /** - * Construct a UMessageBuilder for a notification message. - * - * @param source The topic the message is published to (a.k.a Source address). - * @param sink The destination address for the notification (who will receive - * the notification). - * @return Returns the UMessageBuilder with the configured priority and sink. + * Gets a builder for a notification message. + * + * A notification is used to inform a specific consumer about an event that has occurred. + * + * @param source The component that the notification originates from. + * @param sink The URI identifying the destination to send the notification to. + * @return The builder. + * @throws NullPointerException if the source or sink is {@code null}. */ public static UMessageBuilder notification(UUri source, UUri sink) { Objects.requireNonNull(source, "source cannot be null."); Objects.requireNonNull(sink, "sink cannot be null."); - - // Validate the source and sink - if (!UriValidator.isTopic(source) || !UriValidator.isRpcResponse(sink)) { - throw new IllegalArgumentException("source must be a topic and sink must be a response."); - } - return new UMessageBuilder(source, UuidFactory.Factories.UPROTOCOL.factory().create(), - UMessageType.UMESSAGE_TYPE_NOTIFICATION).withSink(sink); + return new UMessageBuilder( + source, + UuidFactory.Factories.UPROTOCOL.factory().create(), + UMessageType.UMESSAGE_TYPE_NOTIFICATION) + .withSink(sink); } /** - * Construct a UMessageBuilder for a request message. - * - * @param source Source address for the message (address of the client sending - * the request message). - * @param sink The method that is being requested (a.k.a. destination - * address). - * @param ttl The time to live in milliseconds. - * @return Returns the UMessageBuilder with the configured priority, sink and - * ttl. + * Gets a builder for an RPC request message. + *
+ * A request message is used to invoke a service's method with some input data, expecting + * the service to reply with a response message which is correlated by means of its + * {@link UAttributes#getReqid() request ID}. + *
+ * The builder will be initialized with {@link UPriority#UPRIORITY_CS4}. + * + * @param source The URI that the sender of the request expects the response message at. + * @param sink The URI identifying the method to invoke. + * @param ttl The number of milliseconds after which the request should no longer be processed + * by the target service. + * @return The builder. + * @throws NullPointerException if the source or sink is {@code null}. + * @throws IllegalArgumentException if the ttl is less than or equal to 0. */ - public static UMessageBuilder request(UUri source, UUri sink, Integer ttl) { + public static UMessageBuilder request(UUri source, UUri sink, int ttl) { Objects.requireNonNull(source, "source cannot be null."); - Objects.requireNonNull(ttl, "ttl cannot be null."); Objects.requireNonNull(sink, "sink cannot be null."); - // Validate the source and sink - if (!UriValidator.isRpcMethod(sink) || !UriValidator.isRpcResponse(source)) { - throw new IllegalArgumentException("source must be an rpc method and sink must be a request."); - } - - // Validate the ttl if (ttl <= 0) { throw new IllegalArgumentException("ttl must be greater than 0."); } - return new UMessageBuilder(source, UuidFactory.Factories.UPROTOCOL.factory().create(), - UMessageType.UMESSAGE_TYPE_REQUEST).withTtl(ttl).withSink(sink); + return new UMessageBuilder( + source, + UuidFactory.Factories.UPROTOCOL.factory().create(), + UMessageType.UMESSAGE_TYPE_REQUEST) + .withSink(sink) + .withTtl(ttl) + .withPriority(UPriority.UPRIORITY_CS4); } /** - * Construct a UMessageBuilder for a response message. - * - * @param source The source address of the method that was requested - * @param sink The destination of the client thatsend the request. - * @param reqid The original request UUID used to correlate the response to the - * request. - * @return Returns the UMessageBuilder with the configured priority, sink and - * reqid. + * Gets a builder for an RPC response message. + *
+ * A response message is used to send the outcome of processing a request message + * to the original sender of the request message. + *
+ * The builder will be initialized with {@link UPriority#UPRIORITY_CS4}. + * + * # Arguments + * + * * `reply_to_address` - The URI that the sender of the request expects to receive the response message at. + * * `request_id` - The identifier of the request that this is the response to. + * * `invoked_method` - The URI identifying the method that has been invoked and which the created message is + * the outcome of. + * + * @param source The URI identifying the method that has been invoked and which the created message is + * the outcome of. + * @param sink The URI that the sender of the request expects to receive the response message at. + * @param reqid The identifier of the request that this is the response to. + * @return The builder. + * @throws NullPointerException if any of the parameters are {@code null}. */ public static UMessageBuilder response(UUri source, UUri sink, UUID reqid) { Objects.requireNonNull(source, "source cannot be null."); Objects.requireNonNull(sink, "sink cannot be null for Response."); Objects.requireNonNull(reqid, "reqid cannot be null."); - // Validate the source and sink - if (!UriValidator.isRpcResponse(sink) || !UriValidator.isRpcMethod(source)) { - throw new IllegalArgumentException("sink must be a response and source must be an rpc method."); - } - - try { - UuidValidator.Validators.UPROTOCOL.validator().validate(reqid); - } catch (ValidationException e) { - throw new IllegalArgumentException("reqid is not a valid UUID.", e); - } - - return new UMessageBuilder(source, UuidFactory.Factories.UPROTOCOL.factory().create(), - UMessageType.UMESSAGE_TYPE_RESPONSE).withSink(sink).withReqId(reqid); + return new UMessageBuilder( + source, + UuidFactory.Factories.UPROTOCOL.factory().create(), + UMessageType.UMESSAGE_TYPE_RESPONSE) + .withSink(sink) + .withReqId(reqid) + .withPriority(UPriority.UPRIORITY_CS4); } /** - * Construct a UMessageBuilder for a response message using an existing request. + * Gets a builder for creating an RPC response message in reply to a request. + *
+ * A response message is used to send the outcome of processing a request message + * to the original sender of the request message. + *
+ * The builder will be initialized with values from the given request attributes. + * + * # Arguments + * + * * `request_attributes` - The attributes from the request message. The response message + * builder will be initialized with the corresponding attribute values. * - * @param request The original request {@code UAttributes} used to correlate the - * response to the request. - * @return Returns the UMessageBuilder with the configured source, sink, - * priority, and reqid. + * @param request The attributes from the request message. The response message + * builder will be initialized with the corresponding attribute values. + * @return The builder. + * @throws NullPointerException if request is {@code null}. + * @throws IllegalArgumentException if the request does not contain valid request attributes. */ public static UMessageBuilder response(UAttributes request) { Objects.requireNonNull(request, "request cannot be null."); @@ -172,14 +195,14 @@ public static UMessageBuilder response(UAttributes request) { request.getSink(), UuidFactory.Factories.UPROTOCOL.factory().create(), UMessageType.UMESSAGE_TYPE_RESPONSE) - .withPriority(request.getPriority()) - .withSink(request.getSource()) - .withReqId(request.getId()); + .withPriority(request.getPriority()) + .withSink(request.getSource()) + .withReqId(request.getId()) + .withTtl(request.getTtl()); } /** - * Construct the UMessageBuilder with the configurations that are required for - * every payload transport. + * Creates a builder for attribute values required for all types of messages. * * @param source Source address of the message. * @param id Unique identifier for the message. @@ -193,88 +216,135 @@ private UMessageBuilder(UUri source, UUID id, UMessageType type) { } /** - * Add the time to live in milliseconds. + * Sets the message's identifier. + * + * Every message must have an identifier. If this method is not used, an identifier will be + * generated and set on the message when one of the build methods is invoked. * - * @param ttl the time to live in milliseconds. - * @return Returns the UMessageBuilder with the configured ttl. + * @param id The custom message ID. + * @return The builder with the custom message ID. + * @throws NullPointerException if the id is {@code null}. + * @throws IllegalArgumentException if the id is not a {@link UuidUtils#isUProtocol(UUID) valid uProtocol UUID}. */ - public UMessageBuilder withTtl(Integer ttl) { + public UMessageBuilder withMessageId(UUID id) { + Objects.requireNonNull(id, "id cannot be null."); + if (!UuidUtils.isUProtocol(id)) { + throw new IllegalArgumentException("id must be a valid uProtocol UUID."); + } + this.id = id; + return this; + } + + /** + * Sets the message's time-to-live. + * + * @param ttl The time-to-live in milliseconds. + * @return The builder with the configured ttl. + * @throws IllegalArgumentException if the ttl is negative. + */ + public UMessageBuilder withTtl(int ttl) { + if (ttl < 0) { + throw new IllegalArgumentException("TTL must be a non-negative integer."); + } this.ttl = ttl; return this; } /** - * Add the authorization token used for TAP. + * Sets the message's authorization token used for TAP. * - * @param token the authorization token used for TAP. - * @return Returns the UMessageBuilder with the configured token. + * @param token The token. + * @return The builder with the configured token. + * @throws NullPointerException if the token is {@code null}. + * @throws IllegalStateException if the message is not an RPC request message. */ public UMessageBuilder withToken(String token) { + // [impl->dsn~up-attributes-request-token~1] + Objects.requireNonNull(token, "token cannot be null."); + if (this.type != UMessageType.UMESSAGE_TYPE_REQUEST) { + throw new IllegalStateException("Token can only be set for RPC request messages."); + } this.token = token; return this; } /** - * Add the priority of the message. - * - * @param priority the priority of the message. - * @return Returns the UMessageBuilder with the configured priority. + * Sets the priority of the message. + *
+ * If not set explicitly, the priority will be {@link UPriority#UPRIORITY_UNSPECIFIED}.
+ *
+ * @param priority The priority to be used for sending the message.
+ * @return The builder with the configured priority.
+ * @throws NullPointerException if the priority is {@code null}.
+ * @throws IllegalArgumentException if the builder is used for creating an RPC message
+ * but the priority is less than {@link UPriority#UPRIORITY_CS4}.
*/
public UMessageBuilder withPriority(UPriority priority) {
+ // [impl->dsn~up-attributes-request-priority~1]
+ Objects.requireNonNull(priority, "priority cannot be null.");
+ if (priority.getNumber() < UPriority.UPRIORITY_CS4_VALUE &&
+ (this.type == UMessageType.UMESSAGE_TYPE_REQUEST || this.type == UMessageType.UMESSAGE_TYPE_RESPONSE)) {
+ throw new IllegalArgumentException("priority must be at least CS4 for RPC messages");
+ }
this.priority = priority;
return this;
}
/**
- * Add the permission level of the message.
+ * Sets the message's permission level.
*
- * @param plevel the permission level of the message.
- * @return Returns the UMessageBuilder with the configured plevel.
+ * @param plevel The level.
+ * @return The builder with the configured permission level.
+ * @throws IllegalArgumentException if the permission level is less than 0.
+ * @throws IllegalStateException if the message is not an RPC request message.
*/
- public UMessageBuilder withPermissionLevel(Integer plevel) {
+ public UMessageBuilder withPermissionLevel(int plevel) {
+ // [impl->dsn~up-attributes-permission-level~1]
+ if (plevel < 0) {
+ throw new IllegalArgumentException("Permission level must be greater than or equal to 0.");
+ }
+ if (this.type != UMessageType.UMESSAGE_TYPE_REQUEST) {
+ throw new IllegalStateException("Permission level can only be set for RPC request messages.");
+ }
this.plevel = plevel;
return this;
}
/**
- * Add the traceprent.
+ * Sets the identifier of the W3C Trace Context to convey in the message.
*
- * @param traceparent the trace parent.
- * @return Returns the UMessageBuilder with the configured traceparent.
+ * @param traceparent The identifier.
+ * @return The builder with the configured traceparent.
+ * @throws NullPointerException if the traceparent is {@code null}.
*/
public UMessageBuilder withTraceparent(String traceparent) {
+ // [impl->dsn~up-attributes-traceparent~1]
+ Objects.requireNonNull(traceparent, "traceparent cannot be null.");
this.traceparent = traceparent;
return this;
}
/**
- * Add the communication status of the message.
+ * Sets the message's communication status.
*
- * @param commstatus the communication status of the message.
- * @return Returns the UMessageBuilder with the configured commstatus.
+ * @param commstatus The status.
+ * @return The builder with the configured commstatus.
+ * @throws IllegalStateException if the message is not an RPC response message.
*/
public UMessageBuilder withCommStatus(UCode commstatus) {
+ Objects.requireNonNull(commstatus, "commstatus cannot be null.");
+ if (this.type != UMessageType.UMESSAGE_TYPE_RESPONSE) {
+ throw new IllegalStateException("Communication status can only be set for RPC response messages.");
+ }
this.commstatus = commstatus;
return this;
}
- /**
- * Add the request ID.
- *
- * @param reqid the request ID.
- * @return Returns the UMessageBuilder with the configured reqid.
- */
private UMessageBuilder withReqId(UUID reqid) {
this.reqid = reqid;
return this;
}
- /**
- * Add the explicit destination URI.
- *
- * @param sink the explicit destination URI.
- * @return Returns the UMessageBuilder with the configured sink.
- */
private UMessageBuilder withSink(UUri sink) {
this.sink = sink;
return this;
@@ -285,56 +355,49 @@ private UMessageBuilder withSink(UUri sink) {
*
* @param payload The payload to be packed into the message.
* @return Returns the UMessage with the configured payload.
+ * @throws NullPointerException if the payload is {@code null}.
*/
public UMessage build(UPayload payload) {
- if (payload != null ) {
- this.format = payload.format();
- this.payload = payload.data();
- }
+ Objects.requireNonNull(payload, "payload cannot be null.");
+ this.format = payload.format();
+ this.payload = payload.data();
return build();
}
/**
- * Construct the UMessage from the builder.
+ * Creates the message based on the builder's state.
+ *
+ * # Errors
+ *
+ * If the properties set on the builder do not represent a consistent set of [`UAttributes`],
+ * a [`UMessageError::AttributesValidationError`] is returned.
*
- * @return Returns a constructed
+ * @return A message ready to be sent using a transport implementation.
+ * @throws ValidationException if the properties set on the builder do not represent a
+ * consistent set of attributes as determined by {@link UAttributesValidator#validate(UAttributes)}.
*/
public UMessage build() {
- UMessage.Builder messageBuilder = UMessage.newBuilder();
-
UAttributes.Builder attributesBuilder = UAttributes.newBuilder()
.setSource(source)
.setId(id)
.setType(type);
- Optional
+ * UAttributes contain a message's metadata like message ID, type,
+ * priority, time-to-live, security tokens and more.
+ *
+ * Each message contains some standard attributes like ID and type as well as
+ * some attributes that are specific to the given type of message, such as
+ * the client's permission level in an RPC request message or a status code in
+ * an RPC response message.
+ *
+ * {@code UAttributesValidator} is a base class that contains the functionality
+ * shared by all type specific validators.
+ */
+public abstract class UAttributesValidator {
+
+ /**
+ * Gets a validator that can be used to check a given set of attributes..
+ *
+ * @param attributes The attributes to get a validator for.
+ * @return A validator matching the type of message that the attributes
+ * belong to.
+ */
+ public static UAttributesValidator getValidator(UAttributes attributes) {
+ return getValidator(attributes.getType());
+ }
+
+ /**
+ * Gets a validator that can be used to check attributes of a given type of message.
+ *
+ * @param type The type of message to get a validator for.
+ * @return A validator matching the type of message.
+ */
+ public static UAttributesValidator getValidator(UMessageType type) {
+ switch (type) {
+ case UMESSAGE_TYPE_RESPONSE:
+ return Validators.RESPONSE.validator();
+ case UMESSAGE_TYPE_REQUEST:
+ return Validators.REQUEST.validator();
+ case UMESSAGE_TYPE_NOTIFICATION:
+ return Validators.NOTIFICATION.validator();
+ default:
+ return Validators.PUBLISH.validator();
+ }
+ }
+
+ /**
+ * Verifies that a set of attributes contains a valid message ID.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the message ID is invalid.
+ */
+ public final void validateId(UAttributes attributes) {
+ if (!attributes.hasId()) {
+ throw new ValidationException("Missing message ID");
+ }
+ if (!UuidUtils.isUProtocol(attributes.getId())) {
+ throw new ValidationException("Attributes must contain valid uProtocol UUID in id property");
+ }
+ }
+
+ /**
+ * Verifies that this validator is appropriate for a set of attributes.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes are not of the type returned by
+ * {@link #messageType()}.
+ */
+ public final void validateType(UAttributes attributes) {
+ if (attributes.getType() != messageType()) {
+ throw new ValidationException(String.format(
+ "Invalid message type [%s], expected [%s]",
+ attributes.getType().name(), messageType().name()));
+ }
+ }
+
+ /**
+ * Checks if a given set of attributes contains a valid priority.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the priority contained in the attributes is {@link UPriority#UNRECOGNIZED}.
+ */
+ public final void validatePriority(UAttributes attributes) {
+ if (attributes.getPriority() == UPriority.UNRECOGNIZED) {
+ throw new ValidationException(String.format(
+ "Invalid UPriority [%s]", attributes.getPriority().name()));
+ }
+ }
+
+ /**
+ * Verifies that a set of attributes contains a priority that is appropriate for an RPC request message.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the priority contained in the attributes is not at
+ * least {@link UPriority#UPRIORITY_CS4}.
+ */
+ public final void validateRpcPriority(UAttributes attributes) {
+ if (attributes.getPriority().getNumber() < UPriority.UPRIORITY_CS4_VALUE) {
+ throw new ValidationException(String.format(
+ "RPC Request message must have at least priority [%s] but has [%s]",
+ UPriority.UPRIORITY_CS4.name(), attributes.getPriority().name()));
+ }
+ }
+
+ /**
+ * Checks if a given set of attributes belong to a message that has expired.
+ *
+ * The message is considered expired if the message's creation time plus the
+ * duration indicated by the ttl attribute is before the current
+ * instant in time.
+ *
+ * @param attributes The attributes to check.
+ * @return {@code true} if the given attributes should be considered expired.
+ */
+ public final boolean isExpired(UAttributes attributes) {
+ final int ttl = attributes.getTtl();
+ return ttl > 0 && UuidUtils.isExpired(attributes.getId(), ttl);
+ }
+
+ /*
+ * Gets the type of message that this validator can be used with.
+ *
+ * @return The message type.
+ */
+ abstract UMessageType messageType();
+
+ /**
+ * Verifies that a set of attributes contains a valid source URI.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes do not contain a valid source URI.
+ */
+ public abstract void validateSource(UAttributes attributes);
+
+ /**
+ * Verifies that a set of attributes contains a valid sink URI.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes do not contain a valid sink URI.
+ */
+ public abstract void validateSink(UAttributes attributes);
+
+
+ /**
+ * Checks if a given set of attributes complies with the rules specified for
+ * the type of message they describe.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes are not consistent with the rules
+ * specified for the message type.
+ */
+ public abstract void validate(UAttributes attributes);
+
+ /**
+ * Validates the time-to-live configuration.
+ *
+ * If the UAttributes does not contain a time to live then the ValidationResult is ok.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes contain a negative TTL.
+ */
+ public void validateTtl(UAttributes attributes) {
+ if (!attributes.hasTtl()) {
+ return;
+ }
+ int ttl = attributes.getTtl();
+ if (ttl < 0) {
+ throw new ValidationException(String.format("TTL must be a non-negative integer [%s]", ttl));
+ }
+ }
+
+ /**
+ * Validate the permissionLevel for the default case. If the UAttributes does
+ * not contain a permission level then
+ * the ValidationResult is ok.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes contain a negative permission level.
+ */
+ public final void validatePermissionLevel(UAttributes attributes) {
+ if (!attributes.hasPermissionLevel()) {
+ return;
+ }
+ final var level = attributes.getPermissionLevel();
+ if (level < 0) {
+ throw new ValidationException(
+ String.format("Permission level must be a non-negative integer [%d]", level));
+ }
+ }
+
+ /**
+ * Validators for the message types defined by uProtocol.
+ */
+ public enum Validators {
+ PUBLISH(new Publish()),
+ REQUEST(new Request()),
+ RESPONSE(new Response()),
+ NOTIFICATION(new Notification());
+
+ private final UAttributesValidator uattributesValidator;
+
+ Validators(UAttributesValidator uattributesValidator) {
+ this.uattributesValidator = uattributesValidator;
+ }
+
+ /**
+ * Gets the validator to use for checking attributes.
+ *
+ * @return The validator.
+ */
+ public UAttributesValidator validator() {
+ return uattributesValidator;
+ }
+ }
+
+ /**
+ * Validates attributes describing a Publish message.
+ */
+ private static final class Publish extends UAttributesValidator {
+
+ @Override
+ protected UMessageType messageType() {
+ return UMessageType.UMESSAGE_TYPE_PUBLISH;
+ }
+
+ /**
+ * Verifies that attributes for a publish message contain a valid source URI.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes do not contain a source URI,
+ * or if the source URI contains any wildcards, or if the source URI has an
+ * invalid resource ID.
+ */
+ @Override
+ public void validateSource(UAttributes attributes) {
+ // [impl->dsn~up-attributes-publish-source~1]
+ if (!attributes.hasSource()) {
+ throw new ValidationException("Attributes for a publish message must contain a source URI");
+ }
+ final var source = attributes.getSource();
+ if (UriValidator.hasWildcard(source)) {
+ throw new ValidationException("Source URI must not contain wildcards");
+ }
+ if (!UriValidator.isTopic(source)) {
+ throw new ValidationException("Source is not a valid topic URI");
+ }
+ }
+
+ /**
+ * Verifies that attributes for a publish message do not contain a sink URI.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the sink attribute contains any URI.
+ */
+ @Override
+ public void validateSink(UAttributes attributes) {
+ if (attributes.hasSink()) {
+ throw new ValidationException("Attributes for a publish message must not contain a sink URI");
+ }
+ }
+
+ @Override
+ public void validate(UAttributes attributes) {
+ final var errors = ValidationUtils.collectErrors(attributes,
+ this::validateType,
+ this::validateId,
+ this::validateSource,
+ this::validateSink,
+ this::validateTtl,
+ this::validatePriority
+ );
+ if (!errors.isEmpty()) {
+ throw new ValidationException(errors);
+ }
+ }
+ }
+
+ /**
+ * Validates attributes describing a Notification message.
+ */
+ private static final class Notification extends UAttributesValidator {
+
+ @Override
+ protected UMessageType messageType() {
+ return UMessageType.UMESSAGE_TYPE_NOTIFICATION;
+ }
+
+ /**
+ * Verifies that attributes for a notification message contain a valid source URI.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes do not contain a source URI,
+ * or if the source URI contains any wildcards, or if the source URI has an invalid
+ * resource ID.
+ */
+ @Override
+ public void validateSource(UAttributes attributes) {
+ // [impl->dsn~up-attributes-notification-source~1]
+ if (!attributes.hasSource()) {
+ throw new ValidationException("Attributes for a notification message must contain a source URI");
+ }
+ final var source = attributes.getSource();
+ if (UriValidator.hasWildcard(source)) {
+ throw new ValidationException("Source URI must not contain wildcards");
+ }
+ if (!UriValidator.isTopic(source)) {
+ throw new ValidationException("Source is not a valid topic URI");
+ }
+ }
+
+ /**
+ * Verifies that attributes for a notification message contain a sink URI.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes do not contain a sink URI,
+ * or if the sink URI contains any wildcards, or if the sink URI has a resource
+ * ID != 0.
+ */
+ @Override
+ public void validateSink(UAttributes attributes) {
+ // [impl->dsn~up-attributes-notification-sink~1]
+ if (!attributes.hasSink()) {
+ throw new ValidationException("Attributes for a notification message must contain a sink URI");
+ }
+ final var sink = attributes.getSink();
+ if (UriValidator.hasWildcard(sink)) {
+ throw new ValidationException("Sink URI must not contain wildcards");
+ }
+ if (!UriValidator.isNotificationDestination(sink)) {
+ throw new ValidationException("Sink's resource ID must be 0");
+ }
+ }
+
+ @Override
+ public void validate(UAttributes attributes) {
+ final var errors = ValidationUtils.collectErrors(attributes,
+ this::validateType,
+ this::validateId,
+ this::validateSource,
+ this::validateSink,
+ this::validateTtl,
+ this::validatePriority
+ );
+ if (!errors.isEmpty()) {
+ throw new ValidationException(errors);
+ }
+ }
+ }
+
+ /**
+ Validates attributes describing an RPC Request message.
+ */
+ private static final class Request extends UAttributesValidator {
+
+ @Override
+ protected UMessageType messageType() {
+ return UMessageType.UMESSAGE_TYPE_REQUEST;
+ }
+
+ /**
+ * Verifies that attributes for a message representing an RPC request contain a reply-to-address.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes do not contain a reply-to-address,
+ * or if the reply-to-address contains any wildcards, or if the reply-to-address has a
+ * resource ID != 0.
+ */
+ @Override
+ public void validateSource(UAttributes attributes) {
+ // [impl->dsn~up-attributes-request-source~1]
+ if (!attributes.hasSource()) {
+ throw new ValidationException("""
+ Attributes for a request message must contain a reply-to address in the source property
+ """);
+ }
+ final var source = attributes.getSource();
+ if (UriValidator.hasWildcard(source)) {
+ throw new ValidationException("Source URI must not contain wildcards");
+ }
+ if (!UriValidator.isRpcResponse(source)) {
+ throw new ValidationException("Source is not a valid reply-to address");
+ }
+ }
+
+ /**
+ * Verifies that attributes for a message representing an RPC request indicate the method to invoke.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes do not contain a method-to-invoke,
+ * or if the method-to-invoke contains any wildcards, or if the method-to-invoke
+ * has an invalid resource ID.
+ */
+ @Override
+ public void validateSink(UAttributes attributes) {
+ // [impl->dsn~up-attributes-request-sink~1]
+ if (!attributes.hasSink()) {
+ throw new ValidationException("""
+ Attributes for a request message must contain a method-to-invoke in the sink property
+ """);
+ }
+ final var sink = attributes.getSink();
+ if (UriValidator.hasWildcard(sink)) {
+ throw new ValidationException("Sink URI must not contain wildcards");
+ }
+ if (!UriValidator.isRpcMethod(sink)) {
+ throw new ValidationException("Sink is not a valid method-to-invoke");
+ }
+ }
+
+ /**
+ * Verifies that a set of attributes representing an RPC request contain a valid time-to-live.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes do not contain a time-to-live,
+ * or if the time-to-live is <= 0.
+ */
+ @Override
+ public void validateTtl(UAttributes attributes) {
+ if (!attributes.hasTtl()) {
+ throw new ValidationException("RPC request message must contain a TTL");
+ }
+ int ttl = attributes.getTtl();
+ if (ttl <= 0) {
+ throw new ValidationException(String.format(
+ "RPC request message's TTL must be a positive integer [%d]",
+ ttl));
+ }
+ }
+
+ @Override
+ public void validate(UAttributes attributes) {
+ final var errors = ValidationUtils.collectErrors(attributes,
+ this::validateType,
+ this::validateId,
+ this::validateSource,
+ this::validateSink,
+ this::validateTtl,
+ this::validateRpcPriority,
+ this::validatePermissionLevel
+ );
+ if (!errors.isEmpty()) {
+ throw new ValidationException(errors);
+ }
+ }
+ }
+
+ /**
+ * Implements validations for UAttributes that define a message that is meant
+ * for an RPC response.
+ */
+ private static final class Response extends UAttributesValidator {
+
+ @Override
+ protected UMessageType messageType() {
+ return UMessageType.UMESSAGE_TYPE_RESPONSE;
+ }
+
+ /**
+ * Verifies that attributes for a message representing an RPC response contain the invoked method.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes do not contain the invoked method,
+ * or if the invoked method contains any wildcards, or if the invoked method has an invalid resource ID.
+ */
+ @Override
+ public void validateSource(UAttributes attributes) {
+ // [impl->dsn~up-attributes-response-source~1]
+ if (!attributes.hasSource()) {
+ throw new ValidationException("""
+ Attributes for a response message must contain the invoked method in the source property
+ """);
+ }
+ final var source = attributes.getSource();
+ if (UriValidator.hasWildcard(source)) {
+ throw new ValidationException("Source URI must not contain wildcards");
+ }
+ if (!UriValidator.isRpcMethod(source)) {
+ throw new ValidationException("Source is not a valid method-to-invoke");
+ }
+ }
+
+ /**
+ * Verifies that attributes for a message representing an RPC response contain a valid
+ * reply-to-address.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes do not contain a reply-to-address,
+ * or if the reply-to-address contains any wildcards, or if the reply-to-address
+ * has a resource ID != 0.
+ */
+ @Override
+ public void validateSink(UAttributes attributes) {
+ // [impl->dsn~up-attributes-response-sink~1]
+ if (!attributes.hasSink()) {
+ throw new ValidationException("""
+ Attributes for a response message must contain a reply-to address in the sink property
+ """);
+ }
+ final var sink = attributes.getSink();
+ if (UriValidator.hasWildcard(sink)) {
+ throw new ValidationException("Sink URI must not contain wildcards");
+ }
+ if (!UriValidator.isRpcResponse(sink)) {
+ throw new ValidationException("Sink is not a valid reply-to address");
+ }
+ }
+
+ /**
+ * Verifies that the attributes contain a valid request ID.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes do not contain a request ID,
+ * or if the request ID is not a valid uProtocol UUID.
+ */
+ public void validateReqId(UAttributes attributes) {
+ if (!attributes.hasReqid()) {
+ throw new ValidationException("RPC response message must contain a request ID");
+ }
+ final var reqid = attributes.getReqid();
+ if (!UuidUtils.isUProtocol(reqid)) {
+ throw new ValidationException("Request ID is not a valid uProtocol UUID");
+ }
+ }
+
+ /**
+ * Verifies that a set of attributes contains a valid communication status.
+ *
+ * @param attributes The attributes to check.
+ * @throws ValidationException if the attributes contain an unsupported status code.
+ */
+ public void validateCommstatus(UAttributes attributes) {
+ if (!attributes.hasCommstatus()) {
+ return;
+ }
+ final var commstatus = attributes.getCommstatus();
+ if (commstatus == UCode.UNRECOGNIZED) {
+ throw new ValidationException("Unsupported communication status");
+ }
+ }
+
+ @Override
+ public void validate(UAttributes attributes) {
+ final var errors = ValidationUtils.collectErrors(attributes,
+ this::validateType,
+ this::validateId,
+ this::validateSource,
+ this::validateSink,
+ this::validateReqId,
+ this::validateTtl,
+ this::validateRpcPriority,
+ this::validateCommstatus
+ );
+ if (!errors.isEmpty()) {
+ throw new ValidationException(errors);
+ }
+ }
+ }
+}
diff --git a/src/test/java/org/eclipse/uprotocol/transport/builder/UMessageBuilderTest.java b/src/test/java/org/eclipse/uprotocol/transport/builder/UMessageBuilderTest.java
index d81b5e6c..b17e97a6 100644
--- a/src/test/java/org/eclipse/uprotocol/transport/builder/UMessageBuilderTest.java
+++ b/src/test/java/org/eclipse/uprotocol/transport/builder/UMessageBuilderTest.java
@@ -12,315 +12,388 @@
*/
package org.eclipse.uprotocol.transport.builder;
+import org.eclipse.uprotocol.v1.UAttributes;
import org.eclipse.uprotocol.v1.UCode;
import org.eclipse.uprotocol.v1.UPriority;
import org.eclipse.uprotocol.v1.UUri;
import org.eclipse.uprotocol.v1.UMessage;
import org.eclipse.uprotocol.v1.UMessageType;
+import org.eclipse.uprotocol.v1.UPayloadFormat;
import org.eclipse.uprotocol.v1.UUID;
+import org.eclipse.uprotocol.communication.UPayload;
+import org.eclipse.uprotocol.transport.validator.UAttributesValidator;
import org.eclipse.uprotocol.uuid.factory.UuidFactory;
-import org.eclipse.uprotocol.v1.UAttributes;
-import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import com.google.protobuf.ByteString;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
-public class UMessageBuilderTest {
-
- @Test
- public void testPublish() {
- UMessage publish = UMessageBuilder.publish(buildTopic()).build();
- assertNotNull(publish);
- assertEquals(UMessageType.UMESSAGE_TYPE_PUBLISH, publish.getAttributes().getType());
- assertEquals(UPriority.UPRIORITY_CS1, publish.getAttributes().getPriority());
- }
-
- @Test
- public void testNotification() {
- UUri sink = buildSink();
- UMessage notification = UMessageBuilder.notification(buildTopic(), sink).build();
- assertNotNull(notification);
- assertEquals(UMessageType.UMESSAGE_TYPE_NOTIFICATION, notification.getAttributes().getType());
- assertEquals(UPriority.UPRIORITY_CS1, notification.getAttributes().getPriority());
- assertEquals(sink, notification.getAttributes().getSink());
- }
+class UMessageBuilderTest {
- @Test
- public void testRequest() {
- UUri sink = buildMethod();
- Integer ttl = 1000;
- UMessage request = UMessageBuilder.request(buildSource(), sink, ttl).build();
- assertNotNull(request);
- assertEquals(UMessageType.UMESSAGE_TYPE_REQUEST, request.getAttributes().getType());
- assertEquals(UPriority.UPRIORITY_CS4, request.getAttributes().getPriority());
- assertEquals(sink, request.getAttributes().getSink());
- assertEquals(ttl, request.getAttributes().getTtl());
- }
-
- @Test
- public void testResponse() {
- UUri sink = buildSink();
- UUID reqId = UuidFactory.Factories.UPROTOCOL.factory().create();
- UMessage response = UMessageBuilder.response(buildMethod(), sink, reqId).build();
- assertNotNull(response);
- assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, response.getAttributes().getType());
- assertEquals(UPriority.UPRIORITY_CS4, response.getAttributes().getPriority());
- assertEquals(sink, response.getAttributes().getSink());
- assertEquals(reqId, response.getAttributes().getReqid());
- }
-
- @Test
- @DisplayName("Test response with existing request")
- public void testResponseWithExistingRequest() {
- UMessage request = UMessageBuilder.request(buildSource(), buildMethod(), 1000).build();
- UMessage response = UMessageBuilder.response(request.getAttributes()).build();
- assertNotNull(response);
- assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, response.getAttributes().getType());
- assertEquals(UPriority.UPRIORITY_CS4, request.getAttributes().getPriority());
- assertEquals(UPriority.UPRIORITY_CS4, response.getAttributes().getPriority());
- assertEquals(request.getAttributes().getSource(), response.getAttributes().getSink());
- assertEquals(request.getAttributes().getSink(), response.getAttributes().getSource());
- assertEquals(request.getAttributes().getId(), response.getAttributes().getReqid());
- }
+ private static final UUri UURI_DEFAULT = UUri.newBuilder()
+ .setUeId(1)
+ .setUeVersionMajor(1)
+ .setResourceId(0)
+ .build();
- @Test
- public void testBuild() {
- UMessageBuilder builder = UMessageBuilder.publish(buildTopic())
- .withToken("test_token")
- .withPermissionLevel(2)
- .withCommStatus(UCode.CANCELLED)
- .withTraceparent("myParents");
- UMessage message = builder.build();
- UAttributes attributes = message.getAttributes();
- assertNotNull(message);
- assertNotNull(attributes);
- assertEquals(UMessageType.UMESSAGE_TYPE_PUBLISH, attributes.getType());
- assertEquals(UPriority.UPRIORITY_CS1, attributes.getPriority());
- assertEquals("test_token", attributes.getToken());
- assertEquals(2, attributes.getPermissionLevel());
- assertEquals(UCode.CANCELLED, attributes.getCommstatus());
- assertEquals("myParents", attributes.getTraceparent());
- }
+ private static final UUri UURI_METHOD = UUri.newBuilder()
+ .setUeId(0x001a_1a5b)
+ .setUeVersionMajor(0x04)
+ .setResourceId(0x7a5f)
+ .build();
+ private static final UUri UURI_TOPIC = UUri.newBuilder()
+ .setUeId(1)
+ .setUeVersionMajor(1)
+ .setResourceId(0x8000)
+ .build();
-
@Test
- @DisplayName("Test building UMessage with empty payload")
- public void testBuildWithAnyPayload() {
- UMessage message = UMessageBuilder.publish(buildTopic())
- .build();
- assertNotNull(message);
- assertFalse(message.hasPayload());
+ void testWithMessageIdPanicsForInvalidUuid() {
+ UUID invalidMessageId = UUID.newBuilder()
+ .setMsb(0x00000000000000abL)
+ .setLsb(0x0000000000018000L)
+ .build();
+ assertThrows(
+ IllegalArgumentException.class, () -> {
+ UMessageBuilder.publish(UURI_TOPIC).withMessageId(invalidMessageId);
+ });
}
-
- @Test
- @DisplayName("Test building response message with the wrong priority value of UPRIORITY_CS3")
- public void testBuildResponseWithWrongPriority() {
- UUri sink = buildSink();
- UUID reqId = UuidFactory.Factories.UPROTOCOL.factory().create();
- UMessage response = UMessageBuilder.response(buildMethod(), sink, reqId)
- .withPriority(UPriority.UPRIORITY_CS3)
- .build();
- assertNotNull(response);
- assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, response.getAttributes().getType());
- assertEquals(UPriority.UPRIORITY_CS4, response.getAttributes().getPriority());
- assertEquals(sink, response.getAttributes().getSink());
- assertEquals(reqId, response.getAttributes().getReqid());
+ @ParameterizedTest
+ @CsvSource(useHeadersInDisplayName = true, textBlock = """
+ permLevel, commStatus, token
+ 5, ,
+ , NOT_FOUND,
+ , , my-token
+ """)
+ void testPublishMessageBuilderRejectsInvalidAttributes(
+ Integer permLevel,
+ UCode commStatus,
+ String token
+ ) {
+ var builder = UMessageBuilder.publish(UURI_TOPIC);
+ assertBuilderPanics(builder, permLevel, commStatus, token);
}
- @Test
- @DisplayName("Test building request message with the wrong priority value of UPRIORITY_CS3")
- public void testBuildRequestWithWrongPriority() {
- UUri sink = buildMethod();
- Integer ttl = 1000;
- UMessage request = UMessageBuilder.request(buildSource(), sink, ttl)
- .withPriority(UPriority.UPRIORITY_CS3)
- .build();
- assertNotNull(request);
- assertEquals(UMessageType.UMESSAGE_TYPE_REQUEST, request.getAttributes().getType());
- assertEquals(UPriority.UPRIORITY_CS4, request.getAttributes().getPriority());
- assertEquals(sink, request.getAttributes().getSink());
- assertEquals(ttl, request.getAttributes().getTtl());
+ @ParameterizedTest
+ @CsvSource(useHeadersInDisplayName = true, textBlock = """
+ permLevel, commStatus, token
+ 5, ,
+ , NOT_FOUND,
+ , , my-token
+ """)
+ void testNotificationMessageBuilderRejectsInvalidAttributes(
+ Integer permLevel,
+ UCode commStatus,
+ String token
+ ) {
+ var builder = UMessageBuilder.notification(UURI_TOPIC, UURI_DEFAULT);
+ assertBuilderPanics(builder, permLevel, commStatus, token);
}
- @Test
- @DisplayName("Test building notification message with the wrong priority value of UPRIORITY_CS0")
- public void testBuildNotificationWithWrongPriority() {
- UUri sink = buildSink();
- UMessage notification = UMessageBuilder.notification(buildTopic(), sink)
- .withPriority(UPriority.UPRIORITY_CS0)
- .build();
- assertNotNull(notification);
- assertEquals(UMessageType.UMESSAGE_TYPE_NOTIFICATION, notification.getAttributes().getType());
- assertEquals(UPriority.UPRIORITY_CS1, notification.getAttributes().getPriority());
- assertEquals(sink, notification.getAttributes().getSink());
+ void assertBuilderPanics(
+ UMessageBuilder builder,
+ Integer permLevel,
+ UCode commStatus,
+ String token
+ ) {
+ if (permLevel != null) {
+ assertThrows(
+ IllegalStateException.class,
+ () -> builder.withPermissionLevel(permLevel)
+ );
+ } else if (commStatus != null) {
+ assertThrows(
+ IllegalStateException.class,
+ () -> builder.withCommStatus(commStatus)
+ );
+ } else if (token != null) {
+ assertThrows(
+ IllegalStateException.class,
+ () -> builder.withToken(token)
+ );
+ }
}
- @Test
- @DisplayName("Test building publish message with the wrong priority value of UPRIORITY_CS0")
- public void testBuildPublishWithWrongPriority() {
- UMessage publish = UMessageBuilder.publish(buildTopic())
- .withPriority(UPriority.UPRIORITY_CS0)
- .build();
- assertNotNull(publish);
- assertEquals(UMessageType.UMESSAGE_TYPE_PUBLISH, publish.getAttributes().getType());
- assertEquals(UPriority.UPRIORITY_CS1, publish.getAttributes().getPriority());
+ @ParameterizedTest
+ @ValueSource(ints = { -1, 0 })
+ void testRequestRejectsInvalidTtl(int ttl) {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> UMessageBuilder.request(UURI_DEFAULT, UURI_METHOD, ttl)
+ );
}
- @Test
- @DisplayName("Test building publish message with the priority value of UPRIORITY_CS4")
- public void testBuildPublishWithPriority() {
- UMessage publish = UMessageBuilder.publish(buildTopic())
- .withPriority(UPriority.UPRIORITY_CS4)
- .build();
- assertNotNull(publish);
- assertEquals(UMessageType.UMESSAGE_TYPE_PUBLISH, publish.getAttributes().getType());
- assertEquals(UPriority.UPRIORITY_CS4, publish.getAttributes().getPriority());
+ @ParameterizedTest
+ @CsvSource(useHeadersInDisplayName = true, textBlock = """
+ permLevel, commStatus, priority
+ -1, ,
+ , NOT_FOUND,
+ , , UPRIORITY_UNSPECIFIED
+ , , UPRIORITY_CS0
+ , , UPRIORITY_CS1
+ , , UPRIORITY_CS2
+ , , UPRIORITY_CS3
+ """)
+ void testRequestMessageBuilderRejectsInvalidAttributes(
+ Integer permLevel,
+ UCode commStatus,
+ UPriority priority
+ ) {
+ var builder = UMessageBuilder.request(UURI_DEFAULT, UURI_METHOD, 5000);
+
+ if (permLevel != null) {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> builder.withPermissionLevel(permLevel)
+ );
+ } else if (commStatus != null) {
+ assertThrows(
+ IllegalStateException.class,
+ () -> builder.withCommStatus(commStatus)
+ );
+ } else if (priority != null) {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> builder.withPriority(priority)
+ );
+ }
}
-
- private UUri buildSink() {
- return UUri.newBuilder().setAuthorityName("vcu.someVin.veh.ultifi.gm.com")
- .setUeId(1)
- .setUeVersionMajor(1)
- .setResourceId(0).build();
+ @ParameterizedTest
+ @CsvSource(useHeadersInDisplayName = true, textBlock = """
+ ttl, permLevel, token, priority
+ -1, , ,
+ , 5, ,
+ , , my-token,
+ , , , UPRIORITY_UNSPECIFIED
+ , , , UPRIORITY_CS0
+ , , , UPRIORITY_CS1
+ , , , UPRIORITY_CS2
+ , , , UPRIORITY_CS3
+ """)
+ void testResponseMessageBuilderRejectsInvalidAttributes(
+ Integer ttl,
+ Integer permLevel,
+ String token,
+ UPriority priority
+ ) {
+ var builder = UMessageBuilder.response(
+ UURI_METHOD,
+ UURI_DEFAULT,
+ UuidFactory.Factories.UPROTOCOL.factory().create());
+
+ if (ttl != null) {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> builder.withTtl(ttl)
+ );
+ } else if (permLevel != null) {
+ assertThrows(
+ IllegalStateException.class,
+ () -> builder.withPermissionLevel(permLevel)
+ );
+ } else if (token != null) {
+ assertThrows(
+ IllegalStateException.class,
+ () -> builder.withToken(token)
+ );
+ } else if (priority != null) {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> builder.withPriority(priority)
+ );
+ }
}
-
@Test
- @DisplayName("Test publish when source is not a valid topic")
- public void testPublishWithInvalidSource() {
- assertThrows(IllegalArgumentException.class,
- () -> UMessageBuilder.publish(buildSource()).build());
+ void testResponseRejectsNonRequestAttributes() {
+ UAttributes attributes = UAttributes.newBuilder()
+ .setType(UMessageType.UMESSAGE_TYPE_PUBLISH)
+ .setId(UuidFactory.Factories.UPROTOCOL.factory().create())
+ .setSource(UURI_TOPIC)
+ .build();
+ UAttributesValidator.getValidator(attributes).validate(attributes);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> UMessageBuilder.response(attributes)
+ );
}
-
@Test
- @DisplayName("Test notification when source is not a valid topic")
- public void testNotificationWithInvalidSource() {
- assertThrows(IllegalArgumentException.class,
- () -> UMessageBuilder.notification(buildSource(), buildSink()).build());
+ void testBuildSupportsRepeatedInvocation() {
+ UMessageBuilder builder = UMessageBuilder.publish(UURI_TOPIC);
+ UMessage messageOne = builder
+ .withMessageId(UuidFactory.Factories.UPROTOCOL.factory().create())
+ .build(UPayload.pack(ByteString.copyFromUtf8("locked"), UPayloadFormat.UPAYLOAD_FORMAT_TEXT));
+ UMessage messageTwo = builder
+ .withMessageId(UuidFactory.Factories.UPROTOCOL.factory().create())
+ .build(UPayload.pack(ByteString.copyFromUtf8("unlocked"), UPayloadFormat.UPAYLOAD_FORMAT_TEXT));
+ assertEquals(messageOne.getAttributes().getType(), messageTwo.getAttributes().getType());
+ assertNotEquals(messageOne.getAttributes().getId(), messageTwo.getAttributes().getId());
+ assertEquals(messageOne.getAttributes().getSource(), messageTwo.getAttributes().getSource());
+ assertNotEquals(messageOne.getPayload(), messageTwo.getPayload());
}
-
@Test
- @DisplayName("Test notification when sink is not a valid UUri")
- public void testNotificationWithInvalidSink() {
- assertThrows(IllegalArgumentException.class,
- () -> UMessageBuilder.notification(buildTopic(), buildTopic()).build());
+ void testBuildRetainsAllPublishAttributes() {
+ var messageId = UuidFactory.Factories.UPROTOCOL.factory().create();
+ String traceparent = "traceparent";
+ UUri topic = UURI_TOPIC;
+ UMessage message = UMessageBuilder.publish(topic)
+ .withMessageId(messageId)
+ .withTtl(5000)
+ .withTraceparent(traceparent)
+ .build(UPayload.pack(ByteString.copyFromUtf8("locked"), UPayloadFormat.UPAYLOAD_FORMAT_TEXT));
+
+ assertEquals(messageId, message.getAttributes().getId());
+ assertEquals(UPriority.UPRIORITY_UNSPECIFIED, message.getAttributes().getPriority());
+ assertEquals(topic, message.getAttributes().getSource());
+ assertFalse(message.getAttributes().hasSink());
+ assertEquals(5000, message.getAttributes().getTtl());
+ assertEquals(traceparent, message.getAttributes().getTraceparent());
+ assertEquals(UMessageType.UMESSAGE_TYPE_PUBLISH, message.getAttributes().getType());
+ assertEquals(UPayloadFormat.UPAYLOAD_FORMAT_TEXT, message.getAttributes().getPayloadFormat());
+
+ var proto = message.toByteString();
+ assertDoesNotThrow(() -> {
+ var deserializedMessage = UMessage.parseFrom(proto);
+ assertEquals(message, deserializedMessage);
+ });
}
-
@Test
- @DisplayName("Test request when source is not valid")
- public void testRequestWithInvalidSource() {
- assertThrows(IllegalArgumentException.class,
- () -> UMessageBuilder.request(buildMethod(), buildMethod(), 1000).build());
+ void testBuildRetainsAllNotificationAttributes() {
+ var messageId = UuidFactory.Factories.UPROTOCOL.factory().create();
+ String traceparent = "traceparent";
+ var origin = UURI_TOPIC;
+ var destination = UURI_DEFAULT;
+ UMessage message = UMessageBuilder.notification(origin, destination)
+ .withMessageId(messageId)
+ .withPriority(UPriority.UPRIORITY_CS2)
+ .withTtl(5000)
+ .withTraceparent(traceparent)
+ .build(UPayload.pack(ByteString.copyFromUtf8("locked"), UPayloadFormat.UPAYLOAD_FORMAT_TEXT));
+
+ assertEquals(messageId, message.getAttributes().getId());
+ assertEquals(UPriority.UPRIORITY_CS2, message.getAttributes().getPriority());
+ assertEquals(origin, message.getAttributes().getSource());
+ assertEquals(destination, message.getAttributes().getSink());
+ assertEquals(5000, message.getAttributes().getTtl());
+ assertEquals(traceparent, message.getAttributes().getTraceparent());
+ assertEquals(UMessageType.UMESSAGE_TYPE_NOTIFICATION, message.getAttributes().getType());
+ assertEquals(UPayloadFormat.UPAYLOAD_FORMAT_TEXT, message.getAttributes().getPayloadFormat());
+
+ var proto = message.toByteString();
+ assertDoesNotThrow(() -> {
+ var deserializedMessage = UMessage.parseFrom(proto);
+ assertEquals(message, deserializedMessage);
+ });
}
@Test
- @DisplayName("Test request when sink is not valid")
- public void testRequestWithInvalidSink() {
- assertThrows(IllegalArgumentException.class,
- () -> UMessageBuilder.request(buildSource(), buildSource(), 1000).build());
+ void testBuildRetainsAllRequestAttributes() {
+ var messageId = UuidFactory.Factories.UPROTOCOL.factory().create();
+ var token = "token";
+ String traceparent = "traceparent";
+ var methodToInvoke = UURI_METHOD;
+ var replyToAddress = UURI_DEFAULT;
+ UMessage message = UMessageBuilder.request(replyToAddress, methodToInvoke, 5000)
+ .withMessageId(messageId)
+ .withPermissionLevel(5)
+ .withPriority(UPriority.UPRIORITY_CS4)
+ .withToken(token)
+ .withTraceparent(traceparent)
+ .build(UPayload.pack(ByteString.copyFromUtf8("locked"), UPayloadFormat.UPAYLOAD_FORMAT_TEXT));
+
+ assertEquals(messageId, message.getAttributes().getId());
+ assertEquals(5, message.getAttributes().getPermissionLevel());
+ assertEquals(UPriority.UPRIORITY_CS4, message.getAttributes().getPriority());
+ assertEquals(replyToAddress, message.getAttributes().getSource());
+ assertEquals(methodToInvoke, message.getAttributes().getSink());
+ assertEquals(token, message.getAttributes().getToken());
+ assertEquals(5000, message.getAttributes().getTtl());
+ assertEquals(traceparent, message.getAttributes().getTraceparent());
+ assertEquals(UMessageType.UMESSAGE_TYPE_REQUEST, message.getAttributes().getType());
+ assertEquals(UPayloadFormat.UPAYLOAD_FORMAT_TEXT, message.getAttributes().getPayloadFormat());
+
+ var proto = message.toByteString();
+ assertDoesNotThrow(() -> {
+ var deserializedMessage = UMessage.parseFrom(proto);
+ assertEquals(message, deserializedMessage);
+ });
}
@Test
- @DisplayName("Test request when source and sink are not valid")
- public void testRequestWithInvalidSourceAndSink() {
- assertThrows(IllegalArgumentException.class,
- () -> UMessageBuilder.request(buildMethod(), buildSource(), 1000).build());
+ void testBuilderCopiesRequestAttributes() {
+ var requestMessageId = UuidFactory.Factories.UPROTOCOL.factory().create();
+ var responseMessageId = UuidFactory.Factories.UPROTOCOL.factory().create();
+ var methodToInvoke = UURI_METHOD;
+ var replyToAddress = UURI_DEFAULT;
+ UMessage requestMessage = UMessageBuilder.request(replyToAddress, methodToInvoke, 5000)
+ .withMessageId(requestMessageId)
+ .withPriority(UPriority.UPRIORITY_CS5)
+ .build();
+ UMessage message = UMessageBuilder.response(requestMessage.getAttributes())
+ .withMessageId(responseMessageId)
+ .withCommStatus(UCode.DEADLINE_EXCEEDED)
+ .build();
+
+ assertEquals(responseMessageId, message.getAttributes().getId());
+ assertEquals(UCode.DEADLINE_EXCEEDED, message.getAttributes().getCommstatus());
+ assertEquals(UPriority.UPRIORITY_CS5, message.getAttributes().getPriority());
+ assertEquals(requestMessageId, message.getAttributes().getReqid());
+ assertEquals(methodToInvoke, message.getAttributes().getSource());
+ assertEquals(replyToAddress, message.getAttributes().getSink());
+ assertEquals(5000, message.getAttributes().getTtl());
+ assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, message.getAttributes().getType());
+ assertEquals(UPayloadFormat.UPAYLOAD_FORMAT_UNSPECIFIED, message.getAttributes().getPayloadFormat());
+ assertTrue(message.getPayload().isEmpty());
}
-
@Test
- @DisplayName("Test request when the ttl is null")
- public void testRequestWithNullTtl() {
- assertThrows(NullPointerException.class,
- () -> UMessageBuilder.request(buildSource(), buildMethod(), null).build());
- }
-
-
- @Test
- @DisplayName("Test request when ttl is negative")
- public void testRequestWithNegativeTtl() {
- assertThrows(IllegalArgumentException.class,
- () -> UMessageBuilder.request(buildSource(), buildMethod(), -1).build());
- }
-
-
- @Test
- @DisplayName("Test response when source is not valid")
- public void testResponseWithInvalidSource() {
- assertThrows(IllegalArgumentException.class,
- () -> UMessageBuilder.response(buildSink(), buildSink(),
- UuidFactory.Factories.UPROTOCOL.factory().create()).build());
- }
-
-
- @Test
- @DisplayName("Test response when sink is not valid")
- public void testResponseWithInvalidSink() {
- assertThrows(IllegalArgumentException.class,
- () -> UMessageBuilder.response(buildMethod(), buildMethod(),
- UuidFactory.Factories.UPROTOCOL.factory().create()).build());
- }
-
- @Test
- @DisplayName("Test response when source and sink are not valid")
- public void testResponseWithInvalidSourceAndSink() {
- assertThrows(IllegalArgumentException.class,
- () -> UMessageBuilder.response(buildSource(), buildSource(),
- UuidFactory.Factories.UPROTOCOL.factory().create()).build());
- }
-
-
- @Test
- @DisplayName("Test response when reqId is null")
- public void testResponseWithNullReqId() {
- assertThrows(NullPointerException.class,
- () -> UMessageBuilder.response(buildMethod(), buildSink(), null).build());
- }
-
-
- @Test
- @DisplayName("Test response when we pass an invalid reqid")
- public void testResponseWithInvalidReqId() {
- assertThrows(IllegalArgumentException.class,
- () -> UMessageBuilder.response(buildMethod(), buildSink(), UUID.getDefaultInstance()).build());
- }
-
-
- @Test
- @DisplayName("Test notification when source is not a valid topic and and sink is not valid")
- public void testNotificationWithInvalidSourceAndSink() {
- assertThrows(IllegalArgumentException.class,
- () -> UMessageBuilder.notification(buildSink(), buildSource()).build());
- }
-
-
- @Test
- @DisplayName("Test response builder when we pass UAttributes that is not a valid request type")
- public void testResponseBuilderWithInvalidRequestType() {
- assertThrows(IllegalArgumentException.class,
- () -> UMessageBuilder.response(UAttributes.getDefaultInstance()).build());
- }
-
-
- private UUri buildSource() {
- return UUri.newBuilder().setUeId(2).setUeVersionMajor(1).setResourceId(0).build();
- }
-
- private UUri buildTopic() {
- return UUri.newBuilder().setUeId(2).setUeVersionMajor(1).setResourceId(0x8000).build();
- }
-
- private UUri buildMethod() {
- return UUri.newBuilder().setUeId(2).setUeVersionMajor(1).setResourceId(1).build();
+ void testBuildRetainsAllResponseAttributes() {
+ var messageId = UuidFactory.Factories.UPROTOCOL.factory().create();
+ var requestId = UuidFactory.Factories.UPROTOCOL.factory().create();
+ var traceparent = "traceparent";
+ var methodToInvoke = UURI_METHOD;
+ var replyToAddress = UURI_DEFAULT;
+ UMessage message = UMessageBuilder.response(methodToInvoke, replyToAddress, requestId)
+ .withMessageId(messageId)
+ .withCommStatus(UCode.DEADLINE_EXCEEDED)
+ .withPriority(UPriority.UPRIORITY_CS5)
+ .withTtl(4000)
+ .withTraceparent(traceparent)
+ .build();
+
+ assertEquals(messageId, message.getAttributes().getId());
+ assertEquals(UCode.DEADLINE_EXCEEDED, message.getAttributes().getCommstatus());
+ assertEquals(UPriority.UPRIORITY_CS5, message.getAttributes().getPriority());
+ assertEquals(requestId, message.getAttributes().getReqid());
+ assertEquals(methodToInvoke, message.getAttributes().getSource());
+ assertEquals(replyToAddress, message.getAttributes().getSink());
+ assertEquals(4000, message.getAttributes().getTtl());
+ assertEquals(traceparent, message.getAttributes().getTraceparent());
+ assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, message.getAttributes().getType());
+ assertEquals(UPayloadFormat.UPAYLOAD_FORMAT_UNSPECIFIED, message.getAttributes().getPayloadFormat());
+ assertTrue(message.getPayload().isEmpty());
+
+ var proto = message.toByteString();
+ assertDoesNotThrow(() -> {
+ var deserializedMessage = UMessage.parseFrom(proto);
+ assertEquals(message, deserializedMessage);
+ });
}
}
diff --git a/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributeValidatorTest.java b/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributeValidatorTest.java
deleted file mode 100644
index c35fb49c..00000000
--- a/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributeValidatorTest.java
+++ /dev/null
@@ -1,562 +0,0 @@
-/**
- * SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information regarding copyright ownership.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Apache License Version 2.0 which is available at
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-package org.eclipse.uprotocol.transport.validator;
-
-import static org.junit.Assert.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import org.eclipse.uprotocol.v1.UAttributes;
-import org.eclipse.uprotocol.v1.UMessage;
-import org.eclipse.uprotocol.v1.UMessageType;
-import org.eclipse.uprotocol.v1.UPriority;
-import org.eclipse.uprotocol.v1.UUID;
-import org.eclipse.uprotocol.transport.builder.UMessageBuilder;
-import org.eclipse.uprotocol.transport.validate.UAttributesValidator;
-import org.eclipse.uprotocol.uuid.factory.UuidFactory;
-import org.eclipse.uprotocol.v1.UUri;
-import org.eclipse.uprotocol.validation.ValidationException;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-
-public class UAttributeValidatorTest {
-
- private UUri buildDefaultUUri() {
- return UUri.newBuilder().setUeId(1).setUeVersionMajor(1).setResourceId(0).build();
- }
-
- private UUri buildMethodUUri() {
- return UUri.newBuilder().setUeId(1).setUeVersionMajor(1).setResourceId(1).build();
- }
-
- private UUri buildTopicUUri() {
- return UUri.newBuilder().setUeId(1).setUeVersionMajor(1).setResourceId(0x8000).build();
- }
-
- @Test
- @DisplayName("""
- Test creating a UMessage of type publish then validating it using \
- UAttributeValidator for the happy path
- """)
- public void testUAttributeValidatorHappyPath() {
- UMessage message = UMessageBuilder.publish(buildTopicUUri()).build();
-
- UAttributesValidator validator = UAttributesValidator.getValidator(message.getAttributes());
- validator.validate(message.getAttributes());
- assertEquals(validator.toString(), "UAttributesValidator.Publish");
- }
-
- @Test
- @DisplayName("Test validation a notification message using UAttributeValidator")
- public void testUAttributeValidatorNotification() {
- UMessage message = UMessageBuilder.notification(buildTopicUUri(), buildDefaultUUri()).build();
-
- UAttributesValidator validator = UAttributesValidator.getValidator(message.getAttributes());
- validator.validate(message.getAttributes());
- assertEquals(validator.toString(), "UAttributesValidator.Notification");
- }
-
- @Test
- @DisplayName("Test validation a request message using UAttributeValidator")
- public void testUAttributeValidatorRequest() {
- UMessage message = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build();
-
- UAttributesValidator validator = UAttributesValidator.getValidator(message.getAttributes());
- validator.validate(message.getAttributes());
- assertEquals(validator.toString(), "UAttributesValidator.Request");
- }
-
- @Test
- @DisplayName("Test validation a response message using UAttributeValidator")
- public void testUAttributeValidatorResponse() {
- UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build();
- UMessage response = UMessageBuilder.response(
- buildMethodUUri(),
- buildDefaultUUri(),
- request.getAttributes().getId())
- .build();
-
- UAttributesValidator validator = UAttributesValidator.getValidator(response.getAttributes());
- validator.validate(response.getAttributes());
- assertEquals(validator.toString(), "UAttributesValidator.Response");
- }
-
- @Test
- @DisplayName("Test validation a response message using UAttributeValidator when passed request UAttributes")
- public void testUAttributeValidatorResponseWithRequestAttributes() {
- UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build();
- UMessage response = UMessageBuilder.response(request.getAttributes()).build();
-
- UAttributesValidator validator = UAttributesValidator.getValidator(response.getAttributes());
- validator.validate(response.getAttributes());
- assertEquals(validator.toString(), "UAttributesValidator.Response");
- }
-
- @Test
- @DisplayName("Test validation failed when using the publish validator to test request messages")
- public void testUAttributeValidatorRequestWithPublishValidator() {
- UMessage message = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build();
-
- UAttributesValidator validator = UAttributesValidator.Validators.PUBLISH.validator();
- ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(message.getAttributes()));
- assertEquals(validator.toString(), "UAttributesValidator.Publish");
- assertEquals(
- result.getMessage(),
- "Wrong Attribute Type [UMESSAGE_TYPE_REQUEST],Sink should not be present");
- }
-
- @Test
- @DisplayName("Test validation failed when using the notification validator to test publish messages")
- public void testUAttributeValidatorPublishWithNotificationValidator() {
- UMessage message = UMessageBuilder.publish(buildTopicUUri()).build();
-
- UAttributesValidator validator = UAttributesValidator.Validators.NOTIFICATION.validator();
- ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(message.getAttributes()));
- assertEquals(validator.toString(), "UAttributesValidator.Notification");
- assertEquals(result.getMessage(), "Wrong Attribute Type [UMESSAGE_TYPE_PUBLISH],Missing Sink");
- }
-
- @Test
- @DisplayName("Test validation failed when using the request validator to test response messages")
- public void testUAttributeValidatorResponseWithRequestValidator() {
- UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build();
- UMessage response = UMessageBuilder.response(request.getAttributes()).build();
-
- UAttributesValidator validator = UAttributesValidator.Validators.REQUEST.validator();
- ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(response.getAttributes()));
- assertEquals(validator.toString(), "UAttributesValidator.Request");
- assertEquals(
- result.getMessage(),
- """
- Wrong Attribute Type [UMESSAGE_TYPE_RESPONSE],Missing TTL,Invalid Sink Uri,Message should not have a reqid\
- """);
- }
-
- @Test
- @DisplayName("Test validation failed when using the response validator to test notification messages")
- public void testUAttributeValidatorNotificationWithResponseValidator() {
- UMessage message = UMessageBuilder.notification(buildTopicUUri(), buildDefaultUUri()).build();
-
- UAttributesValidator validator = UAttributesValidator.Validators.RESPONSE.validator();
- ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(message.getAttributes()));
- assertEquals(validator.toString(), "UAttributesValidator.Response");
- assertEquals(
- result.getMessage(),
- """
- Wrong Attribute Type [UMESSAGE_TYPE_NOTIFICATION],Invalid UPriority [UPRIORITY_CS1],Missing correlationId\
- """);
- }
-
- @Test
- @DisplayName("Test validation of request message has an invalid sink attribute")
- public void testUAttributeValidatorRequestMissingSink() {
- UAttributes attributes = UAttributes.newBuilder()
- .setSource(buildDefaultUUri())
- .setSink(buildDefaultUUri())
- .setId(UuidFactory.Factories.UPROTOCOL.factory().create())
- .setType(UMessageType.UMESSAGE_TYPE_REQUEST)
- .setPriority(UPriority.UPRIORITY_CS4)
- .setTtl(1000)
- .build();
-
- UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
- assertEquals(validator.toString(), "UAttributesValidator.Request");
- assertEquals(result.getMessage(), "Invalid Sink Uri");
- }
-
- @Test
- @DisplayName("Test validation of request message that has a permission level that is less than 0")
- public void testUAttributeValidatorRequestInvalidPermissionLevel() {
- UMessage message = UMessageBuilder.request(
- buildDefaultUUri(),
- buildMethodUUri(),
- 1000)
- .withPermissionLevel(-1)
- .build();
-
- UAttributesValidator validator = UAttributesValidator.getValidator(message.getAttributes());
- ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(message.getAttributes()));
- assertEquals(validator.toString(), "UAttributesValidator.Request");
- assertEquals(result.getMessage(), "Invalid Permission Level");
- }
-
- @Test
- @DisplayName("Test validation of request message that has a permission level that is greater than 0")
- public void testUAttributeValidatorRequestValidPermissionLevel() {
- UMessage message = UMessageBuilder.request(
- buildDefaultUUri(),
- buildMethodUUri(),
- 1000)
- .withPermissionLevel(1)
- .build();
-
- UAttributesValidator validator = UAttributesValidator.getValidator(message.getAttributes());
- validator.validate(message.getAttributes());
- assertFalse(validator.isExpired(message.getAttributes()));
- assertEquals(validator.toString(), "UAttributesValidator.Request");
- }
-
- @Test
- @DisplayName("Test validation of request message that has TTL that is less than 0")
- public void testUAttributeValidatorRequestInvalidTTL() {
- UMessage message = UMessageBuilder.publish(buildTopicUUri()).withTtl(-1).build();
-
- UAttributesValidator validator = UAttributesValidator.getValidator(message.getAttributes());
- ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(message.getAttributes()));
- assertFalse(validator.isExpired(message.getAttributes()));
- assertEquals(validator.toString(), "UAttributesValidator.Publish");
- assertEquals(result.getMessage(), "Invalid TTL [-1]");
- }
-
- @Test
- @DisplayName("Test validation of request message where the message has expired")
- public void testUAttributeValidatorRequestExpired() throws InterruptedException {
- UMessage message = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1).build();
- Thread.sleep(100);
- UAttributesValidator validator = UAttributesValidator.getValidator(message.getAttributes());
- assertTrue(validator.isExpired(message.getAttributes()));
- }
-
- @Test
- @DisplayName("Test validator isExpired() for an ID that is mall formed and doesn't have the time")
- public void testUAttributeValidatorRequestExpiredMalformedId() {
- UAttributes attributes = UAttributes.newBuilder()
- .setId(UUID.getDefaultInstance())
- .setType(UMessageType.UMESSAGE_TYPE_REQUEST)
- .build();
- UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- assertFalse(validator.isExpired(attributes));
- }
-
- @Test
- @DisplayName("Test validation fails when a publish messages has a reqid")
- public void testUAttributeValidatorPublishWithReqId() {
- UMessage publish = UMessageBuilder.publish(buildTopicUUri()).build();
- UAttributes attributes = UAttributes.newBuilder()
- .mergeFrom(publish.getAttributes())
- .setReqid(UUID.getDefaultInstance())
- .build();
-
- UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
- assertEquals(validator.toString(), "UAttributesValidator.Publish");
- assertEquals(result.getMessage(), "Message should not have a reqid");
- }
-
- @Test
- @DisplayName("Test notification validation where the sink is missing")
- public void testUAttributeValidatorNotificationMissingSink() {
- final UMessage message = UMessageBuilder.notification(buildTopicUUri(), buildDefaultUUri()).build();
- final UAttributes attributes = UAttributes.newBuilder().mergeFrom(message.getAttributes()).clearSink().build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Notification");
- assertEquals(result.getMessage(), "Missing Sink");
- }
-
- @Test
- @DisplayName("Test notification validation where the sink the default instance")
- public void testUAttributeValidatorNotificationDefaultSink() {
- final UMessage message = UMessageBuilder.notification(buildTopicUUri(), buildDefaultUUri()).build();
- final UAttributes attributes = UAttributes.newBuilder()
- .mergeFrom(message.getAttributes())
- .setSink(UUri.getDefaultInstance())
- .build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Notification");
- assertEquals(result.getMessage(), "Missing Sink");
- }
-
- @Test
- @DisplayName("Test notification validation where the sink is NOT the defaultResourceId")
- public void testUAttributeValidatorNotificationDefaultResourceId() {
- final UAttributes attributes = UAttributes.newBuilder()
- .setSource(buildTopicUUri())
- .setId(UuidFactory.Factories.UPROTOCOL.factory().create())
- .setSink(buildMethodUUri())
- .setType(UMessageType.UMESSAGE_TYPE_NOTIFICATION)
- .setPriority(UPriority.UPRIORITY_CS1)
- .build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Notification");
- assertEquals(result.getMessage(), "Invalid Sink Uri");
- }
-
- @Test
- @DisplayName("Test validatePriority when priority is less than CS0")
- public void testUAttributeValidatorValidatePriorityLessThanCS0() {
- final UMessage message = UMessageBuilder.publish(buildTopicUUri()).build();
- final UAttributes attributes = UAttributes.newBuilder()
- .mergeFrom(message.getAttributes())
- .setPriority(UPriority.UPRIORITY_UNSPECIFIED)
- .build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Publish");
- assertEquals(result.getMessage(), "Invalid UPriority [UPRIORITY_UNSPECIFIED]");
- }
-
- @Test
- @DisplayName("Test validatePriority when priority CS0")
- public void testUAttributeValidatorValidatePriorityIsCS0() {
- final UMessage message = UMessageBuilder.publish(buildTopicUUri()).build();
- final UAttributes attributes = UAttributes.newBuilder().
- mergeFrom(message.getAttributes()).setPriority(UPriority.UPRIORITY_CS0).build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Publish");
- assertEquals(result.getMessage(), "Invalid UPriority [UPRIORITY_CS0]");
- }
-
- @Test
- @DisplayName("Test validateId when id is missing")
- public void testUAttributeValidatorValidateIdMissing() {
- final UMessage message = UMessageBuilder.publish(buildTopicUUri()).build();
- final UAttributes attributes = UAttributes.newBuilder().mergeFrom(message.getAttributes()).clearId().build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Publish");
- assertEquals(result.getMessage(), "Missing id");
- }
-
- @Test
- @DisplayName("Test validateId when id is the default instance")
- public void testUAttributeValidatorValidateIdDefault() {
- final UMessage message = UMessageBuilder.publish(buildTopicUUri()).build();
- final UAttributes attributes = UAttributes.newBuilder()
- .mergeFrom(message.getAttributes())
- .setId(UUID.getDefaultInstance())
- .build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Publish");
- assertEquals(result.getMessage(), "Attributes must contain valid uProtocol UUID in id property");
- }
-
- @Test
- @DisplayName("Test publish validateSink when sink is not empty")
- public void testUAttributeValidatorValidateSinkNotEmpty() {
- final UMessage message = UMessageBuilder.publish(buildTopicUUri()).build();
- final UAttributes attributes = UAttributes.newBuilder()
- .mergeFrom(message.getAttributes())
- .setSink(buildDefaultUUri())
- .build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Publish");
- assertEquals(result.getMessage(), "Sink should not be present");
- }
-
- @Test
- @DisplayName("Test validateSink of a request message that is missing a sink")
- public void testUAttributeValidatorValidateSinkMissing() {
- final UMessage message = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build();
- final UAttributes attributes = UAttributes.newBuilder().mergeFrom(message.getAttributes()).clearSink().build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Request");
- assertEquals(result.getMessage(), "Missing Sink");
- }
-
- @Test
- @DisplayName("Test validateTtl of a request message where ttl is less than 0")
- public void testUAttributeValidatorValidateTtlLessThanZero() {
- final UAttributes attributes = UAttributes.newBuilder()
- .setSource(buildDefaultUUri())
- .setSink(buildMethodUUri())
- .setTtl(-1)
- .setId(UuidFactory.Factories.UPROTOCOL.factory().create())
- .setType(UMessageType.UMESSAGE_TYPE_REQUEST)
- .setPriority(UPriority.UPRIORITY_CS4).build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Request");
- assertEquals(result.getMessage(), "Invalid TTL [-1]");
- }
-
- @Test
- @DisplayName("Test validatePriority of a request message where priority is less than CS4")
- public void testUAttributeValidatorValidatePriorityLessThanCS4() {
- final UMessage message = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build();
- final UAttributes attributes = UAttributes.newBuilder()
- .mergeFrom(message.getAttributes())
- .setPriority(UPriority.UPRIORITY_CS3)
- .build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Request");
- assertEquals(result.getMessage(), "Invalid UPriority [UPRIORITY_CS3]");
- }
-
- @Test
- @DisplayName("Test validateSink for a response message where the sink is missing")
- public void testUAttributeValidatorValidateSinkResponseMissing() {
- final UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build();
- final UMessage response = UMessageBuilder.response(request.getAttributes()).build();
- final UAttributes attributes = UAttributes.newBuilder().mergeFrom(response.getAttributes()).clearSink().build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Response");
- assertEquals(result.getMessage(), "Missing Sink");
- }
-
- @Test
- @DisplayName("Test validateSink for a response message where the sink is the default instance")
- public void testUAttributeValidatorValidateSinkResponseDefault() {
- final UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build();
- final UMessage response = UMessageBuilder.response(request.getAttributes()).build();
- final UAttributes attributes = UAttributes.newBuilder()
- .mergeFrom(response.getAttributes())
- .setSink(UUri.getDefaultInstance())
- .build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Response");
- assertEquals(result.getMessage(), "Missing Sink");
- }
-
- @Test
- @DisplayName("Test validateSink for a response message where the sink is NOT the defaultResourceId")
- public void testUAttributeValidatorValidateSinkResponseDefaultResourceId() {
- final UAttributes attributes = UAttributes.newBuilder()
- .setSource(buildMethodUUri())
- .setSink(buildMethodUUri())
- .setTtl(1000)
- .setType(UMessageType.UMESSAGE_TYPE_RESPONSE)
- .setPriority(UPriority.UPRIORITY_CS4)
- .setId(UuidFactory.Factories.UPROTOCOL.factory().create())
- .setReqid(UuidFactory.Factories.UPROTOCOL.factory().create())
- .build();
-
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Response");
- assertEquals(result.getMessage(), "Invalid Sink Uri");
- }
-
- @Test
- @DisplayName("Test validateReqId for a response message when the reqid is missing")
- public void testUAttributeValidatorValidateReqIdMissing() {
- final UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build();
- final UMessage response = UMessageBuilder.response(request.getAttributes()).build();
- final UAttributes attributes = UAttributes.newBuilder()
- .mergeFrom(response.getAttributes())
- .clearReqid()
- .build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Response");
- assertEquals(result.getMessage(), "Missing correlationId");
- }
-
- @Test
- @DisplayName("Test validateReqId for a response message when the reqid is the default instance")
- public void testUAttributeValidatorValidateReqIdDefault() {
- final UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build();
- final UMessage response = UMessageBuilder.response(request.getAttributes()).build();
- final UAttributes attributes = UAttributes.newBuilder()
- .mergeFrom(response.getAttributes())
- .setReqid(UUID.getDefaultInstance())
- .build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Response");
- assertEquals(result.getMessage(), "Missing correlationId");
- }
-
- @Test
- @DisplayName("Test validateReqId for a response message when the reqid not a valid uprotocol UUID")
- public void testUAttributeValidatorValidateReqIdInvalid() {
- final UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build();
- final UMessage response = UMessageBuilder.response(request.getAttributes()).build();
- final UAttributes attributes = UAttributes.newBuilder()
- .mergeFrom(response.getAttributes())
- .setReqid(UUID.newBuilder().setLsb(0xbeadbeef).setMsb(0xdeadbeef))
- .build();
- final UAttributesValidator validator = UAttributesValidator.getValidator(attributes);
- final ValidationException result = assertThrows(
- ValidationException.class,
- () -> validator.validate(attributes));
-
- assertEquals(validator.toString(), "UAttributesValidator.Response");
- assertEquals(result.getMessage(), "Invalid correlation UUID");
- }
-}
diff --git a/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributesValidatorTest.java b/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributesValidatorTest.java
new file mode 100644
index 00000000..9401d9dd
--- /dev/null
+++ b/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributesValidatorTest.java
@@ -0,0 +1,791 @@
+/**
+ * SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.eclipse.uprotocol.transport.validator;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.stream.Stream;
+
+import org.eclipse.uprotocol.v1.UAttributes;
+import org.eclipse.uprotocol.v1.UCode;
+import org.eclipse.uprotocol.v1.UMessageType;
+import org.eclipse.uprotocol.v1.UPriority;
+import org.eclipse.uprotocol.v1.UUID;
+import org.eclipse.uprotocol.uuid.factory.UuidFactory;
+import org.eclipse.uprotocol.v1.UUri;
+import org.eclipse.uprotocol.validation.ValidationException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class UAttributesValidatorTest {
+
+ private static final UUri UURI_DEFAULT = UUri.newBuilder().setUeId(1).setUeVersionMajor(1).setResourceId(0).build();
+
+ private static final UUri UURI_METHOD = UUri.newBuilder()
+ .setUeId(0x001a_1a5b)
+ .setUeVersionMajor(0x04)
+ .setResourceId(0x7a5f)
+ .build();
+
+ private static final UUri UURI_TOPIC = UUri.newBuilder()
+ .setUeId(1)
+ .setUeVersionMajor(1)
+ .setResourceId(0x8000)
+ .build();
+
+ private static final UUri UURI_WILDCARD_RESOURCE = UUri.newBuilder()
+ .setUeId(1)
+ .setUeVersionMajor(1)
+ .setResourceId(0xFFFF)
+ .build();
+
+ private static final UUID UUID_INVALID = UUID.newBuilder()
+ .setMsb(0x000000000001C000)
+ .setLsb(0x0000000000000000)
+ .build();
+
+ @Test
+ void testValidateTypeFailsForUnexpectedTypeCode() {
+ UAttributes attributes = UAttributes.newBuilder()
+ .setType(UMessageType.UMESSAGE_TYPE_UNSPECIFIED)
+ .build();
+ assertThrows(
+ ValidationException.class,
+ () -> UAttributesValidator.Validators.PUBLISH.validator().validate(attributes));
+ assertThrows(
+ ValidationException.class,
+ () -> UAttributesValidator.Validators.NOTIFICATION.validator().validate(attributes));
+ assertThrows(
+ ValidationException.class,
+ () -> UAttributesValidator.Validators.REQUEST.validator().validate(attributes));
+ assertThrows(
+ ValidationException.class,
+ () -> UAttributesValidator.Validators.RESPONSE.validator().validate(attributes));
+ }
+
+ @ParameterizedTest
+ @CsvSource(useHeadersInDisplayName = true, textBlock = """
+ messageType, expectedValidatorType
+ # succeeds for Unspecified message
+ UMESSAGE_TYPE_UNSPECIFIED, UMESSAGE_TYPE_PUBLISH
+ # succeeds for Publish message
+ UMESSAGE_TYPE_PUBLISH, UMESSAGE_TYPE_PUBLISH
+ # succeeds for Notification message
+ UMESSAGE_TYPE_NOTIFICATION, UMESSAGE_TYPE_NOTIFICATION
+ # succeeds for Request message
+ UMESSAGE_TYPE_REQUEST, UMESSAGE_TYPE_REQUEST
+ # succeeds for Response message
+ UMESSAGE_TYPE_RESPONSE, UMESSAGE_TYPE_RESPONSE
+ """)
+ void testGetValidatorReturnsMatchingValidator(
+ UMessageType messageType,
+ UMessageType expectedValidatorType
+ ) {
+ var validator = UAttributesValidator.getValidator(messageType);
+ assertEquals(expectedValidatorType, validator.messageType());
+ }
+
+ static Stream
- * The message has expired when the current time is greater than the original
- * UUID time
- * plus the ttl attribute.
- *
- * @param uAttributes UAttributes with time to live value.
- * @return Returns a true if the original time plus the ttl is less than the
- * current time
- */
- public boolean isExpired(UAttributes uAttributes) {
- final int ttl = uAttributes.getTtl();
- final Optional