diff --git a/README.adoc b/README.adoc index dd3b0cd..48d2967 100644 --- a/README.adoc +++ b/README.adoc @@ -54,9 +54,9 @@ python -m pip install . *This will install the up-python, making its classes and modules available for import in your python code.* -=== Using The Sdk +=== Using The Library -The SDK is broken up into different packages that are described in <> below. Each package contains a README.adoc file that describes the purpose of the package and how to use it. Packages are organized into the following directories: +The Library is broken up into different packages that are described in <> below. Each package contains a README.adoc file that describes the purpose of the package and how to use it. Packages are organized into the following directories: .Package Folders [#pkg-folders,width=100%,cols="20%,80%",options="header"] diff --git a/tests/test_rpc/test_rpc.py b/tests/test_rpc/test_rpc.py index 6507a87..a4751d0 100644 --- a/tests/test_rpc/test_rpc.py +++ b/tests/test_rpc/test_rpc.py @@ -29,16 +29,26 @@ from concurrent.futures import Future from google.protobuf.any_pb2 import Any from google.protobuf.wrappers_pb2 import Int32Value +from uprotocol.rpc.calloptions import CallOptions + from uprotocol.cloudevent.cloudevents_pb2 import CloudEvent from uprotocol.proto.uattributes_pb2 import UPriority from uprotocol.proto.upayload_pb2 import UPayload, UPayloadFormat -from uprotocol.proto.uri_pb2 import UUri, UEntity +from uprotocol.proto.uri_pb2 import UUri, UEntity, UAuthority from uprotocol.proto.ustatus_pb2 import UStatus, UCode from uprotocol.rpc.rpcclient import RpcClient from uprotocol.rpc.rpcmapper import RpcMapper from uprotocol.rpc.rpcresult import RpcResult from uprotocol.transport.builder.uattributesbuilder import UAttributesBuilder from uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from uprotocol.uri.factory.uresource_builder import UResourceBuilder +from uprotocol.proto.umessage_pb2 import UMessage + + +def build_source(): + return UUri(authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), + entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), + resource=UResourceBuilder.for_rpc_request(None)) def build_cloud_event(): @@ -55,77 +65,77 @@ def build_topic(): return LongUriSerializer().deserialize("//vcu.vin/hartley/1/rpc.Raise") -def build_uattributes(): - return UAttributesBuilder.request(UPriority.UPRIORITY_CS4, UUri(entity=UEntity(name="hartley")), 1000).build() +def build_calloptions(): + return CallOptions() class ReturnsNumber3(RpcClient): - def invoke_method(self, topic, payload, attributes): + def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): future = Future() any_obj = Any() any_obj.Pack(Int32Value(value=3)) data = UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, value=any_obj.SerializeToString()) - future.set_result(data) + future.set_result(UMessage(payload=data)) return future class HappyPath(RpcClient): - def invoke_method(self, topic, payload, attributes): + def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): future = Future() data = build_upayload() - future.set_result(data) + future.set_result(UMessage(payload=data)) return future class WithUStatusCodeInsteadOfHappyPath(RpcClient): - def invoke_method(self, topic, payload, attributes): + def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): future = Future() status = UStatus(code=UCode.INVALID_ARGUMENT, message="boom") any_value = Any() any_value.Pack(status) data = UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, value=any_value.SerializeToString()) - future.set_result(data) + future.set_result(UMessage(payload=data)) return future class WithUStatusCodeHappyPath(RpcClient): - def invoke_method(self, topic, payload, attributes): + def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): future = Future() status = UStatus(code=UCode.OK, message="all good") any_value = Any() any_value.Pack(status) data = UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, value=any_value.SerializeToString()) - future.set_result(data) + future.set_result(UMessage(payload=data)) return future class ThatBarfsCrapyPayload(RpcClient): - def invoke_method(self, topic, payload, attributes): + def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): future = Future() response = UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_RAW, value=bytes([0])) - future.set_result(response) + future.set_result(UMessage(payload=response)) return future class ThatCompletesWithAnException(RpcClient): - def invoke_method(self, topic, payload, attributes): + def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): future = Future() future.set_exception(RuntimeError("Boom")) return future class ThatReturnsTheWrongProto(RpcClient): - def invoke_method(self, topic, payload, attributes): + def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): future = Future() any_value = Any() any_value.Pack(Int32Value(value=42)) data = UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, value=any_value.SerializeToString()) - future.set_result(data) + future.set_result(UMessage(payload=data)) return future class WithNullInPayload(RpcClient): - def invoke_method(self, topic, payload, attributes): + def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): future = Future() future.set_result(None) return future @@ -170,14 +180,14 @@ class TestRpc(unittest.TestCase): def test_compose_happy_path(self): rpc_response = RpcMapper.map_response_to_result( - ReturnsNumber3().invoke_method(build_topic(), build_upayload(), build_uattributes()), Int32Value) + ReturnsNumber3().invoke_method(build_topic(), build_upayload(), build_calloptions()), Int32Value) mapped = rpc_response.map(lambda x: x.value + 5) self.assertTrue(rpc_response.isSuccess()) self.assertEqual(8, mapped.successValue()) def test_compose_that_returns_status(self): rpc_response = RpcMapper.map_response_to_result( - WithUStatusCodeInsteadOfHappyPath().invoke_method(build_topic(), build_upayload(), build_uattributes()), + WithUStatusCodeInsteadOfHappyPath().invoke_method(build_topic(), build_upayload(), build_calloptions()), Int32Value) mapped = rpc_response.map(lambda x: x.value + 5) self.assertTrue(rpc_response.isFailure()) @@ -186,7 +196,7 @@ def test_compose_that_returns_status(self): def test_compose_with_failure(self): rpc_response = RpcMapper.map_response_to_result( - ThatCompletesWithAnException().invoke_method(build_topic(), build_upayload(), build_uattributes()), + ThatCompletesWithAnException().invoke_method(build_topic(), build_upayload(), build_calloptions()), Int32Value) mapped = rpc_response.map(lambda x: x.value + 5) self.assertTrue(rpc_response.isFailure()) @@ -195,14 +205,14 @@ def test_compose_with_failure(self): def test_success_invoke_method_happy_flow_using_mapResponseToRpcResponse(self): rpc_response = RpcMapper.map_response_to_result( - HappyPath().invoke_method(build_topic(), build_upayload(), build_uattributes()), + HappyPath().invoke_method(build_topic(), build_upayload(), build_calloptions()), CloudEvent) self.assertTrue(rpc_response.isSuccess()) self.assertEqual(build_cloud_event(), rpc_response.successValue()) def test_fail_invoke_method_when_invoke_method_returns_a_status_using_mapResponseToRpcResponse(self): rpc_response = RpcMapper.map_response_to_result( - WithUStatusCodeInsteadOfHappyPath().invoke_method(build_topic(), build_upayload(), build_uattributes()), + WithUStatusCodeInsteadOfHappyPath().invoke_method(build_topic(), build_upayload(), build_calloptions()), CloudEvent) self.assertTrue(rpc_response.isFailure()) self.assertEqual(UCode.INVALID_ARGUMENT, rpc_response.failureValue().code) @@ -210,7 +220,7 @@ def test_fail_invoke_method_when_invoke_method_returns_a_status_using_mapRespons def test_fail_invoke_method_when_invoke_method_threw_an_exception_using_mapResponseToRpcResponse(self): rpc_response = RpcMapper.map_response_to_result( - ThatCompletesWithAnException().invoke_method(build_topic(), build_upayload(), build_uattributes()), + ThatCompletesWithAnException().invoke_method(build_topic(), build_upayload(), build_calloptions()), CloudEvent) self.assertTrue(rpc_response.isFailure()) self.assertEqual(UCode.UNKNOWN, rpc_response.failureValue().code) @@ -218,24 +228,25 @@ def test_fail_invoke_method_when_invoke_method_threw_an_exception_using_mapRespo def test_fail_invoke_method_when_invoke_method_returns_a_bad_proto_using_mapResponseToRpcResponse(self): rpc_response = RpcMapper.map_response_to_result( - ThatReturnsTheWrongProto().invoke_method(build_topic(), build_upayload(), build_uattributes()), + ThatReturnsTheWrongProto().invoke_method(build_topic(), build_upayload(), build_calloptions()), CloudEvent) self.assertTrue(rpc_response.isFailure()) self.assertEqual(UCode.UNKNOWN, rpc_response.failureValue().code) - self.assertEqual("Unknown payload type [type.googleapis.com/google.protobuf.Int32Value]. Expected [io.cloudevents.v1.CloudEvent]", rpc_response.failureValue().message) + self.assertEqual( + "Unknown payload type [type.googleapis.com/google.protobuf.Int32Value]. Expected [" + "io.cloudevents.v1.CloudEvent]", + rpc_response.failureValue().message) def test_success_invoke_method_happy_flow_using_mapResponse(self): rpc_response = RpcMapper.map_response( - HappyPath().invoke_method(build_topic(), build_upayload(), build_uattributes()), + HappyPath().invoke_method(build_topic(), build_upayload(), build_calloptions()), CloudEvent) self.assertEqual(build_cloud_event(), rpc_response.result()) def test_fail_invoke_method_when_invoke_method_returns_a_status_using_mapResponse(self): rpc_response = RpcMapper.map_response( - WithUStatusCodeInsteadOfHappyPath().invoke_method(build_topic(), build_upayload(), build_uattributes()), + WithUStatusCodeInsteadOfHappyPath().invoke_method(build_topic(), build_upayload(), build_calloptions()), CloudEvent) - exception=RuntimeError("Unknown payload type [type.googleapis.com/uprotocol.v1.UStatus]. Expected [CloudEvent]") - self.assertEqual(str(exception),str(rpc_response.exception())) - - - + exception = RuntimeError( + "Unknown payload type [type.googleapis.com/uprotocol.v1.UStatus]. Expected [CloudEvent]") + self.assertEqual(str(exception), str(rpc_response.exception())) diff --git a/tests/test_transport/test_builder/test_uattributesbuilder.py b/tests/test_transport/test_builder/test_uattributesbuilder.py index 0baefbf..5133c03 100644 --- a/tests/test_transport/test_builder/test_uattributesbuilder.py +++ b/tests/test_transport/test_builder/test_uattributesbuilder.py @@ -29,9 +29,14 @@ from uprotocol.transport.builder.uattributesbuilder import UAttributesBuilder from uprotocol.proto.uattributes_pb2 import UPriority, UMessageType from uprotocol.proto.uri_pb2 import UUri, UAuthority, UEntity -from uprotocol.uri.builder.uresource_builder import UResourceBuilder +from uprotocol.uri.factory.uresource_builder import UResourceBuilder from uprotocol.uuid.factory.uuidfactory import Factories +def build_source(): + return UUri(authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), + entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), + resource=UResourceBuilder.for_rpc_request(None)) + def build_sink(): return UUri(authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), @@ -46,7 +51,8 @@ def get_uuid(): class TestUAttributesBuilder(unittest.TestCase): def test_publish(self): - builder = UAttributesBuilder.publish(UPriority.UPRIORITY_CS1) + source = build_source() + builder = UAttributesBuilder.publish(source, UPriority.UPRIORITY_CS1) self.assertIsNotNone(builder) attributes = builder.build() self.assertIsNotNone(attributes) @@ -54,8 +60,9 @@ def test_publish(self): self.assertEqual(UPriority.UPRIORITY_CS1, attributes.priority) def test_notification(self): + source = build_source() sink = build_sink() - builder = UAttributesBuilder.notification(UPriority.UPRIORITY_CS1, sink) + builder = UAttributesBuilder.notification(source, sink, UPriority.UPRIORITY_CS1) self.assertIsNotNone(builder) attributes = builder.build() self.assertIsNotNone(attributes) @@ -64,9 +71,10 @@ def test_notification(self): self.assertEqual(sink, attributes.sink) def test_request(self): + source = build_source() sink = build_sink() ttl = 1000 - builder = UAttributesBuilder.request(UPriority.UPRIORITY_CS4, sink, ttl) + builder = UAttributesBuilder.request(source, sink, UPriority.UPRIORITY_CS4, ttl) self.assertIsNotNone(builder) attributes = builder.build() self.assertIsNotNone(attributes) @@ -76,9 +84,10 @@ def test_request(self): self.assertEqual(ttl, attributes.ttl) def test_response(self): + source = build_source() sink = build_sink() req_id = get_uuid() - builder = UAttributesBuilder.response(UPriority.UPRIORITY_CS6, sink, req_id) + builder = UAttributesBuilder.response(source, sink, UPriority.UPRIORITY_CS6, req_id) self.assertIsNotNone(builder) attributes = builder.build() self.assertIsNotNone(attributes) @@ -89,7 +98,7 @@ def test_response(self): def test_build(self): req_id = get_uuid() - builder = UAttributesBuilder.publish(UPriority.UPRIORITY_CS1).withTtl(1000).withToken("test_token").withSink( + builder = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS1).withTtl(1000).withToken("test_token").withSink( build_sink()).withPermissionLevel(2).withCommStatus(1).withReqId(req_id) attributes = builder.build() self.assertIsNotNone(attributes) @@ -97,6 +106,7 @@ def test_build(self): self.assertEqual(UPriority.UPRIORITY_CS1, attributes.priority) self.assertEqual(1000, attributes.ttl) self.assertEqual("test_token", attributes.token) + self.assertEqual(build_source(), attributes.source) self.assertEqual(build_sink(), attributes.sink) self.assertEqual(2, attributes.permission_level) self.assertEqual(1, attributes.commstatus) diff --git a/tests/test_transport/test_validate/test_uattributesvalidator.py b/tests/test_transport/test_validate/test_uattributesvalidator.py index 672e3a5..d3dcfde 100644 --- a/tests/test_transport/test_validate/test_uattributesvalidator.py +++ b/tests/test_transport/test_validate/test_uattributesvalidator.py @@ -33,7 +33,7 @@ from uprotocol.proto.uuid_pb2 import UUID from uprotocol.transport.builder.uattributesbuilder import UAttributesBuilder from uprotocol.transport.validate.uattributesvalidator import UAttributesValidator, Validators -from uprotocol.uri.builder.uresource_builder import UResourceBuilder +from uprotocol.uri.factory.uresource_builder import UResourceBuilder from uprotocol.uri.serializer.longuriserializer import LongUriSerializer from uprotocol.uuid.factory.uuidfactory import Factories from uprotocol.validation.validationresult import ValidationResult @@ -44,30 +44,34 @@ def build_sink(): entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), resource=UResourceBuilder.for_rpc_response()) +def build_source(): + return UUri(authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), + entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), + resource=UResourceBuilder.for_rpc_request(None)) class TestUAttributesValidator(unittest.TestCase): def test_fetching_validator_for_valid_types(self): - publish = UAttributesValidator.get_validator(UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).build()) + publish = UAttributesValidator.get_validator(UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).build()) self.assertEqual("UAttributesValidator.Publish", str(publish)) request = UAttributesValidator.get_validator( - UAttributesBuilder.request(UPriority.UPRIORITY_CS4, UUri(), 1000).build()) + UAttributesBuilder.request(build_source(), UUri(), UPriority.UPRIORITY_CS4, 1000).build()) self.assertEqual("UAttributesValidator.Request", str(request)) response = UAttributesValidator.get_validator( - UAttributesBuilder.response(UPriority.UPRIORITY_CS4, UUri(), Factories.UPROTOCOL.create()).build()) + UAttributesBuilder.response(build_source(), UUri(), UPriority.UPRIORITY_CS4, Factories.UPROTOCOL.create()).build()) self.assertEqual("UAttributesValidator.Response", str(response)) def test_validate_uAttributes_for_publish_message_payload(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).build() validator = Validators.PUBLISH.validator() status = validator.validate(attributes) self.assertTrue(status.is_success()) self.assertEqual("", status.get_message()) def test_validate_uAttributes_for_publish_message_payload_alls(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withTtl(1000).withSink( + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withTtl(1000).withSink( build_sink()).withPermissionLevel(2).withCommStatus(3).withReqId(Factories.UPROTOCOL.create()).build() validator = Validators.PUBLISH.validator() @@ -76,7 +80,7 @@ def test_validate_uAttributes_for_publish_message_payload_alls(self): self.assertEqual("", status.get_message()) def test_validate_uAttributes_for_publish_message_payload_invalid_type(self): - attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS0, build_sink(), + attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS0, Factories.UPROTOCOL.create()).build() validator = Validators.PUBLISH.validator() status = validator.validate(attributes) @@ -84,7 +88,7 @@ def test_validate_uAttributes_for_publish_message_payload_invalid_type(self): self.assertEqual("Wrong Attribute Type [UMESSAGE_TYPE_RESPONSE]", status.get_message()) def test_validate_uAttributes_for_publish_message_payload_invalid_ttl(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withTtl(-1).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withTtl(-1).build() validator = Validators.PUBLISH.validator() status = validator.validate(attributes) @@ -92,21 +96,21 @@ def test_validate_uAttributes_for_publish_message_payload_invalid_ttl(self): self.assertEqual("Invalid TTL [-1]", status.get_message()) def test_validate_uAttributes_for_publish_message_payload_invalid_sink(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withSink(UUri()).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withSink(UUri()).build() validator = Validators.PUBLISH.validator() status = validator.validate(attributes) self.assertTrue(status.is_failure()) self.assertEqual("Uri is empty.", status.get_message()) def test_validate_uAttributes_for_publish_message_payload_invalid_permission_level(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withPermissionLevel(-42).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withPermissionLevel(-42).build() validator = Validators.PUBLISH.validator() status = validator.validate(attributes) self.assertTrue(status.is_failure()) self.assertEqual("Invalid Permission Level", status.get_message()) def test_validate_uAttributes_for_publish_message_payload_invalid_communication_status(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withCommStatus(-42).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withCommStatus(-42).build() validator = Validators.PUBLISH.validator() status = validator.validate(attributes) self.assertTrue(status.is_failure()) @@ -124,14 +128,14 @@ def test_validate_uAttributes_for_publish_message_payload_invalid_communication_ # self.assertEqual("Invalid UUID", status.get_message()) def test_validate_uAttributes_for_rpc_request_message_payload(self): - attributes = UAttributesBuilder.request(UPriority.UPRIORITY_CS4, build_sink(), 1000).build() + attributes = UAttributesBuilder.request(build_source(), build_sink(), UPriority.UPRIORITY_CS4, 1000).build() validator = Validators.REQUEST.validator() status = validator.validate(attributes) self.assertTrue(status.is_success()) self.assertEqual("", status.get_message()) def test_validate_uAttributes_for_rpc_request_message_payload_alls(self): - attributes = UAttributesBuilder.request(UPriority.UPRIORITY_CS4, build_sink(), 1000).withPermissionLevel( + attributes = UAttributesBuilder.request(build_source(), build_sink(), UPriority.UPRIORITY_CS4, 1000).withPermissionLevel( 2).withCommStatus(3).withReqId(Factories.UPROTOCOL.create()).build() validator = Validators.REQUEST.validator() @@ -140,7 +144,7 @@ def test_validate_uAttributes_for_rpc_request_message_payload_alls(self): self.assertEqual("", status.get_message()) def test_validate_uAttributes_for_rpc_request_message_payload_invalid_type(self): - attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS4, build_sink(), + attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS4, Factories.UPROTOCOL.create()).withTtl(1000).build() validator = Validators.REQUEST.validator() @@ -149,7 +153,7 @@ def test_validate_uAttributes_for_rpc_request_message_payload_invalid_type(self) self.assertEqual("Wrong Attribute Type [UMESSAGE_TYPE_RESPONSE]", status.get_message()) def test_validate_uAttributes_for_rpc_request_message_payload_invalid_ttl(self): - attributes = UAttributesBuilder.request(UPriority.UPRIORITY_CS4, build_sink(), -1).build() + attributes = UAttributesBuilder.request(build_source(), build_sink(), UPriority.UPRIORITY_CS4, -1).build() validator = Validators.REQUEST.validator() status = validator.validate(attributes) @@ -157,7 +161,7 @@ def test_validate_uAttributes_for_rpc_request_message_payload_invalid_ttl(self): self.assertEqual("Invalid TTL [-1]", status.get_message()) def test_validate_uAttributes_for_rpc_request_message_payload_invalid_sink(self): - attributes = UAttributesBuilder.request(UPriority.UPRIORITY_CS4, UUri(), 1000).build() + attributes = UAttributesBuilder.request(build_source(), UUri(), UPriority.UPRIORITY_CS4, 1000).build() validator = Validators.REQUEST.validator() status = validator.validate(attributes) @@ -165,7 +169,7 @@ def test_validate_uAttributes_for_rpc_request_message_payload_invalid_sink(self) self.assertEqual("Uri is empty.", status.get_message()) def test_validate_uAttributes_for_rpc_request_message_payload_invalid_permission_level(self): - attributes = UAttributesBuilder.request(UPriority.UPRIORITY_CS4, build_sink(), 1000).withPermissionLevel( + attributes = UAttributesBuilder.request(build_source(), build_sink(), UPriority.UPRIORITY_CS4, 1000).withPermissionLevel( -42).build() validator = Validators.REQUEST.validator() @@ -174,7 +178,7 @@ def test_validate_uAttributes_for_rpc_request_message_payload_invalid_permission self.assertEqual("Invalid Permission Level", status.get_message()) def test_validate_uAttributes_for_rpc_request_message_payload_invalid_communication_status(self): - attributes = UAttributesBuilder.request(UPriority.UPRIORITY_CS4, build_sink(), 1000).withCommStatus(-42).build() + attributes = UAttributesBuilder.request(build_source(), build_sink(), UPriority.UPRIORITY_CS4, 1000).withCommStatus(-42).build() validator = Validators.REQUEST.validator() status = validator.validate(attributes) @@ -193,7 +197,7 @@ def test_validate_uAttributes_for_rpc_request_message_payload_invalid_communicat # self.assertEqual("Invalid UUID", status.get_message()) def test_validate_uAttributes_for_rpc_response_message_payload(self): - attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS4, build_sink(), + attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS4, Factories.UPROTOCOL.create()).build() validator = Validators.RESPONSE.validator() @@ -202,7 +206,7 @@ def test_validate_uAttributes_for_rpc_response_message_payload(self): self.assertEqual("", status.get_message()) def test_validate_uAttributes_for_rpc_response_message_payload_alls(self): - attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS4, build_sink(), + attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS4, Factories.UPROTOCOL.create()).withPermissionLevel(2).withCommStatus( 3).build() @@ -212,7 +216,7 @@ def test_validate_uAttributes_for_rpc_response_message_payload_alls(self): self.assertEqual("", status.get_message()) def test_validate_uAttributes_for_rpc_response_message_payload_invalid_type(self): - attributes = UAttributesBuilder.notification(UPriority.UPRIORITY_CS4, build_sink()).build() + attributes = UAttributesBuilder.notification(build_source(), build_sink(), UPriority.UPRIORITY_CS4).build() validator = Validators.RESPONSE.validator() status = validator.validate(attributes) @@ -220,7 +224,7 @@ def test_validate_uAttributes_for_rpc_response_message_payload_invalid_type(self self.assertEqual("Wrong Attribute Type [UMESSAGE_TYPE_PUBLISH],Missing correlationId", status.get_message()) def test_validate_uAttributes_for_rpc_response_message_payload_invalid_ttl(self): - attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS4, build_sink(), + attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS4, Factories.UPROTOCOL.create()).withTtl(-1).build() validator = Validators.RESPONSE.validator() @@ -229,7 +233,7 @@ def test_validate_uAttributes_for_rpc_response_message_payload_invalid_ttl(self) self.assertEqual("Invalid TTL [-1]", status.get_message()) def test_validate_uAttributes_for_rpc_response_message_payload_missing_sink_and_missing_requestId(self): - attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS4, UUri(), UUID()).build() + attributes = UAttributesBuilder.response(build_source(), UUri(), UPriority.UPRIORITY_CS4, UUID()).build() validator = Validators.RESPONSE.validator() status = validator.validate(attributes) @@ -237,7 +241,7 @@ def test_validate_uAttributes_for_rpc_response_message_payload_missing_sink_and_ self.assertEqual("Missing Sink,Missing correlationId", status.get_message()) def test_validate_uAttributes_for_rpc_response_message_payload_invalid_permission_level(self): - attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS4, build_sink(), + attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS4, Factories.UPROTOCOL.create()).withPermissionLevel(-42).build() validator = Validators.RESPONSE.validator() @@ -246,7 +250,7 @@ def test_validate_uAttributes_for_rpc_response_message_payload_invalid_permissio self.assertEqual("Invalid Permission Level", status.get_message()) def test_validate_uAttributes_for_rpc_response_message_payload_invalid_communication_status(self): - attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS4, build_sink(), + attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS4, Factories.UPROTOCOL.create()).withCommStatus(-42).build() validator = Validators.RESPONSE.validator() @@ -255,7 +259,7 @@ def test_validate_uAttributes_for_rpc_response_message_payload_invalid_communica self.assertEqual("Invalid Communication Status Code", status.get_message()) def test_validate_uAttributes_for_rpc_response_message_payload_missing_request_id(self): - attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS4, build_sink(), UUID()).build() + attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS4, UUID()).build() validator = Validators.RESPONSE.validator() status = validator.validate(attributes) @@ -275,43 +279,35 @@ def test_validate_uAttributes_for_rpc_response_message_payload_missing_request_i # ---- def test_validate_uAttributes_for_publish_message_payload_not_expired(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).build() validator = Validators.PUBLISH.validator() - status = validator.is_expired(attributes) - self.assertTrue(status.is_success()) - self.assertEqual("", status.get_message()) + self.assertFalse(validator.is_expired(attributes)) def test_validate_uAttributes_for_publish_message_payload_not_expired_withTtl_zero(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withTtl(0).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withTtl(0).build() validator = Validators.PUBLISH.validator() - status = validator.is_expired(attributes) - self.assertTrue(status.is_success()) - self.assertEqual("", status.get_message()) + self.assertFalse(validator.is_expired(attributes)) def test_validate_uAttributes_for_publish_message_payload_not_expired_withTtl(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withTtl(10000).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withTtl(10000).build() validator = Validators.PUBLISH.validator() - status = validator.is_expired(attributes) - self.assertTrue(status.is_success()) - self.assertEqual("", status.get_message()) + self.assertFalse(validator.is_expired(attributes)) def test_validate_uAttributes_for_publish_message_payload_expired_withTtl(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withTtl(1).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withTtl(1).build() time.sleep(0.8) validator = Validators.PUBLISH.validator() - status = validator.is_expired(attributes) - self.assertTrue(status.is_failure()) - self.assertEqual("Payload is expired", status.get_message()) + self.assertTrue(validator.is_expired(attributes)) # ---- def test_validating_publish_invalid_ttl_attribute(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withTtl(-1).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withTtl(-1).build() validator = Validators.PUBLISH.validator() status = validator.validate_ttl(attributes) @@ -319,7 +315,7 @@ def test_validating_publish_invalid_ttl_attribute(self): self.assertEqual("Invalid TTL [-1]", status.get_message()) def test_validating_valid_ttl_attribute(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withTtl(100).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withTtl(100).build() validator = Validators.PUBLISH.validator() status = validator.validate_ttl(attributes) @@ -327,7 +323,7 @@ def test_validating_valid_ttl_attribute(self): def test_validating_invalid_sink_attribute(self): uri = LongUriSerializer().deserialize("//") - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withSink(uri).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withSink(uri).build() validator = Validators.PUBLISH.validator() status = validator.validate_sink(attributes) self.assertTrue(status.is_failure()) @@ -337,7 +333,7 @@ def test_validating_valid_sink_attribute(self): uri = UUri(authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), resource=UResourceBuilder.for_rpc_response()) - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withSink(uri).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withSink(uri).build() validator = Validators.PUBLISH.validator() status = validator.validate_sink(attributes) @@ -356,7 +352,7 @@ def test_validating_valid_sink_attribute(self): # self.assertEqual("Invalid UUID", status.get_message()) def test_validating_valid_ReqId_attribute(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withReqId( + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withReqId( Factories.UPROTOCOL.create()).build() validator = Validators.PUBLISH.validator() @@ -364,7 +360,7 @@ def test_validating_valid_ReqId_attribute(self): self.assertEqual(ValidationResult.success(), status) def test_validating_invalid_PermissionLevel_attribute(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withPermissionLevel(-1).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withPermissionLevel(-1).build() validator = Validators.PUBLISH.validator() status = validator.validate_permission_level(attributes) @@ -372,14 +368,14 @@ def test_validating_invalid_PermissionLevel_attribute(self): self.assertEqual("Invalid Permission Level", status.get_message()) def test_validating_valid_PermissionLevel_attribute(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withPermissionLevel(3).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withPermissionLevel(3).build() validator = Validators.PUBLISH.validator() status = validator.validate_permission_level(attributes) self.assertEqual(ValidationResult.success(), status) def test_validating_valid_PermissionLevel_attribute_invalid(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withPermissionLevel(0).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withPermissionLevel(0).build() validator = Validators.PUBLISH.validator() status = validator.validate_permission_level(attributes) @@ -387,7 +383,7 @@ def test_validating_valid_PermissionLevel_attribute_invalid(self): self.assertEqual("Invalid Permission Level", status.get_message()) def test_validating_invalid_commstatus_attribute(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withCommStatus(100).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withCommStatus(100).build() validator = Validators.PUBLISH.validator() status = validator.validate_comm_status(attributes) @@ -395,14 +391,14 @@ def test_validating_invalid_commstatus_attribute(self): self.assertEqual("Invalid Communication Status Code", status.get_message()) def test_validating_valid_commstatus_attribute(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withCommStatus(UCode.ABORTED).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withCommStatus(UCode.ABORTED).build() validator = Validators.PUBLISH.validator() status = validator.validate_comm_status(attributes) self.assertEqual(ValidationResult.success(), status) def test_validating_request_message_types(self): - attributes = UAttributesBuilder.request(UPriority.UPRIORITY_CS6, build_sink(), 100).build() + attributes = UAttributesBuilder.request(build_source(), build_sink(), UPriority.UPRIORITY_CS6, 100).build() validator = UAttributesValidator.get_validator(attributes) self.assertEqual("UAttributesValidator.Request", str(validator)) @@ -411,7 +407,7 @@ def test_validating_request_message_types(self): self.assertEqual("", status.get_message()) def test_validating_request_validator_with_wrong_messagetype(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS6).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS6).build() validator = Validators.REQUEST.validator() self.assertEqual("UAttributesValidator.Request", str(validator)) @@ -420,8 +416,8 @@ def test_validating_request_validator_with_wrong_messagetype(self): self.assertEqual("Wrong Attribute Type [UMESSAGE_TYPE_PUBLISH],Missing TTL,Missing Sink", status.get_message()) def test_validating_request_validator_with_wrong_bad_ttl(self): - attributes = UAttributesBuilder.request(UPriority.UPRIORITY_CS6, - LongUriSerializer().deserialize("/hartley/1/rpc.response"), -1).build() + attributes = UAttributesBuilder.request(build_source(), LongUriSerializer().deserialize("/hartley/1/rpc.response"), + UPriority.UPRIORITY_CS6, -1).build() validator = Validators.REQUEST.validator() self.assertEqual("UAttributesValidator.Request", str(validator)) @@ -430,9 +426,8 @@ def test_validating_request_validator_with_wrong_bad_ttl(self): self.assertEqual("Invalid TTL [-1]", status.get_message()) def test_validating_response_validator_with_wrong_bad_ttl(self): - attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS6, - LongUriSerializer().deserialize("/hartley/1/rpc.response"), - Factories.UPROTOCOL.create()).withTtl(-1).build() + attributes = UAttributesBuilder.response(build_source(), LongUriSerializer().deserialize("/hartley/1/rpc.response"), + UPriority.UPRIORITY_CS6, Factories.UPROTOCOL.create()).withTtl(-1).build() validator = Validators.RESPONSE.validator() self.assertEqual("UAttributesValidator.Response", str(validator)) @@ -441,7 +436,7 @@ def test_validating_response_validator_with_wrong_bad_ttl(self): self.assertEqual("Invalid TTL [-1]", status.get_message()) def test_validating_publish_validator_with_wrong_messagetype(self): - attributes = UAttributesBuilder.request(UPriority.UPRIORITY_CS6, build_sink(), 1000).build() + attributes = UAttributesBuilder.request(build_source(), build_sink(), UPriority.UPRIORITY_CS6, 1000).build() validator = Validators.PUBLISH.validator() self.assertEqual("UAttributesValidator.Publish", str(validator)) status = validator.validate(attributes) @@ -449,7 +444,7 @@ def test_validating_publish_validator_with_wrong_messagetype(self): self.assertEqual("Wrong Attribute Type [UMESSAGE_TYPE_REQUEST]", status.get_message()) def test_validating_response_validator_with_wrong_messagetype(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS6).build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS6).build() validator = Validators.RESPONSE.validator() self.assertEqual("UAttributesValidator.Response", str(validator)) @@ -459,7 +454,7 @@ def test_validating_response_validator_with_wrong_messagetype(self): status.get_message()) def test_validating_request_containing_token(self): - attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withToken("null").build() + attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withToken("null").build() validator = UAttributesValidator.get_validator(attributes) self.assertEqual("UAttributesValidator.Publish", str(validator)) @@ -468,7 +463,7 @@ def test_validating_request_containing_token(self): def test_valid_request_methoduri_in_sink(self): sink = LongUriSerializer().deserialize("/test.service/1/rpc.method") - attributes = UAttributesBuilder.request(UPriority.UPRIORITY_CS0, sink, 3000).build() + attributes = UAttributesBuilder.request(build_source(), sink, UPriority.UPRIORITY_CS0, 3000).build() validator = UAttributesValidator.get_validator(attributes) self.assertEqual("UAttributesValidator.Request", str(validator)) status = validator.validate(attributes) @@ -476,7 +471,7 @@ def test_valid_request_methoduri_in_sink(self): def test_invalid_request_methoduri_in_sink(self): sink = LongUriSerializer().deserialize("/test.client/1/test.response") - attributes = UAttributesBuilder.request(UPriority.UPRIORITY_CS0, sink, 3000).build() + attributes = UAttributesBuilder.request(build_source(), sink, UPriority.UPRIORITY_CS0, 3000).build() validator = UAttributesValidator.get_validator(attributes) self.assertEqual("UAttributesValidator.Request", str(validator)) status = validator.validate(attributes) @@ -484,7 +479,7 @@ def test_invalid_request_methoduri_in_sink(self): def test_valid_response_uri_in_sink(self): sink = LongUriSerializer().deserialize("/test.client/1/rpc.response") - attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS0, sink, Factories.UPROTOCOL.create()).build() + attributes = UAttributesBuilder.response(build_source(), sink, UPriority.UPRIORITY_CS0, Factories.UPROTOCOL.create()).build() validator = UAttributesValidator.get_validator(attributes) self.assertEqual("UAttributesValidator.Response", str(validator)) status = validator.validate(attributes) @@ -492,7 +487,7 @@ def test_valid_response_uri_in_sink(self): def test_invalid_response_uri_in_sink(self): sink = LongUriSerializer().deserialize("/test.client/1/rpc.method") - attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS0, sink, Factories.UPROTOCOL.create()).build() + attributes = UAttributesBuilder.response(build_source(), sink, UPriority.UPRIORITY_CS0, Factories.UPROTOCOL.create()).build() validator = UAttributesValidator.get_validator(attributes) self.assertEqual("UAttributesValidator.Response", str(validator)) status = validator.validate(attributes) diff --git a/tests/test_uri/test_serializer/test_longuriserializer.py b/tests/test_uri/test_serializer/test_longuriserializer.py index 7b89c8f..fddac3e 100644 --- a/tests/test_uri/test_serializer/test_longuriserializer.py +++ b/tests/test_uri/test_serializer/test_longuriserializer.py @@ -28,7 +28,7 @@ import unittest from uprotocol.proto.uri_pb2 import UEntity, UUri, UAuthority, UResource -from uprotocol.uri.builder.uresource_builder import UResourceBuilder +from uprotocol.uri.factory.uresource_builder import UResourceBuilder from uprotocol.uri.serializer.longuriserializer import LongUriSerializer from uprotocol.uri.validator.urivalidator import UriValidator diff --git a/tests/test_uri/test_serializer/test_microuriserializer.py b/tests/test_uri/test_serializer/test_microuriserializer.py index 2b66f8e..9df8bb0 100644 --- a/tests/test_uri/test_serializer/test_microuriserializer.py +++ b/tests/test_uri/test_serializer/test_microuriserializer.py @@ -28,7 +28,7 @@ import unittest from uprotocol.proto.uri_pb2 import UEntity, UUri, UAuthority, UResource -from uprotocol.uri.builder.uresource_builder import UResourceBuilder +from uprotocol.uri.factory.uresource_builder import UResourceBuilder from uprotocol.uri.serializer.microuriserializer import MicroUriSerializer from uprotocol.uri.validator.urivalidator import UriValidator diff --git a/tests/test_uri/test_serializer/test_uriserializer.py b/tests/test_uri/test_serializer/test_uriserializer.py index 5f0ef32..9ad89f3 100644 --- a/tests/test_uri/test_serializer/test_uriserializer.py +++ b/tests/test_uri/test_serializer/test_uriserializer.py @@ -38,7 +38,8 @@ class TestUriSerializer(unittest.TestCase): def test_build_resolved_valid_long_micro_uri(self): long_uuri = UUri(authority=UAuthority(name="testauth"), entity=UEntity(name="neelam"), resource=UResource(name="rpc", instance="response")) - micro_uuri = UUri(entity=UEntity(id=29999, version_major=254), resource=UResource(id=39999)) + micro_uuri = UUri(authority=UAuthority(id="abcdefg".encode('UTF-8')), + entity=UEntity(id=29999, version_major=254), resource=UResource(id=39999)) microuri = MicroUriSerializer().serialize(micro_uuri) longuri = LongUriSerializer().serialize(long_uuri) resolved_uuri = LongUriSerializer().build_resolved(longuri, microuri) diff --git a/tests/test_uri/test_validator/test_urivalidator.py b/tests/test_uri/test_validator/test_urivalidator.py index c674235..691a278 100644 --- a/tests/test_uri/test_validator/test_urivalidator.py +++ b/tests/test_uri/test_validator/test_urivalidator.py @@ -333,7 +333,7 @@ def test_all_valid_rpc_response_uris(self): self.assertTrue(status.is_success) def test_valid_rpc_response_uri(self): - uuri = UUri(entity=UEntity(name="neelam"),resource=UResource(name="rpc",id=19999)) + uuri = UUri(entity=UEntity(name="neelam"),resource=UResource(name="rpc",id=0,instance="response")) status = UriValidator.validate_rpc_response(uuri) self.assertTrue(UriValidator.is_rpc_response(uuri)) self.assertTrue(status.is_success) diff --git a/uprotocol/cloudevent/datamodel/ucloudeventattributes.py b/uprotocol/cloudevent/datamodel/ucloudeventattributes.py index d30f3e3..c28244e 100644 --- a/uprotocol/cloudevent/datamodel/ucloudeventattributes.py +++ b/uprotocol/cloudevent/datamodel/ucloudeventattributes.py @@ -48,6 +48,7 @@ def __init__(self, priority: UPriority, hash_value: str = None, ttl: int = None, self.priority = priority self.ttl = ttl self.token = token + self.traceparent = None @staticmethod def empty(): @@ -65,7 +66,8 @@ def is_empty(self): building a CloudEvent. """ return (self.hash is None or self.hash.isspace()) and (self.ttl is None) and ( - self.token is None or self.token.isspace()) and (self.priority is None or self.priority.isspace()) + self.token is None or self.token.isspace()) and (self.priority is None or self.priority.isspace()) and ( + self.traceparent is None or self.traceparent.isspace()) def get_hash(self) -> str: """ @@ -102,14 +104,15 @@ def __eq__(self, other): return False return ( self.hash == other.hash and self.priority == other.priority and self.ttl == other.ttl and self.token - == other.token) + == other.token and self.traceparent == other.traceparent) def __hash__(self): - return hash((self.hash, self.priority, self.ttl, self.token)) + return hash((self.hash, self.priority, self.ttl, self.token, self.traceparent)) def __str__(self): + traceparent_string = f", traceparent='{self.traceparent}'" if self.traceparent else "" return f"UCloudEventAttributes{{hash='{self.hash}', priority={self.priority}," \ - f" ttl={self.ttl}, token='{self.token}'}}" + f" ttl={self.ttl}, token='{self.token}'{traceparent_string}}}" class UCloudEventAttributesBuilder: @@ -122,6 +125,7 @@ def __init__(self): self.priority = None self.ttl = None self.token = None + self.traceparent = None def with_hash(self, hash_value: str): """ @@ -161,6 +165,16 @@ def with_token(self, token: str): self.token = token return self + + def with_traceparent(self, traceparent: str): + """ + An identifier used to correlate observability across related events. + @param traceparent: identifier + @return Returns a traceparent attribute. + """ + self.traceparent = traceparent + return self + def build(self): """ Construct the UCloudEventAttributes from the builder.

@@ -172,5 +186,5 @@ def build(self): if __name__ == "__main__": # Example usage: attributes = UCloudEventAttributesBuilder().with_hash("abc123").with_priority(UPriority.UPRIORITY_CS0).with_ttl( - 1000).with_token("xyz456").build() + 1000).with_token("xyz456").with_traceparent("123456").build() print(attributes) diff --git a/uprotocol/cloudevent/factory/cloudeventfactory.py b/uprotocol/cloudevent/factory/cloudeventfactory.py index c80ed74..3788702 100644 --- a/uprotocol/cloudevent/factory/cloudeventfactory.py +++ b/uprotocol/cloudevent/factory/cloudeventfactory.py @@ -105,7 +105,7 @@ def failed_response(application_uri_for_rpc: str, service_method_uri: str, reque @param service_method_uri: The uri for the method that was called on the service Ex. :/body.access/1/rpc.UpdateDoor @param request_id:The cloud event id from the original request cloud event that this response if for. - @param communication_status: A {@link Code} value that indicates of a platform communication error while + @param communication_status: A Code value that indicates of a platform communication error while delivering this CloudEvent. @param attributes:Additional attributes such as ttl, hash and priority. @return:Returns an response CloudEvent Response for the use case of RPC Response message that failed. @@ -182,7 +182,7 @@ def build_base_cloud_event(id: str, source: str, proto_payload_bytes: bytes, pro @param id:Event unique identifier. @param source: Identifies who is sending this event in the format of a uProtocol URI that can be built from a - {@link UUri} object. + UUri object. @param proto_payload_bytes:The serialized Event data with the content type of "application/x-protobuf". @param proto_payload_schema:The schema of the proto payload bytes, for example you can use protoPayload.getTypeUrl() on your service/app object. diff --git a/uprotocol/cloudevent/factory/ucloudevent.py b/uprotocol/cloudevent/factory/ucloudevent.py index 78af030..7eb3a14 100644 --- a/uprotocol/cloudevent/factory/ucloudevent.py +++ b/uprotocol/cloudevent/factory/ucloudevent.py @@ -33,6 +33,8 @@ from uprotocol.proto.ustatus_pb2 import UCode from uprotocol.proto.uattributes_pb2 import UMessageType, UPriority, UAttributes +from uprotocol.proto.upayload_pb2 import UPayload +from uprotocol.proto.uri_pb2 import UUri from uprotocol.uri.serializer.longuriserializer import LongUriSerializer from uprotocol.uuid.factory.uuidutils import UUIDUtils from uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer @@ -175,7 +177,7 @@ def get_communication_status(ce: CloudEvent) -> int: cloudEvent, it will be indicated in this attribute. If the attribute does not exist, it is assumed that everything was UCode.OK_VALUE.

@param ce: CloudEvent with the platformError to be extracted. - @return: Returns a {@link UCode} value that indicates of a platform communication error while delivering this + @return: Returns a UCode value that indicates of a platform communication error while delivering this CloudEvent or UCode.OK_VALUE. """ try: @@ -299,8 +301,8 @@ def unpack(ce: CloudEvent, clazz): is implemented. If anything goes wrong, an empty optional will be returned.

Example:
Optional<SomeMessage> unpacked = UCloudEvent.unpack(cloudEvent, SomeMessage.class);


@param ce:CloudEvent containing the payload to extract. - @param clazz:The class that extends {@link Message} that the payload is extracted into. - @return: Returns a {@link Message} payload of the class type that is provided. + @param clazz:The class that extends Message that the payload is extracted into. + @return: Returns a Message payload of the class type that is provided. """ try: any_obj = UCloudEvent.get_payload(ce) @@ -367,7 +369,8 @@ def get_message_type(ce_type): @staticmethod def get_content_type_from_upayload_format(payload_format: UPayloadFormat): """ - Retrieves the content type string based on the provided UPayloadFormat enumeration.

+ Retrieves the string representation of the data content type based on the provided UPayloadFormat.
+ This method uses the uProtocol mimeType custom options declared in upayload.proto. @param payload_format The UPayloadFormat enumeration representing the payload format. @return The corresponding content type string based on the payload format. """ @@ -382,12 +385,13 @@ def get_content_type_from_upayload_format(payload_format: UPayloadFormat): @staticmethod def get_upayload_format_from_content_type(contenttype: str): """ - Retrieves the payload format enumeration based on the provided content type.

- @param contenttype The content type string representing the format of the payload. + Retrieves the payload format enumeration based on the provided string representation of the data content type
+ This method uses the uProtocol mimeType custom options declared in upayload.proto. + @param contentType The content type string representing the format of the payload. @return The corresponding UPayloadFormat enumeration based on the content type. """ if contenttype is None: - return UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF + return UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY content_type_mapping = { "application/json": UPayloadFormat.UPAYLOAD_FORMAT_JSON, @@ -407,18 +411,27 @@ def fromMessage(message: UMessage) -> CloudEvent: @param message The UMessage protobuf containing the data @return returns the cloud event """ + attributes = message.attributes + payload = message.payload + + if attributes is None: + attributes = UAttributes() + if payload is None: + payload = UPayload() + data = bytearray() json_attributes = {"id": LongUuidSerializer.instance().serialize(attributes.id), "source": LongUriSerializer().serialize(message.attributes.source), "type": UCloudEvent.get_event_type(attributes.type)} - contenttype = UCloudEvent.get_content_type_from_upayload_format(message.payload.format) + contenttype = UCloudEvent.get_content_type_from_upayload_format(payload.format) + if contenttype: json_attributes['datacontenttype'] = "application/x-protobuf" # IMPORTANT: Currently, ONLY the VALUE format is supported in the SDK! - if message.payload.HasField('value'): - data = message.payload.value + if payload.HasField('value'): + data = payload.value if attributes.HasField('ttl'): json_attributes['ttl'] = attributes.ttl if attributes.priority > 0: @@ -447,14 +460,13 @@ def toMessage(event: CloudEvent) -> UMessage: """ if event is None: raise ValueError("Cloud Event can't be None") - source = LongUriSerializer().deserialize(UCloudEvent.get_source(event)) + payload = UPayload( format=UCloudEvent.get_upayload_format_from_content_type(UCloudEvent.get_data_content_type(event)), value=UCloudEvent.get_payload(event).SerializeToString()) attributes = UAttributes(id=LongUuidSerializer.instance().deserialize(UCloudEvent.get_id(event)), - type=UCloudEvent.get_message_type(UCloudEvent.get_type(event))) - attributes.source.CopyFrom(source) - + type=UCloudEvent.get_message_type(UCloudEvent.get_type(event)), + source=LongUriSerializer().deserialize(UCloudEvent.get_source(event))) if UCloudEvent.has_communication_status_problem(event): attributes.commstatus = UCloudEvent.get_communication_status(event) priority = UCloudEvent.get_priority(event) diff --git a/uprotocol/rpc/rpcclient.py b/uprotocol/rpc/rpcclient.py index 1f16833..65937d3 100644 --- a/uprotocol/rpc/rpcclient.py +++ b/uprotocol/rpc/rpcclient.py @@ -28,9 +28,9 @@ from abc import ABC, abstractmethod from concurrent.futures import Future -from uprotocol.proto.uattributes_pb2 import UAttributes from uprotocol.proto.uri_pb2 import UUri from uprotocol.proto.upayload_pb2 import UPayload +from uprotocol.rpc.calloptions import CallOptions class RpcClient(ABC): @@ -45,13 +45,16 @@ class RpcClient(ABC): """ @abstractmethod - def invoke_method(self, topic: UUri, payload: UPayload, attributes: UAttributes) -> Future: + def invoke_method(self, methodUri: UUri, request_payload: UPayload, options: CallOptions) -> Future: """ - Support for RPC method invocation.

- - @param topic: topic of the method to be invoked (i.e. the name of the API we are calling). - @param payload:The request message to be sent to the server. - @param attributes: metadata for the method invocation (i.e. priority, timeout, etc.) + API for clients to invoke a method (send an RPC request) and receive the response (the returned + Future UMessage.
+ Client will set method to be the URI of the method they want to invoke, + payload to the request message, and attributes with the various metadata for the + method invocation. + @param methodUri The method URI to be invoked, ex (long form): /example.hello_world/1/rpc.SayHello. + @param requestPayload The request message to be sent to the server. + @param options RPC method invocation call options, see CallOptions @return: Returns the CompletableFuture with the result or exception. """ pass diff --git a/uprotocol/rpc/rpcmapper.py b/uprotocol/rpc/rpcmapper.py index dcee680..601c8e0 100644 --- a/uprotocol/rpc/rpcmapper.py +++ b/uprotocol/rpc/rpcmapper.py @@ -38,32 +38,32 @@ class RpcMapper: """ RPC Wrapper is an interface that provides static methods to be able to wrap an RPC request with an RPC Response ( - uP-L2). APIs that return Message assumes that the payload is protobuf serialized com.google.protobuf.Any ( - UPayloadFormat.PROTOBUF) and will barf if anything else is passed + uP-L2). APIs that return Message assumes that the message is protobuf serialized com.google.protobuf.Any ( + UMessageFormat.PROTOBUF) and will barf if anything else is passed """ @staticmethod - def map_response(payload_future: Future, expected_cls): + def map_response(message_future: Future, expected_cls): """ - Map a response of CompletableFuture<UPayload> from Link into a CompletableFuture containing the + Map a response of CompletableFuture<UMessage> from Link into a CompletableFuture containing the declared expected return type of the RPC method or an exception.

- @param response_future:CompletableFuture<UPayload> response from uTransport. + @param response_future:CompletableFuture<UMessage> response from uTransport. @param expected_cls:The class name of the declared expected return type of the RPC method. @return:Returns a CompletableFuture containing the declared expected return type of the RPC method or an exception. """ response_future: Future = Future() - def handle_response(payload): + def handle_response(message): nonlocal response_future - payload = payload.result() - if not payload: + message = message.result() + if not message or not message.HasField('payload'): response_future.set_exception( RuntimeError(f"Server returned a null payload. Expected {expected_cls.__name__}")) try: any_message = any_pb2.Any() - any_message.ParseFromString(payload.value) + any_message.ParseFromString(message.payload.value) if any_message.Is(expected_cls.DESCRIPTOR): response_future.set_result(RpcMapper.unpack_payload(any_message, expected_cls)) else: @@ -74,7 +74,7 @@ def handle_response(payload): except Exception as e: response_future.set_exception(RuntimeError(f"{str(e)} [{UStatus.__name__}]")) - payload_future.add_done_callback(handle_response) + message_future.add_done_callback(handle_response) return response_future @@ -89,19 +89,19 @@ def map_response_to_result(response_future: Future, expected_cls): or a UStatus containing any errors. """ - def handle_response(payload): - if payload.exception(): - exception = payload.exception() + def handle_response(message): + if message.exception(): + exception = message.exception() return RpcResult.failure(value=exception, message=str(exception)) - payload = payload.result() - if not payload: + message = message.result() + if not message or not message.HasField('payload'): exception = RuntimeError(f"Server returned a null payload. Expected {expected_cls.__name__}") return RpcResult.failure(value=exception, message=str(exception)) try: any_message = any_pb2.Any() - any_message.ParseFromString(payload.value) + any_message.ParseFromString(message.payload.value) if any_message.Is(expected_cls.DESCRIPTOR): if expected_cls == UStatus: @@ -136,11 +136,11 @@ def calculate_status_result(payload): @staticmethod def unpack_payload(payload, expected_cls): """ - Unpack a payload of type {@link Any} into an object of type T, which is what was packing into the {@link Any} + Unpack a payload of type Any into an object of type T, which is what was packing into the Any object.

- @param payload:an {@link Any} message containing a type of expectedClazz. - @param expected_cls:The class name of the object packed into the {@link Any} - @return:Returns an object of type T and of the class name specified, that was packed into the {@link Any} + @param payload:an Any message containing a type of expectedClazz. + @param expected_cls:The class name of the object packed into the Any + @return:Returns an object of type T and of the class name specified, that was packed into the Any object. """ try: diff --git a/uprotocol/rpc/rpcserver.py b/uprotocol/rpc/rpcserver.py new file mode 100644 index 0000000..58ba748 --- /dev/null +++ b/uprotocol/rpc/rpcserver.py @@ -0,0 +1,59 @@ +# ------------------------------------------------------------------------- + +# Copyright (c) 2023 General Motors GTO LLC +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# SPDX-FileType: SOURCE +# SPDX-FileCopyrightText: 2023 General Motors GTO LLC +# SPDX-License-Identifier: Apache-2.0 + +# ------------------------------------------------------------------------- + +from abc import ABC, abstractmethod + +from uprotocol.proto.uri_pb2 import UUri +from uprotocol.proto.ustatus_pb2 import UStatus +from uprotocol.rpc.urpclistener import URpcListener + +class RpcServer(ABC): + """ + RpcServer is an interface called by uServices to register method listeners for incoming RPC requests + from clients. + """ + + @abstractmethod + def register_rpc_listener(method: UUri, listener: URpcListener) -> UStatus: + ''' + Register a listener for a particular method URI to be notified when requests are sent against said method. +

Note: Only one listener is allowed to be registered per method URI. + @param method Uri for the method to register the listener for. + @param listener The listener for handling the request method. + @return Returns the status of registering the RpcListener. + ''' + pass + + @abstractmethod + def unregister_rpc_listener(method: UUri, listener: URpcListener) -> UStatus: + ''' + Unregister an RPC listener for a given method Uri. Messages arriving on this topic will no longer be processed + by this listener. + @param method Resolved UUri for where the listener was registered to receive messages from. + @param listener The method to execute to process the date for the topic. + @return Returns status of registering the RpcListener + ''' + pass \ No newline at end of file diff --git a/uprotocol/rpc/urpclistener.py b/uprotocol/rpc/urpclistener.py new file mode 100644 index 0000000..a09ed48 --- /dev/null +++ b/uprotocol/rpc/urpclistener.py @@ -0,0 +1,46 @@ +# ------------------------------------------------------------------------- + +# Copyright (c) 2023 General Motors GTO LLC +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# SPDX-FileType: SOURCE +# SPDX-FileCopyrightText: 2023 General Motors GTO LLC +# SPDX-License-Identifier: Apache-2.0 + +# ------------------------------------------------------------------------- + +from abc import ABC, abstractmethod +from concurrent.futures import Future + +from uprotocol.proto.umessage_pb2 import UMessage + +class URpcListener(ABC): + ''' + uService (servers) implement this to receive requests messages from clients.
+ The service must implement the onReceive(UMessage, CompletableFuture) method to handle + the request and then complete the future passed to the method that triggers the uLink library to + send (over the transport) the response. + ''' + + @abstractmethod + def on_receive(message: UMessage, response_future: Future) -> None: + ''' + Method called to handle/process events. + @param message Message received. + ''' + pass \ No newline at end of file diff --git a/uprotocol/transport/builder/uattributesbuilder.py b/uprotocol/transport/builder/uattributesbuilder.py index 5a55ee1..40cc194 100644 --- a/uprotocol/transport/builder/uattributesbuilder.py +++ b/uprotocol/transport/builder/uattributesbuilder.py @@ -40,7 +40,8 @@ class UAttributesBuilder: @param priority uProtocol Prioritization classifications. """ - def __init__(self, id: UUID, type: UMessageType, priority: UPriority): + def __init__(self, source: UUri, id: UUID, type: UMessageType, priority: UPriority): + self.source = source self.id = id self.type = UMessageType.Name(type) self.priority = priority @@ -50,42 +51,52 @@ def __init__(self, id: UUID, type: UMessageType, priority: UPriority): self.plevel = None self.commstatus = None self.reqid = None + self.traceparent = None @staticmethod - def publish(priority: UPriority): + def publish(source: UUri, priority: UPriority): """ Construct a UAttributesBuilder for a publish message. + @param source Source address of the message. @param priority The priority of the message. @return Returns the UAttributesBuilder with the configured priority. """ + if source is None: + raise ValueError("Source cannot be None.") if priority is None: raise ValueError("UPriority cannot be None.") - return UAttributesBuilder(Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_PUBLISH, priority) + return UAttributesBuilder(source, Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_PUBLISH, priority) @staticmethod - def notification(priority: UPriority, sink: UUri): + def notification(source: UUri, sink: UUri, priority: UPriority): """ Construct a UAttributesBuilder for a notification message. + @param source Source address of the message. + @param sink The destination URI. @param priority The priority of the message. - @param sink The destination URI. - @return Returns the UAttributesBuilder with the configured priority and sink. + @return Returns the UAttributesBuilder with the configured source, priority and sink. """ + if source is None: + raise ValueError("Source cannot be None.") if priority is None: raise ValueError("UPriority cannot be null.") if sink is None: raise ValueError("sink cannot be null.") - return UAttributesBuilder(Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_PUBLISH, priority).withSink( - sink) + return UAttributesBuilder(source, Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_PUBLISH, priority + ).withSink(sink) @staticmethod - def request(priority: UPriority, sink: UUri, ttl: int): + def request(source: UUri, sink: UUri, priority: UPriority, ttl: int): """ Construct a UAttributesBuilder for a request message. + @param source Source address of the message. + @param sink The destination URI. @param priority The priority of the message. - @param sink The destination URI. - @param ttl The time to live in milliseconds. + @param ttl The time to live in milliseconds. @return Returns the UAttributesBuilder with the configured priority, sink and ttl. """ + if source is None: + raise ValueError("Source cannot be None.") if priority is None: raise ValueError("UPriority cannot be null.") if sink is None: @@ -93,16 +104,17 @@ def request(priority: UPriority, sink: UUri, ttl: int): if ttl is None: raise ValueError("ttl cannot be null.") - return UAttributesBuilder(Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_REQUEST, priority).withTtl( - ttl).withSink(sink) + return UAttributesBuilder(source, Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_REQUEST, priority + ).withTtl(ttl).withSink(sink) @staticmethod - def response(priority: UPriority, sink: UUri, reqid: UUID): + def response(source: UUri, sink: UUri, priority: UPriority, reqid: UUID): """ Construct a UAttributesBuilder for a response message. + @param source Source address of the message. + @param sink The destination URI. @param priority The priority of the message. - @param sink The destination URI. - @param reqid The original request UUID used to correlate the response to the request. + @param reqid The original request UUID used to correlate the response to the request. @return Returns the UAttributesBuilder with the configured priority, sink and reqid. """ if priority is None: @@ -112,8 +124,8 @@ def response(priority: UPriority, sink: UUri, reqid: UUID): if reqid is None: raise ValueError("reqid cannot be null.") - return UAttributesBuilder(Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_RESPONSE, priority).withSink( - sink).withReqId(reqid) + return UAttributesBuilder(source, Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_RESPONSE, priority + ).withSink(sink).withReqId(reqid) def withTtl(self, ttl: int): """ @@ -134,7 +146,7 @@ def withToken(self, token: str): """ self.token = token return self - + def withSink(self, sink: UUri): """ Add the explicit destination URI. @@ -174,6 +186,17 @@ def withReqId(self, reqid: UUID): """ self.reqid = reqid return self + + def withTraceparent(self, traceparent: str): + """ + Add the traceparent. + + @param reqid the traceparent. + @return Returns the UAttributesBuilder with the configured traceparent. + """ + self.traceparent = traceparent + return self + def build(self): """ @@ -181,7 +204,7 @@ def build(self): @return Returns a constructed """ - attributes = UAttributes(id=self.id, type=self.type, priority=self.priority) + attributes = UAttributes(source=self.source, id=self.id, type=self.type, priority=self.priority) if self.sink is not None: attributes.sink.CopyFrom(self.sink) if self.ttl is not None: @@ -192,6 +215,8 @@ def build(self): attributes.commstatus = self.commstatus if self.reqid is not None: attributes.reqid.CopyFrom(self.reqid) + if self.traceparent is not None: + attributes.traceparent = self.traceparent if self.token != None: attributes.token = self.token return attributes diff --git a/uprotocol/transport/builder/upayloadbuilder.py b/uprotocol/transport/builder/upayloadbuilder.py new file mode 100644 index 0000000..fd547b1 --- /dev/null +++ b/uprotocol/transport/builder/upayloadbuilder.py @@ -0,0 +1,78 @@ +# ------------------------------------------------------------------------- + +# Copyright (c) 2023 General Motors GTO LLC +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# SPDX-FileType: SOURCE +# SPDX-FileCopyrightText: 2023 General Motors GTO LLC +# SPDX-License-Identifier: Apache-2.0 + +# ------------------------------------------------------------------------- + +import typing + +from uprotocol.proto.upayload_pb2 import UPayload, UPayloadFormat +from google.protobuf.any_pb2 import Any +from google.protobuf import message + +class UPayloadBuilder: + + def pack_to_any(message: message) -> UPayload: + ''' + Build a uPayload from google.protobuf.Message by stuffing the message into an Any. + @param message the message to pack + @return the UPayload + ''' + any_message = Any() + any_message.Pack(message) + return UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY, + value=any_message.SerializeToString()) + + def pack(message: message) -> UPayload: + ''' + Build a uPayload from google.protobuf.Message using protobuf PayloadFormat. + @param message the message to pack + @return the UPayload + ''' + return UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, + value=message.SerializeToString()) + + def unpack(payload: UPayload, clazz: typing.Type) -> typing.Type: + ''' + Unpack a uPayload into a google.protobuf.Message. + @param payload the payload to unpack + @param clazz the class of the message to unpack + @return the unpacked message + ''' + if payload is None or payload.value is None: + return None + try: + if payload.format == UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF: + message = clazz() + message.ParseFromString(payload.value) + return message + elif payload.format == UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY: + any_message = Any() + any_message.ParseFromString(payload.value) + message = clazz() + any_message.Unpack(message) + return message + else: + return None + except: + return None \ No newline at end of file diff --git a/uprotocol/transport/ulistener.py b/uprotocol/transport/ulistener.py index 1e69b69..7ccea5e 100644 --- a/uprotocol/transport/ulistener.py +++ b/uprotocol/transport/ulistener.py @@ -27,10 +27,7 @@ from abc import ABC, abstractmethod -from uprotocol.proto.uattributes_pb2 import UAttributes -from uprotocol.proto.uri_pb2 import UUri -from uprotocol.proto.upayload_pb2 import UPayload -from uprotocol.proto.ustatus_pb2 import UStatus +from uprotocol.proto.umessage_pb2 import UMessage class UListener(ABC): @@ -40,12 +37,9 @@ class UListener(ABC): """ @abstractmethod - def on_receive(self, topic: UUri, payload: UPayload, attributes: UAttributes) -> UStatus: + def on_receive(self, umsg: UMessage) -> None: """ - Method called to handle/process events.

- @param topic: Topic the underlying source of the message. - @param payload: Payload of the message. - @param attributes: Transportation attributes. - @return Returns an Ack every time a message is received and processed. + Method called to handle/process messages.

+ @param umsg: UMessage to be sent. """ pass diff --git a/uprotocol/transport/utransport.py b/uprotocol/transport/utransport.py index eaad0c2..c41ffee 100644 --- a/uprotocol/transport/utransport.py +++ b/uprotocol/transport/utransport.py @@ -27,12 +27,10 @@ from abc import ABC, abstractmethod -from uprotocol.proto.uattributes_pb2 import UAttributes -from uprotocol.proto.uri_pb2 import UEntity from uprotocol.proto.uri_pb2 import UUri from uprotocol.transport.ulistener import UListener -from uprotocol.proto.upayload_pb2 import UPayload from uprotocol.proto.ustatus_pb2 import UStatus +from uprotocol.proto.umessage_pb2 import UMessage class UTransport(ABC): """ @@ -43,46 +41,35 @@ class UTransport(ABC): """ @abstractmethod - def authenticate(self, u_entity: UEntity) -> UStatus: + def send(self, message: UMessage) -> UStatus: """ - API used to authenticate with the underlining transport layer that the uEntity passed matches the transport - specific identity. MUST pass a resolved UUri.

- @param u_entity:Resolved UEntity - @return: Returns OKSTATUS if authenticate was successful, FAILSTATUS if the calling uE is not authenticated. - """ - pass - - @abstractmethod - def send(self, topic: UUri, payload: UPayload, attributes: UAttributes) -> UStatus: - """ - Transmit UPayload to the topic using the attributes defined in UTransportAttributes.

- @param topic:Resolved UUri topic to send the payload to. - @param payload:Actual payload. - @param attributes:Additional transport attributes. - @return:Returns OKSTATUS if the payload has been successfully sent (ACK'ed), otherwise it returns FAILSTATUS - with the appropriate failure. + Send a message (in parts) over the transport. + @param message the UMessage to be sent. + @return Returns UStatus with UCode set to the status code (successful or failure). """ pass @abstractmethod def register_listener(self, topic: UUri, listener: UListener) -> UStatus: """ - Register listener to be called when UPayload is received for the specific topic.

- @param topic:Resolved UUri for where the message arrived via the underlying transport technology. - @param listener:The method to execute to process the date for the topic. - @return:Returns OKSTATUS if the listener is unregistered correctly, otherwise it returns FAILSTATUS with the - appropriate failure. + Register UListener for UUri topic to be called when a message is received. + @param topic UUri to listen for messages from. + @param listener The UListener that will be execute when the message is + received on the given UUri. + @return Returns UStatus with UCode.OK if the listener is registered + correctly, otherwise it returns with the appropriate failure. """ pass @abstractmethod def unregister_listener(self, topic: UUri, listener: UListener) -> UStatus: """ - Unregister a listener for a given topic. Messages arriving on this topic will no longer be processed by this - listener. - @param topic:Resolved UUri for where the listener was registered to receive messages from. - @param listener:The method to execute to process the date for the topic. - @return:Returns OKSTATUS if the listener is unregistered correctly, otherwise it returns FAILSTATUS with the - appropriate failure. + Unregister UListener for UUri topic. Messages arriving on this topic will + no longer be processed by this listener. + @param topic UUri to the listener was registered for. + @param listener The UListener that will no longer want to be registered to receive + messages. + @return Returns UStatus with UCode.OK if the listener is unregistered + correctly, otherwise it returns with the appropriate failure. """ pass diff --git a/uprotocol/transport/validate/uattributesvalidator.py b/uprotocol/transport/validate/uattributesvalidator.py index a92ffef..60a30f5 100644 --- a/uprotocol/transport/validate/uattributesvalidator.py +++ b/uprotocol/transport/validate/uattributesvalidator.py @@ -85,21 +85,21 @@ def validate(self, attributes: UAttributes) -> ValidationResult: return ValidationResult.success() @staticmethod - def is_expired(u_attributes: UAttributes): - """ - Indication if the Payload with these UAttributes is expired.

- @param u_attributes:UAttributes with time to live value. - @return: Returns a ValidationResult that is success meaning not expired or failed with a validation message or - expiration. - """ - maybe_ttl = u_attributes.ttl + def is_expired(u_attributes: UAttributes) -> bool: + ''' + Check the time-to-live attribute to see if it has expired.
+ 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 + ''' + ttl = u_attributes.ttl maybe_time = UUIDUtils.getTime(u_attributes.id) - ttl = maybe_ttl - if ttl <= 0: - return ValidationResult.success() - delta = int(time.time() * 1000)- maybe_time - return ValidationResult.failure("Payload is expired") if delta >= ttl else ValidationResult.success() + if not u_attributes.HasField('ttl') or maybe_time is None or ttl <=0: + return False + + return (maybe_time + ttl) < int(time.time() * 1000) @staticmethod def validate_ttl(attr: UAttributes) -> ValidationResult: diff --git a/uprotocol/uri/builder/__init__.py b/uprotocol/uri/factory/__init__.py similarity index 100% rename from uprotocol/uri/builder/__init__.py rename to uprotocol/uri/factory/__init__.py diff --git a/uprotocol/uri/factory/uentity_factory.py b/uprotocol/uri/factory/uentity_factory.py new file mode 100644 index 0000000..804aceb --- /dev/null +++ b/uprotocol/uri/factory/uentity_factory.py @@ -0,0 +1,62 @@ +# ------------------------------------------------------------------------- + +# Copyright (c) 2023 General Motors GTO LLC +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# SPDX-FileType: SOURCE +# SPDX-FileCopyrightText: 2023 General Motors GTO LLC +# SPDX-License-Identifier: Apache-2.0 + +# ------------------------------------------------------------------------- + +from uprotocol.proto.uri_pb2 import UEntity +from uprotocol.proto.uprotocol_options_pb2 import UProtocolOptions +from google.protobuf.descriptor_pb2 import ServiceDescriptorProto + +class UEntityFactory: + """ + Factory for creating UEntity objects. + """ + + @staticmethod + def from_proto(descriptor: ServiceDescriptorProto) -> UEntity: + ''' + Builds a UEntity for an protobuf generated code Service Descriptor. + @param descriptor The protobuf generated code Service Descriptor. + @return Returns a UEntity for an protobuf generated code Service Descriptor. + ''' + + if descriptor is None: + return UEntity() + + options = descriptor.options + + uentity = UEntity() + + name = options.getExtension(UProtocolOptions.name) + id = options.getExtension(UProtocolOptions.id) + version = options.getExtension(UProtocolOptions.version_major) + + if name is not None: + uentity.name = name + if id is not None: + uentity.id = id + if version is not None: + uentity.version_major = version + + return uentity \ No newline at end of file diff --git a/uprotocol/uri/builder/uresource_builder.py b/uprotocol/uri/factory/uresource_builder.py similarity index 84% rename from uprotocol/uri/builder/uresource_builder.py rename to uprotocol/uri/factory/uresource_builder.py index 6df1be5..cdf22fb 100644 --- a/uprotocol/uri/builder/uresource_builder.py +++ b/uprotocol/uri/factory/uresource_builder.py @@ -55,3 +55,13 @@ def from_id(id): raise ValueError("id cannot be None") return UResourceBuilder.for_rpc_request_with_id(id) if id < UResourceBuilder.MAX_RPC_ID else UResource(id=id) + + @staticmethod + def from_proto(instance): + ''' + Build a UResource from a protobuf message. This method will determine if + the message is a RPC or topic message based on the message type + @param message The protobuf message. + @return Returns a UResource for an RPC request. + ''' + pass \ No newline at end of file diff --git a/uprotocol/uri/serializer/longuriserializer.py b/uprotocol/uri/serializer/longuriserializer.py index a0afe26..784f05c 100644 --- a/uprotocol/uri/serializer/longuriserializer.py +++ b/uprotocol/uri/serializer/longuriserializer.py @@ -44,7 +44,7 @@ class LongUriSerializer(UriSerializer): def serialize(self, uri: UUri) -> str: """ - Support for serializing {@link UUri} objects into their String format.

+ Support for serializing UUri objects into their String format.

@param uri: UUri object to be serialized to the String format. @return:Returns the String format of the supplied UUri that can be used as a sink or a source in a uProtocol publish communication. @@ -197,6 +197,8 @@ def parse_from_string(resource_string: str) -> UResource: u_resource.instance = resource_instance if resource_message is not None: u_resource.message = resource_message + if "rpc" in resource_name and resource_instance is not None and "response" in resource_instance: + u_resource.id = 0 return u_resource diff --git a/uprotocol/uri/serializer/microuriserializer.py b/uprotocol/uri/serializer/microuriserializer.py index 80ab4c6..7b06c4f 100644 --- a/uprotocol/uri/serializer/microuriserializer.py +++ b/uprotocol/uri/serializer/microuriserializer.py @@ -32,7 +32,7 @@ from uprotocol.proto.uri_pb2 import UAuthority from uprotocol.proto.uri_pb2 import UEntity from uprotocol.proto.uri_pb2 import UUri -from uprotocol.uri.builder.uresource_builder import UResourceBuilder +from uprotocol.uri.factory.uresource_builder import UResourceBuilder from uprotocol.uri.serializer.uriserializer import UriSerializer from uprotocol.uri.validator.urivalidator import UriValidator @@ -84,29 +84,18 @@ def serialize(self, uri: UUri) -> bytes: os = io.BytesIO() os.write(bytes([self.UP_VERSION])) - # Determine the uAuthority type to be written - remote_case = "REMOTE_NOT_SET" - - if len(uri.authority.ip) > 0: - remote_case = "IP" - elif len(uri.authority.id) > 0: - remote_case = "ID" - elif len(uri.authority.name) > 0: - remote_case = "NAME" - if remote_case == "REMOTE_NOT_SET": - address_type = AddressType.LOCAL - elif remote_case == "IP": - length = len(uri.authority.ip) + if uri.authority.HasField('ip'): + length: int = len(uri.authority.ip) if length == 4: address_type = AddressType.IPv4 elif length == 16: address_type = AddressType.IPv6 else: return bytearray() - elif remote_case == "ID": + elif uri.authority.HasField('id'): address_type = AddressType.ID else: - return bytearray() + address_type = AddressType.LOCAL os.write(address_type.value.to_bytes(1, 'big')) @@ -135,9 +124,9 @@ def serialize(self, uri: UUri) -> bytes: os.write(len(uri.authority.id).to_bytes(1, 'big')) try: - if remote_case == "IP": + if uri.authority.HasField("ip"): os.write(uri.authority.ip) - elif remote_case == "ID": + elif uri.authority.HasField("id"): os.write(uri.authority.id) except Exception as e: print(e) # Handle the exception as needed diff --git a/uprotocol/uri/serializer/uriserializer.py b/uprotocol/uri/serializer/uriserializer.py index 49c527e..b68f9f2 100644 --- a/uprotocol/uri/serializer/uriserializer.py +++ b/uprotocol/uri/serializer/uriserializer.py @@ -60,7 +60,7 @@ def serialize(self, uri: UUri) -> T: def build_resolved(self, long_uri: str, micro_uri: bytes) -> UUri: """ - Build a fully resolved {@link UUri} from the serialized long format and the serializes micro format.

+ Build a fully resolved UUri from the serialized long format and the serializes micro format.

@param long_uri:UUri serialized as a Sting. @param micro_uri:UUri serialized as a byte[]. @return:Returns a UUri object serialized from one of the forms. diff --git a/uprotocol/uri/validator/urivalidator.py b/uprotocol/uri/validator/urivalidator.py index ca3a7f8..8be64f0 100644 --- a/uprotocol/uri/validator/urivalidator.py +++ b/uprotocol/uri/validator/urivalidator.py @@ -30,7 +30,7 @@ from uprotocol.proto.uri_pb2 import UResource from uprotocol.proto.uri_pb2 import UUri from uprotocol.validation.validationresult import ValidationResult - +from multipledispatch import dispatch class UriValidator: """ @@ -92,9 +92,12 @@ def validate_rpc_response(uri: UUri) -> ValidationResult: @staticmethod def is_empty(uri: UUri) -> bool: - if uri is None: - raise ValueError("Uri cannot be None.") - return not uri.HasField('authority') and not uri.HasField('entity') and not uri.HasField('resource') + ''' + Indicates that this URI is an empty as it does not contain authority, entity, and resource. + @param uri UUri to check if it is empty + @return Returns true if this URI is an empty container and has no valuable information in building uProtocol sinks or sources. + ''' + return uri is not None and not uri.HasField('authority') and not uri.HasField('entity') and not uri.HasField('resource') @staticmethod @@ -104,54 +107,87 @@ def is_rpc_method(uri: UUri) -> bool: @param uri: @return:Returns true if this resource specifies an RPC method call or RPC response. """ - if uri is None: - raise ValueError("Uri cannot be None.") return not UriValidator.is_empty(uri) and uri.resource.name == "rpc" and ( uri.resource.HasField('instance') and uri.resource.instance.strip() != "" or ( uri.resource.HasField('id') and uri.resource.id != 0)) @staticmethod - def is_resolved(uuri: UUri) -> bool: - if uuri is None: - raise ValueError("Uri cannot be None.") + def is_resolved(uri: UUri) -> bool: - return not UriValidator.is_empty(uuri) + return uri is not None and not UriValidator.is_empty(uri) and \ + UriValidator.is_long_form(uri) and UriValidator.is_micro_form(uri) @staticmethod - def is_rpc_response(uuri: UUri) -> bool: - if uuri is None: - raise ValueError("Uri cannot be None.") + def is_rpc_response(uri: UUri) -> bool: + if uri is None: + return False + + resource = uri.resource - return UriValidator.is_rpc_method(uuri) and ( - (uuri.resource.HasField('instance') and "response" in uuri.resource.instance) or ( - uuri.resource.HasField('id') and uuri.resource.id != 0)) + return "rpc" in resource.name and uri.HasField("resource") and "response" in resource.instance and resource.HasField("id") and resource.id == 0 @staticmethod - def is_micro_form(uuri: UUri) -> bool: + @dispatch(UUri) + def is_micro_form(uri: UUri) -> bool: """ Determines if this UUri can be serialized into a micro form UUri.

@param uuri: An UUri proto message object @return:Returns true if this UUri can be serialized into a micro form UUri. """ - if uuri is None: - raise ValueError("Uri cannot be None.") - return not UriValidator.is_empty(uuri) and uuri.entity.HasField('id') and uuri.resource.HasField('id') and ( - not uuri.HasField('authority') or uuri.authority.HasField('ip') or uuri.authority.HasField('id')) + return uri is not None and not UriValidator.is_empty(uri) and uri.entity.HasField('id') \ + and uri.resource.HasField('id') and UriValidator.is_micro_form(uri.authority) + @staticmethod - def is_long_form(uuri: UUri) -> bool: + @dispatch(UAuthority) + def is_micro_form(authority: UAuthority) -> bool: + ''' + check if UAuthority can be represented in micro format. Micro UAuthorities are local or ones + that contain IP address or IDs. + @param authority UAuthority to check + @return Returns true if UAuthority can be represented in micro format + ''' + + + return UriValidator.is_local(authority) or (authority.HasField('ip') or (authority.HasField('id'))) + + @staticmethod + @dispatch(UUri) + def is_long_form(uri: UUri) -> bool: """ Determines if this UUri can be serialized into a long form UUri.

@param uuri: An UUri proto message object @return:Returns true if this UUri can be serialized into a long form UUri. """ - if uuri is None: - raise ValueError("Uri cannot be None.") - return not UriValidator.is_empty(uuri) and not (uuri.HasField('authority') and uuri.authority.HasField( - 'name')) and not uuri.entity.name.strip() == '' and not uuri.resource.name.strip() == '' + + return uri is not None and not UriValidator.is_empty(uri) and UriValidator.is_long_form(uri.authority) and \ + uri.entity.name.strip() != "" and uri.resource.name.strip() != "" + + @staticmethod + @dispatch(UAuthority) + def is_long_form(authority: UAuthority) -> bool: + ''' + Returns true if UAuthority contains names so that it can be serialized into long format. + @param authority UAuthority to check + @return Returns true if URI contains names so that it can be serialized into long format. + ''' + return authority is not None and authority.HasField('name') and authority.name.strip() != "" + + @staticmethod + def is_local(authority: UAuthority) -> bool: + ''' + Returns true if UAuthority is local meaning there is no name/ip/id set. + @param authority UAuthority to check if it is local or not + @return Returns true if UAuthority is local meaning the Authority is not populated with name, ip and id + ''' + return (authority is None) or (authority == UAuthority()) @staticmethod def is_remote(authority: UAuthority) -> bool: - if authority is None: - raise ValueError("Authority cannot be None.") - return not all([authority.name.strip() == "", len(authority.ip) == 0, len(authority.id) == 0]) + ''' + Returns true if UAuthority is remote meaning the name and/or ip/id is populated. + @param authority UAuthority to check if it is remote or not + @return Returns true if UAuthority is remote meaning the name and/or ip/id is populated. + ''' + return (authority is not None) and (not authority == UAuthority()) and \ + (UriValidator.is_long_form(authority) or UriValidator.is_micro_form(authority)) \ No newline at end of file