From 821847556bc1e2899565251c03a33abf62ade7cb Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 19 Oct 2023 15:18:06 -0400 Subject: [PATCH 01/47] Initial commit --- .gitignore | 6 + LICENSE | 201 ++++++++++++ org_eclipse_uprotocol/__init__.py | 0 org_eclipse_uprotocol/cloudevent/__init__.py | 0 .../cloudevent/datamodel/__init__.py | 0 .../datamodel/ucloudeventattributes.py | 96 ++++++ .../cloudevent/datamodel/ucloudeventtype.py | 40 +++ .../cloudevent/factory/__init__.py | 0 .../cloudevent/factory/cloudeventfactory.py | 141 +++++++++ .../cloudevent/factory/ucloudevent.py | 164 ++++++++++ .../cloudevent/serialize/__init__.py | 0 .../serialize/base64protobufserializer.py | 38 +++ .../serialize/cloudeventserializer.py | 35 +++ .../serialize/cloudeventserializers.py | 34 +++ .../serialize/cloudeventtojsonserializer.py | 76 +++++ .../cloudeventtoprotobufserializer.py | 38 +++ .../cloudevent/validate/__init__.py | 0 .../validate/cloudeventvalidator.py | 285 ++++++++++++++++++ .../cloudevent/validate/validationresult.py | 88 ++++++ org_eclipse_uprotocol/rpc/__init__.py | 0 org_eclipse_uprotocol/rpc/calloptions.py | 70 +++++ org_eclipse_uprotocol/rpc/rpcclient.py | 12 + org_eclipse_uprotocol/rpc/rpcmapper.py | 83 +++++ org_eclipse_uprotocol/rpc/rpcresult.py | 132 ++++++++ org_eclipse_uprotocol/transport/__init__.py | 0 .../transport/datamodel/__init__.py | 0 .../transport/datamodel/uattributes.py | 94 ++++++ .../transport/datamodel/ulistener.py | 41 +++ .../transport/datamodel/umessagetype.py | 54 ++++ .../transport/datamodel/upayload.py | 80 +++++ .../transport/datamodel/upriority.py | 63 ++++ .../transport/datamodel/userializationhint.py | 65 ++++ .../transport/datamodel/ustatus.py | 137 +++++++++ org_eclipse_uprotocol/transport/utransport.py | 49 +++ .../transport/validate/__init__.py | 0 .../validate/uattributesvalidator.py | 178 +++++++++++ org_eclipse_uprotocol/uri/__init__.py | 0 .../uri/datamodel/__init__.py | 0 .../uri/datamodel/uauthority.py | 113 +++++++ .../uri/datamodel/uentity.py | 98 ++++++ .../uri/datamodel/uresource.py | 121 ++++++++ org_eclipse_uprotocol/uri/datamodel/uuri.py | 83 +++++ .../uri/serializer/__init__.py | 0 .../uri/serializer/longuriserializer.py | 164 ++++++++++ .../uri/serializer/microuriserializer.py | 143 +++++++++ .../uri/serializer/uriserializer.py | 64 ++++ .../uri/validator/__init__.py | 0 .../uri/validator/urivalidator.py | 63 ++++ org_eclipse_uprotocol/uuid/__init__.py | 0 .../uuid/factory/__init__.py | 150 +++++++++ .../uuid/factory/uuidfactory.py | 66 ++++ .../uuid/factory/uuidutils.py | 113 +++++++ .../uuid/validate/__init__.py | 0 .../uuid/validate/uuidvalidator.py | 111 +++++++ setup.cfg | 20 ++ setup.py | 4 + 56 files changed, 3613 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 org_eclipse_uprotocol/__init__.py create mode 100644 org_eclipse_uprotocol/cloudevent/__init__.py create mode 100644 org_eclipse_uprotocol/cloudevent/datamodel/__init__.py create mode 100644 org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py create mode 100644 org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py create mode 100644 org_eclipse_uprotocol/cloudevent/factory/__init__.py create mode 100644 org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py create mode 100644 org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py create mode 100644 org_eclipse_uprotocol/cloudevent/serialize/__init__.py create mode 100644 org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py create mode 100644 org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializer.py create mode 100644 org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py create mode 100644 org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py create mode 100644 org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py create mode 100644 org_eclipse_uprotocol/cloudevent/validate/__init__.py create mode 100644 org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py create mode 100644 org_eclipse_uprotocol/cloudevent/validate/validationresult.py create mode 100644 org_eclipse_uprotocol/rpc/__init__.py create mode 100644 org_eclipse_uprotocol/rpc/calloptions.py create mode 100644 org_eclipse_uprotocol/rpc/rpcclient.py create mode 100644 org_eclipse_uprotocol/rpc/rpcmapper.py create mode 100644 org_eclipse_uprotocol/rpc/rpcresult.py create mode 100644 org_eclipse_uprotocol/transport/__init__.py create mode 100644 org_eclipse_uprotocol/transport/datamodel/__init__.py create mode 100644 org_eclipse_uprotocol/transport/datamodel/uattributes.py create mode 100644 org_eclipse_uprotocol/transport/datamodel/ulistener.py create mode 100644 org_eclipse_uprotocol/transport/datamodel/umessagetype.py create mode 100644 org_eclipse_uprotocol/transport/datamodel/upayload.py create mode 100644 org_eclipse_uprotocol/transport/datamodel/upriority.py create mode 100644 org_eclipse_uprotocol/transport/datamodel/userializationhint.py create mode 100644 org_eclipse_uprotocol/transport/datamodel/ustatus.py create mode 100644 org_eclipse_uprotocol/transport/utransport.py create mode 100644 org_eclipse_uprotocol/transport/validate/__init__.py create mode 100644 org_eclipse_uprotocol/transport/validate/uattributesvalidator.py create mode 100644 org_eclipse_uprotocol/uri/__init__.py create mode 100644 org_eclipse_uprotocol/uri/datamodel/__init__.py create mode 100644 org_eclipse_uprotocol/uri/datamodel/uauthority.py create mode 100644 org_eclipse_uprotocol/uri/datamodel/uentity.py create mode 100644 org_eclipse_uprotocol/uri/datamodel/uresource.py create mode 100644 org_eclipse_uprotocol/uri/datamodel/uuri.py create mode 100644 org_eclipse_uprotocol/uri/serializer/__init__.py create mode 100644 org_eclipse_uprotocol/uri/serializer/longuriserializer.py create mode 100644 org_eclipse_uprotocol/uri/serializer/microuriserializer.py create mode 100644 org_eclipse_uprotocol/uri/serializer/uriserializer.py create mode 100644 org_eclipse_uprotocol/uri/validator/__init__.py create mode 100644 org_eclipse_uprotocol/uri/validator/urivalidator.py create mode 100644 org_eclipse_uprotocol/uuid/__init__.py create mode 100644 org_eclipse_uprotocol/uuid/factory/__init__.py create mode 100644 org_eclipse_uprotocol/uuid/factory/uuidfactory.py create mode 100644 org_eclipse_uprotocol/uuid/factory/uuidutils.py create mode 100644 org_eclipse_uprotocol/uuid/validate/__init__.py create mode 100644 org_eclipse_uprotocol/uuid/validate/uuidvalidator.py create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de40452 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea/ +**/__pycache__/ +**/build/ +**/dist/ +**/*.egg-info/ +**/.DS_Store \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/org_eclipse_uprotocol/__init__.py b/org_eclipse_uprotocol/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/cloudevent/__init__.py b/org_eclipse_uprotocol/cloudevent/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/cloudevent/datamodel/__init__.py b/org_eclipse_uprotocol/cloudevent/datamodel/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py new file mode 100644 index 0000000..d09a25c --- /dev/null +++ b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py @@ -0,0 +1,96 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- + +from typing import Optional + +from org_eclipse_uprotocol.transport.datamodel.upriority import UPriority + + +class UCloudEventAttributes: + + def __init__(self, hash_value: str, priority: str, ttl: int, token: str): + self.hash = hash_value + self.priority = priority + self.ttl = ttl + self.token = token + + @staticmethod + def empty(): + return UCloudEventAttributes(None, None, None, None) + + def is_empty(self): + return not any((self.hash, self.priority, self.ttl, self.token)) + + def hash(self) -> Optional[str]: + return None if not self.hash or self.hash.isspace() else self.hash + + def priority(self) -> Optional[str]: + return self.priority + + def ttl(self) -> Optional[int]: + return self.ttl + + def token(self) -> Optional[str]: + return None if not self.token or self.token.isspace() else self.token + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, UCloudEventAttributes): + return False + return ( + self.hash == other.hash and self.priority == other.priority and self.ttl == other.ttl and self.token == other.token) + + def __hash__(self): + return hash((self.hash, self.priority, self.ttl, self.token)) + + def __str__(self): + return f"UCloudEventAttributes{{hash='{self.hash}', priority={self.priority}," \ + f" ttl={self.ttl}, token='{self.token}'}}" + + +class UCloudEventAttributesBuilder: + def __init__(self): + self.hash = None + self.priority = None + self.ttl = None + self.token = None + + def with_hash(self, hash_value: str): + self.hash = hash_value + return self + + def with_priority(self, priority: UPriority): + self.priority = priority + return self + + def with_ttl(self, ttl: int): + self.ttl = ttl + return self + + def with_token(self, token: str): + self.token = token + return self + + def build(self): + return UCloudEventAttributes(self.hash, self.priority.qos_string, self.ttl, self.token) diff --git a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py new file mode 100644 index 0000000..404d125 --- /dev/null +++ b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py @@ -0,0 +1,40 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +from enum import Enum +from typing import Optional + + +class UCloudEventType(Enum): + PUBLISH = "pub.v1" + REQUEST = "req.v1" + RESPONSE = "res.v1" + + def type(self) -> str: + return self.value + + @staticmethod + def value_of_type(typestr: str) -> Optional['UCloudEventType']: + for ce_type in UCloudEventType: + if ce_type.type() == typestr: + return ce_type + return None diff --git a/org_eclipse_uprotocol/cloudevent/factory/__init__.py b/org_eclipse_uprotocol/cloudevent/factory/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py b/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py new file mode 100644 index 0000000..26f08d6 --- /dev/null +++ b/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py @@ -0,0 +1,141 @@ +# ------------------------------------------------------------------------- +# 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. + +# ------------------------------------------------------------------------- +from cloudevents.sdk.event import v1 +from cloudevents.sdk.event.v1 import Event +from google.protobuf import empty_pb2 +from google.protobuf.any_pb2 import Any + +from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributes +from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventtype import UCloudEventType +from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent +from org_eclipse_uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer +from org_eclipse_uprotocol.transport.datamodel.upriority import UPriority +from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority +from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity +from org_eclipse_uprotocol.uri.datamodel.uresource import UResource +from org_eclipse_uprotocol.uri.datamodel.uuri import UUri +from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from org_eclipse_uprotocol.uuid.factory.uuidfactory import Factories + + +class CloudEventFactory: + PROTOBUF_CONTENT_TYPE = "application/x-protobuf" + + @staticmethod + def request(application_uri_for_rpc: str, service_method_uri: str, request_id: str, proto_payload: Any, + attributes: UCloudEventAttributes) -> Event: + event_id = CloudEventFactory.generate_cloud_event_id() + event = CloudEventFactory.build_base_cloud_event(event_id, application_uri_for_rpc, + Base64ProtobufSerializer.deserialize( + proto_payload.SerializeToString()), + proto_payload.DESCRIPTOR.full_name, attributes) + event.SetEventType(UCloudEventType.REQUEST.type()) + ext = event.Get("Extensions")[0] + ext["sink"] = service_method_uri + ext["reqid"] = request_id + event.SetExtensions(ext) + return event + + @staticmethod + def response(application_uri_for_rpc: str, service_method_uri: str, request_id: str, proto_payload: Any, + attributes: UCloudEventAttributes) -> Event: + event_id = CloudEventFactory.generate_cloud_event_id() + event = CloudEventFactory.build_base_cloud_event(event_id, service_method_uri, + Base64ProtobufSerializer.deserialize( + proto_payload.SerializeToString()), + proto_payload.DESCRIPTOR.full_name, attributes) + event.SetEventType(UCloudEventType.RESPONSE.type()) + ext = event.Get("Extensions")[0] + ext["sink"] = application_uri_for_rpc + ext["reqid"] = request_id + event.SetExtensions(ext) + + return event + + @staticmethod + def failed_response(application_uri_for_rpc: str, service_method_uri: str, request_id: str, + communication_status: int, attributes: UCloudEventAttributes) -> Event: + event_id = CloudEventFactory.generate_cloud_event_id() + # Create an Any message packing an Empty message + empty_proto_payload = Any() + empty_proto_payload.Pack(empty_pb2.Empty()) + event = CloudEventFactory.build_base_cloud_event(event_id, service_method_uri, + Base64ProtobufSerializer.deserialize( + empty_proto_payload.SerializeToString()), # Empty payload + "google.protobuf.Empty", attributes) + event.SetEventType(UCloudEventType.RESPONSE.type()) + ext = event.Get("Extensions")[0] + ext["sink"] = application_uri_for_rpc + ext["reqid"] = request_id + ext["commstatus"] = communication_status + event.SetExtensions(ext) + + return event + + @staticmethod + def publish(source: str, proto_payload: Any, attributes: UCloudEventAttributes) -> Event: + event_id = CloudEventFactory.generate_cloud_event_id() + event = CloudEventFactory.build_base_cloud_event(event_id, source, Base64ProtobufSerializer.deserialize( + proto_payload.SerializeToString()), proto_payload.DESCRIPTOR.full_name, attributes) + event.SetEventType(UCloudEventType.PUBLISH.type()) + return event + + @staticmethod + def notification(source: str, sink: str, proto_payload: Any, attributes: UCloudEventAttributes) -> Event: + event_id = CloudEventFactory.generate_cloud_event_id() + event = CloudEventFactory.build_base_cloud_event(event_id, source, Base64ProtobufSerializer.deserialize( + proto_payload.SerializeToString()), proto_payload.DESCRIPTOR.full_name, attributes) + event.SetEventType(UCloudEventType.PUBLISH.type()) + ext = event.Get("Extensions")[0] + ext["sink"] = sink + event.SetExtensions(ext) + return event + + @staticmethod + def generate_cloud_event_id() -> str: + # uuid8 + uuid_inst = Factories.UPROTOCOL.create() + return str(uuid_inst) + + @staticmethod + def build_base_cloud_event(id: str, source: str, proto_payload_bytes: str, proto_payload_schema: str, + attributes: UCloudEventAttributes) -> Event: + # Set extensions + extensions = {} + if attributes.ttl: + extensions["ttl"] = attributes.ttl + + if attributes.priority: + extensions["priority"] = attributes.priority + + if attributes.hash: + extensions["hash"] = attributes.hash + + if attributes.token: + extensions["token"] = attributes.token + + cloud_event = v1.Event() + cloud_event.SetEventID(id) + cloud_event.SetSource(source) + cloud_event.SetData(proto_payload_bytes) + cloud_event.SetExtensions(extensions) + return cloud_event diff --git a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py new file mode 100644 index 0000000..954788c --- /dev/null +++ b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py @@ -0,0 +1,164 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- + +import base64 +from datetime import datetime, timedelta + +from cloudevents.sdk.event import v1 +from cloudevents.sdk.event.v1 import Event +from google.protobuf import any_pb2 +from google.protobuf.message import DecodeError +from google.rpc.code_pb2 import Code + +from org_eclipse_uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer +from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils + + +class UCloudEvent: + @staticmethod + def get_source(ce: Event) -> str: + return str(ce.source) + + @staticmethod + def get_sink(ce: Event) -> str: + return UCloudEvent.extract_string_value_from_extension("sink", ce) + + @staticmethod + def get_request_id(ce: Event) -> str: + return UCloudEvent.extract_string_value_from_extension("reqid", ce) + + @staticmethod + def get_hash(ce: Event) -> str: + return UCloudEvent.extract_string_value_from_extension("hash", ce) + + @staticmethod + def get_priority(ce: Event) -> str: + return UCloudEvent.extract_string_value_from_extension("priority", ce) + + @staticmethod + def get_ttl(ce: Event) -> int: + ttl_str = UCloudEvent.extract_string_value_from_extension("ttl", ce) + return int(ttl_str) if ttl_str is not None else None + + @staticmethod + def get_token(ce: Event) -> str: + return UCloudEvent.extract_string_value_from_extension("token", ce) + + @staticmethod + def get_communication_status(ce: Event) -> int: + comm_status = UCloudEvent.extract_string_value_from_extension("commstatus", ce) + return int(comm_status) if comm_status is not None else Code.OK + + @staticmethod + def has_communication_status_problem(ce: Event) -> bool: + return UCloudEvent.get_communication_status(ce) != 0 + + @staticmethod + def add_communication_status(ce: Event, communication_status) -> Event: + if communication_status is None: + return ce + + ce.extensions["commstatus"] = communication_status + return ce + + @staticmethod + def get_creation_timestamp(ce: Event) -> int: + cloud_event_id = ce.id + uuid = UUIDUtils.fromString(cloud_event_id) + + return UUIDUtils.getTime(uuid) if uuid is not None else None + + @staticmethod + def is_expired_by_cloud_event_creation_date(ce: Event) -> bool: + maybe_ttl = UCloudEvent.get_ttl(ce) + if maybe_ttl is None or maybe_ttl <= 0: + return False + + cloud_event_creation_time = ce.time + if cloud_event_creation_time is None: + return False + + now = datetime.now() + creation_time_plus_ttl = cloud_event_creation_time + timedelta(milliseconds=maybe_ttl) + + return now > creation_time_plus_ttl + + @staticmethod + def is_expired(ce: Event) -> bool: + maybe_ttl = UCloudEvent.get_ttl(ce) + if maybe_ttl is None or maybe_ttl <= 0: + return False + cloud_event_id = ce.id + + try: + uuid = UUIDUtils.fromString(cloud_event_id) + if uuid is None: + return False + delta = datetime.utcnow().timestamp() - UUIDUtils.getTime(uuid).timestamp() + except ValueError: + # Invalid UUID, handle accordingly + delta = 0 + return delta >= maybe_ttl + + # Check if a CloudEvent is a valid UUIDv6 or v8 . + @staticmethod + def is_cloud_event_id(ce: Event) -> bool: + cloud_event_id = ce.id + uuid = UUIDUtils.fromString(cloud_event_id) + + return uuid is not None and UUIDUtils.isuuid(uuid) + + @staticmethod + def get_payload(ce: Event) -> any_pb2.Any: + data = ce.data + if data is None: + return any_pb2.Any() + try: + return any_pb2.Any().FromString(Base64ProtobufSerializer.serialize(ce.data)) + except DecodeError: + return any_pb2.Any() + + @staticmethod + def unpack(ce: Event, clazz): + try: + return UCloudEvent.get_payload(ce).Unpack(clazz) + except DecodeError: + return None + + @staticmethod + def to_string(ce: Event) -> str: + if ce is not None: + sink_str = UCloudEvent.get_sink(ce) + sink_str = f", sink='{sink_str}'" if sink_str is not None else "" + return f"CloudEvent{{id='{ce.id}', source='{ce.source}'{sink_str}, type='{ce.type}'}}" + else: + return "null" + + @staticmethod + def extract_string_value_from_extension(extension_name, ce: Event) -> str: + return ce.extensions.get(extension_name) + + @staticmethod + def extract_integer_value_from_extension(extension_name, ce: Event) -> int: + value = UCloudEvent.extract_string_value_from_extension(extension_name, ce) + return int(value) if value is not None else None diff --git a/org_eclipse_uprotocol/cloudevent/serialize/__init__.py b/org_eclipse_uprotocol/cloudevent/serialize/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py new file mode 100644 index 0000000..8ba8a34 --- /dev/null +++ b/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py @@ -0,0 +1,38 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +import base64 +from builtins import str + + +class Base64ProtobufSerializer: + @staticmethod + def deserialize(proto_bytes: bytes) -> str: + if proto_bytes is None: + return "" + return base64.b64encode(proto_bytes).decode('utf-8') # return base64.b64decode(proto_bytes).decode('utf-8') + + @staticmethod + def serialize(string_to_serialize: str) -> bytes: # input is base64 string + if string_to_serialize is None: + return bytearray() + return base64.b64decode(string_to_serialize.encode('utf-8')) diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializer.py new file mode 100644 index 0000000..cecc5fa --- /dev/null +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializer.py @@ -0,0 +1,35 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +from abc import ABC, abstractmethod +from typing import Any + + +class CloudEventSerializer(ABC): + + @abstractmethod + def serialize(self, cloud_event: Any) -> bytes: + pass + + @abstractmethod + def deserialize(self, bytes_data: bytes) -> Any: + pass diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py new file mode 100644 index 0000000..d5b990a --- /dev/null +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py @@ -0,0 +1,34 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +from enum import Enum + +from org_eclipse_uprotocol.cloudevent.serialize.cloudeventtojsonserializer import CloudEventToJsonSerializer +from org_eclipse_uprotocol.cloudevent.serialize.cloudeventtoprotobufserializer import CloudEventToProtobufSerializer + + +class CloudEventSerializers(Enum): + JSON = CloudEventToJsonSerializer() + PROTOBUF = CloudEventToProtobufSerializer() + + def serializer(self): + return self.value diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py new file mode 100644 index 0000000..b9a2734 --- /dev/null +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py @@ -0,0 +1,76 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +import json + +from cloudevents.sdk.event.v1 import Event + +from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializer import CloudEventSerializer + + +class CloudEventToJsonSerializer(CloudEventSerializer): + def __init__(self): + self.serializer = json.JSONEncoder(indent=2, sort_keys=False) + + def cloud_event_to_dict(self, ce: Event): + """ + Convert a CloudEvent instance to a Python dictionary. + """ + cloud_event_dict = {"specversion": ce.specversion, "type": ce.type, "source": ce.source, "id": ce.id, + "time": ce.time, "datacontenttype": ce.content_type, "dataschema": ce.schema, + "subject": ce.subject, "data": ce.data, "extensions": ce.extensions, } + return cloud_event_dict + + def cloud_dict_to_cloud_event(self, ce_dict: dict): + """ + Convert a CloudEvent dict to a Cloudevent instance. + """ + + event = Event() + if 'type' in ce_dict: + event.SetEventType(ce_dict['type']) + if 'source' in ce_dict: + event.SetSource(ce_dict['source']) + if 'datacontenttype' in ce_dict: + event.SetContentType(ce_dict['datacontenttype']) + if 'id' in ce_dict: + event.SetEventID(ce_dict['id']) + if 'data' in ce_dict: + event.SetData(ce_dict['data']) + if 'extensions' in ce_dict: + event.SetExtensions(ce_dict['extensions']) + if 'time' in ce_dict: + event.SetEventTime(ce_dict['time']) + if 'dataschema' in ce_dict: + event.SetSchema(ce_dict['dataschema']) + if 'subject' in ce_dict: + event.SetSubject(ce_dict['subject']) + + return event + + def serialize(self, ce: Event) -> bytes: + ce_dict = self.cloud_event_to_dict(ce) + return self.serializer.encode(ce_dict).encode('utf-8') + + def deserialize(self, bytes_data: bytes) -> Event: + cloud_event_dict = json.loads(bytes_data.decode('utf-8')) + return self.cloud_dict_to_cloud_event(cloud_event_dict) diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py new file mode 100644 index 0000000..9083f2f --- /dev/null +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py @@ -0,0 +1,38 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- + +from cloudevents.sdk.event.v1 import Event + +from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializer import CloudEventSerializer + + +# To do - implementation is pending, convert cloud event to cloudevent proto +class CloudEventToProtobufSerializer(CloudEventSerializer): + def __init__(self): + pass + + def serialize(self, ce: Event) -> bytes: + pass + + def deserialize(self, bytes_data: bytes): + pass diff --git a/org_eclipse_uprotocol/cloudevent/validate/__init__.py b/org_eclipse_uprotocol/cloudevent/validate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py new file mode 100644 index 0000000..df8be4e --- /dev/null +++ b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py @@ -0,0 +1,285 @@ +# ------------------------------------------------------------------------- +from abc import ABC, abstractmethod +# ------------------------------------------------------------------------- +from enum import Enum + +from cloudevents.sdk.event.v1 import Event +from google.protobuf import empty_pb2 +from google.protobuf.any_pb2 import Any +from google.rpc.status_pb2 import Status + +from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventtype import UCloudEventType +from org_eclipse_uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory +from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent +from org_eclipse_uprotocol.cloudevent.validate.validationresult import ValidationResult +from org_eclipse_uprotocol.transport.datamodel.upriority import UPriority +from org_eclipse_uprotocol.uri.datamodel import uuri +from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority +from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity +from org_eclipse_uprotocol.uri.datamodel.uresource import UResource +from org_eclipse_uprotocol.uri.datamodel.uuri import UUri +from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer + + +# 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. + + +class CloudEventValidator(ABC): + @staticmethod + def get_validator(ce: Event): + cloud_event_type = ce.type + maybe_type = UCloudEventType(cloud_event_type) + if maybe_type not in UCloudEventType: + return Validators.PUBLISH.validator() + + elif maybe_type == UCloudEventType.RESPONSE: + return Validators.RESPONSE.validator() + elif maybe_type == UCloudEventType.REQUEST: + return Validators.REQUEST.validator() + else: + return Validators.PUBLISH.validator() + + def validate(self, ce: Event) -> Status: + validation_results = [self.validate_version(ce), self.validate_id(ce), self.validate_source(ce), + self.validate_type(ce), self.validate_sink(ce)] + + error_messages = [result.get_message() for result in validation_results if not result.is_success()] + error_message = ", ".join(error_messages) + + if not error_message: + return ValidationResult.success().to_status() + else: + return ValidationResult.failure(", ".join(error_messages)) + + def validate_version(self, ce: Event) -> ValidationResult: + return self.validate_version_spec(ce.specversion) + + @staticmethod + def validate_version_spec(version) -> ValidationResult: + if version == "1.0": + return ValidationResult.success() + else: + return ValidationResult.failure(f"Invalid CloudEvent version [{version}]. CloudEvent version must be 1.0.") + + def validate_id(self, ce: Event) -> ValidationResult: + return (ValidationResult.success() if UCloudEvent.is_cloud_event_id(ce) else ValidationResult.failure( + f"Invalid CloudEvent Id [{ce.id}]. CloudEvent Id must be of type UUIDv8.")) + + @abstractmethod + def validate_source(self, ce: Event): + raise NotImplementedError("Subclasses must implement this method") + + @abstractmethod + def validate_type(self, ce: Event): + raise NotImplementedError("Subclasses must implement this method") + + def validate_sink(self, ce: Event) -> ValidationResult: + maybe_sink = UCloudEvent.get_sink(ce) + if maybe_sink: + sink = maybe_sink + check_sink = self.validate_u_entity_uri(sink) + if check_sink.is_failure(): + return ValidationResult.failure(f"Invalid CloudEvent sink [{sink}]. {check_sink.get_message()}") + + return ValidationResult.success() + + @staticmethod + def validate_u_entity_uri(uri: str) -> ValidationResult: # from uri string + # UUri + # Uri = LongUriSerializer.instance().deserialize(uri) + # return validateUEntityUri(Uri) + uri = LongUriSerializer().deserialize(uri) + # Uri = UriFactory.parse_from_uri(uri) + return CloudEventValidator.validate_u_entity_uri_from_UURI(uri) + + @staticmethod + def validate_u_entity_uri_from_UURI(uri: uuri) -> ValidationResult: # from uuri + u_authority = uri.u_authority + if u_authority.is_marked_remote: + if not u_authority.device: + return ValidationResult.failure("Uri is configured to be remote and is missing uAuthority device name.") + + if not uri.u_entity.name: + return ValidationResult.failure("Uri is missing uSoftware Entity name.") + + return ValidationResult.success() + + @staticmethod + def validate_topic_uri(uri: str) -> ValidationResult: # from uri string + Uri = LongUriSerializer().deserialize(uri) + return CloudEventValidator.validate_topic_uri_from_UURI(Uri) + + @staticmethod + def validate_topic_uri_from_UURI(uri: uuri) -> ValidationResult: # from uuri + validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI(uri) + if validationResult.is_success(): + return validationResult + + u_resource = uri.u_resource + if not u_resource.name: + return ValidationResult.failure("Uri is missing uResource name.") + + if not u_resource.message: + return ValidationResult.failure("Uri is missing Message information.") + + return ValidationResult.success() + + @staticmethod + def validate_rpc_topic_uri(uri: str) -> ValidationResult: # from uri string + Uri = LongUriSerializer.deserialize(uri) + return CloudEventValidator.validate_rpc_topic_uri_from_uuri(Uri) + + @staticmethod + def validate_rpc_topic_uri_from_uuri(uri: uuri) -> ValidationResult: # from uuri + validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI(uri) + if validationResult.is_failure(): + return ValidationResult.failure( + f"Invalid RPC uri application response topic. {validationResult.get_message()}") + + u_resource = uri.u_resource + topic = f"{u_resource.name}.{u_resource.instance}" if u_resource.instance else f"{u_resource.name}" + if topic != "rpc.response": + return ValidationResult.failure("Invalid RPC uri application response topic. Uri is missing rpc.response.") + + return ValidationResult.success() + + @staticmethod + def validate_rpc_method(uri: str) -> ValidationResult: # string uri + Uri = LongUriSerializer.deserialize(uri) + validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI(Uri) + if validationResult.is_failure(): + return ValidationResult.failure(f"Invalid RPC method uri. {validationResult.get_message()}") + + u_resource = Uri.u_resource + if not u_resource.is_rpc_method: + return ValidationResult.failure( + "Invalid RPC method uri. Uri should be the method to be called, or method from response.") + + return ValidationResult.success() + + +class Publish(CloudEventValidator): + def validate_source(self, cl_event: Event) -> ValidationResult: + source = cl_event.source + check_source = self.validate_topic_uri(source) + if check_source.is_failure(): + return ValidationResult.failure( + f"Invalid Publish type CloudEvent source [{source}]. {check_source.get_message()}") + + return ValidationResult.success() + + def validate_type(self, cl_event: Event) -> ValidationResult: + return (ValidationResult.success() if cl_event.type == "pub.v1" else ValidationResult.failure( + f"Invalid CloudEvent type [{cl_event.type}]. CloudEvent of type Publish must have a type of 'pub.v1'")) + + def __str__(self) -> str: + return "CloudEventValidator.Publish" + + +class Notification(Publish): + def validate_sink(self, cl_event: Event) -> ValidationResult: + maybe_sink = UCloudEvent.get_sink(cl_event) + if not maybe_sink: + return ValidationResult.failure("Invalid CloudEvent sink. Notification CloudEvent sink must be an uri.") + else: + sink = maybe_sink + check_sink = self.validate_u_entity_uri(sink) + if check_sink.is_failure(): + return ValidationResult.failure( + f"Invalid Notification type CloudEvent sink [{sink}]. {check_sink.get_message()}") + + return ValidationResult.success() + + def __str__(self): + return "CloudEventValidator.Notification" + + +class Request(CloudEventValidator): + def validate_source(self, cl_event: Event) -> ValidationResult: + source = cl_event.source + check_source = self.validate_rpc_topic_uri(source) + if check_source.is_failure(): + return ValidationResult.failure( + f"Invalid RPC Request CloudEvent source [{source}]. {check_source.get_message()}") + return ValidationResult.success() + + def validate_sink(self, cl_event: Event) -> ValidationResult: + maybe_sink = UCloudEvent.get_sink(cl_event) + if not maybe_sink: + return ValidationResult.failure( + "Invalid RPC Request CloudEvent sink. Request CloudEvent sink must be uri for the method to be called.") + else: + sink = maybe_sink + check_sink = self.validate_rpc_method(sink) + if check_sink.is_failure(): + return ValidationResult.failure( + f"Invalid RPC Request CloudEvent sink [{sink}]. {check_sink.get_message()}") + + return ValidationResult.success() + + def validate_type(self, cl_event: Event) -> ValidationResult: + return (ValidationResult.success() if cl_event.type == "req.v1" else ValidationResult.failure( + f"Invalid CloudEvent type [{cl_event.type}]. CloudEvent of type Request must have a type of 'req.v1'")) + + def __str__(self): + return "CloudEventValidator.Request" + + +class Response(CloudEventValidator): + def validate_source(self, cl_event: Event) -> ValidationResult: + source = cl_event.source + check_source = self.validate_rpc_method(source) + if check_source.is_failure(): + return ValidationResult.failure( + f"Invalid RPC Response CloudEvent source [{source}]. {check_source.get_message()}") + + return ValidationResult.success() + + def validate_sink(self, cl_event) -> ValidationResult: + maybe_sink = UCloudEvent.get_sink(cl_event) + if not maybe_sink: + return ValidationResult.failure( + "Invalid CloudEvent sink. Response CloudEvent sink must be uri the destination of the response.") + else: + sink = maybe_sink + check_sink = self.validate_rpc_topic_uri(sink) + if check_sink.is_failure(): + return ValidationResult.failure( + f"Invalid RPC Response CloudEvent sink [{sink}]. {check_sink.get_message()}") + + return ValidationResult.success() + + def validate_type(self, cl_event: Event) -> ValidationResult: + return (ValidationResult.success() if cl_event.type == "res.v1" else ValidationResult.failure( + f"Invalid CloudEvent type [{cl_event.type}]. CloudEvent of type Response must have a type of 'res.v1'")) + + def __str__(self): + return "CloudEventValidator.Response" + + +class Validators(Enum): + PUBLISH = Publish() + NOTIFICATION = Notification() + REQUEST = Request() + RESPONSE = Response() + + def __init__(self, cloud_event_validator: CloudEventValidator): + self.cloud_event_validator = cloud_event_validator + + def validator(self) -> CloudEventValidator: + return self.cloud_event_validator diff --git a/org_eclipse_uprotocol/cloudevent/validate/validationresult.py b/org_eclipse_uprotocol/cloudevent/validate/validationresult.py new file mode 100644 index 0000000..4abc313 --- /dev/null +++ b/org_eclipse_uprotocol/cloudevent/validate/validationresult.py @@ -0,0 +1,88 @@ +# ------------------------------------------------------------------------- + +# 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. + +from abc import ABC, abstractmethod + +# ------------------------------------------------------------------------- +from google.rpc.code_pb2 import Code +from google.rpc.status_pb2 import Status + + +class ValidationResult(ABC): + STATUS_SUCCESS = Status(code=Code.OK, message="OK") + + def __init__(self): + pass + + @abstractmethod + def to_status(self) -> Status: + pass + + @abstractmethod + def is_success(self) -> bool: + pass + + def is_failure(self) -> bool: + return not self.is_success() + + @abstractmethod + def get_message(self) -> str: + pass + + @staticmethod + def success(): + return Success() + + @staticmethod + def failure(message): + return Failure(message) + + +class Failure(ValidationResult): + def __init__(self, message): + super().__init__() + self.message = message if message else "Validation Failed." + + def to_status(self) -> Status: + return Status(code=3, message=self.message) + + def is_success(self) -> bool: + return False + + def get_message(self) -> str: + return self.message + + def __str__(self): + return f"ValidationResult.Failure(message='{self.message}')" + + +class Success(ValidationResult): + def to_status(self) -> Status: + return ValidationResult.STATUS_SUCCESS + + def is_success(self) -> bool: + return True + + def get_message(self) -> str: + return "" + + def __str__(self): + return "ValidationResult.Success()" diff --git a/org_eclipse_uprotocol/rpc/__init__.py b/org_eclipse_uprotocol/rpc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/rpc/calloptions.py b/org_eclipse_uprotocol/rpc/calloptions.py new file mode 100644 index 0000000..9a9d58f --- /dev/null +++ b/org_eclipse_uprotocol/rpc/calloptions.py @@ -0,0 +1,70 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +class CallOptions: + TIMEOUT_DEFAULT = 10000 + + def __init__(self, timeout=TIMEOUT_DEFAULT, token=""): + self.mTimeout = timeout + self.mToken = token if token else "" + + @classmethod + def default(cls): + return cls() + + @property + def timeout(self): + return self.mTimeout + + @property + def token(self): + return self.mToken if self.mToken else None + + def __eq__(self, other): + if not isinstance(other, CallOptions): + return False + return self.mTimeout == other.mTimeout and self.mToken == other.mToken + + def __hash__(self): + return hash((self.mTimeout, self.mToken)) + + def __str__(self): + return f"CallOptions{{mTimeout={self.mTimeout}, mToken='{self.mToken}'}}" + + +class CallOptionsBuilder: + TIMEOUT_DEFAULT = 10000 + + def __init__(self): + self.mTimeout = self.TIMEOUT_DEFAULT + self.mToken = "" + + def with_timeout(self, timeout): + self.mTimeout = timeout if timeout > 0 else self.TIMEOUT_DEFAULT + return self + + def with_token(self, token): + self.mToken = token + return self + + def build(self): + return CallOptions(self.mTimeout, self.mToken) diff --git a/org_eclipse_uprotocol/rpc/rpcclient.py b/org_eclipse_uprotocol/rpc/rpcclient.py new file mode 100644 index 0000000..f44313f --- /dev/null +++ b/org_eclipse_uprotocol/rpc/rpcclient.py @@ -0,0 +1,12 @@ +from abc import ABC, abstractmethod +from concurrent.futures import Future + +from org_eclipse_uprotocol.transport.datamodel.uattributes import UAttributes +from org_eclipse_uprotocol.transport.datamodel.upayload import UPayload +from org_eclipse_uprotocol.uri.datamodel.uuri import UUri + + +class RpcClient(ABC): + @abstractmethod + def invoke_method(self, topic: UUri, payload: UPayload, attributes: UAttributes) -> Future: # Future of Upayload + pass diff --git a/org_eclipse_uprotocol/rpc/rpcmapper.py b/org_eclipse_uprotocol/rpc/rpcmapper.py new file mode 100644 index 0000000..ad254cc --- /dev/null +++ b/org_eclipse_uprotocol/rpc/rpcmapper.py @@ -0,0 +1,83 @@ +from concurrent.futures import Future + +from google.rpc.code_pb2 import Code +from google.rpc.status_pb2 import Status +from sdv_simulation.protofiles.ultifi.vehicle.body.access.v1 import access_topics_pb2 + +from org_eclipse_uprotocol.rpc.rpcresult import RpcResult + + +class RpcMapper: + @staticmethod + def map_response(response_future: Future, expected_cls): + def handle_response(payload, exception): + if exception: + raise exception + payload = payload.result() + if not payload: + raise RuntimeError(f"Server returned a null payload. Expected {expected_cls.__name__}") + + try: + any_message = UPayload.get_any_from_payload_data(payload.data) + if any_message.Is(expected_cls.DESCRIPTOR): + return RpcMapper.unpack_payload(any_message, expected_cls) + except Exception as e: + raise RuntimeError(f"{str(e)} [{Status.__name__}]") from e + + raise RuntimeError(f"Unknown payload type [{any_message.type_url}]. Expected [{expected_cls.__name__}]") + + result = None # Initialize result + + def callbackwrapper(payload, exception=None): + nonlocal result + result = handle_response(payload, exception) + + response_future.add_done_callback(callbackwrapper) + return result + + @staticmethod + def map_response_to_result(response_future: Future, expected_cls): + def handle_response(payload, exception=None): + if exception: + raise exception + payload = payload.result() + if not payload: + raise RuntimeError(f"Server returned a null payload. Expected {expected_cls.__name__}") + + try: + any_message = UPayload.get_any_from_payload_data(payload.data) + + if any_message.Is(expected_cls.DESCRIPTOR): + if expected_cls == Status: + return RpcMapper.calculate_status_result(any_message) + else: + return RpcResult.success(RpcMapper.unpack_payload(any_message, expected_cls)) + + if any_message.Is(Status.DESCRIPTOR): + return RpcMapper.calculate_status_result(any_message) + except Exception as e: + raise RuntimeError(f"{str(e)} [{Status.__name__}]") from e + + raise RuntimeError(f"Unknown payload type [{any_message.type_url}]. Expected [{expected_cls.__name__}]") + + result = None # Initialize result + + def callback_wrapper(payload, exception=None): + nonlocal result + result = handle_response(payload, exception) + + response_future.add_done_callback(callback_wrapper) + return result + + @staticmethod + def calculate_status_result(payload): + status = RpcMapper.unpack_payload(payload, Status) + return RpcResult.success(status) if status.code == Code.OK else RpcResult.failure(status) + + @staticmethod + def unpack_payload(payload, expected_cls): + try: + payload.Unpack(expected_cls) + return expected_cls + except Exception as e: + raise RuntimeError(f"{str(e)} [{Status.__name__}]") from e diff --git a/org_eclipse_uprotocol/rpc/rpcresult.py b/org_eclipse_uprotocol/rpc/rpcresult.py new file mode 100644 index 0000000..83534ef --- /dev/null +++ b/org_eclipse_uprotocol/rpc/rpcresult.py @@ -0,0 +1,132 @@ +from abc import ABC, abstractmethod +from typing import Callable, TypeVar, Union + +from google.rpc.code_pb2 import Code +from google.rpc.status_pb2 import Status + +T = TypeVar('T') + + +class RpcResult(ABC): + + @abstractmethod + def isSuccess(self) -> bool: + pass + + @abstractmethod + def isFailure(self) -> bool: + pass + + @abstractmethod + def getOrElse(self, default_value: T) -> T: + pass + + @abstractmethod + def map(self, f: Callable[[T], T]) -> 'RpcResult': + pass + + @abstractmethod + def flatMap(self, f: Callable[[T], 'RpcResult']) -> 'RpcResult': + pass + + @abstractmethod + def filter(self, f: Callable[[T], bool]) -> 'RpcResult': + pass + + @abstractmethod + def failureValue(self) -> Status: + pass + + @abstractmethod + def successValue(self) -> T: + pass + + def success(value: T) -> 'RpcResult': + return Success(value) + + def failure(value: Union[Status, Exception, None] = None, code: Code = Code.UNKNOWN, + message: str = '') -> 'RpcResult': + return Failure(value, code, message) + + def flatten(result: 'RpcResult') -> 'RpcResult': + return result.flatMap(lambda x: x) + + +class Success(RpcResult): + + def __init__(self, value: T): + self.value = value + + def isSuccess(self) -> bool: + return True + + def isFailure(self) -> bool: + return False + + def getOrElse(self, default_value: T) -> T: + return self.successValue() + + def map(self, f: Callable[[T], T]) -> RpcResult: + try: + return self.success() + except Exception as e: + return self.failure(e) + + def flatMap(self, f: Callable[[T], RpcResult]) -> RpcResult: + try: + return f(self.successValue()) + except Exception as e: + return self.failure(e) + + def filter(self, f: Callable[[T], bool]) -> RpcResult: + try: + return self if f(self.successValue()) else self.failure(Code.FAILED_PRECONDITION, "filtered out") + except Exception as e: + return self.failure(e) + + def failureValue(self) -> Status: + raise ValueError("Method failureValue() called on a Success instance") + + def successValue(self) -> T: + return self.value + + def __str__(self) -> str: + return f"Success({self.successValue()})" + + +class Failure(RpcResult): + + def __init__(self, value: Union[Status, Exception, None] = None, code: Code = Code.UNKNOWN, message: str = ''): + if isinstance(value, Status): + self.value = value + elif isinstance(value, Exception): + self.value = Status(code=code, message=str(value)) + else: + self.value = Status(code=code, message=message) + + def isSuccess(self) -> bool: + return False + + def isFailure(self) -> bool: + return True + + def getOrElse(self, default_value: T) -> T: + return default_value + + def map(self, f: Callable[[T], T]) -> RpcResult: + return self.failure(self) + + def flatMap(self, f: Callable[[T], RpcResult]) -> RpcResult: + return self.failure(self.failureValue()) + + def filter(self, f: Callable[[T], bool]) -> RpcResult: + return self.failure(self) + + def failureValue(self) -> Status: + return self.value + + def successValue(self) -> T: + raise ValueError("Method successValue() called on a Failure instance") + + def __str__(self) -> str: + return f"Failure({self.value})" diff --git a/org_eclipse_uprotocol/transport/__init__.py b/org_eclipse_uprotocol/transport/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/transport/datamodel/__init__.py b/org_eclipse_uprotocol/transport/datamodel/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/transport/datamodel/uattributes.py b/org_eclipse_uprotocol/transport/datamodel/uattributes.py new file mode 100644 index 0000000..9c73b27 --- /dev/null +++ b/org_eclipse_uprotocol/transport/datamodel/uattributes.py @@ -0,0 +1,94 @@ +# ------------------------------------------------------------------------- +from org_eclipse_uprotocol.transport.datamodel.umessagetype import UMessageType +from org_eclipse_uprotocol.transport.datamodel.upriority import UPriority +# 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. + +# ------------------------------------------------------------------------- + +from org_eclipse_uprotocol.uri.datamodel.uuri import UUri +from org_eclipse_uprotocol.uuid.factory import UUID + + +class UAttributesBuilder: + def __init__(self, id: UUID, type: UMessageType, priority: UPriority): + self.id = id + self.type = type + self.priority = priority + self.ttl = None + self.token = None + self.sink = None + self.plevel = None + self.commstatus = None + self.reqid = None + + def with_ttl(self, ttl: int): + self.ttl = ttl + return self + + def with_token(self, token: str): + self.token = token + return self + + def with_sink(self, sink: UUri): + self.sink = sink + return self + + def with_permission_level(self, plevel: int): + self.plevel = plevel + return self + + def with_comm_status(self, commstatus: int): + self.commstatus = commstatus + return self + + def with_reqid(self, reqid: UUID): + self.reqid = reqid + return self + + def build(self): + return UAttributes(self.id, self.type, self.priority, self.ttl, self.token, self.sink, self.plevel, + self.commstatus, self.reqid) + + +class UAttributes: + def __init__(self, id: UUID, type: UMessageType, priority: UPriority, ttl: int, token: str, sink: UUri, plevel: int, + commstatus: int, reqid: UUID): + self.id = id + self.type = type + self.priority = priority + self.ttl = ttl + self.token = token + self.sink = sink + self.plevel = plevel + self.commstatus = commstatus + self.reqid = reqid + + @staticmethod + def empty(): + return UAttributes(None, None, None, None, None, None, None, None, None) + + @staticmethod + def for_rpc_request(id: UUID, sink: UUri) -> UAttributesBuilder: + return UAttributesBuilder(id, UMessageType.REQUEST, UPriority.REALTIME_INTERACTIVE).with_sink(sink) + + @staticmethod + def for_rpc_response(id: UUID, sink: UUri, reqid: UUID) -> UAttributesBuilder: + return UAttributesBuilder(id, UMessageType.RESPONSE, UPriority.REALTIME_INTERACTIVE).with_sink(sink).with_reqid( + reqid) diff --git a/org_eclipse_uprotocol/transport/datamodel/ulistener.py b/org_eclipse_uprotocol/transport/datamodel/ulistener.py new file mode 100644 index 0000000..024edff --- /dev/null +++ b/org_eclipse_uprotocol/transport/datamodel/ulistener.py @@ -0,0 +1,41 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +from abc import ABC, abstractmethod + +from org_eclipse_uprotocol.transport.datamodel.uattributes import UAttributes +from org_eclipse_uprotocol.transport.datamodel.upayload import UPayload +from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus +from org_eclipse_uprotocol.uri.datamodel.uuri import UUri + + +class UListener(ABC): + @abstractmethod + def on_receive(self, topic: UUri, payload: UPayload, attributes: UAttributes) -> UStatus: + """ + 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. + """ + pass diff --git a/org_eclipse_uprotocol/transport/datamodel/umessagetype.py b/org_eclipse_uprotocol/transport/datamodel/umessagetype.py new file mode 100644 index 0000000..62a4cd0 --- /dev/null +++ b/org_eclipse_uprotocol/transport/datamodel/umessagetype.py @@ -0,0 +1,54 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +from enum import Enum, unique + + +@unique +class UMessageType(Enum): + PUBLISH = (0, "pub.v1") # Publish or notification event + REQUEST = (1, "req.v1") # Request + RESPONSE = (2, "res.v1") # Response + + def __init__(self, value: int, name: str): + self.int_value = value + self.string_value = name + + def int_value(self): + return self.int_value + + def string_value(self): + return self.string_value + + @classmethod + def from_int(cls, value: int): + for item in cls: + if item.int_value == value: + return item + return None + + @classmethod + def from_string(cls, value: str): + for item in cls: + if item.string_value == value: + return item + return None diff --git a/org_eclipse_uprotocol/transport/datamodel/upayload.py b/org_eclipse_uprotocol/transport/datamodel/upayload.py new file mode 100644 index 0000000..eaf2be8 --- /dev/null +++ b/org_eclipse_uprotocol/transport/datamodel/upayload.py @@ -0,0 +1,80 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +import sys + +from google.protobuf import any_pb2 + + +class USerializationHint: + def __init__(self, hint_value): + self.hint_value = hint_value + + +class UPayload: + EMPTY = None + + def __init__(self, data: bytes, size: int, hint: USerializationHint): + self.data = data + self.size = size + self.hint = hint + + @classmethod + def empty(cls): + if cls.EMPTY is None: + cls.EMPTY = cls(bytearray(), 0, None) + return cls.EMPTY + + @classmethod + def from_string(cls, payload: str, hint: USerializationHint): + return cls(bytearray(payload.encode()), hint) + + def get_data(self) -> bytes: + return self.data if self.data is not None else UPayload.empty().data + + def hint(self): + return self.hint + + def is_empty(self): + return self.data is None or len(self.data) == 0 + + def get_size(self) -> int: + return self.size + + @staticmethod + def get_any_from_payload_data(data: bytes): + any_message = any_pb2.Any() + any_message.ParseFromString(data) + return any_message + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, UPayload): + return False + return self.data == other.data and self.hint == other.hint and self.size == other.size + + def __hash__(self): + return hash((sys.hash_info.width, tuple(self.data), self.hint, self.size)) + + def __str__(self): + return f"UPayload{{data={list(self.data)}, size={self.size}, hint={self.hint}}}" diff --git a/org_eclipse_uprotocol/transport/datamodel/upriority.py b/org_eclipse_uprotocol/transport/datamodel/upriority.py new file mode 100644 index 0000000..26b4212 --- /dev/null +++ b/org_eclipse_uprotocol/transport/datamodel/upriority.py @@ -0,0 +1,63 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +from enum import Enum, unique + + +@unique +class UPriority(Enum): + # Low Priority. No bandwidth assurance such as File Transfer. + LOW = ("CS0", 0) + # Standard, undifferentiated application such as General (unclassified). + STANDARD = ("CS1", 1) + # Operations, Administration, and Management such as Streamer messages (sub, connect, etc…) + OPERATIONS = ("CS2", 2) + # Multimedia streaming such as Video Streaming + MULTIMEDIA_STREAMING = ("CS3", 3) + # Real-time interactive such as High priority (rpc events) + REALTIME_INTERACTIVE = ("CS4", 4) + # Signaling such as Important + SIGNALING = ("CS5", 5) + # Network control such as Safety Critical + NETWORK_CONTROL = ("CS6", 6) + + @property + def qos_string(self): + return self.value[0] + + @property + def int_value(self): + return self.value[1] + + @classmethod + def from_int(cls, value: int): + for item in cls: + if item.value[1] == value: + return item + return None + + @classmethod + def from_string(cls, value: str): + for item in cls: + if item.value[0] == value: + return item + return None diff --git a/org_eclipse_uprotocol/transport/datamodel/userializationhint.py b/org_eclipse_uprotocol/transport/datamodel/userializationhint.py new file mode 100644 index 0000000..da1f922 --- /dev/null +++ b/org_eclipse_uprotocol/transport/datamodel/userializationhint.py @@ -0,0 +1,65 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +from enum import Enum, unique + + +@unique +class USerializationHint(Enum): + # Serialization hint is unknown + UNKNOWN = (0, "") + + # serialized com.google.protobuf.Any type + PROTOBUF = (1, "application/x-protobuf") + + # data is a UTF-8 string containing a JSON structure + JSON = (2, "application/json") + + # data is a UTF-8 string containing a JSON structure + SOMEIP = (3, "application/x-someip") + + # Raw binary data that has not been serialized + RAW = (4, "application/octet-stream") + + def __init__(self, hint_number: int, mime_type: str): + self.hint_number = hint_number + self.mime_type = mime_type + + def get_hint_number(self): + return self.hint_number + + def get_mime_type(self): + return self.mime_type + + @classmethod + def from_int(cls, value: int): + for item in cls: + if item.value[0] == value: + return item + return None + + @classmethod + def from_string(cls, mime_type: str): + for item in cls: + if item.value[1] == mime_type: + return item + return None diff --git a/org_eclipse_uprotocol/transport/datamodel/ustatus.py b/org_eclipse_uprotocol/transport/datamodel/ustatus.py new file mode 100644 index 0000000..3eab316 --- /dev/null +++ b/org_eclipse_uprotocol/transport/datamodel/ustatus.py @@ -0,0 +1,137 @@ +# ------------------------------------------------------------------------- + +# 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. + +from abc import ABC, abstractmethod +# ------------------------------------------------------------------------- +from enum import Enum +from typing import Optional + + +class Code(Enum): + OK = 0 + CANCELLED = 1 + UNKNOWN = 2 + INVALID_ARGUMENT = 3 + DEADLINE_EXCEEDED = 4 + NOT_FOUND = 5 + ALREADY_EXISTS = 6 + PERMISSION_DENIED = 7 + UNAUTHENTICATED = 16 + RESOURCE_EXHAUSTED = 8 + FAILED_PRECONDITION = 9 + ABORTED = 10 + OUT_OF_RANGE = 11 + UNIMPLEMENTED = 12 + INTERNAL = 13 + UNAVAILABLE = 14 + DATA_LOSS = 15 + UNSPECIFIED = -1 + + +class UStatus(ABC): + OK = "ok" + FAILED = "failed" + + @abstractmethod + def isSuccess(self) -> bool: + pass + + @abstractmethod + def msg(self) -> str: + pass + + @abstractmethod + def getCode(self) -> int: + pass + + def isFailed(self) -> bool: + return not self.isSuccess() + + def __str__(self) -> str: + return f"UStatus {'ok' if self.isSuccess() else 'failed'} " \ + f"{'id=' if self.isSuccess() else 'msg='}{self.msg()} code={self.getCode()}" + + @staticmethod + def ok(): + return OKStatus(UStatus.OK) + + @staticmethod + def ok_with_ack_id(ack_id: str): + return OKStatus(ack_id) + + @staticmethod + def failed(): + return FailStatus(UStatus.FAILED, Code.UNKNOWN) + + @staticmethod + def failed_with_msg(msg: str): + return FailStatus(msg, Code.UNKNOWN) + + @staticmethod + def failed_with_msg_and_code(msg: str, code: Code): + return FailStatus(msg, code) + + @staticmethod + def from_int(value: int) -> Optional[Code]: + for item in Code: + if item.value == value: + return item + return None + + @staticmethod + def from_google_rpc_code(code: int) -> Optional[Code]: + if code == -1: # UNRECOGNIZED + return None + for item in Code: + if item.value == code: + return item + return None + + +class OKStatus(UStatus): + + def __init__(self, ack_id: str): + self.ack_id = ack_id + + def isSuccess(self) -> bool: + return True + + def msg(self) -> str: + return self.ack_id + + def getCode(self) -> int: + return Code.OK.value + + +class FailStatus(UStatus): + + def __init__(self, fail_msg: str, code: Code): + self.fail_msg = fail_msg + self.code = code + + def isSuccess(self) -> bool: + return False + + def msg(self) -> str: + return self.fail_msg + + def getCode(self) -> int: + return self.code.value diff --git a/org_eclipse_uprotocol/transport/utransport.py b/org_eclipse_uprotocol/transport/utransport.py new file mode 100644 index 0000000..7a069fb --- /dev/null +++ b/org_eclipse_uprotocol/transport/utransport.py @@ -0,0 +1,49 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +from abc import ABC, abstractmethod + +from org_eclipse_uprotocol.transport.datamodel.uattributes import UAttributes +from org_eclipse_uprotocol.transport.datamodel.ulistener import UListener +from org_eclipse_uprotocol.transport.datamodel.upayload import UPayload +from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus +from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity +from org_eclipse_uprotocol.uri.datamodel.uuri import UUri + + +class UTransport(ABC): + + @abstractmethod + def authenticate(self, u_entity: UEntity) -> UStatus: + pass + + @abstractmethod + def send(self, topic: UUri, payload: UPayload, attributes: UAttributes) -> UStatus: + pass + + @abstractmethod + def register_listener(self, topic: UUri, listener: UListener) -> UStatus: + pass + + @abstractmethod + def unregister_listener(self, topic: UUri, listener: UListener) -> UStatus: + pass diff --git a/org_eclipse_uprotocol/transport/validate/__init__.py b/org_eclipse_uprotocol/transport/validate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py new file mode 100644 index 0000000..51d0b22 --- /dev/null +++ b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py @@ -0,0 +1,178 @@ +# ------------------------------------------------------------------------- +import time +from abc import abstractmethod +# ------------------------------------------------------------------------- +from enum import Enum + +from org_eclipse_uprotocol.transport.datamodel.uattributes import UAttributes +from org_eclipse_uprotocol.transport.datamodel.umessagetype import UMessageType +from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus, Code +from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator +from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils + + +# 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. + + +class UAttributesValidator: + @staticmethod + def get_validator(attribute: UAttributes): + if attribute.type is None: + return Validators.PUBLISH.validator() + elif attribute.type == UMessageType.RESPONSE: + return Validators.RESPONSE.validator() + elif attribute.type == UMessageType.REQUEST: + return Validators.REQUEST.validator() + else: + return Validators.PUBLISH.validator() + + @staticmethod + def validate_id(attr: UAttributes) -> UStatus: + attr_id = attr.id + try: + if UUIDUtils.isuuid(attr_id): + return UStatus.ok() + else: + return UStatus.failed_with_msg_and_code(f"Invalid UUID [{attr_id}]", Code.INVALID_ARGUMENT) + except Exception as e: + return UStatus.failed_with_msg_and_code(f"Invalid UUID [{attr_id}] [{str(e)}]", Code.INVALID_ARGUMENT) + + @staticmethod + def validate_priority(attr: UAttributes) -> UStatus: + return UStatus.failed_with_msg_and_code("Priority is missing", + Code.INVALID_ARGUMENT) if attr.priority is None else UStatus.ok() + + @staticmethod + def validate_ttl(attr: UAttributes) -> UStatus: + if attr.ttl and attr.ttl <= 0: + return UStatus.failed_with_msg_and_code(f"Invalid TTL [{attr.ttl}]", Code.INVALID_ARGUMENT) + else: + return UStatus.ok() + + @staticmethod + def validate_sink(attr: UAttributes) -> UStatus: + return UriValidator.validate(attr.sink) if attr.sink else UStatus.ok() + + @staticmethod + def validate_permission_level(attr: UAttributes) -> UStatus: + if attr.plevel and attr.plevel > 0: + return UStatus.ok() + else: + return UStatus.failed_with_msg_and_code("Invalid Permission Level", Code.INVALID_ARGUMENT) + + @staticmethod + def validate_comm_status(attr: UAttributes) -> UStatus: + return UStatus.ok() if attr.commstatus else UStatus.failed_with_msg_and_code( + "Invalid Communication Status Code", Code.INVALID_ARGUMENT) + + @staticmethod + def validate_req_id(attr: UAttributes) -> UStatus: + return UStatus.failed_with_msg_and_code("Invalid UUID", + Code.INVALID_ARGUMENT) if attr.reqid and not UUIDUtils.isuuid( + attr.reqid) else UStatus.ok() + + @abstractmethod + def validate_type(self, attr: UAttributes): + raise NotImplementedError("Subclasses must implement this method.") + + def validate(self, attributes: UAttributes): + error_messages = [self.validate_id(attributes), self.validate_type(attributes), + self.validate_priority(attributes), self.validate_ttl(attributes), + self.validate_sink(attributes), self.validate_comm_status(attributes), + self.validate_permission_level(attributes), self.validate_req_id(attributes)] + + error_messages = [status.msg() for status in error_messages if + status and status.getCode() == Code.INVALID_ARGUMENT] + + if error_messages: + return UStatus.failed_with_msg_and_code(",".join(error_messages), Code.INVALID_ARGUMENT) + else: + return UStatus.ok() + + def is_expired(u_attributes: UAttributes): + try: + maybe_ttl = u_attributes.ttl + maybe_time = UUIDUtils.getTime(u_attributes.id) + + if maybe_time is None: + return UStatus.failed_with_msg_and_code("Invalid Time", Code.INVALID_ARGUMENT) + + if maybe_ttl is None or maybe_ttl <= 0: + return UStatus.ok_with_ack_id("Not Expired") + + delta = time.time() * 1000 - maybe_time + + return UStatus.failed_with_msg_and_code("Payload is expired", + Code.DEADLINE_EXCEEDED) if delta >= maybe_ttl else UStatus.ok_with_ack_id( + "Not Expired") + except Exception: + return UStatus.ok_with_ack_id("Not Expired") + + +class Publish(UAttributesValidator): + def validate_type(self, attributes_value: UAttributes) -> UStatus: + return UStatus.ok() if attributes_value.type == UMessageType.PUBLISH else UStatus.failed_with_msg_and_code( + f"Wrong Attribute Type [{attributes_value.type}]", Code.INVALID_ARGUMENT) + + def __str__(self): + return "UAttributesValidator.Publish" + + +class Request(UAttributesValidator): + def validate_type(self, attributes_value: UAttributes) -> UStatus: + return UStatus.ok() if attributes_value.type == UMessageType.REQUEST else UStatus.failed_with_msg_and_code( + f"Wrong Attribute Type [{attributes_value.type}]", Code.INVALID_ARGUMENT) + + def validate_sink(self, attributes_value: UAttributes) -> UStatus: + return UriValidator.validate_rpc_response( + attributes_value.sink) if attributes_value.sink else UStatus.failed_with_msg_and_code("Missing Sink", + Code.INVALID_ARGUMENT) + + def validate_ttl(self, attributes_value: UAttributes) -> UStatus: + return UStatus.ok() if attributes_value.ttl and attributes_value.ttl > 0 else UStatus.failed_with_msg_and_code( + "Missing TTL", Code.INVALID_ARGUMENT) + + def __str__(self): + return "UAttributesValidator.Request" + + +class Response(UAttributesValidator): + def validate_type(self, attributes_value: UAttributes) -> UStatus: + return UStatus.ok() if attributes_value.type == UMessageType.RESPONSE else UStatus.failed_with_msg_and_code( + f"Wrong Attribute Type [{attributes_value.type}]", Code.INVALID_ARGUMENT) + + def validate_sink(self, attributes_value: UAttributes) -> UStatus: + return UriValidator.validate_rpc_method( + attributes_value.sink) if attributes_value.sink else UStatus.failed_with_msg_and_code("Missing Sink", + Code.INVALID_ARGUMENT) + + def validate_req_id(self, attributes_value: UAttributes) -> UStatus: + return UStatus.ok() if attributes_value.reqid and UUIDUtils.isuuid( + attributes_value.reqid) else UStatus.failed_with_msg_and_code("Missing correlationId", + Code.INVALID_ARGUMENT) + + def __str__(self): + return "UAttributesValidator.Response" + + +class Validators(Enum): + PUBLISH = Publish() + REQUEST = Request() + RESPONSE = Response() + + def validator(self): + return self.value diff --git a/org_eclipse_uprotocol/uri/__init__.py b/org_eclipse_uprotocol/uri/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/uri/datamodel/__init__.py b/org_eclipse_uprotocol/uri/datamodel/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/uri/datamodel/uauthority.py b/org_eclipse_uprotocol/uri/datamodel/uauthority.py new file mode 100644 index 0000000..078b241 --- /dev/null +++ b/org_eclipse_uprotocol/uri/datamodel/uauthority.py @@ -0,0 +1,113 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +import ipaddress +from typing import Optional + + +class UAuthority: + EMPTY = None + + def __init__(self, device: str, domain: str, address: ipaddress.IPv4Address, marked_remote: bool, + marked_resolved: bool): + self.device = device.lower() if device else None + self.domain = domain.lower() if domain else None + self.address = address + self.marked_remote = marked_remote + self.marked_resolved = marked_resolved + + @staticmethod + def local(): + return UAuthority.EMPTY + + @staticmethod + def long_remote(device: str, domain: str): + + return UAuthority(device, domain, None, True, False) + + @staticmethod + def micro_remote(address: ipaddress.IPv4Address): + return UAuthority(None, None, address, True, False) + + @staticmethod + def resolved_remote(device: str, domain: str, address: ipaddress.IPv4Address): + resolved = device is not None and device.strip() != "" and address is not None + return UAuthority(device, domain, address, True, resolved) + + @staticmethod + def empty(): + + return UAuthority.EMPTY + + def is_remote(self) -> bool: + return self.is_marked_Remote() + + # + def is_local(self) -> bool: + return self.is_empty() and not self.is_marked_Remote() + + def is_marked_Remote(self) -> bool: + return self.marked_remote + + def device(self) -> Optional[str]: + return self.device if self.device else None + + def domain(self) -> Optional[str]: + return self.domain if self.domain else None + + def address(self) -> Optional[ipaddress.IPv4Address]: + try: + return ipaddress.IPv4Address(self.address) + except ValueError: + return None + + def is_marked_remote(self) -> bool: + return self.marked_remote + + def is_resolved(self) -> bool: + return self.marked_resolved + + def is_long_form(self) -> bool: + return self.is_local() or self.device is not None + + def is_micro_form(self) -> bool: + return self.is_local() or self.address is not None + + def is_empty(self) -> bool: + return all([self.domain is None or self.domain.strip() == "", self.device is None or self.device.strip() == "", + self.address is None]) + + def __eq__(self, other): + if not isinstance(other, UAuthority): + return False + return ( + self.marked_remote == other.marked_remote and self.device == other.device and self.domain == other.domain) + + def __hash__(self): + return hash((self.device, self.domain, self.marked_remote)) + + def __str__(self): + return f"UAuthority{{device='{self.device}', domain='{self.domain}', markedRemote={self.marked_remote}}}" + + +# Initialize EMPTY +UAuthority.EMPTY = UAuthority(None, None, None, False, True) diff --git a/org_eclipse_uprotocol/uri/datamodel/uentity.py b/org_eclipse_uprotocol/uri/datamodel/uentity.py new file mode 100644 index 0000000..329a4d5 --- /dev/null +++ b/org_eclipse_uprotocol/uri/datamodel/uentity.py @@ -0,0 +1,98 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +class UEntity: + EMPTY = None + + def __init__(self, name: str, version: int, identifier: int, marked_resolved: bool): + if name is None: + raise ValueError("Software Entity must have a name") + self.name = name + self.version = version + self.id = identifier + self.marked_resolved = marked_resolved + + @staticmethod + def resolved_format(name: str, version: int, identifier: int): + resolved = name and name.strip() and identifier is not None + return UEntity(name if name else "", version, identifier, resolved) + + @staticmethod + def long_format(name: str, version: int): + return UEntity(name if name else "", version, None, False) + + @staticmethod + def long_format_name(name: str): + return UEntity(name if name else "", None, None, False) + + @staticmethod + def micro_format(identifier: int): + return UEntity("", None, identifier, False) + + @staticmethod + def micro_format_id_version(identifier: int, version: int): + return UEntity("", version, identifier, False) + + @staticmethod + def empty(): + if UEntity.EMPTY is None: + UEntity.EMPTY = UEntity("", None, None, False) + return UEntity.EMPTY + + def is_empty(self) -> bool: + return not (self.name or self.version or self.id) + + def is_resolved(self) -> bool: + return self.marked_resolved + + def is_long_form(self) -> bool: + return bool(self.name) + + def is_micro_form(self) -> bool: + return self.id is not None + + def get_name(self) -> str: + return self.name + + def get_version(self) -> int: + return self.version + + def get_id(self) -> int: + return self.id + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, UEntity): + return False + return ( + self.marked_resolved == other.marked_resolved and self.name == other.name and self.version == other.version and self.id == other.id) + + def __hash__(self): + return hash((self.name, self.version, self.id, self.marked_resolved)) + + def __str__(self): + return f"UEntity{{name='{self.name}', version={self.version}, id={self.id}, markedResolved={self.marked_resolved}}}" + + +# Initialize EMPTY +UEntity.EMPTY = UEntity("", None, None, False) diff --git a/org_eclipse_uprotocol/uri/datamodel/uresource.py b/org_eclipse_uprotocol/uri/datamodel/uresource.py new file mode 100644 index 0000000..597b887 --- /dev/null +++ b/org_eclipse_uprotocol/uri/datamodel/uresource.py @@ -0,0 +1,121 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +class UResource: + EMPTY = None + RESPONSE = None + + def __init__(self, name: str, instance: str, message: str, identifier: int, marked_resolved: bool): + self.name = name if name else "" + self.instance = instance + self.message = message + self.id = identifier + self.marked_resolved = marked_resolved + + @staticmethod + def resolved_format(name: str, instance: str, message: str, identifier: int): + resolved = name and name.strip() and identifier is not None + return UResource(name if name else "", instance, message, identifier, resolved) + + @staticmethod + def long_format(name: str): + return UResource(name if name else "", None, None, None, False) + + @staticmethod + def long_format_instance_message(name: str, instance: str, message: str): + return UResource(name if name else "", instance, message, None, False) + + @staticmethod + def micro_format(identifier: int): + return UResource("", None, None, identifier, False) + + @staticmethod + def for_rpc_request(method_name: str): + return UResource("rpc", method_name, None, None, False) + + @staticmethod + def for_rpc_request_with_id(method_id: int): + return UResource("rpc", None, None, method_id, False) + + @staticmethod + def for_rpc_request_with_name_and_id(method_name: str, method_id: int): + resolved = method_name and method_name.strip() and method_id is not None + return UResource("rpc", method_name, None, method_id, resolved) + + @staticmethod + def for_rpc_response(): + return UResource.RESPONSE + + def is_rpc_method(self) -> bool: + return self.name == "rpc" + + @staticmethod + def empty(): + if UResource.EMPTY is None: + UResource.EMPTY = UResource("", None, None, None, False) + return UResource.EMPTY + + def is_empty(self) -> bool: + return not (self.name and self.name.strip()) or self.name == "rpc" and not ( + self.instance or self.message or self.id) + + def get_name(self) -> str: + return self.name + + def get_id(self) -> int: + return self.id + + def get_instance(self) -> str: + return self.instance + + def get_message(self) -> str: + return self.message + + def is_resolved(self) -> bool: + return self.marked_resolved + + def is_long_form(self) -> bool: + if self.name == "rpc": + return bool(self.instance) + return bool(self.name) + + def is_micro_form(self) -> bool: + return self.id is not None + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, UResource): + return False + return ( + self.marked_resolved == other.marked_resolved and self.name == other.name and self.instance == other.instance and self.message == other.message and self.id == other.id) + + def __hash__(self): + return hash((self.name, self.instance, self.message, self.id, self.marked_resolved)) + + def __str__(self): + return f"UResource{{name='{self.name}', instance='{self.instance}', message='{self.message}', id={self.id}, markedResolved={self.marked_resolved}}}" + + +# Initialize EMPTY and RESPONSE +UResource.EMPTY = UResource("", None, None, None, False) +UResource.RESPONSE = UResource("rpc", "response", None, 0, True) diff --git a/org_eclipse_uprotocol/uri/datamodel/uuri.py b/org_eclipse_uprotocol/uri/datamodel/uuri.py new file mode 100644 index 0000000..7518a8e --- /dev/null +++ b/org_eclipse_uprotocol/uri/datamodel/uuri.py @@ -0,0 +1,83 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority +from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity +from org_eclipse_uprotocol.uri.datamodel.uresource import UResource + + +class UUri: + EMPTY = None + + def __init__(self, authority: UAuthority, entity: UEntity, resource: UResource): + self.authority = authority if authority else UAuthority.empty() + self.entity = entity if entity else UEntity.empty() + self.resource = resource if resource else UResource.empty() + + @staticmethod + def rpc_response(authority: UAuthority, entity: UEntity): + return UUri(authority, entity, UResource.for_rpc_response()) + + @staticmethod + def empty(): + if UUri.EMPTY is None: + UUri.EMPTY = UUri(UAuthority.empty(), UEntity.empty(), UResource.empty()) + return UUri.EMPTY + + def is_empty(self): + return self.authority.is_empty() and self.entity.is_empty() and self.resource.is_empty() + + def is_resolved(self) -> bool: + return self.authority.is_resolved() and self.entity.is_resolved() and self.resource.is_resolved() + + def is_long_form(self) -> bool: + return self.authority.is_long_form() and (self.entity.is_long_form() or self.entity.is_empty()) and ( + self.resource.is_long_form() or self.resource.is_empty()) + + def is_micro_form(self) -> bool: + return self.authority.is_micro_form() and self.entity.is_micro_form() and self.resource.is_micro_form() + + def get_u_authority(self) -> UAuthority: + return self.authority + + def get_u_entity(self) -> UEntity: + return self.entity + + def get_u_resource(self) -> UResource: + return self.resource + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, UUri): + return False + return self.authority == other.authority and self.entity == other.entity and self.resource == other.resource + + def __hash__(self): + return hash((self.authority, self.entity, self.resource)) + + def __str__(self): + return f"UUri{{uAuthority={self.authority}, uEntity={self.entity}, uResource={self.resource}}}" + + +# Initialize EMPTY +UUri.EMPTY = UUri(UAuthority.empty(), UEntity.empty(), UResource.empty()) diff --git a/org_eclipse_uprotocol/uri/serializer/__init__.py b/org_eclipse_uprotocol/uri/serializer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/uri/serializer/longuriserializer.py b/org_eclipse_uprotocol/uri/serializer/longuriserializer.py new file mode 100644 index 0000000..7416e94 --- /dev/null +++ b/org_eclipse_uprotocol/uri/serializer/longuriserializer.py @@ -0,0 +1,164 @@ +# ------------------------------------------------------------------------- + +# 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. + +import re + +# ------------------------------------------------------------------------- +from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority +from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity +from org_eclipse_uprotocol.uri.datamodel.uresource import UResource +from org_eclipse_uprotocol.uri.datamodel.uuri import UUri +from org_eclipse_uprotocol.uri.serializer.uriserializer import UriSerializer + + +# Define the classes UUri, UAuthority, UEntity, and UResource with their respective methods and properties. + + +class LongUriSerializer(UriSerializer): + # def __init__(self): + # pass + # + # @staticmethod + # def instance(): + # return LongUriSerializer() + + def serialize(self, uri: UUri) -> str: + if uri is None or uri.is_empty(): + return "" + + sb = [] + + sb.append(self.build_authority_part_of_uri(uri.get_u_authority())) + + if uri.get_u_authority().is_marked_remote(): + sb.append("/") + + if uri.get_u_entity().is_empty(): + return "".join(sb) + + sb.append(self.build_software_entity_part_of_uri(uri.get_u_entity())) + + sb.append(self.build_resource_part_of_uri(uri.get_u_resource())) + + return re.sub('/+$', '', "".join(sb)) + + @staticmethod + def build_resource_part_of_uri(res: UResource) -> str: + if res.is_empty(): + return "" + + sb = ["/", res.get_name()] + + if res.get_instance(): + sb.append("." + res.get_instance()) + + if res.get_message(): + sb.append("#" + res.get_message()) + + return "".join(sb) + + @staticmethod + def build_software_entity_part_of_uri(entity: UEntity) -> str: + sb = [entity.get_name().strip(), "/"] + + if entity.get_version(): + sb.append(str(entity.get_version())) + + return "".join(sb) + + @staticmethod + def build_authority_part_of_uri(authority: UAuthority) -> str: + if authority.is_local(): + return "/" + + partial_uri = ["//"] + maybe_device = authority.device + maybe_domain = authority.domain + + if maybe_device: + partial_uri.append(maybe_device) + if maybe_domain: + partial_uri.append(".") + + if maybe_domain: + partial_uri.append(maybe_domain) + + return "".join(partial_uri) + + def deserialize(self, u_protocol_uri: str) -> UUri: + if u_protocol_uri is None or u_protocol_uri.strip() == "": + return UUri.empty() + + uri = u_protocol_uri.split(":")[-1].replace('\\', '/') + is_local = not uri.startswith("//") + uri_parts = uri.split("/") + number_of_parts_in_uri = len(uri_parts) + + if number_of_parts_in_uri == 0 or number_of_parts_in_uri == 1: + if is_local: + return UUri.empty() + else: + return UUri(UAuthority.long_remote("", ""), UEntity.empty(), UResource.empty()) + + use_name = uri_parts[1] + use_version = "" + + if is_local: + auth = UAuthority.local() + if number_of_parts_in_uri > 2: + use_version = uri_parts[2] + res = self.parse_from_string(uri_parts[3]) if number_of_parts_in_uri > 3 else UResource.empty() + else: + res = UResource.empty() + else: + authority_parts = uri_parts[2].split(".") + device = authority_parts[0] + domain = ".".join(authority_parts[1:]) if len(authority_parts) > 1 else "" + auth = UAuthority.long_remote(device, domain) + + if number_of_parts_in_uri > 3: + use_name = uri_parts[3] + if number_of_parts_in_uri > 4: + use_version = uri_parts[4] + res = self.parse_from_string(uri_parts[5]) if number_of_parts_in_uri > 5 else UResource.empty() + else: + res = UResource.empty() + else: + return UUri(auth, UEntity.empty(), UResource.empty()) + + use_version_int = int(use_version) if use_version.strip() else None + + return UUri(auth, UEntity.long_format(use_name, use_version_int), res) + + @staticmethod + def parse_from_string(resource_string: str) -> UResource: + if resource_string is None: + raise ValueError("Resource must have a command name.") + + parts = resource_string.split("#") + name_and_instance = parts[0] + + name_and_instance_parts = name_and_instance.split(".") + resource_name = name_and_instance_parts[0] + resource_instance = name_and_instance_parts[1] if len(name_and_instance_parts) > 1 else None + resource_message = parts[1] if len(parts) > 1 else None + + return UResource.long_format_instance_message(resource_name, resource_instance, resource_message) diff --git a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py new file mode 100644 index 0000000..9912a0e --- /dev/null +++ b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py @@ -0,0 +1,143 @@ +# ------------------------------------------------------------------------- + +# 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. + +import io +# ------------------------------------------------------------------------- +import ipaddress +import socket +from enum import Enum +from typing import Optional + +from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority +from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity +from org_eclipse_uprotocol.uri.datamodel.uresource import UResource +from org_eclipse_uprotocol.uri.datamodel.uuri import UUri +from org_eclipse_uprotocol.uri.serializer.uriserializer import UriSerializer + + +# class AddressType(Enum): +# LOCAL = 0 +# IPV4 = 1 +# IPV6 = 2 +# +# def __init__(self, value): +# self._value = value +# +# @property +# def value(self): +# return self._value + +class AddressType(Enum): + LOCAL = 0 + IPv4 = 1 + IPv6 = 2 + + def getValue(self): + return self.value + + @classmethod + def from_value(cls, value): + for addr_type in cls: + if addr_type.value == value: + return addr_type + return None # Return None if no matching enum value is found + + +class MicroUriSerializer(UriSerializer): + LOCAL_MICRO_URI_LENGTH = 8 + IPV4_MICRO_URI_LENGTH = 12 + IPV6_MICRO_URI_LENGTH = 24 + UP_VERSION = 0x1 + + def serialize(self, uri: UUri) -> bytes: + if uri is None or uri.is_empty(): + return bytearray() + + maybe_address = uri.get_u_authority().address + maybe_ue_id = uri.get_u_entity().id + maybe_uresource_id = uri.get_u_resource().id + + if maybe_uresource_id is None or maybe_ue_id is None: + return bytearray() + + os = io.BytesIO() + os.write(bytes([self.UP_VERSION])) + + if maybe_address: + os.write( + bytes([AddressType.IPv4.getValue()]) if isinstance(maybe_address, ipaddress.IPv4Address) else bytes( + [AddressType.IPv6.getValue()])) + else: + os.write(bytes([AddressType.LOCAL.getValue()])) + + os.write(maybe_uresource_id.to_bytes(2, byteorder='big')) + + maybe_uauthority_address_bytes = self.calculate_uauthority_bytes(uri.get_u_authority()) + if maybe_uauthority_address_bytes: + os.write(maybe_uauthority_address_bytes) + + os.write(maybe_ue_id.to_bytes(2, byteorder='big')) + + version = uri.get_u_entity().get_version() + os.write(version.to_bytes(1, byteorder='big') if version else b'\x00') + os.write(b'\x00') # Unused byte + + return os.getvalue() + + def calculate_uauthority_bytes(self, uauthority: UAuthority) -> Optional[bytes]: + maybe_address = uauthority.address + return maybe_address.packed if maybe_address else None + + def deserialize(self, micro_uri: bytes) -> UUri: + if micro_uri is None or len(micro_uri) < self.LOCAL_MICRO_URI_LENGTH: + return UUri.empty() + + if micro_uri[0] != self.UP_VERSION: + return UUri.empty() + + uresource_id = int.from_bytes(micro_uri[2:4], byteorder='big') + address_type = AddressType.from_value(micro_uri[1]) + + if address_type == AddressType.LOCAL and len(micro_uri) != self.LOCAL_MICRO_URI_LENGTH: + return UUri.empty() + elif address_type == AddressType.IPv4 and len(micro_uri) != self.IPV4_MICRO_URI_LENGTH: + return UUri.empty() + elif address_type == AddressType.IPv6 and len(micro_uri) != self.IPV6_MICRO_URI_LENGTH: + return UUri.empty() + + index = 4 + if address_type == AddressType.LOCAL: + u_authority = UAuthority.local() + else: + try: + inet_address = socket.inet_ntop(socket.AF_INET, micro_uri[ + index:index + 4]) if address_type == AddressType.IPv4 else socket.inet_ntop( + socket.AF_INET6, micro_uri[index:index + 16]) + u_authority = UAuthority.micro_remote(ipaddress.ip_address(inet_address)) + except: + u_authority = UAuthority.local() + index += 4 if address_type == AddressType.IPv4 else 16 + + ue_id = int.from_bytes(micro_uri[index:index + 2], byteorder='big') + ui_version = micro_uri[index + 2] + + return UUri(u_authority, UEntity.micro_format_id_version(ue_id, ui_version if ui_version != 0 else None), + UResource.micro_format(uresource_id)) diff --git a/org_eclipse_uprotocol/uri/serializer/uriserializer.py b/org_eclipse_uprotocol/uri/serializer/uriserializer.py new file mode 100644 index 0000000..431eff9 --- /dev/null +++ b/org_eclipse_uprotocol/uri/serializer/uriserializer.py @@ -0,0 +1,64 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +from abc import ABC, abstractmethod +from re import T +from typing import Optional + +from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority +from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity +from org_eclipse_uprotocol.uri.datamodel.uresource import UResource +from org_eclipse_uprotocol.uri.datamodel.uuri import UUri + + +class UriSerializer(ABC): + @abstractmethod + def deserialize(self, uri: T) -> UUri: + pass + + @abstractmethod + def serialize(self, uri: UUri) -> T: + pass + + def build_resolved(self, long_uri: str, micro_uri: bytes) -> Optional[UUri]: + if (not long_uri or long_uri.isspace()) and (not micro_uri or len(micro_uri) == 0): + return Optional.of(UUri.empty()) + from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer + from org_eclipse_uprotocol.uri.serializer.microuriserializer import MicroUriSerializer + long_u_uri = LongUriSerializer().deserialize(long_uri) + micro_u_uri = MicroUriSerializer().deserialize(micro_uri) + + u_authority = (UAuthority.local() if long_u_uri.get_u_authority().is_local() else UAuthority.resolved_remote( + long_u_uri.get_u_authority().device or None, long_u_uri.get_u_authority().domain or None, + micro_u_uri.get_u_authority().address or None)) + + u_entity = UEntity.resolved_format(long_u_uri.get_u_entity().get_name(), + long_u_uri.get_u_entity().get_version() or None, + micro_u_uri.get_u_entity().get_id() or None) + + u_resource = UResource.resolved_format(long_u_uri.get_u_resource().get_name(), + long_u_uri.get_u_resource().get_instance() or None, + long_u_uri.get_u_resource().get_message() or None, + micro_u_uri.get_u_resource().get_id() or None) + + u_uri = UUri(u_authority, u_entity, u_resource) + return u_uri if u_uri.is_resolved() else None diff --git a/org_eclipse_uprotocol/uri/validator/__init__.py b/org_eclipse_uprotocol/uri/validator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/uri/validator/urivalidator.py b/org_eclipse_uprotocol/uri/validator/urivalidator.py new file mode 100644 index 0000000..b1af7bb --- /dev/null +++ b/org_eclipse_uprotocol/uri/validator/urivalidator.py @@ -0,0 +1,63 @@ +# ------------------------------------------------------------------------- +from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus, Code +# 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. + +# ------------------------------------------------------------------------- + +from org_eclipse_uprotocol.uri.datamodel.uresource import UResource +from org_eclipse_uprotocol.uri.datamodel.uuri import UUri + + +class UriValidator: + @staticmethod + def validate(uri: UUri) -> UStatus: + if uri.is_empty(): + return UStatus.failed_with_msg_and_code("Uri is empty.", Code.INVALID_ARGUMENT) + + if uri.get_u_entity().name.strip() == "": + return UStatus.failed_with_msg_and_code("Uri is missing uSoftware Entity name.", Code.INVALID_ARGUMENT) + + return UStatus.ok() + + @staticmethod + def validate_rpc_method(uri: UUri) -> UStatus: + status = UriValidator.validate(uri) + if status.isFailed(): + return status + + u_resource = uri.get_u_resource() + if not u_resource.is_rpc_method(): + return UStatus.failed_with_msg_and_code( + "Invalid RPC method uri. Uri should be the method to be called, or method from response.", + Code.INVALID_ARGUMENT) + + return UStatus.ok() + + @staticmethod + def validate_rpc_response(uri: UUri) -> UStatus: + status = UriValidator.validate(uri) + if status.isFailed(): + return status + + u_resource = uri.get_u_resource() + if not (u_resource.is_rpc_method() and u_resource.instance == UResource.for_rpc_response().instance): + return UStatus.failed_with_msg_and_code("Invalid RPC response type.", Code.INVALID_ARGUMENT) + + return UStatus.ok() diff --git a/org_eclipse_uprotocol/uuid/__init__.py b/org_eclipse_uprotocol/uuid/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/uuid/factory/__init__.py b/org_eclipse_uprotocol/uuid/factory/__init__.py new file mode 100644 index 0000000..75039d5 --- /dev/null +++ b/org_eclipse_uprotocol/uuid/factory/__init__.py @@ -0,0 +1,150 @@ + +import secrets +import time +import uuid +from typing import Tuple + + +class UUID(uuid.UUID): + r"""UUID draft version objects""" + + def __init__( + self, + hex: str = None, + bytes: bytes = None, + bytes_le: bytes = None, + fields: Tuple[int, int, int, int, int, int] = None, + int: int = None, + version: int = None, + *, + is_safe=uuid.SafeUUID.unknown + ) -> None: + r"""Create a UUID.""" + + if int is None or [hex, bytes, bytes_le, fields].count(None) != 4: + return super().__init__( + hex=hex, + bytes=bytes, + bytes_le=bytes_le, + fields=fields, + int=int, + version=version, + is_safe=is_safe, + ) + if not 0 <= int < 1 << 128: + raise ValueError("int is out of range (need a 128-bit value)") + if version is not None: + if not 6 <= version <= 8: + raise ValueError("illegal version number") + # Set the variant to RFC 4122. + int &= ~(0xC000 << 48) + int |= 0x8000 << 48 + # Set the version number. + int &= ~(0xF000 << 64) + int |= version << 76 + super().__init__(int=int, is_safe=is_safe) + + @property + def subsec(self) -> int: + return ((self.int >> 64) & 0x0FFF) << 8 | ((self.int >> 54) & 0xFF) + + @property + def time(self) -> int: + if self.version == 6: + return ( + (self.time_low << 28) + | (self.time_mid << 12) + | (self.time_hi_version & 0x0FFF) + ) + if self.version == 7: + return self.int >> 80 + if self.version == 8: + return (self.int >> 80) * 10**6 + _subsec_decode(self.subsec) + return super().time + + +def _subsec_decode(value: int) -> int: + return -(-value * 10**6 // 2**20) + + +def _subsec_encode(value: int) -> int: + return value * 2**20 // 10**6 + + +_last_v6_timestamp = None +_last_v7_timestamp = None +_last_v8_timestamp = None + + +def uuid6(clock_seq: int = None) -> UUID: + r"""UUID version 6 is a field-compatible version of UUIDv1, reordered for + improved DB locality. It is expected that UUIDv6 will primarily be + used in contexts where there are existing v1 UUIDs. Systems that do + not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead. + + If 'clock_seq' is given, it is used as the sequence number; + otherwise a random 14-bit sequence number is chosen.""" + + global _last_v6_timestamp + + nanoseconds = time.time_ns() + # 0x01b21dd213814000 is the number of 100-ns intervals between the + # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00. + timestamp = nanoseconds // 100 + 0x01B21DD213814000 + if _last_v6_timestamp is not None and timestamp <= _last_v6_timestamp: + timestamp = _last_v6_timestamp + 1 + _last_v6_timestamp = timestamp + if clock_seq is None: + clock_seq = secrets.randbits(14) # instead of stable storage + time_high_and_time_mid = (timestamp >> 12) & 0xFFFFFFFFFFFF + time_low_and_version = timestamp & 0x0FFF + uuid_int = time_high_and_time_mid << 80 + uuid_int |= time_low_and_version << 64 + uuid_int |= (clock_seq & 0x3FFF) << 48 + uuid_int |= secrets.randbits(48) + return UUID(int=uuid_int, version=6) + + +def uuid7() -> UUID: + r"""UUID version 7 features a time-ordered value field derived from the + widely implemented and well known Unix Epoch timestamp source, the + number of milliseconds seconds since midnight 1 Jan 1970 UTC, leap + seconds excluded. As well as improved entropy characteristics over + versions 1 or 6. + + Implementations SHOULD utilize UUID version 7 over UUID version 1 and + 6 if possible.""" + + global _last_v7_timestamp + + nanoseconds = time.time_ns() + timestamp_ms, _ = divmod(nanoseconds, 10**6) + if _last_v7_timestamp is not None and timestamp_ms <= _last_v7_timestamp: + timestamp_ms = _last_v7_timestamp + 1 + _last_v7_timestamp = timestamp_ms + uuid_int = (timestamp_ms & 0xFFFFFFFFFFFF) << 80 + uuid_int |= secrets.randbits(76) + return UUID(int=uuid_int, version=7) + + +def uuid8() -> UUID: + r"""UUID version 8 features a time-ordered value field derived from the + widely implemented and well known Unix Epoch timestamp source, the + number of nanoseconds seconds since midnight 1 Jan 1970 UTC, leap + seconds excluded.""" + + global _last_v8_timestamp + + nanoseconds = time.time_ns() + if _last_v8_timestamp is not None and nanoseconds <= _last_v8_timestamp: + nanoseconds = _last_v8_timestamp + 1 + _last_v8_timestamp = nanoseconds + timestamp_ms, timestamp_ns = divmod(nanoseconds, 10**6) + subsec = _subsec_encode(timestamp_ns) + subsec_a = subsec >> 8 + subsec_b = subsec & 0xFF + uuid_int = (timestamp_ms & 0xFFFFFFFFFFFF) << 80 + uuid_int |= subsec_a << 64 + uuid_int |= subsec_b << 54 + uuid_int |= secrets.randbits(54) + return UUID(int=uuid_int, version=8) diff --git a/org_eclipse_uprotocol/uuid/factory/uuidfactory.py b/org_eclipse_uprotocol/uuid/factory/uuidfactory.py new file mode 100644 index 0000000..2ab25ff --- /dev/null +++ b/org_eclipse_uprotocol/uuid/factory/uuidfactory.py @@ -0,0 +1,66 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +import time +import uuid + +import org_eclipse_uprotocol.uuid.factory as uuid + + +class UUIDFactory: + def create(self, instant=None): + if instant is None: + instant = int(time.time() * 1000) # Current time in milliseconds + return self._create(instant) + + def _create(self, instant): + pass + + +class UUIDv6Factory(UUIDFactory): + + def _create(self, instant): + return uuid.uuid6() + + +class UUIDv8Factory(UUIDFactory): + + def _create(self, instant): + return uuid.uuid8() + + +class Factories: + UUIDV6 = UUIDv6Factory() + UPROTOCOL = UUIDv8Factory() + + +def is_uuid_version_6(uuid): + try: + return uuid.version == 6 + + except ValueError: + # Invalid UUID string + return False + + +def is_version_8(uuid): + return uuid.version == 8 diff --git a/org_eclipse_uprotocol/uuid/factory/uuidutils.py b/org_eclipse_uprotocol/uuid/factory/uuidutils.py new file mode 100644 index 0000000..f48699e --- /dev/null +++ b/org_eclipse_uprotocol/uuid/factory/uuidutils.py @@ -0,0 +1,113 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- +import uuid +from enum import Enum +from typing import Optional + +from org_eclipse_uprotocol.uuid.factory import UUID + + +class Version(Enum): + VERSION_UNKNOWN = 0 + VERSION_RANDOM_BASED = 4 + VERSION_TIME_ORDERED = 6 + VERSION_UPROTOCOL = 8 + + @staticmethod + def getVersion(value): + for version in Version: + if version.value == value: + return version + return None + + +class UUIDUtils: + @staticmethod + def toString(uuid_obj: UUID) -> str: + return str(uuid_obj) if uuid_obj is not None else None + + @staticmethod + def toBytes(uuid_obj: UUID) -> Optional[bytes]: + if uuid_obj is None: + return None + uuid_bytes = uuid_obj.bytes + return uuid_bytes + + @staticmethod + def fromBytes(bytes_list: bytes) -> Optional[UUID]: + if bytes_list is None or len(bytes_list) != 16: + return None + uuid_bytes = bytes(bytes_list) + return uuid.UUID(bytes=uuid_bytes) + + @staticmethod + def fromString(string: str) -> Optional[UUID]: + try: + return uuid.UUID(string) + except ValueError: + return None + + @staticmethod + def getVersion(uuid_obj: UUID) -> Optional[Version]: + if uuid_obj is None: + return None + return Version.getVersion(uuid_obj.version) + + @staticmethod + def getVariant(uuid_obj: UUID) -> Optional[str]: + if uuid_obj is None: + return None + return uuid_obj.variant + + @staticmethod + def isUProtocol(uuid_obj: UUID) -> bool: + return UUIDUtils.getVersion(uuid_obj) == Version.VERSION_UPROTOCOL if uuid_obj is not None else False + + @staticmethod + def isUuidv6(uuid_obj: UUID) -> bool: + if uuid_obj is None: + return False + return UUIDUtils.getVersion(uuid_obj) == Version.VERSION_TIME_ORDERED and UUIDUtils.getVariant( + uuid_obj) == uuid.RFC_4122 if uuid_obj is not None else False + + @staticmethod + def isuuid(uuid_obj: UUID) -> bool: + return UUIDUtils.isUProtocol(uuid_obj) or UUIDUtils.isUuidv6(uuid_obj) if uuid_obj is not None else False + + @staticmethod + def getTime(uuid: UUID): + time = None + version = UUIDUtils.getVersion(uuid) + if uuid is None or version is None: + return None + + if version == Version.VERSION_UPROTOCOL: + time = uuid.int >> 16 + elif version == Version.VERSION_TIME_ORDERED: + try: + # Convert 100-nanoseconds ticks to milliseconds + time = uuid.time // 10000 + except ValueError: + return None + + return time diff --git a/org_eclipse_uprotocol/uuid/validate/__init__.py b/org_eclipse_uprotocol/uuid/validate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py b/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py new file mode 100644 index 0000000..5580992 --- /dev/null +++ b/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py @@ -0,0 +1,111 @@ +# ------------------------------------------------------------------------- + +# 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. + +from collections import namedtuple +# ------------------------------------------------------------------------- +from enum import Enum + +from org_eclipse_uprotocol.uuid.factory import UUID +from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils + + +class UuidVariant(Enum): + VARIANT_RFC_4122 = 0 + + +class ValidationResult(namedtuple('ValidationResult', ['is_failure', 'message'])): + @staticmethod + def success(): + return ValidationResult(False, "") + + @staticmethod + def failure(message): + return ValidationResult(True, message) + + +class UuidValidator: + @staticmethod + def get_validator(uuid: UUID): + if UUIDUtils.isUuidv6(uuid): + return Validators.UUIDV6.validator() + elif UUIDUtils.isUProtocol(uuid): + return Validators.UPROTOCOL.validator() + else: + return Validators.UNKNOWN.validator() + + def validate(self, uuid: UUID) -> ValidationResult: + error_messages = [self.validate_version(uuid), self.validate_variant(uuid), self.validate_time(uuid)] + error_messages = [result.message for result in error_messages if result.is_failure] + error_message = ", ".join(error_messages) + if not error_message: + return ValidationResult.success() + return ValidationResult.failure(f"Invalid argument value: {error_message}") + + def validate_version(self, uuid: UUID) -> ValidationResult: + return ValidationResult.failure("Invalid UUID Version") # Override in subclasses + + def validate_time(self, uuid: UUID) -> ValidationResult: + time = uuid.time + return ValidationResult.success() if time > 0 else ValidationResult.failure("Invalid UUID Time") + + def validate_variant(self, uuid: UUID) -> ValidationResult: + variant = uuid.variant + return ValidationResult.failure( + "Invalid UUID Variant") if variant != UuidVariant.VARIANT_RFC_4122 else ValidationResult.success() + + +class InvalidValidator(UuidValidator): + def validate_version(self, uuid: UUID) -> ValidationResult: + return ValidationResult.failure("Invalid UUID Version") + + def validate_variant(self, uuid: UUID) -> ValidationResult: + return ValidationResult.failure("Invalid UUID Variant") + + +class UUIDv6Validator(UuidValidator): + def validate_version(self, uuid: UUID) -> ValidationResult: + return ValidationResult.success() if uuid.version == 6 else ValidationResult.failure("Not a UUIDv6 Version") + + def validate_variant(self, uuid: UUID) -> ValidationResult: + return ValidationResult.success() if "RFC 4122" in uuid.variant else ValidationResult.failure( + "Invalid UUIDv6 variant") + + +class UUIDv8Validator(UuidValidator): + def validate_version(self, uuid: UUID) -> ValidationResult: + return ValidationResult.success() if uuid.version == 8 else ValidationResult.failure("Invalid UUIDv8 Version") + + def validate_variant(self, uuid: UUID) -> ValidationResult: + return ValidationResult.success() + + +class Validators(Enum): + UNKNOWN = InvalidValidator() # Use a default validator instance + UUIDV6 = UUIDv6Validator() # Use a default validator instance + UPROTOCOL = UUIDv8Validator() # Use a default validator instance + + def validator(self): + return self.value + + def __new__(cls, value): + obj = object.__new__(cls) + obj._value_ = value + return obj diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..529f652 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,20 @@ +[metadata] +name = uprotocol-sdk-python +author = Neelam Kushwah +author_email = neelam.kushwah@gm.com +description = UProtocol Python SDK +version = 1 +keywords = + uprotocol + sdk + python + +[options] +python_requires = >= 3.8 +packages = find: +zip_safe = False +install_requires = + cloudevents + googleapis-common-protos>=1.56.4 + + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6b40b52 --- /dev/null +++ b/setup.py @@ -0,0 +1,4 @@ +from setuptools import setup + +if __name__ == '__main__': + setup() From a79fd5fd8bb2407d87bdd189986dde298c1ab845 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 19 Oct 2023 16:19:53 -0400 Subject: [PATCH 02/47] Update README.md --- README.md | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fbf0373..6937853 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,83 @@ -# uprotocol-sdk-python -uProtocol Python SDK +image:https://github.com/eclipse-uprotocol/.github/raw/main/logo/uprotocol_logo.png[alt=uProtocol,640] + +image:https://img.shields.io/badge/License-Apache%202.0-blue.svg[License,link=https://opensource.org/licenses/Apache-2.0] + += Eclipse uProtocol Python SDK +:toc: + +*_IMPORTANT NOTE:_ This project is under active development* + +== Overview + +The main object of this module is to enable constructing and deconstructing uProtocol CloudEvents. + +The core module contains functional factory methods for creating CloudEvents as well as functional factory methods that make it more intuitive to create URIs that are used to configure source and sink (destination) elements in the uProtocol CloudEvents. + +This module contains the data model structures as well as core functionality for building uProtocol CloudEvents and URIs for sink and source attributes. + +The SDKs are then used by the code generators to auto-populate service stubs with generated code that builds CloudEvents. For more information on auto-generating service stubs, please refer to http://github.com/eclipse-uprotocol/uprotocol[uProtocol Main Project] + +== Getting Started + + +=== Requirements + +- Python 3.8 or above +- pip + +=== Setup SDK local repository and install + +``` +$ git clone https://github.com/eclipse-uprotocol/uprotocol-sdk-python.git +$ cd uprotocol-sdk-python +$ pip install . + +``` +*This will install the uprotocol-sdk-python, making its classes and modules available for import in your python code.* + +=== UriFactory + +Matches the uProtocol URI Format. and is used to define source and sink (destination) attributes of uProtocol CloudEvents. +The factory builds URIs. + +URI is used as a method to uniquely identify devices, services, and resources on the network. + +*An Uri is built from the following elements:* + +* *UAuthority* - represents the device and domain of the software, the deployment. You can specify local or remote options. +* *UEntity* - The Software Entity defines the software name and version. +* *UResource* - The resource of the software can be a service name, and instance in the service and the name of the protobuf IDL message. + +==== UAuthority + +An Authority consists of a device and a domain per uProtocol URI format. + +An Authority represents the deployment location of a specific Software Entity. + +==== UEntity - uE + +An Software Entity is a piece of software deployed somewhere on a device. The uE is used in the source and sink parts of communicating software. + +A uE that *publishes* events is a *Service* role. + +A uE that *consumes* events is an *Application* role. + +A uE may combine bother Service and Application roles. + + +==== UResource + +A service API - defined in the uE - has Resources and Methods. Both of these are represented by the UResource class. + +An UResource is something that can be manipulated/controlled/exposed by a service. + +Resources are unique when prepended with UAuthority that represents the device and Software Entity that represents the service. + +An Resource represents a resource from a Service such as "door" and an optional specific instance such as "front_left". +In addition, it can optionally contain the name of the resource Message type, such as "Door". + +The Message type matches the protobuf service IDL that defines structured data types. A message is a data structure type used to define data that is passed in events and rpc methods. + +=== CloudEventFactory +Factory class that builds the various types of CloudEvents for uProtocol (publish, notification, request, response) + From c45ed2f5e2beb1c86a2ccdeba8ca2fd7361b1eef Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 19 Oct 2023 16:28:51 -0400 Subject: [PATCH 03/47] Replaced README markdown file with an Ascii doc file --- README.md => README.adoc | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README.md => README.adoc (100%) diff --git a/README.md b/README.adoc similarity index 100% rename from README.md rename to README.adoc From ec824f01c310afb7f2a9c0c21c50a5ad5a265386 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 19 Oct 2023 23:09:04 -0400 Subject: [PATCH 04/47] Added copyright in rpc module and optimized imports --- .../cloudevent/factory/cloudeventfactory.py | 7 ---- .../cloudevent/factory/ucloudevent.py | 2 - .../validate/cloudeventvalidator.py | 41 ++++++++----------- .../cloudevent/validate/validationresult.py | 3 +- org_eclipse_uprotocol/rpc/rpcclient.py | 23 +++++++++++ org_eclipse_uprotocol/rpc/rpcmapper.py | 25 ++++++++++- org_eclipse_uprotocol/rpc/rpcresult.py | 23 +++++++++++ .../transport/datamodel/uattributes.py | 4 +- .../transport/datamodel/ustatus.py | 3 +- org_eclipse_uprotocol/transport/utransport.py | 1 + .../validate/uattributesvalidator.py | 26 +++++++----- .../uri/datamodel/uauthority.py | 1 + org_eclipse_uprotocol/uri/datamodel/uuri.py | 1 + .../uri/serializer/longuriserializer.py | 6 +-- .../uri/serializer/microuriserializer.py | 15 +------ .../uri/serializer/uriserializer.py | 1 + .../uri/validator/urivalidator.py | 3 +- .../uuid/factory/uuidfactory.py | 1 + .../uuid/factory/uuidutils.py | 1 + .../uuid/validate/uuidvalidator.py | 3 +- 20 files changed, 123 insertions(+), 67 deletions(-) diff --git a/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py b/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py index 26f08d6..51f6b21 100644 --- a/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py +++ b/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py @@ -26,14 +26,7 @@ from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributes from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventtype import UCloudEventType -from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent from org_eclipse_uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer -from org_eclipse_uprotocol.transport.datamodel.upriority import UPriority -from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority -from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity -from org_eclipse_uprotocol.uri.datamodel.uresource import UResource -from org_eclipse_uprotocol.uri.datamodel.uuri import UUri -from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer from org_eclipse_uprotocol.uuid.factory.uuidfactory import Factories diff --git a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py index 954788c..1b3ae99 100644 --- a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py +++ b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py @@ -21,10 +21,8 @@ # ------------------------------------------------------------------------- -import base64 from datetime import datetime, timedelta -from cloudevents.sdk.event import v1 from cloudevents.sdk.event.v1 import Event from google.protobuf import any_pb2 from google.protobuf.message import DecodeError diff --git a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py index df8be4e..d225ccb 100644 --- a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py +++ b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py @@ -1,36 +1,17 @@ # ------------------------------------------------------------------------- -from abc import ABC, abstractmethod -# ------------------------------------------------------------------------- -from enum import Enum - -from cloudevents.sdk.event.v1 import Event -from google.protobuf import empty_pb2 -from google.protobuf.any_pb2 import Any -from google.rpc.status_pb2 import Status - -from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder -from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventtype import UCloudEventType -from org_eclipse_uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory -from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent -from org_eclipse_uprotocol.cloudevent.validate.validationresult import ValidationResult -from org_eclipse_uprotocol.transport.datamodel.upriority import UPriority -from org_eclipse_uprotocol.uri.datamodel import uuri -from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority -from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity -from org_eclipse_uprotocol.uri.datamodel.uresource import UResource -from org_eclipse_uprotocol.uri.datamodel.uuri import UUri -from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer - # 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 +# "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 @@ -38,6 +19,20 @@ # specific language governing permissions and limitations # under the License. +# ------------------------------------------------------------------------- + +from abc import ABC, abstractmethod +from enum import Enum + +from cloudevents.sdk.event.v1 import Event +from google.rpc.status_pb2 import Status + +from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventtype import UCloudEventType +from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent +from org_eclipse_uprotocol.cloudevent.validate.validationresult import ValidationResult +from org_eclipse_uprotocol.uri.datamodel import uuri +from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer + class CloudEventValidator(ABC): @staticmethod diff --git a/org_eclipse_uprotocol/cloudevent/validate/validationresult.py b/org_eclipse_uprotocol/cloudevent/validate/validationresult.py index 4abc313..fd97223 100644 --- a/org_eclipse_uprotocol/cloudevent/validate/validationresult.py +++ b/org_eclipse_uprotocol/cloudevent/validate/validationresult.py @@ -19,9 +19,10 @@ # specific language governing permissions and limitations # under the License. +# ------------------------------------------------------------------------- + from abc import ABC, abstractmethod -# ------------------------------------------------------------------------- from google.rpc.code_pb2 import Code from google.rpc.status_pb2 import Status diff --git a/org_eclipse_uprotocol/rpc/rpcclient.py b/org_eclipse_uprotocol/rpc/rpcclient.py index f44313f..c584489 100644 --- a/org_eclipse_uprotocol/rpc/rpcclient.py +++ b/org_eclipse_uprotocol/rpc/rpcclient.py @@ -1,3 +1,26 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- + from abc import ABC, abstractmethod from concurrent.futures import Future diff --git a/org_eclipse_uprotocol/rpc/rpcmapper.py b/org_eclipse_uprotocol/rpc/rpcmapper.py index ad254cc..0823813 100644 --- a/org_eclipse_uprotocol/rpc/rpcmapper.py +++ b/org_eclipse_uprotocol/rpc/rpcmapper.py @@ -1,10 +1,33 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- + from concurrent.futures import Future from google.rpc.code_pb2 import Code from google.rpc.status_pb2 import Status -from sdv_simulation.protofiles.ultifi.vehicle.body.access.v1 import access_topics_pb2 from org_eclipse_uprotocol.rpc.rpcresult import RpcResult +from org_eclipse_uprotocol.transport.datamodel.upayload import UPayload class RpcMapper: diff --git a/org_eclipse_uprotocol/rpc/rpcresult.py b/org_eclipse_uprotocol/rpc/rpcresult.py index 83534ef..202895e 100644 --- a/org_eclipse_uprotocol/rpc/rpcresult.py +++ b/org_eclipse_uprotocol/rpc/rpcresult.py @@ -1,3 +1,26 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- + from abc import ABC, abstractmethod from typing import Callable, TypeVar, Union diff --git a/org_eclipse_uprotocol/transport/datamodel/uattributes.py b/org_eclipse_uprotocol/transport/datamodel/uattributes.py index 9c73b27..d5138fc 100644 --- a/org_eclipse_uprotocol/transport/datamodel/uattributes.py +++ b/org_eclipse_uprotocol/transport/datamodel/uattributes.py @@ -1,6 +1,4 @@ # ------------------------------------------------------------------------- -from org_eclipse_uprotocol.transport.datamodel.umessagetype import UMessageType -from org_eclipse_uprotocol.transport.datamodel.upriority import UPriority # Copyright (c) 2023 General Motors GTO LLC # Licensed to the Apache Software Foundation (ASF) under one @@ -22,6 +20,8 @@ # ------------------------------------------------------------------------- +from org_eclipse_uprotocol.transport.datamodel.umessagetype import UMessageType +from org_eclipse_uprotocol.transport.datamodel.upriority import UPriority from org_eclipse_uprotocol.uri.datamodel.uuri import UUri from org_eclipse_uprotocol.uuid.factory import UUID diff --git a/org_eclipse_uprotocol/transport/datamodel/ustatus.py b/org_eclipse_uprotocol/transport/datamodel/ustatus.py index 3eab316..9e20f44 100644 --- a/org_eclipse_uprotocol/transport/datamodel/ustatus.py +++ b/org_eclipse_uprotocol/transport/datamodel/ustatus.py @@ -19,8 +19,9 @@ # specific language governing permissions and limitations # under the License. -from abc import ABC, abstractmethod # ------------------------------------------------------------------------- + +from abc import ABC, abstractmethod from enum import Enum from typing import Optional diff --git a/org_eclipse_uprotocol/transport/utransport.py b/org_eclipse_uprotocol/transport/utransport.py index 7a069fb..3c56274 100644 --- a/org_eclipse_uprotocol/transport/utransport.py +++ b/org_eclipse_uprotocol/transport/utransport.py @@ -20,6 +20,7 @@ # under the License. # ------------------------------------------------------------------------- + from abc import ABC, abstractmethod from org_eclipse_uprotocol.transport.datamodel.uattributes import UAttributes diff --git a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py index 51d0b22..c4fb00a 100644 --- a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py +++ b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py @@ -1,17 +1,7 @@ # ------------------------------------------------------------------------- -import time -from abc import abstractmethod -# ------------------------------------------------------------------------- -from enum import Enum - -from org_eclipse_uprotocol.transport.datamodel.uattributes import UAttributes -from org_eclipse_uprotocol.transport.datamodel.umessagetype import UMessageType -from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus, Code -from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator -from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils - # 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 @@ -19,7 +9,9 @@ # 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 @@ -27,6 +19,18 @@ # specific language governing permissions and limitations # under the License. +# ------------------------------------------------------------------------- + +import time +from abc import abstractmethod +from enum import Enum + +from org_eclipse_uprotocol.transport.datamodel.uattributes import UAttributes +from org_eclipse_uprotocol.transport.datamodel.umessagetype import UMessageType +from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus, Code +from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator +from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils + class UAttributesValidator: @staticmethod diff --git a/org_eclipse_uprotocol/uri/datamodel/uauthority.py b/org_eclipse_uprotocol/uri/datamodel/uauthority.py index 078b241..4198b82 100644 --- a/org_eclipse_uprotocol/uri/datamodel/uauthority.py +++ b/org_eclipse_uprotocol/uri/datamodel/uauthority.py @@ -20,6 +20,7 @@ # under the License. # ------------------------------------------------------------------------- + import ipaddress from typing import Optional diff --git a/org_eclipse_uprotocol/uri/datamodel/uuri.py b/org_eclipse_uprotocol/uri/datamodel/uuri.py index 7518a8e..0be748a 100644 --- a/org_eclipse_uprotocol/uri/datamodel/uuri.py +++ b/org_eclipse_uprotocol/uri/datamodel/uuri.py @@ -20,6 +20,7 @@ # under the License. # ------------------------------------------------------------------------- + from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity from org_eclipse_uprotocol.uri.datamodel.uresource import UResource diff --git a/org_eclipse_uprotocol/uri/serializer/longuriserializer.py b/org_eclipse_uprotocol/uri/serializer/longuriserializer.py index 7416e94..968347a 100644 --- a/org_eclipse_uprotocol/uri/serializer/longuriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/longuriserializer.py @@ -19,9 +19,10 @@ # specific language governing permissions and limitations # under the License. +# ------------------------------------------------------------------------- + import re -# ------------------------------------------------------------------------- from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity from org_eclipse_uprotocol.uri.datamodel.uresource import UResource @@ -29,9 +30,6 @@ from org_eclipse_uprotocol.uri.serializer.uriserializer import UriSerializer -# Define the classes UUri, UAuthority, UEntity, and UResource with their respective methods and properties. - - class LongUriSerializer(UriSerializer): # def __init__(self): # pass diff --git a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py index 9912a0e..ccece0b 100644 --- a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py @@ -19,8 +19,9 @@ # specific language governing permissions and limitations # under the License. -import io # ------------------------------------------------------------------------- + +import io import ipaddress import socket from enum import Enum @@ -33,18 +34,6 @@ from org_eclipse_uprotocol.uri.serializer.uriserializer import UriSerializer -# class AddressType(Enum): -# LOCAL = 0 -# IPV4 = 1 -# IPV6 = 2 -# -# def __init__(self, value): -# self._value = value -# -# @property -# def value(self): -# return self._value - class AddressType(Enum): LOCAL = 0 IPv4 = 1 diff --git a/org_eclipse_uprotocol/uri/serializer/uriserializer.py b/org_eclipse_uprotocol/uri/serializer/uriserializer.py index 431eff9..52dd5ff 100644 --- a/org_eclipse_uprotocol/uri/serializer/uriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/uriserializer.py @@ -20,6 +20,7 @@ # under the License. # ------------------------------------------------------------------------- + from abc import ABC, abstractmethod from re import T from typing import Optional diff --git a/org_eclipse_uprotocol/uri/validator/urivalidator.py b/org_eclipse_uprotocol/uri/validator/urivalidator.py index b1af7bb..519056b 100644 --- a/org_eclipse_uprotocol/uri/validator/urivalidator.py +++ b/org_eclipse_uprotocol/uri/validator/urivalidator.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------- -from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus, Code + # Copyright (c) 2023 General Motors GTO LLC # Licensed to the Apache Software Foundation (ASF) under one @@ -21,6 +21,7 @@ # ------------------------------------------------------------------------- +from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus, Code from org_eclipse_uprotocol.uri.datamodel.uresource import UResource from org_eclipse_uprotocol.uri.datamodel.uuri import UUri diff --git a/org_eclipse_uprotocol/uuid/factory/uuidfactory.py b/org_eclipse_uprotocol/uuid/factory/uuidfactory.py index 2ab25ff..c22ec1d 100644 --- a/org_eclipse_uprotocol/uuid/factory/uuidfactory.py +++ b/org_eclipse_uprotocol/uuid/factory/uuidfactory.py @@ -20,6 +20,7 @@ # under the License. # ------------------------------------------------------------------------- + import time import uuid diff --git a/org_eclipse_uprotocol/uuid/factory/uuidutils.py b/org_eclipse_uprotocol/uuid/factory/uuidutils.py index f48699e..1b057cd 100644 --- a/org_eclipse_uprotocol/uuid/factory/uuidutils.py +++ b/org_eclipse_uprotocol/uuid/factory/uuidutils.py @@ -20,6 +20,7 @@ # under the License. # ------------------------------------------------------------------------- + import uuid from enum import Enum from typing import Optional diff --git a/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py b/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py index 5580992..ea45dbe 100644 --- a/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py +++ b/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py @@ -19,8 +19,9 @@ # specific language governing permissions and limitations # under the License. -from collections import namedtuple # ------------------------------------------------------------------------- + +from collections import namedtuple from enum import Enum from org_eclipse_uprotocol.uuid.factory import UUID From c10de3a9241a557622e93ef80bdf0a8f4fb213c2 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Mon, 23 Oct 2023 11:17:17 -0400 Subject: [PATCH 05/47] Added Short Uri Format --- .../transport/datamodel/upayload.py | 58 +++--- .../transport/datamodel/userializationhint.py | 34 +++- .../transport/datamodel/ustatus.py | 36 ++-- .../validate/uattributesvalidator.py | 39 ++-- .../uri/serializer/microuriserializer.py | 5 +- .../uri/serializer/shorturiserializer.py | 179 ++++++++++++++++++ 6 files changed, 266 insertions(+), 85 deletions(-) create mode 100644 org_eclipse_uprotocol/uri/serializer/shorturiserializer.py diff --git a/org_eclipse_uprotocol/transport/datamodel/upayload.py b/org_eclipse_uprotocol/transport/datamodel/upayload.py index eaf2be8..e42314a 100644 --- a/org_eclipse_uprotocol/transport/datamodel/upayload.py +++ b/org_eclipse_uprotocol/transport/datamodel/upayload.py @@ -20,61 +20,49 @@ # under the License. # ------------------------------------------------------------------------- -import sys -from google.protobuf import any_pb2 +from typing import Optional - -class USerializationHint: - def __init__(self, hint_value): - self.hint_value = hint_value +from org_eclipse_uprotocol.transport.datamodel.userializationhint import USerializationHint class UPayload: EMPTY = None - def __init__(self, data: bytes, size: int, hint: USerializationHint): - self.data = data - self.size = size - self.hint = hint + def __init__(self, data: bytes, hint: Optional[USerializationHint] = None): + self.data = data if data is not None else bytes() + self.hint = hint if hint is not None else USerializationHint.UNKNOWN + + def get_data(self) -> bytes: + return self.data + + def get_hint(self) -> USerializationHint: + return self.hint @classmethod def empty(cls): if cls.EMPTY is None: - cls.EMPTY = cls(bytearray(), 0, None) + cls.EMPTY = UPayload(bytes(), USerializationHint.UNKNOWN) return cls.EMPTY - @classmethod - def from_string(cls, payload: str, hint: USerializationHint): - return cls(bytearray(payload.encode()), hint) - - def get_data(self) -> bytes: - return self.data if self.data is not None else UPayload.empty().data - - def hint(self): - return self.hint - def is_empty(self): - return self.data is None or len(self.data) == 0 - - def get_size(self) -> int: - return self.size - - @staticmethod - def get_any_from_payload_data(data: bytes): - any_message = any_pb2.Any() - any_message.ParseFromString(data) - return any_message + return len(self.data) == 0 def __eq__(self, other): if self is other: return True - if not isinstance(other, UPayload): + if other is None or self.__class__ != other.__class__: return False - return self.data == other.data and self.hint == other.hint and self.size == other.size + return self.data == other.data and self.hint == other.hint def __hash__(self): - return hash((sys.hash_info.width, tuple(self.data), self.hint, self.size)) + return hash((hash(self.data), self.hint)) def __str__(self): - return f"UPayload{{data={list(self.data)}, size={self.size}, hint={self.hint}}}" + return f"UPayload{{data={list(self.data)}, hint={self.hint}}}" + + +# Example usage +if __name__ == "__main__": + payload = UPayload(b"example_data", USerializationHint.UNKNOWN) + print(payload) diff --git a/org_eclipse_uprotocol/transport/datamodel/userializationhint.py b/org_eclipse_uprotocol/transport/datamodel/userializationhint.py index da1f922..98cab5a 100644 --- a/org_eclipse_uprotocol/transport/datamodel/userializationhint.py +++ b/org_eclipse_uprotocol/transport/datamodel/userializationhint.py @@ -40,6 +40,9 @@ class USerializationHint(Enum): # Raw binary data that has not been serialized RAW = (4, "application/octet-stream") + # Text Format + TEXT = (5, "text/plain") + def __init__(self, hint_number: int, mime_type: str): self.hint_number = hint_number self.mime_type = mime_type @@ -51,15 +54,30 @@ def get_mime_type(self): return self.mime_type @classmethod - def from_int(cls, value: int): - for item in cls: - if item.value[0] == value: - return item + def from_hint_number(cls, value: int): + for hint in cls: + if hint.get_hint_number() == value: + return hint return None @classmethod - def from_string(cls, mime_type: str): - for item in cls: - if item.value[1] == mime_type: - return item + def from_mime_type(cls, value: str): + for hint in cls: + if hint.get_mime_type() == value: + return hint return None + + +# Example usage +if __name__ == "__main__": + hint = USerializationHint.PROTOBUF + print("Hint Number:", hint.get_hint_number()) + print("MIME Type:", hint.get_mime_type()) + + hint_by_number = USerializationHint.from_hint_number(3) + if hint_by_number: + print("Hint found by number:", hint_by_number.name) + + hint_by_mime_type = USerializationHint.from_mime_type("application/json") + if hint_by_mime_type: + print("Hint found by MIME type:", hint_by_mime_type.name) diff --git a/org_eclipse_uprotocol/transport/datamodel/ustatus.py b/org_eclipse_uprotocol/transport/datamodel/ustatus.py index 9e20f44..db7d80a 100644 --- a/org_eclipse_uprotocol/transport/datamodel/ustatus.py +++ b/org_eclipse_uprotocol/transport/datamodel/ustatus.py @@ -25,25 +25,27 @@ from enum import Enum from typing import Optional +import google.rpc.code_pb2 + class Code(Enum): - OK = 0 - CANCELLED = 1 - UNKNOWN = 2 - INVALID_ARGUMENT = 3 - DEADLINE_EXCEEDED = 4 - NOT_FOUND = 5 - ALREADY_EXISTS = 6 - PERMISSION_DENIED = 7 - UNAUTHENTICATED = 16 - RESOURCE_EXHAUSTED = 8 - FAILED_PRECONDITION = 9 - ABORTED = 10 - OUT_OF_RANGE = 11 - UNIMPLEMENTED = 12 - INTERNAL = 13 - UNAVAILABLE = 14 - DATA_LOSS = 15 + OK = google.rpc.code_pb2.OK + CANCELLED = google.rpc.code_pb2.CANCELLED + UNKNOWN = google.rpc.code_pb2.UNKNOWN + INVALID_ARGUMENT = google.rpc.code_pb2.INVALID_ARGUMENT + DEADLINE_EXCEEDED = google.rpc.code_pb2.DEADLINE_EXCEEDED + NOT_FOUND = google.rpc.code_pb2.NOT_FOUND + ALREADY_EXISTS = google.rpc.code_pb2.ALREADY_EXISTS + PERMISSION_DENIED = google.rpc.code_pb2.PERMISSION_DENIED + UNAUTHENTICATED = google.rpc.code_pb2.UNAUTHENTICATED + RESOURCE_EXHAUSTED = google.rpc.code_pb2.RESOURCE_EXHAUSTED + FAILED_PRECONDITION = google.rpc.code_pb2.FAILED_PRECONDITION + ABORTED = google.rpc.code_pb2.ABORTED + OUT_OF_RANGE =google.rpc.code_pb2.OUT_OF_RANGE + UNIMPLEMENTED = google.rpc.code_pb2.UNIMPLEMENTED + INTERNAL = google.rpc.code_pb2.INTERNAL + UNAVAILABLE = google.rpc.code_pb2.UNAVAILABLE + DATA_LOSS = google.rpc.code_pb2.DATA_LOSS UNSPECIFIED = -1 diff --git a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py index c4fb00a..27351cc 100644 --- a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py +++ b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py @@ -23,6 +23,7 @@ import time from abc import abstractmethod +from datetime import datetime from enum import Enum from org_eclipse_uprotocol.transport.datamodel.uattributes import UAttributes @@ -47,13 +48,10 @@ def get_validator(attribute: UAttributes): @staticmethod def validate_id(attr: UAttributes) -> UStatus: attr_id = attr.id - try: - if UUIDUtils.isuuid(attr_id): - return UStatus.ok() - else: - return UStatus.failed_with_msg_and_code(f"Invalid UUID [{attr_id}]", Code.INVALID_ARGUMENT) - except Exception as e: - return UStatus.failed_with_msg_and_code(f"Invalid UUID [{attr_id}] [{str(e)}]", Code.INVALID_ARGUMENT) + if UUIDUtils.isuuid(attr_id): + return UStatus.ok() + else: + return UStatus.failed_with_msg_and_code("Invalid UUID [{}]".format(attr_id), Code.INVALID_ARGUMENT) @staticmethod def validate_priority(attr: UAttributes) -> UStatus: @@ -107,24 +105,23 @@ def validate(self, attributes: UAttributes): else: return UStatus.ok() - def is_expired(u_attributes: UAttributes): - try: - maybe_ttl = u_attributes.ttl - maybe_time = UUIDUtils.getTime(u_attributes.id) + def is_expired(self, u_attributes: UAttributes): + maybe_ttl = u_attributes.ttl + maybe_time = UUIDUtils.getTime(u_attributes.id) - if maybe_time is None: - return UStatus.failed_with_msg_and_code("Invalid Time", Code.INVALID_ARGUMENT) + if maybe_time is None: + return UStatus.failed_with_msg_and_code("Invalid Time", Code.INVALID_ARGUMENT) - if maybe_ttl is None or maybe_ttl <= 0: - return UStatus.ok_with_ack_id("Not Expired") - - delta = time.time() * 1000 - maybe_time + if maybe_ttl is None: + return UStatus.ok_with_ack_id("Not Expired") - return UStatus.failed_with_msg_and_code("Payload is expired", - Code.DEADLINE_EXCEEDED) if delta >= maybe_ttl else UStatus.ok_with_ack_id( - "Not Expired") - except Exception: + ttl = maybe_ttl + if ttl <= 0: return UStatus.ok_with_ack_id("Not Expired") + delta = int((datetime.now() - maybe_time).total_seconds() * 1000) + return UStatus.failed_with_msg_and_code("Payload is expired", + Code.DEADLINE_EXCEEDED) if delta >= ttl else UStatus.ok_with_ack_id( + "Not Expired") class Publish(UAttributesValidator): diff --git a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py index ccece0b..6abecf2 100644 --- a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py @@ -57,16 +57,13 @@ class MicroUriSerializer(UriSerializer): UP_VERSION = 0x1 def serialize(self, uri: UUri) -> bytes: - if uri is None or uri.is_empty(): + if uri is None or uri.is_empty() or not uri.is_micro_form(): return bytearray() maybe_address = uri.get_u_authority().address maybe_ue_id = uri.get_u_entity().id maybe_uresource_id = uri.get_u_resource().id - if maybe_uresource_id is None or maybe_ue_id is None: - return bytearray() - os = io.BytesIO() os.write(bytes([self.UP_VERSION])) diff --git a/org_eclipse_uprotocol/uri/serializer/shorturiserializer.py b/org_eclipse_uprotocol/uri/serializer/shorturiserializer.py new file mode 100644 index 0000000..98a2113 --- /dev/null +++ b/org_eclipse_uprotocol/uri/serializer/shorturiserializer.py @@ -0,0 +1,179 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- + +import re + +from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority +from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity +from org_eclipse_uprotocol.uri.datamodel.uresource import UResource +from org_eclipse_uprotocol.uri.datamodel.uuri import UUri +from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from org_eclipse_uprotocol.uri.serializer.uriserializer import UriSerializer + + +class ShortUriSerializer(UriSerializer): + SCHEME = "s:" # Required Scheme + INSTANCE = None + + def __init__(self): + pass + + @classmethod + def instance(cls): + if cls.INSTANCE is None: + cls.INSTANCE = ShortUriSerializer() + return cls.INSTANCE + + def serialize(self, uri): + if uri is None or uri.is_empty(): + return "" + + sb = [self.SCHEME] + sb.append(self.build_authority_part_of_uri(uri.get_u_authority())) + + if uri.get_u_authority().is_marked_remote(): + sb.append("/") + + if uri.get_u_entity().is_empty(): + return ''.join(sb) + + sb.append(self.build_software_entity_part_of_uri(uri.get_u_entity())) + sb.append(self.build_resource_part_of_uri(uri.get_u_resource())) + + return re.sub('/+$', '', ''.join(sb)) + + def deserialize(self, u_protocol_uri): + if u_protocol_uri is None or u_protocol_uri.isspace() or self.SCHEME not in u_protocol_uri: + return UUri.empty() + + uri = u_protocol_uri[u_protocol_uri.index(":") + 1:].replace('\\', '/') + is_local = not uri.startswith("//") + + uri_parts = uri.split("/") + number_of_parts_in_uri = len(uri_parts) + + if number_of_parts_in_uri == 0 or number_of_parts_in_uri == 1: + return UUri.empty() if is_local else UUri(UAuthority.long_remote("", ""), UEntity.empty(), + UResource.empty()) + + use_name = "" + use_version = "" + u_resource = UResource.empty() + authority_parts = uri_parts[2].split(".") + device = authority_parts[0] + domain = "" + + if len(authority_parts) > 1: + domain = ".".join(authority_parts[1:]) + + u_authority = UAuthority.long_remote(device, domain) + + if len(uri_parts) > 3: + use_name = uri_parts[3] + + if number_of_parts_in_uri > 4: + use_version = uri_parts[4] + + if number_of_parts_in_uri > 5: + try: + resource_id = int(uri_parts[5]) + u_resource = UResource.micro_format(resource_id) + except ValueError: + return UUri.empty() + else: + u_resource = UResource.empty() + else: + u_resource = UResource.empty() + else: + return UUri(u_authority, UEntity.empty(), UResource.empty()) + + use_version_int = None + try: + if use_version.strip(): + use_version_int = int(use_version) + except ValueError: + return UUri.empty() + + use_id = None + try: + if use_name.strip(): + use_id = int(use_name) + except ValueError: + return UUri.empty() + + return UUri(u_authority, UEntity.micro_format_id_version(use_id, use_version_int), u_resource) + + def build_resource_part_of_uri(self, u_resource): + if u_resource.is_empty() or not u_resource.is_micro_form(): + return "" + sb = ["/"] + if u_resource.get_id(): + sb.append(str(u_resource.get_id())) + + return ''.join(sb) + + def build_software_entity_part_of_uri(self, ue: UEntity): + sb = [] + if ue.get_id(): + sb.append(str(ue.get_id())) + sb.append("/") + if ue.get_version(): + sb.append(str(ue.get_version())) + return ''.join(sb) + + def build_authority_part_of_uri(self, authority: UAuthority): + if authority.is_local(): + return "/" + partial_uri = "//" + device = authority.device + domain = authority.domain + + if device: + partial_uri += device + if domain: + partial_uri += "." + + if domain: + partial_uri += domain + + return partial_uri + + +if __name__ == '__main__': + # Example usage + # Create a UUri object + u_authority = UAuthority.long_remote("vcu", "vin") + # u_entity = UEntity.micro_format_id_version(1, 2) + u_entity = UEntity.resolved_format("neelam", 1, 2) + # u_resource = UResource.micro_format(3) + u_resource = UResource.resolved_format("salary", "raise", "Salary", 4) + u_uri = UUri(u_authority, u_entity, u_resource) + + # Serialize the UUri object + serializer = ShortUriSerializer.instance() + serialized_uri = serializer.serialize(u_uri) + print("Serialized URI:", serialized_uri) + + # Deserialize a string to a UUri object + deserialized_uri = serializer.deserialize(serialized_uri) + print("Deserialized UUri:", deserialized_uri) From 57dda20a0d746cdc71ee70df5152e1c6605f0480 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Mon, 23 Oct 2023 12:30:03 -0400 Subject: [PATCH 06/47] Removed Short Uri Serializer --- .../uri/serializer/shorturiserializer.py | 179 ------------------ 1 file changed, 179 deletions(-) delete mode 100644 org_eclipse_uprotocol/uri/serializer/shorturiserializer.py diff --git a/org_eclipse_uprotocol/uri/serializer/shorturiserializer.py b/org_eclipse_uprotocol/uri/serializer/shorturiserializer.py deleted file mode 100644 index 98a2113..0000000 --- a/org_eclipse_uprotocol/uri/serializer/shorturiserializer.py +++ /dev/null @@ -1,179 +0,0 @@ -# ------------------------------------------------------------------------- - -# 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. - -# ------------------------------------------------------------------------- - -import re - -from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority -from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity -from org_eclipse_uprotocol.uri.datamodel.uresource import UResource -from org_eclipse_uprotocol.uri.datamodel.uuri import UUri -from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer -from org_eclipse_uprotocol.uri.serializer.uriserializer import UriSerializer - - -class ShortUriSerializer(UriSerializer): - SCHEME = "s:" # Required Scheme - INSTANCE = None - - def __init__(self): - pass - - @classmethod - def instance(cls): - if cls.INSTANCE is None: - cls.INSTANCE = ShortUriSerializer() - return cls.INSTANCE - - def serialize(self, uri): - if uri is None or uri.is_empty(): - return "" - - sb = [self.SCHEME] - sb.append(self.build_authority_part_of_uri(uri.get_u_authority())) - - if uri.get_u_authority().is_marked_remote(): - sb.append("/") - - if uri.get_u_entity().is_empty(): - return ''.join(sb) - - sb.append(self.build_software_entity_part_of_uri(uri.get_u_entity())) - sb.append(self.build_resource_part_of_uri(uri.get_u_resource())) - - return re.sub('/+$', '', ''.join(sb)) - - def deserialize(self, u_protocol_uri): - if u_protocol_uri is None or u_protocol_uri.isspace() or self.SCHEME not in u_protocol_uri: - return UUri.empty() - - uri = u_protocol_uri[u_protocol_uri.index(":") + 1:].replace('\\', '/') - is_local = not uri.startswith("//") - - uri_parts = uri.split("/") - number_of_parts_in_uri = len(uri_parts) - - if number_of_parts_in_uri == 0 or number_of_parts_in_uri == 1: - return UUri.empty() if is_local else UUri(UAuthority.long_remote("", ""), UEntity.empty(), - UResource.empty()) - - use_name = "" - use_version = "" - u_resource = UResource.empty() - authority_parts = uri_parts[2].split(".") - device = authority_parts[0] - domain = "" - - if len(authority_parts) > 1: - domain = ".".join(authority_parts[1:]) - - u_authority = UAuthority.long_remote(device, domain) - - if len(uri_parts) > 3: - use_name = uri_parts[3] - - if number_of_parts_in_uri > 4: - use_version = uri_parts[4] - - if number_of_parts_in_uri > 5: - try: - resource_id = int(uri_parts[5]) - u_resource = UResource.micro_format(resource_id) - except ValueError: - return UUri.empty() - else: - u_resource = UResource.empty() - else: - u_resource = UResource.empty() - else: - return UUri(u_authority, UEntity.empty(), UResource.empty()) - - use_version_int = None - try: - if use_version.strip(): - use_version_int = int(use_version) - except ValueError: - return UUri.empty() - - use_id = None - try: - if use_name.strip(): - use_id = int(use_name) - except ValueError: - return UUri.empty() - - return UUri(u_authority, UEntity.micro_format_id_version(use_id, use_version_int), u_resource) - - def build_resource_part_of_uri(self, u_resource): - if u_resource.is_empty() or not u_resource.is_micro_form(): - return "" - sb = ["/"] - if u_resource.get_id(): - sb.append(str(u_resource.get_id())) - - return ''.join(sb) - - def build_software_entity_part_of_uri(self, ue: UEntity): - sb = [] - if ue.get_id(): - sb.append(str(ue.get_id())) - sb.append("/") - if ue.get_version(): - sb.append(str(ue.get_version())) - return ''.join(sb) - - def build_authority_part_of_uri(self, authority: UAuthority): - if authority.is_local(): - return "/" - partial_uri = "//" - device = authority.device - domain = authority.domain - - if device: - partial_uri += device - if domain: - partial_uri += "." - - if domain: - partial_uri += domain - - return partial_uri - - -if __name__ == '__main__': - # Example usage - # Create a UUri object - u_authority = UAuthority.long_remote("vcu", "vin") - # u_entity = UEntity.micro_format_id_version(1, 2) - u_entity = UEntity.resolved_format("neelam", 1, 2) - # u_resource = UResource.micro_format(3) - u_resource = UResource.resolved_format("salary", "raise", "Salary", 4) - u_uri = UUri(u_authority, u_entity, u_resource) - - # Serialize the UUri object - serializer = ShortUriSerializer.instance() - serialized_uri = serializer.serialize(u_uri) - print("Serialized URI:", serialized_uri) - - # Deserialize a string to a UUri object - deserialized_uri = serializer.deserialize(serialized_uri) - print("Deserialized UUri:", deserialized_uri) From 366689569b1429d1f1dfd87e6dd8fc912709988a Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Mon, 23 Oct 2023 13:51:05 -0400 Subject: [PATCH 07/47] Updated rcpmapper.py --- org_eclipse_uprotocol/rpc/rpcmapper.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/org_eclipse_uprotocol/rpc/rpcmapper.py b/org_eclipse_uprotocol/rpc/rpcmapper.py index 0823813..33de616 100644 --- a/org_eclipse_uprotocol/rpc/rpcmapper.py +++ b/org_eclipse_uprotocol/rpc/rpcmapper.py @@ -23,11 +23,11 @@ from concurrent.futures import Future +from google.protobuf import any_pb2 from google.rpc.code_pb2 import Code from google.rpc.status_pb2 import Status from org_eclipse_uprotocol.rpc.rpcresult import RpcResult -from org_eclipse_uprotocol.transport.datamodel.upayload import UPayload class RpcMapper: @@ -41,7 +41,8 @@ def handle_response(payload, exception): raise RuntimeError(f"Server returned a null payload. Expected {expected_cls.__name__}") try: - any_message = UPayload.get_any_from_payload_data(payload.data) + any_message = any_pb2.Any() + any_message.ParseFromString(payload.data) if any_message.Is(expected_cls.DESCRIPTOR): return RpcMapper.unpack_payload(any_message, expected_cls) except Exception as e: @@ -68,7 +69,8 @@ def handle_response(payload, exception=None): raise RuntimeError(f"Server returned a null payload. Expected {expected_cls.__name__}") try: - any_message = UPayload.get_any_from_payload_data(payload.data) + any_message = any_pb2.Any() + any_message.ParseFromString(payload.data) if any_message.Is(expected_cls.DESCRIPTOR): if expected_cls == Status: From 426a1ad0d09c0db70fd7d6ca1944b417db522924 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Tue, 24 Oct 2023 13:29:03 -0400 Subject: [PATCH 08/47] Added legal docs --- CONTRIBUTING.adoc | 36 ++++++++++++++++++++++++++++++++++++ CONTRIBUTORS.adoc | 10 ++++++++++ NOTICE.adoc | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 CONTRIBUTING.adoc create mode 100644 CONTRIBUTORS.adoc create mode 100644 NOTICE.adoc diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc new file mode 100644 index 0000000..d5bb791 --- /dev/null +++ b/CONTRIBUTING.adoc @@ -0,0 +1,36 @@ += Contributing to Eclipse uProtocol + +Thanks for your interest in this project. Contributions are welcome! + +== Developer resources + +Information regarding source code management, builds, coding standards, and +more. + +https://projects.eclipse.org/proposals/eclipse-uprotocol + +The project maintains the following source code repositories + +* https://github.com/eclipse-uprotocol + +== Eclipse Contributor Agreement + +Before your contribution can be accepted by the project team contributors must +electronically sign the Eclipse Contributor Agreement (ECA). + +* http://www.eclipse.org/legal/ECA.php + +Commits that are provided by non-committers must have a Signed-off-by field in +the footer indicating that the author is aware of the terms by which the +contribution has been provided to the project. The non-committer must +additionally have an Eclipse Foundation account and must have a signed Eclipse +Contributor Agreement (ECA) on file. + +For more information, please see the Eclipse Committer Handbook: +https://www.eclipse.org/projects/handbook/#resources-commit + +== Contact + +Contact the project developers via the project's "dev" list. + +* https://accounts.eclipse.org/mailing-list/uprotocol-dev \ No newline at end of file diff --git a/CONTRIBUTORS.adoc b/CONTRIBUTORS.adoc new file mode 100644 index 0000000..90a86ab --- /dev/null +++ b/CONTRIBUTORS.adoc @@ -0,0 +1,10 @@ += Eclipse uProtocol Contributors + +These are the contributors to Eclipse uProtocol Python SDK + +|=== +| GitHub username | Name + +|https://github.com/neelam-kushwah[neelam-kushwah] |Neelam Kushwah + +|=== \ No newline at end of file diff --git a/NOTICE.adoc b/NOTICE.adoc new file mode 100644 index 0000000..5fbea7b --- /dev/null +++ b/NOTICE.adoc @@ -0,0 +1,33 @@ += Notices for Eclipse uProtocol + +This content is produced and maintained by the Eclipse uProtocol project. + +* Project home: https://projects.eclipse.org/projects/automotive.uprotocol + +== Trademarks + +Eclipse uProtocol is trademark of the Eclipse Foundation. +Eclipse, and the Eclipse Logo are registered trademarks of the Eclipse Foundation. + +== Copyright + +All content is the property of the respective authors or their employers. +For more information regarding authorship of content, please consult the +listed source code repository logs. + +== Declared Project Licenses + +This program and the accompanying materials are made available under the +terms of the or the Apache License, Version 2.0 +which is available at https://www.apache.org/licenses/LICENSE-2.0. + +SPDX-License-Identifier: Apache-2.0 + +== Third-party Content + +The following are libraries that uProtocol project uses: + +* http://cloudevents.io +* http://protobuf.dev + +NOTE: Please refer to link:setup.cfg[] for more information of library dependencies \ No newline at end of file From 80bafe7799016a7ee75c0923fd9b720526b7035e Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Wed, 25 Oct 2023 13:12:14 -0400 Subject: [PATCH 09/47] Fix cloud event interoperability issue --- .../cloudevent/datamodel/ucloudeventtype.py | 1 + .../cloudevent/factory/cloudeventfactory.py | 122 +++++++----------- .../cloudevent/factory/ucloudevent.py | 81 ++++++------ .../serialize/base64protobufserializer.py | 1 + .../serialize/cloudeventserializer.py | 1 + .../serialize/cloudeventserializers.py | 1 + .../serialize/cloudeventtojsonserializer.py | 53 +------- .../cloudeventtoprotobufserializer.py | 6 +- .../validate/cloudeventvalidator.py | 77 +++++------ 9 files changed, 144 insertions(+), 199 deletions(-) diff --git a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py index 404d125..5370b18 100644 --- a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py +++ b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py @@ -20,6 +20,7 @@ # under the License. # ------------------------------------------------------------------------- + from enum import Enum from typing import Optional diff --git a/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py b/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py index 51f6b21..e9bb7e0 100644 --- a/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py +++ b/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py @@ -19,14 +19,13 @@ # under the License. # ------------------------------------------------------------------------- -from cloudevents.sdk.event import v1 -from cloudevents.sdk.event.v1 import Event + +from cloudevents.http import CloudEvent from google.protobuf import empty_pb2 from google.protobuf.any_pb2 import Any from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributes from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventtype import UCloudEventType -from org_eclipse_uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer from org_eclipse_uprotocol.uuid.factory.uuidfactory import Factories @@ -35,73 +34,64 @@ class CloudEventFactory: @staticmethod def request(application_uri_for_rpc: str, service_method_uri: str, request_id: str, proto_payload: Any, - attributes: UCloudEventAttributes) -> Event: + attributes: UCloudEventAttributes) -> CloudEvent: event_id = CloudEventFactory.generate_cloud_event_id() - event = CloudEventFactory.build_base_cloud_event(event_id, application_uri_for_rpc, - Base64ProtobufSerializer.deserialize( - proto_payload.SerializeToString()), - proto_payload.DESCRIPTOR.full_name, attributes) - event.SetEventType(UCloudEventType.REQUEST.type()) - ext = event.Get("Extensions")[0] - ext["sink"] = service_method_uri - ext["reqid"] = request_id - event.SetExtensions(ext) - return event + cloud_event = CloudEventFactory.build_base_cloud_event(event_id, application_uri_for_rpc, + + proto_payload.SerializeToString(), + proto_payload.DESCRIPTOR.full_name, attributes, + UCloudEventType.REQUEST.type()) + cloud_event.__setitem__("sink", service_method_uri) + cloud_event.__setitem__("reqid", request_id) + return cloud_event @staticmethod def response(application_uri_for_rpc: str, service_method_uri: str, request_id: str, proto_payload: Any, - attributes: UCloudEventAttributes) -> Event: + attributes: UCloudEventAttributes) -> CloudEvent: event_id = CloudEventFactory.generate_cloud_event_id() - event = CloudEventFactory.build_base_cloud_event(event_id, service_method_uri, - Base64ProtobufSerializer.deserialize( - proto_payload.SerializeToString()), - proto_payload.DESCRIPTOR.full_name, attributes) - event.SetEventType(UCloudEventType.RESPONSE.type()) - ext = event.Get("Extensions")[0] - ext["sink"] = application_uri_for_rpc - ext["reqid"] = request_id - event.SetExtensions(ext) - - return event + cloud_event = CloudEventFactory.build_base_cloud_event(event_id, service_method_uri, + + proto_payload.SerializeToString(), + proto_payload.DESCRIPTOR.full_name, attributes, + UCloudEventType.RESPONSE.type()) + cloud_event.__setitem__("sink", application_uri_for_rpc) + cloud_event.__setitem__("reqid", request_id) + return cloud_event @staticmethod def failed_response(application_uri_for_rpc: str, service_method_uri: str, request_id: str, - communication_status: int, attributes: UCloudEventAttributes) -> Event: + communication_status: int, attributes: UCloudEventAttributes) -> CloudEvent: event_id = CloudEventFactory.generate_cloud_event_id() # Create an Any message packing an Empty message empty_proto_payload = Any() empty_proto_payload.Pack(empty_pb2.Empty()) - event = CloudEventFactory.build_base_cloud_event(event_id, service_method_uri, - Base64ProtobufSerializer.deserialize( - empty_proto_payload.SerializeToString()), # Empty payload - "google.protobuf.Empty", attributes) - event.SetEventType(UCloudEventType.RESPONSE.type()) - ext = event.Get("Extensions")[0] - ext["sink"] = application_uri_for_rpc - ext["reqid"] = request_id - ext["commstatus"] = communication_status - event.SetExtensions(ext) - - return event + cloud_event = CloudEventFactory.build_base_cloud_event(event_id, service_method_uri, + + empty_proto_payload.SerializeToString(), # Empty payload + "google.protobuf.Empty", attributes, + UCloudEventType.RESPONSE.type()) + cloud_event.__setitem__("sink", application_uri_for_rpc) + cloud_event.__setitem__("reqid", request_id) + cloud_event.__setitem__("commstatus", communication_status) + + return cloud_event @staticmethod - def publish(source: str, proto_payload: Any, attributes: UCloudEventAttributes) -> Event: + def publish(source: str, proto_payload: Any, attributes: UCloudEventAttributes) -> CloudEvent: event_id = CloudEventFactory.generate_cloud_event_id() - event = CloudEventFactory.build_base_cloud_event(event_id, source, Base64ProtobufSerializer.deserialize( - proto_payload.SerializeToString()), proto_payload.DESCRIPTOR.full_name, attributes) - event.SetEventType(UCloudEventType.PUBLISH.type()) - return event + cloud_event = CloudEventFactory.build_base_cloud_event(event_id, source, proto_payload.SerializeToString(), + proto_payload.DESCRIPTOR.full_name, attributes, + UCloudEventType.PUBLISH.type()) + return cloud_event @staticmethod - def notification(source: str, sink: str, proto_payload: Any, attributes: UCloudEventAttributes) -> Event: + def notification(source: str, sink: str, proto_payload: Any, attributes: UCloudEventAttributes) -> CloudEvent: event_id = CloudEventFactory.generate_cloud_event_id() - event = CloudEventFactory.build_base_cloud_event(event_id, source, Base64ProtobufSerializer.deserialize( - proto_payload.SerializeToString()), proto_payload.DESCRIPTOR.full_name, attributes) - event.SetEventType(UCloudEventType.PUBLISH.type()) - ext = event.Get("Extensions")[0] - ext["sink"] = sink - event.SetExtensions(ext) - return event + cloud_event = CloudEventFactory.build_base_cloud_event(event_id, source, proto_payload.SerializeToString(), + proto_payload.DESCRIPTOR.full_name, attributes, + UCloudEventType.PUBLISH.type()) + cloud_event.__setitem__("sink", sink) + return cloud_event @staticmethod def generate_cloud_event_id() -> str: @@ -110,25 +100,11 @@ def generate_cloud_event_id() -> str: return str(uuid_inst) @staticmethod - def build_base_cloud_event(id: str, source: str, proto_payload_bytes: str, proto_payload_schema: str, - attributes: UCloudEventAttributes) -> Event: - # Set extensions - extensions = {} - if attributes.ttl: - extensions["ttl"] = attributes.ttl - - if attributes.priority: - extensions["priority"] = attributes.priority - - if attributes.hash: - extensions["hash"] = attributes.hash - - if attributes.token: - extensions["token"] = attributes.token - - cloud_event = v1.Event() - cloud_event.SetEventID(id) - cloud_event.SetSource(source) - cloud_event.SetData(proto_payload_bytes) - cloud_event.SetExtensions(extensions) + def build_base_cloud_event(id: str, source: str, proto_payload_bytes: bytes, proto_payload_schema: str, + attributes: UCloudEventAttributes, type) -> CloudEvent: + json_attributes = {"ttl": attributes.ttl, "priority": attributes.priority, "hash": attributes.hash, + "token": attributes.token, "id": id, "source": source, "type": type} + + cloud_event = CloudEvent(json_attributes, proto_payload_bytes) + return cloud_event diff --git a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py index 1b3ae99..8f6d51b 100644 --- a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py +++ b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py @@ -23,76 +23,74 @@ from datetime import datetime, timedelta -from cloudevents.sdk.event.v1 import Event +from cloudevents.http import CloudEvent from google.protobuf import any_pb2 from google.protobuf.message import DecodeError from google.rpc.code_pb2 import Code -from org_eclipse_uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils class UCloudEvent: @staticmethod - def get_source(ce: Event) -> str: - return str(ce.source) + def get_source(ce: CloudEvent) -> str: + return UCloudEvent.extract_string_value_from_attributes("source", ce) @staticmethod - def get_sink(ce: Event) -> str: - return UCloudEvent.extract_string_value_from_extension("sink", ce) + def get_sink(ce: CloudEvent) -> str: + return UCloudEvent.extract_string_value_from_attributes("sink", ce) @staticmethod - def get_request_id(ce: Event) -> str: - return UCloudEvent.extract_string_value_from_extension("reqid", ce) + def get_request_id(ce: CloudEvent) -> str: + return UCloudEvent.extract_string_value_from_attributes("reqid", ce) @staticmethod - def get_hash(ce: Event) -> str: - return UCloudEvent.extract_string_value_from_extension("hash", ce) + def get_hash(ce: CloudEvent) -> str: + return UCloudEvent.extract_string_value_from_attributes("hash", ce) @staticmethod - def get_priority(ce: Event) -> str: - return UCloudEvent.extract_string_value_from_extension("priority", ce) + def get_priority(ce: CloudEvent) -> str: + return UCloudEvent.extract_string_value_from_attributes("priority", ce) @staticmethod - def get_ttl(ce: Event) -> int: - ttl_str = UCloudEvent.extract_string_value_from_extension("ttl", ce) + def get_ttl(ce: CloudEvent) -> int: + ttl_str = UCloudEvent.extract_string_value_from_attributes("ttl", ce) return int(ttl_str) if ttl_str is not None else None @staticmethod - def get_token(ce: Event) -> str: - return UCloudEvent.extract_string_value_from_extension("token", ce) + def get_token(ce: CloudEvent) -> str: + return UCloudEvent.extract_string_value_from_attributes("token", ce) @staticmethod - def get_communication_status(ce: Event) -> int: - comm_status = UCloudEvent.extract_string_value_from_extension("commstatus", ce) + def get_communication_status(ce: CloudEvent) -> int: + comm_status = UCloudEvent.extract_string_value_from_attributes("commstatus", ce) return int(comm_status) if comm_status is not None else Code.OK @staticmethod - def has_communication_status_problem(ce: Event) -> bool: + def has_communication_status_problem(ce: CloudEvent) -> bool: return UCloudEvent.get_communication_status(ce) != 0 @staticmethod - def add_communication_status(ce: Event, communication_status) -> Event: + def add_communication_status(ce: CloudEvent, communication_status) -> CloudEvent: if communication_status is None: return ce - - ce.extensions["commstatus"] = communication_status + ce.__setitem__("commstatus", communication_status) return ce @staticmethod - def get_creation_timestamp(ce: Event) -> int: - cloud_event_id = ce.id + def get_creation_timestamp(ce: CloudEvent) -> int: + cloud_event_id = UCloudEvent.extract_string_value_from_attributes("id", ce) uuid = UUIDUtils.fromString(cloud_event_id) return UUIDUtils.getTime(uuid) if uuid is not None else None @staticmethod - def is_expired_by_cloud_event_creation_date(ce: Event) -> bool: + def is_expired_by_cloud_event_creation_date(ce: CloudEvent) -> bool: maybe_ttl = UCloudEvent.get_ttl(ce) if maybe_ttl is None or maybe_ttl <= 0: return False - cloud_event_creation_time = ce.time + cloud_event_creation_time = UCloudEvent.extract_string_value_from_attributes("time", ce) if cloud_event_creation_time is None: return False @@ -102,11 +100,11 @@ def is_expired_by_cloud_event_creation_date(ce: Event) -> bool: return now > creation_time_plus_ttl @staticmethod - def is_expired(ce: Event) -> bool: + def is_expired(ce: CloudEvent) -> bool: maybe_ttl = UCloudEvent.get_ttl(ce) if maybe_ttl is None or maybe_ttl <= 0: return False - cloud_event_id = ce.id + cloud_event_id = UCloudEvent.extract_string_value_from_attributes("id", ce) try: uuid = UUIDUtils.fromString(cloud_event_id) @@ -120,43 +118,46 @@ def is_expired(ce: Event) -> bool: # Check if a CloudEvent is a valid UUIDv6 or v8 . @staticmethod - def is_cloud_event_id(ce: Event) -> bool: - cloud_event_id = ce.id + def is_cloud_event_id(ce: CloudEvent) -> bool: + cloud_event_id = UCloudEvent.extract_string_value_from_attributes("id", ce) uuid = UUIDUtils.fromString(cloud_event_id) return uuid is not None and UUIDUtils.isuuid(uuid) @staticmethod - def get_payload(ce: Event) -> any_pb2.Any: - data = ce.data + def get_payload(ce: CloudEvent) -> any_pb2.Any: + data = ce.get_data() if data is None: return any_pb2.Any() try: - return any_pb2.Any().FromString(Base64ProtobufSerializer.serialize(ce.data)) + return any_pb2.Any().FromString(data) except DecodeError: return any_pb2.Any() @staticmethod - def unpack(ce: Event, clazz): + def unpack(ce: CloudEvent, clazz): try: return UCloudEvent.get_payload(ce).Unpack(clazz) except DecodeError: return None @staticmethod - def to_string(ce: Event) -> str: + def to_string(ce: CloudEvent) -> str: if ce is not None: sink_str = UCloudEvent.get_sink(ce) sink_str = f", sink='{sink_str}'" if sink_str is not None else "" - return f"CloudEvent{{id='{ce.id}', source='{ce.source}'{sink_str}, type='{ce.type}'}}" + id = UCloudEvent.extract_string_value_from_attributes("id", ce) + source = UCloudEvent.extract_string_value_from_attributes("source", ce) + type = UCloudEvent.extract_string_value_from_attributes("type", ce) + return f"CloudEvent{{id='{id}', source='{source}'{sink_str}, type='{type}'}}" else: return "null" @staticmethod - def extract_string_value_from_extension(extension_name, ce: Event) -> str: - return ce.extensions.get(extension_name) + def extract_string_value_from_attributes(attr_name, ce: CloudEvent) -> str: + return ce.get_attributes().get(attr_name) @staticmethod - def extract_integer_value_from_extension(extension_name, ce: Event) -> int: - value = UCloudEvent.extract_string_value_from_extension(extension_name, ce) + def extract_integer_value_from_attributes(attr_name, ce: CloudEvent) -> int: + value = UCloudEvent.extract_string_value_from_attributes(attr_name, ce) return int(value) if value is not None else None diff --git a/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py index 8ba8a34..7067b27 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py @@ -20,6 +20,7 @@ # under the License. # ------------------------------------------------------------------------- + import base64 from builtins import str diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializer.py index cecc5fa..e82f149 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializer.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializer.py @@ -20,6 +20,7 @@ # under the License. # ------------------------------------------------------------------------- + from abc import ABC, abstractmethod from typing import Any diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py index d5b990a..7e2bb5c 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py @@ -20,6 +20,7 @@ # under the License. # ------------------------------------------------------------------------- + from enum import Enum from org_eclipse_uprotocol.cloudevent.serialize.cloudeventtojsonserializer import CloudEventToJsonSerializer diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py index b9a2734..2cfef09 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py @@ -1,5 +1,4 @@ # ------------------------------------------------------------------------- - # Copyright (c) 2023 General Motors GTO LLC # Licensed to the Apache Software Foundation (ASF) under one @@ -20,57 +19,17 @@ # under the License. # ------------------------------------------------------------------------- -import json -from cloudevents.sdk.event.v1 import Event +from cloudevents.conversion import to_json +from cloudevents.http import CloudEvent, from_json from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializer import CloudEventSerializer class CloudEventToJsonSerializer(CloudEventSerializer): - def __init__(self): - self.serializer = json.JSONEncoder(indent=2, sort_keys=False) - - def cloud_event_to_dict(self, ce: Event): - """ - Convert a CloudEvent instance to a Python dictionary. - """ - cloud_event_dict = {"specversion": ce.specversion, "type": ce.type, "source": ce.source, "id": ce.id, - "time": ce.time, "datacontenttype": ce.content_type, "dataschema": ce.schema, - "subject": ce.subject, "data": ce.data, "extensions": ce.extensions, } - return cloud_event_dict - - def cloud_dict_to_cloud_event(self, ce_dict: dict): - """ - Convert a CloudEvent dict to a Cloudevent instance. - """ - - event = Event() - if 'type' in ce_dict: - event.SetEventType(ce_dict['type']) - if 'source' in ce_dict: - event.SetSource(ce_dict['source']) - if 'datacontenttype' in ce_dict: - event.SetContentType(ce_dict['datacontenttype']) - if 'id' in ce_dict: - event.SetEventID(ce_dict['id']) - if 'data' in ce_dict: - event.SetData(ce_dict['data']) - if 'extensions' in ce_dict: - event.SetExtensions(ce_dict['extensions']) - if 'time' in ce_dict: - event.SetEventTime(ce_dict['time']) - if 'dataschema' in ce_dict: - event.SetSchema(ce_dict['dataschema']) - if 'subject' in ce_dict: - event.SetSubject(ce_dict['subject']) - - return event - def serialize(self, ce: Event) -> bytes: - ce_dict = self.cloud_event_to_dict(ce) - return self.serializer.encode(ce_dict).encode('utf-8') + def serialize(self, ce: CloudEvent) -> bytes: + return to_json(ce) - def deserialize(self, bytes_data: bytes) -> Event: - cloud_event_dict = json.loads(bytes_data.decode('utf-8')) - return self.cloud_dict_to_cloud_event(cloud_event_dict) + def deserialize(self, bytes_data: bytes) -> CloudEvent: + return from_json(bytes_data) diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py index 9083f2f..910d499 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py @@ -21,7 +21,7 @@ # ------------------------------------------------------------------------- -from cloudevents.sdk.event.v1 import Event +from cloudevents.http import CloudEvent from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializer import CloudEventSerializer @@ -31,8 +31,8 @@ class CloudEventToProtobufSerializer(CloudEventSerializer): def __init__(self): pass - def serialize(self, ce: Event) -> bytes: + def serialize(self, ce: CloudEvent) -> bytes: pass - def deserialize(self, bytes_data: bytes): + def deserialize(self, bytes_data: bytes) -> CloudEvent: pass diff --git a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py index d225ccb..2f988e4 100644 --- a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py +++ b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py @@ -24,20 +24,20 @@ from abc import ABC, abstractmethod from enum import Enum -from cloudevents.sdk.event.v1 import Event +from cloudevents.http import CloudEvent from google.rpc.status_pb2 import Status from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventtype import UCloudEventType from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent from org_eclipse_uprotocol.cloudevent.validate.validationresult import ValidationResult -from org_eclipse_uprotocol.uri.datamodel import uuri +from org_eclipse_uprotocol.uri.datamodel.uuri import UUri from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer class CloudEventValidator(ABC): @staticmethod - def get_validator(ce: Event): - cloud_event_type = ce.type + def get_validator(ce: CloudEvent): + cloud_event_type = ce.get_attributes().get("type") maybe_type = UCloudEventType(cloud_event_type) if maybe_type not in UCloudEventType: return Validators.PUBLISH.validator() @@ -49,7 +49,7 @@ def get_validator(ce: Event): else: return Validators.PUBLISH.validator() - def validate(self, ce: Event) -> Status: + def validate(self, ce: CloudEvent) -> Status: validation_results = [self.validate_version(ce), self.validate_id(ce), self.validate_source(ce), self.validate_type(ce), self.validate_sink(ce)] @@ -61,8 +61,8 @@ def validate(self, ce: Event) -> Status: else: return ValidationResult.failure(", ".join(error_messages)) - def validate_version(self, ce: Event) -> ValidationResult: - return self.validate_version_spec(ce.specversion) + def validate_version(self, ce: CloudEvent) -> ValidationResult: + return self.validate_version_spec(ce.get_attributes().get("specversion")) @staticmethod def validate_version_spec(version) -> ValidationResult: @@ -71,19 +71,19 @@ def validate_version_spec(version) -> ValidationResult: else: return ValidationResult.failure(f"Invalid CloudEvent version [{version}]. CloudEvent version must be 1.0.") - def validate_id(self, ce: Event) -> ValidationResult: + def validate_id(self, ce: CloudEvent) -> ValidationResult: return (ValidationResult.success() if UCloudEvent.is_cloud_event_id(ce) else ValidationResult.failure( f"Invalid CloudEvent Id [{ce.id}]. CloudEvent Id must be of type UUIDv8.")) @abstractmethod - def validate_source(self, ce: Event): + def validate_source(self, ce: CloudEvent): raise NotImplementedError("Subclasses must implement this method") @abstractmethod - def validate_type(self, ce: Event): + def validate_type(self, ce: CloudEvent): raise NotImplementedError("Subclasses must implement this method") - def validate_sink(self, ce: Event) -> ValidationResult: + def validate_sink(self, ce: CloudEvent) -> ValidationResult: maybe_sink = UCloudEvent.get_sink(ce) if maybe_sink: sink = maybe_sink @@ -103,13 +103,13 @@ def validate_u_entity_uri(uri: str) -> ValidationResult: # from uri string return CloudEventValidator.validate_u_entity_uri_from_UURI(uri) @staticmethod - def validate_u_entity_uri_from_UURI(uri: uuri) -> ValidationResult: # from uuri - u_authority = uri.u_authority + def validate_u_entity_uri_from_UURI(uri: UUri) -> ValidationResult: # from uuri + u_authority = uri.get_u_authority() if u_authority.is_marked_remote: if not u_authority.device: return ValidationResult.failure("Uri is configured to be remote and is missing uAuthority device name.") - if not uri.u_entity.name: + if not uri.get_u_entity().name: return ValidationResult.failure("Uri is missing uSoftware Entity name.") return ValidationResult.success() @@ -120,12 +120,12 @@ def validate_topic_uri(uri: str) -> ValidationResult: # from uri string return CloudEventValidator.validate_topic_uri_from_UURI(Uri) @staticmethod - def validate_topic_uri_from_UURI(uri: uuri) -> ValidationResult: # from uuri + def validate_topic_uri_from_UURI(uri: UUri) -> ValidationResult: # from uuri validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI(uri) if validationResult.is_success(): return validationResult - u_resource = uri.u_resource + u_resource = uri.get_u_resource() if not u_resource.name: return ValidationResult.failure("Uri is missing uResource name.") @@ -140,13 +140,13 @@ def validate_rpc_topic_uri(uri: str) -> ValidationResult: # from uri string return CloudEventValidator.validate_rpc_topic_uri_from_uuri(Uri) @staticmethod - def validate_rpc_topic_uri_from_uuri(uri: uuri) -> ValidationResult: # from uuri + def validate_rpc_topic_uri_from_uuri(uri: UUri) -> ValidationResult: # from uuri validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI(uri) if validationResult.is_failure(): return ValidationResult.failure( f"Invalid RPC uri application response topic. {validationResult.get_message()}") - u_resource = uri.u_resource + u_resource = uri.get_u_resource() topic = f"{u_resource.name}.{u_resource.instance}" if u_resource.instance else f"{u_resource.name}" if topic != "rpc.response": return ValidationResult.failure("Invalid RPC uri application response topic. Uri is missing rpc.response.") @@ -160,7 +160,7 @@ def validate_rpc_method(uri: str) -> ValidationResult: # string uri if validationResult.is_failure(): return ValidationResult.failure(f"Invalid RPC method uri. {validationResult.get_message()}") - u_resource = Uri.u_resource + u_resource = Uri.get_u_resource() if not u_resource.is_rpc_method: return ValidationResult.failure( "Invalid RPC method uri. Uri should be the method to be called, or method from response.") @@ -169,8 +169,8 @@ def validate_rpc_method(uri: str) -> ValidationResult: # string uri class Publish(CloudEventValidator): - def validate_source(self, cl_event: Event) -> ValidationResult: - source = cl_event.source + def validate_source(self, cl_event: CloudEvent) -> ValidationResult: + source = cl_event.get_attributes().get("source") check_source = self.validate_topic_uri(source) if check_source.is_failure(): return ValidationResult.failure( @@ -178,16 +178,17 @@ def validate_source(self, cl_event: Event) -> ValidationResult: return ValidationResult.success() - def validate_type(self, cl_event: Event) -> ValidationResult: - return (ValidationResult.success() if cl_event.type == "pub.v1" else ValidationResult.failure( - f"Invalid CloudEvent type [{cl_event.type}]. CloudEvent of type Publish must have a type of 'pub.v1'")) + def validate_type(self, cl_event: CloudEvent) -> ValidationResult: + type = cl_event.get_attributes().get("type") + return (ValidationResult.success() if type == "pub.v1" else ValidationResult.failure( + f"Invalid CloudEvent type [{type}]. CloudEvent of type Publish must have a type of 'pub.v1'")) def __str__(self) -> str: return "CloudEventValidator.Publish" class Notification(Publish): - def validate_sink(self, cl_event: Event) -> ValidationResult: + def validate_sink(self, cl_event: CloudEvent) -> ValidationResult: maybe_sink = UCloudEvent.get_sink(cl_event) if not maybe_sink: return ValidationResult.failure("Invalid CloudEvent sink. Notification CloudEvent sink must be an uri.") @@ -205,15 +206,15 @@ def __str__(self): class Request(CloudEventValidator): - def validate_source(self, cl_event: Event) -> ValidationResult: - source = cl_event.source + def validate_source(self, cl_event: CloudEvent) -> ValidationResult: + source = cl_event.get_attributes().get("source") check_source = self.validate_rpc_topic_uri(source) if check_source.is_failure(): return ValidationResult.failure( f"Invalid RPC Request CloudEvent source [{source}]. {check_source.get_message()}") return ValidationResult.success() - def validate_sink(self, cl_event: Event) -> ValidationResult: + def validate_sink(self, cl_event: CloudEvent) -> ValidationResult: maybe_sink = UCloudEvent.get_sink(cl_event) if not maybe_sink: return ValidationResult.failure( @@ -227,17 +228,19 @@ def validate_sink(self, cl_event: Event) -> ValidationResult: return ValidationResult.success() - def validate_type(self, cl_event: Event) -> ValidationResult: - return (ValidationResult.success() if cl_event.type == "req.v1" else ValidationResult.failure( - f"Invalid CloudEvent type [{cl_event.type}]. CloudEvent of type Request must have a type of 'req.v1'")) + def validate_type(self, cl_event: CloudEvent) -> ValidationResult: + type = cl_event.get_attributes().get("type") + + return (ValidationResult.success() if type == "req.v1" else ValidationResult.failure( + f"Invalid CloudEvent type [{type}]. CloudEvent of type Request must have a type of 'req.v1'")) def __str__(self): return "CloudEventValidator.Request" class Response(CloudEventValidator): - def validate_source(self, cl_event: Event) -> ValidationResult: - source = cl_event.source + def validate_source(self, cl_event: CloudEvent) -> ValidationResult: + source = cl_event.get_attributes().get("source") check_source = self.validate_rpc_method(source) if check_source.is_failure(): return ValidationResult.failure( @@ -259,9 +262,11 @@ def validate_sink(self, cl_event) -> ValidationResult: return ValidationResult.success() - def validate_type(self, cl_event: Event) -> ValidationResult: - return (ValidationResult.success() if cl_event.type == "res.v1" else ValidationResult.failure( - f"Invalid CloudEvent type [{cl_event.type}]. CloudEvent of type Response must have a type of 'res.v1'")) + def validate_type(self, cl_event: CloudEvent) -> ValidationResult: + type = cl_event.get_attributes().get("type") + + return (ValidationResult.success() if type == "res.v1" else ValidationResult.failure( + f"Invalid CloudEvent type [{type}]. CloudEvent of type Response must have a type of 'res.v1'")) def __str__(self): return "CloudEventValidator.Response" From deb9700a075c155b0d0fea3ab44bd6ce4e5187a0 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Wed, 25 Oct 2023 15:46:26 -0400 Subject: [PATCH 10/47] Add method and class comments to the cloudevent package --- .../datamodel/ucloudeventattributes.py | 70 +++++++++- .../cloudevent/datamodel/ucloudeventtype.py | 8 ++ .../cloudevent/factory/cloudeventfactory.py | 74 +++++++++- .../cloudevent/factory/ucloudevent.py | 129 +++++++++++++++++- .../serialize/base64protobufserializer.py | 17 ++- .../serialize/cloudeventserializers.py | 3 + .../serialize/cloudeventtojsonserializer.py | 3 + .../cloudeventtoprotobufserializer.py | 6 +- .../validate/cloudeventvalidator.py | 108 +++++++++++++-- .../cloudevent/validate/validationresult.py | 11 ++ 10 files changed, 412 insertions(+), 17 deletions(-) diff --git a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py index d09a25c..8c1f3a5 100644 --- a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py +++ b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py @@ -27,8 +27,19 @@ class UCloudEventAttributes: + """ + Specifies the properties that can configure the UCloudEvent. + """ def __init__(self, hash_value: str, priority: str, ttl: int, token: str): + """ + Construct the properties object.

+ @param hash_value: an HMAC generated on the data portion of the CloudEvent message using the device key. + @param priority: uProtocol Prioritization classifications defined at QoS in SDV-202. + @param ttl: How long this event should live for after it was generated (in milliseconds). Events without this + attribute (or value is 0) MUST NOT timeout. + @param token: Oauth2 access token to perform the access request defined in the request message. + """ self.hash = hash_value self.priority = priority self.ttl = ttl @@ -36,21 +47,47 @@ def __init__(self, hash_value: str, priority: str, ttl: int, token: str): @staticmethod def empty(): + """ + Static factory method for creating an empty cloud event attributes object, to avoid working with null

+ @return: Returns an empty cloud event attributes that indicates that there are no added additional + attributes to configure. + """ return UCloudEventAttributes(None, None, None, None) def is_empty(self): + """ + Indicates that there are no added additional attributes to configure when building a CloudEvent.

+ @return: Returns true if this attributes container is an empty container and has no valuable information in + building a CloudEvent. + """ return not any((self.hash, self.priority, self.ttl, self.token)) def hash(self) -> Optional[str]: + """ + An HMAC generated on the data portion of the CloudEvent message using the device key.

+ @return: Returns an Optional hash attribute. + """ return None if not self.hash or self.hash.isspace() else self.hash def priority(self) -> Optional[str]: + """ + uProtocol Prioritization classifications defined at QoS in SDV-202.

+ @return: Returns an Optional priority attribute. + """ return self.priority def ttl(self) -> Optional[int]: + """ + How long this event should live for after it was generated (in milliseconds).

+ @return: Returns an Optional time to live attribute. + """ return self.ttl def token(self) -> Optional[str]: + """ + Oauth2 access token to perform the access request defined in the request message.

+ @return: Returns an Optional OAuth token attribute. + """ return None if not self.token or self.token.isspace() else self.token def __eq__(self, other): @@ -59,7 +96,8 @@ def __eq__(self, other): if not isinstance(other, UCloudEventAttributes): return False return ( - self.hash == other.hash and self.priority == other.priority and self.ttl == other.ttl and self.token == other.token) + self.hash == other.hash and self.priority == other.priority and self.ttl == other.ttl and self.token + == other.token) def __hash__(self): return hash((self.hash, self.priority, self.ttl, self.token)) @@ -70,6 +108,10 @@ def __str__(self): class UCloudEventAttributesBuilder: + """ + Builder for constructing the UCloudEventAttributes. + """ + def __init__(self): self.hash = None self.priority = None @@ -77,20 +119,46 @@ def __init__(self): self.token = None def with_hash(self, hash_value: str): + """ + Add an HMAC generated on the data portion of the CloudEvent message using the device key.

+ @param hash_value: hash an HMAC generated on the data portion of the CloudEvent message using the device key. + @return: Returns the UCloudEventAttributesBuilder with the configured hash. + """ self.hash = hash_value return self def with_priority(self, priority: UPriority): + """ + Add a uProtocol Prioritization classifications defined at QoS in SDV-202.

+ @param priority: priority uProtocol Prioritization classifications defined at QoS in SDV-202. + @return: Returns the UCloudEventAttributesBuilder with the configured priority. + """ self.priority = priority return self def with_ttl(self, ttl: int): + """ + Add a time to live which is how long this event should live for after it was generated (in milliseconds). + Events without this attribute (or value is 0) MUST NOT timeout.

+ @param ttl: How long this event should live for after it was generated (in milliseconds). Events without this + attribute (or value is 0) MUST NOT timeout. + @return: Returns the UCloudEventAttributesBuilder with the configured time to live. + """ self.ttl = ttl return self def with_token(self, token: str): + """ + Add an Oauth2 access token to perform the access request defined in the request message.

+ @param token: An Oauth2 access token to perform the access request defined in the request message. + @return: Returns the UCloudEventAttributesBuilder with the configured OAuth token. + """ self.token = token return self def build(self): + """ + Construct the UCloudEventAttributes from the builder.

+ @return: Returns a constructed UProperty. + """ return UCloudEventAttributes(self.hash, self.priority.qos_string, self.ttl, self.token) diff --git a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py index 5370b18..adc9bad 100644 --- a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py +++ b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py @@ -26,6 +26,9 @@ class UCloudEventType(Enum): + """ + Enumeration for the core types of uProtocol CloudEvents. + """ PUBLISH = "pub.v1" REQUEST = "req.v1" RESPONSE = "res.v1" @@ -35,6 +38,11 @@ def type(self) -> str: @staticmethod def value_of_type(typestr: str) -> Optional['UCloudEventType']: + """ + Convert a String type into a maybe UCloudEventType.

+ @param typestr: The String value of the UCloudEventType. + @return: returns the UCloudEventType associated with the provided String. + """ for ce_type in UCloudEventType: if ce_type.type() == typestr: return ce_type diff --git a/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py b/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py index e9bb7e0..49d0382 100644 --- a/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py +++ b/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py @@ -29,12 +29,25 @@ from org_eclipse_uprotocol.uuid.factory.uuidfactory import Factories +# A factory is a part of the software has methods to generate concrete objects, usually of the same type or +# interface. CloudEvents is a specification for describing events in a common way. We will use CloudEvents to +# formulate all kinds of events (messages) that will be sent to and from devices. The CloudEvent factory knows how +# to generate CloudEvents of the 4 core types: req.v1, res.v1, pub.v1, and file.v1 class CloudEventFactory: PROTOBUF_CONTENT_TYPE = "application/x-protobuf" @staticmethod def request(application_uri_for_rpc: str, service_method_uri: str, request_id: str, proto_payload: Any, attributes: UCloudEventAttributes) -> CloudEvent: + """ + Create a CloudEvent for an event for the use case of: RPC Request message. + @param application_uri_for_rpc: The uri for the application requesting the RPC. + @param service_method_uri: The uri for the method to be called on the service Ex. :/body.access/1/rpc.UpdateDoor + @param request_id:The attribute id from the original request + @param proto_payload:Protobuf Any object with the Message command to be executed on the sink service. + @param attributes: Additional attributes such as ttl, hash, priority and token. + @return: Returns an request CloudEvent. + """ event_id = CloudEventFactory.generate_cloud_event_id() cloud_event = CloudEventFactory.build_base_cloud_event(event_id, application_uri_for_rpc, @@ -48,6 +61,18 @@ def request(application_uri_for_rpc: str, service_method_uri: str, request_id: s @staticmethod def response(application_uri_for_rpc: str, service_method_uri: str, request_id: str, proto_payload: Any, attributes: UCloudEventAttributes) -> CloudEvent: + """ + Create a CloudEvent for an event for the use case of: RPC Response message. + @param application_uri_for_rpc: The destination of the response. The uri for the original application that + requested the RPC and this response is for. + @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 proto_payload: The protobuf serialized response message as defined by the application interface or the + google.rpc.Status message containing the details of an error. + @param attributes: Additional attributes such as ttl, hash and priority. + @return: Returns an response CloudEvent. + """ event_id = CloudEventFactory.generate_cloud_event_id() cloud_event = CloudEventFactory.build_base_cloud_event(event_id, service_method_uri, @@ -61,6 +86,18 @@ def response(application_uri_for_rpc: str, service_method_uri: str, request_id: @staticmethod def failed_response(application_uri_for_rpc: str, service_method_uri: str, request_id: str, communication_status: int, attributes: UCloudEventAttributes) -> CloudEvent: + """ + Create a CloudEvent for an event for the use case of: RPC Response message that failed. + @param application_uri_for_rpc: The destination of the response. The uri for the original application that + requested the RPC and this response is for. + @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 + 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. + """ event_id = CloudEventFactory.generate_cloud_event_id() # Create an Any message packing an Empty message empty_proto_payload = Any() @@ -78,6 +115,13 @@ def failed_response(application_uri_for_rpc: str, service_method_uri: str, reque @staticmethod def publish(source: str, proto_payload: Any, attributes: UCloudEventAttributes) -> CloudEvent: + """ + Create a CloudEvent for an event for the use case of: Publish generic message. + @param source:The uri of the topic being published. + @param proto_payload:protobuf Any object with the Message to be published. + @param attributes:Additional attributes such as ttl, hash and priority. + @return:Returns a publish CloudEvent. + """ event_id = CloudEventFactory.generate_cloud_event_id() cloud_event = CloudEventFactory.build_base_cloud_event(event_id, source, proto_payload.SerializeToString(), proto_payload.DESCRIPTOR.full_name, attributes, @@ -86,6 +130,16 @@ def publish(source: str, proto_payload: Any, attributes: UCloudEventAttributes) @staticmethod def notification(source: str, sink: str, proto_payload: Any, attributes: UCloudEventAttributes) -> CloudEvent: + """ + Create a CloudEvent for an event for the use case of: Publish a notification message. A published event + containing the sink (destination) is often referred to as a notification, it is an event sent to a specific + consumer. + @param source: The uri of the topic being published. + @param sink: The uri of the destination of this notification. + @param proto_payload: protobuf Any object with the Message to be published. + @param attributes: Additional attributes such as ttl, hash and priority. + @return: Returns a publish CloudEvent. + """ event_id = CloudEventFactory.generate_cloud_event_id() cloud_event = CloudEventFactory.build_base_cloud_event(event_id, source, proto_payload.SerializeToString(), proto_payload.DESCRIPTOR.full_name, attributes, @@ -95,13 +149,31 @@ def notification(source: str, sink: str, proto_payload: Any, attributes: UCloudE @staticmethod def generate_cloud_event_id() -> str: - # uuid8 + """ + Generate a UUIDv8 + @return: Returns a UUIDv8 id. + """ uuid_inst = Factories.UPROTOCOL.create() return str(uuid_inst) @staticmethod def build_base_cloud_event(id: str, source: str, proto_payload_bytes: bytes, proto_payload_schema: str, attributes: UCloudEventAttributes, type) -> CloudEvent: + """ + Base CloudEvent builder that is the same for all CloudEvent types. + + @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. + @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. + @param attributes:Additional cloud event attributes that can be passed in. All attributes are optional and + will be added only if they were configured. + @param type: Type of the cloud event + @return:Returns a CloudEventBuilder that can be additionally configured and then by calling .build() + construct a CloudEvent ready to be serialized and sent to the transport layer. + """ json_attributes = {"ttl": attributes.ttl, "priority": attributes.priority, "hash": attributes.hash, "token": attributes.token, "id": id, "source": source, "type": type} diff --git a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py index 8f6d51b..e69a316 100644 --- a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py +++ b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py @@ -32,46 +32,113 @@ class UCloudEvent: + """ + Class to extract information from a CloudEvent. + """ + @staticmethod def get_source(ce: CloudEvent) -> str: + """ + Extract the source from a cloud event. The source is a mandatory attribute. The CloudEvent constructor does + not allow creating a cloud event without a source.

+ @param ce:CloudEvent with source to be extracted. + @return:Returns the String value of a CloudEvent source attribute. + """ return UCloudEvent.extract_string_value_from_attributes("source", ce) @staticmethod def get_sink(ce: CloudEvent) -> str: + """ + Extract the sink from a cloud event. The sink attribute is optional.

+ @param ce:CloudEvent with sink to be extracted. + @return:Returns an Optional String value of a CloudEvent sink attribute if it exists, otherwise an + Optional.empty() is returned. + """ return UCloudEvent.extract_string_value_from_attributes("sink", ce) @staticmethod def get_request_id(ce: CloudEvent) -> str: + """ + Extract the request id from a cloud event that is a response RPC CloudEvent. The attribute is optional.

+ @param ce: the response RPC CloudEvent with request id to be extracted. + @return: Returns an Optional String value of a response RPC CloudEvent request id attribute if it exists, + otherwise an Optional.empty() is returned. + """ return UCloudEvent.extract_string_value_from_attributes("reqid", ce) @staticmethod def get_hash(ce: CloudEvent) -> str: + """ + Extract the hash attribute from a cloud event. The hash attribute is optional.

+ @param ce: CloudEvent with hash to be extracted. + @return:Returns an Optional String value of a CloudEvent hash attribute if it exists, otherwise an + Optional.empty() is returned. + """ return UCloudEvent.extract_string_value_from_attributes("hash", ce) @staticmethod def get_priority(ce: CloudEvent) -> str: + """ + Extract the string value of the priority attribute from a cloud event. The priority attribute is + optional.

+ @param ce:CloudEvent with priority to be extracted. + @return:Returns an Optional String value of a CloudEvent priority attribute if it exists, otherwise an + Optional.empty() is returned. + """ return UCloudEvent.extract_string_value_from_attributes("priority", ce) @staticmethod def get_ttl(ce: CloudEvent) -> int: + """ + Extract the integer value of the ttl attribute from a cloud event. The ttl attribute is optional.

+ @param ce:CloudEvent with ttl to be extracted. + @return: Returns an Optional String value of a CloudEvent ttl attribute if it exists,otherwise an + Optional.empty() is returned. + """ ttl_str = UCloudEvent.extract_string_value_from_attributes("ttl", ce) return int(ttl_str) if ttl_str is not None else None @staticmethod def get_token(ce: CloudEvent) -> str: + """ + Extract the string value of the token attribute from a cloud event. The token attribute is optional.

+ @param ce: CloudEvent with token to be extracted. + @return:Returns an Optional String value of a CloudEvent priority token if it exists, otherwise an + Optional.empty() is returned. + """ return UCloudEvent.extract_string_value_from_attributes("token", ce) @staticmethod def get_communication_status(ce: CloudEvent) -> int: + """ + Extract the integer value of the communication status attribute from a cloud event. The communication status + attribute is optional. If there was a platform communication error that occurred while delivering this + cloudEvent, it will be indicated in this attribute. If the attribute does not exist, it is assumed that + everything was Code.OK_VALUE.

+ @param ce: CloudEvent with the platformError to be extracted. + @return: Returns a {@link Code} value that indicates of a platform communication error while delivering this + CloudEvent or Code.OK_VALUE. + """ comm_status = UCloudEvent.extract_string_value_from_attributes("commstatus", ce) return int(comm_status) if comm_status is not None else Code.OK @staticmethod def has_communication_status_problem(ce: CloudEvent) -> bool: + """ + Indication of a platform communication error that occurred while trying to deliver the CloudEvent.

+ @param ce:CloudEvent to be queried for a platform delivery error. + @return:returns true if the provided CloudEvent is marked with having a platform delivery problem. + """ return UCloudEvent.get_communication_status(ce) != 0 @staticmethod def add_communication_status(ce: CloudEvent, communication_status) -> CloudEvent: + """ + Returns a new CloudEvent from the supplied CloudEvent, with the platform communication added.

+ @param ce:CloudEvent that the platform delivery error will be added. + @param communication_status:the platform delivery error Code to add to the CloudEvent. + @return:Returns a new CloudEvent from the supplied CloudEvent, with the platform communication added. + """ if communication_status is None: return ce ce.__setitem__("commstatus", communication_status) @@ -79,6 +146,11 @@ def add_communication_status(ce: CloudEvent, communication_status) -> CloudEvent @staticmethod def get_creation_timestamp(ce: CloudEvent) -> int: + """ + Extract the timestamp from the UUIDV8 CloudEvent Id.

+ @param ce:The CloudEvent with the timestamp to extract. + @return:Return the timestamp from the UUIDV8 CloudEvent Id or an empty Optional if timestamp can't be extracted. + """ cloud_event_id = UCloudEvent.extract_string_value_from_attributes("id", ce) uuid = UUIDUtils.fromString(cloud_event_id) @@ -86,6 +158,13 @@ def get_creation_timestamp(ce: CloudEvent) -> int: @staticmethod def is_expired_by_cloud_event_creation_date(ce: CloudEvent) -> bool: + """ + Calculate if a CloudEvent configured with a creation time and a ttl attribute is expired. The ttl attribute + is a configuration of how long this event should live for after it was generated (in milliseconds)

+ @param ce:The CloudEvent to inspect for being expired. + @return:Returns true if the CloudEvent was configured with a ttl > 0 and a creation time to compare for + expiration. + """ maybe_ttl = UCloudEvent.get_ttl(ce) if maybe_ttl is None or maybe_ttl <= 0: return False @@ -101,6 +180,12 @@ def is_expired_by_cloud_event_creation_date(ce: CloudEvent) -> bool: @staticmethod def is_expired(ce: CloudEvent) -> bool: + """ + Calculate if a CloudEvent configured with UUIDv8 id and a ttl attribute is expired. The ttl attribute is a + configuration of how long this event should live for after it was generated (in milliseconds).

+ @param ce:The CloudEvent to inspect for being expired. + @return:Returns true if the CloudEvent was configured with a ttl > 0 and UUIDv8 id to compare for expiration. + """ maybe_ttl = UCloudEvent.get_ttl(ce) if maybe_ttl is None or maybe_ttl <= 0: return False @@ -116,9 +201,13 @@ def is_expired(ce: CloudEvent) -> bool: delta = 0 return delta >= maybe_ttl - # Check if a CloudEvent is a valid UUIDv6 or v8 . @staticmethod def is_cloud_event_id(ce: CloudEvent) -> bool: + """ + Check if a CloudEvent is a valid UUIDv6 or v8 .

+ @param ce:The CloudEvent with the id to inspect. + @return: Returns true if the CloudEvent is valid. + """ cloud_event_id = UCloudEvent.extract_string_value_from_attributes("id", ce) uuid = UUIDUtils.fromString(cloud_event_id) @@ -126,6 +215,12 @@ def is_cloud_event_id(ce: CloudEvent) -> bool: @staticmethod def get_payload(ce: CloudEvent) -> any_pb2.Any: + """ + Extract the payload from the CloudEvent as a protobuf Any object.
An all or nothing error handling + strategy is implemented. If anything goes wrong, an Any.getDefaultInstance() will be returned.

+ @param ce:CloudEvent containing the payload to extract. + @return:Extracts the payload from a CloudEvent as a Protobuf Any object. + """ data = ce.get_data() if data is None: return any_pb2.Any() @@ -136,6 +231,15 @@ def get_payload(ce: CloudEvent) -> any_pb2.Any: @staticmethod def unpack(ce: CloudEvent, clazz): + """ + Extract the payload from the CloudEvent as a protobuf Message of the provided class. The protobuf of this + message class must be loaded on the client for this to work.
An all or nothing error handling strategy + 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. + """ try: return UCloudEvent.get_payload(ce).Unpack(clazz) except DecodeError: @@ -143,6 +247,13 @@ def unpack(ce: CloudEvent, clazz): @staticmethod def to_string(ce: CloudEvent) -> str: + """ + Function used to pretty print a CloudEvent containing only the id, source, type and maybe a sink. Used mainly + for logging.

+ @param ce:The CloudEvent we want to pretty print. + @return:returns the String representation of the CloudEvent containing only the id, source, type and maybe a + sink. + """ if ce is not None: sink_str = UCloudEvent.get_sink(ce) sink_str = f", sink='{sink_str}'" if sink_str is not None else "" @@ -155,9 +266,25 @@ def to_string(ce: CloudEvent) -> str: @staticmethod def extract_string_value_from_attributes(attr_name, ce: CloudEvent) -> str: + """ + Utility for extracting the String value of an attribute.

+ @param attr_name:The name of the CloudEvent attribute. + @param ce:The CloudEvent containing the data. + @return:the Optional String value of an attribute matching the attribute name, or an Optional.empty() is the + value does not exist. + """ + return ce.get_attributes().get(attr_name) @staticmethod def extract_integer_value_from_attributes(attr_name, ce: CloudEvent) -> int: + """ + + Utility for extracting the Integer value of an attribute.

+ @param attr_name:The name of the CloudEvent attribute. + @param ce:The CloudEvent containing the data. + @return:returns the Optional Integer value of an attribute matching the attribute name,or an Optional.empty() + is the value does not exist. + """ value = UCloudEvent.extract_string_value_from_attributes(attr_name, ce) return int(value) if value is not None else None diff --git a/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py index 7067b27..eaf87da 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py @@ -26,14 +26,29 @@ class Base64ProtobufSerializer: + """ + Helper for serializing Base64 protobuf data. + """ + @staticmethod def deserialize(proto_bytes: bytes) -> str: + """ + Deserialize a base64 protobuf payload into a Base64 String.

+ + @param proto_bytes: byte[] data + @return: Returns a String from the base64 protobuf payload. + """ if proto_bytes is None: return "" return base64.b64encode(proto_bytes).decode('utf-8') # return base64.b64decode(proto_bytes).decode('utf-8') @staticmethod - def serialize(string_to_serialize: str) -> bytes: # input is base64 string + def serialize(string_to_serialize: str) -> bytes: + """ + Serialize a String into Base64 format.

+ @param string_to_serialize:String to serialize. + @return: Returns the Base64 formatted String as a byte[]. + """ if string_to_serialize is None: return bytearray() return base64.b64decode(string_to_serialize.encode('utf-8')) diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py index 7e2bb5c..e60aef4 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py @@ -28,6 +28,9 @@ class CloudEventSerializers(Enum): + """ + Provides Singleton instances of the CloudEvent Serializers. + """ JSON = CloudEventToJsonSerializer() PROTOBUF = CloudEventToProtobufSerializer() diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py index 2cfef09..57ef2cb 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py @@ -27,6 +27,9 @@ class CloudEventToJsonSerializer(CloudEventSerializer): + """ + CloudEventSerializer to serialize and deserialize CloudEvents to JSON format. + """ def serialize(self, ce: CloudEvent) -> bytes: return to_json(ce) diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py index 910d499..42c858b 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py @@ -26,8 +26,12 @@ from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializer import CloudEventSerializer -# To do - implementation is pending, convert cloud event to cloudevent proto +# ToDo- convert cloud event to cloudevent proto class CloudEventToProtobufSerializer(CloudEventSerializer): + """ + CloudEventSerializer to serialize and deserialize CloudEvents to protobuf format. + """ + def __init__(self): pass diff --git a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py index 2f988e4..0de61fd 100644 --- a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py +++ b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py @@ -35,8 +35,18 @@ class CloudEventValidator(ABC): + """ + Validates a CloudEvent using google.grpc.Status
+ google.grpc.Status + """ + @staticmethod def get_validator(ce: CloudEvent): + """ + Obtain a CloudEventValidator according to the type attribute in the CloudEvent.

+ @param ce:The CloudEvent with the type attribute. + @return:Returns a CloudEventValidator according to the type attribute in the CloudEvent. + """ cloud_event_type = ce.get_attributes().get("type") maybe_type = UCloudEventType(cloud_event_type) if maybe_type not in UCloudEventType: @@ -50,6 +60,13 @@ def get_validator(ce: CloudEvent): return Validators.PUBLISH.validator() def validate(self, ce: CloudEvent) -> Status: + """ + Validate the CloudEvent. A CloudEventValidator instance is obtained according to the type attribute on the + CloudEvent.

+ @param ce:The CloudEvent to validate. + @return:Returns a google.rpc.Status with success or a google.rpc.Status with failure containing all the + errors that were found. + """ validation_results = [self.validate_version(ce), self.validate_id(ce), self.validate_source(ce), self.validate_type(ce), self.validate_sink(ce)] @@ -72,18 +89,34 @@ def validate_version_spec(version) -> ValidationResult: return ValidationResult.failure(f"Invalid CloudEvent version [{version}]. CloudEvent version must be 1.0.") def validate_id(self, ce: CloudEvent) -> ValidationResult: + id = UCloudEvent.extract_string_value_from_attributes("id", ce) return (ValidationResult.success() if UCloudEvent.is_cloud_event_id(ce) else ValidationResult.failure( - f"Invalid CloudEvent Id [{ce.id}]. CloudEvent Id must be of type UUIDv8.")) + f"Invalid CloudEvent Id [{id}]. CloudEvent Id must be of type UUIDv8.")) @abstractmethod def validate_source(self, ce: CloudEvent): + """ + Validate the source value of a cloud event.

+ @param ce:The cloud event containing the source to validate. + @return:Returns the ValidationResult containing a success or a failure with the error message. + """ raise NotImplementedError("Subclasses must implement this method") @abstractmethod def validate_type(self, ce: CloudEvent): + """ + Validate the type value of a cloud event.

+ @param ce:The cloud event containing the type to validate. + @return:Returns the ValidationResult containing a success or a failure with the error message. + """ raise NotImplementedError("Subclasses must implement this method") def validate_sink(self, ce: CloudEvent) -> ValidationResult: + """ + Validate the sink value of a cloud event in the default scenario where the sink attribute is optional.

+ @param ce:The cloud event containing the sink to validate. + @return:Returns the ValidationResult containing a success or a failure with the error message. + """ maybe_sink = UCloudEvent.get_sink(ce) if maybe_sink: sink = maybe_sink @@ -94,16 +127,18 @@ def validate_sink(self, ce: CloudEvent) -> ValidationResult: return ValidationResult.success() @staticmethod - def validate_u_entity_uri(uri: str) -> ValidationResult: # from uri string - # UUri - # Uri = LongUriSerializer.instance().deserialize(uri) - # return validateUEntityUri(Uri) + def validate_u_entity_uri(uri: str) -> ValidationResult: + """ + Validate an UriPart for an Software Entity must have an authority in the case of a microRemote uri, + and must contain the name of the USE.

+ @param uri:uri string to validate. + @return:Returns the ValidationResult containing a success or a failure with the error message. + """ uri = LongUriSerializer().deserialize(uri) - # Uri = UriFactory.parse_from_uri(uri) return CloudEventValidator.validate_u_entity_uri_from_UURI(uri) @staticmethod - def validate_u_entity_uri_from_UURI(uri: UUri) -> ValidationResult: # from uuri + def validate_u_entity_uri_from_UURI(uri: UUri) -> ValidationResult: u_authority = uri.get_u_authority() if u_authority.is_marked_remote: if not u_authority.device: @@ -115,12 +150,24 @@ def validate_u_entity_uri_from_UURI(uri: UUri) -> ValidationResult: # from uuri return ValidationResult.success() @staticmethod - def validate_topic_uri(uri: str) -> ValidationResult: # from uri string + def validate_topic_uri(uri: str) -> ValidationResult: + """ + Validate a UriPart that is to be used as a topic in publish scenarios for events such as publish, + file and notification.

+ @param uri:String UriPart to validate + @return:Returns the ValidationResult containing a success or a failure with the error message. + """ Uri = LongUriSerializer().deserialize(uri) return CloudEventValidator.validate_topic_uri_from_UURI(Uri) @staticmethod - def validate_topic_uri_from_UURI(uri: UUri) -> ValidationResult: # from uuri + def validate_topic_uri_from_UURI(uri: UUri) -> ValidationResult: + """ + Validate a UriPart that is to be used as a topic in publish scenarios for events such as publish, + file and notification.

+ @param uri: UriPart to validate. + @return:Returns the ValidationResult containing a success or a failure with the error message. + """ validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI(uri) if validationResult.is_success(): return validationResult @@ -135,12 +182,24 @@ def validate_topic_uri_from_UURI(uri: UUri) -> ValidationResult: # from uuri return ValidationResult.success() @staticmethod - def validate_rpc_topic_uri(uri: str) -> ValidationResult: # from uri string + def validate_rpc_topic_uri(uri: str) -> ValidationResult: + """ + Validate a UriPart that is meant to be used as the application response topic for rpc calls.
Used in + Request source values and Response sink values.

+ @param uri:String UriPart to validate. + @return:Returns the ValidationResult containing a success or a failure with the error message. + """ Uri = LongUriSerializer.deserialize(uri) return CloudEventValidator.validate_rpc_topic_uri_from_uuri(Uri) @staticmethod - def validate_rpc_topic_uri_from_uuri(uri: UUri) -> ValidationResult: # from uuri + def validate_rpc_topic_uri_from_uuri(uri: UUri) -> ValidationResult: + """ + Validate a UriPart that is meant to be used as the application response topic for rpc calls.
Used in + Request source values and Response sink values.

+ @param uri:UriPart to validate. + @return:Returns the ValidationResult containing a success or a failure with the error message. + """ validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI(uri) if validationResult.is_failure(): return ValidationResult.failure( @@ -154,7 +213,13 @@ def validate_rpc_topic_uri_from_uuri(uri: UUri) -> ValidationResult: # from uur return ValidationResult.success() @staticmethod - def validate_rpc_method(uri: str) -> ValidationResult: # string uri + def validate_rpc_method(uri: str) -> ValidationResult: + """ + Validate a UriPart that is meant to be used as an RPC method URI. Used in Request sink values and Response + source values.

+ @param uri: String UriPart to validate + @return:Returns the ValidationResult containing a success or a failure with the error message. + """ Uri = LongUriSerializer.deserialize(uri) validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI(Uri) if validationResult.is_failure(): @@ -169,6 +234,10 @@ def validate_rpc_method(uri: str) -> ValidationResult: # string uri class Publish(CloudEventValidator): + """ + Implements Validations for a CloudEvent of type Publish. + """ + def validate_source(self, cl_event: CloudEvent) -> ValidationResult: source = cl_event.get_attributes().get("source") check_source = self.validate_topic_uri(source) @@ -188,6 +257,10 @@ def __str__(self) -> str: class Notification(Publish): + """ + Implements Validations for a CloudEvent of type Publish that behaves as a Notification, meaning it must have a sink. + """ + def validate_sink(self, cl_event: CloudEvent) -> ValidationResult: maybe_sink = UCloudEvent.get_sink(cl_event) if not maybe_sink: @@ -206,6 +279,10 @@ def __str__(self): class Request(CloudEventValidator): + """ + Implements Validations for a CloudEvent for RPC Request. + """ + def validate_source(self, cl_event: CloudEvent) -> ValidationResult: source = cl_event.get_attributes().get("source") check_source = self.validate_rpc_topic_uri(source) @@ -239,6 +316,10 @@ def __str__(self): class Response(CloudEventValidator): + """ + Implements Validations for a CloudEvent for RPC Response. + """ + def validate_source(self, cl_event: CloudEvent) -> ValidationResult: source = cl_event.get_attributes().get("source") check_source = self.validate_rpc_method(source) @@ -273,6 +354,9 @@ def __str__(self): class Validators(Enum): + """ + Enum that hold the implementations of CloudEventValidator according to type. + """ PUBLISH = Publish() NOTIFICATION = Notification() REQUEST = Request() diff --git a/org_eclipse_uprotocol/cloudevent/validate/validationresult.py b/org_eclipse_uprotocol/cloudevent/validate/validationresult.py index fd97223..5ca6e1b 100644 --- a/org_eclipse_uprotocol/cloudevent/validate/validationresult.py +++ b/org_eclipse_uprotocol/cloudevent/validate/validationresult.py @@ -28,6 +28,9 @@ class ValidationResult(ABC): + """ + Class wrapping a ValidationResult of success or failure wrapping the value of a google.rpc.Status. + """ STATUS_SUCCESS = Status(code=Code.OK, message="OK") def __init__(self): @@ -58,6 +61,10 @@ def failure(message): class Failure(ValidationResult): + """ + Implementation for failure, wrapping the message. + """ + def __init__(self, message): super().__init__() self.message = message if message else "Validation Failed." @@ -76,6 +83,10 @@ def __str__(self): class Success(ValidationResult): + """ + Implementation for success, wrapping a google.rpc.Status with Code 0 for success. + """ + def to_status(self) -> Status: return ValidationResult.STATUS_SUCCESS From b36c2f1264df2a80d9c38f56af8e0edfe2dbc07c Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Wed, 25 Oct 2023 16:06:31 -0400 Subject: [PATCH 11/47] Add method and class comments to the rpc package --- org_eclipse_uprotocol/rpc/calloptions.py | 31 +++++++++++++++++++++++ org_eclipse_uprotocol/rpc/rpcclient.py | 20 ++++++++++++++- org_eclipse_uprotocol/rpc/rpcmapper.py | 32 ++++++++++++++++++++++++ org_eclipse_uprotocol/rpc/rpcresult.py | 4 +++ 4 files changed, 86 insertions(+), 1 deletion(-) diff --git a/org_eclipse_uprotocol/rpc/calloptions.py b/org_eclipse_uprotocol/rpc/calloptions.py index 9a9d58f..efd620f 100644 --- a/org_eclipse_uprotocol/rpc/calloptions.py +++ b/org_eclipse_uprotocol/rpc/calloptions.py @@ -21,7 +21,13 @@ # ------------------------------------------------------------------------- class CallOptions: + """ + This class is used when making uRPC calls to pass additional options. + """ TIMEOUT_DEFAULT = 10000 + """ + Default timeout of a call in milliseconds. + """ def __init__(self, timeout=TIMEOUT_DEFAULT, token=""): self.mTimeout = timeout @@ -33,10 +39,18 @@ def default(cls): @property def timeout(self): + """ + Get a timeout.

+ @return: A timeout in milliseconds. + """ return self.mTimeout @property def token(self): + """ + Get an OAuth2 access token.

+ @return: An Optional OAuth2 access token. + """ return self.mToken if self.mToken else None def __eq__(self, other): @@ -52,6 +66,9 @@ def __str__(self): class CallOptionsBuilder: + """ + Builder for constructing CallOptions. + """ TIMEOUT_DEFAULT = 10000 def __init__(self): @@ -59,12 +76,26 @@ def __init__(self): self.mToken = "" def with_timeout(self, timeout): + """ + Add a timeout.

+ @param timeout:A timeout in milliseconds. + @return:This builder. + """ self.mTimeout = timeout if timeout > 0 else self.TIMEOUT_DEFAULT return self def with_token(self, token): + """ + Add an OAuth2 access token.

+ @param token:An OAuth2 access token. + @return:This builder. + """ self.mToken = token return self def build(self): + """ + Construct a CallOptions from this builder.

+ @return:A constructed CallOptions. + """ return CallOptions(self.mTimeout, self.mToken) diff --git a/org_eclipse_uprotocol/rpc/rpcclient.py b/org_eclipse_uprotocol/rpc/rpcclient.py index c584489..4a3c79e 100644 --- a/org_eclipse_uprotocol/rpc/rpcclient.py +++ b/org_eclipse_uprotocol/rpc/rpcclient.py @@ -30,6 +30,24 @@ class RpcClient(ABC): + """ + RpcClient is an interface used by code generators for uProtocol services defined in proto files such as the core + uProtocol services found in here.
The + interface provides a + clean contract for all transports to implement to be able to support RPC on their platform.
Each platform MUST + implement this interface.
For more details please refer to
+ [RpcClient + Specifications] + """ + @abstractmethod - def invoke_method(self, topic: UUri, payload: UPayload, attributes: UAttributes) -> Future: # Future of Upayload + def invoke_method(self, topic: UUri, payload: UPayload, attributes: UAttributes) -> 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:etadata for the method invocation (i.e. priority, timeout, etc.) + @return: Returns the CompletableFuture with the result or exception. + """ pass diff --git a/org_eclipse_uprotocol/rpc/rpcmapper.py b/org_eclipse_uprotocol/rpc/rpcmapper.py index 33de616..7c3b199 100644 --- a/org_eclipse_uprotocol/rpc/rpcmapper.py +++ b/org_eclipse_uprotocol/rpc/rpcmapper.py @@ -31,8 +31,23 @@ 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 ( + USerializationHint.PROTOBUF) and will barf if anything else is passed + """ + @staticmethod def map_response(response_future: Future, expected_cls): + """ + Map a response of CompletableFuture<UPayload> 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 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. + """ + def handle_response(payload, exception): if exception: raise exception @@ -61,6 +76,15 @@ def callbackwrapper(payload, exception=None): @staticmethod def map_response_to_result(response_future: Future, expected_cls): + """ + Map a response of CompletableFuture<Any> from Link into a CompletableFuture containing an RpcResult + containing the declared expected return type T, or a Status containing any errors.

+ @param response_future:CompletableFuture<Any> response from Link. + @param expected_cls:The class name of the declared expected return type of the RPC method. + @return:Returns a CompletableFuture containing an RpcResult containing the declared expected return type T, + or a Status containing any errors. + """ + def handle_response(payload, exception=None): if exception: raise exception @@ -101,6 +125,14 @@ 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} + 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} + object. + """ try: payload.Unpack(expected_cls) return expected_cls diff --git a/org_eclipse_uprotocol/rpc/rpcresult.py b/org_eclipse_uprotocol/rpc/rpcresult.py index 202895e..79f3138 100644 --- a/org_eclipse_uprotocol/rpc/rpcresult.py +++ b/org_eclipse_uprotocol/rpc/rpcresult.py @@ -31,6 +31,10 @@ class RpcResult(ABC): + """ + Wrapper class for RPC Stub calls. It contains a Success with the type of the RPC call, or a failure with the + Status returned by the failed call. + """ @abstractmethod def isSuccess(self) -> bool: From 434e1dadddc547a4cd70ac27a18c5e0a76d76b5e Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Wed, 25 Oct 2023 17:43:17 -0400 Subject: [PATCH 12/47] Add method and class comments to transport package --- .../transport/datamodel/uattributes.py | 46 +++++++ .../transport/datamodel/ulistener.py | 15 +- .../transport/datamodel/umessagetype.py | 15 ++ .../transport/datamodel/upayload.py | 24 ++++ .../transport/datamodel/upriority.py | 10 ++ .../transport/datamodel/userializationhint.py | 26 ++-- .../transport/datamodel/ustatus.py | 25 +++- org_eclipse_uprotocol/transport/utransport.py | 35 +++++ .../validate/uattributesvalidator.py | 128 +++++++++++++++++- 9 files changed, 302 insertions(+), 22 deletions(-) diff --git a/org_eclipse_uprotocol/transport/datamodel/uattributes.py b/org_eclipse_uprotocol/transport/datamodel/uattributes.py index d5138fc..94f1fa6 100644 --- a/org_eclipse_uprotocol/transport/datamodel/uattributes.py +++ b/org_eclipse_uprotocol/transport/datamodel/uattributes.py @@ -27,6 +27,7 @@ class UAttributesBuilder: + def __init__(self, id: UUID, type: UMessageType, priority: UPriority): self.id = id self.type = type @@ -68,11 +69,37 @@ def build(self): class UAttributes: + """ + When sending data over uTransport the basic API for send uses a source topic and the UPayload as the + data.
Any other information about the message is placed in the UAttributes class.
The UAttributes class + holds the additional information along with business methods for understanding more about the actual message + sent. UAttributes is the class that defines the Payload. It is the place for configuring time to live, + priority, security tokens and more.
Each UAttributes class defines a different type of message payload. The + payload can represent a simple published payload with some state change, Payload representing an RPC request or + Payload representing an RPC response. + """ + def __init__(self, id: UUID, type: UMessageType, priority: UPriority, ttl: int, token: str, sink: UUri, plevel: int, commstatus: int, reqid: UUID): + """ + Construct the transport UAttributes object.

+ @param id:Unique identifier for the message. Required. + @param type:Message type such as Publish a state change, RPC request or RPC response. Required. + @param priority:Message priority. Required. + @param ttl:Time to live in milliseconds. + @param token:Authorization token used for TAP. + @param sink:Explicit destination URI, used in notifications and RPC messages. + @param plevel:Permission Level. + @param commstatus:Communication Status, used to indicate platform communication errors that occurred during + delivery. + @param reqid: Request ID, used to indicate the id of the RPC request that matches this RPC response. + """ + + # Required Attributes self.id = id self.type = type self.priority = priority + # Optional Attributes self.ttl = ttl self.token = token self.sink = sink @@ -82,13 +109,32 @@ def __init__(self, id: UUID, type: UMessageType, priority: UPriority, ttl: int, @staticmethod def empty(): + """ + Static factory method for creating an empty attributes object, to avoid working with null.

+ @return: Returns an empty attributes that indicates that there are no added additional attributes to + configure.
An empty UAttributes is not valid, in the same way null is not valid, this is because + UAttributes has 3 required values - id, type and priority. + """ return UAttributes(None, None, None, None, None, None, None, None, None) @staticmethod def for_rpc_request(id: UUID, sink: UUri) -> UAttributesBuilder: + """ + Static factory method for creating a base UAttributes for an RPC request.

+ @param id:id Unique identifier for the RPC request message. + @param sink:UUri describing the exact RPC command. + @return:Returns a base UAttributes that can be used to build an RPC request. + """ return UAttributesBuilder(id, UMessageType.REQUEST, UPriority.REALTIME_INTERACTIVE).with_sink(sink) @staticmethod def for_rpc_response(id: UUID, sink: UUri, reqid: UUID) -> UAttributesBuilder: + """ + Static factory method for creating a base UAttributes for an RPC response.

+ @param id:Unique identifier for the RPC response message. + @param sink:UUri describing where the response needs to go. + @param reqid:The UUID of the message that this response is responding to. + @return: Returns a base UAttributes that can be used to build an RPC response. + """ return UAttributesBuilder(id, UMessageType.RESPONSE, UPriority.REALTIME_INTERACTIVE).with_sink(sink).with_reqid( reqid) diff --git a/org_eclipse_uprotocol/transport/datamodel/ulistener.py b/org_eclipse_uprotocol/transport/datamodel/ulistener.py index 024edff..67c62d5 100644 --- a/org_eclipse_uprotocol/transport/datamodel/ulistener.py +++ b/org_eclipse_uprotocol/transport/datamodel/ulistener.py @@ -29,13 +29,18 @@ class UListener(ABC): + """ + For any implementation that defines some kind of callback or function that will be called to handle incoming + messages. + """ + @abstractmethod def on_receive(self, topic: UUri, payload: UPayload, attributes: UAttributes) -> UStatus: """ - 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 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. """ pass diff --git a/org_eclipse_uprotocol/transport/datamodel/umessagetype.py b/org_eclipse_uprotocol/transport/datamodel/umessagetype.py index 62a4cd0..8e7a6dc 100644 --- a/org_eclipse_uprotocol/transport/datamodel/umessagetype.py +++ b/org_eclipse_uprotocol/transport/datamodel/umessagetype.py @@ -20,11 +20,16 @@ # under the License. # ------------------------------------------------------------------------- + from enum import Enum, unique @unique class UMessageType(Enum): + """ + uProtocol defines message types. Using the message type, validation can be performed to ensure transport validity + of the data in the UAttributes. + """ PUBLISH = (0, "pub.v1") # Publish or notification event REQUEST = (1, "req.v1") # Request RESPONSE = (2, "res.v1") # Response @@ -41,6 +46,11 @@ def string_value(self): @classmethod def from_int(cls, value: int): + """ + Find the Message type from a numeric value. Mind you, it might not exist.

+ @param value:numeric message type. + @return:Returns the UMessageType matching the numeric value. + """ for item in cls: if item.int_value == value: return item @@ -48,6 +58,11 @@ def from_int(cls, value: int): @classmethod def from_string(cls, value: str): + """ + Find the Message type from a string value. Mind you, it might not exist. + @param value:string message type. + @return:Returns the UMessageType matching the string value. + """ for item in cls: if item.string_value == value: return item diff --git a/org_eclipse_uprotocol/transport/datamodel/upayload.py b/org_eclipse_uprotocol/transport/datamodel/upayload.py index e42314a..3cb049c 100644 --- a/org_eclipse_uprotocol/transport/datamodel/upayload.py +++ b/org_eclipse_uprotocol/transport/datamodel/upayload.py @@ -27,25 +27,49 @@ class UPayload: + """ + The UPayload contains the clean Payload information along with its raw serialized structure of a byte[]. + """ EMPTY = None def __init__(self, data: bytes, hint: Optional[USerializationHint] = None): + """ + Create a UPayload.

+ @param data:A byte array of the actual data. + @param hint:Hint regarding the bytes contained within the UPayload + """ self.data = data if data is not None else bytes() self.hint = hint if hint is not None else USerializationHint.UNKNOWN def get_data(self) -> bytes: + """ + The actual serialized or raw data, which can be deserialized or simply used as is.

+ @return:Returns the actual serialized or raw data, which can be deserialized or simply used as is. + """ return self.data def get_hint(self) -> USerializationHint: + """ + The hint regarding the bytes contained within the UPayload. + @return:Returns the hint regarding the bytes contained within the UPayload. + """ return self.hint @classmethod def empty(cls): + """ + + @return: Returns an empty representation of UPayload. + """ if cls.EMPTY is None: cls.EMPTY = UPayload(bytes(), USerializationHint.UNKNOWN) return cls.EMPTY def is_empty(self): + """ + + @return:Returns true if the data in the UPayload is empty. + """ return len(self.data) == 0 def __eq__(self, other): diff --git a/org_eclipse_uprotocol/transport/datamodel/upriority.py b/org_eclipse_uprotocol/transport/datamodel/upriority.py index 26b4212..7327bd4 100644 --- a/org_eclipse_uprotocol/transport/datamodel/upriority.py +++ b/org_eclipse_uprotocol/transport/datamodel/upriority.py @@ -50,6 +50,11 @@ def int_value(self): @classmethod def from_int(cls, value: int): + """ + Find the Priority matching the numeric value. Mind you, it might not exist.

+ @param value:numeric priority value. + @return:Returns the Priority matching the numeric value. Mind you, it might not exist. + """ for item in cls: if item.value[1] == value: return item @@ -57,6 +62,11 @@ def from_int(cls, value: int): @classmethod def from_string(cls, value: str): + """ + Find the Priority matching the QOS String value. Mind you, it might not exist.

+ @param value:QOS String priority value. + @return:Returns the Priority matching the QOS String value. Mind you, it might not exist. + """ for item in cls: if item.value[0] == value: return item diff --git a/org_eclipse_uprotocol/transport/datamodel/userializationhint.py b/org_eclipse_uprotocol/transport/datamodel/userializationhint.py index 98cab5a..d790ebf 100644 --- a/org_eclipse_uprotocol/transport/datamodel/userializationhint.py +++ b/org_eclipse_uprotocol/transport/datamodel/userializationhint.py @@ -20,6 +20,7 @@ # under the License. # ------------------------------------------------------------------------- + from enum import Enum, unique @@ -55,6 +56,11 @@ def get_mime_type(self): @classmethod def from_hint_number(cls, value: int): + """ + Find the serialization hint matching the mimeType value. Mind you, it might not exist.

+ @param value:numeric hint value. + @return:Returns the USerializationHint matching the numeric value. + """ for hint in cls: if hint.get_hint_number() == value: return hint @@ -62,22 +68,12 @@ def from_hint_number(cls, value: int): @classmethod def from_mime_type(cls, value: str): + """ + Find the serialization hint matching the String value. Mind you, it might not exist.

+ @param value:String hint value. + @return:Returns the USerializationHint matching the String value. + """ for hint in cls: if hint.get_mime_type() == value: return hint return None - - -# Example usage -if __name__ == "__main__": - hint = USerializationHint.PROTOBUF - print("Hint Number:", hint.get_hint_number()) - print("MIME Type:", hint.get_mime_type()) - - hint_by_number = USerializationHint.from_hint_number(3) - if hint_by_number: - print("Hint found by number:", hint_by_number.name) - - hint_by_mime_type = USerializationHint.from_mime_type("application/json") - if hint_by_mime_type: - print("Hint found by MIME type:", hint_by_mime_type.name) diff --git a/org_eclipse_uprotocol/transport/datamodel/ustatus.py b/org_eclipse_uprotocol/transport/datamodel/ustatus.py index db7d80a..c7514ac 100644 --- a/org_eclipse_uprotocol/transport/datamodel/ustatus.py +++ b/org_eclipse_uprotocol/transport/datamodel/ustatus.py @@ -29,6 +29,11 @@ class Code(Enum): + """ + Enum to contain the status code that we map to google.rpc.Code.
Please refer to code.protofor + documentation on the codes listed below + """ OK = google.rpc.code_pb2.OK CANCELLED = google.rpc.code_pb2.CANCELLED UNKNOWN = google.rpc.code_pb2.UNKNOWN @@ -41,7 +46,7 @@ class Code(Enum): RESOURCE_EXHAUSTED = google.rpc.code_pb2.RESOURCE_EXHAUSTED FAILED_PRECONDITION = google.rpc.code_pb2.FAILED_PRECONDITION ABORTED = google.rpc.code_pb2.ABORTED - OUT_OF_RANGE =google.rpc.code_pb2.OUT_OF_RANGE + OUT_OF_RANGE = google.rpc.code_pb2.OUT_OF_RANGE UNIMPLEMENTED = google.rpc.code_pb2.UNIMPLEMENTED INTERNAL = google.rpc.code_pb2.INTERNAL UNAVAILABLE = google.rpc.code_pb2.UNAVAILABLE @@ -50,6 +55,10 @@ class Code(Enum): class UStatus(ABC): + """ + UProtocol general status for all operations.
A UStatus is generated using the static factory methods, + making is easy to quickly create UStatus objects.
Example: UStatus ok = UStatus.ok(); + """ OK = "ok" FAILED = "failed" @@ -66,6 +75,10 @@ def getCode(self) -> int: pass def isFailed(self) -> bool: + """ + Return true if UStatus is a failure.

+ @return:Returns true if the UStatus is failed. + """ return not self.isSuccess() def __str__(self) -> str: @@ -110,8 +123,15 @@ def from_google_rpc_code(code: int) -> Optional[Code]: class OKStatus(UStatus): + """ + A successful UStatus. + """ def __init__(self, ack_id: str): + """ + A successful status could contain an id for tracking purposes. + @param ack_id: + """ self.ack_id = ack_id def isSuccess(self) -> bool: @@ -125,6 +145,9 @@ def getCode(self) -> int: class FailStatus(UStatus): + """ + A failed UStatus. + """ def __init__(self, fail_msg: str, code: Code): self.fail_msg = fail_msg diff --git a/org_eclipse_uprotocol/transport/utransport.py b/org_eclipse_uprotocol/transport/utransport.py index 3c56274..0ccacbf 100644 --- a/org_eclipse_uprotocol/transport/utransport.py +++ b/org_eclipse_uprotocol/transport/utransport.py @@ -32,19 +32,54 @@ class UTransport(ABC): + """ + UTransport is the uP-L1 interface that provides a common API for uE developers to send and receive + messages.
UTransport implementations contain the details for connecting to the underlying transport technology + and sending UMessage using the configured technology.
For more information please refer to + link + """ @abstractmethod def authenticate(self, u_entity: UEntity) -> 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. + """ 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. + """ 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. + """ pass diff --git a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py index 27351cc..2c6e70d 100644 --- a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py +++ b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py @@ -21,7 +21,6 @@ # ------------------------------------------------------------------------- -import time from abc import abstractmethod from datetime import datetime from enum import Enum @@ -34,8 +33,25 @@ class UAttributesValidator: + """ + UAttributes is the class that defines the Payload. It is the place for configuring time to live, priority, + security tokens and more.

+ Each UAttributes class defines a different type of message payload. The payload can represent a simple published + payload with some state change,Payload representing an RPC request or Payload representing an RPC response.

+ UAttributesValidator is a base class for all UAttribute validators, that can help validate that the + UAttributes object is correctly defined to define the Payload correctly. + + """ + @staticmethod def get_validator(attribute: UAttributes): + """ + Static factory method for getting a validator according to the UMessageType defined in the + UAttributes.
+ @param attribute: UAttributes containing the UMessageType. + @return: returns a UAttributesValidator according to the UMessageType defined in the + UAttributes. + """ if attribute.type is None: return Validators.PUBLISH.validator() elif attribute.type == UMessageType.RESPONSE: @@ -47,6 +63,11 @@ def get_validator(attribute: UAttributes): @staticmethod def validate_id(attr: UAttributes) -> UStatus: + """ + Validate the id attribute, it is required.

+ @param attr:UAttributes object containing the id to validate. + @return:Returns a UStatus that is success or failed with a failure message. + """ attr_id = attr.id if UUIDUtils.isuuid(attr_id): return UStatus.ok() @@ -55,11 +76,22 @@ def validate_id(attr: UAttributes) -> UStatus: @staticmethod def validate_priority(attr: UAttributes) -> UStatus: + """ + Validate the UPriority since it is required.

+ @param attr:UAttributes object containing the message priority to validate. + @return:Returns a UStatus that is success or failed with a failure message. + """ return UStatus.failed_with_msg_and_code("Priority is missing", Code.INVALID_ARGUMENT) if attr.priority is None else UStatus.ok() @staticmethod def validate_ttl(attr: UAttributes) -> UStatus: + """ + Validate the time to live configuration. If the UAttributes does not contain a time to live then the UStatus + is ok.

+ @param attr:UAttributes object containing the message time to live configuration to validate. + @return:Returns a UStatus that is success or failed with a failure message. + """ if attr.ttl and attr.ttl <= 0: return UStatus.failed_with_msg_and_code(f"Invalid TTL [{attr.ttl}]", Code.INVALID_ARGUMENT) else: @@ -67,10 +99,22 @@ def validate_ttl(attr: UAttributes) -> UStatus: @staticmethod def validate_sink(attr: UAttributes) -> UStatus: + """ + Validate the sink UriPart for the default case. If the UAttributes does not contain a sink then the UStatus + is ok.

+ @param attr:UAttributes object containing the sink to validate. + @return:Returns a UStatus that is success or failed with a failure message. + """ return UriValidator.validate(attr.sink) if attr.sink else UStatus.ok() @staticmethod def validate_permission_level(attr: UAttributes) -> UStatus: + """ + Validate the permissionLevel for the default case. If the UAttributes does not contain a permission level + then the UStatus is ok.

+ @param attr:UAttributes object containing the permission level to validate. + @return:Returns a UStatus indicating if the permissionLevel is valid or not. + """ if attr.plevel and attr.plevel > 0: return UStatus.ok() else: @@ -78,20 +122,43 @@ def validate_permission_level(attr: UAttributes) -> UStatus: @staticmethod def validate_comm_status(attr: UAttributes) -> UStatus: + """ + Validate the commStatus for the default case. If the UAttributes does not contain a comm status then the + UStatus is ok.

+ @param attr:UAttributes object containing the comm status to validate. + @return:Returns a UStatus that is success or failed with a failure message. + """ return UStatus.ok() if attr.commstatus else UStatus.failed_with_msg_and_code( "Invalid Communication Status Code", Code.INVALID_ARGUMENT) @staticmethod def validate_req_id(attr: UAttributes) -> UStatus: + """ + Validate the correlationId for the default case. If the UAttributes does not contain a request id then the + UStatus is ok.

+ @param attr:Attributes object containing the request id to validate. + @return:Returns a UStatus that is success or failed with a failure message. + """ return UStatus.failed_with_msg_and_code("Invalid UUID", Code.INVALID_ARGUMENT) if attr.reqid and not UUIDUtils.isuuid( attr.reqid) else UStatus.ok() @abstractmethod def validate_type(self, attr: UAttributes): + """ + Validate the UMessageType attribute, it is required.

+ @param attr:UAttributes object containing the message type to validate. + @return:Returns a UStatus that is success or failed with a failure message. + """ raise NotImplementedError("Subclasses must implement this method.") def validate(self, attributes: UAttributes): + """ + Take a UAttributes object and run validations.

+ @param attributes:The UAttriubes to validate. + @return:Returns a UStatus that is success or failed with a message containing all validation errors + for invalid configurations. + """ error_messages = [self.validate_id(attributes), self.validate_type(attributes), self.validate_priority(attributes), self.validate_ttl(attributes), self.validate_sink(attributes), self.validate_comm_status(attributes), @@ -106,6 +173,12 @@ def validate(self, attributes: UAttributes): return UStatus.ok() def is_expired(self, u_attributes: UAttributes): + """ + Indication if the Payload with these UAttributes is expired.

+ @param u_attributes:UAttributes with time to live value. + @return: Returns a UStatus that is success meaning not expired or failed with a validation message or + expiration. + """ maybe_ttl = u_attributes.ttl maybe_time = UUIDUtils.getTime(u_attributes.id) @@ -125,7 +198,16 @@ def is_expired(self, u_attributes: UAttributes): class Publish(UAttributesValidator): + """ + Implements validations for UAttributes that define a message that is meant for publishing state changes. + """ + def validate_type(self, attributes_value: UAttributes) -> UStatus: + """ + Validates that attributes for a message meant to publish state changes has the correct type.

+ @param attributes_value:UAttributes object containing the message type to validate. + @return:Returns a UStatus that is success or failed with a failure message. + """ return UStatus.ok() if attributes_value.type == UMessageType.PUBLISH else UStatus.failed_with_msg_and_code( f"Wrong Attribute Type [{attributes_value.type}]", Code.INVALID_ARGUMENT) @@ -134,16 +216,36 @@ def __str__(self): class Request(UAttributesValidator): + """ + Implements validations for UAttributes that define a message that is meant for an RPC request. + """ + def validate_type(self, attributes_value: UAttributes) -> UStatus: + """ + Validates that attributes for a message meant for an RPC request has the correct type.

+ @param attributes_value:UAttributes object containing the message type to validate. + @return:Returns a UStatus that is success or failed with a failure message. + """ return UStatus.ok() if attributes_value.type == UMessageType.REQUEST else UStatus.failed_with_msg_and_code( f"Wrong Attribute Type [{attributes_value.type}]", Code.INVALID_ARGUMENT) def validate_sink(self, attributes_value: UAttributes) -> UStatus: + """ + Validates that attributes for a message meant for an RPC request has a destination sink.

In the case + of an RPC request, the sink is required. + @param attributes_value:UAttributes object containing the sink to validate. + @return:Returns a UStatus that is success or failed with a failure message. + """ return UriValidator.validate_rpc_response( attributes_value.sink) if attributes_value.sink else UStatus.failed_with_msg_and_code("Missing Sink", Code.INVALID_ARGUMENT) def validate_ttl(self, attributes_value: UAttributes) -> UStatus: + """ + Validate the time to live configuration.
In the case of an RPC request, the time to live is required.

+ @param attributes_value:UAttributes object containing the time to live to validate. + @return:Returns a UStatus that is success or failed with a failure message. + """ return UStatus.ok() if attributes_value.ttl and attributes_value.ttl > 0 else UStatus.failed_with_msg_and_code( "Missing TTL", Code.INVALID_ARGUMENT) @@ -152,16 +254,36 @@ def __str__(self): class Response(UAttributesValidator): + """ + Implements validations for UAttributes that define a message that is meant for an RPC response. + """ + def validate_type(self, attributes_value: UAttributes) -> UStatus: + """ + Validates that attributes for a message meant for an RPC response has the correct type.

+ @param attributes_value:UAttributes object containing the message type to validate. + @return:Returns a UStatus that is success or failed with a failure message. + """ return UStatus.ok() if attributes_value.type == UMessageType.RESPONSE else UStatus.failed_with_msg_and_code( f"Wrong Attribute Type [{attributes_value.type}]", Code.INVALID_ARGUMENT) def validate_sink(self, attributes_value: UAttributes) -> UStatus: + """ + Validates that attributes for a message meant for an RPC response has a destination sink.
In the case of + an RPC response, the sink is required.

+ @param attributes_value:UAttributes object containing the sink to validate. + @return:Returns a UStatus that is success or failed with a failure message. + """ return UriValidator.validate_rpc_method( attributes_value.sink) if attributes_value.sink else UStatus.failed_with_msg_and_code("Missing Sink", Code.INVALID_ARGUMENT) def validate_req_id(self, attributes_value: UAttributes) -> UStatus: + """ + Validate the correlationId. n the case of an RPC response, the correlation id is required.

+ @param attributes_value:UAttributes object containing the correlation id to validate. + @return:Returns a UStatus that is success or failed with a failure message. + """ return UStatus.ok() if attributes_value.reqid and UUIDUtils.isuuid( attributes_value.reqid) else UStatus.failed_with_msg_and_code("Missing correlationId", Code.INVALID_ARGUMENT) @@ -171,6 +293,10 @@ def __str__(self): class Validators(Enum): + """ + Validators Factory.
Example: UAttributesValidator validateForPublishMessageType = + UAttributesValidator.Validators.PUBLISH.validator() + """ PUBLISH = Publish() REQUEST = Request() RESPONSE = Response() From 3749a986446742e91b5fa73d4833fc6d89d10446 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 26 Oct 2023 11:50:42 -0400 Subject: [PATCH 13/47] Add method and class comments to the uri and uuid package --- .../uri/datamodel/uauthority.py | 110 +++++++++++++++- .../uri/datamodel/uentity.py | 104 ++++++++++++++- .../uri/datamodel/uresource.py | 123 +++++++++++++++++- org_eclipse_uprotocol/uri/datamodel/uuri.py | 57 ++++++++ .../uri/serializer/longuriserializer.py | 37 +++++- .../uri/serializer/microuriserializer.py | 21 ++- .../uri/serializer/uriserializer.py | 23 ++++ .../uri/validator/urivalidator.py | 21 +++ .../uuid/factory/uuidutils.py | 72 +++++++++- .../uuid/validate/uuidvalidator.py | 4 + 10 files changed, 551 insertions(+), 21 deletions(-) diff --git a/org_eclipse_uprotocol/uri/datamodel/uauthority.py b/org_eclipse_uprotocol/uri/datamodel/uauthority.py index 4198b82..646b38f 100644 --- a/org_eclipse_uprotocol/uri/datamodel/uauthority.py +++ b/org_eclipse_uprotocol/uri/datamodel/uauthority.py @@ -26,10 +26,28 @@ class UAuthority: + """ + An Authority represents the deployment location of a specific Software Entity.
Data representation of an + Authority.
An Authority consists of a device, a domain and a micro version in the form of an IP + Address.
Device and domain names are used as part of the URI for device and service discovery. Optimized micro + versions of the UUri will use the IP Address.
Devices will be grouped together into realms of Zone of + Authority.
+ """ EMPTY = None def __init__(self, device: str, domain: str, address: ipaddress.IPv4Address, marked_remote: bool, marked_resolved: bool): + """ + Constructor for building a UAuthority.

+ @param device: The device a software entity is deployed on, such as the VCU, CCU or cloud provider. A device + is a logical independent representation of a service bus in different execution environments. Devices will + be grouped together into realms of Zone of Authority. + @param domain: The domain a software entity is deployed on, such as vehicle or Cloud (PaaS). + @param address:The device IP address of the device. + @param marked_remote:Indicates if this UAuthority was implicitly marked as remote. + @param marked_resolved:Indicates that this uAuthority has all the information needed to be serialised in the + long format or the micro format of a UUri. + """ self.device = device.lower() if device else None self.domain = domain.lower() if domain else None self.address = address @@ -38,59 +56,138 @@ def __init__(self, device: str, domain: str, address: ipaddress.IPv4Address, mar @staticmethod def local(): + """ + Static factory method for creating a local uAuthority.
A local uri does not contain an authority and looks + like this:
 :<service>/<version>/<resource>#<Message> 

+ @return:Returns a local uAuthority that has no domain, device, or ip address information, indicating to + uProtocol that the uAuthority part in the UUri is relative to the sender/receiver deployment environment. + """ return UAuthority.EMPTY @staticmethod def long_remote(device: str, domain: str): - + """ + Static factory method for creating a remote authority supporting the long serialization information + representation of a UUri.
Building a UAuthority with this method will create an unresolved uAuthority + that can only be serialised in long UUri format. An uri with a long representation of uAUthority can be + serialised as follows: +
 //<device>.<domain>/<service>/<version>/<resource>#<Message>
+         
+ @param device:The device a software entity is deployed on, such as the VCU, CCU or cloud provider. + @param domain:The domain a software entity is deployed on, such as vehicle or Cloud (PaaS). Vehicle Domain + name MUST be that of the vehicle VIN. + @return:Returns a uAuthority that contains the device and the domain and can only be serialized in long UUri + format. + """ return UAuthority(device, domain, None, True, False) @staticmethod def micro_remote(address: ipaddress.IPv4Address): + """ + Static factory method for creating a remote authority supporting the micro serialization information + representation of a UUri.
Building a UAuthority with this method will create an unresolved uAuthority that + can only be serialised in micro UUri format.

+ @param address:The ip address of the device a software entity is deployed on. + @return:Returns a uAuthority that contains only the internet address of the device, and can only be + serialized in micro UUri format. + """ return UAuthority(None, None, address, True, False) @staticmethod def resolved_remote(device: str, domain: str, address: ipaddress.IPv4Address): + """ + Static factory method for creating a remote authority that is completely resolved with name, device and ip + address of the device.
Building a UAuthority with this method will enable serialisation in both UUri + formats, long UUri format and micro UUri format. Note that in the case of missing data, this will not fail, + but simply create a UAuthority that is not resolved.

+ @param device:The device name for long serialization of UUri. + @param domain:The domain name for long serialization of UUri. + @param address:The IP address for the device, for micro serialization of UUri. + @return:Returns a uAuthority that contains all resolved data and so can be serialized into a long UUri or a + micro UUri. + """ resolved = device is not None and device.strip() != "" and address is not None return UAuthority(device, domain, address, True, resolved) @staticmethod def empty(): - + """ + Static factory method for creating an empty uAuthority, to avoid working with null
Empty uAuthority is + still serializable in both long and micro UUri formats, and will be as local to the current deployment + environment.

+ @return:Returns an empty authority that has no domain, device, or device ip address information. + """ return UAuthority.EMPTY def is_remote(self) -> bool: + """ + Support for determining if this uAuthority defines a deployment that is defined as remote.

+ @return:Returns true if this uAuthority is remote, meaning it contains information for serialising a long + UUri or a micro UUri. + """ return self.is_marked_Remote() # def is_local(self) -> bool: + """ + Support for determining if this uAuthority is defined for a local deployment.

+ @return:Returns true if this uAuthority is local, meaning does not contain a device/domain for long UUri or + information for micro UUri. + """ return self.is_empty() and not self.is_marked_Remote() - def is_marked_Remote(self) -> bool: - return self.marked_remote - def device(self) -> Optional[str]: + """ + Accessing an optional device of the uAuthority.

+ @return:Returns the device a software entity is deployed on, such as the VCU, CCU or cloud provider. + """ return self.device if self.device else None def domain(self) -> Optional[str]: + """ + Accessing an optional domain of the uAuthority.

+ @return:Returns the domain a software entity is deployed on, such as vehicle or backoffice. + """ return self.domain if self.domain else None def address(self) -> Optional[ipaddress.IPv4Address]: + """ + Accessing an optional IP address configuration of the uAuthority.

+ @return:eturns the device IP address. + """ try: return ipaddress.IPv4Address(self.address) except ValueError: return None def is_marked_remote(self) -> bool: + """ + Support for determining if this uAuthority was configured to be remote.

+ @return:Returns true if this uAuthority is explicitly configured remote deployment. + """ return self.marked_remote def is_resolved(self) -> bool: + """ + Indicates that this UAuthority has already been resolved.
A resolved UAuthority means that it has all the + information needed to be serialised in the long format or the micro format of a UUri.

+ @return:Returns true if this UAuthority is resolved with all the information needed to be serialised in the + long format or the micro format of a UUri. + """ return self.marked_resolved def is_long_form(self) -> bool: + """ + Determine if the UAuthority can be used to serialize a UUri in long format.

+ @return:Returns true if the UAuthority can be used to serialize a UUri in long format. + """ return self.is_local() or self.device is not None def is_micro_form(self) -> bool: + """ + Determine if the UAuthority can be used to serialize a UUri in micro format.

+ @return:Returns true if the uAuthority can be serialized a UUri in micro format. + """ return self.is_local() or self.address is not None def is_empty(self) -> bool: @@ -101,7 +198,8 @@ def __eq__(self, other): if not isinstance(other, UAuthority): return False return ( - self.marked_remote == other.marked_remote and self.device == other.device and self.domain == other.domain) + self.marked_remote == other.marked_remote and self.device == other.device and self.domain == + other.domain) def __hash__(self): return hash((self.device, self.domain, self.marked_remote)) diff --git a/org_eclipse_uprotocol/uri/datamodel/uentity.py b/org_eclipse_uprotocol/uri/datamodel/uentity.py index 329a4d5..6408d61 100644 --- a/org_eclipse_uprotocol/uri/datamodel/uentity.py +++ b/org_eclipse_uprotocol/uri/datamodel/uentity.py @@ -21,9 +21,24 @@ # ------------------------------------------------------------------------- class UEntity: + """ + Data representation of an Software Entity - uE
Software entities are distinguished by using a unique + name or a unique id along with the specific version of the software.
An Software Entity is a piece of software + deployed somewhere on a uDevice.
The Software Entity is used in the source and sink parts of communicating + software.
A uE that publishes events is a Service role.
A uE that consumes events is an + Application role. + """ EMPTY = None def __init__(self, name: str, version: int, identifier: int, marked_resolved: bool): + """ + Build a Software Entity that represents a communicating piece of software.

+ @param name:The name of the software such as petapp or body.access. + @param version:The software version. If not supplied, the latest version of the service will be used. + @param identifier:A numeric identifier for the software entity which is a one-to-one correspondence with the + software name. + @param marked_resolved:Indicates that this uResource was populated with intent of having all data. + """ if name is None: raise ValueError("Software Entity must have a name") self.name = name @@ -33,50 +48,133 @@ def __init__(self, name: str, version: int, identifier: int, marked_resolved: bo @staticmethod def resolved_format(name: str, version: int, identifier: int): + """ + Create a complete UEntity with all the information so that it can be used in long form UUri serialisation and + micro form UUri serialisation.
In the case of missing elements such as name or id, the UEntity will not be + marked as resolvable and will not be usable in serialisation formats.

+ @param name:The name of the software such as petapp or body.access. + @param version:The software version. If not supplied, the latest version of the service will be used. + @param identifier:A numeric identifier for the software entity which is a one-to-one correspondence with the + software name. + @return:Returns a complete UEntity with all the information so that it can be used in long form UUri + serialisation and micro form UUri serialisation. + """ resolved = name and name.strip() and identifier is not None return UEntity(name if name else "", version, identifier, resolved) @staticmethod def long_format(name: str, version: int): + """ + Static factory method for creating a uE using the software entity name, that can be used in long form UUri + serialisation.

+ @param name:The software entity name, such as petapp or body.access. + @param version:The software entity version. + @return:Returns an UEntity with the name and the version of the service and can only be serialized to long + UUri format. + """ return UEntity(name if name else "", version, None, False) @staticmethod def long_format_name(name: str): + """ + Static factory method for creating a uE using the software entity name, that can be used in long form UUri + serialisation.

+ @param name:The software entity name, such as petapp or body.access. + @return:Returns an UEntity with the name where the version is the latest version of the service and can only + be serialized to long UUri format. + """ return UEntity(name if name else "", None, None, False) @staticmethod def micro_format(identifier: int): + """ + Static factory method for creating a uE using the software entity identification number, that can be used to + serialize micro UUris.

+ @param identifier: numeric identifier for the software entity which is a one-to-one correspondence with the + software name. + @return:Returns an UEntity with the name where the version is the latest version of the service and can only + be serialized to long micro UUri format. + """ return UEntity("", None, identifier, False) @staticmethod def micro_format_id_version(identifier: int, version: int): + """ + Static factory method for creating a uE using the software entity identification number, that can be used to + serialize micro UUris.

+ @param identifier:A numeric identifier for the software entity which is a one-to-one correspondence with the + software name. + @param version:The software entity version. + @return:Returns an UEntity with the name and the version of the service and can only be serialized to micro + UUri format. + """ return UEntity("", version, identifier, False) @staticmethod def empty(): + """ + Static factory method for creating an empty software entity, to avoid working with null

+ @return:Returns an empty software entity that has a blank name, no unique id and no version information. + """ if UEntity.EMPTY is None: UEntity.EMPTY = UEntity("", None, None, False) return UEntity.EMPTY def is_empty(self) -> bool: + """ + Indicates that this software entity is an empty container and has no valuable information in building + uProtocol sinks or sources.

+ @return: Returns true if this software entity is an empty container and has no valuable information in + building uProtocol sinks or sources. + """ return not (self.name or self.version or self.id) def is_resolved(self) -> bool: + """ + Return true if the UEntity contains both the name and IDs meaning it contains all the information to be + serialized into a long UUri or a micro form UUri.

+ @return:Returns true of this resource contains resolved information meaning it contains all the information + to be serialized into a long UUri or a micro form UUri. + """ return self.marked_resolved def is_long_form(self) -> bool: + """ + Determine if this software entity can be serialised into a long UUri form.

+ @return:Returns true if this software entity can be serialised into a long UUri form, meaning it has at least + a name. + """ return bool(self.name) def is_micro_form(self) -> bool: + """ + Returns true if the Uri part contains the id's which will allow the Uri part to be serialized into micro + form.

+ @return:Returns true if the Uri part can be serialized into micro form, meaning is has at least a unique + numeric identifier. + """ return self.id is not None def get_name(self) -> str: + """ + Returns the name of the software.

+ @return: Returns the name of the software such as petpp or body.access. + """ return self.name def get_version(self) -> int: + """ + Returns the software version if it exists.

+ @return:Returns the software version if it exists. If the version does not exist, the latest version of the + service will be used. + """ return self.version def get_id(self) -> int: + """ + Returns the software id if it exists. The software id represents the numeric identifier of the uE.

+ @return: Returns the software id if it exists. The software id represents the numeric identifier of the uE. + """ return self.id def __eq__(self, other): @@ -85,13 +183,15 @@ def __eq__(self, other): if not isinstance(other, UEntity): return False return ( - self.marked_resolved == other.marked_resolved and self.name == other.name and self.version == other.version and self.id == other.id) + self.marked_resolved == other.marked_resolved and self.name == other.name and self.version == + other.version and self.id == other.id) def __hash__(self): return hash((self.name, self.version, self.id, self.marked_resolved)) def __str__(self): - return f"UEntity{{name='{self.name}', version={self.version}, id={self.id}, markedResolved={self.marked_resolved}}}" + return (f"UEntity{{name='{self.name}', version={self.version}, id={self.id}, markedResolved=" + f"{self.marked_resolved}}}") # Initialize EMPTY diff --git a/org_eclipse_uprotocol/uri/datamodel/uresource.py b/org_eclipse_uprotocol/uri/datamodel/uresource.py index 597b887..1e2b8dd 100644 --- a/org_eclipse_uprotocol/uri/datamodel/uresource.py +++ b/org_eclipse_uprotocol/uri/datamodel/uresource.py @@ -21,10 +21,28 @@ # ------------------------------------------------------------------------- class UResource: + """ + A service API - defined in the UEntity - has Resources and Methods. Both of these are represented by the + UResource class.
A uResource represents a resource from a Service such as "door" and an optional specific + instance such as "front_left". In addition, it can optionally contain the name of the resource Message type, + such as "Door". The Message type matches the protobuf service IDL that defines structured data types.
An + UResource is something that can be manipulated/controlled/exposed by a service. Resources are unique when + prepended with UAuthority that represents the device and UEntity that represents the service. + """ EMPTY = None RESPONSE = None def __init__(self, name: str, instance: str, message: str, identifier: int, marked_resolved: bool): + """ + Create a uResource. The resource is something that is manipulated by a service such as a door.

+ @param name:The name of the resource as a noun such as door or window, or in the case a method that + manipulates the resource, a verb. + @param instance:An instance of a resource such as front_left. + @param message:The Message type matches the protobuf service IDL message name that defines structured data + types.A message is a data structure type used to define data that is passed in events and rpc methods. + @param identifier:The numeric representation of this uResource. + @param marked_resolved:Indicates that this uResource was populated with intent of having all data. + """ self.name = name if name else "" self.instance = instance self.message = message @@ -33,72 +51,171 @@ def __init__(self, name: str, instance: str, message: str, identifier: int, mark @staticmethod def resolved_format(name: str, instance: str, message: str, identifier: int): + """ + Build a UResource that has all elements resolved and can be serialized in a long UUri or a micro UUri.

+ @param name:The name of the resource as a noun such as door or window, or in the case a method that + manipulates the resource, a verb. + @param instance:An instance of a resource such as front_left. + @param message:The Message type matches the protobuf service IDL message name that defines structured data + types.A message is a data structure type used to define data that is passed in events and rpc methods. + @param identifier:The numeric representation of this uResource. + @return:Returns a UResource that has all the information that is needed to serialize into a long UUri or a + micro UUri. + """ resolved = name and name.strip() and identifier is not None return UResource(name if name else "", instance, message, identifier, resolved) @staticmethod def long_format(name: str): + """ + Build a UResource that can be serialized into a long UUri. Mostly used for publishing messages.

+ @param name:The name of the resource as a noun such as door or window, or in the case a method that + manipulates the resource, a verb. + @return:Returns a UResource that can be serialized into a long UUri. + """ return UResource(name if name else "", None, None, None, False) @staticmethod def long_format_instance_message(name: str, instance: str, message: str): + """ + Build a UResource that can be serialized into a long UUri. Mostly used for publishing messages.

+ @param name:The name of the resource as a noun such as door or window, or in the case a method that + manipulates the resource, a verb. + @param instance:An instance of a resource such as front_left. + @param message:The Message type matches the protobuf service IDL message name that defines structured data + types. A message is a data structure type used to define data that is passed in events and rpc methods. + @return:Returns a UResource that can be serialised into a long UUri. + """ return UResource(name if name else "", instance, message, None, False) @staticmethod def micro_format(identifier: int): + """ + Build a UResource that can be serialised into a micro UUri. Mostly used for publishing messages.

+ @param identifier:The numeric representation of this uResource. + @return:Returns a UResource that can be serialised into a micro UUri. + """ return UResource("", None, None, identifier, False) @staticmethod def for_rpc_request(method_name: str): + """ + Build a UResource for rpc request, using only the long format.

+ @param method_name:The RPC method name. + @return:Returns a UResource used for an RPC request that could be serialised in long format. + """ return UResource("rpc", method_name, None, None, False) @staticmethod def for_rpc_request_with_id(method_id: int): + """ + Build a UResource for rpc request, using only the micro format.

+ @param method_id:The numeric representation method name for the RPC. + @return:Returns a UResource used for an RPC request that could be serialised in micro format. + """ return UResource("rpc", None, None, method_id, False) @staticmethod def for_rpc_request_with_name_and_id(method_name: str, method_id: int): + """ + Build a UResource for rpc request, using both the long and micro format information.

+ @param method_name:The RPC method name. + @param method_id:The numeric representation method name for the RPC. + @return:Returns a UResource used for an RPC request that could be serialised in long and micro format. + """ resolved = method_name and method_name.strip() and method_id is not None return UResource("rpc", method_name, None, method_id, resolved) @staticmethod def for_rpc_response(): + """ + Static factory method for creating a response resource that is returned from RPC calls

+ @return:Returns a response resource used for response RPC calls. + """ return UResource.RESPONSE def is_rpc_method(self) -> bool: + """ + Returns true if this resource specifies an RPC method call or RPC response.

+ @return:Returns true if this resource specifies an RPC method call or RPC response. + """ return self.name == "rpc" @staticmethod def empty(): + """ + Static factory method for creating an empty resource, to avoid working with null

+ @return:Returns an empty resource that has a blank name and no message instance information. + """ if UResource.EMPTY is None: UResource.EMPTY = UResource("", None, None, None, False) return UResource.EMPTY def is_empty(self) -> bool: + """ + Indicates that this resource is an empty container and has no valuable information in building uProtocol + URI.

+ @return:Returns true if this resource is an empty container and has no valuable information in building + uProtocol URI. + """ return not (self.name and self.name.strip()) or self.name == "rpc" and not ( self.instance or self.message or self.id) def get_name(self) -> str: + """ + + @return: Returns the name of the resource as a noun such as door or window, or in the case a method that + manipulates the resource, a verb. + """ return self.name def get_id(self) -> int: + """ + + @return: Returns the resource id if it exists. + """ return self.id def get_instance(self) -> str: + """ + An instance of a resource such as front_left or in the case of RPC a method name that manipulates the + resource such as UpdateDoor.

+ @return:Returns the resource instance of the resource if it exists. If the instance does not exist, + it is assumed that all the instances of the resource are wanted. + """ return self.instance def get_message(self) -> str: + """ + The Message type matches the protobuf service IDL that defines structured data types.
A message is a data + structure type used to define data that is passed in events and rpc methods.

+ @return:Returns the Message type matches the protobuf service IDL that defines structured data types. + """ return self.message def is_resolved(self) -> bool: + """ + Return true if this resource contains both ID and names.
Method type of UResource requires name, instance, + and ID where a topic type of UResource also requires message to not be null

+ @return:Returns true of this resource contains resolved information + """ return self.marked_resolved def is_long_form(self) -> bool: + """ + Returns true if the uResource contains names so that it can be serialized to long format.

+ @return:Returns true if the uResource contains names so that it can be serialized to long format. + """ if self.name == "rpc": return bool(self.instance) return bool(self.name) def is_micro_form(self) -> bool: + """ + Returns true if the uResource contains the id's which will allow the Uri part to be serialized into micro + form.

+ @return:Returns true if the uResource can be serialized into micro form. + """ return self.id is not None def __eq__(self, other): @@ -107,13 +224,15 @@ def __eq__(self, other): if not isinstance(other, UResource): return False return ( - self.marked_resolved == other.marked_resolved and self.name == other.name and self.instance == other.instance and self.message == other.message and self.id == other.id) + self.marked_resolved == other.marked_resolved and self.name == other.name and self.instance == + other.instance and self.message == other.message and self.id == other.id) def __hash__(self): return hash((self.name, self.instance, self.message, self.id, self.marked_resolved)) def __str__(self): - return f"UResource{{name='{self.name}', instance='{self.instance}', message='{self.message}', id={self.id}, markedResolved={self.marked_resolved}}}" + return (f"UResource{{name='{self.name}', instance='{self.instance}', message='{self.message}', id={self.id}, " + f"markedResolved={self.marked_resolved}}}") # Initialize EMPTY and RESPONSE diff --git a/org_eclipse_uprotocol/uri/datamodel/uuri.py b/org_eclipse_uprotocol/uri/datamodel/uuri.py index 0be748a..3a72699 100644 --- a/org_eclipse_uprotocol/uri/datamodel/uuri.py +++ b/org_eclipse_uprotocol/uri/datamodel/uuri.py @@ -27,43 +27,100 @@ class UUri: + """ + Data representation of uProtocol URI.
This class will be used to represent the source and sink ( + destination) parts of the Packet, for example in a CloudEvent Packet.
UUri is used as a method to uniquely + identify devices, services, and resources on the network.
Where software is deployed, what the service is + called along with a version and the resources in the service. Defining a common URI for the system allows + applications and/or services to publish and discover each other as well as maintain a database/repository of + microservices in the various vehicles.
Example for long format serialization: +
//<device>.<domain>/<service>/<version>/<resource>#<message>
+ """ EMPTY = None def __init__(self, authority: UAuthority, entity: UEntity, resource: UResource): + """ + Create a full URI.

+ @param authority:The uAuthority represents the deployment location of a specific Software Entity . + @param entity:The uEntity in the role of a service or in the role of an application is the software and version. + @param resource:The uResource is something that is manipulated by a service such as a Door. + """ self.authority = authority if authority else UAuthority.empty() self.entity = entity if entity else UEntity.empty() self.resource = resource if resource else UResource.empty() @staticmethod def rpc_response(authority: UAuthority, entity: UEntity): + """ + Create an RPC Response UUri passing the Authority and Entity information.

+ @param authority:The uAuthority represents the deployment location of a specific Software Entity. + @param entity:The SW entity information. + @return:Returns a UUri of a constructed RPC Response. + """ return UUri(authority, entity, UResource.for_rpc_response()) @staticmethod def empty(): + """ + Static factory method for creating an empty uri, to avoid working with null

+ @return:Returns an empty uri to avoid working with null. + """ if UUri.EMPTY is None: UUri.EMPTY = UUri(UAuthority.empty(), UEntity.empty(), UResource.empty()) return UUri.EMPTY def is_empty(self): + """ + Indicates that this URI is an empty container and has no valuable information in building uProtocol sinks or + sources.

+ @return:Returns true if this URI is an empty container and has no valuable information in building uProtocol + sinks or sources. + """ return self.authority.is_empty() and self.entity.is_empty() and self.resource.is_empty() def is_resolved(self) -> bool: + """ + Returns true if URI contains both names and numeric representations of the names inside its belly.
Meaning + that this UUri can be serialized to long or micro formats.

+ @return:Returns true if URI contains both names and numeric representations of the names inside its + belly.Meaning that this UUri can be serialized to long or micro formats. + """ return self.authority.is_resolved() and self.entity.is_resolved() and self.resource.is_resolved() def is_long_form(self) -> bool: + """ + Determines if this UUri can be serialized into a long form UUri.

+ @return:Returns true if this UUri can be serialized into a long form UUri. + """ return self.authority.is_long_form() and (self.entity.is_long_form() or self.entity.is_empty()) and ( self.resource.is_long_form() or self.resource.is_empty()) def is_micro_form(self) -> bool: + """ + Determines if this UUri can be serialized into a micro form UUri.

+ @return:Returns true if this UUri can be serialized into a micro form UUri. + """ return self.authority.is_micro_form() and self.entity.is_micro_form() and self.resource.is_micro_form() def get_u_authority(self) -> UAuthority: + """ + + @return: Returns the Authority represents the deployment location of a specific Software Entity. + """ return self.authority def get_u_entity(self) -> UEntity: + """ + + @return:Returns the USE in the role of a service or in the role of an application. + """ return self.entity def get_u_resource(self) -> UResource: + """ + + @return: Returns the resource, something that is manipulated by a service such as a Door. + """ return self.resource def __eq__(self, other): diff --git a/org_eclipse_uprotocol/uri/serializer/longuriserializer.py b/org_eclipse_uprotocol/uri/serializer/longuriserializer.py index 968347a..3e088ba 100644 --- a/org_eclipse_uprotocol/uri/serializer/longuriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/longuriserializer.py @@ -31,14 +31,19 @@ class LongUriSerializer(UriSerializer): - # def __init__(self): - # pass - # - # @staticmethod - # def instance(): - # return LongUriSerializer() + """ + UUri Serializer that serializes a UUri to a long format string per + + https://github.com/eclipse-uprotocol/uprotocol-spec/blob/main/basics/uri.adoc + """ def serialize(self, uri: UUri) -> str: + """ + Support for serializing {@link 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. + """ if uri is None or uri.is_empty(): return "" @@ -75,6 +80,11 @@ def build_resource_part_of_uri(res: UResource) -> str: @staticmethod def build_software_entity_part_of_uri(entity: UEntity) -> str: + """ + Create the service part of the uProtocol URI from an UEntity object.

+ @param entity:Software Entity representing a service or an application. + @return: Returns the String representation of the UEntity in the uProtocol URI. + """ sb = [entity.get_name().strip(), "/"] if entity.get_version(): @@ -84,6 +94,11 @@ def build_software_entity_part_of_uri(entity: UEntity) -> str: @staticmethod def build_authority_part_of_uri(authority: UAuthority) -> str: + """ + Create the authority part of the uProtocol URI from an UAuthority object.

+ @param authority:represents the deployment location of a specific Software Entity in the Ultiverse. + @return:Returns the String representation of the Authority in the uProtocol URI. + """ if authority.is_local(): return "/" @@ -102,6 +117,11 @@ def build_authority_part_of_uri(authority: UAuthority) -> str: return "".join(partial_uri) def deserialize(self, u_protocol_uri: str) -> UUri: + """ + Deserialize a String into a UUri object.

+ @param u_protocol_uri:A long format uProtocol URI. + @return:Returns an UUri data object. + """ if u_protocol_uri is None or u_protocol_uri.strip() == "": return UUri.empty() @@ -148,6 +168,11 @@ def deserialize(self, u_protocol_uri: str) -> UUri: @staticmethod def parse_from_string(resource_string: str) -> UResource: + """ + Static factory method for creating a UResource using a string that contains name + instance + message.

+ @param resource_string:String that contains the UResource information. + @return:Returns a UResource object. + """ if resource_string is None: raise ValueError("Resource must have a command name.") diff --git a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py index 6abecf2..593e105 100644 --- a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py @@ -35,6 +35,9 @@ class AddressType(Enum): + """ + The type of address used for Micro URI. + """ LOCAL = 0 IPv4 = 1 IPv6 = 2 @@ -51,12 +54,22 @@ def from_value(cls, value): class MicroUriSerializer(UriSerializer): + """ + UUri Serializer that serializes a UUri to a byte[] (micro format) per + https://github.com/eclipse-uprotocol/uprotocol-spec/blob/main/basics/uri.adoc + """ LOCAL_MICRO_URI_LENGTH = 8 IPV4_MICRO_URI_LENGTH = 12 IPV6_MICRO_URI_LENGTH = 24 UP_VERSION = 0x1 def serialize(self, uri: UUri) -> bytes: + """ + Serialize a UUri into a byte[] following the Micro-URI specifications.

+ @param uri:The UUri data object. + @return:Returns a byte[] representing the serialized UUri. + """ if uri is None or uri.is_empty() or not uri.is_micro_form(): return bytearray() @@ -93,6 +106,11 @@ def calculate_uauthority_bytes(self, uauthority: UAuthority) -> Optional[bytes]: return maybe_address.packed if maybe_address else None def deserialize(self, micro_uri: bytes) -> UUri: + """ + Deserialize a byte[] into a UUri object.

+ @param micro_uri:A byte[] uProtocol micro URI. + @return:Returns an UUri data object from the serialized format of a microUri. + """ if micro_uri is None or len(micro_uri) < self.LOCAL_MICRO_URI_LENGTH: return UUri.empty() @@ -115,7 +133,8 @@ def deserialize(self, micro_uri: bytes) -> UUri: else: try: inet_address = socket.inet_ntop(socket.AF_INET, micro_uri[ - index:index + 4]) if address_type == AddressType.IPv4 else socket.inet_ntop( + index:index + 4]) if address_type == AddressType.IPv4\ + else socket.inet_ntop( socket.AF_INET6, micro_uri[index:index + 16]) u_authority = UAuthority.micro_remote(ipaddress.ip_address(inet_address)) except: diff --git a/org_eclipse_uprotocol/uri/serializer/uriserializer.py b/org_eclipse_uprotocol/uri/serializer/uriserializer.py index 52dd5ff..bc15ba7 100644 --- a/org_eclipse_uprotocol/uri/serializer/uriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/uriserializer.py @@ -32,15 +32,38 @@ class UriSerializer(ABC): + """ + UUris are used in transport layers and hence need to be serialized.
Each transport supports different + serialization formats.
For more information, please refer to + https://github.com/eclipse-uprotocol/uprotocol-spec/blob/main/basics/uri.adoc + """ + @abstractmethod def deserialize(self, uri: T) -> UUri: + """ + Deserialize from the format to a UUri.

+ @param uri:serialized UUri. + @return:Returns a UUri object from the serialized format from the wire. + """ pass @abstractmethod def serialize(self, uri: UUri) -> T: + """ + Serialize from a UUri to a specific serialization format.

+ @param uri:UUri object to be serialized to the format T. + @return:Returns the UUri in the transport serialized format. + """ pass def build_resolved(self, long_uri: str, micro_uri: bytes) -> Optional[UUri]: + """ + Build a fully resolved {@link 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. + """ if (not long_uri or long_uri.isspace()) and (not micro_uri or len(micro_uri) == 0): return Optional.of(UUri.empty()) from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer diff --git a/org_eclipse_uprotocol/uri/validator/urivalidator.py b/org_eclipse_uprotocol/uri/validator/urivalidator.py index 519056b..1267c34 100644 --- a/org_eclipse_uprotocol/uri/validator/urivalidator.py +++ b/org_eclipse_uprotocol/uri/validator/urivalidator.py @@ -27,8 +27,17 @@ class UriValidator: + """ + Class for validating Uris. + """ + @staticmethod def validate(uri: UUri) -> UStatus: + """ + Validate a UUri to ensure that it has at least a name for the uEntity.

+ @param uri:UUri to validate. + @return:Returns UStatus containing a success or a failure with the error message. + """ if uri.is_empty(): return UStatus.failed_with_msg_and_code("Uri is empty.", Code.INVALID_ARGUMENT) @@ -39,6 +48,12 @@ def validate(uri: UUri) -> UStatus: @staticmethod def validate_rpc_method(uri: UUri) -> UStatus: + """ + Validate a UUri that is meant to be used as an RPC method URI. Used in Request sink values and Response + source values.

+ @param uri:UUri to validate. + @return:Returns UStatus containing a success or a failure with the error message. + """ status = UriValidator.validate(uri) if status.isFailed(): return status @@ -53,6 +68,12 @@ def validate_rpc_method(uri: UUri) -> UStatus: @staticmethod def validate_rpc_response(uri: UUri) -> UStatus: + """ + Validate a UUri that is meant to be used as an RPC response URI. Used in Request source values and + Response sink values.

+ @param uri:UUri to validate. + @return:Returns UStatus containing a success or a failure with the error message. + """ status = UriValidator.validate(uri) if status.isFailed(): return status diff --git a/org_eclipse_uprotocol/uuid/factory/uuidutils.py b/org_eclipse_uprotocol/uuid/factory/uuidutils.py index 1b057cd..720a1d4 100644 --- a/org_eclipse_uprotocol/uuid/factory/uuidutils.py +++ b/org_eclipse_uprotocol/uuid/factory/uuidutils.py @@ -29,13 +29,21 @@ class Version(Enum): - VERSION_UNKNOWN = 0 - VERSION_RANDOM_BASED = 4 - VERSION_TIME_ORDERED = 6 - VERSION_UPROTOCOL = 8 + """ + UUID Version + """ + VERSION_UNKNOWN = 0 # An unknown version. + VERSION_RANDOM_BASED = 4 # The randomly or pseudo-randomly generated version specified in RFC-4122. + VERSION_TIME_ORDERED = 6 # The time-ordered version with gregorian epoch proposed by Peabody and Davis. + VERSION_UPROTOCOL = 8 # The custom or free-form version proposed by Peabody and Davis. @staticmethod def getVersion(value): + """ + Get the Version from the passed integer representation of the version.

+ @param value:The integer representation of the version. + @return:The Version object or Optional.empty() if the value is not a valid version. + """ for version in Version: if version.value == value: return version @@ -43,12 +51,26 @@ def getVersion(value): class UUIDUtils: + """ + UUID Utils class that provides utility methods for uProtocol IDs + """ + @staticmethod def toString(uuid_obj: UUID) -> str: + """ + Convert the UUID to a String. + @param uuid_obj:UUID object + @return:String representation of the UUID or Optional.empty() if the UUID is null. + """ return str(uuid_obj) if uuid_obj is not None else None @staticmethod def toBytes(uuid_obj: UUID) -> Optional[bytes]: + """ + Convert the UUID to byte array.

+ @param uuid_obj:UUID object + @return:The byte array or Optional.empty() if the UUID is null. + """ if uuid_obj is None: return None uuid_bytes = uuid_obj.bytes @@ -56,6 +78,12 @@ def toBytes(uuid_obj: UUID) -> Optional[bytes]: @staticmethod def fromBytes(bytes_list: bytes) -> Optional[UUID]: + """ + Convert the byte array to a UUID.

+ @param bytes_list:The UUID in bytes format. + @return:UUIDv8 object built from the byte array or Optional.empty() if the byte array is null or not 16 bytes + long. + """ if bytes_list is None or len(bytes_list) != 16: return None uuid_bytes = bytes(bytes_list) @@ -63,6 +91,12 @@ def fromBytes(bytes_list: bytes) -> Optional[UUID]: @staticmethod def fromString(string: str) -> Optional[UUID]: + """ + Create a UUID from the passed string.

+ @param string:the string representation of the uuid. + @return:The UUID object representation of the string or Optional.empty() if the string is null, empty, + or invalid. + """ try: return uuid.UUID(string) except ValueError: @@ -70,22 +104,42 @@ def fromString(string: str) -> Optional[UUID]: @staticmethod def getVersion(uuid_obj: UUID) -> Optional[Version]: + """ + Fetch the UUID version.

+ @param uuid_obj:The UUID to fetch the version from. + @return: Return the UUID version from the UUID object or Optional.empty() if the uuid is null. + """ if uuid_obj is None: return None return Version.getVersion(uuid_obj.version) @staticmethod def getVariant(uuid_obj: UUID) -> Optional[str]: + """ + Fetch the Variant from the passed UUID.

+ @param uuid_obj:The UUID to fetch the variant from. + @return:UUID variant or Empty if uuid is null. + """ if uuid_obj is None: return None return uuid_obj.variant @staticmethod def isUProtocol(uuid_obj: UUID) -> bool: + """ + Verify if version is a formal UUIDv8 uProtocol ID.

+ @param uuid_obj:UUID object + @return:true if is a uProtocol UUID or false if uuid passed is null or the UUID is not uProtocol format. + """ return UUIDUtils.getVersion(uuid_obj) == Version.VERSION_UPROTOCOL if uuid_obj is not None else False @staticmethod def isUuidv6(uuid_obj: UUID) -> bool: + """ + Verify if version is UUIDv6

+ @param uuid_obj:UUID object + @return:true if is UUID version 6 or false if uuid is null or not version 6 + """ if uuid_obj is None: return False return UUIDUtils.getVersion(uuid_obj) == Version.VERSION_TIME_ORDERED and UUIDUtils.getVariant( @@ -93,10 +147,20 @@ def isUuidv6(uuid_obj: UUID) -> bool: @staticmethod def isuuid(uuid_obj: UUID) -> bool: + """ + Verify uuid is either v6 or v8

+ @param uuid_obj: UUID object + @return:true if is UUID version 6 or 8 + """ return UUIDUtils.isUProtocol(uuid_obj) or UUIDUtils.isUuidv6(uuid_obj) if uuid_obj is not None else False @staticmethod def getTime(uuid: UUID): + """ + Return the number of milliseconds since unix epoch from a passed UUID.

+ @param uuid:passed uuid to fetch the time. + @return:number of milliseconds since unix epoch or empty if uuid is null. + """ time = None version = UUIDUtils.getVersion(uuid) if uuid is None or version is None: diff --git a/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py b/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py index ea45dbe..d71527c 100644 --- a/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py +++ b/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py @@ -43,6 +43,10 @@ def failure(message): class UuidValidator: + """ + UUID Validator class that validates UUIDs + """ + @staticmethod def get_validator(uuid: UUID): if UUIDUtils.isUuidv6(uuid): From 544e0d194c52ac2868ae898f2f4ff0520fb17246 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Mon, 30 Oct 2023 14:21:13 -0400 Subject: [PATCH 14/47] Removed UUri data model and implemented uuri proto class --- .../validate/cloudeventvalidator.py | 2 +- .../{uri/datamodel => proto}/__init__.py | 0 org_eclipse_uprotocol/proto/uri_pb2.py | 34 +++ org_eclipse_uprotocol/rpc/rpcclient.py | 2 +- .../transport/datamodel/uattributes.py | 2 +- .../transport/datamodel/ulistener.py | 2 +- org_eclipse_uprotocol/transport/utransport.py | 4 +- .../uri/datamodel/uauthority.py | 212 ------------------ org_eclipse_uprotocol/uri/datamodel/uuri.py | 141 ------------ org_eclipse_uprotocol/uri/factory/__init__.py | 0 .../uri/factory/uauthority_factory.py | 140 ++++++++++++ .../uentity.py => factory/uentity_factory.py} | 166 ++++++-------- .../uresource_factory.py} | 190 ++++++---------- .../uri/factory/uuri_factory.py | 103 +++++++++ .../uri/serializer/longuriserializer.py | 78 +++---- .../uri/serializer/microuriserializer.py | 51 +++-- .../uri/serializer/uriserializer.py | 31 +-- .../uri/validator/urivalidator.py | 19 +- 18 files changed, 512 insertions(+), 665 deletions(-) rename org_eclipse_uprotocol/{uri/datamodel => proto}/__init__.py (100%) create mode 100644 org_eclipse_uprotocol/proto/uri_pb2.py delete mode 100644 org_eclipse_uprotocol/uri/datamodel/uauthority.py delete mode 100644 org_eclipse_uprotocol/uri/datamodel/uuri.py create mode 100644 org_eclipse_uprotocol/uri/factory/__init__.py create mode 100644 org_eclipse_uprotocol/uri/factory/uauthority_factory.py rename org_eclipse_uprotocol/uri/{datamodel/uentity.py => factory/uentity_factory.py} (56%) rename org_eclipse_uprotocol/uri/{datamodel/uresource.py => factory/uresource_factory.py} (57%) create mode 100644 org_eclipse_uprotocol/uri/factory/uuri_factory.py diff --git a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py index 0de61fd..479c4de 100644 --- a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py +++ b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py @@ -30,7 +30,7 @@ from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventtype import UCloudEventType from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent from org_eclipse_uprotocol.cloudevent.validate.validationresult import ValidationResult -from org_eclipse_uprotocol.uri.datamodel.uuri import UUri +from org_eclipse_uprotocol.proto.uri_pb2 import UUri from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer diff --git a/org_eclipse_uprotocol/uri/datamodel/__init__.py b/org_eclipse_uprotocol/proto/__init__.py similarity index 100% rename from org_eclipse_uprotocol/uri/datamodel/__init__.py rename to org_eclipse_uprotocol/proto/__init__.py diff --git a/org_eclipse_uprotocol/proto/uri_pb2.py b/org_eclipse_uprotocol/proto/uri_pb2.py new file mode 100644 index 0000000..6f22b89 --- /dev/null +++ b/org_eclipse_uprotocol/proto/uri_pb2.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: uri.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\turi.proto\x12\x0cuprotocol.v1\"\x85\x01\n\x04UUri\x12+\n\tauthority\x18\x01 \x01(\x0b\x32\x18.uprotocol.v1.UAuthority\x12%\n\x06\x65ntity\x18\x02 \x01(\x0b\x32\x15.uprotocol.v1.UEntity\x12)\n\x08resource\x18\x03 \x01(\x0b\x32\x17.uprotocol.v1.UResource\"S\n\nUAuthority\x12\x0f\n\x07isLocal\x18\x01 \x01(\x08\x12\x0e\n\x04name\x18\x02 \x01(\tH\x00\x12\x0c\n\x02ip\x18\x03 \x01(\x0cH\x00\x12\x0c\n\x02id\x18\x04 \x01(\x0cH\x00\x42\x08\n\x06remote\"\x8b\x01\n\x07UEntity\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x02id\x18\x02 \x01(\rH\x00\x88\x01\x01\x12\x1a\n\rversion_major\x18\x03 \x01(\rH\x01\x88\x01\x01\x12\x1a\n\rversion_minor\x18\x04 \x01(\rH\x02\x88\x01\x01\x42\x05\n\x03_idB\x10\n\x0e_version_majorB\x10\n\x0e_version_minor\"w\n\tUResource\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x15\n\x08instance\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07message\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x0f\n\x02id\x18\x04 \x01(\rH\x02\x88\x01\x01\x42\x0b\n\t_instanceB\n\n\x08_messageB\x05\n\x03_id\"-\n\tUUriBatch\x12 \n\x04uris\x18\x01 \x03(\x0b\x32\x12.uprotocol.v1.UUriB\'\n\x18org.eclipse.uprotocol.v1B\tUUriProtoP\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'uri_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + _globals['DESCRIPTOR']._options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\030org.eclipse.uprotocol.v1B\tUUriProtoP\001' + _globals['_UURI']._serialized_start=28 + _globals['_UURI']._serialized_end=161 + _globals['_UAUTHORITY']._serialized_start=163 + _globals['_UAUTHORITY']._serialized_end=246 + _globals['_UENTITY']._serialized_start=249 + _globals['_UENTITY']._serialized_end=388 + _globals['_URESOURCE']._serialized_start=390 + _globals['_URESOURCE']._serialized_end=509 + _globals['_UURIBATCH']._serialized_start=511 + _globals['_UURIBATCH']._serialized_end=556 +# @@protoc_insertion_point(module_scope) diff --git a/org_eclipse_uprotocol/rpc/rpcclient.py b/org_eclipse_uprotocol/rpc/rpcclient.py index 4a3c79e..acf97d3 100644 --- a/org_eclipse_uprotocol/rpc/rpcclient.py +++ b/org_eclipse_uprotocol/rpc/rpcclient.py @@ -26,7 +26,7 @@ from org_eclipse_uprotocol.transport.datamodel.uattributes import UAttributes from org_eclipse_uprotocol.transport.datamodel.upayload import UPayload -from org_eclipse_uprotocol.uri.datamodel.uuri import UUri +from org_eclipse_uprotocol.proto.uri_pb2 import UUri class RpcClient(ABC): diff --git a/org_eclipse_uprotocol/transport/datamodel/uattributes.py b/org_eclipse_uprotocol/transport/datamodel/uattributes.py index 94f1fa6..7699d75 100644 --- a/org_eclipse_uprotocol/transport/datamodel/uattributes.py +++ b/org_eclipse_uprotocol/transport/datamodel/uattributes.py @@ -22,7 +22,7 @@ from org_eclipse_uprotocol.transport.datamodel.umessagetype import UMessageType from org_eclipse_uprotocol.transport.datamodel.upriority import UPriority -from org_eclipse_uprotocol.uri.datamodel.uuri import UUri +from org_eclipse_uprotocol.proto.uri_pb2 import UUri from org_eclipse_uprotocol.uuid.factory import UUID diff --git a/org_eclipse_uprotocol/transport/datamodel/ulistener.py b/org_eclipse_uprotocol/transport/datamodel/ulistener.py index 67c62d5..2bc6189 100644 --- a/org_eclipse_uprotocol/transport/datamodel/ulistener.py +++ b/org_eclipse_uprotocol/transport/datamodel/ulistener.py @@ -25,7 +25,7 @@ from org_eclipse_uprotocol.transport.datamodel.uattributes import UAttributes from org_eclipse_uprotocol.transport.datamodel.upayload import UPayload from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus -from org_eclipse_uprotocol.uri.datamodel.uuri import UUri +from org_eclipse_uprotocol.proto.uri_pb2 import UUri class UListener(ABC): diff --git a/org_eclipse_uprotocol/transport/utransport.py b/org_eclipse_uprotocol/transport/utransport.py index 0ccacbf..50b6508 100644 --- a/org_eclipse_uprotocol/transport/utransport.py +++ b/org_eclipse_uprotocol/transport/utransport.py @@ -27,8 +27,8 @@ from org_eclipse_uprotocol.transport.datamodel.ulistener import UListener from org_eclipse_uprotocol.transport.datamodel.upayload import UPayload from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus -from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity -from org_eclipse_uprotocol.uri.datamodel.uuri import UUri +from org_eclipse_uprotocol.proto.uri_pb2 import UEntity +from org_eclipse_uprotocol.proto.uri_pb2 import UUri class UTransport(ABC): diff --git a/org_eclipse_uprotocol/uri/datamodel/uauthority.py b/org_eclipse_uprotocol/uri/datamodel/uauthority.py deleted file mode 100644 index 646b38f..0000000 --- a/org_eclipse_uprotocol/uri/datamodel/uauthority.py +++ /dev/null @@ -1,212 +0,0 @@ -# ------------------------------------------------------------------------- - -# 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. - -# ------------------------------------------------------------------------- - -import ipaddress -from typing import Optional - - -class UAuthority: - """ - An Authority represents the deployment location of a specific Software Entity.
Data representation of an - Authority.
An Authority consists of a device, a domain and a micro version in the form of an IP - Address.
Device and domain names are used as part of the URI for device and service discovery. Optimized micro - versions of the UUri will use the IP Address.
Devices will be grouped together into realms of Zone of - Authority.
- """ - EMPTY = None - - def __init__(self, device: str, domain: str, address: ipaddress.IPv4Address, marked_remote: bool, - marked_resolved: bool): - """ - Constructor for building a UAuthority.

- @param device: The device a software entity is deployed on, such as the VCU, CCU or cloud provider. A device - is a logical independent representation of a service bus in different execution environments. Devices will - be grouped together into realms of Zone of Authority. - @param domain: The domain a software entity is deployed on, such as vehicle or Cloud (PaaS). - @param address:The device IP address of the device. - @param marked_remote:Indicates if this UAuthority was implicitly marked as remote. - @param marked_resolved:Indicates that this uAuthority has all the information needed to be serialised in the - long format or the micro format of a UUri. - """ - self.device = device.lower() if device else None - self.domain = domain.lower() if domain else None - self.address = address - self.marked_remote = marked_remote - self.marked_resolved = marked_resolved - - @staticmethod - def local(): - """ - Static factory method for creating a local uAuthority.
A local uri does not contain an authority and looks - like this:
 :<service>/<version>/<resource>#<Message> 

- @return:Returns a local uAuthority that has no domain, device, or ip address information, indicating to - uProtocol that the uAuthority part in the UUri is relative to the sender/receiver deployment environment. - """ - return UAuthority.EMPTY - - @staticmethod - def long_remote(device: str, domain: str): - """ - Static factory method for creating a remote authority supporting the long serialization information - representation of a UUri.
Building a UAuthority with this method will create an unresolved uAuthority - that can only be serialised in long UUri format. An uri with a long representation of uAUthority can be - serialised as follows: -
 //<device>.<domain>/<service>/<version>/<resource>#<Message>
-         
- @param device:The device a software entity is deployed on, such as the VCU, CCU or cloud provider. - @param domain:The domain a software entity is deployed on, such as vehicle or Cloud (PaaS). Vehicle Domain - name MUST be that of the vehicle VIN. - @return:Returns a uAuthority that contains the device and the domain and can only be serialized in long UUri - format. - """ - return UAuthority(device, domain, None, True, False) - - @staticmethod - def micro_remote(address: ipaddress.IPv4Address): - """ - Static factory method for creating a remote authority supporting the micro serialization information - representation of a UUri.
Building a UAuthority with this method will create an unresolved uAuthority that - can only be serialised in micro UUri format.

- @param address:The ip address of the device a software entity is deployed on. - @return:Returns a uAuthority that contains only the internet address of the device, and can only be - serialized in micro UUri format. - """ - return UAuthority(None, None, address, True, False) - - @staticmethod - def resolved_remote(device: str, domain: str, address: ipaddress.IPv4Address): - """ - Static factory method for creating a remote authority that is completely resolved with name, device and ip - address of the device.
Building a UAuthority with this method will enable serialisation in both UUri - formats, long UUri format and micro UUri format. Note that in the case of missing data, this will not fail, - but simply create a UAuthority that is not resolved.

- @param device:The device name for long serialization of UUri. - @param domain:The domain name for long serialization of UUri. - @param address:The IP address for the device, for micro serialization of UUri. - @return:Returns a uAuthority that contains all resolved data and so can be serialized into a long UUri or a - micro UUri. - """ - resolved = device is not None and device.strip() != "" and address is not None - return UAuthority(device, domain, address, True, resolved) - - @staticmethod - def empty(): - """ - Static factory method for creating an empty uAuthority, to avoid working with null
Empty uAuthority is - still serializable in both long and micro UUri formats, and will be as local to the current deployment - environment.

- @return:Returns an empty authority that has no domain, device, or device ip address information. - """ - return UAuthority.EMPTY - - def is_remote(self) -> bool: - """ - Support for determining if this uAuthority defines a deployment that is defined as remote.

- @return:Returns true if this uAuthority is remote, meaning it contains information for serialising a long - UUri or a micro UUri. - """ - return self.is_marked_Remote() - - # - def is_local(self) -> bool: - """ - Support for determining if this uAuthority is defined for a local deployment.

- @return:Returns true if this uAuthority is local, meaning does not contain a device/domain for long UUri or - information for micro UUri. - """ - return self.is_empty() and not self.is_marked_Remote() - - def device(self) -> Optional[str]: - """ - Accessing an optional device of the uAuthority.

- @return:Returns the device a software entity is deployed on, such as the VCU, CCU or cloud provider. - """ - return self.device if self.device else None - - def domain(self) -> Optional[str]: - """ - Accessing an optional domain of the uAuthority.

- @return:Returns the domain a software entity is deployed on, such as vehicle or backoffice. - """ - return self.domain if self.domain else None - - def address(self) -> Optional[ipaddress.IPv4Address]: - """ - Accessing an optional IP address configuration of the uAuthority.

- @return:eturns the device IP address. - """ - try: - return ipaddress.IPv4Address(self.address) - except ValueError: - return None - - def is_marked_remote(self) -> bool: - """ - Support for determining if this uAuthority was configured to be remote.

- @return:Returns true if this uAuthority is explicitly configured remote deployment. - """ - return self.marked_remote - - def is_resolved(self) -> bool: - """ - Indicates that this UAuthority has already been resolved.
A resolved UAuthority means that it has all the - information needed to be serialised in the long format or the micro format of a UUri.

- @return:Returns true if this UAuthority is resolved with all the information needed to be serialised in the - long format or the micro format of a UUri. - """ - return self.marked_resolved - - def is_long_form(self) -> bool: - """ - Determine if the UAuthority can be used to serialize a UUri in long format.

- @return:Returns true if the UAuthority can be used to serialize a UUri in long format. - """ - return self.is_local() or self.device is not None - - def is_micro_form(self) -> bool: - """ - Determine if the UAuthority can be used to serialize a UUri in micro format.

- @return:Returns true if the uAuthority can be serialized a UUri in micro format. - """ - return self.is_local() or self.address is not None - - def is_empty(self) -> bool: - return all([self.domain is None or self.domain.strip() == "", self.device is None or self.device.strip() == "", - self.address is None]) - - def __eq__(self, other): - if not isinstance(other, UAuthority): - return False - return ( - self.marked_remote == other.marked_remote and self.device == other.device and self.domain == - other.domain) - - def __hash__(self): - return hash((self.device, self.domain, self.marked_remote)) - - def __str__(self): - return f"UAuthority{{device='{self.device}', domain='{self.domain}', markedRemote={self.marked_remote}}}" - - -# Initialize EMPTY -UAuthority.EMPTY = UAuthority(None, None, None, False, True) diff --git a/org_eclipse_uprotocol/uri/datamodel/uuri.py b/org_eclipse_uprotocol/uri/datamodel/uuri.py deleted file mode 100644 index 3a72699..0000000 --- a/org_eclipse_uprotocol/uri/datamodel/uuri.py +++ /dev/null @@ -1,141 +0,0 @@ -# ------------------------------------------------------------------------- - -# 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. - -# ------------------------------------------------------------------------- - -from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority -from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity -from org_eclipse_uprotocol.uri.datamodel.uresource import UResource - - -class UUri: - """ - Data representation of uProtocol URI.
This class will be used to represent the source and sink ( - destination) parts of the Packet, for example in a CloudEvent Packet.
UUri is used as a method to uniquely - identify devices, services, and resources on the network.
Where software is deployed, what the service is - called along with a version and the resources in the service. Defining a common URI for the system allows - applications and/or services to publish and discover each other as well as maintain a database/repository of - microservices in the various vehicles.
Example for long format serialization: -
//<device>.<domain>/<service>/<version>/<resource>#<message>
- """ - EMPTY = None - - def __init__(self, authority: UAuthority, entity: UEntity, resource: UResource): - """ - Create a full URI.

- @param authority:The uAuthority represents the deployment location of a specific Software Entity . - @param entity:The uEntity in the role of a service or in the role of an application is the software and version. - @param resource:The uResource is something that is manipulated by a service such as a Door. - """ - self.authority = authority if authority else UAuthority.empty() - self.entity = entity if entity else UEntity.empty() - self.resource = resource if resource else UResource.empty() - - @staticmethod - def rpc_response(authority: UAuthority, entity: UEntity): - """ - Create an RPC Response UUri passing the Authority and Entity information.

- @param authority:The uAuthority represents the deployment location of a specific Software Entity. - @param entity:The SW entity information. - @return:Returns a UUri of a constructed RPC Response. - """ - return UUri(authority, entity, UResource.for_rpc_response()) - - @staticmethod - def empty(): - """ - Static factory method for creating an empty uri, to avoid working with null

- @return:Returns an empty uri to avoid working with null. - """ - if UUri.EMPTY is None: - UUri.EMPTY = UUri(UAuthority.empty(), UEntity.empty(), UResource.empty()) - return UUri.EMPTY - - def is_empty(self): - """ - Indicates that this URI is an empty container and has no valuable information in building uProtocol sinks or - sources.

- @return:Returns true if this URI is an empty container and has no valuable information in building uProtocol - sinks or sources. - """ - return self.authority.is_empty() and self.entity.is_empty() and self.resource.is_empty() - - def is_resolved(self) -> bool: - """ - Returns true if URI contains both names and numeric representations of the names inside its belly.
Meaning - that this UUri can be serialized to long or micro formats.

- @return:Returns true if URI contains both names and numeric representations of the names inside its - belly.Meaning that this UUri can be serialized to long or micro formats. - """ - return self.authority.is_resolved() and self.entity.is_resolved() and self.resource.is_resolved() - - def is_long_form(self) -> bool: - """ - Determines if this UUri can be serialized into a long form UUri.

- @return:Returns true if this UUri can be serialized into a long form UUri. - """ - return self.authority.is_long_form() and (self.entity.is_long_form() or self.entity.is_empty()) and ( - self.resource.is_long_form() or self.resource.is_empty()) - - def is_micro_form(self) -> bool: - """ - Determines if this UUri can be serialized into a micro form UUri.

- @return:Returns true if this UUri can be serialized into a micro form UUri. - """ - return self.authority.is_micro_form() and self.entity.is_micro_form() and self.resource.is_micro_form() - - def get_u_authority(self) -> UAuthority: - """ - - @return: Returns the Authority represents the deployment location of a specific Software Entity. - """ - return self.authority - - def get_u_entity(self) -> UEntity: - """ - - @return:Returns the USE in the role of a service or in the role of an application. - """ - return self.entity - - def get_u_resource(self) -> UResource: - """ - - @return: Returns the resource, something that is manipulated by a service such as a Door. - """ - return self.resource - - def __eq__(self, other): - if self is other: - return True - if not isinstance(other, UUri): - return False - return self.authority == other.authority and self.entity == other.entity and self.resource == other.resource - - def __hash__(self): - return hash((self.authority, self.entity, self.resource)) - - def __str__(self): - return f"UUri{{uAuthority={self.authority}, uEntity={self.entity}, uResource={self.resource}}}" - - -# Initialize EMPTY -UUri.EMPTY = UUri(UAuthority.empty(), UEntity.empty(), UResource.empty()) diff --git a/org_eclipse_uprotocol/uri/factory/__init__.py b/org_eclipse_uprotocol/uri/factory/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/uri/factory/uauthority_factory.py b/org_eclipse_uprotocol/uri/factory/uauthority_factory.py new file mode 100644 index 0000000..2fffbb0 --- /dev/null +++ b/org_eclipse_uprotocol/uri/factory/uauthority_factory.py @@ -0,0 +1,140 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- + +import ipaddress +from typing import Optional +from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority + + +class UAuthorityFactory: + """ + A factory is a part of the software that has methods to generate concrete objects, usually of the same type.
+ An Authority represents the deployment location of a specific Software Entity.
Data representation of an + Authority.
An Authority consists of a device, a domain and a micro version in the form of an IP + Address.
Device and domain names are used as part of the URI for device and service discovery. Optimized micro + versions of the UUri will use the IP Address.
Devices will be grouped together into realms of Zone of + Authority.
The UAuthority Factory knows how to generate UAuthority proto message.
+ """ + + @staticmethod + def local() -> UAuthority: + """ + Static factory method for creating a local uAuthority.
A local uri does not contain an authority and looks + like this:
 :<service>/<version>/<resource>#<Message> 

+ @return:Returns a local uAuthority that has no domain, device, or ip address information, indicating to + uProtocol that the uAuthority part in the UUri is relative to the sender/receiver deployment environment. + """ + return UAuthority(isLocal=True) + + @staticmethod + def long_remote(name: str) -> UAuthority: + """ + Static factory method for creating a remote authority supporting the long serialization information + representation of a UUri.
Building a UAuthority with this method will create an unresolved uAuthority + that can only be serialised in long UUri format. An uri with a long representation of uAUthority can be + serialised as follows: +
 //<device>.<domain>/<service>/<version>/<resource>#<Message>
+         
+ @param name:domain & device name as a string. + @return:Returns a uAuthority that contains the device and the domain and can only be serialized in long UUri + format. + """ + return UAuthority(name=name, isLocal=False) + + @staticmethod + def micro_remote(ip: bytes) -> UAuthority: + """ + Static factory method for creating a remote authority supporting the micro serialization information + representation of a UUri.
Building a UAuthority with this method will create an unresolved uAuthority that + can only be serialised in micro UUri format.

+ @param ip:The ip address of the device a software entity is deployed on in bytes + @return:Returns a uAuthority that contains only the internet address of the device, and can only be + serialized in micro UUri format. + """ + return UAuthority(ip=ip, isLocal=False) + + @staticmethod + def empty() -> UAuthority: + """ + Static factory method for creating an empty uAuthority, to avoid working with null
Empty uAuthority is + still serializable in both long and micro UUri formats, and will be as local to the current deployment + environment.

+ @return:Returns an empty authority that has no domain, device, or device ip address information. + """ + return UAuthority() + + @staticmethod + def is_remote(uauthority: UAuthority) -> bool: + """ + Support for determining if this uAuthority defines a deployment that is defined as remote.

+ @param uauthority: uAuthority protobuf message + @return:Returns true if this uAuthority is remote, meaning it contains information for serialising a long + UUri or a micro UUri. + """ + return not uauthority.isLocal + + @staticmethod + def is_local(uauthority: UAuthority) -> bool: + """ + Support for determining if this uAuthority is defined for a local deployment.

+ @param uauthority: uAuthority protobuf message + @return:Returns true if this uAuthority is local, meaning does not contain a device/domain for long UUri or + information for micro UUri. + """ + return UAuthorityFactory.is_empty(uauthority) and uauthority.isLocal + + @staticmethod + def is_long_form(uauthority: UAuthority) -> bool: + """ + Determine if the UAuthority can be used to serialize a UUri in long format.

+ @param uauthority: uAuthority protobuf message + @return:Returns true if the UAuthority can be used to serialize a UUri in long format. + """ + return uauthority.isLocal or not uauthority.name.strip() == "" + + @staticmethod + def is_micro_form(uauthority: UAuthority) -> bool: + """ + Determine if the UAuthority can be used to serialize a UUri in micro format.

+ @param uauthority: uAuthority protobuf message + @return:Returns true if the uAuthority can be serialized a UUri in micro format. + """ + return uauthority.isLocal or len(uauthority.ip) != 0 + + @staticmethod + def is_empty(uauthority: UAuthority) -> bool: + return all([uauthority.name.strip() == "", len(uauthority.ip) == 0]) + + @staticmethod + def resolved_remote(name: str, ip: bytes): + """ + Static factory method for creating a remote authority that is completely resolved with name, device and ip + address of the device.
Building a UAuthority with this method will enable serialisation in both UUri + formats, long UUri format and micro UUri format. Note that in the case of missing data, this will not fail, + but simply create a UAuthority that is not resolved.

+ @param name:The domain and device name as a string for long serialization of UUri. + @param ip:The IP address for the device in bytes, for micro serialization of UUri. + @return:Returns a uAuthority that contains all resolved data and so can be serialized into a long UUri or a + micro UUri. + """ + return UAuthority(name=name if name else None, ip=ip if ip else None) diff --git a/org_eclipse_uprotocol/uri/datamodel/uentity.py b/org_eclipse_uprotocol/uri/factory/uentity_factory.py similarity index 56% rename from org_eclipse_uprotocol/uri/datamodel/uentity.py rename to org_eclipse_uprotocol/uri/factory/uentity_factory.py index 6408d61..ec87d73 100644 --- a/org_eclipse_uprotocol/uri/datamodel/uentity.py +++ b/org_eclipse_uprotocol/uri/factory/uentity_factory.py @@ -20,62 +20,41 @@ # under the License. # ------------------------------------------------------------------------- -class UEntity: + + +from org_eclipse_uprotocol.proto.uri_pb2 import UEntity + + +class UEntityFactory: """ + A factory is a part of the software that has methods to generate concrete objects, usually of the same type.
Data representation of an Software Entity - uE
Software entities are distinguished by using a unique name or a unique id along with the specific version of the software.
An Software Entity is a piece of software deployed somewhere on a uDevice.
The Software Entity is used in the source and sink parts of communicating software.
A uE that publishes events is a Service role.
A uE that consumes events is an - Application role. + Application role.
The UEntity Factory knows how to generate UEntity proto message.
""" - EMPTY = None - - def __init__(self, name: str, version: int, identifier: int, marked_resolved: bool): - """ - Build a Software Entity that represents a communicating piece of software.

- @param name:The name of the software such as petapp or body.access. - @param version:The software version. If not supplied, the latest version of the service will be used. - @param identifier:A numeric identifier for the software entity which is a one-to-one correspondence with the - software name. - @param marked_resolved:Indicates that this uResource was populated with intent of having all data. - """ - if name is None: - raise ValueError("Software Entity must have a name") - self.name = name - self.version = version - self.id = identifier - self.marked_resolved = marked_resolved @staticmethod - def resolved_format(name: str, version: int, identifier: int): - """ - Create a complete UEntity with all the information so that it can be used in long form UUri serialisation and - micro form UUri serialisation.
In the case of missing elements such as name or id, the UEntity will not be - marked as resolvable and will not be usable in serialisation formats.

- @param name:The name of the software such as petapp or body.access. - @param version:The software version. If not supplied, the latest version of the service will be used. - @param identifier:A numeric identifier for the software entity which is a one-to-one correspondence with the - software name. - @return:Returns a complete UEntity with all the information so that it can be used in long form UUri - serialisation and micro form UUri serialisation. - """ - resolved = name and name.strip() and identifier is not None - return UEntity(name if name else "", version, identifier, resolved) - - @staticmethod - def long_format(name: str, version: int): + def long_format(name: str, version_minor: int = 0, version_major: int = 0) -> UEntity: """ Static factory method for creating a uE using the software entity name, that can be used in long form UUri serialisation.

@param name:The software entity name, such as petapp or body.access. - @param version:The software entity version. + @param version_minor:minor version of the uEntity + @param version_major:major version of the uEntity @return:Returns an UEntity with the name and the version of the service and can only be serialized to long UUri format. """ - return UEntity(name if name else "", version, None, False) + entity = UEntity(name=name if name else "") + if version_minor != 0 and version_minor is not None: + entity.version_minor = version_minor + if version_major != 0 and version_major is not None: + entity.version_major = version_major + return entity @staticmethod - def long_format_name(name: str): + def long_format_name(name: str) -> UEntity: """ Static factory method for creating a uE using the software entity name, that can be used in long form UUri serialisation.

@@ -83,10 +62,10 @@ def long_format_name(name: str): @return:Returns an UEntity with the name where the version is the latest version of the service and can only be serialized to long UUri format. """ - return UEntity(name if name else "", None, None, False) + return UEntity(name=name if name else "") @staticmethod - def micro_format(identifier: int): + def micro_format(identifier: int) -> UEntity: """ Static factory method for creating a uE using the software entity identification number, that can be used to serialize micro UUris.

@@ -95,20 +74,30 @@ def micro_format(identifier: int): @return:Returns an UEntity with the name where the version is the latest version of the service and can only be serialized to long micro UUri format. """ - return UEntity("", None, identifier, False) + entity = UEntity() + if identifier is not None or identifier != 0: + entity.id = identifier + return entity @staticmethod - def micro_format_id_version(identifier: int, version: int): + def micro_format_id_version(identifier: int, version_major: int, version_minor:int): """ Static factory method for creating a uE using the software entity identification number, that can be used to serialize micro UUris.

@param identifier:A numeric identifier for the software entity which is a one-to-one correspondence with the software name. - @param version:The software entity version. + @param version_major:major version of the uEntity @return:Returns an UEntity with the name and the version of the service and can only be serialized to micro UUri format. """ - return UEntity("", version, identifier, False) + entity=UEntity() + if identifier is not None or identifier!=0: + entity.id=identifier + if version_major is not None or version_major!=0: + entity.version_major=version_major + if version_minor is not None or version_minor!=0: + entity.version_minor=version_minor + return entity @staticmethod def empty(): @@ -116,83 +105,56 @@ def empty(): Static factory method for creating an empty software entity, to avoid working with null

@return:Returns an empty software entity that has a blank name, no unique id and no version information. """ - if UEntity.EMPTY is None: - UEntity.EMPTY = UEntity("", None, None, False) - return UEntity.EMPTY + return UEntity() - def is_empty(self) -> bool: + @staticmethod + def is_empty(uentity: UEntity) -> bool: """ Indicates that this software entity is an empty container and has no valuable information in building uProtocol sinks or sources.

+ @param uentity: UEntity protobuf message @return: Returns true if this software entity is an empty container and has no valuable information in building uProtocol sinks or sources. """ - return not (self.name or self.version or self.id) - - def is_resolved(self) -> bool: - """ - Return true if the UEntity contains both the name and IDs meaning it contains all the information to be - serialized into a long UUri or a micro form UUri.

- @return:Returns true of this resource contains resolved information meaning it contains all the information - to be serialized into a long UUri or a micro form UUri. - """ - return self.marked_resolved + return ( + uentity.name.strip() == "" and uentity.version_major == 0 and uentity.id == 0 and + uentity.version_minor == 0) - def is_long_form(self) -> bool: + @staticmethod + def is_long_form(uentity: UEntity) -> bool: """ Determine if this software entity can be serialised into a long UUri form.

+ @param uentity: UEntity protobuf message @return:Returns true if this software entity can be serialised into a long UUri form, meaning it has at least a name. """ - return bool(self.name) + return bool(uentity.name) - def is_micro_form(self) -> bool: + @staticmethod + def is_micro_form(uentity: UEntity) -> bool: """ Returns true if the Uri part contains the id's which will allow the Uri part to be serialized into micro form.

+ @param uentity: UEntity protobuf message @return:Returns true if the Uri part can be serialized into micro form, meaning is has at least a unique numeric identifier. """ - return self.id is not None - - def get_name(self) -> str: - """ - Returns the name of the software.

- @return: Returns the name of the software such as petpp or body.access. - """ - return self.name + return uentity.id != 0 - def get_version(self) -> int: - """ - Returns the software version if it exists.

- @return:Returns the software version if it exists. If the version does not exist, the latest version of the - service will be used. - """ - return self.version - - def get_id(self) -> int: + @staticmethod + def resolved_format(name: str, version_major: int, version_minor: int, identifier: int): """ - Returns the software id if it exists. The software id represents the numeric identifier of the uE.

- @return: Returns the software id if it exists. The software id represents the numeric identifier of the uE. + Create a complete UEntity with all the information so that it can be used in long form UUri serialisation and + micro form UUri serialisation.
In the case of missing elements such as name or id, the UEntity will not be + marked as resolvable and will not be usable in serialisation formats.

+ @param name:The name of the software such as petapp or body.access. + @param version_major:The software major version. If not supplied, the latest version of the service will be + used. + @param version_minor:The software minor version. + @param identifier:A numeric identifier for the software entity which is a one-to-one correspondence with the + software name. + @return:Returns a complete UEntity with all the information so that it can be used in long form UUri + serialisation and micro form UUri serialisation. """ - return self.id - - def __eq__(self, other): - if self is other: - return True - if not isinstance(other, UEntity): - return False - return ( - self.marked_resolved == other.marked_resolved and self.name == other.name and self.version == - other.version and self.id == other.id) - - def __hash__(self): - return hash((self.name, self.version, self.id, self.marked_resolved)) - - def __str__(self): - return (f"UEntity{{name='{self.name}', version={self.version}, id={self.id}, markedResolved=" - f"{self.marked_resolved}}}") - - -# Initialize EMPTY -UEntity.EMPTY = UEntity("", None, None, False) + return UEntity(name if name else "", version_major if version_major else 0, + version_minor if version_minor else 0, identifier if identifier else 0) diff --git a/org_eclipse_uprotocol/uri/datamodel/uresource.py b/org_eclipse_uprotocol/uri/factory/uresource_factory.py similarity index 57% rename from org_eclipse_uprotocol/uri/datamodel/uresource.py rename to org_eclipse_uprotocol/uri/factory/uresource_factory.py index 1e2b8dd..c90d9b0 100644 --- a/org_eclipse_uprotocol/uri/datamodel/uresource.py +++ b/org_eclipse_uprotocol/uri/factory/uresource_factory.py @@ -20,63 +20,35 @@ # under the License. # ------------------------------------------------------------------------- -class UResource: + + +from org_eclipse_uprotocol.proto.uri_pb2 import UResource + + +class UResourceFactory: """ + A factory is a part of the software that has methods to generate concrete objects, usually of the same type.
A service API - defined in the UEntity - has Resources and Methods. Both of these are represented by the UResource class.
A uResource represents a resource from a Service such as "door" and an optional specific instance such as "front_left". In addition, it can optionally contain the name of the resource Message type, such as "Door". The Message type matches the protobuf service IDL that defines structured data types.
An UResource is something that can be manipulated/controlled/exposed by a service. Resources are unique when - prepended with UAuthority that represents the device and UEntity that represents the service. + prepended with UAuthority that represents the device and UEntity that represents the service.
The UResource + Factory knows how to generate UResource proto message.
""" - EMPTY = None - RESPONSE = None - - def __init__(self, name: str, instance: str, message: str, identifier: int, marked_resolved: bool): - """ - Create a uResource. The resource is something that is manipulated by a service such as a door.

- @param name:The name of the resource as a noun such as door or window, or in the case a method that - manipulates the resource, a verb. - @param instance:An instance of a resource such as front_left. - @param message:The Message type matches the protobuf service IDL message name that defines structured data - types.A message is a data structure type used to define data that is passed in events and rpc methods. - @param identifier:The numeric representation of this uResource. - @param marked_resolved:Indicates that this uResource was populated with intent of having all data. - """ - self.name = name if name else "" - self.instance = instance - self.message = message - self.id = identifier - self.marked_resolved = marked_resolved @staticmethod - def resolved_format(name: str, instance: str, message: str, identifier: int): - """ - Build a UResource that has all elements resolved and can be serialized in a long UUri or a micro UUri.

- @param name:The name of the resource as a noun such as door or window, or in the case a method that - manipulates the resource, a verb. - @param instance:An instance of a resource such as front_left. - @param message:The Message type matches the protobuf service IDL message name that defines structured data - types.A message is a data structure type used to define data that is passed in events and rpc methods. - @param identifier:The numeric representation of this uResource. - @return:Returns a UResource that has all the information that is needed to serialize into a long UUri or a - micro UUri. - """ - resolved = name and name.strip() and identifier is not None - return UResource(name if name else "", instance, message, identifier, resolved) - - @staticmethod - def long_format(name: str): + def long_format(name: str) -> UResource: """ Build a UResource that can be serialized into a long UUri. Mostly used for publishing messages.

@param name:The name of the resource as a noun such as door or window, or in the case a method that manipulates the resource, a verb. @return:Returns a UResource that can be serialized into a long UUri. """ - return UResource(name if name else "", None, None, None, False) + return UResource(name=name if name else "") @staticmethod - def long_format_instance_message(name: str, instance: str, message: str): + def long_format_instance_message(name: str, instance: str, message: str) -> UResource: """ Build a UResource that can be serialized into a long UUri. Mostly used for publishing messages.

@param name:The name of the resource as a noun such as door or window, or in the case a method that @@ -86,7 +58,12 @@ def long_format_instance_message(name: str, instance: str, message: str): types. A message is a data structure type used to define data that is passed in events and rpc methods. @return:Returns a UResource that can be serialised into a long UUri. """ - return UResource(name if name else "", instance, message, None, False) + resource = UResource(name=name if name else "") + if instance is not None or instance != "": + resource.instance = instance + if message is not None or message != "": + resource.message = message + return resource @staticmethod def micro_format(identifier: int): @@ -95,7 +72,7 @@ def micro_format(identifier: int): @param identifier:The numeric representation of this uResource. @return:Returns a UResource that can be serialised into a micro UUri. """ - return UResource("", None, None, identifier, False) + return UResource(id=identifier if identifier else 0) @staticmethod def for_rpc_request(method_name: str): @@ -104,7 +81,7 @@ def for_rpc_request(method_name: str): @param method_name:The RPC method name. @return:Returns a UResource used for an RPC request that could be serialised in long format. """ - return UResource("rpc", method_name, None, None, False) + return UResource(name="rpc", instance=method_name if method_name else "") @staticmethod def for_rpc_request_with_id(method_id: int): @@ -113,7 +90,7 @@ def for_rpc_request_with_id(method_id: int): @param method_id:The numeric representation method name for the RPC. @return:Returns a UResource used for an RPC request that could be serialised in micro format. """ - return UResource("rpc", None, None, method_id, False) + return UResource(name="rpc", id=method_id if method_id else 0) @staticmethod def for_rpc_request_with_name_and_id(method_name: str, method_id: int): @@ -123,8 +100,12 @@ def for_rpc_request_with_name_and_id(method_name: str, method_id: int): @param method_id:The numeric representation method name for the RPC. @return:Returns a UResource used for an RPC request that could be serialised in long and micro format. """ - resolved = method_name and method_name.strip() and method_id is not None - return UResource("rpc", method_name, None, method_id, resolved) + resource = UResource(name="rpc") + if method_name is not None or method_name != "": + resource.instance = method_name + if method_id is not None or method_id != 0: + resource.id = method_id + return resource @staticmethod def for_rpc_response(): @@ -132,14 +113,16 @@ def for_rpc_response(): Static factory method for creating a response resource that is returned from RPC calls

@return:Returns a response resource used for response RPC calls. """ - return UResource.RESPONSE + return UResource(name="rpc", instance="response") - def is_rpc_method(self) -> bool: + @staticmethod + def is_rpc_method(uresource: UResource) -> bool: """ Returns true if this resource specifies an RPC method call or RPC response.

+ @param uresource: UResource protobuf message @return:Returns true if this resource specifies an RPC method call or RPC response. """ - return self.name == "rpc" + return uresource.name == "rpc" @staticmethod def empty(): @@ -147,94 +130,59 @@ def empty(): Static factory method for creating an empty resource, to avoid working with null

@return:Returns an empty resource that has a blank name and no message instance information. """ - if UResource.EMPTY is None: - UResource.EMPTY = UResource("", None, None, None, False) - return UResource.EMPTY + return UResource() - def is_empty(self) -> bool: + @staticmethod + def is_empty(uresource: UResource) -> bool: """ Indicates that this resource is an empty container and has no valuable information in building uProtocol URI.

+ @param uresource: UResource protobuf message @return:Returns true if this resource is an empty container and has no valuable information in building uProtocol URI. """ - return not (self.name and self.name.strip()) or self.name == "rpc" and not ( - self.instance or self.message or self.id) - - def get_name(self) -> str: - """ - - @return: Returns the name of the resource as a noun such as door or window, or in the case a method that - manipulates the resource, a verb. - """ - return self.name - - def get_id(self) -> int: - """ - - @return: Returns the resource id if it exists. - """ - return self.id + return uresource.name.strip() == "" or uresource.name == "rpc" and not ( + uresource.instance.strip() != "" or uresource.message.strip() != "" or uresource.id != 0) - def get_instance(self) -> str: - """ - An instance of a resource such as front_left or in the case of RPC a method name that manipulates the - resource such as UpdateDoor.

- @return:Returns the resource instance of the resource if it exists. If the instance does not exist, - it is assumed that all the instances of the resource are wanted. - """ - return self.instance - - def get_message(self) -> str: - """ - The Message type matches the protobuf service IDL that defines structured data types.
A message is a data - structure type used to define data that is passed in events and rpc methods.

- @return:Returns the Message type matches the protobuf service IDL that defines structured data types. - """ - return self.message - - def is_resolved(self) -> bool: - """ - Return true if this resource contains both ID and names.
Method type of UResource requires name, instance, - and ID where a topic type of UResource also requires message to not be null

- @return:Returns true of this resource contains resolved information - """ - return self.marked_resolved - - def is_long_form(self) -> bool: + @staticmethod + def is_long_form(uresource: UResource) -> bool: """ Returns true if the uResource contains names so that it can be serialized to long format.

+ @param uresource: UResource protobuf message @return:Returns true if the uResource contains names so that it can be serialized to long format. """ - if self.name == "rpc": - return bool(self.instance) - return bool(self.name) + if uresource.name == "rpc": + return bool(uresource.instance) + return bool(uresource.name) - def is_micro_form(self) -> bool: + @staticmethod + def is_micro_form(uresource: UResource) -> bool: """ Returns true if the uResource contains the id's which will allow the Uri part to be serialized into micro form.

+ @param uresource: UResource protobuf message @return:Returns true if the uResource can be serialized into micro form. """ - return self.id is not None - - def __eq__(self, other): - if self is other: - return True - if not isinstance(other, UResource): - return False - return ( - self.marked_resolved == other.marked_resolved and self.name == other.name and self.instance == - other.instance and self.message == other.message and self.id == other.id) + return uresource.id != 0 - def __hash__(self): - return hash((self.name, self.instance, self.message, self.id, self.marked_resolved)) - - def __str__(self): - return (f"UResource{{name='{self.name}', instance='{self.instance}', message='{self.message}', id={self.id}, " - f"markedResolved={self.marked_resolved}}}") - - -# Initialize EMPTY and RESPONSE -UResource.EMPTY = UResource("", None, None, None, False) -UResource.RESPONSE = UResource("rpc", "response", None, 0, True) + @staticmethod + def resolved_format(name: str, instance: str, message: str, identifier: int): + """ + Build a UResource that has all elements resolved and can be serialized in a long UUri or a micro UUri.

+ @param name:The name of the resource as a noun such as door or window, or in the case a method that + manipulates the resource, a verb. + @param instance:An instance of a resource such as front_left. + @param message:The Message type matches the protobuf service IDL message name that defines structured data + types.A message is a data structure type used to define data that is passed in events and rpc methods. + @param identifier:The numeric representation of this uResource. + @return:Returns a UResource that has all the information that is needed to serialize into a long UUri or a + micro UUri. + """ + resource = UResource(name if name else "") + if instance is not None or instance != "": + resource.instance = instance + if message is not None or message != "": + resource.message = message + if identifier is not None or identifier != 0: + resource.id = identifier + return resource diff --git a/org_eclipse_uprotocol/uri/factory/uuri_factory.py b/org_eclipse_uprotocol/uri/factory/uuri_factory.py new file mode 100644 index 0000000..3fea766 --- /dev/null +++ b/org_eclipse_uprotocol/uri/factory/uuri_factory.py @@ -0,0 +1,103 @@ +# ------------------------------------------------------------------------- + +# 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. + +# ------------------------------------------------------------------------- + +from org_eclipse_uprotocol.proto.uri_pb2 import UEntity +from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority +from org_eclipse_uprotocol.proto.uri_pb2 import UResource +from org_eclipse_uprotocol.proto.uri_pb2 import UUri +from org_eclipse_uprotocol.uri.factory.uauthority_factory import UAuthorityFactory +from org_eclipse_uprotocol.uri.factory.uentity_factory import UEntityFactory +from org_eclipse_uprotocol.uri.factory.uresource_factory import UResourceFactory + + +class UUriFactory: + """ + A factory is a part of the software that has methods to generate concrete objects, usually of the same type.
+ Data representation of uProtocol URI.
This class will be used to represent the source and sink ( + destination) parts of the Packet, for example in a CloudEvent Packet.
UUri is used as a method to uniquely + identify devices, services, and resources on the network.
Where software is deployed, what the service is + called along with a version and the resources in the service. Defining a common URI for the system allows + applications and/or services to publish and discover each other as well as maintain a database/repository of + microservices in the various vehicles.
Example for long format serialization: +
//<device>.<domain>/<service>/<version>/<resource>#<message>

+ The UUri Factory knows how to generate UUri proto message.
+ """ + + @staticmethod + def create_uuri(authority: UAuthority, entity: UEntity, resource: UResource): + """ + Create an UUri passing the Authority, Entity and Resource proto message + @param authority: The uAuthority represents the deployment location of a specific Software Entity. + @param entity: The SW entity information. + @param resource: The SW resource information + @return: + """ + return UUri(authority=authority, entity=entity, resource=resource) + + @staticmethod + def rpc_response(authority: UAuthority, entity: UEntity) -> UUri: + """ + Create an RPC Response UUri passing the Authority and Entity information.

+ @param authority:The uAuthority represents the deployment location of a specific Software Entity. + @param entity:The SW entity information. + @return:Returns a UUri of a constructed RPC Response. + """ + return UUri(authority=authority, entity=entity, resource=UResourceFactory.for_rpc_response()) + + @staticmethod + def empty() -> UUri: + """ + Static factory method for creating an empty uri, to avoid working with null

+ @return:Returns an empty uri to avoid working with null. + """ + return UUri() + + def is_empty(uuri: UUri) -> bool: + """ + Indicates that this URI is an empty container and has no valuable information in building uProtocol sinks or + sources.

+ @param uuri: An UUri proto message object + @return:Returns true if this URI is an empty container and has no valuable information in building uProtocol + sinks or sources. + """ + return UAuthorityFactory.is_empty(uuri.authority) and UEntityFactory.is_empty( + uuri.entity) and UResourceFactory.is_empty(uuri.resource) + + def is_long_form(uuri: 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. + """ + return UAuthorityFactory.is_long_form(uuri.authority) and ( + UEntityFactory.is_long_form(uuri.entity) or UEntityFactory.is_empty(uuri.entity)) and ( + UResourceFactory.is_long_form(uuri.resource) or UResourceFactory.is_empty(uuri.resource)) + + def is_micro_form(uuri: 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. + """ + return UAuthorityFactory.is_micro_form(uuri.authority) and UEntityFactory.is_micro_form( + uuri.entity) and UResourceFactory.is_micro_form(uuri.resource) diff --git a/org_eclipse_uprotocol/uri/serializer/longuriserializer.py b/org_eclipse_uprotocol/uri/serializer/longuriserializer.py index 3e088ba..b37a148 100644 --- a/org_eclipse_uprotocol/uri/serializer/longuriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/longuriserializer.py @@ -23,10 +23,18 @@ import re -from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority -from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity -from org_eclipse_uprotocol.uri.datamodel.uresource import UResource -from org_eclipse_uprotocol.uri.datamodel.uuri import UUri +from org_eclipse_uprotocol.proto.uri_pb2 import UUri +from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority +from org_eclipse_uprotocol.proto.uri_pb2 import UResource +from org_eclipse_uprotocol.proto.uri_pb2 import UEntity + + + +from org_eclipse_uprotocol.uri.factory.uauthority_factory import UAuthorityFactory +from org_eclipse_uprotocol.uri.factory.uentity_factory import UEntityFactory +from org_eclipse_uprotocol.uri.factory.uresource_factory import UResourceFactory +from org_eclipse_uprotocol.uri.factory.uuri_factory import UUriFactory + from org_eclipse_uprotocol.uri.serializer.uriserializer import UriSerializer @@ -44,37 +52,37 @@ def serialize(self, uri: UUri) -> str: @return:Returns the String format of the supplied UUri that can be used as a sink or a source in a uProtocol publish communication. """ - if uri is None or uri.is_empty(): + if uri is None or UUriFactory.is_empty(uri): return "" sb = [] - sb.append(self.build_authority_part_of_uri(uri.get_u_authority())) + sb.append(self.build_authority_part_of_uri(uri.authority)) - if uri.get_u_authority().is_marked_remote(): + if not UAuthorityFactory.is_local(uri.authority): sb.append("/") - if uri.get_u_entity().is_empty(): + if UEntityFactory.is_empty(uri.entity): return "".join(sb) - sb.append(self.build_software_entity_part_of_uri(uri.get_u_entity())) + sb.append(self.build_software_entity_part_of_uri(uri.entity)) - sb.append(self.build_resource_part_of_uri(uri.get_u_resource())) + sb.append(self.build_resource_part_of_uri(uri.resource)) return re.sub('/+$', '', "".join(sb)) @staticmethod def build_resource_part_of_uri(res: UResource) -> str: - if res.is_empty(): + if UResourceFactory.is_empty(res): return "" - sb = ["/", res.get_name()] + sb = ["/", res.name] - if res.get_instance(): - sb.append("." + res.get_instance()) + if res.instance: + sb.append("." + res.instance) - if res.get_message(): - sb.append("#" + res.get_message()) + if res.message: + sb.append("#" + res.message) return "".join(sb) @@ -85,10 +93,10 @@ def build_software_entity_part_of_uri(entity: UEntity) -> str: @param entity:Software Entity representing a service or an application. @return: Returns the String representation of the UEntity in the uProtocol URI. """ - sb = [entity.get_name().strip(), "/"] + sb = [entity.name.strip(), "/"] - if entity.get_version(): - sb.append(str(entity.get_version())) + if entity.version_major: + sb.append(str(entity.version_major)) return "".join(sb) @@ -99,20 +107,14 @@ def build_authority_part_of_uri(authority: UAuthority) -> str: @param authority:represents the deployment location of a specific Software Entity in the Ultiverse. @return:Returns the String representation of the Authority in the uProtocol URI. """ - if authority.is_local(): + if UAuthorityFactory.is_local(authority): return "/" partial_uri = ["//"] - maybe_device = authority.device - maybe_domain = authority.domain - - if maybe_device: - partial_uri.append(maybe_device) - if maybe_domain: - partial_uri.append(".") + maybe_name = authority.name - if maybe_domain: - partial_uri.append(maybe_domain) + if maybe_name: + partial_uri.append(maybe_name) return "".join(partial_uri) @@ -132,25 +134,25 @@ def deserialize(self, u_protocol_uri: str) -> UUri: if number_of_parts_in_uri == 0 or number_of_parts_in_uri == 1: if is_local: - return UUri.empty() + return UUriFactory.empty() else: - return UUri(UAuthority.long_remote("", ""), UEntity.empty(), UResource.empty()) + return UUriFactory.create_uuri(UAuthority.long_remote("", ""), UEntity.empty(), UResource.empty()) use_name = uri_parts[1] use_version = "" if is_local: - auth = UAuthority.local() + auth = UAuthorityFactory.local() if number_of_parts_in_uri > 2: use_version = uri_parts[2] res = self.parse_from_string(uri_parts[3]) if number_of_parts_in_uri > 3 else UResource.empty() else: - res = UResource.empty() + res = UResourceFactory.empty() else: authority_parts = uri_parts[2].split(".") device = authority_parts[0] domain = ".".join(authority_parts[1:]) if len(authority_parts) > 1 else "" - auth = UAuthority.long_remote(device, domain) + auth = UAuthorityFactory.long_remote(device+domain) if number_of_parts_in_uri > 3: use_name = uri_parts[3] @@ -158,13 +160,13 @@ def deserialize(self, u_protocol_uri: str) -> UUri: use_version = uri_parts[4] res = self.parse_from_string(uri_parts[5]) if number_of_parts_in_uri > 5 else UResource.empty() else: - res = UResource.empty() + res = UResourceFactory.empty() else: - return UUri(auth, UEntity.empty(), UResource.empty()) + return UUriFactory.create_uuri(auth, UEntity.empty(), UResource.empty()) use_version_int = int(use_version) if use_version.strip() else None - return UUri(auth, UEntity.long_format(use_name, use_version_int), res) + return UUriFactory.create_uuri(auth, UEntityFactory.long_format(use_name,version_major= use_version_int), res) @staticmethod def parse_from_string(resource_string: str) -> UResource: @@ -184,4 +186,4 @@ def parse_from_string(resource_string: str) -> UResource: resource_instance = name_and_instance_parts[1] if len(name_and_instance_parts) > 1 else None resource_message = parts[1] if len(parts) > 1 else None - return UResource.long_format_instance_message(resource_name, resource_instance, resource_message) + return UResourceFactory.long_format_instance_message(resource_name, resource_instance, resource_message) diff --git a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py index 593e105..9ea83cd 100644 --- a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py @@ -27,10 +27,17 @@ from enum import Enum from typing import Optional -from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority -from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity -from org_eclipse_uprotocol.uri.datamodel.uresource import UResource -from org_eclipse_uprotocol.uri.datamodel.uuri import UUri +from org_eclipse_uprotocol.proto.uri_pb2 import UUri +from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority +from org_eclipse_uprotocol.proto.uri_pb2 import UResource +from org_eclipse_uprotocol.proto.uri_pb2 import UEntity + + + +from org_eclipse_uprotocol.uri.factory.uauthority_factory import UAuthorityFactory +from org_eclipse_uprotocol.uri.factory.uentity_factory import UEntityFactory +from org_eclipse_uprotocol.uri.factory.uresource_factory import UResourceFactory +from org_eclipse_uprotocol.uri.factory.uuri_factory import UUriFactory from org_eclipse_uprotocol.uri.serializer.uriserializer import UriSerializer @@ -70,12 +77,12 @@ def serialize(self, uri: UUri) -> bytes: @param uri:The UUri data object. @return:Returns a byte[] representing the serialized UUri. """ - if uri is None or uri.is_empty() or not uri.is_micro_form(): + if uri is None or UUriFactory.is_empty(uri) or not UUriFactory.is_micro_form(uri): return bytearray() - maybe_address = uri.get_u_authority().address - maybe_ue_id = uri.get_u_entity().id - maybe_uresource_id = uri.get_u_resource().id + maybe_address = uri.authority.ip + maybe_ue_id = uri.entity.id + maybe_uresource_id = uri.resource.id os = io.BytesIO() os.write(bytes([self.UP_VERSION])) @@ -89,21 +96,21 @@ def serialize(self, uri: UUri) -> bytes: os.write(maybe_uresource_id.to_bytes(2, byteorder='big')) - maybe_uauthority_address_bytes = self.calculate_uauthority_bytes(uri.get_u_authority()) + maybe_uauthority_address_bytes = self.calculate_uauthority_bytes(uri.authority) if maybe_uauthority_address_bytes: os.write(maybe_uauthority_address_bytes) os.write(maybe_ue_id.to_bytes(2, byteorder='big')) - version = uri.get_u_entity().get_version() + version = uri.entity.version_major os.write(version.to_bytes(1, byteorder='big') if version else b'\x00') os.write(b'\x00') # Unused byte return os.getvalue() def calculate_uauthority_bytes(self, uauthority: UAuthority) -> Optional[bytes]: - maybe_address = uauthority.address - return maybe_address.packed if maybe_address else None + maybe_ip = uauthority.ip + return maybe_ip.packed if maybe_ip else None def deserialize(self, micro_uri: bytes) -> UUri: """ @@ -112,37 +119,37 @@ def deserialize(self, micro_uri: bytes) -> UUri: @return:Returns an UUri data object from the serialized format of a microUri. """ if micro_uri is None or len(micro_uri) < self.LOCAL_MICRO_URI_LENGTH: - return UUri.empty() + return UUriFactory.empty() if micro_uri[0] != self.UP_VERSION: - return UUri.empty() + return UUriFactory.empty() uresource_id = int.from_bytes(micro_uri[2:4], byteorder='big') address_type = AddressType.from_value(micro_uri[1]) if address_type == AddressType.LOCAL and len(micro_uri) != self.LOCAL_MICRO_URI_LENGTH: - return UUri.empty() + return UUriFactory.empty() elif address_type == AddressType.IPv4 and len(micro_uri) != self.IPV4_MICRO_URI_LENGTH: - return UUri.empty() + return UUriFactory.empty() elif address_type == AddressType.IPv6 and len(micro_uri) != self.IPV6_MICRO_URI_LENGTH: - return UUri.empty() + return UUriFactory.empty() index = 4 if address_type == AddressType.LOCAL: - u_authority = UAuthority.local() + u_authority = UAuthorityFactory.local() else: try: inet_address = socket.inet_ntop(socket.AF_INET, micro_uri[ index:index + 4]) if address_type == AddressType.IPv4\ else socket.inet_ntop( socket.AF_INET6, micro_uri[index:index + 16]) - u_authority = UAuthority.micro_remote(ipaddress.ip_address(inet_address)) + u_authority = UAuthorityFactory.micro_remote(socket.inet_aton(ipaddress.ip_address(inet_address))) except: - u_authority = UAuthority.local() + u_authority = UAuthorityFactory.local() index += 4 if address_type == AddressType.IPv4 else 16 ue_id = int.from_bytes(micro_uri[index:index + 2], byteorder='big') ui_version = micro_uri[index + 2] - return UUri(u_authority, UEntity.micro_format_id_version(ue_id, ui_version if ui_version != 0 else None), - UResource.micro_format(uresource_id)) + return UUriFactory.create_uuri(u_authority, UEntityFactory.micro_format_id_version(ue_id, ui_version if ui_version != 0 else None), + UResourceFactory.micro_format(uresource_id)) diff --git a/org_eclipse_uprotocol/uri/serializer/uriserializer.py b/org_eclipse_uprotocol/uri/serializer/uriserializer.py index bc15ba7..70118df 100644 --- a/org_eclipse_uprotocol/uri/serializer/uriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/uriserializer.py @@ -25,10 +25,11 @@ from re import T from typing import Optional -from org_eclipse_uprotocol.uri.datamodel.uauthority import UAuthority -from org_eclipse_uprotocol.uri.datamodel.uentity import UEntity -from org_eclipse_uprotocol.uri.datamodel.uresource import UResource -from org_eclipse_uprotocol.uri.datamodel.uuri import UUri +from org_eclipse_uprotocol.proto.uri_pb2 import UUri +from org_eclipse_uprotocol.uri.factory.uauthority_factory import UAuthorityFactory +from org_eclipse_uprotocol.uri.factory.uentity_factory import UEntityFactory +from org_eclipse_uprotocol.uri.factory.uresource_factory import UResourceFactory +from org_eclipse_uprotocol.uri.factory.uuri_factory import UUriFactory class UriSerializer(ABC): @@ -65,24 +66,24 @@ def build_resolved(self, long_uri: str, micro_uri: bytes) -> Optional[UUri]: @return:Returns a UUri object serialized from one of the forms. """ if (not long_uri or long_uri.isspace()) and (not micro_uri or len(micro_uri) == 0): - return Optional.of(UUri.empty()) + return UUriFactory.empty() from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer from org_eclipse_uprotocol.uri.serializer.microuriserializer import MicroUriSerializer long_u_uri = LongUriSerializer().deserialize(long_uri) micro_u_uri = MicroUriSerializer().deserialize(micro_uri) - u_authority = (UAuthority.local() if long_u_uri.get_u_authority().is_local() else UAuthority.resolved_remote( - long_u_uri.get_u_authority().device or None, long_u_uri.get_u_authority().domain or None, - micro_u_uri.get_u_authority().address or None)) + u_authority = (UAuthorityFactory.local() if UAuthorityFactory.is_local( + long_u_uri.authority) else UAuthorityFactory.resolved_remote(long_u_uri.authority.name or None, + micro_u_uri.authority.ip or None)) - u_entity = UEntity.resolved_format(long_u_uri.get_u_entity().get_name(), - long_u_uri.get_u_entity().get_version() or None, - micro_u_uri.get_u_entity().get_id() or None) + u_entity = UEntityFactory.resolved_format(long_u_uri.entity.name, long_u_uri.entity.version_major or None, + long_u_uri.entity.version_minor or None, + micro_u_uri.entity.id or None) - u_resource = UResource.resolved_format(long_u_uri.get_u_resource().get_name(), - long_u_uri.get_u_resource().get_instance() or None, - long_u_uri.get_u_resource().get_message() or None, - micro_u_uri.get_u_resource().get_id() or None) + u_resource = UResourceFactory.resolved_format(long_u_uri.resource.name, + long_u_uri.resource.instance or None, + long_u_uri.resource.message or None, + micro_u_uri.resource.id or None) u_uri = UUri(u_authority, u_entity, u_resource) return u_uri if u_uri.is_resolved() else None diff --git a/org_eclipse_uprotocol/uri/validator/urivalidator.py b/org_eclipse_uprotocol/uri/validator/urivalidator.py index 1267c34..032ac3b 100644 --- a/org_eclipse_uprotocol/uri/validator/urivalidator.py +++ b/org_eclipse_uprotocol/uri/validator/urivalidator.py @@ -22,8 +22,10 @@ # ------------------------------------------------------------------------- from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus, Code -from org_eclipse_uprotocol.uri.datamodel.uresource import UResource -from org_eclipse_uprotocol.uri.datamodel.uuri import UUri + +from org_eclipse_uprotocol.proto.uri_pb2 import UUri +from org_eclipse_uprotocol.uri.factory.uresource_factory import UResourceFactory +from org_eclipse_uprotocol.uri.factory.uuri_factory import UUriFactory class UriValidator: @@ -38,10 +40,10 @@ def validate(uri: UUri) -> UStatus: @param uri:UUri to validate. @return:Returns UStatus containing a success or a failure with the error message. """ - if uri.is_empty(): + if UUriFactory.is_empty(uri): return UStatus.failed_with_msg_and_code("Uri is empty.", Code.INVALID_ARGUMENT) - if uri.get_u_entity().name.strip() == "": + if uri.entity.name.strip() == "": return UStatus.failed_with_msg_and_code("Uri is missing uSoftware Entity name.", Code.INVALID_ARGUMENT) return UStatus.ok() @@ -58,8 +60,8 @@ def validate_rpc_method(uri: UUri) -> UStatus: if status.isFailed(): return status - u_resource = uri.get_u_resource() - if not u_resource.is_rpc_method(): + u_resource = uri.resource + if not UResourceFactory.is_rpc_method(u_resource): return UStatus.failed_with_msg_and_code( "Invalid RPC method uri. Uri should be the method to be called, or method from response.", Code.INVALID_ARGUMENT) @@ -78,8 +80,9 @@ def validate_rpc_response(uri: UUri) -> UStatus: if status.isFailed(): return status - u_resource = uri.get_u_resource() - if not (u_resource.is_rpc_method() and u_resource.instance == UResource.for_rpc_response().instance): + u_resource = uri.resource + if not (UResourceFactory.is_rpc_method( + u_resource) and u_resource.instance == UResourceFactory.for_rpc_response().instance): return UStatus.failed_with_msg_and_code("Invalid RPC response type.", Code.INVALID_ARGUMENT) return UStatus.ok() From 39ae6e12c7f26d3b481a10659c475092ffbc7fbe Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Mon, 30 Oct 2023 20:45:23 -0400 Subject: [PATCH 15/47] Updated uuri proto --- org_eclipse_uprotocol/proto/uri_pb2.py | 16 ++++++++-------- .../uri/factory/uauthority_factory.py | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/org_eclipse_uprotocol/proto/uri_pb2.py b/org_eclipse_uprotocol/proto/uri_pb2.py index 6f22b89..5b475e4 100644 --- a/org_eclipse_uprotocol/proto/uri_pb2.py +++ b/org_eclipse_uprotocol/proto/uri_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\turi.proto\x12\x0cuprotocol.v1\"\x85\x01\n\x04UUri\x12+\n\tauthority\x18\x01 \x01(\x0b\x32\x18.uprotocol.v1.UAuthority\x12%\n\x06\x65ntity\x18\x02 \x01(\x0b\x32\x15.uprotocol.v1.UEntity\x12)\n\x08resource\x18\x03 \x01(\x0b\x32\x17.uprotocol.v1.UResource\"S\n\nUAuthority\x12\x0f\n\x07isLocal\x18\x01 \x01(\x08\x12\x0e\n\x04name\x18\x02 \x01(\tH\x00\x12\x0c\n\x02ip\x18\x03 \x01(\x0cH\x00\x12\x0c\n\x02id\x18\x04 \x01(\x0cH\x00\x42\x08\n\x06remote\"\x8b\x01\n\x07UEntity\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x02id\x18\x02 \x01(\rH\x00\x88\x01\x01\x12\x1a\n\rversion_major\x18\x03 \x01(\rH\x01\x88\x01\x01\x12\x1a\n\rversion_minor\x18\x04 \x01(\rH\x02\x88\x01\x01\x42\x05\n\x03_idB\x10\n\x0e_version_majorB\x10\n\x0e_version_minor\"w\n\tUResource\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x15\n\x08instance\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07message\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x0f\n\x02id\x18\x04 \x01(\rH\x02\x88\x01\x01\x42\x0b\n\t_instanceB\n\n\x08_messageB\x05\n\x03_id\"-\n\tUUriBatch\x12 \n\x04uris\x18\x01 \x03(\x0b\x32\x12.uprotocol.v1.UUriB\'\n\x18org.eclipse.uprotocol.v1B\tUUriProtoP\x01\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\turi.proto\x12\x0cuprotocol.v1\"\x85\x01\n\x04UUri\x12+\n\tauthority\x18\x01 \x01(\x0b\x32\x18.uprotocol.v1.UAuthority\x12%\n\x06\x65ntity\x18\x02 \x01(\x0b\x32\x15.uprotocol.v1.UEntity\x12)\n\x08resource\x18\x03 \x01(\x0b\x32\x17.uprotocol.v1.UResource\"B\n\nUAuthority\x12\x0e\n\x04name\x18\x01 \x01(\tH\x00\x12\x0c\n\x02ip\x18\x02 \x01(\x0cH\x00\x12\x0c\n\x02id\x18\x03 \x01(\x0cH\x00\x42\x08\n\x06remote\"\x8b\x01\n\x07UEntity\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x02id\x18\x02 \x01(\rH\x00\x88\x01\x01\x12\x1a\n\rversion_major\x18\x03 \x01(\rH\x01\x88\x01\x01\x12\x1a\n\rversion_minor\x18\x04 \x01(\rH\x02\x88\x01\x01\x42\x05\n\x03_idB\x10\n\x0e_version_majorB\x10\n\x0e_version_minor\"w\n\tUResource\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x15\n\x08instance\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07message\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x0f\n\x02id\x18\x04 \x01(\rH\x02\x88\x01\x01\x42\x0b\n\t_instanceB\n\n\x08_messageB\x05\n\x03_id\"-\n\tUUriBatch\x12 \n\x04uris\x18\x01 \x03(\x0b\x32\x12.uprotocol.v1.UUriB\'\n\x18org.eclipse.uprotocol.v1B\tUUriProtoP\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -24,11 +24,11 @@ _globals['_UURI']._serialized_start=28 _globals['_UURI']._serialized_end=161 _globals['_UAUTHORITY']._serialized_start=163 - _globals['_UAUTHORITY']._serialized_end=246 - _globals['_UENTITY']._serialized_start=249 - _globals['_UENTITY']._serialized_end=388 - _globals['_URESOURCE']._serialized_start=390 - _globals['_URESOURCE']._serialized_end=509 - _globals['_UURIBATCH']._serialized_start=511 - _globals['_UURIBATCH']._serialized_end=556 + _globals['_UAUTHORITY']._serialized_end=229 + _globals['_UENTITY']._serialized_start=232 + _globals['_UENTITY']._serialized_end=371 + _globals['_URESOURCE']._serialized_start=373 + _globals['_URESOURCE']._serialized_end=492 + _globals['_UURIBATCH']._serialized_start=494 + _globals['_UURIBATCH']._serialized_end=539 # @@protoc_insertion_point(module_scope) diff --git a/org_eclipse_uprotocol/uri/factory/uauthority_factory.py b/org_eclipse_uprotocol/uri/factory/uauthority_factory.py index 2fffbb0..8996244 100644 --- a/org_eclipse_uprotocol/uri/factory/uauthority_factory.py +++ b/org_eclipse_uprotocol/uri/factory/uauthority_factory.py @@ -44,7 +44,7 @@ def local() -> UAuthority: @return:Returns a local uAuthority that has no domain, device, or ip address information, indicating to uProtocol that the uAuthority part in the UUri is relative to the sender/receiver deployment environment. """ - return UAuthority(isLocal=True) + return UAuthority() @staticmethod def long_remote(name: str) -> UAuthority: @@ -59,7 +59,7 @@ def long_remote(name: str) -> UAuthority: @return:Returns a uAuthority that contains the device and the domain and can only be serialized in long UUri format. """ - return UAuthority(name=name, isLocal=False) + return UAuthority(name=name) @staticmethod def micro_remote(ip: bytes) -> UAuthority: @@ -71,7 +71,7 @@ def micro_remote(ip: bytes) -> UAuthority: @return:Returns a uAuthority that contains only the internet address of the device, and can only be serialized in micro UUri format. """ - return UAuthority(ip=ip, isLocal=False) + return UAuthority(ip=ip) @staticmethod def empty() -> UAuthority: @@ -91,7 +91,7 @@ def is_remote(uauthority: UAuthority) -> bool: @return:Returns true if this uAuthority is remote, meaning it contains information for serialising a long UUri or a micro UUri. """ - return not uauthority.isLocal + return not UAuthorityFactory.is_empty(uauthority) @staticmethod def is_local(uauthority: UAuthority) -> bool: @@ -101,7 +101,7 @@ def is_local(uauthority: UAuthority) -> bool: @return:Returns true if this uAuthority is local, meaning does not contain a device/domain for long UUri or information for micro UUri. """ - return UAuthorityFactory.is_empty(uauthority) and uauthority.isLocal + return UAuthorityFactory.is_empty(uauthority) @staticmethod def is_long_form(uauthority: UAuthority) -> bool: @@ -110,7 +110,7 @@ def is_long_form(uauthority: UAuthority) -> bool: @param uauthority: uAuthority protobuf message @return:Returns true if the UAuthority can be used to serialize a UUri in long format. """ - return uauthority.isLocal or not uauthority.name.strip() == "" + return not uauthority.name.strip() == "" @staticmethod def is_micro_form(uauthority: UAuthority) -> bool: @@ -119,11 +119,11 @@ def is_micro_form(uauthority: UAuthority) -> bool: @param uauthority: uAuthority protobuf message @return:Returns true if the uAuthority can be serialized a UUri in micro format. """ - return uauthority.isLocal or len(uauthority.ip) != 0 + return len(uauthority.ip) != 0 @staticmethod def is_empty(uauthority: UAuthority) -> bool: - return all([uauthority.name.strip() == "", len(uauthority.ip) == 0]) + return all([uauthority.name.strip() == "", len(uauthority.ip) == 0,len(uauthority.id) == 0]) @staticmethod def resolved_remote(name: str, ip: bytes): From cb01884248f3bea3e76a9707a0dc9062476a92ef Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Mon, 30 Oct 2023 21:25:01 -0400 Subject: [PATCH 16/47] Fixed issue by changing condition from or to and in entity and resource factory class --- .../uri/factory/uentity_factory.py | 8 ++++---- .../uri/factory/uresource_factory.py | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/org_eclipse_uprotocol/uri/factory/uentity_factory.py b/org_eclipse_uprotocol/uri/factory/uentity_factory.py index ec87d73..929f2c0 100644 --- a/org_eclipse_uprotocol/uri/factory/uentity_factory.py +++ b/org_eclipse_uprotocol/uri/factory/uentity_factory.py @@ -75,7 +75,7 @@ def micro_format(identifier: int) -> UEntity: be serialized to long micro UUri format. """ entity = UEntity() - if identifier is not None or identifier != 0: + if identifier is not None and identifier != 0: entity.id = identifier return entity @@ -91,11 +91,11 @@ def micro_format_id_version(identifier: int, version_major: int, version_minor:i UUri format. """ entity=UEntity() - if identifier is not None or identifier!=0: + if identifier is not None and identifier!=0: entity.id=identifier - if version_major is not None or version_major!=0: + if version_major is not None and version_major!=0: entity.version_major=version_major - if version_minor is not None or version_minor!=0: + if version_minor is not None and version_minor!=0: entity.version_minor=version_minor return entity diff --git a/org_eclipse_uprotocol/uri/factory/uresource_factory.py b/org_eclipse_uprotocol/uri/factory/uresource_factory.py index c90d9b0..e4ca546 100644 --- a/org_eclipse_uprotocol/uri/factory/uresource_factory.py +++ b/org_eclipse_uprotocol/uri/factory/uresource_factory.py @@ -59,9 +59,9 @@ def long_format_instance_message(name: str, instance: str, message: str) -> URes @return:Returns a UResource that can be serialised into a long UUri. """ resource = UResource(name=name if name else "") - if instance is not None or instance != "": + if instance is not None and instance != "": resource.instance = instance - if message is not None or message != "": + if message is not None and message != "": resource.message = message return resource @@ -101,9 +101,9 @@ def for_rpc_request_with_name_and_id(method_name: str, method_id: int): @return:Returns a UResource used for an RPC request that could be serialised in long and micro format. """ resource = UResource(name="rpc") - if method_name is not None or method_name != "": + if method_name is not None and method_name != "": resource.instance = method_name - if method_id is not None or method_id != 0: + if method_id is not None and method_id != 0: resource.id = method_id return resource @@ -179,10 +179,10 @@ def resolved_format(name: str, instance: str, message: str, identifier: int): micro UUri. """ resource = UResource(name if name else "") - if instance is not None or instance != "": + if instance is not None and instance != "": resource.instance = instance - if message is not None or message != "": + if message is not None and message != "": resource.message = message - if identifier is not None or identifier != 0: + if identifier is not None and identifier != 0: resource.id = identifier return resource From b3389876cc199958978fa04b0d557e306625205d Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Tue, 31 Oct 2023 10:14:53 -0400 Subject: [PATCH 17/47] Reformat files --- .../uri/factory/uauthority_factory.py | 4 +--- .../uri/factory/uentity_factory.py | 16 ++++++++-------- .../uri/factory/uuri_factory.py | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/org_eclipse_uprotocol/uri/factory/uauthority_factory.py b/org_eclipse_uprotocol/uri/factory/uauthority_factory.py index 8996244..0652c69 100644 --- a/org_eclipse_uprotocol/uri/factory/uauthority_factory.py +++ b/org_eclipse_uprotocol/uri/factory/uauthority_factory.py @@ -21,8 +21,6 @@ # ------------------------------------------------------------------------- -import ipaddress -from typing import Optional from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority @@ -123,7 +121,7 @@ def is_micro_form(uauthority: UAuthority) -> bool: @staticmethod def is_empty(uauthority: UAuthority) -> bool: - return all([uauthority.name.strip() == "", len(uauthority.ip) == 0,len(uauthority.id) == 0]) + return all([uauthority.name.strip() == "", len(uauthority.ip) == 0, len(uauthority.id) == 0]) @staticmethod def resolved_remote(name: str, ip: bytes): diff --git a/org_eclipse_uprotocol/uri/factory/uentity_factory.py b/org_eclipse_uprotocol/uri/factory/uentity_factory.py index 929f2c0..9818cfa 100644 --- a/org_eclipse_uprotocol/uri/factory/uentity_factory.py +++ b/org_eclipse_uprotocol/uri/factory/uentity_factory.py @@ -80,7 +80,7 @@ def micro_format(identifier: int) -> UEntity: return entity @staticmethod - def micro_format_id_version(identifier: int, version_major: int, version_minor:int): + def micro_format_id_version(identifier: int, version_major: int, version_minor: int): """ Static factory method for creating a uE using the software entity identification number, that can be used to serialize micro UUris.

@@ -90,13 +90,13 @@ def micro_format_id_version(identifier: int, version_major: int, version_minor:i @return:Returns an UEntity with the name and the version of the service and can only be serialized to micro UUri format. """ - entity=UEntity() - if identifier is not None and identifier!=0: - entity.id=identifier - if version_major is not None and version_major!=0: - entity.version_major=version_major - if version_minor is not None and version_minor!=0: - entity.version_minor=version_minor + entity = UEntity() + if identifier is not None and identifier != 0: + entity.id = identifier + if version_major is not None and version_major != 0: + entity.version_major = version_major + if version_minor is not None and version_minor != 0: + entity.version_minor = version_minor return entity @staticmethod diff --git a/org_eclipse_uprotocol/uri/factory/uuri_factory.py b/org_eclipse_uprotocol/uri/factory/uuri_factory.py index 3fea766..533f99d 100644 --- a/org_eclipse_uprotocol/uri/factory/uuri_factory.py +++ b/org_eclipse_uprotocol/uri/factory/uuri_factory.py @@ -21,8 +21,8 @@ # ------------------------------------------------------------------------- -from org_eclipse_uprotocol.proto.uri_pb2 import UEntity from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority +from org_eclipse_uprotocol.proto.uri_pb2 import UEntity from org_eclipse_uprotocol.proto.uri_pb2 import UResource from org_eclipse_uprotocol.proto.uri_pb2 import UUri from org_eclipse_uprotocol.uri.factory.uauthority_factory import UAuthorityFactory From 18cf8a2870603a96bf852e16a4bb56cd488d43c3 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Wed, 1 Nov 2023 14:54:32 -0400 Subject: [PATCH 18/47] Update the logic to validate the rpc method --- org_eclipse_uprotocol/uri/factory/uresource_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org_eclipse_uprotocol/uri/factory/uresource_factory.py b/org_eclipse_uprotocol/uri/factory/uresource_factory.py index e4ca546..1811ef3 100644 --- a/org_eclipse_uprotocol/uri/factory/uresource_factory.py +++ b/org_eclipse_uprotocol/uri/factory/uresource_factory.py @@ -122,7 +122,7 @@ def is_rpc_method(uresource: UResource) -> bool: @param uresource: UResource protobuf message @return:Returns true if this resource specifies an RPC method call or RPC response. """ - return uresource.name == "rpc" + return uresource.name == "rpc" and (uresource.instance.strip() != "" or uresource.id != 0) @staticmethod def empty(): From 211c9cbc66f2238b44431020c4e0b42825fa0cca Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Fri, 3 Nov 2023 19:29:14 -0400 Subject: [PATCH 19/47] Fix micro serializer issue and remove factory classes for uuri datamodel --- .../uri/builder/uresource_builder.py | 31 +++ .../uri/factory/uauthority_factory.py | 138 ------------- .../uri/factory/uentity_factory.py | 160 --------------- .../uri/factory/uresource_factory.py | 188 ------------------ .../uri/factory/uuri_factory.py | 103 ---------- .../uri/serializer/longuriserializer.py | 133 +++++++------ .../uri/serializer/microuriserializer.py | 151 ++++++++------ .../uri/serializer/uriserializer.py | 29 ++- .../uri/validator/urivalidator.py | 95 ++++++++- 9 files changed, 290 insertions(+), 738 deletions(-) create mode 100644 org_eclipse_uprotocol/uri/builder/uresource_builder.py delete mode 100644 org_eclipse_uprotocol/uri/factory/uauthority_factory.py delete mode 100644 org_eclipse_uprotocol/uri/factory/uentity_factory.py delete mode 100644 org_eclipse_uprotocol/uri/factory/uresource_factory.py delete mode 100644 org_eclipse_uprotocol/uri/factory/uuri_factory.py diff --git a/org_eclipse_uprotocol/uri/builder/uresource_builder.py b/org_eclipse_uprotocol/uri/builder/uresource_builder.py new file mode 100644 index 0000000..c74f573 --- /dev/null +++ b/org_eclipse_uprotocol/uri/builder/uresource_builder.py @@ -0,0 +1,31 @@ +from org_eclipse_uprotocol.proto.uri_pb2 import UResource + + +class UResourceBuilder: + MAX_RPC_ID = 1000 + + @staticmethod + def for_rpc_response(): + return UResource(name="rpc", instance="response", id=0) + + @staticmethod + def for_rpc_request(method, id=None): + uresource = UResource(name="rpc") + if method is not None: + uresource.instance = method + if id is not None: + uresource.id = id + + return uresource + + @staticmethod + def for_rpc_request_with_id(id): + return UResourceBuilder.for_rpc_request(None, id) + + @staticmethod + def from_id(id): + if id is None: + raise ValueError("id cannot be None") + + return UResourceBuilder.for_rpc_request(id) if id < UResourceBuilder.MAX_RPC_ID else UResource(id=id) + diff --git a/org_eclipse_uprotocol/uri/factory/uauthority_factory.py b/org_eclipse_uprotocol/uri/factory/uauthority_factory.py deleted file mode 100644 index 0652c69..0000000 --- a/org_eclipse_uprotocol/uri/factory/uauthority_factory.py +++ /dev/null @@ -1,138 +0,0 @@ -# ------------------------------------------------------------------------- - -# 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. - -# ------------------------------------------------------------------------- - -from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority - - -class UAuthorityFactory: - """ - A factory is a part of the software that has methods to generate concrete objects, usually of the same type.
- An Authority represents the deployment location of a specific Software Entity.
Data representation of an - Authority.
An Authority consists of a device, a domain and a micro version in the form of an IP - Address.
Device and domain names are used as part of the URI for device and service discovery. Optimized micro - versions of the UUri will use the IP Address.
Devices will be grouped together into realms of Zone of - Authority.
The UAuthority Factory knows how to generate UAuthority proto message.
- """ - - @staticmethod - def local() -> UAuthority: - """ - Static factory method for creating a local uAuthority.
A local uri does not contain an authority and looks - like this:
 :<service>/<version>/<resource>#<Message> 

- @return:Returns a local uAuthority that has no domain, device, or ip address information, indicating to - uProtocol that the uAuthority part in the UUri is relative to the sender/receiver deployment environment. - """ - return UAuthority() - - @staticmethod - def long_remote(name: str) -> UAuthority: - """ - Static factory method for creating a remote authority supporting the long serialization information - representation of a UUri.
Building a UAuthority with this method will create an unresolved uAuthority - that can only be serialised in long UUri format. An uri with a long representation of uAUthority can be - serialised as follows: -
 //<device>.<domain>/<service>/<version>/<resource>#<Message>
-         
- @param name:domain & device name as a string. - @return:Returns a uAuthority that contains the device and the domain and can only be serialized in long UUri - format. - """ - return UAuthority(name=name) - - @staticmethod - def micro_remote(ip: bytes) -> UAuthority: - """ - Static factory method for creating a remote authority supporting the micro serialization information - representation of a UUri.
Building a UAuthority with this method will create an unresolved uAuthority that - can only be serialised in micro UUri format.

- @param ip:The ip address of the device a software entity is deployed on in bytes - @return:Returns a uAuthority that contains only the internet address of the device, and can only be - serialized in micro UUri format. - """ - return UAuthority(ip=ip) - - @staticmethod - def empty() -> UAuthority: - """ - Static factory method for creating an empty uAuthority, to avoid working with null
Empty uAuthority is - still serializable in both long and micro UUri formats, and will be as local to the current deployment - environment.

- @return:Returns an empty authority that has no domain, device, or device ip address information. - """ - return UAuthority() - - @staticmethod - def is_remote(uauthority: UAuthority) -> bool: - """ - Support for determining if this uAuthority defines a deployment that is defined as remote.

- @param uauthority: uAuthority protobuf message - @return:Returns true if this uAuthority is remote, meaning it contains information for serialising a long - UUri or a micro UUri. - """ - return not UAuthorityFactory.is_empty(uauthority) - - @staticmethod - def is_local(uauthority: UAuthority) -> bool: - """ - Support for determining if this uAuthority is defined for a local deployment.

- @param uauthority: uAuthority protobuf message - @return:Returns true if this uAuthority is local, meaning does not contain a device/domain for long UUri or - information for micro UUri. - """ - return UAuthorityFactory.is_empty(uauthority) - - @staticmethod - def is_long_form(uauthority: UAuthority) -> bool: - """ - Determine if the UAuthority can be used to serialize a UUri in long format.

- @param uauthority: uAuthority protobuf message - @return:Returns true if the UAuthority can be used to serialize a UUri in long format. - """ - return not uauthority.name.strip() == "" - - @staticmethod - def is_micro_form(uauthority: UAuthority) -> bool: - """ - Determine if the UAuthority can be used to serialize a UUri in micro format.

- @param uauthority: uAuthority protobuf message - @return:Returns true if the uAuthority can be serialized a UUri in micro format. - """ - return len(uauthority.ip) != 0 - - @staticmethod - def is_empty(uauthority: UAuthority) -> bool: - return all([uauthority.name.strip() == "", len(uauthority.ip) == 0, len(uauthority.id) == 0]) - - @staticmethod - def resolved_remote(name: str, ip: bytes): - """ - Static factory method for creating a remote authority that is completely resolved with name, device and ip - address of the device.
Building a UAuthority with this method will enable serialisation in both UUri - formats, long UUri format and micro UUri format. Note that in the case of missing data, this will not fail, - but simply create a UAuthority that is not resolved.

- @param name:The domain and device name as a string for long serialization of UUri. - @param ip:The IP address for the device in bytes, for micro serialization of UUri. - @return:Returns a uAuthority that contains all resolved data and so can be serialized into a long UUri or a - micro UUri. - """ - return UAuthority(name=name if name else None, ip=ip if ip else None) diff --git a/org_eclipse_uprotocol/uri/factory/uentity_factory.py b/org_eclipse_uprotocol/uri/factory/uentity_factory.py deleted file mode 100644 index 9818cfa..0000000 --- a/org_eclipse_uprotocol/uri/factory/uentity_factory.py +++ /dev/null @@ -1,160 +0,0 @@ -# ------------------------------------------------------------------------- - -# 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. - -# ------------------------------------------------------------------------- - - -from org_eclipse_uprotocol.proto.uri_pb2 import UEntity - - -class UEntityFactory: - """ - A factory is a part of the software that has methods to generate concrete objects, usually of the same type.
- Data representation of an Software Entity - uE
Software entities are distinguished by using a unique - name or a unique id along with the specific version of the software.
An Software Entity is a piece of software - deployed somewhere on a uDevice.
The Software Entity is used in the source and sink parts of communicating - software.
A uE that publishes events is a Service role.
A uE that consumes events is an - Application role.
The UEntity Factory knows how to generate UEntity proto message.
- """ - - @staticmethod - def long_format(name: str, version_minor: int = 0, version_major: int = 0) -> UEntity: - """ - Static factory method for creating a uE using the software entity name, that can be used in long form UUri - serialisation.

- @param name:The software entity name, such as petapp or body.access. - @param version_minor:minor version of the uEntity - @param version_major:major version of the uEntity - @return:Returns an UEntity with the name and the version of the service and can only be serialized to long - UUri format. - """ - entity = UEntity(name=name if name else "") - if version_minor != 0 and version_minor is not None: - entity.version_minor = version_minor - if version_major != 0 and version_major is not None: - entity.version_major = version_major - return entity - - @staticmethod - def long_format_name(name: str) -> UEntity: - """ - Static factory method for creating a uE using the software entity name, that can be used in long form UUri - serialisation.

- @param name:The software entity name, such as petapp or body.access. - @return:Returns an UEntity with the name where the version is the latest version of the service and can only - be serialized to long UUri format. - """ - return UEntity(name=name if name else "") - - @staticmethod - def micro_format(identifier: int) -> UEntity: - """ - Static factory method for creating a uE using the software entity identification number, that can be used to - serialize micro UUris.

- @param identifier: numeric identifier for the software entity which is a one-to-one correspondence with the - software name. - @return:Returns an UEntity with the name where the version is the latest version of the service and can only - be serialized to long micro UUri format. - """ - entity = UEntity() - if identifier is not None and identifier != 0: - entity.id = identifier - return entity - - @staticmethod - def micro_format_id_version(identifier: int, version_major: int, version_minor: int): - """ - Static factory method for creating a uE using the software entity identification number, that can be used to - serialize micro UUris.

- @param identifier:A numeric identifier for the software entity which is a one-to-one correspondence with the - software name. - @param version_major:major version of the uEntity - @return:Returns an UEntity with the name and the version of the service and can only be serialized to micro - UUri format. - """ - entity = UEntity() - if identifier is not None and identifier != 0: - entity.id = identifier - if version_major is not None and version_major != 0: - entity.version_major = version_major - if version_minor is not None and version_minor != 0: - entity.version_minor = version_minor - return entity - - @staticmethod - def empty(): - """ - Static factory method for creating an empty software entity, to avoid working with null

- @return:Returns an empty software entity that has a blank name, no unique id and no version information. - """ - return UEntity() - - @staticmethod - def is_empty(uentity: UEntity) -> bool: - """ - Indicates that this software entity is an empty container and has no valuable information in building - uProtocol sinks or sources.

- @param uentity: UEntity protobuf message - @return: Returns true if this software entity is an empty container and has no valuable information in - building uProtocol sinks or sources. - """ - return ( - uentity.name.strip() == "" and uentity.version_major == 0 and uentity.id == 0 and - uentity.version_minor == 0) - - @staticmethod - def is_long_form(uentity: UEntity) -> bool: - """ - Determine if this software entity can be serialised into a long UUri form.

- @param uentity: UEntity protobuf message - @return:Returns true if this software entity can be serialised into a long UUri form, meaning it has at least - a name. - """ - return bool(uentity.name) - - @staticmethod - def is_micro_form(uentity: UEntity) -> bool: - """ - Returns true if the Uri part contains the id's which will allow the Uri part to be serialized into micro - form.

- @param uentity: UEntity protobuf message - @return:Returns true if the Uri part can be serialized into micro form, meaning is has at least a unique - numeric identifier. - """ - return uentity.id != 0 - - @staticmethod - def resolved_format(name: str, version_major: int, version_minor: int, identifier: int): - """ - Create a complete UEntity with all the information so that it can be used in long form UUri serialisation and - micro form UUri serialisation.
In the case of missing elements such as name or id, the UEntity will not be - marked as resolvable and will not be usable in serialisation formats.

- @param name:The name of the software such as petapp or body.access. - @param version_major:The software major version. If not supplied, the latest version of the service will be - used. - @param version_minor:The software minor version. - @param identifier:A numeric identifier for the software entity which is a one-to-one correspondence with the - software name. - @return:Returns a complete UEntity with all the information so that it can be used in long form UUri - serialisation and micro form UUri serialisation. - """ - return UEntity(name if name else "", version_major if version_major else 0, - version_minor if version_minor else 0, identifier if identifier else 0) diff --git a/org_eclipse_uprotocol/uri/factory/uresource_factory.py b/org_eclipse_uprotocol/uri/factory/uresource_factory.py deleted file mode 100644 index 1811ef3..0000000 --- a/org_eclipse_uprotocol/uri/factory/uresource_factory.py +++ /dev/null @@ -1,188 +0,0 @@ -# ------------------------------------------------------------------------- - -# 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. - -# ------------------------------------------------------------------------- - - -from org_eclipse_uprotocol.proto.uri_pb2 import UResource - - -class UResourceFactory: - """ - A factory is a part of the software that has methods to generate concrete objects, usually of the same type.
- A service API - defined in the UEntity - has Resources and Methods. Both of these are represented by the - UResource class.
A uResource represents a resource from a Service such as "door" and an optional specific - instance such as "front_left". In addition, it can optionally contain the name of the resource Message type, - such as "Door". The Message type matches the protobuf service IDL that defines structured data types.
An - UResource is something that can be manipulated/controlled/exposed by a service. Resources are unique when - prepended with UAuthority that represents the device and UEntity that represents the service.
The UResource - Factory knows how to generate UResource proto message.
- """ - - @staticmethod - def long_format(name: str) -> UResource: - """ - Build a UResource that can be serialized into a long UUri. Mostly used for publishing messages.

- @param name:The name of the resource as a noun such as door or window, or in the case a method that - manipulates the resource, a verb. - @return:Returns a UResource that can be serialized into a long UUri. - """ - return UResource(name=name if name else "") - - @staticmethod - def long_format_instance_message(name: str, instance: str, message: str) -> UResource: - """ - Build a UResource that can be serialized into a long UUri. Mostly used for publishing messages.

- @param name:The name of the resource as a noun such as door or window, or in the case a method that - manipulates the resource, a verb. - @param instance:An instance of a resource such as front_left. - @param message:The Message type matches the protobuf service IDL message name that defines structured data - types. A message is a data structure type used to define data that is passed in events and rpc methods. - @return:Returns a UResource that can be serialised into a long UUri. - """ - resource = UResource(name=name if name else "") - if instance is not None and instance != "": - resource.instance = instance - if message is not None and message != "": - resource.message = message - return resource - - @staticmethod - def micro_format(identifier: int): - """ - Build a UResource that can be serialised into a micro UUri. Mostly used for publishing messages.

- @param identifier:The numeric representation of this uResource. - @return:Returns a UResource that can be serialised into a micro UUri. - """ - return UResource(id=identifier if identifier else 0) - - @staticmethod - def for_rpc_request(method_name: str): - """ - Build a UResource for rpc request, using only the long format.

- @param method_name:The RPC method name. - @return:Returns a UResource used for an RPC request that could be serialised in long format. - """ - return UResource(name="rpc", instance=method_name if method_name else "") - - @staticmethod - def for_rpc_request_with_id(method_id: int): - """ - Build a UResource for rpc request, using only the micro format.

- @param method_id:The numeric representation method name for the RPC. - @return:Returns a UResource used for an RPC request that could be serialised in micro format. - """ - return UResource(name="rpc", id=method_id if method_id else 0) - - @staticmethod - def for_rpc_request_with_name_and_id(method_name: str, method_id: int): - """ - Build a UResource for rpc request, using both the long and micro format information.

- @param method_name:The RPC method name. - @param method_id:The numeric representation method name for the RPC. - @return:Returns a UResource used for an RPC request that could be serialised in long and micro format. - """ - resource = UResource(name="rpc") - if method_name is not None and method_name != "": - resource.instance = method_name - if method_id is not None and method_id != 0: - resource.id = method_id - return resource - - @staticmethod - def for_rpc_response(): - """ - Static factory method for creating a response resource that is returned from RPC calls

- @return:Returns a response resource used for response RPC calls. - """ - return UResource(name="rpc", instance="response") - - @staticmethod - def is_rpc_method(uresource: UResource) -> bool: - """ - Returns true if this resource specifies an RPC method call or RPC response.

- @param uresource: UResource protobuf message - @return:Returns true if this resource specifies an RPC method call or RPC response. - """ - return uresource.name == "rpc" and (uresource.instance.strip() != "" or uresource.id != 0) - - @staticmethod - def empty(): - """ - Static factory method for creating an empty resource, to avoid working with null

- @return:Returns an empty resource that has a blank name and no message instance information. - """ - return UResource() - - @staticmethod - def is_empty(uresource: UResource) -> bool: - """ - Indicates that this resource is an empty container and has no valuable information in building uProtocol - URI.

- @param uresource: UResource protobuf message - @return:Returns true if this resource is an empty container and has no valuable information in building - uProtocol URI. - """ - return uresource.name.strip() == "" or uresource.name == "rpc" and not ( - uresource.instance.strip() != "" or uresource.message.strip() != "" or uresource.id != 0) - - @staticmethod - def is_long_form(uresource: UResource) -> bool: - """ - Returns true if the uResource contains names so that it can be serialized to long format.

- @param uresource: UResource protobuf message - @return:Returns true if the uResource contains names so that it can be serialized to long format. - """ - if uresource.name == "rpc": - return bool(uresource.instance) - return bool(uresource.name) - - @staticmethod - def is_micro_form(uresource: UResource) -> bool: - """ - Returns true if the uResource contains the id's which will allow the Uri part to be serialized into micro - form.

- @param uresource: UResource protobuf message - @return:Returns true if the uResource can be serialized into micro form. - """ - return uresource.id != 0 - - @staticmethod - def resolved_format(name: str, instance: str, message: str, identifier: int): - """ - Build a UResource that has all elements resolved and can be serialized in a long UUri or a micro UUri.

- @param name:The name of the resource as a noun such as door or window, or in the case a method that - manipulates the resource, a verb. - @param instance:An instance of a resource such as front_left. - @param message:The Message type matches the protobuf service IDL message name that defines structured data - types.A message is a data structure type used to define data that is passed in events and rpc methods. - @param identifier:The numeric representation of this uResource. - @return:Returns a UResource that has all the information that is needed to serialize into a long UUri or a - micro UUri. - """ - resource = UResource(name if name else "") - if instance is not None and instance != "": - resource.instance = instance - if message is not None and message != "": - resource.message = message - if identifier is not None and identifier != 0: - resource.id = identifier - return resource diff --git a/org_eclipse_uprotocol/uri/factory/uuri_factory.py b/org_eclipse_uprotocol/uri/factory/uuri_factory.py deleted file mode 100644 index 533f99d..0000000 --- a/org_eclipse_uprotocol/uri/factory/uuri_factory.py +++ /dev/null @@ -1,103 +0,0 @@ -# ------------------------------------------------------------------------- - -# 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. - -# ------------------------------------------------------------------------- - -from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority -from org_eclipse_uprotocol.proto.uri_pb2 import UEntity -from org_eclipse_uprotocol.proto.uri_pb2 import UResource -from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.uri.factory.uauthority_factory import UAuthorityFactory -from org_eclipse_uprotocol.uri.factory.uentity_factory import UEntityFactory -from org_eclipse_uprotocol.uri.factory.uresource_factory import UResourceFactory - - -class UUriFactory: - """ - A factory is a part of the software that has methods to generate concrete objects, usually of the same type.
- Data representation of uProtocol URI.
This class will be used to represent the source and sink ( - destination) parts of the Packet, for example in a CloudEvent Packet.
UUri is used as a method to uniquely - identify devices, services, and resources on the network.
Where software is deployed, what the service is - called along with a version and the resources in the service. Defining a common URI for the system allows - applications and/or services to publish and discover each other as well as maintain a database/repository of - microservices in the various vehicles.
Example for long format serialization: -
//<device>.<domain>/<service>/<version>/<resource>#<message>

- The UUri Factory knows how to generate UUri proto message.
- """ - - @staticmethod - def create_uuri(authority: UAuthority, entity: UEntity, resource: UResource): - """ - Create an UUri passing the Authority, Entity and Resource proto message - @param authority: The uAuthority represents the deployment location of a specific Software Entity. - @param entity: The SW entity information. - @param resource: The SW resource information - @return: - """ - return UUri(authority=authority, entity=entity, resource=resource) - - @staticmethod - def rpc_response(authority: UAuthority, entity: UEntity) -> UUri: - """ - Create an RPC Response UUri passing the Authority and Entity information.

- @param authority:The uAuthority represents the deployment location of a specific Software Entity. - @param entity:The SW entity information. - @return:Returns a UUri of a constructed RPC Response. - """ - return UUri(authority=authority, entity=entity, resource=UResourceFactory.for_rpc_response()) - - @staticmethod - def empty() -> UUri: - """ - Static factory method for creating an empty uri, to avoid working with null

- @return:Returns an empty uri to avoid working with null. - """ - return UUri() - - def is_empty(uuri: UUri) -> bool: - """ - Indicates that this URI is an empty container and has no valuable information in building uProtocol sinks or - sources.

- @param uuri: An UUri proto message object - @return:Returns true if this URI is an empty container and has no valuable information in building uProtocol - sinks or sources. - """ - return UAuthorityFactory.is_empty(uuri.authority) and UEntityFactory.is_empty( - uuri.entity) and UResourceFactory.is_empty(uuri.resource) - - def is_long_form(uuri: 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. - """ - return UAuthorityFactory.is_long_form(uuri.authority) and ( - UEntityFactory.is_long_form(uuri.entity) or UEntityFactory.is_empty(uuri.entity)) and ( - UResourceFactory.is_long_form(uuri.resource) or UResourceFactory.is_empty(uuri.resource)) - - def is_micro_form(uuri: 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. - """ - return UAuthorityFactory.is_micro_form(uuri.authority) and UEntityFactory.is_micro_form( - uuri.entity) and UResourceFactory.is_micro_form(uuri.resource) diff --git a/org_eclipse_uprotocol/uri/serializer/longuriserializer.py b/org_eclipse_uprotocol/uri/serializer/longuriserializer.py index b37a148..639c0e3 100644 --- a/org_eclipse_uprotocol/uri/serializer/longuriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/longuriserializer.py @@ -23,19 +23,12 @@ import re -from org_eclipse_uprotocol.proto.uri_pb2 import UUri from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority -from org_eclipse_uprotocol.proto.uri_pb2 import UResource from org_eclipse_uprotocol.proto.uri_pb2 import UEntity - - - -from org_eclipse_uprotocol.uri.factory.uauthority_factory import UAuthorityFactory -from org_eclipse_uprotocol.uri.factory.uentity_factory import UEntityFactory -from org_eclipse_uprotocol.uri.factory.uresource_factory import UResourceFactory -from org_eclipse_uprotocol.uri.factory.uuri_factory import UUriFactory - +from org_eclipse_uprotocol.proto.uri_pb2 import UResource +from org_eclipse_uprotocol.proto.uri_pb2 import UUri from org_eclipse_uprotocol.uri.serializer.uriserializer import UriSerializer +from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator class LongUriSerializer(UriSerializer): @@ -52,39 +45,35 @@ def serialize(self, uri: UUri) -> str: @return:Returns the String format of the supplied UUri that can be used as a sink or a source in a uProtocol publish communication. """ - if uri is None or UUriFactory.is_empty(uri): + if uri is None or UriValidator.is_empty(uri): return "" sb = [] - sb.append(self.build_authority_part_of_uri(uri.authority)) + if not UriValidator.is_authority_empty(uri.authority): + sb.append(self.build_authority_part_of_uri(uri.authority)) - if not UAuthorityFactory.is_local(uri.authority): - sb.append("/") - - if UEntityFactory.is_empty(uri.entity): - return "".join(sb) + sb.append("/") sb.append(self.build_software_entity_part_of_uri(uri.entity)) - sb.append(self.build_resource_part_of_uri(uri.resource)) return re.sub('/+$', '', "".join(sb)) @staticmethod - def build_resource_part_of_uri(res: UResource) -> str: - if UResourceFactory.is_empty(res): - return "" + def build_resource_part_of_uri(u_resource: UResource) -> str: - sb = ["/", res.name] + if UriValidator.is_resource_empty(u_resource): + return "" - if res.instance: - sb.append("." + res.instance) + sb = "/" + u_resource.name - if res.message: - sb.append("#" + res.message) + if u_resource.instance: + sb += "." + u_resource.instance + if u_resource.message: + sb += "#" + u_resource.message - return "".join(sb) + return sb @staticmethod def build_software_entity_part_of_uri(entity: UEntity) -> str: @@ -93,12 +82,13 @@ def build_software_entity_part_of_uri(entity: UEntity) -> str: @param entity:Software Entity representing a service or an application. @return: Returns the String representation of the UEntity in the uProtocol URI. """ - sb = [entity.name.strip(), "/"] + sb = str(entity.name.strip()) + sb += "/" - if entity.version_major: - sb.append(str(entity.version_major)) + if entity.version_major > 0: + sb += str(entity.version_major) - return "".join(sb) + return sb @staticmethod def build_authority_part_of_uri(authority: UAuthority) -> str: @@ -107,16 +97,14 @@ def build_authority_part_of_uri(authority: UAuthority) -> str: @param authority:represents the deployment location of a specific Software Entity in the Ultiverse. @return:Returns the String representation of the Authority in the uProtocol URI. """ - if UAuthorityFactory.is_local(authority): - return "/" - partial_uri = ["//"] + partial_uri = "//" maybe_name = authority.name - if maybe_name: - partial_uri.append(maybe_name) + if maybe_name is not None or maybe_name != "": + partial_uri += maybe_name - return "".join(partial_uri) + return partial_uri def deserialize(self, u_protocol_uri: str) -> UUri: """ @@ -125,57 +113,68 @@ def deserialize(self, u_protocol_uri: str) -> UUri: @return:Returns an UUri data object. """ if u_protocol_uri is None or u_protocol_uri.strip() == "": - return UUri.empty() - - uri = u_protocol_uri.split(":")[-1].replace('\\', '/') + return UUri() + uri = u_protocol_uri[u_protocol_uri.index(":") + 1:] if ":" in u_protocol_uri else u_protocol_uri.replace('\\', + '/') is_local = not uri.startswith("//") uri_parts = uri.split("/") number_of_parts_in_uri = len(uri_parts) if number_of_parts_in_uri == 0 or number_of_parts_in_uri == 1: - if is_local: - return UUriFactory.empty() - else: - return UUriFactory.create_uuri(UAuthority.long_remote("", ""), UEntity.empty(), UResource.empty()) + return UUri() - use_name = uri_parts[1] + use_name = "" use_version = "" + u_resource = None + u_authority = None if is_local: - auth = UAuthorityFactory.local() + use_name = uri_parts[1] if number_of_parts_in_uri > 2: use_version = uri_parts[2] - res = self.parse_from_string(uri_parts[3]) if number_of_parts_in_uri > 3 else UResource.empty() - else: - res = UResourceFactory.empty() - else: - authority_parts = uri_parts[2].split(".") - device = authority_parts[0] - domain = ".".join(authority_parts[1:]) if len(authority_parts) > 1 else "" - auth = UAuthorityFactory.long_remote(device+domain) + if number_of_parts_in_uri > 3: + u_resource = self.parse_from_string(uri_parts[3]) - if number_of_parts_in_uri > 3: + else: + if uri_parts[2].strip() == "": + return UUri() + u_authority = UAuthority(name=uri_parts[2]) + if len(uri_parts) > 3: use_name = uri_parts[3] if number_of_parts_in_uri > 4: use_version = uri_parts[4] - res = self.parse_from_string(uri_parts[5]) if number_of_parts_in_uri > 5 else UResource.empty() - else: - res = UResourceFactory.empty() + if number_of_parts_in_uri > 5: + u_resource = self.parse_from_string(uri_parts[5]) else: - return UUriFactory.create_uuri(auth, UEntity.empty(), UResource.empty()) + return UUri(authority=u_authority) + + use_version_int = None + try: + if use_version.strip() != "": + use_version_int = int(use_version) + except ValueError: + return UUri() - use_version_int = int(use_version) if use_version.strip() else None + u_entity_builder = UEntity(name=use_name) + if use_version_int is not None: + u_entity_builder.version_major = use_version_int - return UUriFactory.create_uuri(auth, UEntityFactory.long_format(use_name,version_major= use_version_int), res) + new_uri = UUri(entity=u_entity_builder) + if u_authority is not None: + new_uri.authority = u_authority + if u_resource is not None: + new_uri.resource = u_resource + + return new_uri @staticmethod def parse_from_string(resource_string: str) -> UResource: """ - Static factory method for creating a UResource using a string that contains name + instance + message.

+ Static builder method for creating a UResource using a string that contains name + instance + message.

@param resource_string:String that contains the UResource information. @return:Returns a UResource object. """ - if resource_string is None: + if resource_string is None or resource_string.strip() == "": raise ValueError("Resource must have a command name.") parts = resource_string.split("#") @@ -186,4 +185,10 @@ def parse_from_string(resource_string: str) -> UResource: resource_instance = name_and_instance_parts[1] if len(name_and_instance_parts) > 1 else None resource_message = parts[1] if len(parts) > 1 else None - return UResourceFactory.long_format_instance_message(resource_name, resource_instance, resource_message) + u_resource = UResource(name=resource_name) + if resource_instance is not None: + u_resource.instance = resource_instance + if resource_message is not None: + u_resource.message = resource_message + + return u_resource diff --git a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py index 9ea83cd..bcea2f4 100644 --- a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py @@ -22,23 +22,14 @@ # ------------------------------------------------------------------------- import io -import ipaddress -import socket from enum import Enum -from typing import Optional -from org_eclipse_uprotocol.proto.uri_pb2 import UUri from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority -from org_eclipse_uprotocol.proto.uri_pb2 import UResource from org_eclipse_uprotocol.proto.uri_pb2 import UEntity - - - -from org_eclipse_uprotocol.uri.factory.uauthority_factory import UAuthorityFactory -from org_eclipse_uprotocol.uri.factory.uentity_factory import UEntityFactory -from org_eclipse_uprotocol.uri.factory.uresource_factory import UResourceFactory -from org_eclipse_uprotocol.uri.factory.uuri_factory import UUriFactory +from org_eclipse_uprotocol.proto.uri_pb2 import UUri +from org_eclipse_uprotocol.uri.builder.uresource_builder import UResourceBuilder from org_eclipse_uprotocol.uri.serializer.uriserializer import UriSerializer +from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator class AddressType(Enum): @@ -48,9 +39,10 @@ class AddressType(Enum): LOCAL = 0 IPv4 = 1 IPv6 = 2 + ID = 3 def getValue(self): - return self.value + return bytes(self.value) @classmethod def from_value(cls, value): @@ -77,40 +69,75 @@ def serialize(self, uri: UUri) -> bytes: @param uri:The UUri data object. @return:Returns a byte[] representing the serialized UUri. """ - if uri is None or UUriFactory.is_empty(uri) or not UUriFactory.is_micro_form(uri): + + if uri is None or UriValidator.is_empty(uri) or not UriValidator.is_micro_form(uri): return bytearray() - maybe_address = uri.authority.ip maybe_ue_id = uri.entity.id maybe_uresource_id = uri.resource.id os = io.BytesIO() os.write(bytes([self.UP_VERSION])) - if maybe_address: - os.write( - bytes([AddressType.IPv4.getValue()]) if isinstance(maybe_address, ipaddress.IPv4Address) else bytes( - [AddressType.IPv6.getValue()])) + # 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": + type = AddressType.LOCAL + elif remote_case == "IP": + length = len(uri.authority.ip) + if length == 4: + type = AddressType.IPv4 + elif length == 16: + type = AddressType.IPv6 + else: + return bytearray() + elif remote_case == "ID": + type = AddressType.ID else: - os.write(bytes([AddressType.LOCAL.getValue()])) + return bytearray() - os.write(maybe_uresource_id.to_bytes(2, byteorder='big')) + os.write(type.getValue()) - maybe_uauthority_address_bytes = self.calculate_uauthority_bytes(uri.authority) - if maybe_uauthority_address_bytes: - os.write(maybe_uauthority_address_bytes) + # URESOURCE_ID + os.write((maybe_uresource_id >> 8).to_bytes(2, 'big')) + os.write((maybe_uresource_id & 0xFF).to_bytes(1, 'big')) - os.write(maybe_ue_id.to_bytes(2, byteorder='big')) + # UENTITY_ID + os.write((maybe_ue_id >> 8).to_bytes(1, 'big')) + os.write((maybe_ue_id & 0xFF).to_bytes(1, 'big')) - version = uri.entity.version_major - os.write(version.to_bytes(1, byteorder='big') if version else b'\x00') - os.write(b'\x00') # Unused byte + # UE_VERSION + unsigned_value = uri.entity.version_major + if unsigned_value > 127: + signed_byte = unsigned_value - 256 + else: + signed_byte = unsigned_value + os.write(uri.entity.version_major.to_bytes(1, byteorder='big')) + # UNUSED + os.write(bytes([0])) - return os.getvalue() + # Populating the UAuthority + if type != AddressType.LOCAL: + # Write the ID length if the type is ID + if type == AddressType.ID: + os.write(len(uri.authority.id)).to_bytes(2, 'big') + + try: + if remote_case == "IP": + os.write(uri.authority.ip) + elif remote_case == "ID": + os.write(uri.authority.id) + except Exception as e: + print(e) # Handle the exception as needed - def calculate_uauthority_bytes(self, uauthority: UAuthority) -> Optional[bytes]: - maybe_ip = uauthority.ip - return maybe_ip.packed if maybe_ip else None + return os.getvalue() def deserialize(self, micro_uri: bytes) -> UUri: """ @@ -119,37 +146,45 @@ def deserialize(self, micro_uri: bytes) -> UUri: @return:Returns an UUri data object from the serialized format of a microUri. """ if micro_uri is None or len(micro_uri) < self.LOCAL_MICRO_URI_LENGTH: - return UUriFactory.empty() + return UUri() if micro_uri[0] != self.UP_VERSION: - return UUriFactory.empty() + return UUri() + + u_resource_id = ((micro_uri[2] & 0xFF) << 8) | (micro_uri[3] & 0xFF) + type = AddressType.from_value(micro_uri[1]) - uresource_id = int.from_bytes(micro_uri[2:4], byteorder='big') - address_type = AddressType.from_value(micro_uri[1]) + # Validate Type is found + if type is None: + return UUri() + # Validate that the micro_uri is the correct length for the type + address_type = type if address_type == AddressType.LOCAL and len(micro_uri) != self.LOCAL_MICRO_URI_LENGTH: - return UUriFactory.empty() + return UUri() elif address_type == AddressType.IPv4 and len(micro_uri) != self.IPV4_MICRO_URI_LENGTH: - return UUriFactory.empty() + return UUri() elif address_type == AddressType.IPv6 and len(micro_uri) != self.IPV6_MICRO_URI_LENGTH: - return UUriFactory.empty() + return UUri() - index = 4 - if address_type == AddressType.LOCAL: - u_authority = UAuthorityFactory.local() - else: - try: - inet_address = socket.inet_ntop(socket.AF_INET, micro_uri[ - index:index + 4]) if address_type == AddressType.IPv4\ - else socket.inet_ntop( - socket.AF_INET6, micro_uri[index:index + 16]) - u_authority = UAuthorityFactory.micro_remote(socket.inet_aton(ipaddress.ip_address(inet_address))) - except: - u_authority = UAuthorityFactory.local() - index += 4 if address_type == AddressType.IPv4 else 16 - - ue_id = int.from_bytes(micro_uri[index:index + 2], byteorder='big') - ui_version = micro_uri[index + 2] - - return UUriFactory.create_uuri(u_authority, UEntityFactory.micro_format_id_version(ue_id, ui_version if ui_version != 0 else None), - UResourceFactory.micro_format(uresource_id)) + # UENTITY_ID + ue_id = ((micro_uri[4] & 0xFF) << 8) | (micro_uri[5] & 0xFF) + + # UE_VERSION + ui_version = micro_uri[6] + + u_authority = None + if address_type in (AddressType.IPv4, AddressType.IPv6): + length = 4 if address_type == AddressType.IPv4 else 16 + data = micro_uri[8:8 + length] + u_authority = UAuthority(ip=bytes(data)) + elif address_type == AddressType.ID: + length = micro_uri[8] + u_authority = UAuthority(id=bytes(micro_uri[9:9 + length])) + + uri = UUri(entity=UEntity(id=ue_id, version_major=ui_version), resource=UResourceBuilder.from_id(u_resource_id)) + + if u_authority is not None: + uri.authority = u_authority + + return uri diff --git a/org_eclipse_uprotocol/uri/serializer/uriserializer.py b/org_eclipse_uprotocol/uri/serializer/uriserializer.py index 70118df..cdf45b4 100644 --- a/org_eclipse_uprotocol/uri/serializer/uriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/uriserializer.py @@ -26,10 +26,7 @@ from typing import Optional from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.uri.factory.uauthority_factory import UAuthorityFactory -from org_eclipse_uprotocol.uri.factory.uentity_factory import UEntityFactory -from org_eclipse_uprotocol.uri.factory.uresource_factory import UResourceFactory -from org_eclipse_uprotocol.uri.factory.uuri_factory import UUriFactory +from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator class UriSerializer(ABC): @@ -66,24 +63,22 @@ def build_resolved(self, long_uri: str, micro_uri: bytes) -> Optional[UUri]: @return:Returns a UUri object serialized from one of the forms. """ if (not long_uri or long_uri.isspace()) and (not micro_uri or len(micro_uri) == 0): - return UUriFactory.empty() + return UUri() from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer from org_eclipse_uprotocol.uri.serializer.microuriserializer import MicroUriSerializer long_u_uri = LongUriSerializer().deserialize(long_uri) micro_u_uri = MicroUriSerializer().deserialize(micro_uri) - u_authority = (UAuthorityFactory.local() if UAuthorityFactory.is_local( - long_u_uri.authority) else UAuthorityFactory.resolved_remote(long_u_uri.authority.name or None, - micro_u_uri.authority.ip or None)) - u_entity = UEntityFactory.resolved_format(long_u_uri.entity.name, long_u_uri.entity.version_major or None, - long_u_uri.entity.version_minor or None, - micro_u_uri.entity.id or None) - u_resource = UResourceFactory.resolved_format(long_u_uri.resource.name, - long_u_uri.resource.instance or None, - long_u_uri.resource.message or None, - micro_u_uri.resource.id or None) + u_authority=micro_u_uri.authority + u_authority.name=long_u_uri.authority.name - u_uri = UUri(u_authority, u_entity, u_resource) - return u_uri if u_uri.is_resolved() else None + u_entity= micro_u_uri.u_entity + u_entity.name =long_u_uri.entity.name + + u_resource=long_u_uri.resource + u_resource.id=micro_u_uri.resource.id + + u_uri = UUri(authority=u_authority, entity=u_entity,resource= u_resource) + return u_uri if UriValidator.is_resolved(u_uri) else None diff --git a/org_eclipse_uprotocol/uri/validator/urivalidator.py b/org_eclipse_uprotocol/uri/validator/urivalidator.py index 032ac3b..0cc9846 100644 --- a/org_eclipse_uprotocol/uri/validator/urivalidator.py +++ b/org_eclipse_uprotocol/uri/validator/urivalidator.py @@ -19,13 +19,12 @@ # specific language governing permissions and limitations # under the License. +from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority # ------------------------------------------------------------------------- - -from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus, Code - +from org_eclipse_uprotocol.proto.uri_pb2 import UEntity +from org_eclipse_uprotocol.proto.uri_pb2 import UResource from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.uri.factory.uresource_factory import UResourceFactory -from org_eclipse_uprotocol.uri.factory.uuri_factory import UUriFactory +from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus, Code class UriValidator: @@ -40,7 +39,7 @@ def validate(uri: UUri) -> UStatus: @param uri:UUri to validate. @return:Returns UStatus containing a success or a failure with the error message. """ - if UUriFactory.is_empty(uri): + if UriValidator.is_empty(uri): return UStatus.failed_with_msg_and_code("Uri is empty.", Code.INVALID_ARGUMENT) if uri.entity.name.strip() == "": @@ -60,8 +59,7 @@ def validate_rpc_method(uri: UUri) -> UStatus: if status.isFailed(): return status - u_resource = uri.resource - if not UResourceFactory.is_rpc_method(u_resource): + if not UriValidator.is_rpc_method(uri): return UStatus.failed_with_msg_and_code( "Invalid RPC method uri. Uri should be the method to be called, or method from response.", Code.INVALID_ARGUMENT) @@ -81,8 +79,85 @@ def validate_rpc_response(uri: UUri) -> UStatus: return status u_resource = uri.resource - if not (UResourceFactory.is_rpc_method( - u_resource) and u_resource.instance == UResourceFactory.for_rpc_response().instance): + if not (UriValidator.is_rpc_method( + u_resource) and u_resource.instance == UResource.for_rpc_response().instance): return UStatus.failed_with_msg_and_code("Invalid RPC response type.", Code.INVALID_ARGUMENT) return UStatus.ok() + + @staticmethod + def is_empty(uuri: UUri) -> bool: + """ + Indicates that this URI is an empty container and has no valuable information in building uProtocol sinks or + sources.

+ @param uuri: An UUri proto message object + @return:Returns true if this URI is an empty container and has no valuable information in building uProtocol + sinks or sources. + """ + return (UriValidator.is_authority_empty(uuri.authority) and UriValidator.is_entity_empty( + uuri.entity) and UriValidator.is_resource_empty(uuri.resource)) + + @staticmethod + def is_authority_empty(authority: UAuthority) -> bool: + return all([authority.name.strip() == "", len(authority.ip) == 0, len(authority.id) == 0]) + + @staticmethod + def is_entity_empty(entity: UEntity) -> bool: + return all([entity.name.strip() == "", entity.version_major == 0, entity.id == 0, entity.version_minor == 0]) + + @staticmethod + def is_resource_empty(resource: UResource) -> bool: + return resource.name.strip() == "" or resource.name == "rpc" and not ( + resource.instance.strip() != "" or resource.message.strip() != "" or resource.id != 0) + + @staticmethod + def is_rpc_method(uuri: UUri) -> bool: + """ + Returns true if this resource specifies an RPC method call or RPC response.

+ @param uresource: UResource protobuf message + @return:Returns true if this resource specifies an RPC method call or RPC response. + """ + return not UriValidator.is_empty(uuri) and uuri.resource.name == "rpc" and ( + uuri.resource.instance.strip() != "" or uuri.resource.id != 0) + + @staticmethod + def is_resolved(uuri: UUri) -> bool: + if uuri is None: + raise ValueError("Uri cannot be None.") + + return not UriValidator.is_empty(uuri) + + @staticmethod + def is_rpc_response(uuri: UUri) -> bool: + if uuri is None: + raise ValueError("Uri cannot be None.") + + resource = uuri.get_resource() + return UriValidator.is_rpc_method(uuri) and ( + (uuri.resource.instance.strip() != "" and "response" in resource.Instance) or resource.get_id() != 0) + + @staticmethod + def is_micro_form(uuri: 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. + """ + return not UriValidator.is_empty(uuri) and ( + UriValidator.is_authority_empty(uuri.authority) or len(uuri.authority.ip) != 0 or len( + uuri.authority.id) != 0) and uuri.entity.id != 0 and uuri.resource.id != 0 + + @staticmethod + def is_long_form(uuri: 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. + """ + return ( + uuri.authority.name.strip() != "" and uuri.entity.name.strip() != "" and + uuri.resource.name.strip() != "") + + @staticmethod + def is_remote(uuri: UUri) -> bool: + return not UriValidator.is_authority_empty(uuri.authority) From 799671ecbbfcaa160b40363b5c785d126fbf16d4 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Tue, 14 Nov 2023 21:23:22 -0500 Subject: [PATCH 20/47] Align it with 1.5.1 uprotocol-java --- .../datamodel/ucloudeventattributes.py | 30 ++- .../cloudevent/datamodel/ucloudeventtype.py | 49 ---- .../cloudevent/factory/cloudeventfactory.py | 36 ++- .../cloudevent/factory/ucloudevent.py | 34 ++- .../serialize/base64protobufserializer.py | 12 +- .../serialize/cloudeventserializer.py | 12 +- .../serialize/cloudeventserializers.py | 12 +- .../serialize/cloudeventtojsonserializer.py | 13 +- .../cloudeventtoprotobufserializer.py | 12 +- .../validate/cloudeventvalidator.py | 28 ++- .../cloudevent/validate/validationresult.py | 12 +- .../proto/uattributes_pb2.py | 32 +++ org_eclipse_uprotocol/proto/upayload_pb2.py | 26 +++ org_eclipse_uprotocol/proto/uuid_pb2.py | 26 +++ org_eclipse_uprotocol/rpc/calloptions.py | 13 +- org_eclipse_uprotocol/rpc/rpcclient.py | 16 +- org_eclipse_uprotocol/rpc/rpcmapper.py | 14 +- org_eclipse_uprotocol/rpc/rpcresult.py | 12 +- .../factory => transport/builder}/__init__.py | 0 .../transport/builder/uattributesbuilder.py | 219 ++++++++++++++++++ .../transport/datamodel/uattributes.py | 140 ----------- .../transport/datamodel/ulistener.py | 17 +- .../transport/datamodel/umessagetype.py | 69 ------ .../transport/datamodel/upayload.py | 24 +- .../transport/datamodel/upriority.py | 73 ------ .../transport/datamodel/userializationhint.py | 79 ------- .../transport/datamodel/ustatus.py | 12 +- org_eclipse_uprotocol/transport/utransport.py | 18 +- .../validate/uattributesvalidator.py | 36 +-- org_eclipse_uprotocol/uri/builder/__init__.py | 0 .../uri/builder/uresource_builder.py | 28 ++- .../uri/serializer/longuriserializer.py | 12 +- .../uri/serializer/microuriserializer.py | 12 +- .../uri/serializer/uriserializer.py | 28 +-- .../uri/validator/urivalidator.py | 32 +-- .../uuid/factory/__init__.py | 14 +- .../uuid/factory/uuidfactory.py | 46 ++-- .../uuid/factory/uuidutils.py | 140 ++++++----- .../uuid/serializer/__init__.py | 0 .../uuid/serializer/longuuidserializer.py | 64 +++++ .../uuid/serializer/microuuidserializer.py | 66 ++++++ .../uuid/serializer/uuidserializer.py | 57 +++++ .../uuid/validate/uuidvalidator.py | 39 ++-- 43 files changed, 939 insertions(+), 675 deletions(-) delete mode 100644 org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py create mode 100644 org_eclipse_uprotocol/proto/uattributes_pb2.py create mode 100644 org_eclipse_uprotocol/proto/upayload_pb2.py create mode 100644 org_eclipse_uprotocol/proto/uuid_pb2.py rename org_eclipse_uprotocol/{uri/factory => transport/builder}/__init__.py (100%) create mode 100644 org_eclipse_uprotocol/transport/builder/uattributesbuilder.py delete mode 100644 org_eclipse_uprotocol/transport/datamodel/uattributes.py delete mode 100644 org_eclipse_uprotocol/transport/datamodel/umessagetype.py delete mode 100644 org_eclipse_uprotocol/transport/datamodel/upriority.py delete mode 100644 org_eclipse_uprotocol/transport/datamodel/userializationhint.py create mode 100644 org_eclipse_uprotocol/uri/builder/__init__.py create mode 100644 org_eclipse_uprotocol/uuid/serializer/__init__.py create mode 100644 org_eclipse_uprotocol/uuid/serializer/longuuidserializer.py create mode 100644 org_eclipse_uprotocol/uuid/serializer/microuuidserializer.py create mode 100644 org_eclipse_uprotocol/uuid/serializer/uuidserializer.py diff --git a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py index 8c1f3a5..a064b8b 100644 --- a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py +++ b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,21 +9,24 @@ # 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 - +# +# 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 typing import Optional -from org_eclipse_uprotocol.transport.datamodel.upriority import UPriority +from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority class UCloudEventAttributes: @@ -31,7 +34,7 @@ class UCloudEventAttributes: Specifies the properties that can configure the UCloudEvent. """ - def __init__(self, hash_value: str, priority: str, ttl: int, token: str): + def __init__(self, hash_value: str, priority: UPriority, ttl: int, token: str): """ Construct the properties object.

@param hash_value: an HMAC generated on the data portion of the CloudEvent message using the device key. @@ -67,9 +70,9 @@ def hash(self) -> Optional[str]: An HMAC generated on the data portion of the CloudEvent message using the device key.

@return: Returns an Optional hash attribute. """ - return None if not self.hash or self.hash.isspace() else self.hash + return self.hash if self.hash and self.hash.strip() else None - def priority(self) -> Optional[str]: + def priority(self) -> UPriority: """ uProtocol Prioritization classifications defined at QoS in SDV-202.

@return: Returns an Optional priority attribute. @@ -88,7 +91,7 @@ def token(self) -> Optional[str]: Oauth2 access token to perform the access request defined in the request message.

@return: Returns an Optional OAuth token attribute. """ - return None if not self.token or self.token.isspace() else self.token + return self.token if self.token and self.token.strip() else None def __eq__(self, other): if self is other: @@ -161,4 +164,11 @@ def build(self): Construct the UCloudEventAttributes from the builder.

@return: Returns a constructed UProperty. """ - return UCloudEventAttributes(self.hash, self.priority.qos_string, self.ttl, self.token) + return UCloudEventAttributes(self.hash, self.priority, self.ttl, self.token) + + +if __name__ == "__main__": + # Example usage: + attributes = UCloudEventAttributesBuilder().with_hash("abc123").with_priority(UPriority.UPRIORITY_CS0).with_ttl( + 1000).with_token("xyz456").build() + print(attributes) diff --git a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py deleted file mode 100644 index adc9bad..0000000 --- a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventtype.py +++ /dev/null @@ -1,49 +0,0 @@ -# ------------------------------------------------------------------------- - -# 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. - -# ------------------------------------------------------------------------- - -from enum import Enum -from typing import Optional - - -class UCloudEventType(Enum): - """ - Enumeration for the core types of uProtocol CloudEvents. - """ - PUBLISH = "pub.v1" - REQUEST = "req.v1" - RESPONSE = "res.v1" - - def type(self) -> str: - return self.value - - @staticmethod - def value_of_type(typestr: str) -> Optional['UCloudEventType']: - """ - Convert a String type into a maybe UCloudEventType.

- @param typestr: The String value of the UCloudEventType. - @return: returns the UCloudEventType associated with the provided String. - """ - for ce_type in UCloudEventType: - if ce_type.type() == typestr: - return ce_type - return None diff --git a/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py b/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py index 49d0382..ccda6b1 100644 --- a/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py +++ b/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py @@ -1,32 +1,39 @@ # ------------------------------------------------------------------------- -# Copyright (c) 2023 General Motors GTO LLC +# 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 +# "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 - +# +# 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 cloudevents.http import CloudEvent from google.protobuf import empty_pb2 from google.protobuf.any_pb2 import Any from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributes -from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventtype import UCloudEventType +from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent +from org_eclipse_uprotocol.proto.uattributes_pb2 import UMessageType from org_eclipse_uprotocol.uuid.factory.uuidfactory import Factories +from org_eclipse_uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer # A factory is a part of the software has methods to generate concrete objects, usually of the same type or @@ -53,7 +60,8 @@ def request(application_uri_for_rpc: str, service_method_uri: str, request_id: s proto_payload.SerializeToString(), proto_payload.DESCRIPTOR.full_name, attributes, - UCloudEventType.REQUEST.type()) + UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_REQUEST)) cloud_event.__setitem__("sink", service_method_uri) cloud_event.__setitem__("reqid", request_id) return cloud_event @@ -78,7 +86,8 @@ def response(application_uri_for_rpc: str, service_method_uri: str, request_id: proto_payload.SerializeToString(), proto_payload.DESCRIPTOR.full_name, attributes, - UCloudEventType.RESPONSE.type()) + UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_RESPONSE)) cloud_event.__setitem__("sink", application_uri_for_rpc) cloud_event.__setitem__("reqid", request_id) return cloud_event @@ -106,7 +115,8 @@ def failed_response(application_uri_for_rpc: str, service_method_uri: str, reque empty_proto_payload.SerializeToString(), # Empty payload "google.protobuf.Empty", attributes, - UCloudEventType.RESPONSE.type()) + UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_RESPONSE)) cloud_event.__setitem__("sink", application_uri_for_rpc) cloud_event.__setitem__("reqid", request_id) cloud_event.__setitem__("commstatus", communication_status) @@ -125,7 +135,8 @@ def publish(source: str, proto_payload: Any, attributes: UCloudEventAttributes) event_id = CloudEventFactory.generate_cloud_event_id() cloud_event = CloudEventFactory.build_base_cloud_event(event_id, source, proto_payload.SerializeToString(), proto_payload.DESCRIPTOR.full_name, attributes, - UCloudEventType.PUBLISH.type()) + UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_PUBLISH)) return cloud_event @staticmethod @@ -143,7 +154,8 @@ def notification(source: str, sink: str, proto_payload: Any, attributes: UCloudE event_id = CloudEventFactory.generate_cloud_event_id() cloud_event = CloudEventFactory.build_base_cloud_event(event_id, source, proto_payload.SerializeToString(), proto_payload.DESCRIPTOR.full_name, attributes, - UCloudEventType.PUBLISH.type()) + UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_PUBLISH)) cloud_event.__setitem__("sink", sink) return cloud_event @@ -154,7 +166,7 @@ def generate_cloud_event_id() -> str: @return: Returns a UUIDv8 id. """ uuid_inst = Factories.UPROTOCOL.create() - return str(uuid_inst) + return LongUuidSerializer.instance().serialize(uuid_inst) @staticmethod def build_base_cloud_event(id: str, source: str, proto_payload_bytes: bytes, proto_payload_schema: str, diff --git a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py index e69a316..40e8e1e 100644 --- a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py +++ b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,18 +9,22 @@ # 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 - +# +# 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 datetime import datetime, timedelta from cloudevents.http import CloudEvent @@ -28,7 +32,9 @@ from google.protobuf.message import DecodeError from google.rpc.code_pb2 import Code +from org_eclipse_uprotocol.proto.uattributes_pb2 import UMessageType from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils +from org_eclipse_uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer class UCloudEvent: @@ -152,7 +158,7 @@ def get_creation_timestamp(ce: CloudEvent) -> int: @return:Return the timestamp from the UUIDV8 CloudEvent Id or an empty Optional if timestamp can't be extracted. """ cloud_event_id = UCloudEvent.extract_string_value_from_attributes("id", ce) - uuid = UUIDUtils.fromString(cloud_event_id) + uuid = LongUuidSerializer.instance().deserialize(cloud_event_id) return UUIDUtils.getTime(uuid) if uuid is not None else None @@ -166,7 +172,7 @@ def is_expired_by_cloud_event_creation_date(ce: CloudEvent) -> bool: expiration. """ maybe_ttl = UCloudEvent.get_ttl(ce) - if maybe_ttl is None or maybe_ttl <= 0: + if not maybe_ttl or maybe_ttl <= 0: return False cloud_event_creation_time = UCloudEvent.extract_string_value_from_attributes("time", ce) @@ -187,12 +193,12 @@ def is_expired(ce: CloudEvent) -> bool: @return:Returns true if the CloudEvent was configured with a ttl > 0 and UUIDv8 id to compare for expiration. """ maybe_ttl = UCloudEvent.get_ttl(ce) - if maybe_ttl is None or maybe_ttl <= 0: + if not maybe_ttl or maybe_ttl <= 0: return False cloud_event_id = UCloudEvent.extract_string_value_from_attributes("id", ce) try: - uuid = UUIDUtils.fromString(cloud_event_id) + uuid = LongUuidSerializer.instance().deserialize(cloud_event_id) if uuid is None: return False delta = datetime.utcnow().timestamp() - UUIDUtils.getTime(uuid).timestamp() @@ -209,7 +215,7 @@ def is_cloud_event_id(ce: CloudEvent) -> bool: @return: Returns true if the CloudEvent is valid. """ cloud_event_id = UCloudEvent.extract_string_value_from_attributes("id", ce) - uuid = UUIDUtils.fromString(cloud_event_id) + uuid = LongUuidSerializer.instance().deserialize(cloud_event_id) return uuid is not None and UUIDUtils.isuuid(uuid) @@ -288,3 +294,13 @@ def extract_integer_value_from_attributes(attr_name, ce: CloudEvent) -> int: """ value = UCloudEvent.extract_string_value_from_attributes(attr_name, ce) return int(value) if value is not None else None + + @staticmethod + def get_event_type(type): + return {UMessageType.UMESSAGE_TYPE_PUBLISH: "pub.v1", UMessageType.UMESSAGE_TYPE_REQUEST: "req.v1", + UMessageType.UMESSAGE_TYPE_RESPONSE: "res.v1"}.get(type, "") + + @staticmethod + def get_message_type(ce_type): + return {"pub.v1": UMessageType.UMESSAGE_TYPE_PUBLISH, "req.v1": UMessageType.UMESSAGE_TYPE_REQUEST, + "res.v1": UMessageType.UMESSAGE_TYPE_RESPONSE}.get(ce_type, UMessageType.UMESSAGE_TYPE_UNSPECIFIED) diff --git a/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py index eaf87da..a2f3f7b 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,18 +9,22 @@ # 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 - +# +# 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 base64 from builtins import str diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializer.py index e82f149..adf77c8 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializer.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializer.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,18 +9,22 @@ # 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 - +# +# 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 typing import Any diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py index e60aef4..c647ad0 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,18 +9,22 @@ # 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 - +# +# 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 enum import Enum from org_eclipse_uprotocol.cloudevent.serialize.cloudeventtojsonserializer import CloudEventToJsonSerializer diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py index 57ef2cb..e3bc13c 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py @@ -1,6 +1,7 @@ # ------------------------------------------------------------------------- -# Copyright (c) 2023 General Motors GTO LLC +# 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 @@ -8,18 +9,22 @@ # 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 - +# +# 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 cloudevents.conversion import to_json from cloudevents.http import CloudEvent, from_json diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py index 42c858b..41f488c 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,18 +9,22 @@ # 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 - +# +# 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 cloudevents.http import CloudEvent from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializer import CloudEventSerializer diff --git a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py index 479c4de..71544b6 100644 --- a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py +++ b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,27 +9,31 @@ # 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 - +# +# 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 enum import Enum from cloudevents.http import CloudEvent from google.rpc.status_pb2 import Status -from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventtype import UCloudEventType from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent from org_eclipse_uprotocol.cloudevent.validate.validationresult import ValidationResult +from org_eclipse_uprotocol.proto.uattributes_pb2 import UMessageType from org_eclipse_uprotocol.proto.uri_pb2 import UUri from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer @@ -48,13 +52,15 @@ def get_validator(ce: CloudEvent): @return:Returns a CloudEventValidator according to the type attribute in the CloudEvent. """ cloud_event_type = ce.get_attributes().get("type") - maybe_type = UCloudEventType(cloud_event_type) - if maybe_type not in UCloudEventType: + + if cloud_event_type is None or not cloud_event_type: return Validators.PUBLISH.validator() - elif maybe_type == UCloudEventType.RESPONSE: + message_type = UCloudEvent.get_message_type(cloud_event_type) + + if message_type == UMessageType.UMESSAGE_TYPE_RESPONSE: return Validators.RESPONSE.validator() - elif maybe_type == UCloudEventType.REQUEST: + elif message_type == UMessageType.UMESSAGE_TYPE_REQUEST: return Validators.REQUEST.validator() else: return Validators.PUBLISH.validator() @@ -189,7 +195,7 @@ def validate_rpc_topic_uri(uri: str) -> ValidationResult: @param uri:String UriPart to validate. @return:Returns the ValidationResult containing a success or a failure with the error message. """ - Uri = LongUriSerializer.deserialize(uri) + Uri = LongUriSerializer().deserialize(uri) return CloudEventValidator.validate_rpc_topic_uri_from_uuri(Uri) @staticmethod @@ -220,7 +226,7 @@ def validate_rpc_method(uri: str) -> ValidationResult: @param uri: String UriPart to validate @return:Returns the ValidationResult containing a success or a failure with the error message. """ - Uri = LongUriSerializer.deserialize(uri) + Uri = LongUriSerializer().deserialize(uri) validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI(Uri) if validationResult.is_failure(): return ValidationResult.failure(f"Invalid RPC method uri. {validationResult.get_message()}") diff --git a/org_eclipse_uprotocol/cloudevent/validate/validationresult.py b/org_eclipse_uprotocol/cloudevent/validate/validationresult.py index 5ca6e1b..ddf6fe1 100644 --- a/org_eclipse_uprotocol/cloudevent/validate/validationresult.py +++ b/org_eclipse_uprotocol/cloudevent/validate/validationresult.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,18 +9,22 @@ # 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 - +# +# 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 google.rpc.code_pb2 import Code diff --git a/org_eclipse_uprotocol/proto/uattributes_pb2.py b/org_eclipse_uprotocol/proto/uattributes_pb2.py new file mode 100644 index 0000000..1f9eb49 --- /dev/null +++ b/org_eclipse_uprotocol/proto/uattributes_pb2.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: uattributes.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +import org_eclipse_uprotocol.proto.uri_pb2 as uri__pb2 +import org_eclipse_uprotocol.proto.uuid_pb2 as uuid__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11uattributes.proto\x12\x0cuprotocol.v1\x1a\turi.proto\x1a\nuuid.proto\"\xf8\x02\n\x0bUAttributes\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x12.uprotocol.v1.UUID\x12(\n\x04type\x18\x02 \x01(\x0e\x32\x1a.uprotocol.v1.UMessageType\x12%\n\x04sink\x18\x03 \x01(\x0b\x32\x12.uprotocol.v1.UUriH\x00\x88\x01\x01\x12)\n\x08priority\x18\x04 \x01(\x0e\x32\x17.uprotocol.v1.UPriority\x12\x10\n\x03ttl\x18\x05 \x01(\x05H\x01\x88\x01\x01\x12\x1d\n\x10permission_level\x18\x06 \x01(\x05H\x02\x88\x01\x01\x12\x17\n\ncommstatus\x18\x07 \x01(\x05H\x03\x88\x01\x01\x12&\n\x05reqid\x18\x08 \x01(\x0b\x32\x12.uprotocol.v1.UUIDH\x04\x88\x01\x01\x12\x12\n\x05token\x18\t \x01(\tH\x05\x88\x01\x01\x42\x07\n\x05_sinkB\x06\n\x04_ttlB\x13\n\x11_permission_levelB\r\n\x0b_commstatusB\x08\n\x06_reqidB\x08\n\x06_token*\x7f\n\x0cUMessageType\x12\x1d\n\x19UMESSAGE_TYPE_UNSPECIFIED\x10\x00\x12\x19\n\x15UMESSAGE_TYPE_PUBLISH\x10\x01\x12\x19\n\x15UMESSAGE_TYPE_REQUEST\x10\x02\x12\x1a\n\x16UMESSAGE_TYPE_RESPONSE\x10\x03*\xab\x01\n\tUPriority\x12\x19\n\x15UPRIORITY_UNSPECIFIED\x10\x00\x12\x11\n\rUPRIORITY_CS0\x10\x01\x12\x11\n\rUPRIORITY_CS1\x10\x02\x12\x11\n\rUPRIORITY_CS2\x10\x03\x12\x11\n\rUPRIORITY_CS3\x10\x04\x12\x11\n\rUPRIORITY_CS4\x10\x05\x12\x11\n\rUPRIORITY_CS5\x10\x06\x12\x11\n\rUPRIORITY_CS6\x10\x07\x42.\n\x18org.eclipse.uprotocol.v1B\x10UAttributesProtoP\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'uattributes_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + _globals['DESCRIPTOR']._options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\030org.eclipse.uprotocol.v1B\020UAttributesProtoP\001' + _globals['_UMESSAGETYPE']._serialized_start=437 + _globals['_UMESSAGETYPE']._serialized_end=564 + _globals['_UPRIORITY']._serialized_start=567 + _globals['_UPRIORITY']._serialized_end=738 + _globals['_UATTRIBUTES']._serialized_start=59 + _globals['_UATTRIBUTES']._serialized_end=435 +# @@protoc_insertion_point(module_scope) diff --git a/org_eclipse_uprotocol/proto/upayload_pb2.py b/org_eclipse_uprotocol/proto/upayload_pb2.py new file mode 100644 index 0000000..45309a5 --- /dev/null +++ b/org_eclipse_uprotocol/proto/upayload_pb2.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: upayload.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eupayload.proto\x12\x0cuprotocol.v1*\xd8\x01\n\x0eUPayloadFormat\x12\x1f\n\x1bUPAYLOAD_FORMAT_UNSPECIFIED\x10\x00\x12\x1c\n\x18UPAYLOAD_FORMAT_PROTOBUF\x10\x01\x12\x18\n\x14UPAYLOAD_FORMAT_JSON\x10\x02\x12\x1a\n\x16UPAYLOAD_FORMAT_SOMEIP\x10\x03\x12\x1e\n\x1aUPAYLOAD_FORMAT_SOMEIP_TLV\x10\x04\x12\x17\n\x13UPAYLOAD_FORMAT_RAW\x10\x05\x12\x18\n\x14UPAYLOAD_FORMAT_TEXT\x10\x06\x42+\n\x18org.eclipse.uprotocol.v1B\rUPayloadProtoP\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'upayload_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + _globals['DESCRIPTOR']._options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\030org.eclipse.uprotocol.v1B\rUPayloadProtoP\001' + _globals['_UPAYLOADFORMAT']._serialized_start=33 + _globals['_UPAYLOADFORMAT']._serialized_end=249 +# @@protoc_insertion_point(module_scope) diff --git a/org_eclipse_uprotocol/proto/uuid_pb2.py b/org_eclipse_uprotocol/proto/uuid_pb2.py new file mode 100644 index 0000000..d321cd4 --- /dev/null +++ b/org_eclipse_uprotocol/proto/uuid_pb2.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: uuid.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nuuid.proto\x12\x0cuprotocol.v1\" \n\x04UUID\x12\x0b\n\x03msb\x18\x01 \x01(\x06\x12\x0b\n\x03lsb\x18\x02 \x01(\x06\x42\'\n\x18org.eclipse.uprotocol.v1B\tUUIRProtoP\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'uuid_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + _globals['DESCRIPTOR']._options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\030org.eclipse.uprotocol.v1B\tUUIRProtoP\001' + _globals['_UUID']._serialized_start=28 + _globals['_UUID']._serialized_end=60 +# @@protoc_insertion_point(module_scope) diff --git a/org_eclipse_uprotocol/rpc/calloptions.py b/org_eclipse_uprotocol/rpc/calloptions.py index efd620f..11e57f1 100644 --- a/org_eclipse_uprotocol/rpc/calloptions.py +++ b/org_eclipse_uprotocol/rpc/calloptions.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,17 +9,22 @@ # 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 - +# +# 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 # ------------------------------------------------------------------------- + + class CallOptions: """ This class is used when making uRPC calls to pass additional options. diff --git a/org_eclipse_uprotocol/rpc/rpcclient.py b/org_eclipse_uprotocol/rpc/rpcclient.py index acf97d3..a3db5f6 100644 --- a/org_eclipse_uprotocol/rpc/rpcclient.py +++ b/org_eclipse_uprotocol/rpc/rpcclient.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,24 +9,28 @@ # 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 - +# +# 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 org_eclipse_uprotocol.transport.datamodel.uattributes import UAttributes -from org_eclipse_uprotocol.transport.datamodel.upayload import UPayload +from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes from org_eclipse_uprotocol.proto.uri_pb2 import UUri +from org_eclipse_uprotocol.transport.datamodel.upayload import UPayload class RpcClient(ABC): diff --git a/org_eclipse_uprotocol/rpc/rpcmapper.py b/org_eclipse_uprotocol/rpc/rpcmapper.py index 7c3b199..cdbca38 100644 --- a/org_eclipse_uprotocol/rpc/rpcmapper.py +++ b/org_eclipse_uprotocol/rpc/rpcmapper.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,18 +9,22 @@ # 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 - +# +# 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 concurrent.futures import Future from google.protobuf import any_pb2 @@ -34,7 +38,7 @@ 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 ( - USerializationHint.PROTOBUF) and will barf if anything else is passed + UPayloadFormat.PROTOBUF) and will barf if anything else is passed """ @staticmethod diff --git a/org_eclipse_uprotocol/rpc/rpcresult.py b/org_eclipse_uprotocol/rpc/rpcresult.py index 79f3138..138880a 100644 --- a/org_eclipse_uprotocol/rpc/rpcresult.py +++ b/org_eclipse_uprotocol/rpc/rpcresult.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,18 +9,22 @@ # 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 - +# +# 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 typing import Callable, TypeVar, Union diff --git a/org_eclipse_uprotocol/uri/factory/__init__.py b/org_eclipse_uprotocol/transport/builder/__init__.py similarity index 100% rename from org_eclipse_uprotocol/uri/factory/__init__.py rename to org_eclipse_uprotocol/transport/builder/__init__.py diff --git a/org_eclipse_uprotocol/transport/builder/uattributesbuilder.py b/org_eclipse_uprotocol/transport/builder/uattributesbuilder.py new file mode 100644 index 0000000..8f1b2a9 --- /dev/null +++ b/org_eclipse_uprotocol/transport/builder/uattributesbuilder.py @@ -0,0 +1,219 @@ +# ------------------------------------------------------------------------- + +# 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 org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes, UPriority, UMessageType +from org_eclipse_uprotocol.proto.uri_pb2 import * +from org_eclipse_uprotocol.proto.uuid_pb2 import UUID +from org_eclipse_uprotocol.uuid.factory.uuidfactory import * + + +class UAttributesBuilder: + """ + Construct the UAttributesBuilder with the configurations that are required for every payload transport. + + @param id Unique identifier for the message. + @param type Message type such as Publish a state change, RPC request or RPC response. + @param priority uProtocol Prioritization classifications. + """ + + def __init__(self, id: UUID, type: UMessageType, priority: UPriority): + self.id = id + self.type = type + self.priority = priority + self.ttl = None + self.token = None + self.sink = None + self.plevel = None + self.commstatus = None + self.reqid = None + + @staticmethod + def publish(priority: UPriority): + """ + Construct a UAttributesBuilder for a publish message. + @param priority The priority of the message. + @return Returns the UAttributesBuilder with the configured priority. + """ + if priority is None: + raise ValueError("Uri cannot be None.") + return UAttributesBuilder(Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_PUBLISH, priority) + + @staticmethod + def notification(priority: UPriority, sink: UUri): + """ + Construct a UAttributesBuilder for a notification message. + @param priority The priority of the message. + @param sink The destination URI. + @return Returns the UAttributesBuilder with the configured priority and sink. + """ + 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) + + @staticmethod + def request(priority: UPriority, sink: UUri, ttl: int): + """ + Construct a UAttributesBuilder for a request message. + @param priority The priority of the message. + @param sink The destination URI. + @param ttl The time to live in milliseconds. + @return Returns the UAttributesBuilder with the configured priority, sink and ttl. + """ + if priority is None: + raise ValueError("UPriority cannot be null.") + if sink is None: + raise ValueError("sink cannot be null.") + if ttl is None: + raise ValueError("ttl cannot be null.") + + return UAttributesBuilder(Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_REQUEST, priority).withTtl( + ttl).withSink(sink) + + @staticmethod + def response(priority: UPriority, sink: UUri, reqid: UUID): + """ + Construct a UAttributesBuilder for a response message. + @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. + @return Returns the UAttributesBuilder with the configured priority, sink and reqid. + """ + if priority is None: + raise ValueError("UPriority cannot be null.") + if sink is None: + raise ValueError("sink cannot be null.") + if reqid is None: + raise ValueError("reqid cannot be null.") + + return UAttributesBuilder(Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_RESPONSE, priority).withSink( + sink).withReqId(reqid) + + def withTtl(self, ttl: int): + """ + Add the time to live in milliseconds. + + @param ttl the time to live in milliseconds. + @return Returns the UAttributesBuilder with the configured ttl. + """ + self.ttl = ttl + return self + + def withToken(self, token: str): + """ + dd the authorization token used for TAP. + + @param token the authorization token used for TAP. + @return Returns the UAttributesBuilder with the configured token. + """ + self.token = token + return self + + def withSink(self, sink: UUri): + """ + Add the explicit destination URI. + + @param sink the explicit destination URI. + @return Returns the UAttributesBuilder with the configured sink. + """ + self.sink = sink + return self + + def withPermissionLevel(self, plevel: int): + """ + Add the permission level of the message. + + @param plevel the permission level of the message. + @return Returns the UAttributesBuilder with the configured plevel. + """ + self.plevel = plevel + return self + + def withCommStatus(self, commstatus: int): + """ + Add the communication status of the message. + + @param commstatus the communication status of the message. + @return Returns the UAttributesBuilder with the configured commstatus. + """ + self.commstatus = commstatus + return self + + def withReqId(self, reqid: UUID): + """ + Add the request ID. + + @param reqid the request ID. + @return Returns the UAttributesBuilder with the configured reqid. + """ + self.reqid = reqid + return self + + def build(self): + """ + Construct the UAttributes from the builder. + + @return Returns a constructed + """ + attributes = UAttributes(id=self.id, type=self.type, priority=self.priority) + if self.sink is not None: + if self.sink.HasField('authority'): + if self.sink.authority.HasField('id'): + attributes.sink.authority.id = self.sink.authority.id + if self.sink.authority.HasField('name'): + attributes.sink.authority.name = self.sink.authority.name + if self.sink.authority.HasField('ip'): + attributes.sink.authority.ip = self.sink.authority.ip + if self.sink.HasField('entity'): + attributes.sink.entity.name = self.sink.entity.name + if self.sink.entity.HasField('id'): + attributes.sink.entity.id = self.sink.entity.id + if self.sink.entity.HasField('version_major'): + attributes.sink.entity.version_major = self.sink.entity.version_major + if self.sink.entity.HasField('version_minor'): + attributes.sink.entity.version_minor = self.sink.entity.version_minor + if self.sink.HasField('resource'): + attributes.sink.resource.name = self.sink.resource.name + if self.sink.resource.HasField('id'): + attributes.sink.resource.id = self.sink.resource.id + if self.sink.resource.HasField('instance'): + attributes.sink.resource.instance = self.sink.resource.instance + if self.sink.resource.HasField('message'): + attributes.sink.resource.message = self.sink.resource.message + if self.ttl is not None: + attributes.ttl = self.ttl + if self.plevel is not None: + attributes.permission_level = self.plevel + if self.commstatus is not None: + attributes.commstatus = self.commstatus + if self.reqid is not None: + attributes.reqid = self.reqid + if self.token != None: + attributes.token = self.token + return attributes diff --git a/org_eclipse_uprotocol/transport/datamodel/uattributes.py b/org_eclipse_uprotocol/transport/datamodel/uattributes.py deleted file mode 100644 index 7699d75..0000000 --- a/org_eclipse_uprotocol/transport/datamodel/uattributes.py +++ /dev/null @@ -1,140 +0,0 @@ -# ------------------------------------------------------------------------- -# 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. - -# ------------------------------------------------------------------------- - -from org_eclipse_uprotocol.transport.datamodel.umessagetype import UMessageType -from org_eclipse_uprotocol.transport.datamodel.upriority import UPriority -from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.uuid.factory import UUID - - -class UAttributesBuilder: - - def __init__(self, id: UUID, type: UMessageType, priority: UPriority): - self.id = id - self.type = type - self.priority = priority - self.ttl = None - self.token = None - self.sink = None - self.plevel = None - self.commstatus = None - self.reqid = None - - def with_ttl(self, ttl: int): - self.ttl = ttl - return self - - def with_token(self, token: str): - self.token = token - return self - - def with_sink(self, sink: UUri): - self.sink = sink - return self - - def with_permission_level(self, plevel: int): - self.plevel = plevel - return self - - def with_comm_status(self, commstatus: int): - self.commstatus = commstatus - return self - - def with_reqid(self, reqid: UUID): - self.reqid = reqid - return self - - def build(self): - return UAttributes(self.id, self.type, self.priority, self.ttl, self.token, self.sink, self.plevel, - self.commstatus, self.reqid) - - -class UAttributes: - """ - When sending data over uTransport the basic API for send uses a source topic and the UPayload as the - data.
Any other information about the message is placed in the UAttributes class.
The UAttributes class - holds the additional information along with business methods for understanding more about the actual message - sent. UAttributes is the class that defines the Payload. It is the place for configuring time to live, - priority, security tokens and more.
Each UAttributes class defines a different type of message payload. The - payload can represent a simple published payload with some state change, Payload representing an RPC request or - Payload representing an RPC response. - """ - - def __init__(self, id: UUID, type: UMessageType, priority: UPriority, ttl: int, token: str, sink: UUri, plevel: int, - commstatus: int, reqid: UUID): - """ - Construct the transport UAttributes object.

- @param id:Unique identifier for the message. Required. - @param type:Message type such as Publish a state change, RPC request or RPC response. Required. - @param priority:Message priority. Required. - @param ttl:Time to live in milliseconds. - @param token:Authorization token used for TAP. - @param sink:Explicit destination URI, used in notifications and RPC messages. - @param plevel:Permission Level. - @param commstatus:Communication Status, used to indicate platform communication errors that occurred during - delivery. - @param reqid: Request ID, used to indicate the id of the RPC request that matches this RPC response. - """ - - # Required Attributes - self.id = id - self.type = type - self.priority = priority - # Optional Attributes - self.ttl = ttl - self.token = token - self.sink = sink - self.plevel = plevel - self.commstatus = commstatus - self.reqid = reqid - - @staticmethod - def empty(): - """ - Static factory method for creating an empty attributes object, to avoid working with null.

- @return: Returns an empty attributes that indicates that there are no added additional attributes to - configure.
An empty UAttributes is not valid, in the same way null is not valid, this is because - UAttributes has 3 required values - id, type and priority. - """ - return UAttributes(None, None, None, None, None, None, None, None, None) - - @staticmethod - def for_rpc_request(id: UUID, sink: UUri) -> UAttributesBuilder: - """ - Static factory method for creating a base UAttributes for an RPC request.

- @param id:id Unique identifier for the RPC request message. - @param sink:UUri describing the exact RPC command. - @return:Returns a base UAttributes that can be used to build an RPC request. - """ - return UAttributesBuilder(id, UMessageType.REQUEST, UPriority.REALTIME_INTERACTIVE).with_sink(sink) - - @staticmethod - def for_rpc_response(id: UUID, sink: UUri, reqid: UUID) -> UAttributesBuilder: - """ - Static factory method for creating a base UAttributes for an RPC response.

- @param id:Unique identifier for the RPC response message. - @param sink:UUri describing where the response needs to go. - @param reqid:The UUID of the message that this response is responding to. - @return: Returns a base UAttributes that can be used to build an RPC response. - """ - return UAttributesBuilder(id, UMessageType.RESPONSE, UPriority.REALTIME_INTERACTIVE).with_sink(sink).with_reqid( - reqid) diff --git a/org_eclipse_uprotocol/transport/datamodel/ulistener.py b/org_eclipse_uprotocol/transport/datamodel/ulistener.py index 2bc6189..24cc15f 100644 --- a/org_eclipse_uprotocol/transport/datamodel/ulistener.py +++ b/org_eclipse_uprotocol/transport/datamodel/ulistener.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,23 +9,28 @@ # 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 - +# +# 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 org_eclipse_uprotocol.transport.datamodel.uattributes import UAttributes +from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes +from org_eclipse_uprotocol.proto.uri_pb2 import UUri from org_eclipse_uprotocol.transport.datamodel.upayload import UPayload from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus -from org_eclipse_uprotocol.proto.uri_pb2 import UUri class UListener(ABC): diff --git a/org_eclipse_uprotocol/transport/datamodel/umessagetype.py b/org_eclipse_uprotocol/transport/datamodel/umessagetype.py deleted file mode 100644 index 8e7a6dc..0000000 --- a/org_eclipse_uprotocol/transport/datamodel/umessagetype.py +++ /dev/null @@ -1,69 +0,0 @@ -# ------------------------------------------------------------------------- - -# 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. - -# ------------------------------------------------------------------------- - -from enum import Enum, unique - - -@unique -class UMessageType(Enum): - """ - uProtocol defines message types. Using the message type, validation can be performed to ensure transport validity - of the data in the UAttributes. - """ - PUBLISH = (0, "pub.v1") # Publish or notification event - REQUEST = (1, "req.v1") # Request - RESPONSE = (2, "res.v1") # Response - - def __init__(self, value: int, name: str): - self.int_value = value - self.string_value = name - - def int_value(self): - return self.int_value - - def string_value(self): - return self.string_value - - @classmethod - def from_int(cls, value: int): - """ - Find the Message type from a numeric value. Mind you, it might not exist.

- @param value:numeric message type. - @return:Returns the UMessageType matching the numeric value. - """ - for item in cls: - if item.int_value == value: - return item - return None - - @classmethod - def from_string(cls, value: str): - """ - Find the Message type from a string value. Mind you, it might not exist. - @param value:string message type. - @return:Returns the UMessageType matching the string value. - """ - for item in cls: - if item.string_value == value: - return item - return None diff --git a/org_eclipse_uprotocol/transport/datamodel/upayload.py b/org_eclipse_uprotocol/transport/datamodel/upayload.py index 3cb049c..a042cbf 100644 --- a/org_eclipse_uprotocol/transport/datamodel/upayload.py +++ b/org_eclipse_uprotocol/transport/datamodel/upayload.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,21 +9,25 @@ # 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 - +# +# 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 typing import Optional -from org_eclipse_uprotocol.transport.datamodel.userializationhint import USerializationHint +from org_eclipse_uprotocol.proto.upayload_pb2 import UPayloadFormat class UPayload: @@ -32,14 +36,14 @@ class UPayload: """ EMPTY = None - def __init__(self, data: bytes, hint: Optional[USerializationHint] = None): + def __init__(self, data: bytes, hint: UPayloadFormat = None): """ Create a UPayload.

@param data:A byte array of the actual data. @param hint:Hint regarding the bytes contained within the UPayload """ self.data = data if data is not None else bytes() - self.hint = hint if hint is not None else USerializationHint.UNKNOWN + self.hint = hint if hint is not None else UPayloadFormat.UNKNOWN def get_data(self) -> bytes: """ @@ -48,7 +52,7 @@ def get_data(self) -> bytes: """ return self.data - def get_hint(self) -> USerializationHint: + def get_hint(self) -> UPayloadFormat: """ The hint regarding the bytes contained within the UPayload. @return:Returns the hint regarding the bytes contained within the UPayload. @@ -62,7 +66,7 @@ def empty(cls): @return: Returns an empty representation of UPayload. """ if cls.EMPTY is None: - cls.EMPTY = UPayload(bytes(), USerializationHint.UNKNOWN) + cls.EMPTY = UPayload(bytes(), UPayloadFormat.UNKNOWN) return cls.EMPTY def is_empty(self): @@ -88,5 +92,5 @@ def __str__(self): # Example usage if __name__ == "__main__": - payload = UPayload(b"example_data", USerializationHint.UNKNOWN) + payload = UPayload(b"example_data", UPayloadFormat.UNKNOWN) print(payload) diff --git a/org_eclipse_uprotocol/transport/datamodel/upriority.py b/org_eclipse_uprotocol/transport/datamodel/upriority.py deleted file mode 100644 index 7327bd4..0000000 --- a/org_eclipse_uprotocol/transport/datamodel/upriority.py +++ /dev/null @@ -1,73 +0,0 @@ -# ------------------------------------------------------------------------- - -# 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. - -# ------------------------------------------------------------------------- -from enum import Enum, unique - - -@unique -class UPriority(Enum): - # Low Priority. No bandwidth assurance such as File Transfer. - LOW = ("CS0", 0) - # Standard, undifferentiated application such as General (unclassified). - STANDARD = ("CS1", 1) - # Operations, Administration, and Management such as Streamer messages (sub, connect, etc…) - OPERATIONS = ("CS2", 2) - # Multimedia streaming such as Video Streaming - MULTIMEDIA_STREAMING = ("CS3", 3) - # Real-time interactive such as High priority (rpc events) - REALTIME_INTERACTIVE = ("CS4", 4) - # Signaling such as Important - SIGNALING = ("CS5", 5) - # Network control such as Safety Critical - NETWORK_CONTROL = ("CS6", 6) - - @property - def qos_string(self): - return self.value[0] - - @property - def int_value(self): - return self.value[1] - - @classmethod - def from_int(cls, value: int): - """ - Find the Priority matching the numeric value. Mind you, it might not exist.

- @param value:numeric priority value. - @return:Returns the Priority matching the numeric value. Mind you, it might not exist. - """ - for item in cls: - if item.value[1] == value: - return item - return None - - @classmethod - def from_string(cls, value: str): - """ - Find the Priority matching the QOS String value. Mind you, it might not exist.

- @param value:QOS String priority value. - @return:Returns the Priority matching the QOS String value. Mind you, it might not exist. - """ - for item in cls: - if item.value[0] == value: - return item - return None diff --git a/org_eclipse_uprotocol/transport/datamodel/userializationhint.py b/org_eclipse_uprotocol/transport/datamodel/userializationhint.py deleted file mode 100644 index d790ebf..0000000 --- a/org_eclipse_uprotocol/transport/datamodel/userializationhint.py +++ /dev/null @@ -1,79 +0,0 @@ -# ------------------------------------------------------------------------- - -# 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. - -# ------------------------------------------------------------------------- - -from enum import Enum, unique - - -@unique -class USerializationHint(Enum): - # Serialization hint is unknown - UNKNOWN = (0, "") - - # serialized com.google.protobuf.Any type - PROTOBUF = (1, "application/x-protobuf") - - # data is a UTF-8 string containing a JSON structure - JSON = (2, "application/json") - - # data is a UTF-8 string containing a JSON structure - SOMEIP = (3, "application/x-someip") - - # Raw binary data that has not been serialized - RAW = (4, "application/octet-stream") - - # Text Format - TEXT = (5, "text/plain") - - def __init__(self, hint_number: int, mime_type: str): - self.hint_number = hint_number - self.mime_type = mime_type - - def get_hint_number(self): - return self.hint_number - - def get_mime_type(self): - return self.mime_type - - @classmethod - def from_hint_number(cls, value: int): - """ - Find the serialization hint matching the mimeType value. Mind you, it might not exist.

- @param value:numeric hint value. - @return:Returns the USerializationHint matching the numeric value. - """ - for hint in cls: - if hint.get_hint_number() == value: - return hint - return None - - @classmethod - def from_mime_type(cls, value: str): - """ - Find the serialization hint matching the String value. Mind you, it might not exist.

- @param value:String hint value. - @return:Returns the USerializationHint matching the String value. - """ - for hint in cls: - if hint.get_mime_type() == value: - return hint - return None diff --git a/org_eclipse_uprotocol/transport/datamodel/ustatus.py b/org_eclipse_uprotocol/transport/datamodel/ustatus.py index c7514ac..8840bcd 100644 --- a/org_eclipse_uprotocol/transport/datamodel/ustatus.py +++ b/org_eclipse_uprotocol/transport/datamodel/ustatus.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,18 +9,22 @@ # 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 - +# +# 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 enum import Enum from typing import Optional diff --git a/org_eclipse_uprotocol/transport/utransport.py b/org_eclipse_uprotocol/transport/utransport.py index 50b6508..feef884 100644 --- a/org_eclipse_uprotocol/transport/utransport.py +++ b/org_eclipse_uprotocol/transport/utransport.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,26 +9,30 @@ # 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 - +# +# 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 org_eclipse_uprotocol.transport.datamodel.uattributes import UAttributes +from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes +from org_eclipse_uprotocol.proto.uri_pb2 import UEntity +from org_eclipse_uprotocol.proto.uri_pb2 import UUri from org_eclipse_uprotocol.transport.datamodel.ulistener import UListener from org_eclipse_uprotocol.transport.datamodel.upayload import UPayload from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus -from org_eclipse_uprotocol.proto.uri_pb2 import UEntity -from org_eclipse_uprotocol.proto.uri_pb2 import UUri class UTransport(ABC): diff --git a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py index 2c6e70d..9fd76d6 100644 --- a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py +++ b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,24 +9,27 @@ # 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 - +# +# 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 abstractmethod from datetime import datetime from enum import Enum -from org_eclipse_uprotocol.transport.datamodel.uattributes import UAttributes -from org_eclipse_uprotocol.transport.datamodel.umessagetype import UMessageType +from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes, UMessageType from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus, Code from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils @@ -54,9 +57,9 @@ def get_validator(attribute: UAttributes): """ if attribute.type is None: return Validators.PUBLISH.validator() - elif attribute.type == UMessageType.RESPONSE: + elif attribute.type == UMessageType.UMESSAGE_TYPE_RESPONSE: return Validators.RESPONSE.validator() - elif attribute.type == UMessageType.REQUEST: + elif attribute.type == UMessageType.UMESSAGE_TYPE_REQUEST: return Validators.REQUEST.validator() else: return Validators.PUBLISH.validator() @@ -115,7 +118,7 @@ def validate_permission_level(attr: UAttributes) -> UStatus: @param attr:UAttributes object containing the permission level to validate. @return:Returns a UStatus indicating if the permissionLevel is valid or not. """ - if attr.plevel and attr.plevel > 0: + if attr.permission_level and attr.permission_level > 0: return UStatus.ok() else: return UStatus.failed_with_msg_and_code("Invalid Permission Level", Code.INVALID_ARGUMENT) @@ -208,8 +211,9 @@ def validate_type(self, attributes_value: UAttributes) -> UStatus: @param attributes_value:UAttributes object containing the message type to validate. @return:Returns a UStatus that is success or failed with a failure message. """ - return UStatus.ok() if attributes_value.type == UMessageType.PUBLISH else UStatus.failed_with_msg_and_code( - f"Wrong Attribute Type [{attributes_value.type}]", Code.INVALID_ARGUMENT) + return UStatus.ok() if attributes_value.type == UMessageType.UMESSAGE_TYPE_PUBLISH else ( + UStatus.failed_with_msg_and_code( + f"Wrong Attribute Type [{attributes_value.type}]", Code.INVALID_ARGUMENT)) def __str__(self): return "UAttributesValidator.Publish" @@ -226,8 +230,9 @@ def validate_type(self, attributes_value: UAttributes) -> UStatus: @param attributes_value:UAttributes object containing the message type to validate. @return:Returns a UStatus that is success or failed with a failure message. """ - return UStatus.ok() if attributes_value.type == UMessageType.REQUEST else UStatus.failed_with_msg_and_code( - f"Wrong Attribute Type [{attributes_value.type}]", Code.INVALID_ARGUMENT) + return UStatus.ok() if attributes_value.type == UMessageType.UMESSAGE_TYPE_REQUEST else ( + UStatus.failed_with_msg_and_code( + f"Wrong Attribute Type [{attributes_value.type}]", Code.INVALID_ARGUMENT)) def validate_sink(self, attributes_value: UAttributes) -> UStatus: """ @@ -264,8 +269,9 @@ def validate_type(self, attributes_value: UAttributes) -> UStatus: @param attributes_value:UAttributes object containing the message type to validate. @return:Returns a UStatus that is success or failed with a failure message. """ - return UStatus.ok() if attributes_value.type == UMessageType.RESPONSE else UStatus.failed_with_msg_and_code( - f"Wrong Attribute Type [{attributes_value.type}]", Code.INVALID_ARGUMENT) + return UStatus.ok() if attributes_value.type == UMessageType.UMESSAGE_TYPE_RESPONSE else ( + UStatus.failed_with_msg_and_code( + f"Wrong Attribute Type [{attributes_value.type}]", Code.INVALID_ARGUMENT)) def validate_sink(self, attributes_value: UAttributes) -> UStatus: """ diff --git a/org_eclipse_uprotocol/uri/builder/__init__.py b/org_eclipse_uprotocol/uri/builder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/uri/builder/uresource_builder.py b/org_eclipse_uprotocol/uri/builder/uresource_builder.py index c74f573..0ba1f2d 100644 --- a/org_eclipse_uprotocol/uri/builder/uresource_builder.py +++ b/org_eclipse_uprotocol/uri/builder/uresource_builder.py @@ -1,3 +1,30 @@ +# ------------------------------------------------------------------------- + +# 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 org_eclipse_uprotocol.proto.uri_pb2 import UResource @@ -28,4 +55,3 @@ def from_id(id): raise ValueError("id cannot be None") return UResourceBuilder.for_rpc_request(id) if id < UResourceBuilder.MAX_RPC_ID else UResource(id=id) - diff --git a/org_eclipse_uprotocol/uri/serializer/longuriserializer.py b/org_eclipse_uprotocol/uri/serializer/longuriserializer.py index 639c0e3..9b13768 100644 --- a/org_eclipse_uprotocol/uri/serializer/longuriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/longuriserializer.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,18 +9,22 @@ # 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 - +# +# 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 re from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority diff --git a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py index bcea2f4..fdd940b 100644 --- a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,18 +9,22 @@ # 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 - +# +# 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 io from enum import Enum diff --git a/org_eclipse_uprotocol/uri/serializer/uriserializer.py b/org_eclipse_uprotocol/uri/serializer/uriserializer.py index cdf45b4..9e861ee 100644 --- a/org_eclipse_uprotocol/uri/serializer/uriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/uriserializer.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,18 +9,22 @@ # 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 - +# +# 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 re import T from typing import Optional @@ -69,16 +73,14 @@ def build_resolved(self, long_uri: str, micro_uri: bytes) -> Optional[UUri]: long_u_uri = LongUriSerializer().deserialize(long_uri) micro_u_uri = MicroUriSerializer().deserialize(micro_uri) + u_authority = micro_u_uri.authority + u_authority.name = long_u_uri.authority.name + u_entity = micro_u_uri.u_entity + u_entity.name = long_u_uri.entity.name - u_authority=micro_u_uri.authority - u_authority.name=long_u_uri.authority.name - - u_entity= micro_u_uri.u_entity - u_entity.name =long_u_uri.entity.name - - u_resource=long_u_uri.resource - u_resource.id=micro_u_uri.resource.id + u_resource = long_u_uri.resource + u_resource.id = micro_u_uri.resource.id - u_uri = UUri(authority=u_authority, entity=u_entity,resource= u_resource) + u_uri = UUri(authority=u_authority, entity=u_entity, resource=u_resource) return u_uri if UriValidator.is_resolved(u_uri) else None diff --git a/org_eclipse_uprotocol/uri/validator/urivalidator.py b/org_eclipse_uprotocol/uri/validator/urivalidator.py index 0cc9846..f7137c5 100644 --- a/org_eclipse_uprotocol/uri/validator/urivalidator.py +++ b/org_eclipse_uprotocol/uri/validator/urivalidator.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,18 +9,23 @@ # 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 - +# +# 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 org_eclipse_uprotocol.proto.uri_pb2 import UAuthority # ------------------------------------------------------------------------- + + +from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority from org_eclipse_uprotocol.proto.uri_pb2 import UEntity from org_eclipse_uprotocol.proto.uri_pb2 import UResource from org_eclipse_uprotocol.proto.uri_pb2 import UUri @@ -79,8 +84,7 @@ def validate_rpc_response(uri: UUri) -> UStatus: return status u_resource = uri.resource - if not (UriValidator.is_rpc_method( - u_resource) and u_resource.instance == UResource.for_rpc_response().instance): + if not UriValidator.is_rpc_response(uri): return UStatus.failed_with_msg_and_code("Invalid RPC response type.", Code.INVALID_ARGUMENT) return UStatus.ok() @@ -132,9 +136,9 @@ def is_rpc_response(uuri: UUri) -> bool: if uuri is None: raise ValueError("Uri cannot be None.") - resource = uuri.get_resource() - return UriValidator.is_rpc_method(uuri) and ( - (uuri.resource.instance.strip() != "" and "response" in resource.Instance) or resource.get_id() != 0) + return UriValidator.is_rpc_method(uuri) and (( + uuri.resource.instance.strip() != "" and "response" + in uuri.resource.instance) or uuri.resource.id != 0) @staticmethod def is_micro_form(uuri: UUri) -> bool: @@ -144,8 +148,8 @@ def is_micro_form(uuri: UUri) -> bool: @return:Returns true if this UUri can be serialized into a micro form UUri. """ return not UriValidator.is_empty(uuri) and ( - UriValidator.is_authority_empty(uuri.authority) or len(uuri.authority.ip) != 0 or len( - uuri.authority.id) != 0) and uuri.entity.id != 0 and uuri.resource.id != 0 + UriValidator.is_authority_empty(uuri.authority) or len(uuri.authority.ip) != 0 or len( + uuri.authority.id) != 0) and uuri.entity.id != 0 and uuri.resource.id != 0 @staticmethod def is_long_form(uuri: UUri) -> bool: @@ -155,8 +159,8 @@ def is_long_form(uuri: UUri) -> bool: @return:Returns true if this UUri can be serialized into a long form UUri. """ return ( - uuri.authority.name.strip() != "" and uuri.entity.name.strip() != "" and - uuri.resource.name.strip() != "") + uuri.authority.name.strip() != "" and uuri.entity.name.strip() != "" and uuri.resource.name.strip() + != "") @staticmethod def is_remote(uuri: UUri) -> bool: diff --git a/org_eclipse_uprotocol/uuid/factory/__init__.py b/org_eclipse_uprotocol/uuid/factory/__init__.py index 75039d5..ff4a2a0 100644 --- a/org_eclipse_uprotocol/uuid/factory/__init__.py +++ b/org_eclipse_uprotocol/uuid/factory/__init__.py @@ -5,7 +5,7 @@ from typing import Tuple -class UUID(uuid.UUID): +class PythonUUID(uuid.UUID): r"""UUID draft version objects""" def __init__( @@ -76,7 +76,7 @@ def _subsec_encode(value: int) -> int: _last_v8_timestamp = None -def uuid6(clock_seq: int = None) -> UUID: +def uuid6(clock_seq: int = None) -> PythonUUID: r"""UUID version 6 is a field-compatible version of UUIDv1, reordered for improved DB locality. It is expected that UUIDv6 will primarily be used in contexts where there are existing v1 UUIDs. Systems that do @@ -102,10 +102,10 @@ def uuid6(clock_seq: int = None) -> UUID: uuid_int |= time_low_and_version << 64 uuid_int |= (clock_seq & 0x3FFF) << 48 uuid_int |= secrets.randbits(48) - return UUID(int=uuid_int, version=6) + return PythonUUID(int=uuid_int, version=6) -def uuid7() -> UUID: +def uuid7() -> PythonUUID: r"""UUID version 7 features a time-ordered value field derived from the widely implemented and well known Unix Epoch timestamp source, the number of milliseconds seconds since midnight 1 Jan 1970 UTC, leap @@ -124,10 +124,10 @@ def uuid7() -> UUID: _last_v7_timestamp = timestamp_ms uuid_int = (timestamp_ms & 0xFFFFFFFFFFFF) << 80 uuid_int |= secrets.randbits(76) - return UUID(int=uuid_int, version=7) + return PythonUUID(int=uuid_int, version=7) -def uuid8() -> UUID: +def uuid8() -> PythonUUID: r"""UUID version 8 features a time-ordered value field derived from the widely implemented and well known Unix Epoch timestamp source, the number of nanoseconds seconds since midnight 1 Jan 1970 UTC, leap @@ -147,4 +147,4 @@ def uuid8() -> UUID: uuid_int |= subsec_a << 64 uuid_int |= subsec_b << 54 uuid_int |= secrets.randbits(54) - return UUID(int=uuid_int, version=8) + return PythonUUID(int=uuid_int, version=8) diff --git a/org_eclipse_uprotocol/uuid/factory/uuidfactory.py b/org_eclipse_uprotocol/uuid/factory/uuidfactory.py index c22ec1d..c4ee690 100644 --- a/org_eclipse_uprotocol/uuid/factory/uuidfactory.py +++ b/org_eclipse_uprotocol/uuid/factory/uuidfactory.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,22 +9,27 @@ # 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 - +# +# 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 time -import uuid -import org_eclipse_uprotocol.uuid.factory as uuid +from enum import Enum + +from org_eclipse_uprotocol.proto.uuid_pb2 import UUID +from org_eclipse_uprotocol.uuid.factory import * +from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils class UUIDFactory: @@ -39,29 +44,20 @@ def _create(self, instant): class UUIDv6Factory(UUIDFactory): - def _create(self, instant): - return uuid.uuid6() + def _create(self, instant) -> UUID: + python_uuid = uuid6(instant) + msb, lsb = UUIDUtils.get_msb_lsb(python_uuid) + return UUID(msb=msb, lsb=lsb) class UUIDv8Factory(UUIDFactory): - def _create(self, instant): - return uuid.uuid8() + def _create(self, instant) -> UUID: + python_uuid = uuid8() + msb, lsb = UUIDUtils.get_msb_lsb(python_uuid) + return UUID(msb=msb, lsb=lsb) -class Factories: +class Factories(): UUIDV6 = UUIDv6Factory() UPROTOCOL = UUIDv8Factory() - - -def is_uuid_version_6(uuid): - try: - return uuid.version == 6 - - except ValueError: - # Invalid UUID string - return False - - -def is_version_8(uuid): - return uuid.version == 8 diff --git a/org_eclipse_uprotocol/uuid/factory/uuidutils.py b/org_eclipse_uprotocol/uuid/factory/uuidutils.py index 720a1d4..2907fb2 100644 --- a/org_eclipse_uprotocol/uuid/factory/uuidutils.py +++ b/org_eclipse_uprotocol/uuid/factory/uuidutils.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,23 +9,28 @@ # 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 - +# +# 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 uuid from enum import Enum from typing import Optional -from org_eclipse_uprotocol.uuid.factory import UUID +from org_eclipse_uprotocol.proto.uuid_pb2 import UUID +from org_eclipse_uprotocol.uuid.factory import PythonUUID class Version(Enum): @@ -55,52 +60,52 @@ class UUIDUtils: UUID Utils class that provides utility methods for uProtocol IDs """ - @staticmethod - def toString(uuid_obj: UUID) -> str: - """ - Convert the UUID to a String. - @param uuid_obj:UUID object - @return:String representation of the UUID or Optional.empty() if the UUID is null. - """ - return str(uuid_obj) if uuid_obj is not None else None - - @staticmethod - def toBytes(uuid_obj: UUID) -> Optional[bytes]: - """ - Convert the UUID to byte array.

- @param uuid_obj:UUID object - @return:The byte array or Optional.empty() if the UUID is null. - """ - if uuid_obj is None: - return None - uuid_bytes = uuid_obj.bytes - return uuid_bytes - - @staticmethod - def fromBytes(bytes_list: bytes) -> Optional[UUID]: - """ - Convert the byte array to a UUID.

- @param bytes_list:The UUID in bytes format. - @return:UUIDv8 object built from the byte array or Optional.empty() if the byte array is null or not 16 bytes - long. - """ - if bytes_list is None or len(bytes_list) != 16: - return None - uuid_bytes = bytes(bytes_list) - return uuid.UUID(bytes=uuid_bytes) - - @staticmethod - def fromString(string: str) -> Optional[UUID]: - """ - Create a UUID from the passed string.

- @param string:the string representation of the uuid. - @return:The UUID object representation of the string or Optional.empty() if the string is null, empty, - or invalid. - """ - try: - return uuid.UUID(string) - except ValueError: - return None + # @staticmethod + # def toString(uuid_obj: UUID) -> str: + # """ + # Convert the UUID to a String. + # @param uuid_obj:UUID object + # @return:String representation of the UUID or Optional.empty() if the UUID is null. + # """ + # return str(uuid_obj) if uuid_obj is not None else None + # + # @staticmethod + # def toBytes(uuid_obj: UUID) -> Optional[bytes]: + # """ + # Convert the UUID to byte array.

+ # @param uuid_obj:UUID object + # @return:The byte array or Optional.empty() if the UUID is null. + # """ + # if uuid_obj is None: + # return None + # uuid_bytes = uuid_obj.bytes + # return uuid_bytes + # + # @staticmethod + # def fromBytes(bytes_list: bytes) -> Optional[UUID]: + # """ + # Convert the byte array to a UUID.

+ # @param bytes_list:The UUID in bytes format. + # @return:UUIDv8 object built from the byte array or Optional.empty() if the byte array is null or not 16 bytes + # long. + # """ + # if bytes_list is None or len(bytes_list) != 16: + # return None + # uuid_bytes = bytes(bytes_list) + # return uuid.UUID(bytes=uuid_bytes) + + # @staticmethod + # def fromString(string: str) -> Optional[UUID]: + # """ + # Create a UUID from the passed string.

+ # @param string:the string representation of the uuid. + # @return:The UUID object representation of the string or Optional.empty() if the string is null, empty, + # or invalid. + # """ + # try: + # return uuid.UUID(string) + # except ValueError: + # return None @staticmethod def getVersion(uuid_obj: UUID) -> Optional[Version]: @@ -111,7 +116,8 @@ def getVersion(uuid_obj: UUID) -> Optional[Version]: """ if uuid_obj is None: return None - return Version.getVersion(uuid_obj.version) + python_uuid = UUIDUtils.create_pythonuuid_from_msb_lsb(uuid_obj.msb, uuid_obj.lsb) + return Version.getVersion(python_uuid.version) @staticmethod def getVariant(uuid_obj: UUID) -> Optional[str]: @@ -122,7 +128,9 @@ def getVariant(uuid_obj: UUID) -> Optional[str]: """ if uuid_obj is None: return None - return uuid_obj.variant + python_uuid = UUIDUtils.create_pythonuuid_from_msb_lsb(uuid_obj.msb, uuid_obj.lsb) + + return python_uuid.variant @staticmethod def isUProtocol(uuid_obj: UUID) -> bool: @@ -131,6 +139,7 @@ def isUProtocol(uuid_obj: UUID) -> bool: @param uuid_obj:UUID object @return:true if is a uProtocol UUID or false if uuid passed is null or the UUID is not uProtocol format. """ + return UUIDUtils.getVersion(uuid_obj) == Version.VERSION_UPROTOCOL if uuid_obj is not None else False @staticmethod @@ -142,6 +151,7 @@ def isUuidv6(uuid_obj: UUID) -> bool: """ if uuid_obj is None: return False + return UUIDUtils.getVersion(uuid_obj) == Version.VERSION_TIME_ORDERED and UUIDUtils.getVariant( uuid_obj) == uuid.RFC_4122 if uuid_obj is not None else False @@ -152,6 +162,7 @@ def isuuid(uuid_obj: UUID) -> bool: @param uuid_obj: UUID object @return:true if is UUID version 6 or 8 """ + return UUIDUtils.isUProtocol(uuid_obj) or UUIDUtils.isUuidv6(uuid_obj) if uuid_obj is not None else False @staticmethod @@ -165,14 +176,33 @@ def getTime(uuid: UUID): version = UUIDUtils.getVersion(uuid) if uuid is None or version is None: return None + python_uuid = UUIDUtils.create_pythonuuid_from_msb_lsb(uuid.msb, uuid.lsb) if version == Version.VERSION_UPROTOCOL: - time = uuid.int >> 16 + time = python_uuid.int >> 16 elif version == Version.VERSION_TIME_ORDERED: try: # Convert 100-nanoseconds ticks to milliseconds - time = uuid.time // 10000 + time = python_uuid.time // 10000 except ValueError: return None return time + + @staticmethod + def get_msb_lsb(uuid: PythonUUID): + # Convert UUID to a 128-bit integer + uuid_int = int(uuid) + + # Extract most significant bits (first 64 bits) + msb = uuid_int >> 64 + + # Extract least significant bits (last 64 bits) + lsb = uuid_int & ((1 << 64) - 1) + + return msb, lsb + + @staticmethod + def create_pythonuuid_from_msb_lsb(msb: int, lsb: int) -> PythonUUID: + combined_int = (msb << 64) + lsb + return PythonUUID(int=combined_int) diff --git a/org_eclipse_uprotocol/uuid/serializer/__init__.py b/org_eclipse_uprotocol/uuid/serializer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol/uuid/serializer/longuuidserializer.py b/org_eclipse_uprotocol/uuid/serializer/longuuidserializer.py new file mode 100644 index 0000000..6d3e3a4 --- /dev/null +++ b/org_eclipse_uprotocol/uuid/serializer/longuuidserializer.py @@ -0,0 +1,64 @@ +# ------------------------------------------------------------------------- + +# 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 org_eclipse_uprotocol.proto.uuid_pb2 import UUID +from org_eclipse_uprotocol.uuid.factory import PythonUUID +from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils +from org_eclipse_uprotocol.uuid.serializer.uuidserializer import UuidSerializer + + +class LongUuidSerializer(UuidSerializer): + """ + UUID Serializer implementation used to serialize/deserialize UUIDs to/from a string + """ + + @staticmethod + def instance(): + return LongUuidSerializer() + + def deserialize(self, string_uuid: str) -> UUID: + """ + Deserialize from the string format to a UUID. + :param string_uuid: Serialized UUID in string format. + :return: Returns a UUID object from the serialized format from the wire. + """ + if not string_uuid or string_uuid.isspace(): + return UUID() # Return default UUID if string is empty or whitespace + try: + msb, lsb = UUIDUtils.get_msb_lsb(PythonUUID(string_uuid)) + return UUID(msb=msb, lsb=lsb) + except ValueError: + return UUID() # Return default UUID in case of parsing failure + + def serialize(self, uuid: UUID) -> str: + """ + Serialize from a UUID to a string format. + :param uuid: UUID object to be serialized to a string. + :return: Returns the UUID in the string serialized format. + """ + pythonuuid = UUIDUtils.create_pythonuuid_from_msb_lsb(uuid.msb, uuid.lsb) + return str(pythonuuid) if uuid else '' diff --git a/org_eclipse_uprotocol/uuid/serializer/microuuidserializer.py b/org_eclipse_uprotocol/uuid/serializer/microuuidserializer.py new file mode 100644 index 0000000..8c8c485 --- /dev/null +++ b/org_eclipse_uprotocol/uuid/serializer/microuuidserializer.py @@ -0,0 +1,66 @@ +# ------------------------------------------------------------------------- + +# 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 struct + +from org_eclipse_uprotocol.proto.uuid_pb2 import UUID +from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils +from org_eclipse_uprotocol.uuid.serializer.uuidserializer import UuidSerializer + + +class MicroUuidSerializer(UuidSerializer): + """ + UUID Serializer implementation used to serialize/deserialize UUIDs to/from bytes + """ + + @staticmethod + def instance(): + return MicroUuidSerializer() + + def deserialize(self, uuid_bytes: bytes) -> UUID: + """ + Deserialize from the bytes format to a UUID. + :param uuid_bytes: Serialized UUID in bytes format. + :return: Returns a UUID object from the serialized format from the wire. + """ + if not uuid_bytes or len(uuid_bytes) != 16: + return UUID() # Return default UUID if bytes are empty or not 16 bytes + + msb, lsb = struct.unpack('>QQ', uuid_bytes) + return UUID(msb=msb, lsb=lsb) + + def serialize(self, uuid: UUID) -> bytes: + """ + Serialize from a UUID to bytes format. + :param uuid: UUID object to be serialized to bytes. + :return: Returns the UUID in the bytes serialized format. + """ + if uuid is None: + return b'\x00' * 16 # Return 16 zero bytes for a None UUID + pythonuuid = UUIDUtils.create_pythonuuid_from_msb_lsb(uuid.msb, uuid.lsb) + msb, lsb = divmod(pythonuuid.int, 2 ** 64) + return struct.pack('>QQ', msb, lsb) diff --git a/org_eclipse_uprotocol/uuid/serializer/uuidserializer.py b/org_eclipse_uprotocol/uuid/serializer/uuidserializer.py new file mode 100644 index 0000000..ebc1111 --- /dev/null +++ b/org_eclipse_uprotocol/uuid/serializer/uuidserializer.py @@ -0,0 +1,57 @@ +# ------------------------------------------------------------------------- + +# 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 typing import TypeVar, Generic + +from org_eclipse_uprotocol.proto.uuid_pb2 import UUID + +T = TypeVar('T') + + +class UuidSerializer(ABC, Generic[T]): + """ + UUID Serializer interface used to serialize/deserialize UUIDs to/from either Long (string) or micro (bytes) form + """ + + @abstractmethod + def deserialize(self, uuid: T) -> UUID: + """ + Deserialize from the format to a UUID. + :param uuid: Serialized UUID. + :return: Returns a UUID object from the serialized format from the wire. + """ + pass # Implement your deserialization logic here + + @abstractmethod + def serialize(self, uuid: UUID) -> T: + """ + Serialize from a UUID to a specific serialization format. + :param uuid: UUID object to be serialized to the format T. + :return: Returns the UUID in the transport serialized format. + """ + pass # Implement your serialization logic here diff --git a/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py b/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py index d71527c..ce3fc71 100644 --- a/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py +++ b/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # 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 @@ -9,27 +9,31 @@ # 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 - +# +# 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 collections import namedtuple from enum import Enum -from org_eclipse_uprotocol.uuid.factory import UUID -from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils +from org_eclipse_uprotocol.proto.uuid_pb2 import UUID +from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils, Version class UuidVariant(Enum): - VARIANT_RFC_4122 = 0 + VARIANT_RFC_4122 = "RFC 4122" class ValidationResult(namedtuple('ValidationResult', ['is_failure', 'message'])): @@ -65,16 +69,14 @@ def validate(self, uuid: UUID) -> ValidationResult: return ValidationResult.failure(f"Invalid argument value: {error_message}") def validate_version(self, uuid: UUID) -> ValidationResult: - return ValidationResult.failure("Invalid UUID Version") # Override in subclasses + raise NotImplementedError def validate_time(self, uuid: UUID) -> ValidationResult: - time = uuid.time + time = UUIDUtils.getTime(uuid) return ValidationResult.success() if time > 0 else ValidationResult.failure("Invalid UUID Time") def validate_variant(self, uuid: UUID) -> ValidationResult: - variant = uuid.variant - return ValidationResult.failure( - "Invalid UUID Variant") if variant != UuidVariant.VARIANT_RFC_4122 else ValidationResult.success() + raise NotImplementedError class InvalidValidator(UuidValidator): @@ -87,16 +89,23 @@ def validate_variant(self, uuid: UUID) -> ValidationResult: class UUIDv6Validator(UuidValidator): def validate_version(self, uuid: UUID) -> ValidationResult: - return ValidationResult.success() if uuid.version == 6 else ValidationResult.failure("Not a UUIDv6 Version") + version = UUIDUtils.getVersion(uuid) + return ValidationResult.success() if version and version == Version.VERSION_TIME_ORDERED else ( + ValidationResult.failure( + "Not a UUIDv6 Version")) def validate_variant(self, uuid: UUID) -> ValidationResult: - return ValidationResult.success() if "RFC 4122" in uuid.variant else ValidationResult.failure( + variant = UUIDUtils.getVariant(uuid) + return ValidationResult.success() if variant and "RFC 4122" in variant else ValidationResult.failure( "Invalid UUIDv6 variant") class UUIDv8Validator(UuidValidator): def validate_version(self, uuid: UUID) -> ValidationResult: - return ValidationResult.success() if uuid.version == 8 else ValidationResult.failure("Invalid UUIDv8 Version") + version = UUIDUtils.getVersion(uuid) + return ValidationResult.success() if version and version == Version.VERSION_UPROTOCOL else ( + ValidationResult.failure( + "Invalid UUIDv8 Version")) def validate_variant(self, uuid: UUID) -> ValidationResult: return ValidationResult.success() From 73b05b84897e0332c85cecb9805397a5571df680 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 16 Nov 2023 14:38:55 -0500 Subject: [PATCH 21/47] Added uri test cases --- .../uri/serializer/longuriserializer.py | 35 +- .../uri/serializer/microuriserializer.py | 2 +- .../uri/serializer/uriserializer.py | 13 +- .../uri/validator/urivalidator.py | 4 +- org_eclipse_uprotocol_tests/__init__.py | 0 org_eclipse_uprotocol_tests/uri/__init__.py | 0 .../uri/serializer/__init__.py | 0 .../uri/serializer/longuriserializertest.py | 488 ++++++++++++++++++ .../uri/serializer/uriserializertest.py | 45 ++ 9 files changed, 568 insertions(+), 19 deletions(-) create mode 100644 org_eclipse_uprotocol_tests/__init__.py create mode 100644 org_eclipse_uprotocol_tests/uri/__init__.py create mode 100644 org_eclipse_uprotocol_tests/uri/serializer/__init__.py create mode 100644 org_eclipse_uprotocol_tests/uri/serializer/longuriserializertest.py create mode 100644 org_eclipse_uprotocol_tests/uri/serializer/uriserializertest.py diff --git a/org_eclipse_uprotocol/uri/serializer/longuriserializer.py b/org_eclipse_uprotocol/uri/serializer/longuriserializer.py index 9b13768..c35363d 100644 --- a/org_eclipse_uprotocol/uri/serializer/longuriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/longuriserializer.py @@ -54,21 +54,22 @@ def serialize(self, uri: UUri) -> str: sb = [] - if not UriValidator.is_authority_empty(uri.authority): + if uri.HasField('authority'): sb.append(self.build_authority_part_of_uri(uri.authority)) sb.append("/") sb.append(self.build_software_entity_part_of_uri(uri.entity)) - sb.append(self.build_resource_part_of_uri(uri.resource)) + sb.append(self.build_resource_part_of_uri(uri)) return re.sub('/+$', '', "".join(sb)) @staticmethod - def build_resource_part_of_uri(u_resource: UResource) -> str: + def build_resource_part_of_uri(uuri: UUri) -> str: - if UriValidator.is_resource_empty(u_resource): + if not uuri.HasField('resource'): return "" + u_resource = uuri.resource sb = "/" + u_resource.name @@ -118,10 +119,11 @@ def deserialize(self, u_protocol_uri: str) -> UUri: """ if u_protocol_uri is None or u_protocol_uri.strip() == "": return UUri() - uri = u_protocol_uri[u_protocol_uri.index(":") + 1:] if ":" in u_protocol_uri else u_protocol_uri.replace('\\', - '/') + uri = u_protocol_uri[u_protocol_uri.index(":") + 1:] \ + if ":" in u_protocol_uri else u_protocol_uri.replace('\\', '/') + is_local = not uri.startswith("//") - uri_parts = uri.split("/") + uri_parts = LongUriSerializer.remove_empty(uri.split("/")) number_of_parts_in_uri = len(uri_parts) if number_of_parts_in_uri == 0 or number_of_parts_in_uri == 1: @@ -165,9 +167,10 @@ def deserialize(self, u_protocol_uri: str) -> UUri: new_uri = UUri(entity=u_entity_builder) if u_authority is not None: - new_uri.authority = u_authority + new_uri.authority.CopyFrom(u_authority) + if u_resource is not None: - new_uri.resource = u_resource + new_uri.resource.CopyFrom(u_resource) return new_uri @@ -181,10 +184,10 @@ def parse_from_string(resource_string: str) -> UResource: if resource_string is None or resource_string.strip() == "": raise ValueError("Resource must have a command name.") - parts = resource_string.split("#") + parts = LongUriSerializer.remove_empty(resource_string.split("#")) name_and_instance = parts[0] - name_and_instance_parts = name_and_instance.split(".") + name_and_instance_parts = LongUriSerializer.remove_empty(name_and_instance.split(".")) resource_name = name_and_instance_parts[0] resource_instance = name_and_instance_parts[1] if len(name_and_instance_parts) > 1 else None resource_message = parts[1] if len(parts) > 1 else None @@ -196,3 +199,13 @@ def parse_from_string(resource_string: str) -> UResource: u_resource.message = resource_message return u_resource + + @staticmethod + def remove_empty(parts): + result = parts[:] + + # Iterate through the list in reverse and remove empty strings + while result and result[-1] == '': + result.pop() + + return result diff --git a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py index fdd940b..fa51064 100644 --- a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py @@ -189,6 +189,6 @@ def deserialize(self, micro_uri: bytes) -> UUri: uri = UUri(entity=UEntity(id=ue_id, version_major=ui_version), resource=UResourceBuilder.from_id(u_resource_id)) if u_authority is not None: - uri.authority = u_authority + uri.authority.CopyFrom(u_authority) return uri diff --git a/org_eclipse_uprotocol/uri/serializer/uriserializer.py b/org_eclipse_uprotocol/uri/serializer/uriserializer.py index 9e861ee..ca053e2 100644 --- a/org_eclipse_uprotocol/uri/serializer/uriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/uriserializer.py @@ -28,8 +28,7 @@ from abc import ABC, abstractmethod from re import T from typing import Optional - -from org_eclipse_uprotocol.proto.uri_pb2 import UUri +from org_eclipse_uprotocol.proto.uri_pb2 import UUri,UAuthority,UEntity,UResource from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator @@ -72,14 +71,18 @@ def build_resolved(self, long_uri: str, micro_uri: bytes) -> Optional[UUri]: from org_eclipse_uprotocol.uri.serializer.microuriserializer import MicroUriSerializer long_u_uri = LongUriSerializer().deserialize(long_uri) micro_u_uri = MicroUriSerializer().deserialize(micro_uri) + u_authority = UAuthority() + u_authority.CopyFrom(micro_u_uri.authority) - u_authority = micro_u_uri.authority u_authority.name = long_u_uri.authority.name - u_entity = micro_u_uri.u_entity + u_entity = UEntity() + u_entity.CopyFrom(micro_u_uri.entity) + u_entity.name = long_u_uri.entity.name - u_resource = long_u_uri.resource + u_resource = UResource() + u_resource.CopyFrom(long_u_uri.resource) u_resource.id = micro_u_uri.resource.id u_uri = UUri(authority=u_authority, entity=u_entity, resource=u_resource) diff --git a/org_eclipse_uprotocol/uri/validator/urivalidator.py b/org_eclipse_uprotocol/uri/validator/urivalidator.py index f7137c5..5a73532 100644 --- a/org_eclipse_uprotocol/uri/validator/urivalidator.py +++ b/org_eclipse_uprotocol/uri/validator/urivalidator.py @@ -163,5 +163,5 @@ def is_long_form(uuri: UUri) -> bool: != "") @staticmethod - def is_remote(uuri: UUri) -> bool: - return not UriValidator.is_authority_empty(uuri.authority) + def is_remote(authority: UAuthority) -> bool: + return not UriValidator.is_authority_empty(authority) diff --git a/org_eclipse_uprotocol_tests/__init__.py b/org_eclipse_uprotocol_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/uri/__init__.py b/org_eclipse_uprotocol_tests/uri/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/uri/serializer/__init__.py b/org_eclipse_uprotocol_tests/uri/serializer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/uri/serializer/longuriserializertest.py b/org_eclipse_uprotocol_tests/uri/serializer/longuriserializertest.py new file mode 100644 index 0000000..5352044 --- /dev/null +++ b/org_eclipse_uprotocol_tests/uri/serializer/longuriserializertest.py @@ -0,0 +1,488 @@ +import unittest + +from org_eclipse_uprotocol.uri.builder.uresource_builder import UResourceBuilder +from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from org_eclipse_uprotocol.proto.uri_pb2 import UEntity, UUri, UAuthority, UResource +from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator + + +class LongUriSerializerTest(unittest.TestCase): + + def test_using_the_serializers(self): + uri = UUri(entity=UEntity(name="hartley"), resource=UResourceBuilder.for_rpc_request("raise")) + + str_uri = LongUriSerializer().serialize(uri) + self.assertEqual("/hartley//rpc.raise", str_uri) + uri2 = LongUriSerializer().deserialize(str_uri) + self.assertEqual(uri, uri2) + + def test_parse_protocol_uri_when_is_null(self): + uri = LongUriSerializer().deserialize(None) + self.assertTrue(UriValidator.is_empty(uri)) + self.assertFalse(UriValidator.is_resolved(uri)) + self.assertFalse(UriValidator.is_long_form(uri)) + + def test_parse_protocol_uri_when_is_empty_string(self): + uri = '' + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_empty(uuri)) + uri2 = LongUriSerializer().serialize(None) + self.assertTrue(len(uri2) == 0) + + def test_parse_protocol_uri_with_schema_and_slash(self): + uri = "/" + uuri = LongUriSerializer().deserialize(uri) + self.assertFalse(uuri.HasField('authority')) + self.assertTrue(UriValidator.is_empty(uuri)) + self.assertFalse(uuri.HasField('resource')) + self.assertFalse(uuri.HasField('entity')) + uri2 = LongUriSerializer().serialize(UUri()) + self.assertTrue(len(uri2) == 0) + + def test_parse_protocol_uri_with_schema_and_double_slash(self): + uri = "//" + uuri = LongUriSerializer().deserialize(uri) + self.assertFalse(uuri.HasField('authority')) + self.assertFalse(uuri.HasField('resource')) + self.assertFalse(uuri.HasField('entity')) + self.assertTrue(UriValidator.is_empty(uuri)) + + def test_parse_protocol_uri_with_schema_and_3_slash_and_something(self): + uri = "///body.access" + uuri = LongUriSerializer().deserialize(uri) + self.assertFalse(uuri.HasField('authority')) + self.assertFalse(uuri.HasField('resource')) + self.assertFalse(uuri.HasField('entity')) + self.assertTrue(UriValidator.is_empty(uuri)) + self.assertNotEqual("body.access", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + + def test_parse_protocol_uri_with_schema_and_4_slash_and_something(self): + uri = "////body.access" + uuri = LongUriSerializer().deserialize(uri) + + self.assertFalse(UriValidator.is_remote(uuri.authority)) + self.assertFalse(uuri.HasField('resource')) + self.assertFalse(uuri.HasField('entity')) + self.assertTrue(len(uuri.entity.name) == 0) + self.assertEqual(0, uuri.entity.version_major) + + def test_parse_protocol_uri_with_schema_and_5_slash_and_something(self): + uri = "/////body.access" + uuri = LongUriSerializer().deserialize(uri) + self.assertFalse(UriValidator.is_remote(uuri.authority)) + self.assertFalse(uuri.HasField('resource')) + self.assertFalse(uuri.HasField('entity')) + self.assertTrue(UriValidator.is_empty(uuri)) + + def test_parse_protocol_uri_with_schema_and_6_slash_and_something(self): + uri = "//////body.access" + uuri = LongUriSerializer().deserialize(uri) + + self.assertFalse(UriValidator.is_remote(uuri.authority)) + self.assertTrue(UriValidator.is_empty(uuri)) + + def test_parse_protocol_uri_with_local_service_no_version(self): + uri = "/body.access" + uuri = LongUriSerializer().deserialize(uri) + + self.assertFalse(UriValidator.is_remote(uuri.authority)) + self.assertEqual("body.access", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + self.assertEqual(0, uuri.entity.version_minor) + self.assertFalse(uuri.HasField('resource')) + + def test_parse_protocol_uri_with_local_service_with_version(self): + uri = "/body.access/1" + uuri = LongUriSerializer().deserialize(uri) + + self.assertFalse(UriValidator.is_remote(uuri.authority)) + self.assertEqual("body.access", uuri.entity.name) + self.assertEqual(1, uuri.entity.version_major) + self.assertFalse(uuri.HasField('resource')) + + def test_parse_protocol_uri_with_local_service_no_version_with_resource_name_only(self): + uri = "/body.access//door" + uuri = LongUriSerializer().deserialize(uri) + + self.assertFalse(UriValidator.is_remote(uuri.authority)) + self.assertEqual("body.access", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + self.assertEqual(0, uuri.entity.version_minor) + self.assertEqual("door", uuri.resource.name) + self.assertTrue(len(uuri.resource.instance) == 0) + self.assertTrue(len(uuri.resource.message) == 0) + + def test_parse_protocol_uri_with_local_service_with_version_with_resource_name_only(self): + uri = "/body.access/1/door" + uuri = LongUriSerializer().deserialize(uri) + self.assertFalse(UriValidator.is_remote(uuri.authority)) + self.assertEqual("body.access", uuri.entity.name) + self.assertEqual(1, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertTrue(len(uuri.resource.instance) == 0) + self.assertTrue(len(uuri.resource.message) == 0) + + def test_parse_protocol_uri_with_local_service_no_version_with_resource_with_instance(self): + uri = "/body.access//door.front_left" + uuri = LongUriSerializer().deserialize(uri) + self.assertFalse(UriValidator.is_remote(uuri.authority)) + self.assertEqual("body.access", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance) == 0) + self.assertEqual("front_left", uuri.resource.instance) + self.assertTrue(len(uuri.resource.message) == 0) + + def test_parse_protocol_uri_with_local_service_with_version_with_resource_with_getMessage(self): + uri = "/body.access/1/door.front_left" + uuri = LongUriSerializer().deserialize(uri) + self.assertFalse(UriValidator.is_remote(uuri.authority)) + self.assertEqual("body.access", uuri.entity.name) + self.assertNotEqual(0, uuri.entity.version_major) + self.assertEqual(1, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance) == 0) + self.assertEqual("front_left", uuri.resource.instance) + self.assertTrue(len(uuri.resource.message) == 0) + + def test_parse_protocol_uri_with_local_service_no_version_with_resource_with_instance_and_getMessage(self): + uri = "/body.access//door.front_left#Door" + uuri = LongUriSerializer().deserialize(uri) + self.assertFalse(UriValidator.is_remote(uuri.authority)) + self.assertEqual("body.access", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance) == 0) + self.assertEqual("front_left", uuri.resource.instance) + self.assertFalse(len(uuri.resource.message) == 0) + self.assertEqual("Door", uuri.resource.message) + + def test_parse_protocol_uri_with_local_service_with_version_with_resource_with_instance_and_getMessage(self): + uri = "/body.access/1/door.front_left#Door" + uuri = LongUriSerializer().deserialize(uri) + self.assertFalse(UriValidator.is_remote(uuri.authority)) + self.assertEqual("body.access", uuri.entity.name) + self.assertNotEqual(0, uuri.entity.version_major) + self.assertEqual(1, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance) == 0) + self.assertEqual("front_left", uuri.resource.instance) + self.assertFalse(len(uuri.resource.message) == 0) + self.assertEqual("Door", uuri.resource.message) + + def test_parse_protocol_rpc_uri_with_local_service_no_version(self): + uri = "/petapp//rpc.response" + uuri = LongUriSerializer().deserialize(uri) + self.assertFalse(UriValidator.is_remote(uuri.authority)) + self.assertEqual("petapp", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + self.assertEqual("rpc", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance) == 0) + self.assertEqual("response", uuri.resource.instance) + self.assertTrue(len(uuri.resource.message) == 0) + + def test_parse_protocol_rpc_uri_with_local_service_with_version(self): + uri = "/petapp/1/rpc.response" + uuri = LongUriSerializer().deserialize(uri) + self.assertFalse(UriValidator.is_remote(uuri.authority)) + self.assertEqual("petapp", uuri.entity.name) + self.assertNotEqual(0, uuri.entity.version_major) + self.assertEqual(1, uuri.entity.version_major) + self.assertEqual("rpc", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance) == 0) + self.assertEqual("response", uuri.resource.instance) + self.assertTrue(len(uuri.resource.message) == 0) + + def test_parse_protocol_uri_with_remote_service_only_device_and_domain(self): + uri = "//VCU.MY_CAR_VIN" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("VCU.MY_CAR_VIN", uuri.authority.name) + + def test_parse_protocol_uri_with_remote_service_only_device_and_cloud_domain(self): + uri = "//cloud.uprotocol.example.com" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("cloud.uprotocol.example.com", uuri.authority.name) + self.assertFalse(uuri.HasField('entity')) + self.assertFalse(uuri.HasField('resource')) + + def test_parse_protocol_uri_with_remote_service_no_version(self): + uri = "//VCU.MY_CAR_VIN/body.access" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("VCU.MY_CAR_VIN", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("body.access", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + self.assertFalse(uuri.HasField('resource')) + + def test_parse_protocol_uri_with_remote_cloud_service_no_version(self): + uri = "//cloud.uprotocol.example.com/body.access" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("cloud.uprotocol.example.com", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("body.access", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + self.assertFalse(uuri.HasField('resource')) + + def test_parse_protocol_uri_with_remote_service_with_version(self): + uri = "//VCU.MY_CAR_VIN/body.access/1" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("VCU.MY_CAR_VIN", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("body.access", uuri.entity.name) + self.assertNotEqual(0, uuri.entity.version_major) + self.assertEqual(1, uuri.entity.version_major) + self.assertFalse(uuri.HasField('resource')) + + def test_parse_protocol_uri_with_remote_cloud_service_with_version(self): + uri = "//cloud.uprotocol.example.com/body.access/1" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("cloud.uprotocol.example.com", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("body.access", uuri.entity.name) + self.assertNotEqual(0, uuri.entity.version_major) + self.assertEqual(1, uuri.entity.version_major) + self.assertFalse(uuri.HasField('resource')) + + def test_parse_protocol_uri_with_remote_service_no_version_with_resource_name_only(self): + uri = "//VCU.MY_CAR_VIN/body.access//door" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("VCU.MY_CAR_VIN", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("body.access", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertTrue(len(uuri.resource.instance) == 0) + self.assertTrue(len(uuri.resource.message) == 0) + + def test_parse_protocol_uri_with_remote_cloud_service_no_version_with_resource_name_only(self): + uri = "//cloud.uprotocol.example.com/body.access//door" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("cloud.uprotocol.example.com", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("body.access", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertTrue(len(uuri.resource.instance) == 0) + self.assertTrue(len(uuri.resource.message) == 0) + + def test_parse_protocol_uri_with_remote_service_with_version_with_resource_name_only(self): + uri = "//VCU.MY_CAR_VIN/body.access/1/door" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("VCU.MY_CAR_VIN", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("body.access", uuri.entity.name) + self.assertNotEqual(0, uuri.entity.version_major) + self.assertEqual(1, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertTrue(len(uuri.resource.instance) == 0) + self.assertTrue(len(uuri.resource.message) == 0) + + def test_parse_protocol_uri_with_remote_service_cloud_with_version_with_resource_name_only(self): + uri = "//cloud.uprotocol.example.com/body.access/1/door" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("cloud.uprotocol.example.com", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("body.access", uuri.entity.name) + self.assertNotEqual(0, uuri.entity.version_major) + self.assertEqual(1, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertTrue(len(uuri.resource.instance) == 0) + self.assertTrue(len(uuri.resource.message) == 0) + + def test_parse_protocol_uri_with_remote_service_no_version_with_resource_and_instance_no_getMessage(self): + uri = "//VCU.MY_CAR_VIN/body.access//door.front_left" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("VCU.MY_CAR_VIN", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("body.access", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance) == 0) + self.assertEqual("front_left", uuri.resource.instance) + self.assertTrue(len(uuri.resource.message) == 0) + + def test_parse_protocol_uri_with_remote_service_with_version_with_resource_and_instance_no_getMessage(self): + uri = "//VCU.MY_CAR_VIN/body.access/1/door.front_left" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("VCU.MY_CAR_VIN", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("body.access", uuri.entity.name) + self.assertNotEqual(0, uuri.entity.version_major) + self.assertEqual(1, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance) == 0) + self.assertEqual("front_left", uuri.resource.instance) + self.assertTrue(len(uuri.resource.message) == 0) + + def test_parse_protocol_uri_with_remote_service_no_version_with_resource_and_instance_and_getMessage(self): + uri = "//VCU.MY_CAR_VIN/body.access//door.front_left#Door" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("VCU.MY_CAR_VIN", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("body.access", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance) == 0) + self.assertEqual("front_left", uuri.resource.instance) + self.assertFalse(len(uuri.resource.message) == 0) + self.assertEqual("Door", uuri.resource.message) + + def test_parse_protocol_uri_with_remote_cloud_service_no_version_with_resource_and_instance_and_getMessage(self): + uri = "//cloud.uprotocol.example.com/body.access//door.front_left#Door" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("cloud.uprotocol.example.com", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("body.access", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance) == 0) + self.assertEqual("front_left", uuri.resource.instance) + self.assertFalse(len(uuri.resource.message) == 0) + self.assertEqual("Door", uuri.resource.message) + + def test_parse_protocol_uri_with_remote_service_with_version_with_resource_and_instance_and_getMessage(self): + uri = "//VCU.MY_CAR_VIN/body.access/1/door.front_left#Door" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("VCU.MY_CAR_VIN", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("body.access", uuri.entity.name) + self.assertNotEqual(0, uuri.entity.version_major) + self.assertEqual(1, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance) == 0) + self.assertEqual("front_left", uuri.resource.instance) + self.assertFalse(len(uuri.resource.message) == 0) + self.assertEqual("Door", uuri.resource.message) + + def test_parse_protocol_uri_with_remote_cloud_service_with_version_with_resource_and_instance_and_getMessage(self): + uri = "//cloud.uprotocol.example.com/body.access/1/door.front_left#Door" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("cloud.uprotocol.example.com", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("body.access", uuri.entity.name) + self.assertNotEqual(0, uuri.entity.version_major) + self.assertEqual(1, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance) == 0) + self.assertEqual("front_left", uuri.resource.instance) + self.assertFalse(len(uuri.resource.message) == 0) + self.assertEqual("Door", uuri.resource.message) + + def test_parse_protocol_uri_with_remote_service_with_version_with_resource_with_message_device_no_domain(self): + uri = "//VCU/body.access/1/door.front_left" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("VCU", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("body.access", uuri.entity.name) + self.assertNotEqual(0, uuri.entity.version_major) + self.assertEqual(1, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance) == 0) + self.assertEqual("front_left", uuri.resource.instance) + self.assertTrue(len(uuri.resource.message) == 0) + + def test_parse_protocol_rpc_uri_with_remote_service_no_version(self): + uri = "//bo.cloud/petapp//rpc.response" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("bo.cloud", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("petapp", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + self.assertEqual("rpc", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance) == 0) + self.assertEqual("response", uuri.resource.instance) + self.assertTrue(len(uuri.resource.message) == 0) + + def test_parse_protocol_rpc_uri_with_remote_service_with_version(self): + uri = "//bo.cloud/petapp/1/rpc.response" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertFalse(len(uuri.authority.name) == 0) + self.assertEqual("bo.cloud", uuri.authority.name) + self.assertFalse(len(uuri.entity.name) == 0) + self.assertEqual("petapp", uuri.entity.name) + self.assertNotEqual(0, uuri.entity.version_major) + self.assertEqual(1, uuri.entity.version_major) + self.assertEqual("rpc", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance) == 0) + self.assertEqual("response", uuri.resource.instance) + self.assertTrue(len(uuri.resource.message) == 0) + + def test_build_protocol_uri_from__uri_when__uri_isnull(self): + uprotocoluri = LongUriSerializer().serialize(None) + self.assertEqual('', uprotocoluri) + + def test_build_protocol_uri_from__uri_when__uri_isEmpty(self): + uuri = UUri() + uprotocoluri = LongUriSerializer().serialize(uuri) + self.assertEqual('', uprotocoluri) + + def test_build_protocol_uri_from__uri_when__uri_has_empty_use(self): + use = UEntity() + uuri = UUri(authority=UAuthority(), entity=use, resource=UResource(name="door")) + uprotocoluri = LongUriSerializer().serialize(uuri) + self.assertEqual("/////door", uprotocoluri) + + def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_no_version(self): + uuri = UUri(entity=UEntity(name="body.access")) + uprotocoluri = LongUriSerializer().serialize(uuri) + self.assertEqual("/body.access", uprotocoluri) + + def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_and_version(self): + use = UEntity(name="body.access", version_major=1) + uuri = UUri(entity=use, resource=UResource()) + uprotocoluri = LongUriSerializer().serialize(uuri) + self.assertEqual("/body.access/1", uprotocoluri) + + def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_no_version_with_resource(self): + use = UEntity(name="body.access") + uuri = UUri(entity=use, resource=UResource(name="door")) + uprotocoluri = LongUriSerializer().serialize(uuri) + self.assertEqual("/body.access//door", uprotocoluri) + + def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_and_version_with_resource(self): + use = UEntity(name="body.access", version_major=1) + uuri = UUri(entity=use, resource=UResource(name="door")) + uprotocoluri = LongUriSerializer().serialize(uuri) + self.assertEqual("/body.access/1/door", uprotocoluri) + + +if __name__ == '__main__': + unittest.main() diff --git a/org_eclipse_uprotocol_tests/uri/serializer/uriserializertest.py b/org_eclipse_uprotocol_tests/uri/serializer/uriserializertest.py new file mode 100644 index 0000000..0d37d26 --- /dev/null +++ b/org_eclipse_uprotocol_tests/uri/serializer/uriserializertest.py @@ -0,0 +1,45 @@ +import unittest + +from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority, UEntity, UResource, UUri +from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from org_eclipse_uprotocol.uri.serializer.microuriserializer import MicroUriSerializer +from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator + + +class UriSerializerTest(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)) + microuri = MicroUriSerializer().serialize(micro_uuri) + longuri = LongUriSerializer().serialize(long_uuri) + resolved_uuri = LongUriSerializer().build_resolved(longuri, microuri) + self.assertTrue(resolved_uuri) + self.assertFalse(UriValidator.is_empty(resolved_uuri)) + self.assertEqual("testauth", resolved_uuri.authority.name) + self.assertEqual("neelam", resolved_uuri.entity.name) + self.assertEqual(29999, resolved_uuri.entity.id) + self.assertEqual(254, resolved_uuri.entity.version_major) + self.assertEqual("rpc", resolved_uuri.resource.name) + self.assertEqual("response", resolved_uuri.resource.instance) + self.assertEqual(39999, resolved_uuri.resource.id) + + def test_build_resolved_null_long_null_micro_uri(self): + result = MicroUriSerializer().build_resolved(None, None) + self.assertTrue(result) + self.assertTrue(UriValidator.is_empty(result)) + + def test_build_resolved_null_long_micro_uri(self): + result = MicroUriSerializer().build_resolved(None, bytes()) + self.assertTrue(result) + self.assertTrue(UriValidator.is_empty(result)) + + def test_build_resolved_valid_long_null_micro_uri(self): + result = MicroUriSerializer().build_resolved("", bytes()) + self.assertTrue(result) + self.assertTrue(UriValidator.is_empty(result)) + +# +# if __name__ == '__main__': +# unittest.main() From 8eead12a271d3a0bd0159cdb1372a2541992b7f3 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 16 Nov 2023 15:53:44 -0500 Subject: [PATCH 22/47] Add few more long uri serializer test cases --- .../uri/serializer/uriserializer.py | 2 +- .../uri/serializer/longuriserializertest.py | 199 ++++++++++++++++-- .../uri/serializer/uriserializertest.py | 6 +- 3 files changed, 189 insertions(+), 18 deletions(-) diff --git a/org_eclipse_uprotocol/uri/serializer/uriserializer.py b/org_eclipse_uprotocol/uri/serializer/uriserializer.py index ca053e2..f667d74 100644 --- a/org_eclipse_uprotocol/uri/serializer/uriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/uriserializer.py @@ -58,7 +58,7 @@ def serialize(self, uri: UUri) -> T: """ pass - def build_resolved(self, long_uri: str, micro_uri: bytes) -> Optional[UUri]: + 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.

@param long_uri:UUri serialized as a Sting. diff --git a/org_eclipse_uprotocol_tests/uri/serializer/longuriserializertest.py b/org_eclipse_uprotocol_tests/uri/serializer/longuriserializertest.py index 5352044..331c1e1 100644 --- a/org_eclipse_uprotocol_tests/uri/serializer/longuriserializertest.py +++ b/org_eclipse_uprotocol_tests/uri/serializer/longuriserializertest.py @@ -446,42 +446,213 @@ def test_parse_protocol_rpc_uri_with_remote_service_with_version(self): self.assertTrue(len(uuri.resource.message) == 0) def test_build_protocol_uri_from__uri_when__uri_isnull(self): - uprotocoluri = LongUriSerializer().serialize(None) - self.assertEqual('', uprotocoluri) + uprotocol_uri = LongUriSerializer().serialize(None) + self.assertEqual('', uprotocol_uri) def test_build_protocol_uri_from__uri_when__uri_isEmpty(self): uuri = UUri() - uprotocoluri = LongUriSerializer().serialize(uuri) - self.assertEqual('', uprotocoluri) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual('', uprotocol_uri) def test_build_protocol_uri_from__uri_when__uri_has_empty_use(self): use = UEntity() uuri = UUri(authority=UAuthority(), entity=use, resource=UResource(name="door")) - uprotocoluri = LongUriSerializer().serialize(uuri) - self.assertEqual("/////door", uprotocoluri) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("/////door", uprotocol_uri) def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_no_version(self): uuri = UUri(entity=UEntity(name="body.access")) - uprotocoluri = LongUriSerializer().serialize(uuri) - self.assertEqual("/body.access", uprotocoluri) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("/body.access", uprotocol_uri) def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_and_version(self): use = UEntity(name="body.access", version_major=1) uuri = UUri(entity=use, resource=UResource()) - uprotocoluri = LongUriSerializer().serialize(uuri) - self.assertEqual("/body.access/1", uprotocoluri) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("/body.access/1", uprotocol_uri) def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_no_version_with_resource(self): use = UEntity(name="body.access") uuri = UUri(entity=use, resource=UResource(name="door")) - uprotocoluri = LongUriSerializer().serialize(uuri) - self.assertEqual("/body.access//door", uprotocoluri) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("/body.access//door", uprotocol_uri) def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_and_version_with_resource(self): use = UEntity(name="body.access", version_major=1) uuri = UUri(entity=use, resource=UResource(name="door")) - uprotocoluri = LongUriSerializer().serialize(uuri) - self.assertEqual("/body.access/1/door", uprotocoluri) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("/body.access/1/door", uprotocol_uri) + + def test_build_protocol_uri_from_uri_when_uri_has_local_authority_service_no_version_with_resource_with_instance_no_getMessage( + self): + use = UEntity(name="body.access") + uuri = UUri(entity=use, resource=UResource(name="door", instance="front_left")) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("/body.access//door.front_left", uprotocol_uri) + + def test_build_protocol_uri_from_uri_when_uri_has_local_authority_service_and_version_with_resource_with_instance_no_getMessage( + self): + use = UEntity(name="body.access", version_major=1) + uuri = UUri(entity=use, resource=UResource(name="door", instance="front_left")) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("/body.access/1/door.front_left", uprotocol_uri) + + def test_build_protocol_uri_from_uri_when_uri_has_local_authority_service_no_version_with_resource_with_instance_with_getMessage( + self): + use = UEntity(name="body.access") + uuri = UUri(entity=use, resource=UResource(name="door", instance="front_left", message="Door")) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("/body.access//door.front_left#Door", uprotocol_uri) + + def test_build_protocol_uri_from_uri_when_uri_has_local_authority_service_and_version_with_resource_with_instance_with_getMessage( + self): + use = UEntity(name="body.access", version_major=1) + uuri = UUri(entity=use, resource=UResource(name="door", instance="front_left", message="Door")) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("/body.access/1/door.front_left#Door", uprotocol_uri) + + def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_no_version(self): + use = UEntity(name="body.access") + uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("//vcu.my_car_vin/body.access", uprotocol_uri) + + def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_and_version(self): + use = UEntity(name="body.access", version_major=1) + uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("//vcu.my_car_vin/body.access/1", uprotocol_uri) + + def test_build_protocol_uri_from_uri_when_uri_has_remote_cloud_authority_service_and_version(self): + use = UEntity(name="body.access", version_major=1) + uuri = UUri(authority=UAuthority(name="cloud.uprotocol.example.com"), entity=use) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("//cloud.uprotocol.example.com/body.access/1", uprotocol_uri) + + def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_and_version_with_resource(self): + use = UEntity(name="body.access", version_major=1) + uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use, resource=UResource(name="door")) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("//vcu.my_car_vin/body.access/1/door", uprotocol_uri) + + def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_no_version_with_resource(self): + use = UEntity(name="body.access") + uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use, resource=UResource(name="door")) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("//vcu.my_car_vin/body.access//door", uprotocol_uri) + + def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_and_version_with_resource_with_instance_no_getMessage( + self): + use = UEntity(name="body.access", version_major=1) + uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use, + resource=UResource(name="door", instance="front_left")) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("//vcu.my_car_vin/body.access/1/door.front_left", uprotocol_uri) + + def test_build_protocol_uri_from_uri_when_uri_has_remote_cloud_authority_service_and_version_with_resource_with_instance_no_getMessage( + self): + use = UEntity(name="body.access", version_major=1) + uuri = UUri(authority= UAuthority(name="cloud.uprotocol.example.com"),entity=use,resource=UResource(name="door", instance="front_left")) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("//cloud.uprotocol.example.com/body.access/1/door.front_left", uprotocol_uri) + + def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_no_version_with_resource_with_instance_no_getMessage( + self): + use = UEntity(name="body.access") + uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"),entity=use,resource=UResource(name="door", instance="front_left")) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("//vcu.my_car_vin/body.access//door.front_left", uprotocol_uri) + + def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_and_version_with_resource_with_instance_and_getMessage( + self): + use = UEntity(name="body.access", version_major=1) + uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"),entity=use,resource=UResource(name="door", instance="front_left", message="Door")) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("//vcu.my_car_vin/body.access/1/door.front_left#Door", uprotocol_uri) + + def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_no_version_with_resource_with_instance_and_getMessage( + self): + use = UEntity(name="body.access") + uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"),entity= use,resource=UResource(name="door", instance="front_left", message="Door")) + uprotocol_uri = LongUriSerializer().serialize(uuri) + self.assertEqual("//vcu.my_car_vin/body.access//door.front_left#Door", uprotocol_uri) + + def test_build_protocol_uri_for_source_part_of_rpc_request_where_source_is_local(self): + use = UEntity(name="petapp", version_major=1) + resource = UResource(name="rpc",instance="response") + uprotocol_uri = LongUriSerializer().serialize( + UUri(entity=use,resource=resource)) + self.assertEqual("/petapp/1/rpc.response", uprotocol_uri) + + def test_build_protocol_uri_for_source_part_of_rpc_request_where_source_is_remote(self): + uAuthority = UAuthority(name="cloud.uprotocol.example.com") + use = UEntity(name="petapp") + resource = UResource(name="rpc",instance="response") + + uprotocol_uri = LongUriSerializer().serialize( + UUri(authority=uAuthority,entity=use,resource=resource)) + self.assertEqual("//cloud.uprotocol.example.com/petapp//rpc.response", uprotocol_uri) + + def test_build_protocol_uri_from_uri_parts_when_uri_has_remote_authority_service_and_version_with_resource(self): + u_authority = UAuthority(name="vcu.my_car_vin") + use = UEntity(name="body.access", version_major=1) + resource = UResource(name="door") + uprotocol_uri = LongUriSerializer().serialize( + UUri(authority=u_authority, entity=use, resource=resource)) + self.assertEqual("//vcu.my_car_vin/body.access/1/door", uprotocol_uri) + + def test_custom_scheme_no_scheme(self): + u_authority = UAuthority(name="vcu.my_car_vin") + use = UEntity(name="body.access", version_major=1) + resource = UResource(name="door") + ucustom_uri = LongUriSerializer().serialize( + UUri(authority=u_authority, entity=use, resource=resource)) + self.assertEqual("//vcu.my_car_vin/body.access/1/door", ucustom_uri) + + def test_parse_local_protocol_uri_with_custom_scheme(self): + uri = "custom:/body.access//door.front_left#Door" + uuri = LongUriSerializer().deserialize(uri) + self.assertFalse(UriValidator.is_remote(uuri.authority)) + self.assertEqual("body.access", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertFalse(len(uuri.resource.instance.strip())==0) + self.assertEqual("front_left", uuri.resource.instance) + self.assertFalse(len(uuri.resource.message.strip())==0) + self.assertEqual("Door", uuri.resource.message) + + def test_parse_remote_protocol_uri_with_custom_scheme(self): + uri = "custom://vcu.vin/body.access//door.front_left#Door" + uri2 = "//vcu.vin/body.access//door.front_left#Door" + uuri = LongUriSerializer().deserialize(uri) + self.assertTrue(UriValidator.is_remote(uuri.authority)) + self.assertEqual("vcu.vin", uuri.authority.name) + self.assertEqual("body.access", uuri.entity.name) + self.assertEqual(0, uuri.entity.version_major) + self.assertEqual("door", uuri.resource.name) + self.assertEqual("front_left", uuri.resource.instance) + self.assertEqual("Door", uuri.resource.message) + self.assertEqual(uri2, LongUriSerializer().serialize(uuri)) + + def test_deserialize_long_and_micro_passing_null(self): + uri = LongUriSerializer().build_resolved(None, None) + self.assertTrue(uri is not None) + self.assertEqual("", LongUriSerializer().serialize(uri)) + + def test_deserialize_long_and_micro_passing_null_long_uri_empty_byte_array(self): + uri = LongUriSerializer().build_resolved(None, bytearray()) + self.assertTrue(uri is not None) + self.assertEqual("", LongUriSerializer().serialize(uri)) + + def test_deserialize_long_and_micro_passing_nullempty_long_uri_null_byte_array(self): + uri = LongUriSerializer().build_resolved("", None) + self.assertTrue(uri is not None) + self.assertEqual("", LongUriSerializer().serialize(uri)) + + def test_deserialize_long_and_micro_passing_empty_long_uri_empty_byte_array(self): + uri = LongUriSerializer().build_resolved("", bytearray()) + self.assertTrue(uri is not None) + self.assertEqual("", LongUriSerializer().serialize(uri)) if __name__ == '__main__': diff --git a/org_eclipse_uprotocol_tests/uri/serializer/uriserializertest.py b/org_eclipse_uprotocol_tests/uri/serializer/uriserializertest.py index 0d37d26..3234f66 100644 --- a/org_eclipse_uprotocol_tests/uri/serializer/uriserializertest.py +++ b/org_eclipse_uprotocol_tests/uri/serializer/uriserializertest.py @@ -40,6 +40,6 @@ def test_build_resolved_valid_long_null_micro_uri(self): self.assertTrue(result) self.assertTrue(UriValidator.is_empty(result)) -# -# if __name__ == '__main__': -# unittest.main() + +if __name__ == '__main__': + unittest.main() From f6b99e299e1d9fc6fdbaf86cabf1a5012379b92a Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 16 Nov 2023 17:39:44 -0500 Subject: [PATCH 23/47] Fix all issues in long and micro uri serializer --- .../uri/builder/uresource_builder.py | 2 +- .../uri/serializer/microuriserializer.py | 27 +-- .../uri/serializer/longuriserializertest.py | 61 +++++-- .../uri/serializer/microuriserializertest.py | 156 ++++++++++++++++++ .../uri/serializer/uriserializertest.py | 29 +++- 5 files changed, 243 insertions(+), 32 deletions(-) create mode 100644 org_eclipse_uprotocol_tests/uri/serializer/microuriserializertest.py diff --git a/org_eclipse_uprotocol/uri/builder/uresource_builder.py b/org_eclipse_uprotocol/uri/builder/uresource_builder.py index 0ba1f2d..f2ffb61 100644 --- a/org_eclipse_uprotocol/uri/builder/uresource_builder.py +++ b/org_eclipse_uprotocol/uri/builder/uresource_builder.py @@ -54,4 +54,4 @@ def from_id(id): if id is None: raise ValueError("id cannot be None") - return UResourceBuilder.for_rpc_request(id) if id < UResourceBuilder.MAX_RPC_ID else UResource(id=id) + return UResourceBuilder.for_rpc_request_with_id(id) if id < UResourceBuilder.MAX_RPC_ID else UResource(id=id) diff --git a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py index fa51064..69e66ad 100644 --- a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py +++ b/org_eclipse_uprotocol/uri/serializer/microuriserializer.py @@ -26,6 +26,7 @@ import io +import struct from enum import Enum from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority @@ -93,24 +94,24 @@ def serialize(self, uri: UUri) -> bytes: elif len(uri.authority.name) > 0: remote_case = "NAME" if remote_case == "REMOTE_NOT_SET": - type = AddressType.LOCAL + address_type = AddressType.LOCAL elif remote_case == "IP": length = len(uri.authority.ip) if length == 4: - type = AddressType.IPv4 + address_type = AddressType.IPv4 elif length == 16: - type = AddressType.IPv6 + address_type = AddressType.IPv6 else: return bytearray() elif remote_case == "ID": - type = AddressType.ID + address_type = AddressType.ID else: return bytearray() - os.write(type.getValue()) + os.write(address_type.value.to_bytes(1, 'big')) # URESOURCE_ID - os.write((maybe_uresource_id >> 8).to_bytes(2, 'big')) + os.write((maybe_uresource_id >> 8).to_bytes(1, 'big')) os.write((maybe_uresource_id & 0xFF).to_bytes(1, 'big')) # UENTITY_ID @@ -123,15 +124,15 @@ def serialize(self, uri: UUri) -> bytes: signed_byte = unsigned_value - 256 else: signed_byte = unsigned_value - os.write(uri.entity.version_major.to_bytes(1, byteorder='big')) + os.write(struct.pack('b', signed_byte)) # UNUSED os.write(bytes([0])) # Populating the UAuthority - if type != AddressType.LOCAL: + if address_type != AddressType.LOCAL: # Write the ID length if the type is ID - if type == AddressType.ID: - os.write(len(uri.authority.id)).to_bytes(2, 'big') + if address_type == AddressType.ID: + os.write(len(uri.authority.id).to_bytes(1, 'big')) try: if remote_case == "IP": @@ -156,14 +157,14 @@ def deserialize(self, micro_uri: bytes) -> UUri: return UUri() u_resource_id = ((micro_uri[2] & 0xFF) << 8) | (micro_uri[3] & 0xFF) - type = AddressType.from_value(micro_uri[1]) + addresstype = AddressType.from_value(micro_uri[1]) # Validate Type is found - if type is None: + if addresstype is None: return UUri() # Validate that the micro_uri is the correct length for the type - address_type = type + address_type = addresstype if address_type == AddressType.LOCAL and len(micro_uri) != self.LOCAL_MICRO_URI_LENGTH: return UUri() elif address_type == AddressType.IPv4 and len(micro_uri) != self.IPV4_MICRO_URI_LENGTH: diff --git a/org_eclipse_uprotocol_tests/uri/serializer/longuriserializertest.py b/org_eclipse_uprotocol_tests/uri/serializer/longuriserializertest.py index 331c1e1..13de27e 100644 --- a/org_eclipse_uprotocol_tests/uri/serializer/longuriserializertest.py +++ b/org_eclipse_uprotocol_tests/uri/serializer/longuriserializertest.py @@ -1,8 +1,35 @@ +# ------------------------------------------------------------------------- + +# 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 unittest +from org_eclipse_uprotocol.proto.uri_pb2 import UEntity, UUri, UAuthority, UResource from org_eclipse_uprotocol.uri.builder.uresource_builder import UResourceBuilder from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer -from org_eclipse_uprotocol.proto.uri_pb2 import UEntity, UUri, UAuthority, UResource from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator @@ -552,61 +579,61 @@ def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_and_v def test_build_protocol_uri_from_uri_when_uri_has_remote_cloud_authority_service_and_version_with_resource_with_instance_no_getMessage( self): use = UEntity(name="body.access", version_major=1) - uuri = UUri(authority= UAuthority(name="cloud.uprotocol.example.com"),entity=use,resource=UResource(name="door", instance="front_left")) + uuri = UUri(authority=UAuthority(name="cloud.uprotocol.example.com"), entity=use, + resource=UResource(name="door", instance="front_left")) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("//cloud.uprotocol.example.com/body.access/1/door.front_left", uprotocol_uri) def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_no_version_with_resource_with_instance_no_getMessage( self): use = UEntity(name="body.access") - uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"),entity=use,resource=UResource(name="door", instance="front_left")) + uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use, + resource=UResource(name="door", instance="front_left")) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("//vcu.my_car_vin/body.access//door.front_left", uprotocol_uri) def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_and_version_with_resource_with_instance_and_getMessage( self): use = UEntity(name="body.access", version_major=1) - uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"),entity=use,resource=UResource(name="door", instance="front_left", message="Door")) + uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use, + resource=UResource(name="door", instance="front_left", message="Door")) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("//vcu.my_car_vin/body.access/1/door.front_left#Door", uprotocol_uri) def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_no_version_with_resource_with_instance_and_getMessage( self): use = UEntity(name="body.access") - uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"),entity= use,resource=UResource(name="door", instance="front_left", message="Door")) + uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use, + resource=UResource(name="door", instance="front_left", message="Door")) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("//vcu.my_car_vin/body.access//door.front_left#Door", uprotocol_uri) def test_build_protocol_uri_for_source_part_of_rpc_request_where_source_is_local(self): use = UEntity(name="petapp", version_major=1) - resource = UResource(name="rpc",instance="response") - uprotocol_uri = LongUriSerializer().serialize( - UUri(entity=use,resource=resource)) + resource = UResource(name="rpc", instance="response") + uprotocol_uri = LongUriSerializer().serialize(UUri(entity=use, resource=resource)) self.assertEqual("/petapp/1/rpc.response", uprotocol_uri) def test_build_protocol_uri_for_source_part_of_rpc_request_where_source_is_remote(self): uAuthority = UAuthority(name="cloud.uprotocol.example.com") use = UEntity(name="petapp") - resource = UResource(name="rpc",instance="response") + resource = UResource(name="rpc", instance="response") - uprotocol_uri = LongUriSerializer().serialize( - UUri(authority=uAuthority,entity=use,resource=resource)) + uprotocol_uri = LongUriSerializer().serialize(UUri(authority=uAuthority, entity=use, resource=resource)) self.assertEqual("//cloud.uprotocol.example.com/petapp//rpc.response", uprotocol_uri) def test_build_protocol_uri_from_uri_parts_when_uri_has_remote_authority_service_and_version_with_resource(self): u_authority = UAuthority(name="vcu.my_car_vin") use = UEntity(name="body.access", version_major=1) resource = UResource(name="door") - uprotocol_uri = LongUriSerializer().serialize( - UUri(authority=u_authority, entity=use, resource=resource)) + uprotocol_uri = LongUriSerializer().serialize(UUri(authority=u_authority, entity=use, resource=resource)) self.assertEqual("//vcu.my_car_vin/body.access/1/door", uprotocol_uri) def test_custom_scheme_no_scheme(self): u_authority = UAuthority(name="vcu.my_car_vin") use = UEntity(name="body.access", version_major=1) resource = UResource(name="door") - ucustom_uri = LongUriSerializer().serialize( - UUri(authority=u_authority, entity=use, resource=resource)) + ucustom_uri = LongUriSerializer().serialize(UUri(authority=u_authority, entity=use, resource=resource)) self.assertEqual("//vcu.my_car_vin/body.access/1/door", ucustom_uri) def test_parse_local_protocol_uri_with_custom_scheme(self): @@ -616,9 +643,9 @@ def test_parse_local_protocol_uri_with_custom_scheme(self): self.assertEqual("body.access", uuri.entity.name) self.assertEqual(0, uuri.entity.version_major) self.assertEqual("door", uuri.resource.name) - self.assertFalse(len(uuri.resource.instance.strip())==0) + self.assertFalse(len(uuri.resource.instance.strip()) == 0) self.assertEqual("front_left", uuri.resource.instance) - self.assertFalse(len(uuri.resource.message.strip())==0) + self.assertFalse(len(uuri.resource.message.strip()) == 0) self.assertEqual("Door", uuri.resource.message) def test_parse_remote_protocol_uri_with_custom_scheme(self): diff --git a/org_eclipse_uprotocol_tests/uri/serializer/microuriserializertest.py b/org_eclipse_uprotocol_tests/uri/serializer/microuriserializertest.py new file mode 100644 index 0000000..2da4564 --- /dev/null +++ b/org_eclipse_uprotocol_tests/uri/serializer/microuriserializertest.py @@ -0,0 +1,156 @@ +# ------------------------------------------------------------------------- + +# 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 socket +import unittest + +from org_eclipse_uprotocol.proto.uri_pb2 import UEntity, UUri, UAuthority, UResource +from org_eclipse_uprotocol.uri.builder.uresource_builder import UResourceBuilder +from org_eclipse_uprotocol.uri.serializer.microuriserializer import MicroUriSerializer +from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator + + +class MicroUriSerializerTest(unittest.TestCase): + + def test_empty(self): + bytes_uuri = MicroUriSerializer().serialize(UUri()) + self.assertEqual(len(bytes_uuri), 0) + uri2 = MicroUriSerializer().deserialize(bytes_uuri) + self.assertTrue(UriValidator.is_empty(uri2)) + + def test_null(self): + bytes_uuri = MicroUriSerializer().serialize(None) + self.assertEqual(len(bytes_uuri), 0) + uri2 = MicroUriSerializer().deserialize(None) + self.assertTrue(UriValidator.is_empty(uri2)) + + def test_serialize_uri(self): + uri = UUri(entity=UEntity(id=29999, version_major=254), resource=UResource(id=19999)) + bytes_uuri = MicroUriSerializer().serialize(uri) + uri2 = MicroUriSerializer().deserialize(bytes_uuri) + self.assertEqual(uri, uri2) + + def test_serialize_remote_uri_without_address(self): + uri = UUri(authority=UAuthority(name="vcu.vin"), entity=UEntity(id=29999, version_major=254), + resource=UResourceBuilder.for_rpc_response()) + bytes_uuri = MicroUriSerializer().serialize(uri) + self.assertTrue(len(bytes_uuri) == 0) + + def test_serialize_uri_missing_ids(self): + uri = UUri(entity=UEntity(name="hartley"), resource=UResourceBuilder.for_rpc_response()) + bytes_uuri = MicroUriSerializer().serialize(uri) + self.assertTrue(len(bytes_uuri) == 0) + + def test_serialize_uri_missing_resource_id(self): + uri = UUri(entity=UEntity(name="hartley")) + bytes_uuri = MicroUriSerializer().serialize(uri) + self.assertTrue(len(bytes_uuri) == 0) + + def test_deserialize_bad_microuri_length(self): + badMicroUUri = bytes([0x1, 0x0, 0x0, 0x0, 0x0]) + uuri = MicroUriSerializer().deserialize(badMicroUUri) + self.assertTrue(UriValidator.is_empty(uuri)) + + badMicroUUri = bytes([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]) + uuri = MicroUriSerializer().deserialize(badMicroUUri) + self.assertTrue(UriValidator.is_empty(uuri)) + + def test_deserialize_bad_microuri_not_version_1(self): + badMicroUUri = bytes([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]) + uuri = MicroUriSerializer().deserialize(badMicroUUri) + self.assertTrue(UriValidator.is_empty(uuri)) + + def test_deserialize_bad_microuri_not_valid_address_type(self): + badMicroUUri = bytes([0x1, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]) + uuri = MicroUriSerializer().deserialize(badMicroUUri) + self.assertTrue(UriValidator.is_empty(uuri)) + + def test_deserialize_bad_microuri_valid_address_type_invalid_length(self): + badMicroUUri = bytes([0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]) + uuri = MicroUriSerializer().deserialize(badMicroUUri) + self.assertTrue(UriValidator.is_empty(uuri)) + + badMicroUUri = bytes([0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]) + uuri = MicroUriSerializer().deserialize(badMicroUUri) + self.assertTrue(UriValidator.is_empty(uuri)) + + badMicroUUri = bytes([0x1, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]) + uuri = MicroUriSerializer().deserialize(badMicroUUri) + self.assertTrue(UriValidator.is_empty(uuri)) + + def test_serialize_good_ipv4_based_authority(self): + uri = UUri(authority=UAuthority(ip=bytes(socket.inet_pton(socket.AF_INET, "10.0.3.3"))), + entity=UEntity(id=29999, version_major=254), resource=UResourceBuilder.for_rpc_request_with_id(99)) + bytes_uuri = MicroUriSerializer().serialize(uri) + uri2 = MicroUriSerializer().deserialize(bytes_uuri) + self.assertTrue(len(bytes_uuri) > 0) + self.assertTrue(UriValidator.is_micro_form(uri)) + self.assertTrue(UriValidator.is_micro_form(uri2)) + self.assertEqual(str(uri), str(uri2)) + self.assertTrue(uri == uri2) + + def test_serialize_good_ipv6_based_authority(self): + uri = UUri(authority=UAuthority( + ip=bytes(socket.inet_pton(socket.AF_INET6, "2001:0db8:85a3:0000:0000:8a2e:0370:7334"))), + entity=UEntity(id=29999, version_major=254), resource=UResource(id=19999)) + bytes_uuri = MicroUriSerializer().serialize(uri) + uri2 = MicroUriSerializer().deserialize(bytes_uuri) + self.assertTrue(UriValidator.is_micro_form(uri)) + self.assertTrue(len(bytes_uuri) > 0) + self.assertTrue(uri == uri2) + + def test_serialize_id_based_authority(self): + size = 13 + byteArray = bytearray(i for i in range(size)) + uri = UUri(authority=UAuthority(id=bytes(byteArray)), entity=UEntity(id=29999, version_major=254), + resource=UResource(id=19999)) + bytes_uuri = MicroUriSerializer().serialize(uri) + uri2 = MicroUriSerializer().deserialize(bytes_uuri) + self.assertTrue(UriValidator.is_micro_form(uri)) + self.assertTrue(len(bytes_uuri) > 0) + self.assertTrue(uri == uri2) + + def test_serialize_bad_length_ip_based_authority(self): + byteArray = bytes([127, 1, 23, 123, 12, 6]) + uri = UUri(authority=UAuthority(ip=bytes(byteArray)), entity=UEntity(id=29999, version_major=254), + resource=UResource(id=19999)) + bytes_uuri = MicroUriSerializer().serialize(uri) + self.assertTrue(len(bytes_uuri) == 0) + + def test_serialize_id_size_255_based_authority(self): + size = 129 + byteArray = bytes(i for i in range(size)) + uri = UUri(authority=UAuthority(id=bytes(byteArray)), entity=UEntity(id=29999, version_major=254), + resource=UResourceBuilder.for_rpc_request_with_id(99)) + bytes_uuri = MicroUriSerializer().serialize(uri) + self.assertEqual(len(bytes_uuri), 9 + size) + uri2 = MicroUriSerializer().deserialize(bytes_uuri) + self.assertTrue(UriValidator.is_micro_form(uri)) + self.assertTrue(uri == uri2) + + +if __name__ == '__main__': + unittest.main() diff --git a/org_eclipse_uprotocol_tests/uri/serializer/uriserializertest.py b/org_eclipse_uprotocol_tests/uri/serializer/uriserializertest.py index 3234f66..10e5134 100644 --- a/org_eclipse_uprotocol_tests/uri/serializer/uriserializertest.py +++ b/org_eclipse_uprotocol_tests/uri/serializer/uriserializertest.py @@ -1,3 +1,30 @@ +# ------------------------------------------------------------------------- + +# 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 unittest from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority, UEntity, UResource, UUri @@ -10,7 +37,7 @@ class UriSerializerTest(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")) + resource=UResource(name="rpc", instance="response")) micro_uuri = UUri(entity=UEntity(id=29999, version_major=254), resource=UResource(id=39999)) microuri = MicroUriSerializer().serialize(micro_uuri) longuri = LongUriSerializer().serialize(long_uuri) From df88fdf5f120671efaafd8c853acbea96da2a000 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 16 Nov 2023 20:34:59 -0500 Subject: [PATCH 24/47] Complete URI module testing, add test cases, and fix bugs Summary: - Conducted thorough testing of the URI module - Implemented additional test cases to cover various scenarios - Addressed and fixed identified bugs during testing --- .../validate/cloudeventvalidator.py | 2 +- .../transport/datamodel/ustatus.py | 12 + .../validate/uattributesvalidator.py | 170 ++++----- .../uri/validator/urivalidator.py | 94 +++-- .../uuid/validate/uuidvalidator.py | 21 +- .../validation}/__init__.py | 0 .../validationresult.py | 5 + .../uri/validator/uris.json | 107 ++++++ .../uri/validator/urivalidatortest.py | 361 ++++++++++++++++++ 9 files changed, 620 insertions(+), 152 deletions(-) rename {org_eclipse_uprotocol_tests/uri/serializer => org_eclipse_uprotocol/validation}/__init__.py (100%) rename org_eclipse_uprotocol/{cloudevent/validate => validation}/validationresult.py (95%) create mode 100644 org_eclipse_uprotocol_tests/uri/validator/uris.json create mode 100644 org_eclipse_uprotocol_tests/uri/validator/urivalidatortest.py diff --git a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py index 71544b6..1a61304 100644 --- a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py +++ b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py @@ -32,10 +32,10 @@ from google.rpc.status_pb2 import Status from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent -from org_eclipse_uprotocol.cloudevent.validate.validationresult import ValidationResult from org_eclipse_uprotocol.proto.uattributes_pb2 import UMessageType from org_eclipse_uprotocol.proto.uri_pb2 import UUri from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from org_eclipse_uprotocol.validation.validationresult import ValidationResult class CloudEventValidator(ABC): diff --git a/org_eclipse_uprotocol/transport/datamodel/ustatus.py b/org_eclipse_uprotocol/transport/datamodel/ustatus.py index 8840bcd..a85b6af 100644 --- a/org_eclipse_uprotocol/transport/datamodel/ustatus.py +++ b/org_eclipse_uprotocol/transport/datamodel/ustatus.py @@ -57,6 +57,18 @@ class Code(Enum): DATA_LOSS = google.rpc.code_pb2.DATA_LOSS UNSPECIFIED = -1 + def __init__(self, value): + self._value_ = value + + def value(self): + return self._value_ + @staticmethod + def from_int( value: int): + for enum_value in Code: + if enum_value.value == value: + return enum_value + return None + class UStatus(ABC): """ diff --git a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py index 9fd76d6..2c415fe 100644 --- a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py +++ b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py @@ -33,6 +33,7 @@ from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus, Code from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils +from org_eclipse_uprotocol.validation.validationresult import ValidationResult class UAttributesValidator: @@ -64,6 +65,43 @@ def get_validator(attribute: UAttributes): else: return Validators.PUBLISH.validator() + def validate(self, attributes: UAttributes) -> ValidationResult: + """ + Take a UAttributes object and run validations.

+ @param attributes:The UAttriubes to validate. + @return:Returns a UStatus that is success or failed with a message containing all validation errors + for invalid configurations. + """ + error_messages = [self.validate_id(attributes), self.validate_type(attributes), + self.validate_priority(attributes), self.validate_ttl(attributes), + self.validate_sink(attributes), self.validate_comm_status(attributes), + self.validate_permission_level(attributes), self.validate_req_id(attributes)] + + error_messages = [status.msg() for status in error_messages if + status and status.getCode() == Code.INVALID_ARGUMENT] + + if error_messages: + return ValidationResult.failure(",".join(error_messages)) + else: + 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 UStatus that is success meaning not expired or failed with a validation message or + expiration. + """ + maybe_ttl = u_attributes.ttl + maybe_time = UUIDUtils.getTime(u_attributes.id) + + ttl = maybe_ttl + if ttl <= 0: + return ValidationResult.success() + delta = int((datetime.now() - maybe_time).total_seconds() * 1000) + return ValidationResult.failure("Payload is expired") if delta >= ttl else ValidationResult.success() + @staticmethod def validate_id(attr: UAttributes) -> UStatus: """ @@ -73,7 +111,7 @@ def validate_id(attr: UAttributes) -> UStatus: """ attr_id = attr.id if UUIDUtils.isuuid(attr_id): - return UStatus.ok() + return ValidationResult.success() else: return UStatus.failed_with_msg_and_code("Invalid UUID [{}]".format(attr_id), Code.INVALID_ARGUMENT) @@ -85,10 +123,10 @@ def validate_priority(attr: UAttributes) -> UStatus: @return:Returns a UStatus that is success or failed with a failure message. """ return UStatus.failed_with_msg_and_code("Priority is missing", - Code.INVALID_ARGUMENT) if attr.priority is None else UStatus.ok() + Code.INVALID_ARGUMENT) if attr.priority is None else ValidationResult.success() @staticmethod - def validate_ttl(attr: UAttributes) -> UStatus: + def validate_ttl(attr: UAttributes) -> ValidationResult: """ Validate the time to live configuration. If the UAttributes does not contain a time to live then the UStatus is ok.

@@ -96,22 +134,22 @@ def validate_ttl(attr: UAttributes) -> UStatus: @return:Returns a UStatus that is success or failed with a failure message. """ if attr.ttl and attr.ttl <= 0: - return UStatus.failed_with_msg_and_code(f"Invalid TTL [{attr.ttl}]", Code.INVALID_ARGUMENT) + return ValidationResult.failure(f"Invalid TTL [{attr.ttl}]") else: - return UStatus.ok() + return ValidationResult.success() @staticmethod - def validate_sink(attr: UAttributes) -> UStatus: + def validate_sink(attr: UAttributes) -> ValidationResult: """ Validate the sink UriPart for the default case. If the UAttributes does not contain a sink then the UStatus is ok.

@param attr:UAttributes object containing the sink to validate. @return:Returns a UStatus that is success or failed with a failure message. """ - return UriValidator.validate(attr.sink) if attr.sink else UStatus.ok() + return UriValidator.validate(attr.sink) if attr.sink else ValidationResult.success() @staticmethod - def validate_permission_level(attr: UAttributes) -> UStatus: + def validate_permission_level(attr: UAttributes) -> ValidationResult: """ Validate the permissionLevel for the default case. If the UAttributes does not contain a permission level then the UStatus is ok.

@@ -119,32 +157,40 @@ def validate_permission_level(attr: UAttributes) -> UStatus: @return:Returns a UStatus indicating if the permissionLevel is valid or not. """ if attr.permission_level and attr.permission_level > 0: - return UStatus.ok() + return ValidationResult.success() else: - return UStatus.failed_with_msg_and_code("Invalid Permission Level", Code.INVALID_ARGUMENT) + return ValidationResult.failure("Invalid Permission Level") @staticmethod - def validate_comm_status(attr: UAttributes) -> UStatus: + def validate_comm_status(attr: UAttributes) -> ValidationResult: """ Validate the commStatus for the default case. If the UAttributes does not contain a comm status then the UStatus is ok.

@param attr:UAttributes object containing the comm status to validate. @return:Returns a UStatus that is success or failed with a failure message. """ - return UStatus.ok() if attr.commstatus else UStatus.failed_with_msg_and_code( - "Invalid Communication Status Code", Code.INVALID_ARGUMENT) + if attr.HasField('commstatus'): + enum_value = Code.from_int(attr.commstatus) + if enum_value is None: + return ValidationResult.failure("Invalid Communication Status Code") + + return ValidationResult.success() @staticmethod - def validate_req_id(attr: UAttributes) -> UStatus: + def validate_req_id(attr: UAttributes) -> ValidationResult: """ Validate the correlationId for the default case. If the UAttributes does not contain a request id then the UStatus is ok.

@param attr:Attributes object containing the request id to validate. @return:Returns a UStatus that is success or failed with a failure message. """ - return UStatus.failed_with_msg_and_code("Invalid UUID", - Code.INVALID_ARGUMENT) if attr.reqid and not UUIDUtils.isuuid( - attr.reqid) else UStatus.ok() + + if attr.HasField('reqid') and not UUIDUtils.isuuid(attr.reqid): + return ValidationResult.failure("Invalid UUID") + else: + ValidationResult.success() + + @abstractmethod def validate_type(self, attr: UAttributes): @@ -155,65 +201,21 @@ def validate_type(self, attr: UAttributes): """ raise NotImplementedError("Subclasses must implement this method.") - def validate(self, attributes: UAttributes): - """ - Take a UAttributes object and run validations.

- @param attributes:The UAttriubes to validate. - @return:Returns a UStatus that is success or failed with a message containing all validation errors - for invalid configurations. - """ - error_messages = [self.validate_id(attributes), self.validate_type(attributes), - self.validate_priority(attributes), self.validate_ttl(attributes), - self.validate_sink(attributes), self.validate_comm_status(attributes), - self.validate_permission_level(attributes), self.validate_req_id(attributes)] - - error_messages = [status.msg() for status in error_messages if - status and status.getCode() == Code.INVALID_ARGUMENT] - - if error_messages: - return UStatus.failed_with_msg_and_code(",".join(error_messages), Code.INVALID_ARGUMENT) - else: - return UStatus.ok() - - def is_expired(self, u_attributes: UAttributes): - """ - Indication if the Payload with these UAttributes is expired.

- @param u_attributes:UAttributes with time to live value. - @return: Returns a UStatus that is success meaning not expired or failed with a validation message or - expiration. - """ - maybe_ttl = u_attributes.ttl - maybe_time = UUIDUtils.getTime(u_attributes.id) - - if maybe_time is None: - return UStatus.failed_with_msg_and_code("Invalid Time", Code.INVALID_ARGUMENT) - - if maybe_ttl is None: - return UStatus.ok_with_ack_id("Not Expired") - - ttl = maybe_ttl - if ttl <= 0: - return UStatus.ok_with_ack_id("Not Expired") - delta = int((datetime.now() - maybe_time).total_seconds() * 1000) - return UStatus.failed_with_msg_and_code("Payload is expired", - Code.DEADLINE_EXCEEDED) if delta >= ttl else UStatus.ok_with_ack_id( - "Not Expired") - class Publish(UAttributesValidator): """ Implements validations for UAttributes that define a message that is meant for publishing state changes. """ - def validate_type(self, attributes_value: UAttributes) -> UStatus: + def validate_type(self, attributes_value: UAttributes) -> ValidationResult: """ Validates that attributes for a message meant to publish state changes has the correct type.

@param attributes_value:UAttributes object containing the message type to validate. @return:Returns a UStatus that is success or failed with a failure message. """ - return UStatus.ok() if attributes_value.type == UMessageType.UMESSAGE_TYPE_PUBLISH else ( - UStatus.failed_with_msg_and_code( - f"Wrong Attribute Type [{attributes_value.type}]", Code.INVALID_ARGUMENT)) + return ValidationResult.success() if attributes_value.type == UMessageType.UMESSAGE_TYPE_PUBLISH else ( + ValidationResult.failure( + f"Wrong Attribute Type [{attributes_value.type}]")) def __str__(self): return "UAttributesValidator.Publish" @@ -224,17 +226,17 @@ class Request(UAttributesValidator): Implements validations for UAttributes that define a message that is meant for an RPC request. """ - def validate_type(self, attributes_value: UAttributes) -> UStatus: + def validate_type(self, attributes_value: UAttributes) -> ValidationResult: """ Validates that attributes for a message meant for an RPC request has the correct type.

@param attributes_value:UAttributes object containing the message type to validate. @return:Returns a UStatus that is success or failed with a failure message. """ - return UStatus.ok() if attributes_value.type == UMessageType.UMESSAGE_TYPE_REQUEST else ( - UStatus.failed_with_msg_and_code( - f"Wrong Attribute Type [{attributes_value.type}]", Code.INVALID_ARGUMENT)) + return ValidationResult.success() if attributes_value.type == UMessageType.UMESSAGE_TYPE_REQUEST else ( + ValidationResult.failure( + f"Wrong Attribute Type [{attributes_value.type}]")) - def validate_sink(self, attributes_value: UAttributes) -> UStatus: + def validate_sink(self, attributes_value: UAttributes) -> ValidationResult: """ Validates that attributes for a message meant for an RPC request has a destination sink.

In the case of an RPC request, the sink is required. @@ -242,17 +244,15 @@ def validate_sink(self, attributes_value: UAttributes) -> UStatus: @return:Returns a UStatus that is success or failed with a failure message. """ return UriValidator.validate_rpc_response( - attributes_value.sink) if attributes_value.sink else UStatus.failed_with_msg_and_code("Missing Sink", - Code.INVALID_ARGUMENT) + attributes_value.sink) if attributes_value.sink else ValidationResult.failure("Missing Sink") - def validate_ttl(self, attributes_value: UAttributes) -> UStatus: + def validate_ttl(self, attributes_value: UAttributes) -> ValidationResult: """ Validate the time to live configuration.
In the case of an RPC request, the time to live is required.

@param attributes_value:UAttributes object containing the time to live to validate. @return:Returns a UStatus that is success or failed with a failure message. """ - return UStatus.ok() if attributes_value.ttl and attributes_value.ttl > 0 else UStatus.failed_with_msg_and_code( - "Missing TTL", Code.INVALID_ARGUMENT) + return ValidationResult.success() if attributes_value.ttl and attributes_value.ttl > 0 else ValidationResult.failure("Missing TTL") def __str__(self): return "UAttributesValidator.Request" @@ -263,17 +263,17 @@ class Response(UAttributesValidator): Implements validations for UAttributes that define a message that is meant for an RPC response. """ - def validate_type(self, attributes_value: UAttributes) -> UStatus: + def validate_type(self, attributes_value: UAttributes) -> ValidationResult: """ Validates that attributes for a message meant for an RPC response has the correct type.

@param attributes_value:UAttributes object containing the message type to validate. @return:Returns a UStatus that is success or failed with a failure message. """ - return UStatus.ok() if attributes_value.type == UMessageType.UMESSAGE_TYPE_RESPONSE else ( - UStatus.failed_with_msg_and_code( - f"Wrong Attribute Type [{attributes_value.type}]", Code.INVALID_ARGUMENT)) + return ValidationResult.success() if attributes_value.type == UMessageType.UMESSAGE_TYPE_RESPONSE else ( + ValidationResult.failure( + f"Wrong Attribute Type [{attributes_value.type}]")) - def validate_sink(self, attributes_value: UAttributes) -> UStatus: + def validate_sink(self, attributes_value: UAttributes) -> ValidationResult: """ Validates that attributes for a message meant for an RPC response has a destination sink.
In the case of an RPC response, the sink is required.

@@ -281,18 +281,16 @@ def validate_sink(self, attributes_value: UAttributes) -> UStatus: @return:Returns a UStatus that is success or failed with a failure message. """ return UriValidator.validate_rpc_method( - attributes_value.sink) if attributes_value.sink else UStatus.failed_with_msg_and_code("Missing Sink", - Code.INVALID_ARGUMENT) + attributes_value.sink) if attributes_value.sink else ValidationResult.failure("Missing Sink") - def validate_req_id(self, attributes_value: UAttributes) -> UStatus: + def validate_req_id(self, attributes_value: UAttributes) -> ValidationResult: """ Validate the correlationId. n the case of an RPC response, the correlation id is required.

@param attributes_value:UAttributes object containing the correlation id to validate. @return:Returns a UStatus that is success or failed with a failure message. """ - return UStatus.ok() if attributes_value.reqid and UUIDUtils.isuuid( - attributes_value.reqid) else UStatus.failed_with_msg_and_code("Missing correlationId", - Code.INVALID_ARGUMENT) + return ValidationResult.success() if attributes_value.reqid and UUIDUtils.isuuid( + attributes_value.reqid) else ValidationResult.failure("Missing correlationId") def __str__(self): return "UAttributesValidator.Response" diff --git a/org_eclipse_uprotocol/uri/validator/urivalidator.py b/org_eclipse_uprotocol/uri/validator/urivalidator.py index 5a73532..1c809e1 100644 --- a/org_eclipse_uprotocol/uri/validator/urivalidator.py +++ b/org_eclipse_uprotocol/uri/validator/urivalidator.py @@ -29,7 +29,7 @@ from org_eclipse_uprotocol.proto.uri_pb2 import UEntity from org_eclipse_uprotocol.proto.uri_pb2 import UResource from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus, Code +from org_eclipse_uprotocol.validation.validationresult import ValidationResult class UriValidator: @@ -38,22 +38,25 @@ class UriValidator: """ @staticmethod - def validate(uri: UUri) -> UStatus: + def validate(uri: UUri) -> ValidationResult: """ Validate a UUri to ensure that it has at least a name for the uEntity.

@param uri:UUri to validate. @return:Returns UStatus containing a success or a failure with the error message. """ if UriValidator.is_empty(uri): - return UStatus.failed_with_msg_and_code("Uri is empty.", Code.INVALID_ARGUMENT) + return ValidationResult.failure("Uri is empty.") + + if uri.HasField('authority') and not UriValidator.is_remote(uri.authority): + return ValidationResult.failure("Uri is remote missing uAuthority.") if uri.entity.name.strip() == "": - return UStatus.failed_with_msg_and_code("Uri is missing uSoftware Entity name.", Code.INVALID_ARGUMENT) + return ValidationResult.failure("Uri is missing uSoftware Entity name.") - return UStatus.ok() + return ValidationResult.success() @staticmethod - def validate_rpc_method(uri: UUri) -> UStatus: + def validate_rpc_method(uri: UUri) -> ValidationResult: """ Validate a UUri that is meant to be used as an RPC method URI. Used in Request sink values and Response source values.

@@ -61,18 +64,17 @@ def validate_rpc_method(uri: UUri) -> UStatus: @return:Returns UStatus containing a success or a failure with the error message. """ status = UriValidator.validate(uri) - if status.isFailed(): + if status.is_failure(): return status if not UriValidator.is_rpc_method(uri): - return UStatus.failed_with_msg_and_code( - "Invalid RPC method uri. Uri should be the method to be called, or method from response.", - Code.INVALID_ARGUMENT) + return ValidationResult.failure( + "Invalid RPC method uri. Uri should be the method to be called, or method from response.") - return UStatus.ok() + return ValidationResult.success() @staticmethod - def validate_rpc_response(uri: UUri) -> UStatus: + def validate_rpc_response(uri: UUri) -> ValidationResult: """ Validate a UUri that is meant to be used as an RPC response URI. Used in Request source values and Response sink values.

@@ -80,49 +82,35 @@ def validate_rpc_response(uri: UUri) -> UStatus: @return:Returns UStatus containing a success or a failure with the error message. """ status = UriValidator.validate(uri) - if status.isFailed(): + if status.is_failure(): return status - u_resource = uri.resource if not UriValidator.is_rpc_response(uri): - return UStatus.failed_with_msg_and_code("Invalid RPC response type.", Code.INVALID_ARGUMENT) - - return UStatus.ok() - - @staticmethod - def is_empty(uuri: UUri) -> bool: - """ - Indicates that this URI is an empty container and has no valuable information in building uProtocol sinks or - sources.

- @param uuri: An UUri proto message object - @return:Returns true if this URI is an empty container and has no valuable information in building uProtocol - sinks or sources. - """ - return (UriValidator.is_authority_empty(uuri.authority) and UriValidator.is_entity_empty( - uuri.entity) and UriValidator.is_resource_empty(uuri.resource)) + return ValidationResult.failure("Invalid RPC response type.") - @staticmethod - def is_authority_empty(authority: UAuthority) -> bool: - return all([authority.name.strip() == "", len(authority.ip) == 0, len(authority.id) == 0]) + return ValidationResult.success() @staticmethod - def is_entity_empty(entity: UEntity) -> bool: - return all([entity.name.strip() == "", entity.version_major == 0, entity.id == 0, entity.version_minor == 0]) + 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') - @staticmethod - def is_resource_empty(resource: UResource) -> bool: - return resource.name.strip() == "" or resource.name == "rpc" and not ( - resource.instance.strip() != "" or resource.message.strip() != "" or resource.id != 0) @staticmethod - def is_rpc_method(uuri: UUri) -> bool: + def is_rpc_method(uri: UUri) -> bool: """ Returns true if this resource specifies an RPC method call or RPC response.

+ @param uri: + @param uuri: @param uresource: UResource protobuf message @return:Returns true if this resource specifies an RPC method call or RPC response. """ - return not UriValidator.is_empty(uuri) and uuri.resource.name == "rpc" and ( - uuri.resource.instance.strip() != "" or uuri.resource.id != 0) + 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: @@ -136,9 +124,9 @@ def is_rpc_response(uuri: UUri) -> bool: if uuri is None: raise ValueError("Uri cannot be None.") - return UriValidator.is_rpc_method(uuri) and (( - uuri.resource.instance.strip() != "" and "response" - in uuri.resource.instance) or uuri.resource.id != 0) + 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)) @staticmethod def is_micro_form(uuri: UUri) -> bool: @@ -147,9 +135,10 @@ def is_micro_form(uuri: UUri) -> bool: @param uuri: An UUri proto message object @return:Returns true if this UUri can be serialized into a micro form UUri. """ - return not UriValidator.is_empty(uuri) and ( - UriValidator.is_authority_empty(uuri.authority) or len(uuri.authority.ip) != 0 or len( - uuri.authority.id) != 0) and uuri.entity.id != 0 and uuri.resource.id != 0 + 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')) @staticmethod def is_long_form(uuri: UUri) -> bool: @@ -158,10 +147,13 @@ def is_long_form(uuri: UUri) -> bool: @param uuri: An UUri proto message object @return:Returns true if this UUri can be serialized into a long form UUri. """ - return ( - uuri.authority.name.strip() != "" and uuri.entity.name.strip() != "" and uuri.resource.name.strip() - != "") + 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() == '' @staticmethod def is_remote(authority: UAuthority) -> bool: - return not UriValidator.is_authority_empty(authority) + if authority is None: + raise ValueError("Authority cannot be None.") + return not all([authority.name.strip() == "", len(authority.ip) == 0, len(authority.id) == 0]) diff --git a/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py b/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py index ce3fc71..3a13b6c 100644 --- a/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py +++ b/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------- - +from abc import ABC, abstractmethod # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -30,23 +30,14 @@ from org_eclipse_uprotocol.proto.uuid_pb2 import UUID from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils, Version +from org_eclipse_uprotocol.validation.validationresult import ValidationResult class UuidVariant(Enum): VARIANT_RFC_4122 = "RFC 4122" -class ValidationResult(namedtuple('ValidationResult', ['is_failure', 'message'])): - @staticmethod - def success(): - return ValidationResult(False, "") - - @staticmethod - def failure(message): - return ValidationResult(True, message) - - -class UuidValidator: +class UuidValidator(ABC): """ UUID Validator class that validates UUIDs """ @@ -68,6 +59,7 @@ def validate(self, uuid: UUID) -> ValidationResult: return ValidationResult.success() return ValidationResult.failure(f"Invalid argument value: {error_message}") + @abstractmethod def validate_version(self, uuid: UUID) -> ValidationResult: raise NotImplementedError @@ -75,6 +67,7 @@ def validate_time(self, uuid: UUID) -> ValidationResult: time = UUIDUtils.getTime(uuid) return ValidationResult.success() if time > 0 else ValidationResult.failure("Invalid UUID Time") + @abstractmethod def validate_variant(self, uuid: UUID) -> ValidationResult: raise NotImplementedError @@ -92,7 +85,7 @@ def validate_version(self, uuid: UUID) -> ValidationResult: version = UUIDUtils.getVersion(uuid) return ValidationResult.success() if version and version == Version.VERSION_TIME_ORDERED else ( ValidationResult.failure( - "Not a UUIDv6 Version")) + "Not a UUIDv6 Version")) def validate_variant(self, uuid: UUID) -> ValidationResult: variant = UUIDUtils.getVariant(uuid) @@ -105,7 +98,7 @@ def validate_version(self, uuid: UUID) -> ValidationResult: version = UUIDUtils.getVersion(uuid) return ValidationResult.success() if version and version == Version.VERSION_UPROTOCOL else ( ValidationResult.failure( - "Invalid UUIDv8 Version")) + "Invalid UUIDv8 Version")) def validate_variant(self, uuid: UUID) -> ValidationResult: return ValidationResult.success() diff --git a/org_eclipse_uprotocol_tests/uri/serializer/__init__.py b/org_eclipse_uprotocol/validation/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/uri/serializer/__init__.py rename to org_eclipse_uprotocol/validation/__init__.py diff --git a/org_eclipse_uprotocol/cloudevent/validate/validationresult.py b/org_eclipse_uprotocol/validation/validationresult.py similarity index 95% rename from org_eclipse_uprotocol/cloudevent/validate/validationresult.py rename to org_eclipse_uprotocol/validation/validationresult.py index ddf6fe1..024414d 100644 --- a/org_eclipse_uprotocol/cloudevent/validate/validationresult.py +++ b/org_eclipse_uprotocol/validation/validationresult.py @@ -102,3 +102,8 @@ def get_message(self) -> str: def __str__(self): return "ValidationResult.Success()" + + def __eq__(self, other): + if isinstance(other, Success): + return self.to_status() == other.to_status() + return False diff --git a/org_eclipse_uprotocol_tests/uri/validator/uris.json b/org_eclipse_uprotocol_tests/uri/validator/uris.json new file mode 100644 index 0000000..2865428 --- /dev/null +++ b/org_eclipse_uprotocol_tests/uri/validator/uris.json @@ -0,0 +1,107 @@ +{ + "validUris": [ + "/hartley", + "/hartley//", + "hartley/0", + "/1", + "/body.access/1", + "/body.access/1/door.front_left#Door", + "//vcu.vin/body.access/1/door.front_left#Door", + "/body.access/1/rpc.OpenWindow", + "/body.access/1/rpc.response" + ], + "invalidUris": [ + { + "uri": "/", + "status_message": "Uri is empty.", + "reason": "Empty Uri" + }, + { + "uri": "//", + "status_message": "Uri is empty.", + "reason": "Empty Uri" + }, + { + "uri": "//vcu", + "status_message": "Uri is missing uSoftware Entity name.", + "reason": "Missing entity name." + }, + { + "uri": "//vcu.vin/", + "status_message": "Uri is missing uSoftware Entity name.", + "reason": "Missing entity name." + }, + { + "uri": "", + "status_message": "Uri is empty.", + "reason": "Empty Uri" + }, + { + "uri": ":", + "status_message": "Uri is empty.", + "reason": "Contains only schema" + }, + { + "uri": "///", + "status_message": "Uri is empty.", + "reason": "Empty Authority" + }, + { + "uri": "////", + "status_message": "Uri is empty.", + "reason": "Empty Uri" + }, + { + "uri": "1", + "status_message": "Uri is empty.", + "reason": "Invalid Uri, must begin with \"\/\"" + }, + { + "uri": "a", + "status_message": "Uri is empty.", + "reason": "Invalid Uri, must begin with \"\/\"" + } + ], + "validRpcUris": [ + "/petapp/1/rpc.OpenWindow", + "/petapp/1/rpc.response" + ], + "validRpcResponseUris": [ + "/petapp/1/rpc.response" + ], + "invalidRpcResponseUris": [ + "/petapp/1/rpc.OpenWindow" + ], + "invalidRpcUris": [ + { + "uri": "/petapp//", + "reason": "Missing uE version", + "status_message": "Invalid RPC method uri. Uri should be the method to be called, or method from response." + }, + { + "uri": "/petapp", + "reason": "Missing uE version", + "status_message": "Invalid RPC method uri. Uri should be the method to be called, or method from response." + }, + { + "uri": "/petapp/1/", + "reason": "Missing RPC Method Name", + "status_message": "Invalid RPC method uri. Uri should be the method to be called, or method from response." + }, + { + "uri": "/petapp/1/rpc", + "reason": "Missing RPC Method Name", + "status_message": "Invalid RPC method uri. Uri should be the method to be called, or method from response." + }, + { + "uri": "/petapp/1/dummy", + "reason": "Missing RPC Method Name", + "status_message": "Invalid RPC method uri. Uri should be the method to be called, or method from response." + }, + { + "uri": "/petapp/1/rpc_dummy", + "reason": "Missing RPC Method Name", + "status_message": "Invalid RPC method uri. Uri should be the method to be called, or method from response." + } + ] +} \ No newline at end of file diff --git a/org_eclipse_uprotocol_tests/uri/validator/urivalidatortest.py b/org_eclipse_uprotocol_tests/uri/validator/urivalidatortest.py new file mode 100644 index 0000000..d2cadcf --- /dev/null +++ b/org_eclipse_uprotocol_tests/uri/validator/urivalidatortest.py @@ -0,0 +1,361 @@ +# ------------------------------------------------------------------------- + +# 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 json +import os +import unittest + +from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator +from org_eclipse_uprotocol.validation.validationresult import ValidationResult +from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UEntity, UResource, UAuthority + + +class TestUriValidator(unittest.TestCase): + + def test_validate_blank_uri(self): + uri = LongUriSerializer().deserialize(None) + status = UriValidator.validate(uri) + self.assertTrue(UriValidator.is_empty(uri)) + self.assertEqual("Uri is empty.", status.get_message()) + + def test_validate_uri_with_no_entity_getName(self): + uri = LongUriSerializer().deserialize("//") + status = UriValidator.validate(uri) + self.assertTrue(UriValidator.is_empty(uri)) + self.assertEqual("Uri is empty.", status.get_message()) + + def test_validate_uri_with_getEntity(self): + uri = LongUriSerializer().deserialize("/neelam") + status = UriValidator.validate(uri) + self.assertTrue(ValidationResult.success().__eq__(status)) + + + def test_validate_with_malformed_uri(self): + uri = LongUriSerializer().deserialize("neelam") + status = UriValidator.validate(uri) + self.assertTrue(UriValidator.is_empty(uri)) + self.assertEqual("Uri is empty.", status.get_message()) + + def test_validate_with_blank_uentity_name_uri(self): + status = UriValidator.validate(UUri()) + self.assertTrue(status.is_failure()) + self.assertEqual("Uri is empty.", status.get_message()) + + def test_validateRpcMethod_with_valid_uri(self): + uri = LongUriSerializer().deserialize("/neelam//rpc.echo") + status = UriValidator.validate_rpc_method(uri) + self.assertTrue(ValidationResult.success().__eq__(status)) + + def test_validateRpcMethod_with_invalid_uri(self): + uri = LongUriSerializer().deserialize("/neelam/echo") + status = UriValidator.validate_rpc_method(uri) + self.assertTrue(status.is_failure()) + self.assertEqual("Uri is empty.", status.get_message()) + + def test_validateRpcMethod_with_malformed_uri(self): + uri = LongUriSerializer().deserialize("neelam") + status = UriValidator.validate_rpc_method(uri) + self.assertTrue(UriValidator.is_empty(uri)) + self.assertEqual("Uri is empty.", status.get_message()) + + def test_validateRpcResponse_with_valid_uri(self): + uri = LongUriSerializer().deserialize("/neelam//rpc.response") + status = UriValidator.validate_rpc_response(uri) + self.assertTrue(ValidationResult.success().__eq__(status)) + + + def test_validateRpcResponse_with_malformed_uri(self): + uri = LongUriSerializer().deserialize("neelam") + status = UriValidator.validate_rpc_response(uri) + self.assertTrue(UriValidator.is_empty(uri)) + self.assertEqual("Uri is empty.", status.get_message()) + + def test_validateRpcResponse_with_rpc_type(self): + uri = LongUriSerializer().deserialize("/neelam//dummy.wrong") + status = UriValidator.validate_rpc_response(uri) + self.assertTrue(status.is_failure()) + self.assertEqual("Invalid RPC response type.", status.get_message()) + + def test_validateRpcResponse_with_invalid_rpc_response_type(self): + uri = LongUriSerializer().deserialize("/neelam//rpc.wrong") + status = UriValidator.validate_rpc_response(uri) + self.assertTrue(status.is_failure()) + self.assertEqual("Invalid RPC response type.", status.get_message()) + + def test_topic_uri_with_version_when_it_is_valid_remote(self): + uri = "//VCU.MY_CAR_VIN/body.access/1/door.front_left#Door" + status = UriValidator.validate(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_success) + + def test_topic_uri_no_version_when_it_is_valid_remote(self): + uri = "//VCU.MY_CAR_VIN/body.access//door.front_left#Door" + status = UriValidator.validate(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_success) + + def test_topic_uri_with_version_when_it_is_valid_local(self): + uri = "/body.access/1/door.front_left#Door" + status = UriValidator.validate(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_success) + + def test_topic_uri_no_version_when_it_is_valid_local(self): + uri = "/body.access//door.front_left#Door" + status = UriValidator.validate(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_success) + + def test_topic_uri_invalid_when_uri_has_schema_only(self): + uri = ":" + status = UriValidator.validate(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_topic_uri_invalid_when_uri_has_empty_use_name_local(self): + uri = "/" + status = UriValidator.validate(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_topic_uri_invalid_when_uri_is_remote_no_authority(self): + uri = "//" + status = UriValidator.validate(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_topic_uri_invalid_when_uri_is_remote_no_authority_with_use(self): + uri = "///body.access/1/door.front_left#Door" + status = UriValidator.validate(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_topic_uri_invalid_when_uri_is_missing_use_remote(self): + uri = "//VCU.myvin///door.front_left#Door" + status = UriValidator.validate(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_topic_uri_invalid_when_uri_is_missing_use_name_remote(self): + uri = "/1/door.front_left#Door" + status = UriValidator.validate(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_topic_uri_invalid_when_uri_is_missing_use_name_local(self): + uri = "//VCU.myvin//1" + status = UriValidator.validate(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_rpc_topic_uri_with_version_when_it_is_valid_remote(self): + uri = "//bo.cloud/petapp/1/rpc.response" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_success) + + def test_rpc_topic_uri_no_version_when_it_is_valid_remote(self): + uri = "//bo.cloud/petapp//rpc.response" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_success) + + def test_rpc_topic_uri_with_version_when_it_is_valid_local(self): + uri = "/petapp/1/rpc.response" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_success) + + def test_rpc_topic_uri_no_version_when_it_is_valid_local(self): + uri = "/petapp//rpc.response" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_success) + + def test_rpc_topic_uri_invalid_when_uri_has_schema_only(self): + uri = ":" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_rpc_topic_uri_with_version_when_it_is_not_valid_missing_rpc_response_local(self): + uri = "/petapp/1/dog" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_rpc_topic_uri_with_version_when_it_is_not_valid_missing_rpc_response_remote(self): + uri = "//petapp/1/dog" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_rpc_topic_uri_invalid_when_uri_is_remote_no_authority(self): + uri = "//" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_rpc_topic_uri_invalid_when_uri_is_remote_no_authority_with_use(self): + uri = "///body.access/1" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_rpc_topic_uri_invalid_when_uri_is_missing_use(self): + uri = "//VCU.myvin" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_rpc_topic_uri_invalid_when_uri_is_missing_use_name_remote(self): + uri = "/1" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_rpc_topic_uri_invalid_when_uri_is_missing_use_name_local(self): + uri = "//VCU.myvin//1" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_rpc_method_uri_with_version_when_it_is_valid_remote(self): + uri = "//VCU.myvin/body.access/1/rpc.UpdateDoor" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_success) + + def test_rpc_method_uri_no_version_when_it_is_valid_remote(self): + uri = "//VCU.myvin/body.access//rpc.UpdateDoor" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_success) + + def test_rpc_method_uri_with_version_when_it_is_valid_local(self): + uri = "/body.access/1/rpc.UpdateDoor" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_success) + + def test_rpc_method_uri_no_version_when_it_is_valid_local(self): + uri = "/body.access//rpc.UpdateDoor" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_success) + + def test_rpc_method_uri_invalid_when_uri_has_schema_only(self): + uri = ":" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_rpc_method_uri_with_version_when_it_is_not_valid_not_rpc_method_local(self): + uri = "/body.access//UpdateDoor" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_rpc_method_uri_with_version_when_it_is_not_valid_not_rpc_method_remote(self): + uri = "//body.access/1/UpdateDoor" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_rpc_method_uri_invalid_when_uri_is_remote_no_authority(self): + uri = "//" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_rpc_method_uri_invalid_when_uri_is_remote_no_authority_with_use(self): + uri = "///body.access/1/rpc.UpdateDoor" + uuri = LongUriSerializer().deserialize(uri) + status = UriValidator.validate_rpc_method(uuri) + self.assertEqual("", str(uuri)) + self.assertTrue(status.is_failure()) + + def test_rpc_method_uri_invalid_when_uri_is_remote_missing_authority_remotecase(self): + uuri = UUri(entity=UEntity(name="body.access"),resource=UResource(name="rpc",instance="UpdateDoor"),authority= + UAuthority()) + status = UriValidator.validate_rpc_method(uuri) + self.assertTrue(status.is_failure()) + self.assertEqual("Uri is remote missing uAuthority.", status.get_message()) + + def test_rpc_method_uri_invalid_when_uri_is_missing_use(self): + uri = "//VCU.myvin" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_rpc_method_uri_invalid_when_uri_is_missing_use_name_local(self): + uri = "/1/rpc.UpdateDoor" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_rpc_method_uri_invalid_when_uri_is_missing_use_name_remote(self): + uri = "//VCU.myvin//1/rpc.UpdateDoor" + status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + self.assertTrue(status.is_failure()) + + def test_all_valid_uris(self): + # Access the "validUris" array + valid_uris = self.get_json_object()["validUris"] + for valid_uri in valid_uris: + uuri = LongUriSerializer().deserialize(valid_uri) + status = UriValidator.validate(uuri) + self.assertTrue(status.is_success()) + + def test_all_invalid_uris(self): + # Access the "invalidUris" array + invalid_uris = self.get_json_object()["invalidUris"] + for invalid_uri in invalid_uris: + uuri = LongUriSerializer().deserialize(invalid_uri["uri"]) + status = UriValidator.validate(uuri) + self.assertTrue(status.is_failure()) + self.assertEqual(status.get_message(), invalid_uri["status_message"]) + + def test_all_valid_rpc_uris(self): + valid_rpc_uris = self.get_json_object()["validRpcUris"] + for valid_rpc_uri in valid_rpc_uris: + uuri = LongUriSerializer().deserialize(valid_rpc_uri) + status = UriValidator.validate_rpc_method(uuri) + self.assertTrue(status.is_success) + + def test_all_invalid_rpc_uris(self): + invalid_rpc_uris = self.get_json_object()["invalidRpcUris"] + for invalid_rpc_uri in invalid_rpc_uris: + uuri = LongUriSerializer().deserialize(invalid_rpc_uri["uri"]) + print(invalid_rpc_uri['uri']) + status = UriValidator.validate_rpc_method(uuri) + self.assertTrue(status.is_failure()) + self.assertEqual(status.get_message(), invalid_rpc_uri["status_message"]) + + def test_all_valid_rpc_response_uris(self): + valid_rpc_response_uris = self.get_json_object()["validRpcResponseUris"] + for valid_rpc_response_uri in valid_rpc_response_uris: + uuri = LongUriSerializer().deserialize(valid_rpc_response_uri) + status = UriValidator.validate_rpc_response(uuri) + self.assertTrue(UriValidator.is_rpc_response(uuri)) + self.assertTrue(status.is_success) + + def test_valid_rpc_response_uri(self): + uuri = UUri(entity=UEntity(name="neelam"),resource=UResource(name="rpc",id=19999)) + status = UriValidator.validate_rpc_response(uuri) + self.assertTrue(UriValidator.is_rpc_response(uuri)) + self.assertTrue(status.is_success) + + def test_all_invalid_rpc_response_uris(self): + invalid_rpc_response_uris = self.get_json_object()["invalidRpcResponseUris"] + for invalid_rpc_response_uri in invalid_rpc_response_uris: + uuri = LongUriSerializer().deserialize(invalid_rpc_response_uri) + status = UriValidator.validate_rpc_response(uuri) + self.assertTrue(status.is_failure()) + + @staticmethod + def get_json_object(): + current_directory = os.getcwd() + json_file_path = os.path.join(current_directory, "uris.json") + + with open(json_file_path, 'r') as json_file: + json_data = json.load(json_file) + + return json_data + + +if __name__ == '__main__': + unittest.main() From 6501705feba3fc542254b329d1ae1f90eae43f2e Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Tue, 21 Nov 2023 16:16:58 -0500 Subject: [PATCH 25/47] Move to core-api UPayload data model --- .../cloudevent/factory/ucloudevent.py | 37 +++++-- .../proto/cloudevents_pb2.py | 36 +++++++ org_eclipse_uprotocol/proto/upayload_pb2.py | 8 +- org_eclipse_uprotocol/rpc/rpcclient.py | 2 +- org_eclipse_uprotocol/rpc/rpcmapper.py | 5 +- .../transport/datamodel/ulistener.py | 2 +- .../transport/datamodel/upayload.py | 96 ------------------- org_eclipse_uprotocol/transport/utransport.py | 2 +- org_eclipse_uprotocol_tests/__init__.py | 0 .../factory/cloudeventfactorytest.py | 85 ++++++++++++++++ 10 files changed, 163 insertions(+), 110 deletions(-) create mode 100644 org_eclipse_uprotocol/proto/cloudevents_pb2.py delete mode 100644 org_eclipse_uprotocol/transport/datamodel/upayload.py delete mode 100644 org_eclipse_uprotocol_tests/__init__.py create mode 100644 org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py diff --git a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py index 40e8e1e..ff4bb39 100644 --- a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py +++ b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py @@ -52,6 +52,35 @@ def get_source(ce: CloudEvent) -> str: """ return UCloudEvent.extract_string_value_from_attributes("source", ce) + @staticmethod + def get_type(ce: CloudEvent) -> str: + """ + Extract the type from a cloud event. The source is a mandatory attribute. The CloudEvent constructor does + not allow creating a cloud event without a type.

+ @param ce:CloudEvent with source to be extracted. + @return:Returns the String value of a CloudEvent type attribute. + """ + return UCloudEvent.extract_string_value_from_attributes("type", ce) + + @staticmethod + def get_id(ce: CloudEvent) -> str: + """ + Extract the id from a cloud event. The id is a mandatory attribute. The CloudEvent constructor does + not allow creating a cloud event without an id.

+ @param ce:CloudEvent with source to be extracted. + @return:Returns the String value of a CloudEvent id attribute. + """ + return UCloudEvent.extract_string_value_from_attributes("id", ce) + + @staticmethod + def get_specversion(ce: CloudEvent) -> str: + """ + Extract the specversion from a cloud event.

+ @param ce:CloudEvent with source to be extracted. + @return:Returns the String value of a CloudEvent spec version attribute. + """ + return UCloudEvent.extract_string_value_from_attributes("specversion", ce) + @staticmethod def get_sink(ce: CloudEvent) -> str: """ @@ -228,12 +257,8 @@ def get_payload(ce: CloudEvent) -> any_pb2.Any: @return:Extracts the payload from a CloudEvent as a Protobuf Any object. """ data = ce.get_data() - if data is None: - return any_pb2.Any() - try: - return any_pb2.Any().FromString(data) - except DecodeError: - return any_pb2.Any() + return data + @staticmethod def unpack(ce: CloudEvent, clazz): diff --git a/org_eclipse_uprotocol/proto/cloudevents_pb2.py b/org_eclipse_uprotocol/proto/cloudevents_pb2.py new file mode 100644 index 0000000..f84ea9a --- /dev/null +++ b/org_eclipse_uprotocol/proto/cloudevents_pb2.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: cloudevents.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11\x63loudevents.proto\x12\x11io.cloudevents.v1\x1a\x19google/protobuf/any.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb0\x04\n\nCloudEvent\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0e\n\x06source\x18\x02 \x01(\t\x12\x14\n\x0cspec_version\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\t\x12\x41\n\nattributes\x18\x05 \x03(\x0b\x32-.io.cloudevents.v1.CloudEvent.AttributesEntry\x12\x15\n\x0b\x62inary_data\x18\x06 \x01(\x0cH\x00\x12\x13\n\ttext_data\x18\x07 \x01(\tH\x00\x12*\n\nproto_data\x18\x08 \x01(\x0b\x32\x14.google.protobuf.AnyH\x00\x1ai\n\x0f\x41ttributesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x45\n\x05value\x18\x02 \x01(\x0b\x32\x36.io.cloudevents.v1.CloudEvent.CloudEventAttributeValue:\x02\x38\x01\x1a\xd3\x01\n\x18\x43loudEventAttributeValue\x12\x14\n\nce_boolean\x18\x01 \x01(\x08H\x00\x12\x14\n\nce_integer\x18\x02 \x01(\x05H\x00\x12\x13\n\tce_string\x18\x03 \x01(\tH\x00\x12\x12\n\x08\x63\x65_bytes\x18\x04 \x01(\x0cH\x00\x12\x10\n\x06\x63\x65_uri\x18\x05 \x01(\tH\x00\x12\x14\n\nce_uri_ref\x18\x06 \x01(\tH\x00\x12\x32\n\x0c\x63\x65_timestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00\x42\x06\n\x04\x61ttrB\x06\n\x04\x64\x61ta\"@\n\x0f\x43loudEventBatch\x12-\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x1d.io.cloudevents.v1.CloudEventB\x8b\x01\n\x17io.cloudevents.v1.protoP\x01Z\x1a\x63loudevents.io/genproto/v1\xaa\x02\x1a\x43loudNative.CloudEvents.V1\xca\x02\x17Io\\CloudEvents\\V1\\Proto\xea\x02\x1aIo::CloudEvents::V1::Protob\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cloudevents_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + _globals['DESCRIPTOR']._options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\027io.cloudevents.v1.protoP\001Z\032cloudevents.io/genproto/v1\252\002\032CloudNative.CloudEvents.V1\312\002\027Io\\CloudEvents\\V1\\Proto\352\002\032Io::CloudEvents::V1::Proto' + _globals['_CLOUDEVENT_ATTRIBUTESENTRY']._options = None + _globals['_CLOUDEVENT_ATTRIBUTESENTRY']._serialized_options = b'8\001' + _globals['_CLOUDEVENT']._serialized_start=101 + _globals['_CLOUDEVENT']._serialized_end=661 + _globals['_CLOUDEVENT_ATTRIBUTESENTRY']._serialized_start=334 + _globals['_CLOUDEVENT_ATTRIBUTESENTRY']._serialized_end=439 + _globals['_CLOUDEVENT_CLOUDEVENTATTRIBUTEVALUE']._serialized_start=442 + _globals['_CLOUDEVENT_CLOUDEVENTATTRIBUTEVALUE']._serialized_end=653 + _globals['_CLOUDEVENTBATCH']._serialized_start=663 + _globals['_CLOUDEVENTBATCH']._serialized_end=727 +# @@protoc_insertion_point(module_scope) diff --git a/org_eclipse_uprotocol/proto/upayload_pb2.py b/org_eclipse_uprotocol/proto/upayload_pb2.py index 45309a5..c9d638d 100644 --- a/org_eclipse_uprotocol/proto/upayload_pb2.py +++ b/org_eclipse_uprotocol/proto/upayload_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eupayload.proto\x12\x0cuprotocol.v1*\xd8\x01\n\x0eUPayloadFormat\x12\x1f\n\x1bUPAYLOAD_FORMAT_UNSPECIFIED\x10\x00\x12\x1c\n\x18UPAYLOAD_FORMAT_PROTOBUF\x10\x01\x12\x18\n\x14UPAYLOAD_FORMAT_JSON\x10\x02\x12\x1a\n\x16UPAYLOAD_FORMAT_SOMEIP\x10\x03\x12\x1e\n\x1aUPAYLOAD_FORMAT_SOMEIP_TLV\x10\x04\x12\x17\n\x13UPAYLOAD_FORMAT_RAW\x10\x05\x12\x18\n\x14UPAYLOAD_FORMAT_TEXT\x10\x06\x42+\n\x18org.eclipse.uprotocol.v1B\rUPayloadProtoP\x01\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eupayload.proto\x12\x0cuprotocol.v1\"\x86\x01\n\x08UPayload\x12\x13\n\treference\x18\x01 \x01(\x06H\x00\x12\x0f\n\x05value\x18\x02 \x01(\x0cH\x00\x12\x13\n\x06length\x18\x03 \x01(\x05H\x01\x88\x01\x01\x12,\n\x06\x66ormat\x18\x04 \x01(\x0e\x32\x1c.uprotocol.v1.UPayloadFormatB\x06\n\x04\x64\x61taB\t\n\x07_length*\xd8\x01\n\x0eUPayloadFormat\x12\x1f\n\x1bUPAYLOAD_FORMAT_UNSPECIFIED\x10\x00\x12\x1c\n\x18UPAYLOAD_FORMAT_PROTOBUF\x10\x01\x12\x18\n\x14UPAYLOAD_FORMAT_JSON\x10\x02\x12\x1a\n\x16UPAYLOAD_FORMAT_SOMEIP\x10\x03\x12\x1e\n\x1aUPAYLOAD_FORMAT_SOMEIP_TLV\x10\x04\x12\x17\n\x13UPAYLOAD_FORMAT_RAW\x10\x05\x12\x18\n\x14UPAYLOAD_FORMAT_TEXT\x10\x06\x42+\n\x18org.eclipse.uprotocol.v1B\rUPayloadProtoP\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -21,6 +21,8 @@ if _descriptor._USE_C_DESCRIPTORS == False: _globals['DESCRIPTOR']._options = None _globals['DESCRIPTOR']._serialized_options = b'\n\030org.eclipse.uprotocol.v1B\rUPayloadProtoP\001' - _globals['_UPAYLOADFORMAT']._serialized_start=33 - _globals['_UPAYLOADFORMAT']._serialized_end=249 + _globals['_UPAYLOADFORMAT']._serialized_start=170 + _globals['_UPAYLOADFORMAT']._serialized_end=386 + _globals['_UPAYLOAD']._serialized_start=33 + _globals['_UPAYLOAD']._serialized_end=167 # @@protoc_insertion_point(module_scope) diff --git a/org_eclipse_uprotocol/rpc/rpcclient.py b/org_eclipse_uprotocol/rpc/rpcclient.py index a3db5f6..ce8fee9 100644 --- a/org_eclipse_uprotocol/rpc/rpcclient.py +++ b/org_eclipse_uprotocol/rpc/rpcclient.py @@ -30,7 +30,7 @@ from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.transport.datamodel.upayload import UPayload +from org_eclipse_uprotocol.proto.upayload_pb2 import UPayload class RpcClient(ABC): diff --git a/org_eclipse_uprotocol/rpc/rpcmapper.py b/org_eclipse_uprotocol/rpc/rpcmapper.py index cdbca38..628e4b3 100644 --- a/org_eclipse_uprotocol/rpc/rpcmapper.py +++ b/org_eclipse_uprotocol/rpc/rpcmapper.py @@ -32,6 +32,7 @@ from google.rpc.status_pb2 import Status from org_eclipse_uprotocol.rpc.rpcresult import RpcResult +from org_eclipse_uprotocol.proto.upayload_pb2 import UPayload class RpcMapper: @@ -61,7 +62,7 @@ def handle_response(payload, exception): try: any_message = any_pb2.Any() - any_message.ParseFromString(payload.data) + any_message.ParseFromString(payload.value) if any_message.Is(expected_cls.DESCRIPTOR): return RpcMapper.unpack_payload(any_message, expected_cls) except Exception as e: @@ -98,7 +99,7 @@ def handle_response(payload, exception=None): try: any_message = any_pb2.Any() - any_message.ParseFromString(payload.data) + any_message.ParseFromString(payload.value) if any_message.Is(expected_cls.DESCRIPTOR): if expected_cls == Status: diff --git a/org_eclipse_uprotocol/transport/datamodel/ulistener.py b/org_eclipse_uprotocol/transport/datamodel/ulistener.py index 24cc15f..3152c54 100644 --- a/org_eclipse_uprotocol/transport/datamodel/ulistener.py +++ b/org_eclipse_uprotocol/transport/datamodel/ulistener.py @@ -29,7 +29,7 @@ from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.transport.datamodel.upayload import UPayload +from org_eclipse_uprotocol.proto.upayload_pb2 import UPayload from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus diff --git a/org_eclipse_uprotocol/transport/datamodel/upayload.py b/org_eclipse_uprotocol/transport/datamodel/upayload.py deleted file mode 100644 index a042cbf..0000000 --- a/org_eclipse_uprotocol/transport/datamodel/upayload.py +++ /dev/null @@ -1,96 +0,0 @@ -# ------------------------------------------------------------------------- - -# 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 typing import Optional - -from org_eclipse_uprotocol.proto.upayload_pb2 import UPayloadFormat - - -class UPayload: - """ - The UPayload contains the clean Payload information along with its raw serialized structure of a byte[]. - """ - EMPTY = None - - def __init__(self, data: bytes, hint: UPayloadFormat = None): - """ - Create a UPayload.

- @param data:A byte array of the actual data. - @param hint:Hint regarding the bytes contained within the UPayload - """ - self.data = data if data is not None else bytes() - self.hint = hint if hint is not None else UPayloadFormat.UNKNOWN - - def get_data(self) -> bytes: - """ - The actual serialized or raw data, which can be deserialized or simply used as is.

- @return:Returns the actual serialized or raw data, which can be deserialized or simply used as is. - """ - return self.data - - def get_hint(self) -> UPayloadFormat: - """ - The hint regarding the bytes contained within the UPayload. - @return:Returns the hint regarding the bytes contained within the UPayload. - """ - return self.hint - - @classmethod - def empty(cls): - """ - - @return: Returns an empty representation of UPayload. - """ - if cls.EMPTY is None: - cls.EMPTY = UPayload(bytes(), UPayloadFormat.UNKNOWN) - return cls.EMPTY - - def is_empty(self): - """ - - @return:Returns true if the data in the UPayload is empty. - """ - return len(self.data) == 0 - - def __eq__(self, other): - if self is other: - return True - if other is None or self.__class__ != other.__class__: - return False - return self.data == other.data and self.hint == other.hint - - def __hash__(self): - return hash((hash(self.data), self.hint)) - - def __str__(self): - return f"UPayload{{data={list(self.data)}, hint={self.hint}}}" - - -# Example usage -if __name__ == "__main__": - payload = UPayload(b"example_data", UPayloadFormat.UNKNOWN) - print(payload) diff --git a/org_eclipse_uprotocol/transport/utransport.py b/org_eclipse_uprotocol/transport/utransport.py index feef884..8bb738f 100644 --- a/org_eclipse_uprotocol/transport/utransport.py +++ b/org_eclipse_uprotocol/transport/utransport.py @@ -31,7 +31,7 @@ from org_eclipse_uprotocol.proto.uri_pb2 import UEntity from org_eclipse_uprotocol.proto.uri_pb2 import UUri from org_eclipse_uprotocol.transport.datamodel.ulistener import UListener -from org_eclipse_uprotocol.transport.datamodel.upayload import UPayload +from org_eclipse_uprotocol.proto.upayload_pb2 import UPayload from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus diff --git a/org_eclipse_uprotocol_tests/__init__.py b/org_eclipse_uprotocol_tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py b/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py new file mode 100644 index 0000000..4bda811 --- /dev/null +++ b/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py @@ -0,0 +1,85 @@ +# ------------------------------------------------------------------------- + +# 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 unittest + +from google.protobuf import any_pb2 + +from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from org_eclipse_uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory +from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent +from org_eclipse_uprotocol.proto.cloudevents_pb2 import CloudEvent +from org_eclipse_uprotocol.proto.uattributes_pb2 import UMessageType, UPriority +from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UEntity, UResource +from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer + + +class CloudEventFactoryTest(unittest.TestCase): + DATA_CONTENT_TYPE = "application/cloudevents+protobuf" + + def test_create_base_cloud_event(self): + source = self.build_uri_for_test() + + # fake payload + proto_payload = self.build_proto_payload_for_test() + + # additional attributes + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( + UPriority.UPRIORITY_CS1).with_ttl(3).with_token("someOAuthToken").build() + + # build the cloud event + cloud_event = CloudEventFactory.build_base_cloud_event("testme", source, proto_payload, proto_payload.type_url, + u_cloud_event_attributes, UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_PUBLISH)) + + self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) + self.assertEqual("testme", UCloudEvent.get_id(cloud_event)) + self.assertEqual(source, UCloudEvent.get_source(cloud_event)) + self.assertEqual('pub.v1', UCloudEvent.get_type(cloud_event)) + self.assertNotIn("sink", cloud_event.get_attributes()) + self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) + self.assertEqual(UPriority.UPRIORITY_CS1, UCloudEvent.get_priority(cloud_event)) + self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) + self.assertEqual("someOAuthToken", UCloudEvent.get_token(cloud_event)) + self.assertEqual(proto_payload, UCloudEvent.get_payload(cloud_event)) + + def build_uri_for_test(self): + uri = UUri(entity=UEntity(name="body.access"), + resource=UResource(name="door", instance="front_left", message="Door")) + return LongUriSerializer().serialize(uri) + + def build_proto_payload_for_test(self): + ce_proto = CloudEvent(spec_version="1.0", source="https://example.com", id="hello", type="example.demo", + proto_data=any_pb2.Any()) + + any_obj = any_pb2.Any() + any_obj.Pack(ce_proto) + return any_obj + + +if __name__ == '__main__': + unittest.main() From 56fd1ee5e4494a2cdf5f34649d69a253446bbd34 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Wed, 22 Nov 2023 12:57:06 -0500 Subject: [PATCH 26/47] Replace google.rpc.* with UStatus & UCode and add few test cases --- .../datamodel/ucloudeventattributes.py | 13 +- .../cloudevent/factory/cloudeventfactory.py | 23 +- .../cloudevent/factory/ucloudevent.py | 41 ++- .../validate/cloudeventvalidator.py | 10 +- org_eclipse_uprotocol/proto/ustatus_pb2.py | 29 ++ org_eclipse_uprotocol/rpc/rpcmapper.py | 22 +- org_eclipse_uprotocol/rpc/rpcresult.py | 24 +- .../transport/datamodel/__init__.py | 0 .../transport/datamodel/ustatus.py | 179 ----------- .../transport/{datamodel => }/ulistener.py | 2 +- org_eclipse_uprotocol/transport/utransport.py | 5 +- .../validate/uattributesvalidator.py | 10 +- .../validation/validationresult.py | 18 +- .../datamodel/ucloudeventattributestest.py | 64 ++++ .../factory/cloudeventfactorytest.py | 302 ++++++++++++++++-- 15 files changed, 478 insertions(+), 264 deletions(-) create mode 100644 org_eclipse_uprotocol/proto/ustatus_pb2.py delete mode 100644 org_eclipse_uprotocol/transport/datamodel/__init__.py delete mode 100644 org_eclipse_uprotocol/transport/datamodel/ustatus.py rename org_eclipse_uprotocol/transport/{datamodel => }/ulistener.py (96%) create mode 100644 org_eclipse_uprotocol_tests/cloudevent/datamodel/ucloudeventattributestest.py diff --git a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py index a064b8b..8c1e885 100644 --- a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py +++ b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py @@ -27,6 +27,7 @@ from typing import Optional from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority +from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority class UCloudEventAttributes: @@ -63,30 +64,30 @@ def is_empty(self): @return: Returns true if this attributes container is an empty container and has no valuable information in building a CloudEvent. """ - return not any((self.hash, self.priority, self.ttl, self.token)) + 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()) - def hash(self) -> Optional[str]: + def get_hash(self) -> str: """ An HMAC generated on the data portion of the CloudEvent message using the device key.

@return: Returns an Optional hash attribute. """ return self.hash if self.hash and self.hash.strip() else None - def priority(self) -> UPriority: + def get_priority(self) -> UPriority: """ uProtocol Prioritization classifications defined at QoS in SDV-202.

@return: Returns an Optional priority attribute. """ return self.priority - def ttl(self) -> Optional[int]: + def get_ttl(self) -> int: """ How long this event should live for after it was generated (in milliseconds).

@return: Returns an Optional time to live attribute. """ return self.ttl - def token(self) -> Optional[str]: + def get_token(self) -> str: """ Oauth2 access token to perform the access request defined in the request message.

@return: Returns an Optional OAuth token attribute. @@ -136,7 +137,7 @@ def with_priority(self, priority: UPriority): @param priority: priority uProtocol Prioritization classifications defined at QoS in SDV-202. @return: Returns the UCloudEventAttributesBuilder with the configured priority. """ - self.priority = priority + self.priority = UPriority.Name(priority) return self def with_ttl(self, ttl: int): diff --git a/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py b/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py index ccda6b1..603804a 100644 --- a/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py +++ b/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py @@ -61,9 +61,11 @@ def request(application_uri_for_rpc: str, service_method_uri: str, request_id: s proto_payload.SerializeToString(), proto_payload.DESCRIPTOR.full_name, attributes, UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_REQUEST)) + UMessageType.UMESSAGE_TYPE_REQUEST) + ) cloud_event.__setitem__("sink", service_method_uri) cloud_event.__setitem__("reqid", request_id) + return cloud_event @staticmethod @@ -77,7 +79,7 @@ def response(application_uri_for_rpc: str, service_method_uri: str, request_id: :/body.access/1/rpc.UpdateDoor @param request_id:The cloud event id from the original request cloud event that this response if for. @param proto_payload: The protobuf serialized response message as defined by the application interface or the - google.rpc.Status message containing the details of an error. + UStatus message containing the details of an error. @param attributes: Additional attributes such as ttl, hash and priority. @return: Returns an response CloudEvent. """ @@ -90,6 +92,7 @@ def response(application_uri_for_rpc: str, service_method_uri: str, request_id: UMessageType.UMESSAGE_TYPE_RESPONSE)) cloud_event.__setitem__("sink", application_uri_for_rpc) cloud_event.__setitem__("reqid", request_id) + return cloud_event @staticmethod @@ -116,7 +119,8 @@ def failed_response(application_uri_for_rpc: str, service_method_uri: str, reque empty_proto_payload.SerializeToString(), # Empty payload "google.protobuf.Empty", attributes, UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_RESPONSE)) + UMessageType.UMESSAGE_TYPE_RESPONSE) + ) cloud_event.__setitem__("sink", application_uri_for_rpc) cloud_event.__setitem__("reqid", request_id) cloud_event.__setitem__("commstatus", communication_status) @@ -137,6 +141,7 @@ def publish(source: str, proto_payload: Any, attributes: UCloudEventAttributes) proto_payload.DESCRIPTOR.full_name, attributes, UCloudEvent.get_event_type( UMessageType.UMESSAGE_TYPE_PUBLISH)) + return cloud_event @staticmethod @@ -157,6 +162,7 @@ def notification(source: str, sink: str, proto_payload: Any, attributes: UCloudE UCloudEvent.get_event_type( UMessageType.UMESSAGE_TYPE_PUBLISH)) cloud_event.__setitem__("sink", sink) + return cloud_event @staticmethod @@ -186,8 +192,15 @@ def build_base_cloud_event(id: str, source: str, proto_payload_bytes: bytes, pro @return:Returns a CloudEventBuilder that can be additionally configured and then by calling .build() construct a CloudEvent ready to be serialized and sent to the transport layer. """ - json_attributes = {"ttl": attributes.ttl, "priority": attributes.priority, "hash": attributes.hash, - "token": attributes.token, "id": id, "source": source, "type": type} + json_attributes = {"id": id, "source": source, "type": type} + if attributes.get_hash() is not None: + json_attributes['hash'] = attributes.get_hash() + if attributes.get_ttl() is not None: + json_attributes['ttl'] = attributes.get_ttl() + if attributes.get_priority() is not None: + json_attributes['priority'] = attributes.get_priority() + if attributes.get_token() is not None: + json_attributes['token'] = attributes.get_token() cloud_event = CloudEvent(json_attributes, proto_payload_bytes) diff --git a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py index ff4bb39..662397f 100644 --- a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py +++ b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py @@ -30,8 +30,7 @@ from cloudevents.http import CloudEvent from google.protobuf import any_pb2 from google.protobuf.message import DecodeError -from google.rpc.code_pb2 import Code - +from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode from org_eclipse_uprotocol.proto.uattributes_pb2 import UMessageType from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils from org_eclipse_uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer @@ -52,6 +51,26 @@ def get_source(ce: CloudEvent) -> str: """ return UCloudEvent.extract_string_value_from_attributes("source", ce) + @staticmethod + def get_data_content_type(ce: CloudEvent) -> str: + """ + Extract the source from a cloud event. The source is a mandatory attribute. The CloudEvent constructor does + not allow creating a cloud event without a source.

+ @param ce:CloudEvent with source to be extracted. + @return:Returns the String value of a CloudEvent source attribute. + """ + return UCloudEvent.extract_string_value_from_attributes("datacontenttype", ce) + + @staticmethod + def get_data_schema(ce: CloudEvent) -> str: + """ + Extract the source from a cloud event. The source is a mandatory attribute. The CloudEvent constructor does + not allow creating a cloud event without a source.

+ @param ce:CloudEvent with source to be extracted. + @return:Returns the String value of a CloudEvent source attribute. + """ + return UCloudEvent.extract_string_value_from_attributes("dataschema", ce) + @staticmethod def get_type(ce: CloudEvent) -> str: """ @@ -149,13 +168,13 @@ def get_communication_status(ce: CloudEvent) -> int: Extract the integer value of the communication status attribute from a cloud event. The communication status attribute is optional. If there was a platform communication error that occurred while delivering this cloudEvent, it will be indicated in this attribute. If the attribute does not exist, it is assumed that - everything was Code.OK_VALUE.

+ everything was UCode.OK_VALUE.

@param ce: CloudEvent with the platformError to be extracted. - @return: Returns a {@link Code} value that indicates of a platform communication error while delivering this - CloudEvent or Code.OK_VALUE. + @return: Returns a {@link UCode} value that indicates of a platform communication error while delivering this + CloudEvent or UCode.OK_VALUE. """ comm_status = UCloudEvent.extract_string_value_from_attributes("commstatus", ce) - return int(comm_status) if comm_status is not None else Code.OK + return int(comm_status) if comm_status is not None else UCode.OK @staticmethod def has_communication_status_problem(ce: CloudEvent) -> bool: @@ -171,7 +190,7 @@ def add_communication_status(ce: CloudEvent, communication_status) -> CloudEvent """ Returns a new CloudEvent from the supplied CloudEvent, with the platform communication added.

@param ce:CloudEvent that the platform delivery error will be added. - @param communication_status:the platform delivery error Code to add to the CloudEvent. + @param communication_status:the platform delivery error UCode to add to the CloudEvent. @return:Returns a new CloudEvent from the supplied CloudEvent, with the platform communication added. """ if communication_status is None: @@ -257,8 +276,12 @@ def get_payload(ce: CloudEvent) -> any_pb2.Any: @return:Extracts the payload from a CloudEvent as a Protobuf Any object. """ data = ce.get_data() - return data - + if data is None: + return any_pb2.Any() + try: + return any_pb2.Any().FromString(data) + except DecodeError: + return any_pb2.Any() @staticmethod def unpack(ce: CloudEvent, clazz): diff --git a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py index 1a61304..4ad15b0 100644 --- a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py +++ b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py @@ -29,7 +29,7 @@ from enum import Enum from cloudevents.http import CloudEvent -from google.rpc.status_pb2 import Status +from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent from org_eclipse_uprotocol.proto.uattributes_pb2 import UMessageType @@ -40,8 +40,8 @@ class CloudEventValidator(ABC): """ - Validates a CloudEvent using google.grpc.Status
- google.grpc.Status + Validates a CloudEvent using google.grpc.UStatus
+ google.grpc.UStatus """ @staticmethod @@ -65,12 +65,12 @@ def get_validator(ce: CloudEvent): else: return Validators.PUBLISH.validator() - def validate(self, ce: CloudEvent) -> Status: + def validate(self, ce: CloudEvent) -> UStatus: """ Validate the CloudEvent. A CloudEventValidator instance is obtained according to the type attribute on the CloudEvent.

@param ce:The CloudEvent to validate. - @return:Returns a google.rpc.Status with success or a google.rpc.Status with failure containing all the + @return:Returns a UStatus with success or a UStatus with failure containing all the errors that were found. """ validation_results = [self.validate_version(ce), self.validate_id(ce), self.validate_source(ce), diff --git a/org_eclipse_uprotocol/proto/ustatus_pb2.py b/org_eclipse_uprotocol/proto/ustatus_pb2.py new file mode 100644 index 0000000..30b68f2 --- /dev/null +++ b/org_eclipse_uprotocol/proto/ustatus_pb2.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: ustatus.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rustatus.proto\x12\x0cuprotocol.v1\x1a\x19google/protobuf/any.proto\"u\n\x07UStatus\x12!\n\x04\x63ode\x18\x01 \x01(\x0e\x32\x13.uprotocol.v1.UCode\x12\x14\n\x07message\x18\x02 \x01(\tH\x00\x88\x01\x01\x12%\n\x07\x64\x65tails\x18\x03 \x03(\x0b\x32\x14.google.protobuf.AnyB\n\n\x08_message*\xb8\x02\n\x05UCode\x12\x06\n\x02OK\x10\x00\x12\r\n\tCANCELLED\x10\x01\x12\x0b\n\x07UNKNOWN\x10\x02\x12\x14\n\x10INVALID_ARGUMENT\x10\x03\x12\x15\n\x11\x44\x45\x41\x44LINE_EXCEEDED\x10\x04\x12\r\n\tNOT_FOUND\x10\x05\x12\x12\n\x0e\x41LREADY_EXISTS\x10\x06\x12\x15\n\x11PERMISSION_DENIED\x10\x07\x12\x13\n\x0fUNAUTHENTICATED\x10\x10\x12\x16\n\x12RESOURCE_EXHAUSTED\x10\x08\x12\x17\n\x13\x46\x41ILED_PRECONDITION\x10\t\x12\x0b\n\x07\x41\x42ORTED\x10\n\x12\x10\n\x0cOUT_OF_RANGE\x10\x0b\x12\x11\n\rUNIMPLEMENTED\x10\x0c\x12\x0c\n\x08INTERNAL\x10\r\x12\x0f\n\x0bUNAVAILABLE\x10\x0e\x12\r\n\tDATA_LOSS\x10\x0f\x42*\n\x18org.eclipse.uprotocol.v1B\x0cUStatusProtoP\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'ustatus_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + _globals['DESCRIPTOR']._options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\030org.eclipse.uprotocol.v1B\014UStatusProtoP\001' + _globals['_UCODE']._serialized_start=178 + _globals['_UCODE']._serialized_end=490 + _globals['_USTATUS']._serialized_start=58 + _globals['_USTATUS']._serialized_end=175 +# @@protoc_insertion_point(module_scope) diff --git a/org_eclipse_uprotocol/rpc/rpcmapper.py b/org_eclipse_uprotocol/rpc/rpcmapper.py index 628e4b3..953077b 100644 --- a/org_eclipse_uprotocol/rpc/rpcmapper.py +++ b/org_eclipse_uprotocol/rpc/rpcmapper.py @@ -28,8 +28,8 @@ from concurrent.futures import Future from google.protobuf import any_pb2 -from google.rpc.code_pb2 import Code -from google.rpc.status_pb2 import Status +from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode +from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus from org_eclipse_uprotocol.rpc.rpcresult import RpcResult from org_eclipse_uprotocol.proto.upayload_pb2 import UPayload @@ -66,7 +66,7 @@ def handle_response(payload, exception): if any_message.Is(expected_cls.DESCRIPTOR): return RpcMapper.unpack_payload(any_message, expected_cls) except Exception as e: - raise RuntimeError(f"{str(e)} [{Status.__name__}]") from e + raise RuntimeError(f"{str(e)} [{UStatus.__name__}]") from e raise RuntimeError(f"Unknown payload type [{any_message.type_url}]. Expected [{expected_cls.__name__}]") @@ -83,11 +83,11 @@ def callbackwrapper(payload, exception=None): def map_response_to_result(response_future: Future, expected_cls): """ Map a response of CompletableFuture<Any> from Link into a CompletableFuture containing an RpcResult - containing the declared expected return type T, or a Status containing any errors.

+ containing the declared expected return type T, or a UStatus containing any errors.

@param response_future:CompletableFuture<Any> response from Link. @param expected_cls:The class name of the declared expected return type of the RPC method. @return:Returns a CompletableFuture containing an RpcResult containing the declared expected return type T, - or a Status containing any errors. + or a UStatus containing any errors. """ def handle_response(payload, exception=None): @@ -102,15 +102,15 @@ def handle_response(payload, exception=None): any_message.ParseFromString(payload.value) if any_message.Is(expected_cls.DESCRIPTOR): - if expected_cls == Status: + if expected_cls == UStatus: return RpcMapper.calculate_status_result(any_message) else: return RpcResult.success(RpcMapper.unpack_payload(any_message, expected_cls)) - if any_message.Is(Status.DESCRIPTOR): + if any_message.Is(UStatus.DESCRIPTOR): return RpcMapper.calculate_status_result(any_message) except Exception as e: - raise RuntimeError(f"{str(e)} [{Status.__name__}]") from e + raise RuntimeError(f"{str(e)} [{UStatus.__name__}]") from e raise RuntimeError(f"Unknown payload type [{any_message.type_url}]. Expected [{expected_cls.__name__}]") @@ -125,8 +125,8 @@ def callback_wrapper(payload, exception=None): @staticmethod def calculate_status_result(payload): - status = RpcMapper.unpack_payload(payload, Status) - return RpcResult.success(status) if status.code == Code.OK else RpcResult.failure(status) + status = RpcMapper.unpack_payload(payload, UStatus) + return RpcResult.success(status) if status.code == UCode.OK else RpcResult.failure(status) @staticmethod def unpack_payload(payload, expected_cls): @@ -142,4 +142,4 @@ def unpack_payload(payload, expected_cls): payload.Unpack(expected_cls) return expected_cls except Exception as e: - raise RuntimeError(f"{str(e)} [{Status.__name__}]") from e + raise RuntimeError(f"{str(e)} [{UStatus.__name__}]") from e diff --git a/org_eclipse_uprotocol/rpc/rpcresult.py b/org_eclipse_uprotocol/rpc/rpcresult.py index 138880a..bb4676b 100644 --- a/org_eclipse_uprotocol/rpc/rpcresult.py +++ b/org_eclipse_uprotocol/rpc/rpcresult.py @@ -28,8 +28,8 @@ from abc import ABC, abstractmethod from typing import Callable, TypeVar, Union -from google.rpc.code_pb2 import Code -from google.rpc.status_pb2 import Status +from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode +from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus T = TypeVar('T') @@ -37,7 +37,7 @@ class RpcResult(ABC): """ Wrapper class for RPC Stub calls. It contains a Success with the type of the RPC call, or a failure with the - Status returned by the failed call. + UStatus returned by the failed call. """ @abstractmethod @@ -65,7 +65,7 @@ def filter(self, f: Callable[[T], bool]) -> 'RpcResult': pass @abstractmethod - def failureValue(self) -> Status: + def failureValue(self) -> UStatus: pass @abstractmethod @@ -75,7 +75,7 @@ def successValue(self) -> T: def success(value: T) -> 'RpcResult': return Success(value) - def failure(value: Union[Status, Exception, None] = None, code: Code = Code.UNKNOWN, + def failure(value: Union[UStatus, Exception, None] = None, code: UCode = UCode.UNKNOWN, message: str = '') -> 'RpcResult': return Failure(value, code, message) @@ -111,11 +111,11 @@ def flatMap(self, f: Callable[[T], RpcResult]) -> RpcResult: def filter(self, f: Callable[[T], bool]) -> RpcResult: try: - return self if f(self.successValue()) else self.failure(Code.FAILED_PRECONDITION, "filtered out") + return self if f(self.successValue()) else self.failure(UCode.FAILED_PRECONDITION, "filtered out") except Exception as e: return self.failure(e) - def failureValue(self) -> Status: + def failureValue(self) -> UStatus: raise ValueError("Method failureValue() called on a Success instance") def successValue(self) -> T: @@ -127,13 +127,13 @@ def __str__(self) -> str: class Failure(RpcResult): - def __init__(self, value: Union[Status, Exception, None] = None, code: Code = Code.UNKNOWN, message: str = ''): - if isinstance(value, Status): + def __init__(self, value: Union[UStatus, Exception, None] = None, code: UCode = UCode.UNKNOWN, message: str = ''): + if isinstance(value, UStatus): self.value = value elif isinstance(value, Exception): - self.value = Status(code=code, message=str(value)) + self.value = UStatus(code=code, message=str(value)) else: - self.value = Status(code=code, message=message) + self.value = UStatus(code=code, message=message) def isSuccess(self) -> bool: return False @@ -153,7 +153,7 @@ def flatMap(self, f: Callable[[T], RpcResult]) -> RpcResult: def filter(self, f: Callable[[T], bool]) -> RpcResult: return self.failure(self) - def failureValue(self) -> Status: + def failureValue(self) -> UStatus: return self.value def successValue(self) -> T: diff --git a/org_eclipse_uprotocol/transport/datamodel/__init__.py b/org_eclipse_uprotocol/transport/datamodel/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/org_eclipse_uprotocol/transport/datamodel/ustatus.py b/org_eclipse_uprotocol/transport/datamodel/ustatus.py deleted file mode 100644 index a85b6af..0000000 --- a/org_eclipse_uprotocol/transport/datamodel/ustatus.py +++ /dev/null @@ -1,179 +0,0 @@ -# ------------------------------------------------------------------------- - -# 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 enum import Enum -from typing import Optional - -import google.rpc.code_pb2 - - -class Code(Enum): - """ - Enum to contain the status code that we map to google.rpc.Code.
Please refer to code.protofor - documentation on the codes listed below - """ - OK = google.rpc.code_pb2.OK - CANCELLED = google.rpc.code_pb2.CANCELLED - UNKNOWN = google.rpc.code_pb2.UNKNOWN - INVALID_ARGUMENT = google.rpc.code_pb2.INVALID_ARGUMENT - DEADLINE_EXCEEDED = google.rpc.code_pb2.DEADLINE_EXCEEDED - NOT_FOUND = google.rpc.code_pb2.NOT_FOUND - ALREADY_EXISTS = google.rpc.code_pb2.ALREADY_EXISTS - PERMISSION_DENIED = google.rpc.code_pb2.PERMISSION_DENIED - UNAUTHENTICATED = google.rpc.code_pb2.UNAUTHENTICATED - RESOURCE_EXHAUSTED = google.rpc.code_pb2.RESOURCE_EXHAUSTED - FAILED_PRECONDITION = google.rpc.code_pb2.FAILED_PRECONDITION - ABORTED = google.rpc.code_pb2.ABORTED - OUT_OF_RANGE = google.rpc.code_pb2.OUT_OF_RANGE - UNIMPLEMENTED = google.rpc.code_pb2.UNIMPLEMENTED - INTERNAL = google.rpc.code_pb2.INTERNAL - UNAVAILABLE = google.rpc.code_pb2.UNAVAILABLE - DATA_LOSS = google.rpc.code_pb2.DATA_LOSS - UNSPECIFIED = -1 - - def __init__(self, value): - self._value_ = value - - def value(self): - return self._value_ - @staticmethod - def from_int( value: int): - for enum_value in Code: - if enum_value.value == value: - return enum_value - return None - - -class UStatus(ABC): - """ - UProtocol general status for all operations.
A UStatus is generated using the static factory methods, - making is easy to quickly create UStatus objects.
Example: UStatus ok = UStatus.ok(); - """ - OK = "ok" - FAILED = "failed" - - @abstractmethod - def isSuccess(self) -> bool: - pass - - @abstractmethod - def msg(self) -> str: - pass - - @abstractmethod - def getCode(self) -> int: - pass - - def isFailed(self) -> bool: - """ - Return true if UStatus is a failure.

- @return:Returns true if the UStatus is failed. - """ - return not self.isSuccess() - - def __str__(self) -> str: - return f"UStatus {'ok' if self.isSuccess() else 'failed'} " \ - f"{'id=' if self.isSuccess() else 'msg='}{self.msg()} code={self.getCode()}" - - @staticmethod - def ok(): - return OKStatus(UStatus.OK) - - @staticmethod - def ok_with_ack_id(ack_id: str): - return OKStatus(ack_id) - - @staticmethod - def failed(): - return FailStatus(UStatus.FAILED, Code.UNKNOWN) - - @staticmethod - def failed_with_msg(msg: str): - return FailStatus(msg, Code.UNKNOWN) - - @staticmethod - def failed_with_msg_and_code(msg: str, code: Code): - return FailStatus(msg, code) - - @staticmethod - def from_int(value: int) -> Optional[Code]: - for item in Code: - if item.value == value: - return item - return None - - @staticmethod - def from_google_rpc_code(code: int) -> Optional[Code]: - if code == -1: # UNRECOGNIZED - return None - for item in Code: - if item.value == code: - return item - return None - - -class OKStatus(UStatus): - """ - A successful UStatus. - """ - - def __init__(self, ack_id: str): - """ - A successful status could contain an id for tracking purposes. - @param ack_id: - """ - self.ack_id = ack_id - - def isSuccess(self) -> bool: - return True - - def msg(self) -> str: - return self.ack_id - - def getCode(self) -> int: - return Code.OK.value - - -class FailStatus(UStatus): - """ - A failed UStatus. - """ - - def __init__(self, fail_msg: str, code: Code): - self.fail_msg = fail_msg - self.code = code - - def isSuccess(self) -> bool: - return False - - def msg(self) -> str: - return self.fail_msg - - def getCode(self) -> int: - return self.code.value diff --git a/org_eclipse_uprotocol/transport/datamodel/ulistener.py b/org_eclipse_uprotocol/transport/ulistener.py similarity index 96% rename from org_eclipse_uprotocol/transport/datamodel/ulistener.py rename to org_eclipse_uprotocol/transport/ulistener.py index 3152c54..fd68c9b 100644 --- a/org_eclipse_uprotocol/transport/datamodel/ulistener.py +++ b/org_eclipse_uprotocol/transport/ulistener.py @@ -30,7 +30,7 @@ from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes from org_eclipse_uprotocol.proto.uri_pb2 import UUri from org_eclipse_uprotocol.proto.upayload_pb2 import UPayload -from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus +from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus class UListener(ABC): diff --git a/org_eclipse_uprotocol/transport/utransport.py b/org_eclipse_uprotocol/transport/utransport.py index 8bb738f..b2901af 100644 --- a/org_eclipse_uprotocol/transport/utransport.py +++ b/org_eclipse_uprotocol/transport/utransport.py @@ -30,10 +30,9 @@ from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes from org_eclipse_uprotocol.proto.uri_pb2 import UEntity from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.transport.datamodel.ulistener import UListener +from org_eclipse_uprotocol.transport.ulistener import UListener from org_eclipse_uprotocol.proto.upayload_pb2 import UPayload -from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus - +from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus class UTransport(ABC): """ diff --git a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py index 2c415fe..8c774f8 100644 --- a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py +++ b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py @@ -30,7 +30,7 @@ from enum import Enum from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes, UMessageType -from org_eclipse_uprotocol.transport.datamodel.ustatus import UStatus, Code +from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode, UStatus from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils from org_eclipse_uprotocol.validation.validationresult import ValidationResult @@ -78,7 +78,7 @@ def validate(self, attributes: UAttributes) -> ValidationResult: self.validate_permission_level(attributes), self.validate_req_id(attributes)] error_messages = [status.msg() for status in error_messages if - status and status.getCode() == Code.INVALID_ARGUMENT] + status and status.getCode() == UCode.INVALID_ARGUMENT] if error_messages: return ValidationResult.failure(",".join(error_messages)) @@ -113,7 +113,7 @@ def validate_id(attr: UAttributes) -> UStatus: if UUIDUtils.isuuid(attr_id): return ValidationResult.success() else: - return UStatus.failed_with_msg_and_code("Invalid UUID [{}]".format(attr_id), Code.INVALID_ARGUMENT) + return UStatus.failed_with_msg_and_code("Invalid UUID [{}]".format(attr_id), UCode.INVALID_ARGUMENT) @staticmethod def validate_priority(attr: UAttributes) -> UStatus: @@ -123,7 +123,7 @@ def validate_priority(attr: UAttributes) -> UStatus: @return:Returns a UStatus that is success or failed with a failure message. """ return UStatus.failed_with_msg_and_code("Priority is missing", - Code.INVALID_ARGUMENT) if attr.priority is None else ValidationResult.success() + UCode.INVALID_ARGUMENT) if attr.priority is None else ValidationResult.success() @staticmethod def validate_ttl(attr: UAttributes) -> ValidationResult: @@ -170,7 +170,7 @@ def validate_comm_status(attr: UAttributes) -> ValidationResult: @return:Returns a UStatus that is success or failed with a failure message. """ if attr.HasField('commstatus'): - enum_value = Code.from_int(attr.commstatus) + enum_value = UCode.Name(attr.commstatus) if enum_value is None: return ValidationResult.failure("Invalid Communication Status Code") diff --git a/org_eclipse_uprotocol/validation/validationresult.py b/org_eclipse_uprotocol/validation/validationresult.py index 024414d..75fd98d 100644 --- a/org_eclipse_uprotocol/validation/validationresult.py +++ b/org_eclipse_uprotocol/validation/validationresult.py @@ -27,21 +27,21 @@ from abc import ABC, abstractmethod -from google.rpc.code_pb2 import Code -from google.rpc.status_pb2 import Status +from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode, UStatus + class ValidationResult(ABC): """ - Class wrapping a ValidationResult of success or failure wrapping the value of a google.rpc.Status. + Class wrapping a ValidationResult of success or failure wrapping the value of a UStatus. """ - STATUS_SUCCESS = Status(code=Code.OK, message="OK") + STATUS_SUCCESS = UStatus(code=UCode.OK, message="OK") def __init__(self): pass @abstractmethod - def to_status(self) -> Status: + def to_status(self) -> UStatus: pass @abstractmethod @@ -73,8 +73,8 @@ def __init__(self, message): super().__init__() self.message = message if message else "Validation Failed." - def to_status(self) -> Status: - return Status(code=3, message=self.message) + def to_status(self) -> UStatus: + return UStatus(code=3, message=self.message) def is_success(self) -> bool: return False @@ -88,10 +88,10 @@ def __str__(self): class Success(ValidationResult): """ - Implementation for success, wrapping a google.rpc.Status with Code 0 for success. + Implementation for success, wrapping a UStatus with UCode 0 for success. """ - def to_status(self) -> Status: + def to_status(self) -> UStatus: return ValidationResult.STATUS_SUCCESS def is_success(self) -> bool: diff --git a/org_eclipse_uprotocol_tests/cloudevent/datamodel/ucloudeventattributestest.py b/org_eclipse_uprotocol_tests/cloudevent/datamodel/ucloudeventattributestest.py new file mode 100644 index 0000000..a4ee19a --- /dev/null +++ b/org_eclipse_uprotocol_tests/cloudevent/datamodel/ucloudeventattributestest.py @@ -0,0 +1,64 @@ +import unittest + +from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder, \ + UCloudEventAttributes +from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority + + +class UCloudEventAttributesTest(unittest.TestCase): + + # def test_hash_code_equals(self): + # # Assuming you have an equivalent of EqualsVerifier in Python + # # For example, using the `attrs` library to define data classes + # self.fail("Not implemented: Need equivalent of EqualsVerifier in Python") + + def test_to_string(self): + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( + UPriority.UPRIORITY_CS1).with_ttl(3).with_token("someOAuthToken").build() + + expected = "UCloudEventAttributes{hash='somehash', priority=UPRIORITY_CS1, ttl=3, token='someOAuthToken'}" + self.assertEqual(expected, str(u_cloud_event_attributes)) + + def test_create_valid(self): + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( + UPriority.UPRIORITY_CS6).with_ttl(3).with_token("someOAuthToken").build() + + self.assertEqual("somehash", u_cloud_event_attributes.get_hash()) + self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS6), u_cloud_event_attributes.get_priority()) + self.assertEqual(3, u_cloud_event_attributes.get_ttl()) + self.assertEqual("someOAuthToken", u_cloud_event_attributes.get_token()) + + def test_is_empty_function(self): + u_cloud_event_attributes = UCloudEventAttributes.empty() + self.assertTrue(u_cloud_event_attributes.is_empty()) + self.assertTrue(u_cloud_event_attributes.priority is None) + self.assertTrue(u_cloud_event_attributes.token is None) + self.assertTrue(u_cloud_event_attributes.ttl is None) + + def test_is_empty_function_when_built_with_blank_strings(self): + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash(" ").with_token(" ").build() + self.assertTrue(u_cloud_event_attributes.is_empty()) + self.assertTrue(u_cloud_event_attributes.hash.isspace()) + self.assertTrue(u_cloud_event_attributes.priority is None) + self.assertTrue(u_cloud_event_attributes.token.isspace()) + self.assertTrue(u_cloud_event_attributes.ttl is None) + + def test_is_empty_function_permutations(self): + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash(" ").with_token(" ").build() + self.assertTrue(u_cloud_event_attributes.is_empty()) + + u_cloud_event_attributes2 = UCloudEventAttributesBuilder().with_hash("someHash").with_token(" ").build() + self.assertFalse(u_cloud_event_attributes2.is_empty()) + + u_cloud_event_attributes3 = UCloudEventAttributesBuilder().with_hash(" ").with_token("SomeToken").build() + self.assertFalse(u_cloud_event_attributes3.is_empty()) + + u_cloud_event_attributes4 = UCloudEventAttributesBuilder().with_priority(UPriority.UPRIORITY_CS0).build() + self.assertFalse(u_cloud_event_attributes4.is_empty()) + + u_cloud_event_attributes5 = UCloudEventAttributesBuilder().with_ttl(8).build() + self.assertFalse(u_cloud_event_attributes5.is_empty()) + + +if __name__ == '__main__': + unittest.main() diff --git a/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py b/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py index 4bda811..33e01f6 100644 --- a/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py +++ b/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py @@ -29,32 +29,51 @@ from google.protobuf import any_pb2 -from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder, \ + UCloudEventAttributes from org_eclipse_uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent from org_eclipse_uprotocol.proto.cloudevents_pb2 import CloudEvent from org_eclipse_uprotocol.proto.uattributes_pb2 import UMessageType, UPriority from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UEntity, UResource +from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode + from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer +def build_uri_for_test(): + uri = UUri(entity=UEntity(name="body.access"), + resource=UResource(name="door", instance="front_left", message="Door")) + return LongUriSerializer().serialize(uri) + + +def build_proto_payload_for_test(): + ce_proto = CloudEvent(spec_version="1.0", source="https://example.com", id="hello", type="example.demo", + proto_data=any_pb2.Any()) + + any_obj = any_pb2.Any() + any_obj.Pack(ce_proto) + return any_obj + + class CloudEventFactoryTest(unittest.TestCase): - DATA_CONTENT_TYPE = "application/cloudevents+protobuf" + DATA_CONTENT_TYPE = CloudEventFactory.PROTOBUF_CONTENT_TYPE def test_create_base_cloud_event(self): - source = self.build_uri_for_test() + source = build_uri_for_test() # fake payload - proto_payload = self.build_proto_payload_for_test() + proto_payload = build_proto_payload_for_test() # additional attributes u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( UPriority.UPRIORITY_CS1).with_ttl(3).with_token("someOAuthToken").build() # build the cloud event - cloud_event = CloudEventFactory.build_base_cloud_event("testme", source, proto_payload, proto_payload.type_url, - u_cloud_event_attributes, UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_PUBLISH)) + cloud_event = CloudEventFactory.build_base_cloud_event("testme", source, proto_payload.SerializeToString(), + proto_payload.type_url, u_cloud_event_attributes, + UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_PUBLISH)) self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) self.assertEqual("testme", UCloudEvent.get_id(cloud_event)) @@ -62,23 +81,268 @@ def test_create_base_cloud_event(self): self.assertEqual('pub.v1', UCloudEvent.get_type(cloud_event)) self.assertNotIn("sink", cloud_event.get_attributes()) self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) - self.assertEqual(UPriority.UPRIORITY_CS1, UCloudEvent.get_priority(cloud_event)) + self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS1), UCloudEvent.get_priority(cloud_event)) + self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) + self.assertEqual("someOAuthToken", UCloudEvent.get_token(cloud_event)) + self.assertEqual(proto_payload.SerializeToString(), cloud_event.get_data()) + + def test_create_base_cloud_event_with_datacontenttype_and_schema(self): + source = build_uri_for_test() + + # fake payload + proto_payload = build_proto_payload_for_test() + + # additional attributes + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( + UPriority.UPRIORITY_CS1).with_ttl(3).with_token("someOAuthToken").build() + + # build the cloud event + cloud_event = CloudEventFactory.build_base_cloud_event("testme", source, proto_payload.SerializeToString(), + proto_payload.type_url, u_cloud_event_attributes, + UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_PUBLISH)) + + cloud_event.__setitem__('datacontenttype', CloudEventFactory.PROTOBUF_CONTENT_TYPE) + cloud_event.__setitem__('dataschema', proto_payload.type_url) + + # test all attributes + self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) + self.assertEqual("testme", UCloudEvent.get_id(cloud_event)) + self.assertEqual(source, UCloudEvent.get_source(cloud_event)) + self.assertEqual(UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + UCloudEvent.get_type(cloud_event)) + self.assertEqual(self.DATA_CONTENT_TYPE, UCloudEvent.get_data_content_type(cloud_event)) + self.assertEqual(proto_payload.type_url, UCloudEvent.get_data_schema(cloud_event)) + self.assertNotIn("sink", cloud_event.get_attributes()) + self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) + self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS1), UCloudEvent.get_priority(cloud_event)) + self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) + self.assertEqual("someOAuthToken", UCloudEvent.get_token(cloud_event)) + self.assertEqual(proto_payload.SerializeToString(), cloud_event.get_data()) + + def test_create_base_cloud_event_without_attributes(self): + source = build_uri_for_test() + + # fake payload + proto_payload = build_proto_payload_for_test() + + # no additional attributes + u_cloud_event_attributes = UCloudEventAttributes.empty() + + # build the cloud event + # build the cloud event + cloud_event = CloudEventFactory.build_base_cloud_event("testme", source, proto_payload.SerializeToString(), + proto_payload.type_url, u_cloud_event_attributes, + UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_PUBLISH)) + + # test all attributes + self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) + self.assertEqual("testme", UCloudEvent.get_id(cloud_event)) + self.assertEqual(source, UCloudEvent.get_source(cloud_event)) + self.assertEqual(UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + UCloudEvent.get_type(cloud_event)) + self.assertNotIn("sink", cloud_event.get_attributes()) + self.assertNotIn("hash", cloud_event.get_attributes()) + self.assertNotIn("priority", cloud_event.get_attributes()) + self.assertNotIn("ttl", cloud_event.get_attributes()) + self.assertEqual(proto_payload.SerializeToString(), cloud_event.get_data()) + + def test_create_publish_cloud_event(self): + # source + source = build_uri_for_test() + + # fake payload + proto_payload = build_proto_payload_for_test() + + # additional attributes + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( + UPriority.UPRIORITY_CS1).with_ttl(3).build() + + cloud_event = CloudEventFactory.publish(source, proto_payload, u_cloud_event_attributes) + + # test all attributes + self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) + self.assertIsNotNone(UCloudEvent.get_id(cloud_event)) + self.assertEqual(source, UCloudEvent.get_source(cloud_event)) + self.assertEqual(UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + UCloudEvent.get_type(cloud_event)) + self.assertNotIn("sink", cloud_event.get_attributes()) + self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) + self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS1), UCloudEvent.get_priority(cloud_event)) + self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) + + self.assertEqual(proto_payload.SerializeToString(), cloud_event.get_data()) + + def test_create_notification_cloud_event(self): + # source + source = build_uri_for_test() + + # sink + sink = build_uri_for_test() + + # fake payload + proto_payload = build_proto_payload_for_test() + + # additional attributes + + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( + UPriority.UPRIORITY_CS2).with_ttl(3).build() + + # build the cloud event of type publish with destination - a notification + cloud_event = CloudEventFactory.notification(source, sink, proto_payload, u_cloud_event_attributes) + + # test all attributes + self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) + self.assertIsNotNone(UCloudEvent.get_id(cloud_event)) + self.assertEqual(source, UCloudEvent.get_source(cloud_event)) + + self.assertIn("sink", cloud_event.get_attributes()) + self.assertEqual(sink, UCloudEvent.get_sink(cloud_event)) + + self.assertEqual(UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + UCloudEvent.get_type(cloud_event)) + self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) + self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS2), UCloudEvent.get_priority(cloud_event)) + self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) + + self.assertEqual(proto_payload.SerializeToString(), cloud_event.get_data()) + + def test_create_request_cloud_event_from_local_use(self): + # UriPart for the application requesting the RPC + application_uri_for_rpc = build_uri_for_test() + + # service Method UriPart + service_method_uri = build_uri_for_test() + + # fake payload + proto_payload = build_proto_payload_for_test() + + # additional attributes + + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( + UPriority.UPRIORITY_CS2).with_ttl(3).with_token("someOAuthToken").build() + + cloud_event = CloudEventFactory.request(application_uri_for_rpc, service_method_uri, + CloudEventFactory.generate_cloud_event_id(), proto_payload, + u_cloud_event_attributes) + + # test all attributes + self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) + self.assertIsNotNone(UCloudEvent.get_id(cloud_event)) + self.assertEqual(application_uri_for_rpc, UCloudEvent.get_source(cloud_event)) + + self.assertIn("sink", cloud_event.get_attributes()) + self.assertEqual(service_method_uri, UCloudEvent.get_sink(cloud_event)) + + self.assertEqual("req.v1", UCloudEvent.get_type(cloud_event)) + self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) + self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS2), UCloudEvent.get_priority(cloud_event)) self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) self.assertEqual("someOAuthToken", UCloudEvent.get_token(cloud_event)) - self.assertEqual(proto_payload, UCloudEvent.get_payload(cloud_event)) - def build_uri_for_test(self): - uri = UUri(entity=UEntity(name="body.access"), - resource=UResource(name="door", instance="front_left", message="Door")) - return LongUriSerializer().serialize(uri) + self.assertEqual(proto_payload.SerializeToString(), cloud_event.get_data()) + + def test_create_response_cloud_event_originating_from_local_use(self): + # UriPart for the application requesting the RPC + application_uri_for_rpc = build_uri_for_test() + + # service Method UriPart + service_method_uri = build_uri_for_test() + + # fake payload + proto_payload = build_proto_payload_for_test() + + # additional attributes + + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( + UPriority.UPRIORITY_CS2).with_ttl(3).build() + + cloud_event = CloudEventFactory.response(application_uri_for_rpc, service_method_uri, + "requestIdFromRequestCloudEvent", proto_payload, + u_cloud_event_attributes) + + # test all attributes + self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) + self.assertIsNotNone(UCloudEvent.get_id(cloud_event)) + self.assertEqual(service_method_uri, UCloudEvent.get_source(cloud_event)) + + self.assertIn("sink", cloud_event.get_attributes()) + self.assertEqual(application_uri_for_rpc, UCloudEvent.get_sink(cloud_event)) - def build_proto_payload_for_test(self): - ce_proto = CloudEvent(spec_version="1.0", source="https://example.com", id="hello", type="example.demo", - proto_data=any_pb2.Any()) + self.assertEqual("res.v1", UCloudEvent.get_type(cloud_event)) + self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) + self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS2), UCloudEvent.get_priority(cloud_event)) + self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) + + self.assertEqual("requestIdFromRequestCloudEvent", UCloudEvent.get_request_id(cloud_event)) + + # Use assertEqual to compare byte arrays + self.assertEqual(proto_payload.SerializeToString(), cloud_event.get_data()) + + def test_create_failed_response_cloud_event_originating_from_local_use(self): + # UriPart for the application requesting the RPC + application_uri_for_rpc = build_uri_for_test() + + # service Method UriPart + service_method_uri = build_uri_for_test() + + # additional attributes + + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( + UPriority.UPRIORITY_CS2).with_ttl(3).build() + + cloud_event = CloudEventFactory.failed_response(application_uri_for_rpc, service_method_uri, + "requestIdFromRequestCloudEvent", UCode.INVALID_ARGUMENT, + u_cloud_event_attributes) + + # test all attributes + self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) + self.assertIsNotNone(UCloudEvent.get_id(cloud_event)) + self.assertEqual(service_method_uri, UCloudEvent.get_source(cloud_event)) + + self.assertIn("sink", cloud_event.get_attributes()) + self.assertEqual(application_uri_for_rpc, UCloudEvent.get_sink(cloud_event)) + + self.assertEqual("res.v1", UCloudEvent.get_type(cloud_event)) + self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) + self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS2), UCloudEvent.get_priority(cloud_event)) + self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) + self.assertEqual(UCode.INVALID_ARGUMENT, UCloudEvent.get_communication_status(cloud_event)) + + self.assertEqual("requestIdFromRequestCloudEvent", UCloudEvent.get_request_id(cloud_event)) + + def test_create_failed_response_cloud_event_originating_from_remote_use(self): + # UriPart for the application requesting the RPC + application_uri_for_rpc = build_uri_for_test() + + # service Method UriPart + service_method_uri = build_uri_for_test() + + # additional attributes + + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( + UPriority.UPRIORITY_CS2).with_ttl(3).build() + + cloud_event = CloudEventFactory.failed_response(application_uri_for_rpc, service_method_uri, + "requestIdFromRequestCloudEvent", UCode.INVALID_ARGUMENT, + u_cloud_event_attributes) + + # test all attributes + self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) + self.assertIsNotNone(UCloudEvent.get_id(cloud_event)) + self.assertEqual(service_method_uri, UCloudEvent.get_source(cloud_event)) + + self.assertIn("sink", cloud_event.get_attributes()) + self.assertEqual(application_uri_for_rpc, UCloudEvent.get_sink(cloud_event)) + + self.assertEqual("res.v1", UCloudEvent.get_type(cloud_event)) + self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) + self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS2), UCloudEvent.get_priority(cloud_event)) + self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) + self.assertEqual(UCode.INVALID_ARGUMENT, UCloudEvent.get_communication_status(cloud_event)) - any_obj = any_pb2.Any() - any_obj.Pack(ce_proto) - return any_obj + self.assertEqual("requestIdFromRequestCloudEvent", UCloudEvent.get_request_id(cloud_event)) if __name__ == '__main__': From ef46458190df08c29ed093937ace3d9a4df743d4 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Wed, 22 Nov 2023 15:22:25 -0500 Subject: [PATCH 27/47] Bug fixes --- .../transport/builder/uattributesbuilder.py | 4 +- .../validate/uattributesvalidator.py | 83 +++++++------------ 2 files changed, 33 insertions(+), 54 deletions(-) diff --git a/org_eclipse_uprotocol/transport/builder/uattributesbuilder.py b/org_eclipse_uprotocol/transport/builder/uattributesbuilder.py index 8f1b2a9..633979b 100644 --- a/org_eclipse_uprotocol/transport/builder/uattributesbuilder.py +++ b/org_eclipse_uprotocol/transport/builder/uattributesbuilder.py @@ -26,7 +26,7 @@ from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes, UPriority, UMessageType -from org_eclipse_uprotocol.proto.uri_pb2 import * +from org_eclipse_uprotocol.proto.uri_pb2 import UUri from org_eclipse_uprotocol.proto.uuid_pb2 import UUID from org_eclipse_uprotocol.uuid.factory.uuidfactory import * @@ -59,7 +59,7 @@ def publish(priority: UPriority): @return Returns the UAttributesBuilder with the configured priority. """ if priority is None: - raise ValueError("Uri cannot be None.") + raise ValueError("UPriority cannot be None.") return UAttributesBuilder(Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_PUBLISH, priority) @staticmethod diff --git a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py index 8c774f8..3484156 100644 --- a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py +++ b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py @@ -30,7 +30,7 @@ from enum import Enum from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes, UMessageType -from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode, UStatus +from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils from org_eclipse_uprotocol.validation.validationresult import ValidationResult @@ -69,16 +69,16 @@ def validate(self, attributes: UAttributes) -> ValidationResult: """ Take a UAttributes object and run validations.

@param attributes:The UAttriubes to validate. - @return:Returns a UStatus that is success or failed with a message containing all validation errors + @return:Returns a ValidationResult that is success or failed with a message containing all validation errors for invalid configurations. """ - error_messages = [self.validate_id(attributes), self.validate_type(attributes), - self.validate_priority(attributes), self.validate_ttl(attributes), + error_messages = [self.validate_type(attributes), + self.validate_ttl(attributes), self.validate_sink(attributes), self.validate_comm_status(attributes), self.validate_permission_level(attributes), self.validate_req_id(attributes)] - error_messages = [status.msg() for status in error_messages if - status and status.getCode() == UCode.INVALID_ARGUMENT] + error_messages = [status.get_message() for status in error_messages if + status.is_failure()] if error_messages: return ValidationResult.failure(",".join(error_messages)) @@ -90,7 +90,7 @@ 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 UStatus that is success meaning not expired or failed with a validation message or + @return: Returns a ValidationResult that is success meaning not expired or failed with a validation message or expiration. """ maybe_ttl = u_attributes.ttl @@ -102,38 +102,17 @@ def is_expired(u_attributes: UAttributes): delta = int((datetime.now() - maybe_time).total_seconds() * 1000) return ValidationResult.failure("Payload is expired") if delta >= ttl else ValidationResult.success() - @staticmethod - def validate_id(attr: UAttributes) -> UStatus: - """ - Validate the id attribute, it is required.

- @param attr:UAttributes object containing the id to validate. - @return:Returns a UStatus that is success or failed with a failure message. - """ - attr_id = attr.id - if UUIDUtils.isuuid(attr_id): - return ValidationResult.success() - else: - return UStatus.failed_with_msg_and_code("Invalid UUID [{}]".format(attr_id), UCode.INVALID_ARGUMENT) - @staticmethod - def validate_priority(attr: UAttributes) -> UStatus: - """ - Validate the UPriority since it is required.

- @param attr:UAttributes object containing the message priority to validate. - @return:Returns a UStatus that is success or failed with a failure message. - """ - return UStatus.failed_with_msg_and_code("Priority is missing", - UCode.INVALID_ARGUMENT) if attr.priority is None else ValidationResult.success() @staticmethod def validate_ttl(attr: UAttributes) -> ValidationResult: """ - Validate the time to live configuration. If the UAttributes does not contain a time to live then the UStatus + Validate the time to live configuration. If the UAttributes does not contain a time to live then the ValidationResult is ok.

@param attr:UAttributes object containing the message time to live configuration to validate. - @return:Returns a UStatus that is success or failed with a failure message. + @return:Returns a ValidationResult that is success or failed with a failure message. """ - if attr.ttl and attr.ttl <= 0: + if attr.HasField('ttl') and attr.ttl <= 0: return ValidationResult.failure(f"Invalid TTL [{attr.ttl}]") else: return ValidationResult.success() @@ -141,33 +120,33 @@ def validate_ttl(attr: UAttributes) -> ValidationResult: @staticmethod def validate_sink(attr: UAttributes) -> ValidationResult: """ - Validate the sink UriPart for the default case. If the UAttributes does not contain a sink then the UStatus + Validate the sink UriPart for the default case. If the UAttributes does not contain a sink then the ValidationResult is ok.

@param attr:UAttributes object containing the sink to validate. - @return:Returns a UStatus that is success or failed with a failure message. + @return:Returns a ValidationResult that is success or failed with a failure message. """ - return UriValidator.validate(attr.sink) if attr.sink else ValidationResult.success() + return UriValidator.validate(attr.sink) if attr.HasField('sink') else ValidationResult.success() @staticmethod def validate_permission_level(attr: UAttributes) -> ValidationResult: """ Validate the permissionLevel for the default case. If the UAttributes does not contain a permission level - then the UStatus is ok.

+ then the ValidationResult is ok.

@param attr:UAttributes object containing the permission level to validate. - @return:Returns a UStatus indicating if the permissionLevel is valid or not. + @return:Returns a ValidationResult indicating if the permissionLevel is valid or not. """ - if attr.permission_level and attr.permission_level > 0: - return ValidationResult.success() - else: + if attr.HasField('permission_level') and attr.permission_level <= 0: return ValidationResult.failure("Invalid Permission Level") + else: + return ValidationResult.success() @staticmethod def validate_comm_status(attr: UAttributes) -> ValidationResult: """ Validate the commStatus for the default case. If the UAttributes does not contain a comm status then the - UStatus is ok.

+ ValidationResult is ok.

@param attr:UAttributes object containing the comm status to validate. - @return:Returns a UStatus that is success or failed with a failure message. + @return:Returns a ValidationResult that is success or failed with a failure message. """ if attr.HasField('commstatus'): enum_value = UCode.Name(attr.commstatus) @@ -180,15 +159,15 @@ def validate_comm_status(attr: UAttributes) -> ValidationResult: def validate_req_id(attr: UAttributes) -> ValidationResult: """ Validate the correlationId for the default case. If the UAttributes does not contain a request id then the - UStatus is ok.

+ ValidationResult is ok.

@param attr:Attributes object containing the request id to validate. - @return:Returns a UStatus that is success or failed with a failure message. + @return:Returns a ValidationResult that is success or failed with a failure message. """ if attr.HasField('reqid') and not UUIDUtils.isuuid(attr.reqid): return ValidationResult.failure("Invalid UUID") else: - ValidationResult.success() + return ValidationResult.success() @@ -197,7 +176,7 @@ def validate_type(self, attr: UAttributes): """ Validate the UMessageType attribute, it is required.

@param attr:UAttributes object containing the message type to validate. - @return:Returns a UStatus that is success or failed with a failure message. + @return:Returns a ValidationResult that is success or failed with a failure message. """ raise NotImplementedError("Subclasses must implement this method.") @@ -211,7 +190,7 @@ def validate_type(self, attributes_value: UAttributes) -> ValidationResult: """ Validates that attributes for a message meant to publish state changes has the correct type.

@param attributes_value:UAttributes object containing the message type to validate. - @return:Returns a UStatus that is success or failed with a failure message. + @return:Returns a ValidationResult that is success or failed with a failure message. """ return ValidationResult.success() if attributes_value.type == UMessageType.UMESSAGE_TYPE_PUBLISH else ( ValidationResult.failure( @@ -230,7 +209,7 @@ def validate_type(self, attributes_value: UAttributes) -> ValidationResult: """ Validates that attributes for a message meant for an RPC request has the correct type.

@param attributes_value:UAttributes object containing the message type to validate. - @return:Returns a UStatus that is success or failed with a failure message. + @return:Returns a ValidationResult that is success or failed with a failure message. """ return ValidationResult.success() if attributes_value.type == UMessageType.UMESSAGE_TYPE_REQUEST else ( ValidationResult.failure( @@ -241,7 +220,7 @@ def validate_sink(self, attributes_value: UAttributes) -> ValidationResult: Validates that attributes for a message meant for an RPC request has a destination sink.

In the case of an RPC request, the sink is required. @param attributes_value:UAttributes object containing the sink to validate. - @return:Returns a UStatus that is success or failed with a failure message. + @return:Returns a ValidationResult that is success or failed with a failure message. """ return UriValidator.validate_rpc_response( attributes_value.sink) if attributes_value.sink else ValidationResult.failure("Missing Sink") @@ -250,7 +229,7 @@ def validate_ttl(self, attributes_value: UAttributes) -> ValidationResult: """ Validate the time to live configuration.
In the case of an RPC request, the time to live is required.

@param attributes_value:UAttributes object containing the time to live to validate. - @return:Returns a UStatus that is success or failed with a failure message. + @return:Returns a ValidationResult that is success or failed with a failure message. """ return ValidationResult.success() if attributes_value.ttl and attributes_value.ttl > 0 else ValidationResult.failure("Missing TTL") @@ -267,7 +246,7 @@ def validate_type(self, attributes_value: UAttributes) -> ValidationResult: """ Validates that attributes for a message meant for an RPC response has the correct type.

@param attributes_value:UAttributes object containing the message type to validate. - @return:Returns a UStatus that is success or failed with a failure message. + @return:Returns a ValidationResult that is success or failed with a failure message. """ return ValidationResult.success() if attributes_value.type == UMessageType.UMESSAGE_TYPE_RESPONSE else ( ValidationResult.failure( @@ -278,7 +257,7 @@ def validate_sink(self, attributes_value: UAttributes) -> ValidationResult: Validates that attributes for a message meant for an RPC response has a destination sink.
In the case of an RPC response, the sink is required.

@param attributes_value:UAttributes object containing the sink to validate. - @return:Returns a UStatus that is success or failed with a failure message. + @return:Returns a ValidationResult that is success or failed with a failure message. """ return UriValidator.validate_rpc_method( attributes_value.sink) if attributes_value.sink else ValidationResult.failure("Missing Sink") @@ -287,7 +266,7 @@ def validate_req_id(self, attributes_value: UAttributes) -> ValidationResult: """ Validate the correlationId. n the case of an RPC response, the correlation id is required.

@param attributes_value:UAttributes object containing the correlation id to validate. - @return:Returns a UStatus that is success or failed with a failure message. + @return:Returns a ValidationResult that is success or failed with a failure message. """ return ValidationResult.success() if attributes_value.reqid and UUIDUtils.isuuid( attributes_value.reqid) else ValidationResult.failure("Missing correlationId") From a21ffda65da2ff963d7e7b75f2f887ddb73f872d Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Wed, 22 Nov 2023 21:08:59 -0500 Subject: [PATCH 28/47] Add few more test cases --- .../serialize/base64protobufserializer.py | 10 +- .../cloudevent/factory/cloudevent.json | 99 +++++++++++++++++++ .../factory/cloudeventfactorytest.py | 44 ++++++++- setup.cfg | 2 +- 4 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 org_eclipse_uprotocol_tests/cloudevent/factory/cloudevent.json diff --git a/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py index a2f3f7b..73b32ea 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py @@ -28,14 +28,15 @@ import base64 from builtins import str +from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializer import CloudEventSerializer -class Base64ProtobufSerializer: + +class Base64ProtobufSerializer(CloudEventSerializer): """ Helper for serializing Base64 protobuf data. """ - @staticmethod - def deserialize(proto_bytes: bytes) -> str: + def deserialize(self, proto_bytes: bytes) -> str: """ Deserialize a base64 protobuf payload into a Base64 String.

@@ -46,8 +47,7 @@ def deserialize(proto_bytes: bytes) -> str: return "" return base64.b64encode(proto_bytes).decode('utf-8') # return base64.b64decode(proto_bytes).decode('utf-8') - @staticmethod - def serialize(string_to_serialize: str) -> bytes: + def serialize(self, string_to_serialize: str) -> bytes: """ Serialize a String into Base64 format.

@param string_to_serialize:String to serialize. diff --git a/org_eclipse_uprotocol_tests/cloudevent/factory/cloudevent.json b/org_eclipse_uprotocol_tests/cloudevent/factory/cloudevent.json new file mode 100644 index 0000000..a11e728 --- /dev/null +++ b/org_eclipse_uprotocol_tests/cloudevent/factory/cloudevent.json @@ -0,0 +1,99 @@ +[ + { + "serialized_ce": "eyJzcGVjdmVyc2lvbiI6IjEuMCIsImlkIjoidGVzdG1lIiwic291cmNlIjoiL2JvZHkuYWNjZXNzLy9kb29yLmZyb250X2xlZnQjRG9vciIsInR5cGUiOiJwdWIudjEiLCJwcmlvcml0eSI6IlVQUklPUklUWV9DUzEiLCJ0dGwiOjMsImhhc2giOiJzb21laGFzaCIsInRva2VuIjoic29tZU9BdXRoVG9rZW4iLCJkYXRhX2Jhc2U2NCI6IkNqQjBlWEJsTG1kdmIyZHNaV0Z3YVhNdVkyOXRMMmx2TG1Oc2IzVmtaWFpsYm5SekxuWXhMa05zYjNWa1JYWmxiblFTTVFvRmFHVnNiRzhTRTJoMGRIQnpPaTh2WlhoaGJYQnNaUzVqYjIwYUF6RXVNQ0lNWlhoaGJYQnNaUzVrWlcxdlFnQT0ifQ==", + "id": "testme", + "specversion": "1.0", + "source": "/body.access//door.front_left#Door", + "type": "pub.v1", + "priority": "UPRIORITY_CS1", + "ttl": 3, + "hash": "somehash", + "token": "someOAuthToken", + "test_detail": "test_create_base_cloud_event" + }, + { + "serialized_ce": "eyJzcGVjdmVyc2lvbiI6IjEuMCIsImlkIjoidGVzdG1lIiwic291cmNlIjoiL2JvZHkuYWNjZXNzLy9kb29yLmZyb250X2xlZnQjRG9vciIsInR5cGUiOiJwdWIudjEiLCJkYXRhY29udGVudHR5cGUiOiJhcHBsaWNhdGlvbi94LXByb3RvYnVmIiwiZGF0YXNjaGVtYSI6InR5cGUuZ29vZ2xlYXBpcy5jb20vaW8uY2xvdWRldmVudHMudjEuQ2xvdWRFdmVudCIsInByaW9yaXR5IjoiVVBSSU9SSVRZX0NTMSIsInR0bCI6MywiaGFzaCI6InNvbWVoYXNoIiwidG9rZW4iOiJzb21lT0F1dGhUb2tlbiIsImRhdGFfYmFzZTY0IjoiQ2pCMGVYQmxMbWR2YjJkc1pXRndhWE11WTI5dEwybHZMbU5zYjNWa1pYWmxiblJ6TG5ZeExrTnNiM1ZrUlhabGJuUVNNUW9GYUdWc2JHOFNFMmgwZEhCek9pOHZaWGhoYlhCc1pTNWpiMjBhQXpFdU1DSU1aWGhoYlhCc1pTNWtaVzF2UWdBPSJ9", + "id": "testme", + "specversion": "1.0", + "source": "/body.access//door.front_left#Door", + "type": "pub.v1", + "priority": "UPRIORITY_CS1", + "ttl": 3, + "hash": "somehash", + "token": "someOAuthToken", + "dataschema": "type.googleapis.com/io.cloudevents.v1.CloudEvent", + "datacontenttype": "application/x-protobuf", + "test_detail": "test_create_base_cloud_event_with_datacontenttype_and_schema" + }, + { + "serialized_ce": "eyJzcGVjdmVyc2lvbiI6IjEuMCIsImlkIjoidGVzdG1lIiwic291cmNlIjoiL2JvZHkuYWNjZXNzLy9kb29yLmZyb250X2xlZnQjRG9vciIsInR5cGUiOiJwdWIudjEiLCJkYXRhX2Jhc2U2NCI6IkNqQjBlWEJsTG1kdmIyZHNaV0Z3YVhNdVkyOXRMMmx2TG1Oc2IzVmtaWFpsYm5SekxuWXhMa05zYjNWa1JYWmxiblFTTVFvRmFHVnNiRzhTRTJoMGRIQnpPaTh2WlhoaGJYQnNaUzVqYjIwYUF6RXVNQ0lNWlhoaGJYQnNaUzVrWlcxdlFnQT0ifQ==", + "id": "testme", + "specversion": "1.0", + "source": "/body.access//door.front_left#Door", + "type": "pub.v1", + "test_detail": "test_create_base_cloud_event_without_attributes" + }, + { + "serialized_ce": "eyJzcGVjdmVyc2lvbiI6IjEuMCIsImlkIjoiMDE4YmY5YjYtNWMxYy04MDAwLTk4MmYtOGM3MTI1NjQ2OWNjIiwic291cmNlIjoiL2JvZHkuYWNjZXNzLy9kb29yLmZyb250X2xlZnQjRG9vciIsInR5cGUiOiJwdWIudjEiLCJwcmlvcml0eSI6IlVQUklPUklUWV9DUzEiLCJ0dGwiOjMsImhhc2giOiJzb21laGFzaCIsImRhdGFfYmFzZTY0IjoiQ2pCMGVYQmxMbWR2YjJkc1pXRndhWE11WTI5dEwybHZMbU5zYjNWa1pYWmxiblJ6TG5ZeExrTnNiM1ZrUlhabGJuUVNNUW9GYUdWc2JHOFNFMmgwZEhCek9pOHZaWGhoYlhCc1pTNWpiMjBhQXpFdU1DSU1aWGhoYlhCc1pTNWtaVzF2UWdBPSJ9", + "id": "018bf9b6-5c1c-8000-982f-8c71256469cc", + "specversion": "1.0", + "source": "/body.access//door.front_left#Door", + "type": "pub.v1", + "priority": "UPRIORITY_CS1", + "ttl": 3, + "hash": "somehash", + "test_detail": "test_create_publish_cloud_event" + }, + { + "serialized_ce": "eyJzcGVjdmVyc2lvbiI6IjEuMCIsImlkIjoiMDE4YmY5YjktNWFkOS04MDAwLTgzZTUtNmE0ODQxNzNkNjlmIiwic291cmNlIjoiL2JvZHkuYWNjZXNzLy9kb29yLmZyb250X2xlZnQjRG9vciIsInR5cGUiOiJwdWIudjEiLCJzaW5rIjoiL2JvZHkuYWNjZXNzLy9kb29yLmZyb250X2xlZnQjRG9vciIsInByaW9yaXR5IjoiVVBSSU9SSVRZX0NTMiIsInR0bCI6MywiaGFzaCI6InNvbWVoYXNoIiwiZGF0YV9iYXNlNjQiOiJDakIwZVhCbExtZHZiMmRzWldGd2FYTXVZMjl0TDJsdkxtTnNiM1ZrWlhabGJuUnpMbll4TGtOc2IzVmtSWFpsYm5RU01Rb0ZhR1ZzYkc4U0UyaDBkSEJ6T2k4dlpYaGhiWEJzWlM1amIyMGFBekV1TUNJTVpYaGhiWEJzWlM1a1pXMXZRZ0E9In0=", + "id": "018bf9b9-5ad9-8000-83e5-6a484173d69f", + "specversion": "1.0", + "source": "/body.access//door.front_left#Door", + "type": "pub.v1", + "priority": "UPRIORITY_CS2", + "ttl": 3, + "hash": "somehash", + "sink": "/body.access//door.front_left#Door", + "test_detail": "test_create_notification_cloud_event" + }, + { + "serialized_ce": "eyJzcGVjdmVyc2lvbiI6IjEuMCIsImlkIjoiMDE4YmY5YmItMzFiZC04MDAwLWJkMGEtNjViNjNlZWNlMmJkIiwic291cmNlIjoiL2JvZHkuYWNjZXNzLy9kb29yLmZyb250X2xlZnQjRG9vciIsInR5cGUiOiJyZXEudjEiLCJzaW5rIjoiL2JvZHkuYWNjZXNzLy9kb29yLmZyb250X2xlZnQjRG9vciIsInByaW9yaXR5IjoiVVBSSU9SSVRZX0NTMiIsInR0bCI6MywiaGFzaCI6InNvbWVoYXNoIiwidG9rZW4iOiJzb21lT0F1dGhUb2tlbiIsImRhdGFfYmFzZTY0IjoiQ2pCMGVYQmxMbWR2YjJkc1pXRndhWE11WTI5dEwybHZMbU5zYjNWa1pYWmxiblJ6TG5ZeExrTnNiM1ZrUlhabGJuUVNNUW9GYUdWc2JHOFNFMmgwZEhCek9pOHZaWGhoYlhCc1pTNWpiMjBhQXpFdU1DSU1aWGhoYlhCc1pTNWtaVzF2UWdBPSJ9", + "id": "018bf9bb-31bd-8000-bd0a-65b63eece2bd", + "specversion": "1.0", + "source": "/body.access//door.front_left#Door", + "type": "req.v1", + "priority": "UPRIORITY_CS2", + "ttl": 3, + "hash": "somehash", + "token": "someOAuthToken", + "test_detail": "test_create_request_cloud_event_from_local_use" + }, + { + "serialized_ce": "eyJzcGVjdmVyc2lvbiI6IjEuMCIsImlkIjoiMDE4YmY5YmQtM2EzYi04MDAwLWE4Y2ItZTk1NTY4MWU4NTMwIiwic291cmNlIjoiL2JvZHkuYWNjZXNzLy9kb29yLmZyb250X2xlZnQjRG9vciIsInR5cGUiOiJyZXMudjEiLCJzaW5rIjoiL2JvZHkuYWNjZXNzLy9kb29yLmZyb250X2xlZnQjRG9vciIsInByaW9yaXR5IjoiVVBSSU9SSVRZX0NTMiIsInR0bCI6MywiaGFzaCI6InNvbWVoYXNoIiwicmVxaWQiOiJyZXF1ZXN0SWRGcm9tUmVxdWVzdENsb3VkRXZlbnQiLCJkYXRhX2Jhc2U2NCI6IkNqQjBlWEJsTG1kdmIyZHNaV0Z3YVhNdVkyOXRMMmx2TG1Oc2IzVmtaWFpsYm5SekxuWXhMa05zYjNWa1JYWmxiblFTTVFvRmFHVnNiRzhTRTJoMGRIQnpPaTh2WlhoaGJYQnNaUzVqYjIwYUF6RXVNQ0lNWlhoaGJYQnNaUzVrWlcxdlFnQT0ifQ==", + "id": "018bf9bd-3a3b-8000-a8cb-e955681e8530", + "specversion": "1.0", + "source": "/body.access//door.front_left#Door", + "type": "res.v1", + "priority": "UPRIORITY_CS2", + "ttl": 3, + "hash": "somehash", + "reqid": "requestIdFromRequestCloudEvent", + "sink": "/body.access//door.front_left#Door", + "test_detail": "test_create_response_cloud_event_originating_from_local_use" + }, + { + "serialized_ce": "eyJzcGVjdmVyc2lvbiI6IjEuMCIsImlkIjoiMDE4YmY5YmYtMjdlNC04MDAwLThkZDAtMjYyNzdjNzg3OTBlIiwic291cmNlIjoiL2JvZHkuYWNjZXNzLy9kb29yLmZyb250X2xlZnQjRG9vciIsInR5cGUiOiJyZXMudjEiLCJzaW5rIjoiL2JvZHkuYWNjZXNzLy9kb29yLmZyb250X2xlZnQjRG9vciIsImNvbW1zdGF0dXMiOjMsInByaW9yaXR5IjoiVVBSSU9SSVRZX0NTMiIsInR0bCI6MywiaGFzaCI6InNvbWVoYXNoIiwicmVxaWQiOiJyZXF1ZXN0SWRGcm9tUmVxdWVzdENsb3VkRXZlbnQiLCJkYXRhX2Jhc2U2NCI6IkNpbDBlWEJsTG1kdmIyZHNaV0Z3YVhNdVkyOXRMMmR2YjJkc1pTNXdjbTkwYjJKMVppNUZiWEIwZVE9PSJ9", + "id": "018bf9bf-27e4-8000-8dd0-26277c78790e", + "specversion": "1.0", + "source": "/body.access//door.front_left#Door", + "type": "res.v1", + "priority": "UPRIORITY_CS2", + "ttl": 3, + "hash": "somehash", + "reqid": "requestIdFromRequestCloudEvent", + "sink": "/body.access//door.front_left#Door", + "commstatus": 3, + "test_detail": "test_create_a_failed_response_cloud_event" + } + +] \ No newline at end of file diff --git a/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py b/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py index 33e01f6..a87f8b5 100644 --- a/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py +++ b/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py @@ -1,5 +1,6 @@ # ------------------------------------------------------------------------- - +import json +import os # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -33,6 +34,8 @@ UCloudEventAttributes from org_eclipse_uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent +from org_eclipse_uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer +from org_eclipse_uprotocol.cloudevent.serialize.cloudeventtojsonserializer import CloudEventToJsonSerializer from org_eclipse_uprotocol.proto.cloudevents_pb2 import CloudEvent from org_eclipse_uprotocol.proto.uattributes_pb2 import UMessageType, UPriority from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UEntity, UResource @@ -41,6 +44,16 @@ from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer +def get_json_object(): + current_directory = os.getcwd() + json_file_path = os.path.join(current_directory, "cloudevent.json") + + with open(json_file_path, 'r') as json_file: + json_data = json.load(json_file) + + return json_data + + def build_uri_for_test(): uri = UUri(entity=UEntity(name="body.access"), resource=UResource(name="door", instance="front_left", message="Door")) @@ -59,6 +72,35 @@ def build_proto_payload_for_test(): class CloudEventFactoryTest(unittest.TestCase): DATA_CONTENT_TYPE = CloudEventFactory.PROTOBUF_CONTENT_TYPE + def test_all_cloud_events_from_json(self): + # Access the "validUris" array + cloudevents = get_json_object() + for ce_json in cloudevents: + bytes_ce = Base64ProtobufSerializer().serialize(ce_json['serialized_ce']) + cloudevent = CloudEventToJsonSerializer().deserialize(bytes_ce) + self.assertEqual(UCloudEvent.get_id(cloudevent), ce_json['id']) + self.assertEqual(UCloudEvent.get_specversion(cloudevent), ce_json['specversion']) + if 'source' in ce_json: + self.assertEqual(UCloudEvent.get_source(cloudevent), ce_json['source']) + if 'sink' in ce_json: + self.assertEqual(UCloudEvent.get_sink(cloudevent), ce_json['sink']) + if 'type' in ce_json: + self.assertEqual(UCloudEvent.get_type(cloudevent), ce_json['type']) + if 'priority' in ce_json: + self.assertEqual(UCloudEvent.get_priority(cloudevent), ce_json['priority']) + if 'ttl' in ce_json: + self.assertEqual(UCloudEvent.get_ttl(cloudevent), ce_json['ttl']) + if 'hash' in ce_json: + self.assertEqual(UCloudEvent.get_hash(cloudevent), ce_json['hash']) + if 'token' in ce_json: + self.assertEqual(UCloudEvent.get_token(cloudevent), ce_json['token']) + if 'dataschema' in ce_json: + self.assertEqual(UCloudEvent.get_data_schema(cloudevent), ce_json['dataschema']) + if 'datacontenttype' in ce_json: + self.assertEqual(UCloudEvent.get_data_content_type(cloudevent), ce_json['datacontenttype']) + if 'commstatus' in ce_json: + self.assertEqual(UCloudEvent.get_communication_status(cloudevent), ce_json['commstatus']) + def test_create_base_cloud_event(self): source = build_uri_for_test() diff --git a/setup.cfg b/setup.cfg index 529f652..ee13861 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,6 +15,6 @@ packages = find: zip_safe = False install_requires = cloudevents - googleapis-common-protos>=1.56.4 +; googleapis-common-protos>=1.56.4 From 28780935a210c4cecac6d707a4ca07640f6b2258 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 23 Nov 2023 11:06:27 -0500 Subject: [PATCH 29/47] Add test cases for uuid module Add test cases for uuid module and resolve bugs discovered during test execution --- .../uuid/factory/uuidfactory.py | 28 +- .../uuid/factory/uuidutils.py | 68 +---- .../uuid/serializer/longuuidserializer.py | 5 +- .../uuid/serializer/microuuidserializer.py | 4 +- .../uuid/validate/uuidvalidator.py | 13 +- .../datamodel/ucloudeventattributestest.py | 31 +- .../factory/cloudeventfactorytest.py | 8 +- .../uuid/factory/uuidfactorytest.py | 285 ++++++++++++++++++ .../uuid/validator/uuidvalidatortest.py | 139 +++++++++ 9 files changed, 501 insertions(+), 80 deletions(-) create mode 100644 org_eclipse_uprotocol_tests/uuid/factory/uuidfactorytest.py create mode 100644 org_eclipse_uprotocol_tests/uuid/validator/uuidvalidatortest.py diff --git a/org_eclipse_uprotocol/uuid/factory/uuidfactory.py b/org_eclipse_uprotocol/uuid/factory/uuidfactory.py index c4ee690..eafe697 100644 --- a/org_eclipse_uprotocol/uuid/factory/uuidfactory.py +++ b/org_eclipse_uprotocol/uuid/factory/uuidfactory.py @@ -1,5 +1,8 @@ # ------------------------------------------------------------------------- - +import hashlib +import random +import struct +from datetime import datetime # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -35,7 +38,7 @@ class UUIDFactory: def create(self, instant=None): if instant is None: - instant = int(time.time() * 1000) # Current time in milliseconds + instant = datetime.now() return self._create(instant) def _create(self, instant): @@ -45,17 +48,28 @@ def _create(self, instant): class UUIDv6Factory(UUIDFactory): def _create(self, instant) -> UUID: - python_uuid = uuid6(instant) + + python_uuid = uuid6() msb, lsb = UUIDUtils.get_msb_lsb(python_uuid) return UUID(msb=msb, lsb=lsb) class UUIDv8Factory(UUIDFactory): - + MAX_COUNT = 0xfff + _lsb = (random.getrandbits(63) & 0x3fffffffffffffff) | 0x8000000000000000 + UUIDV8_VERSION = 8 + _msb = UUIDV8_VERSION << 12 def _create(self, instant) -> UUID: - python_uuid = uuid8() - msb, lsb = UUIDUtils.get_msb_lsb(python_uuid) - return UUID(msb=msb, lsb=lsb) + time = int(instant.timestamp() * 1000) if instant else int(datetime.now().timestamp() * 1000) + + if time == (self._msb >> 16): + if (self._msb & 0xFFF) < self.MAX_COUNT: + self._msb += 1 + else: + self._msb = (time << 16) | (8 << 12) + + return UUID(msb=self._msb, lsb=self._lsb) + # return UUID(msb=msb, lsb=lsb) class Factories(): diff --git a/org_eclipse_uprotocol/uuid/factory/uuidutils.py b/org_eclipse_uprotocol/uuid/factory/uuidutils.py index 2907fb2..e3ab558 100644 --- a/org_eclipse_uprotocol/uuid/factory/uuidutils.py +++ b/org_eclipse_uprotocol/uuid/factory/uuidutils.py @@ -26,9 +26,11 @@ import uuid +from datetime import datetime from enum import Enum from typing import Optional + from org_eclipse_uprotocol.proto.uuid_pb2 import UUID from org_eclipse_uprotocol.uuid.factory import PythonUUID @@ -43,7 +45,7 @@ class Version(Enum): VERSION_UPROTOCOL = 8 # The custom or free-form version proposed by Peabody and Davis. @staticmethod - def getVersion(value): + def getVersion(value: int): """ Get the Version from the passed integer representation of the version.

@param value:The integer representation of the version. @@ -60,53 +62,6 @@ class UUIDUtils: UUID Utils class that provides utility methods for uProtocol IDs """ - # @staticmethod - # def toString(uuid_obj: UUID) -> str: - # """ - # Convert the UUID to a String. - # @param uuid_obj:UUID object - # @return:String representation of the UUID or Optional.empty() if the UUID is null. - # """ - # return str(uuid_obj) if uuid_obj is not None else None - # - # @staticmethod - # def toBytes(uuid_obj: UUID) -> Optional[bytes]: - # """ - # Convert the UUID to byte array.

- # @param uuid_obj:UUID object - # @return:The byte array or Optional.empty() if the UUID is null. - # """ - # if uuid_obj is None: - # return None - # uuid_bytes = uuid_obj.bytes - # return uuid_bytes - # - # @staticmethod - # def fromBytes(bytes_list: bytes) -> Optional[UUID]: - # """ - # Convert the byte array to a UUID.

- # @param bytes_list:The UUID in bytes format. - # @return:UUIDv8 object built from the byte array or Optional.empty() if the byte array is null or not 16 bytes - # long. - # """ - # if bytes_list is None or len(bytes_list) != 16: - # return None - # uuid_bytes = bytes(bytes_list) - # return uuid.UUID(bytes=uuid_bytes) - - # @staticmethod - # def fromString(string: str) -> Optional[UUID]: - # """ - # Create a UUID from the passed string.

- # @param string:the string representation of the uuid. - # @return:The UUID object representation of the string or Optional.empty() if the string is null, empty, - # or invalid. - # """ - # try: - # return uuid.UUID(string) - # except ValueError: - # return None - @staticmethod def getVersion(uuid_obj: UUID) -> Optional[Version]: """ @@ -116,8 +71,9 @@ def getVersion(uuid_obj: UUID) -> Optional[Version]: """ if uuid_obj is None: return None - python_uuid = UUIDUtils.create_pythonuuid_from_msb_lsb(uuid_obj.msb, uuid_obj.lsb) - return Version.getVersion(python_uuid.version) + + return Version.getVersion((uuid_obj.msb >> 12) & 0x0f) + @staticmethod def getVariant(uuid_obj: UUID) -> Optional[str]: @@ -128,7 +84,7 @@ def getVariant(uuid_obj: UUID) -> Optional[str]: """ if uuid_obj is None: return None - python_uuid = UUIDUtils.create_pythonuuid_from_msb_lsb(uuid_obj.msb, uuid_obj.lsb) + python_uuid = UUIDUtils.create_pythonuuid_from_eclipseuuid(uuid_obj) return python_uuid.variant @@ -176,12 +132,12 @@ def getTime(uuid: UUID): version = UUIDUtils.getVersion(uuid) if uuid is None or version is None: return None - python_uuid = UUIDUtils.create_pythonuuid_from_msb_lsb(uuid.msb, uuid.lsb) if version == Version.VERSION_UPROTOCOL: - time = python_uuid.int >> 16 + time = uuid.msb >> 16 elif version == Version.VERSION_TIME_ORDERED: try: + python_uuid = UUIDUtils.create_pythonuuid_from_eclipseuuid(uuid) # Convert 100-nanoseconds ticks to milliseconds time = python_uuid.time // 10000 except ValueError: @@ -203,6 +159,8 @@ def get_msb_lsb(uuid: PythonUUID): return msb, lsb @staticmethod - def create_pythonuuid_from_msb_lsb(msb: int, lsb: int) -> PythonUUID: - combined_int = (msb << 64) + lsb + def create_pythonuuid_from_eclipseuuid(uuid:UUID) -> PythonUUID: + combined_int = (uuid.msb << 64) + uuid.lsb return PythonUUID(int=combined_int) + # from org_eclipse_uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer + # return PythonUUID(hex=LongUuidSerializer.instance().serialize(uuid)) diff --git a/org_eclipse_uprotocol/uuid/serializer/longuuidserializer.py b/org_eclipse_uprotocol/uuid/serializer/longuuidserializer.py index 6d3e3a4..556f61e 100644 --- a/org_eclipse_uprotocol/uuid/serializer/longuuidserializer.py +++ b/org_eclipse_uprotocol/uuid/serializer/longuuidserializer.py @@ -60,5 +60,8 @@ def serialize(self, uuid: UUID) -> str: :param uuid: UUID object to be serialized to a string. :return: Returns the UUID in the string serialized format. """ - pythonuuid = UUIDUtils.create_pythonuuid_from_msb_lsb(uuid.msb, uuid.lsb) + if uuid is None: + return '' + + pythonuuid = UUIDUtils.create_pythonuuid_from_eclipseuuid(uuid) return str(pythonuuid) if uuid else '' diff --git a/org_eclipse_uprotocol/uuid/serializer/microuuidserializer.py b/org_eclipse_uprotocol/uuid/serializer/microuuidserializer.py index 8c8c485..e557940 100644 --- a/org_eclipse_uprotocol/uuid/serializer/microuuidserializer.py +++ b/org_eclipse_uprotocol/uuid/serializer/microuuidserializer.py @@ -60,7 +60,7 @@ def serialize(self, uuid: UUID) -> bytes: :return: Returns the UUID in the bytes serialized format. """ if uuid is None: - return b'\x00' * 16 # Return 16 zero bytes for a None UUID - pythonuuid = UUIDUtils.create_pythonuuid_from_msb_lsb(uuid.msb, uuid.lsb) + return bytearray() + pythonuuid = UUIDUtils.create_pythonuuid_from_eclipseuuid(uuid) msb, lsb = divmod(pythonuuid.int, 2 ** 64) return struct.pack('>QQ', msb, lsb) diff --git a/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py b/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py index 3a13b6c..33d2246 100644 --- a/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py +++ b/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py @@ -31,6 +31,7 @@ from org_eclipse_uprotocol.proto.uuid_pb2 import UUID from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils, Version from org_eclipse_uprotocol.validation.validationresult import ValidationResult +from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus, UCode class UuidVariant(Enum): @@ -51,13 +52,13 @@ def get_validator(uuid: UUID): else: return Validators.UNKNOWN.validator() - def validate(self, uuid: UUID) -> ValidationResult: + def validate(self, uuid: UUID) -> UStatus: error_messages = [self.validate_version(uuid), self.validate_variant(uuid), self.validate_time(uuid)] - error_messages = [result.message for result in error_messages if result.is_failure] - error_message = ", ".join(error_messages) + error_messages = [result.get_message() for result in error_messages if result.is_failure()] + error_message = ",".join(error_messages) if not error_message: - return ValidationResult.success() - return ValidationResult.failure(f"Invalid argument value: {error_message}") + return ValidationResult.success().to_status() + return UStatus(code=UCode.INVALID_ARGUMENT, message=error_message) @abstractmethod def validate_version(self, uuid: UUID) -> ValidationResult: @@ -65,7 +66,7 @@ def validate_version(self, uuid: UUID) -> ValidationResult: def validate_time(self, uuid: UUID) -> ValidationResult: time = UUIDUtils.getTime(uuid) - return ValidationResult.success() if time > 0 else ValidationResult.failure("Invalid UUID Time") + return ValidationResult.success() if (time is not None and time > 0 )else ValidationResult.failure("Invalid UUID Time") @abstractmethod def validate_variant(self, uuid: UUID) -> ValidationResult: diff --git a/org_eclipse_uprotocol_tests/cloudevent/datamodel/ucloudeventattributestest.py b/org_eclipse_uprotocol_tests/cloudevent/datamodel/ucloudeventattributestest.py index a4ee19a..967229e 100644 --- a/org_eclipse_uprotocol_tests/cloudevent/datamodel/ucloudeventattributestest.py +++ b/org_eclipse_uprotocol_tests/cloudevent/datamodel/ucloudeventattributestest.py @@ -1,3 +1,30 @@ + +# ------------------------------------------------------------------------- + +# 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 unittest from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder, \ @@ -7,10 +34,6 @@ class UCloudEventAttributesTest(unittest.TestCase): - # def test_hash_code_equals(self): - # # Assuming you have an equivalent of EqualsVerifier in Python - # # For example, using the `attrs` library to define data classes - # self.fail("Not implemented: Need equivalent of EqualsVerifier in Python") def test_to_string(self): u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( diff --git a/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py b/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py index a87f8b5..3abbfa1 100644 --- a/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py +++ b/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py @@ -1,6 +1,5 @@ # ------------------------------------------------------------------------- -import json -import os + # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -27,9 +26,9 @@ import unittest - +import json +import os from google.protobuf import any_pb2 - from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder, \ UCloudEventAttributes from org_eclipse_uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory @@ -73,7 +72,6 @@ class CloudEventFactoryTest(unittest.TestCase): DATA_CONTENT_TYPE = CloudEventFactory.PROTOBUF_CONTENT_TYPE def test_all_cloud_events_from_json(self): - # Access the "validUris" array cloudevents = get_json_object() for ce_json in cloudevents: bytes_ce = Base64ProtobufSerializer().serialize(ce_json['serialized_ce']) diff --git a/org_eclipse_uprotocol_tests/uuid/factory/uuidfactorytest.py b/org_eclipse_uprotocol_tests/uuid/factory/uuidfactorytest.py new file mode 100644 index 0000000..8bebff7 --- /dev/null +++ b/org_eclipse_uprotocol_tests/uuid/factory/uuidfactorytest.py @@ -0,0 +1,285 @@ +# ------------------------------------------------------------------------- + +# 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 datetime import datetime, timedelta +import unittest +from org_eclipse_uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer +from org_eclipse_uprotocol.uuid.serializer.microuuidserializer import MicroUuidSerializer +from org_eclipse_uprotocol.uuid.factory.uuidfactory import UUIDFactory, Factories +from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils, Version +from org_eclipse_uprotocol.proto.uuid_pb2 import UUID + + + +class TestUUIDFactory(unittest.TestCase): + + def test_uuidv8_creation(self): + now = datetime.now() + uuid = Factories.UPROTOCOL.create(now) + version = UUIDUtils.getVersion(uuid) + time = UUIDUtils.getTime(uuid) + bytes_data = MicroUuidSerializer.instance().serialize(uuid) + uuid_string = LongUuidSerializer.instance().serialize(uuid) + + self.assertIsNotNone(uuid) + self.assertTrue(UUIDUtils.isUProtocol(uuid)) + self.assertTrue(UUIDUtils.isuuid(uuid)) + self.assertFalse(UUIDUtils.isUuidv6(uuid)) + self.assertTrue(version) + self.assertTrue(time) + self.assertEqual(time, int(now.timestamp() * 1000)) + + self.assertGreater(len(bytes_data), 0) + self.assertFalse(uuid_string.isspace()) + + uuid1 = MicroUuidSerializer.instance().deserialize(bytes_data) + uuid2 = LongUuidSerializer.instance().deserialize(uuid_string) + + self.assertNotEqual(uuid1, UUID()) + self.assertNotEqual(uuid2, UUID()) + self.assertEqual(uuid, uuid1) + self.assertEqual(uuid, uuid2) + + def test_uuidv8_creation_with_null_instant(self): + uuid = Factories.UPROTOCOL.create(None) + version = UUIDUtils.getVersion(uuid) + time = UUIDUtils.getTime(uuid) + bytes_data = MicroUuidSerializer.instance().serialize(uuid) + uuid_string = LongUuidSerializer.instance().serialize(uuid) + + self.assertIsNotNone(uuid) + self.assertTrue(UUIDUtils.isUProtocol(uuid)) + self.assertTrue(UUIDUtils.isuuid(uuid)) + self.assertFalse(UUIDUtils.isUuidv6(uuid)) + self.assertTrue(version) + self.assertTrue(time) + self.assertGreater(len(bytes_data), 0) + self.assertFalse(uuid_string.isspace()) + + uuid1 = MicroUuidSerializer.instance().deserialize(bytes_data) + uuid2 = LongUuidSerializer.instance().deserialize(uuid_string) + + self.assertNotEqual(uuid1, UUID()) + self.assertNotEqual(uuid2, UUID()) + self.assertEqual(uuid, uuid1) + self.assertEqual(uuid, uuid2) + + def test_uuidv8_overflow(self): + uuid_list = [] + max_count = 4095 + + now = datetime.now() + for i in range(max_count * 2): + uuid_list.append(Factories.UPROTOCOL.create(now)) + + self.assertEqual(UUIDUtils.getTime(uuid_list[0]), UUIDUtils.getTime(uuid_list[i])) + self.assertEqual(uuid_list[0].lsb, uuid_list[i].lsb) + if i > max_count: + self.assertEqual(uuid_list[max_count].msb, uuid_list[i].msb) + + def test_uuidv6_creation_with_instant(self): + now = datetime.now() + uuid = Factories.UUIDV6.create(now) + version = UUIDUtils.getVersion(uuid) + # time = UUIDUtils.getTime(uuid) + bytes_data = MicroUuidSerializer.instance().serialize(uuid) + uuid_string = LongUuidSerializer.instance().serialize(uuid) + + self.assertIsNotNone(uuid) + self.assertTrue(UUIDUtils.isUuidv6(uuid)) + self.assertTrue(UUIDUtils.isuuid(uuid)) + self.assertFalse(UUIDUtils.isUProtocol(uuid)) + self.assertTrue(version) + # self.assertTrue(time) + # self.assertEqual(time, int(17007094616498160 * 1000)) + self.assertGreater(len(bytes_data), 0) + self.assertFalse(uuid_string.isspace()) + + uuid1 = MicroUuidSerializer.instance().deserialize(bytes_data) + uuid2 = LongUuidSerializer.instance().deserialize(uuid_string) + + self.assertNotEqual(uuid1, UUID()) + self.assertNotEqual(uuid2, UUID()) + self.assertEqual(uuid, uuid1) + self.assertEqual(uuid, uuid2) + + def test_uuidv6_creation_with_null_instant(self): + uuid = Factories.UUIDV6.create(None) + version = UUIDUtils.getVersion(uuid) + time = UUIDUtils.getTime(uuid) + bytes_data = MicroUuidSerializer.instance().serialize(uuid) + uuid_string = LongUuidSerializer.instance().serialize(uuid) + + self.assertIsNotNone(uuid) + self.assertTrue(UUIDUtils.isUuidv6(uuid)) + self.assertFalse(UUIDUtils.isUProtocol(uuid)) + self.assertTrue(UUIDUtils.isuuid(uuid)) + self.assertTrue(version) + self.assertTrue(time) + self.assertGreater(len(bytes_data), 0) + self.assertFalse(uuid_string.isspace()) + + uuid1 = MicroUuidSerializer.instance().deserialize(bytes_data) + uuid2 = LongUuidSerializer.instance().deserialize(uuid_string) + + self.assertNotEqual(uuid1, UUID()) + self.assertNotEqual(uuid2, UUID()) + self.assertEqual(uuid, uuid1) + self.assertEqual(uuid, uuid2) + + def test_UUIDUtils_for_random_uuid(self): + uuid = LongUuidSerializer.instance().deserialize("195f9bd1-526d-4c28-91b1-ff34c8e3632d") + version = UUIDUtils.getVersion(uuid) + time = UUIDUtils.getTime(uuid) + bytes_data = MicroUuidSerializer.instance().serialize(uuid) + uuid_string = LongUuidSerializer.instance().serialize(uuid) + + self.assertIsNotNone(uuid) + self.assertFalse(UUIDUtils.isUuidv6(uuid)) + self.assertFalse(UUIDUtils.isUProtocol(uuid)) + self.assertFalse(UUIDUtils.isuuid(uuid)) + self.assertTrue(version) + self.assertFalse(time) + self.assertGreater(len(bytes_data), 0) + self.assertFalse(uuid_string.isspace()) + + uuid1 = MicroUuidSerializer.instance().deserialize(bytes_data) + uuid2 = LongUuidSerializer.instance().deserialize(uuid_string) + + self.assertNotEqual(uuid1, UUID()) + self.assertNotEqual(uuid2, UUID()) + self.assertEqual(uuid, uuid1) + self.assertEqual(uuid, uuid2) + + def test_UUIDUtils_for_empty_uuid(self): + uuid = UUID() + version = UUIDUtils.getVersion(uuid) + time = UUIDUtils.getTime(uuid) + bytes_data = MicroUuidSerializer.instance().serialize(uuid) + uuid_string = LongUuidSerializer.instance().serialize(uuid) + + self.assertIsNotNone(uuid) + self.assertFalse(UUIDUtils.isUuidv6(uuid)) + self.assertFalse(UUIDUtils.isUProtocol(uuid)) + self.assertTrue(version) + self.assertEqual(version, Version.VERSION_UNKNOWN) + self.assertFalse(time) + self.assertGreater(len(bytes_data), 0) + self.assertFalse(uuid_string.isspace()) + self.assertFalse(UUIDUtils.isUuidv6(None)) + self.assertFalse(UUIDUtils.isUProtocol(None)) + self.assertFalse(UUIDUtils.isuuid(None)) + + uuid1 = MicroUuidSerializer.instance().deserialize(bytes_data) + + self.assertTrue(uuid1, UUID()) + self.assertEqual(uuid, uuid1) + + uuid2 = LongUuidSerializer.instance().deserialize(uuid_string) + self.assertTrue(uuid2, UUID()) + self.assertEqual(uuid, uuid2) + + def test_UUIDUtils_for_null_uuid(self): + self.assertFalse(UUIDUtils.getVersion(None)) + self.assertEqual(len(MicroUuidSerializer.instance().serialize(None)), 0) + self.assertEqual(len(LongUuidSerializer.instance().serialize(None)),0) + self.assertFalse(UUIDUtils.isUuidv6(None)) + self.assertFalse(UUIDUtils.isUProtocol(None)) + self.assertFalse(UUIDUtils.isuuid(None)) + self.assertFalse(UUIDUtils.getTime(None)) + + def test_uuidutils_from_invalid_uuid(self): + uuid = UUID(msb=9 << 12, lsb=0) # Invalid UUID type + self.assertFalse(UUIDUtils.getVersion(uuid)) + self.assertFalse(UUIDUtils.getTime(uuid)) + self.assertTrue(len(MicroUuidSerializer.instance().serialize(uuid)) > 0) + self.assertFalse(LongUuidSerializer.instance().serialize(uuid).isspace()) + self.assertFalse(UUIDUtils.isUuidv6(uuid)) + self.assertFalse(UUIDUtils.isUProtocol(uuid)) + self.assertFalse(UUIDUtils.isuuid(uuid)) + self.assertFalse(UUIDUtils.getTime(uuid)) + + def test_uuidutils_fromstring_with_invalid_string(self): + uuid = LongUuidSerializer.instance().deserialize(None) + self.assertTrue(uuid == UUID()) + uuid1 = LongUuidSerializer.instance().deserialize("") + self.assertTrue(uuid1 == UUID()) + + def test_uuidutils_frombytes_with_invalid_bytes(self): + uuid = MicroUuidSerializer.instance().deserialize(None) + self.assertTrue(uuid == UUID()) + uuid1 = MicroUuidSerializer.instance().deserialize(bytearray()) + self.assertTrue(uuid1 == UUID()) + + def test_create_uprotocol_uuid_in_the_past(self): + past = datetime.utcnow() - timedelta(seconds=10) + uuid = Factories.UPROTOCOL.create(past) + time = UUIDUtils.getTime(uuid) + self.assertTrue(UUIDUtils.isUProtocol(uuid)) + self.assertTrue(UUIDUtils.isuuid(uuid)) + self.assertTrue(time is not None) + self.assertEqual(time, int(past.timestamp() * 1000)) + + def test_create_uprotocol_uuid_with_different_time_values(self): + uuid = Factories.UPROTOCOL.create() + import time + time.sleep(0.01) # Sleep for 10 milliseconds + uuid1 = Factories.UPROTOCOL.create() + time = UUIDUtils.getTime(uuid) + time1 = UUIDUtils.getTime(uuid1) + + self.assertTrue(UUIDUtils.isUProtocol(uuid)) + self.assertTrue(UUIDUtils.isuuid(uuid)) + self.assertTrue(UUIDUtils.isUProtocol(uuid1)) + self.assertTrue(UUIDUtils.isuuid(uuid1)) + + self.assertTrue(time is not None) + self.assertTrue(time1 is not None) + self.assertNotEqual(time, time1) + + def test_create_both_uuidv6_and_v8_to_compare_performance(self): + uuidv6_list = [] + uuidv8_list = [] + max_count = 10000 + + start = datetime.utcnow() + for _ in range(max_count): + uuidv8_list.append(Factories.UPROTOCOL.create()) + v8_diff = datetime.utcnow() - start + + start = datetime.utcnow() + for _ in range(max_count): + uuidv6_list.append(Factories.UUIDV6.create()) + v6_diff = datetime.utcnow() - start + + print( + f"UUIDv8: [{v8_diff.total_seconds() / max_count}s] UUIDv6: [{v6_diff.total_seconds() / max_count}s]") + + +if __name__ == '__main__': + unittest.main() + diff --git a/org_eclipse_uprotocol_tests/uuid/validator/uuidvalidatortest.py b/org_eclipse_uprotocol_tests/uuid/validator/uuidvalidatortest.py new file mode 100644 index 0000000..1afb0da --- /dev/null +++ b/org_eclipse_uprotocol_tests/uuid/validator/uuidvalidatortest.py @@ -0,0 +1,139 @@ +# ------------------------------------------------------------------------- + +# 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 unittest +from datetime import datetime, timezone + +from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils +from org_eclipse_uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer + +from org_eclipse_uprotocol.validation.validationresult import ValidationResult +from org_eclipse_uprotocol.proto.uuid_pb2 import UUID +from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode +from org_eclipse_uprotocol.uuid.factory.uuidfactory import Factories +from org_eclipse_uprotocol.uuid.validate.uuidvalidator import UuidValidator, Validators + + +class TestUuidValidator(unittest.TestCase): + def test_validator_with_good_uuid(self): + uuid = Factories.UPROTOCOL.create() + status = UuidValidator.get_validator(uuid).validate(uuid) + self.assertEqual(ValidationResult.STATUS_SUCCESS, status) + + def test_good_uuid_string(self): + status = Validators.UPROTOCOL.validator().validate( + Factories.UPROTOCOL.create() + ) + self.assertEqual(status, ValidationResult.STATUS_SUCCESS) + # self.assertTrue(ValidationResult.success().__eq__(status)) + + def test_invalid_uuid(self): + uuid = UUID(msb=0, lsb=0) + status = UuidValidator.get_validator(uuid).validate(uuid) + self.assertEqual(UCode.INVALID_ARGUMENT, status.code) + self.assertEqual( + "Invalid UUID Version,Invalid UUID Variant,Invalid UUID Time", + status.message, + ) + + def test_invalid_time_uuid(self): + epoch_time = datetime.utcfromtimestamp(0).replace(tzinfo=timezone.utc) + + uuid = Factories.UPROTOCOL.create( + epoch_time + ) + status = Validators.UPROTOCOL.validator().validate(uuid) + self.assertEqual(UCode.INVALID_ARGUMENT, status.code) + self.assertEqual("Invalid UUID Time", status.message) + + def test_uuidv8_with_invalid_uuids(self): + validator = Validators.UPROTOCOL.validator() + self.assertIsNotNone(validator) + status = validator.validate(None) + self.assertEqual(UCode.INVALID_ARGUMENT, status.code) + self.assertEqual("Invalid UUIDv8 Version,Invalid UUID Time", status.message) + + def test_uuidv8_with_invalid_types(self): + uuidv6 = Factories.UUIDV6.create() + uuid = UUID(msb=0, lsb=0) + uuidv4 = LongUuidSerializer.instance().deserialize("195f9bd1-526d-4c28-91b1-ff34c8e3632d") + + validator = Validators.UPROTOCOL.validator() + self.assertIsNotNone(validator) + + status = validator.validate(uuidv6) + self.assertEqual(UCode.INVALID_ARGUMENT, status.code) + self.assertEqual("Invalid UUIDv8 Version", status.message) + + status1 = validator.validate(uuid) + self.assertEqual(UCode.INVALID_ARGUMENT, status1.code) + self.assertEqual("Invalid UUIDv8 Version,Invalid UUID Time", status1.message) + + status2 = validator.validate(uuidv4) + self.assertEqual(UCode.INVALID_ARGUMENT, status2.code) + self.assertEqual("Invalid UUIDv8 Version,Invalid UUID Time", status2.message) + + def test_good_uuidv6(self): + uuid = Factories.UUIDV6.create() + + validator = UuidValidator.get_validator(uuid) + self.assertIsNotNone(validator) + self.assertTrue(UUIDUtils.isUuidv6(uuid)) + self.assertEqual(UCode.OK, validator.validate(uuid).code) + + def test_uuidv6_with_invalid_uuid(self): + uuid = UUID(msb=9 << 12, lsb=0) + validator = Validators.UUIDV6.validator() + self.assertIsNotNone(validator) + status = validator.validate(uuid) + self.assertEqual( + "Not a UUIDv6 Version,Invalid UUIDv6 variant,Invalid UUID Time", + status.message, + ) + self.assertEqual(UCode.INVALID_ARGUMENT, status.code) + + def test_uuidv6_with_null_uuid(self): + validator = Validators.UUIDV6.validator() + self.assertIsNotNone(validator) + status = validator.validate(None) + self.assertEqual( + "Not a UUIDv6 Version,Invalid UUIDv6 variant,Invalid UUID Time", + status.message, + ) + self.assertEqual(UCode.INVALID_ARGUMENT, status.code) + + def test_uuidv6_with_uuidv8(self): + uuid = Factories.UPROTOCOL.create() + validator = Validators.UUIDV6.validator() + self.assertIsNotNone(validator) + status = validator.validate(uuid) + self.assertEqual("Not a UUIDv6 Version", status.message) + self.assertEqual(UCode.INVALID_ARGUMENT, status.code) + + +if __name__ == "__main__": + unittest.main() From 25dd59fbf90668905494941e72c542676da28b02 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 23 Nov 2023 12:59:35 -0500 Subject: [PATCH 30/47] Add clean_project script for cleaning build artifacts --- clean_project.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 clean_project.py diff --git a/clean_project.py b/clean_project.py new file mode 100644 index 0000000..8a2ae69 --- /dev/null +++ b/clean_project.py @@ -0,0 +1,22 @@ +import os +import shutil + + +def clean_project(): + # Remove build/ directory + if os.path.exists('build'): + shutil.rmtree('build') + + # Remove dist/ directory + if os.path.exists('dist'): + shutil.rmtree('dist') + + # Remove *.egg-info/ directories + egg_info_directories = [d for d in os.listdir() if d.endswith('.egg-info')] + for egg_info_directory in egg_info_directories: + shutil.rmtree(egg_info_directory) + + +if __name__ == "__main__": + clean_project() + print("Cleanup complete.") From 09132a530d69cc331c89bcba8e8fd4d3675f467a Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 23 Nov 2023 13:12:51 -0500 Subject: [PATCH 31/47] Address review comments 1. Rename the project to uprotocol-python 2. Remove all references to SDV-202 3. Fix spelling mistakes 4. Make hash , token and ttl parameters optional in ucloudevent attributes class --- .../cloudevent/datamodel/ucloudeventattributes.py | 15 ++++++++------- org_eclipse_uprotocol/rpc/rpcclient.py | 2 +- setup.cfg | 3 +-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py index 8c1e885..111c9a5 100644 --- a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py +++ b/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py @@ -35,11 +35,11 @@ class UCloudEventAttributes: Specifies the properties that can configure the UCloudEvent. """ - def __init__(self, hash_value: str, priority: UPriority, ttl: int, token: str): + def __init__(self, priority: UPriority, hash_value: str = None, ttl: int = None, token: str = None): """ Construct the properties object.

@param hash_value: an HMAC generated on the data portion of the CloudEvent message using the device key. - @param priority: uProtocol Prioritization classifications defined at QoS in SDV-202. + @param priority: uProtocol Prioritization classifications. @param ttl: How long this event should live for after it was generated (in milliseconds). Events without this attribute (or value is 0) MUST NOT timeout. @param token: Oauth2 access token to perform the access request defined in the request message. @@ -64,7 +64,8 @@ def is_empty(self): @return: Returns true if this attributes container is an empty container and has no valuable information in 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()) + 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()) def get_hash(self) -> str: """ @@ -75,7 +76,7 @@ def get_hash(self) -> str: def get_priority(self) -> UPriority: """ - uProtocol Prioritization classifications defined at QoS in SDV-202.

+ uProtocol Prioritization classifications.

@return: Returns an Optional priority attribute. """ return self.priority @@ -133,8 +134,8 @@ def with_hash(self, hash_value: str): def with_priority(self, priority: UPriority): """ - Add a uProtocol Prioritization classifications defined at QoS in SDV-202.

- @param priority: priority uProtocol Prioritization classifications defined at QoS in SDV-202. + Add a uProtocol Prioritization classifications.

+ @param priority: priority uProtocol Prioritization classifications. @return: Returns the UCloudEventAttributesBuilder with the configured priority. """ self.priority = UPriority.Name(priority) @@ -165,7 +166,7 @@ def build(self): Construct the UCloudEventAttributes from the builder.

@return: Returns a constructed UProperty. """ - return UCloudEventAttributes(self.hash, self.priority, self.ttl, self.token) + return UCloudEventAttributes(self.priority, self.hash, self.ttl, self.token) if __name__ == "__main__": diff --git a/org_eclipse_uprotocol/rpc/rpcclient.py b/org_eclipse_uprotocol/rpc/rpcclient.py index ce8fee9..12cdc21 100644 --- a/org_eclipse_uprotocol/rpc/rpcclient.py +++ b/org_eclipse_uprotocol/rpc/rpcclient.py @@ -51,7 +51,7 @@ def invoke_method(self, topic: UUri, payload: UPayload, attributes: UAttributes) @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:etadata for the method invocation (i.e. priority, timeout, etc.) + @param attributes: metadata for the method invocation (i.e. priority, timeout, etc.) @return: Returns the CompletableFuture with the result or exception. """ pass diff --git a/setup.cfg b/setup.cfg index ee13861..1240532 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,12 +1,11 @@ [metadata] -name = uprotocol-sdk-python +name = uprotocol-python author = Neelam Kushwah author_email = neelam.kushwah@gm.com description = UProtocol Python SDK version = 1 keywords = uprotocol - sdk python [options] From ca58700318e6eacfabace72f39d1ed1b95c00ba8 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 23 Nov 2023 23:46:46 -0500 Subject: [PATCH 32/47] Add test cases for transport module Add test cases for transport module and resolve bugs discovered during test execution --- .../transport/builder/uattributesbuilder.py | 28 +- .../validate/uattributesvalidator.py | 45 +- .../builder/uattributesbuildertest.py | 107 ++++ .../validate/uattributesvalidatortest.py | 471 ++++++++++++++++++ 4 files changed, 607 insertions(+), 44 deletions(-) create mode 100644 org_eclipse_uprotocol_tests/transport/builder/uattributesbuildertest.py create mode 100644 org_eclipse_uprotocol_tests/transport/validate/uattributesvalidatortest.py diff --git a/org_eclipse_uprotocol/transport/builder/uattributesbuilder.py b/org_eclipse_uprotocol/transport/builder/uattributesbuilder.py index 633979b..4e795f4 100644 --- a/org_eclipse_uprotocol/transport/builder/uattributesbuilder.py +++ b/org_eclipse_uprotocol/transport/builder/uattributesbuilder.py @@ -42,7 +42,7 @@ class UAttributesBuilder: def __init__(self, id: UUID, type: UMessageType, priority: UPriority): self.id = id - self.type = type + self.type = UMessageType.Name(type) self.priority = priority self.ttl = None self.token = None @@ -183,29 +183,7 @@ def build(self): """ attributes = UAttributes(id=self.id, type=self.type, priority=self.priority) if self.sink is not None: - if self.sink.HasField('authority'): - if self.sink.authority.HasField('id'): - attributes.sink.authority.id = self.sink.authority.id - if self.sink.authority.HasField('name'): - attributes.sink.authority.name = self.sink.authority.name - if self.sink.authority.HasField('ip'): - attributes.sink.authority.ip = self.sink.authority.ip - if self.sink.HasField('entity'): - attributes.sink.entity.name = self.sink.entity.name - if self.sink.entity.HasField('id'): - attributes.sink.entity.id = self.sink.entity.id - if self.sink.entity.HasField('version_major'): - attributes.sink.entity.version_major = self.sink.entity.version_major - if self.sink.entity.HasField('version_minor'): - attributes.sink.entity.version_minor = self.sink.entity.version_minor - if self.sink.HasField('resource'): - attributes.sink.resource.name = self.sink.resource.name - if self.sink.resource.HasField('id'): - attributes.sink.resource.id = self.sink.resource.id - if self.sink.resource.HasField('instance'): - attributes.sink.resource.instance = self.sink.resource.instance - if self.sink.resource.HasField('message'): - attributes.sink.resource.message = self.sink.resource.message + attributes.sink.CopyFrom(self.sink) if self.ttl is not None: attributes.ttl = self.ttl if self.plevel is not None: @@ -213,7 +191,7 @@ def build(self): if self.commstatus is not None: attributes.commstatus = self.commstatus if self.reqid is not None: - attributes.reqid = self.reqid + attributes.reqid.CopyFrom(self.reqid) if self.token != None: attributes.token = self.token return attributes diff --git a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py index 3484156..d20315c 100644 --- a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py +++ b/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------- - +import time # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -73,7 +73,7 @@ def validate(self, attributes: UAttributes) -> ValidationResult: for invalid configurations. """ error_messages = [self.validate_type(attributes), - self.validate_ttl(attributes), + self.validate_ttl(attributes), self.validate_sink(attributes), self.validate_comm_status(attributes), self.validate_permission_level(attributes), self.validate_req_id(attributes)] @@ -99,15 +99,14 @@ def is_expired(u_attributes: UAttributes): ttl = maybe_ttl if ttl <= 0: return ValidationResult.success() - delta = int((datetime.now() - maybe_time).total_seconds() * 1000) + delta = int(time.time() * 1000)- maybe_time return ValidationResult.failure("Payload is expired") if delta >= ttl else ValidationResult.success() - - @staticmethod def validate_ttl(attr: UAttributes) -> ValidationResult: """ - Validate the time to live configuration. If the UAttributes does not contain a time to live then the ValidationResult + Validate the time to live configuration. If the UAttributes does not contain a time to live then the + ValidationResult is ok.

@param attr:UAttributes object containing the message time to live configuration to validate. @return:Returns a ValidationResult that is success or failed with a failure message. @@ -120,7 +119,8 @@ def validate_ttl(attr: UAttributes) -> ValidationResult: @staticmethod def validate_sink(attr: UAttributes) -> ValidationResult: """ - Validate the sink UriPart for the default case. If the UAttributes does not contain a sink then the ValidationResult + Validate the sink UriPart for the default case. If the UAttributes does not contain a sink then the + ValidationResult is ok.

@param attr:UAttributes object containing the sink to validate. @return:Returns a ValidationResult that is success or failed with a failure message. @@ -149,8 +149,9 @@ def validate_comm_status(attr: UAttributes) -> ValidationResult: @return:Returns a ValidationResult that is success or failed with a failure message. """ if attr.HasField('commstatus'): - enum_value = UCode.Name(attr.commstatus) - if enum_value is None: + try: + UCode.Name(attr.commstatus) + except ValueError: return ValidationResult.failure("Invalid Communication Status Code") return ValidationResult.success() @@ -169,8 +170,6 @@ def validate_req_id(attr: UAttributes) -> ValidationResult: else: return ValidationResult.success() - - @abstractmethod def validate_type(self, attr: UAttributes): """ @@ -194,7 +193,7 @@ def validate_type(self, attributes_value: UAttributes) -> ValidationResult: """ return ValidationResult.success() if attributes_value.type == UMessageType.UMESSAGE_TYPE_PUBLISH else ( ValidationResult.failure( - f"Wrong Attribute Type [{attributes_value.type}]")) + f"Wrong Attribute Type [{UMessageType.Name(attributes_value.type)}]")) def __str__(self): return "UAttributesValidator.Publish" @@ -212,8 +211,8 @@ def validate_type(self, attributes_value: UAttributes) -> ValidationResult: @return:Returns a ValidationResult that is success or failed with a failure message. """ return ValidationResult.success() if attributes_value.type == UMessageType.UMESSAGE_TYPE_REQUEST else ( - ValidationResult.failure( - f"Wrong Attribute Type [{attributes_value.type}]")) + ValidationResult.failure( + f"Wrong Attribute Type [{UMessageType.Name(attributes_value.type)}]")) def validate_sink(self, attributes_value: UAttributes) -> ValidationResult: """ @@ -223,7 +222,7 @@ def validate_sink(self, attributes_value: UAttributes) -> ValidationResult: @return:Returns a ValidationResult that is success or failed with a failure message. """ return UriValidator.validate_rpc_response( - attributes_value.sink) if attributes_value.sink else ValidationResult.failure("Missing Sink") + attributes_value.sink) if attributes_value.HasField('sink') else ValidationResult.failure("Missing Sink") def validate_ttl(self, attributes_value: UAttributes) -> ValidationResult: """ @@ -231,7 +230,12 @@ def validate_ttl(self, attributes_value: UAttributes) -> ValidationResult: @param attributes_value:UAttributes object containing the time to live to validate. @return:Returns a ValidationResult that is success or failed with a failure message. """ - return ValidationResult.success() if attributes_value.ttl and attributes_value.ttl > 0 else ValidationResult.failure("Missing TTL") + if not attributes_value.HasField('ttl'): + return ValidationResult.failure("Missing TTL") + if attributes_value.ttl <= 0: + return ValidationResult.failure(f"Invalid TTL [{attributes_value.ttl}]") + + return ValidationResult.success() def __str__(self): return "UAttributesValidator.Request" @@ -250,7 +254,7 @@ def validate_type(self, attributes_value: UAttributes) -> ValidationResult: """ return ValidationResult.success() if attributes_value.type == UMessageType.UMESSAGE_TYPE_RESPONSE else ( ValidationResult.failure( - f"Wrong Attribute Type [{attributes_value.type}]")) + f"Wrong Attribute Type [{UMessageType.Name(attributes_value.type)}]")) def validate_sink(self, attributes_value: UAttributes) -> ValidationResult: """ @@ -259,8 +263,11 @@ def validate_sink(self, attributes_value: UAttributes) -> ValidationResult: @param attributes_value:UAttributes object containing the sink to validate. @return:Returns a ValidationResult that is success or failed with a failure message. """ - return UriValidator.validate_rpc_method( - attributes_value.sink) if attributes_value.sink else ValidationResult.failure("Missing Sink") + result = UriValidator.validate_rpc_method(attributes_value.sink) + if result.is_success(): + return result + else: + return ValidationResult.failure("Missing Sink") def validate_req_id(self, attributes_value: UAttributes) -> ValidationResult: """ diff --git a/org_eclipse_uprotocol_tests/transport/builder/uattributesbuildertest.py b/org_eclipse_uprotocol_tests/transport/builder/uattributesbuildertest.py new file mode 100644 index 0000000..c749bb5 --- /dev/null +++ b/org_eclipse_uprotocol_tests/transport/builder/uattributesbuildertest.py @@ -0,0 +1,107 @@ +# ------------------------------------------------------------------------- + +# 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 unittest +from org_eclipse_uprotocol.transport.builder.uattributesbuilder import UAttributesBuilder +from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority, UMessageType +from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UAuthority, UEntity +from org_eclipse_uprotocol.uri.builder.uresource_builder import UResourceBuilder +from org_eclipse_uprotocol.uuid.factory.uuidfactory import Factories + + +def build_sink(): + 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_response()) + + +def get_uuid(): + return Factories.UPROTOCOL.create() + + +class UAttributesBuilderTest(unittest.TestCase): + + def test_publish(self): + builder = UAttributesBuilder.publish(UPriority.UPRIORITY_CS1) + self.assertIsNotNone(builder) + attributes = builder.build() + self.assertIsNotNone(attributes) + self.assertEqual(UMessageType.UMESSAGE_TYPE_PUBLISH, attributes.type) + self.assertEqual(UPriority.UPRIORITY_CS1, attributes.priority) + + def test_notification(self): + sink = build_sink() + builder = UAttributesBuilder.notification(UPriority.UPRIORITY_CS1, sink) + self.assertIsNotNone(builder) + attributes = builder.build() + self.assertIsNotNone(attributes) + self.assertEqual(UMessageType.UMESSAGE_TYPE_PUBLISH, attributes.type) + self.assertEqual(UPriority.UPRIORITY_CS1, attributes.priority) + self.assertEqual(sink, attributes.sink) + + def test_request(self): + sink = build_sink() + ttl = 1000 + builder = UAttributesBuilder.request(UPriority.UPRIORITY_CS4, sink, ttl) + self.assertIsNotNone(builder) + attributes = builder.build() + self.assertIsNotNone(attributes) + self.assertEqual(UMessageType.UMESSAGE_TYPE_REQUEST, attributes.type) + self.assertEqual(UPriority.UPRIORITY_CS4, attributes.priority) + self.assertEqual(sink, attributes.sink) + self.assertEqual(ttl, attributes.ttl) + + def test_response(self): + sink = build_sink() + req_id = get_uuid() + builder = UAttributesBuilder.response(UPriority.UPRIORITY_CS6, sink, req_id) + self.assertIsNotNone(builder) + attributes = builder.build() + self.assertIsNotNone(attributes) + self.assertEqual(UMessageType.UMESSAGE_TYPE_RESPONSE, attributes.type) + self.assertEqual(UPriority.UPRIORITY_CS6, attributes.priority) + self.assertEqual(sink, attributes.sink) + self.assertEqual(req_id, attributes.reqid) + + def test_build(self): + req_id = get_uuid() + builder = UAttributesBuilder.publish(UPriority.UPRIORITY_CS1).withTtl(1000).withToken("test_token").withSink( + build_sink()).withPermissionLevel(2).withCommStatus(1).withReqId(req_id) + attributes = builder.build() + self.assertIsNotNone(attributes) + self.assertEqual(UMessageType.UMESSAGE_TYPE_PUBLISH, attributes.type) + self.assertEqual(UPriority.UPRIORITY_CS1, attributes.priority) + self.assertEqual(1000, attributes.ttl) + self.assertEqual("test_token", attributes.token) + self.assertEqual(build_sink(), attributes.sink) + self.assertEqual(2, attributes.permission_level) + self.assertEqual(1, attributes.commstatus) + self.assertEqual(req_id, attributes.reqid) + + +if __name__ == '__main__': + unittest.main() diff --git a/org_eclipse_uprotocol_tests/transport/validate/uattributesvalidatortest.py b/org_eclipse_uprotocol_tests/transport/validate/uattributesvalidatortest.py new file mode 100644 index 0000000..19504b0 --- /dev/null +++ b/org_eclipse_uprotocol_tests/transport/validate/uattributesvalidatortest.py @@ -0,0 +1,471 @@ +# ------------------------------------------------------------------------- +# +# 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 time +import unittest + +from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority +from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UAuthority, UEntity +from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode +from org_eclipse_uprotocol.proto.uuid_pb2 import UUID +from org_eclipse_uprotocol.transport.builder.uattributesbuilder import UAttributesBuilder +from org_eclipse_uprotocol.transport.validate.uattributesvalidator import UAttributesValidator, Validators +from org_eclipse_uprotocol.uri.builder.uresource_builder import UResourceBuilder +from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from org_eclipse_uprotocol.uuid.factory.uuidfactory import Factories +from org_eclipse_uprotocol.validation.validationresult import ValidationResult + + +def build_sink(): + 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_response()) + + +class UAttributesValidatorTest(unittest.TestCase): + + def test_fetching_validator_for_valid_types(self): + publish = UAttributesValidator.get_validator(UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).build()) + self.assertEqual("UAttributesValidator.Publish", str(publish)) + + request = UAttributesValidator.get_validator( + UAttributesBuilder.request(UPriority.UPRIORITY_CS4, UUri(), 1000).build()) + self.assertEqual("UAttributesValidator.Request", str(request)) + + response = UAttributesValidator.get_validator( + UAttributesBuilder.response(UPriority.UPRIORITY_CS4, UUri(), 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() + 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( + build_sink()).withPermissionLevel(2).withCommStatus(3).withReqId(Factories.UPROTOCOL.create()).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_invalid_type(self): + attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS0, build_sink(), + Factories.UPROTOCOL.create()).build() + validator = Validators.PUBLISH.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + 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() + + validator = Validators.PUBLISH.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + 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() + 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() + 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() + validator = Validators.PUBLISH.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + self.assertEqual("Invalid Communication Status Code", status.get_message()) + + # def test_validate_uAttributes_for_publish_message_payload_invalid_request_id(self): + # uuid = java.util.UUID.randomUUID() + # attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withReqId( + # UUID.newBuilder().setMsb(uuid_java.getMostSignificantBits()).setLsb(uuid_java.getLeastSignificantBits()) + # .build()).build() + # + # validator = Validators.PUBLISH.validator() + # status = validator.validate(attributes) + # self.assertTrue(status.is_failure()) + # 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() + 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( + 2).withCommStatus(3).withReqId(Factories.UPROTOCOL.create()).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_invalid_type(self): + attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS4, build_sink(), + Factories.UPROTOCOL.create()).withTtl(1000).build() + + validator = Validators.REQUEST.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + 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() + + validator = Validators.REQUEST.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + 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() + + validator = Validators.REQUEST.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + 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( + -42).build() + + validator = Validators.REQUEST.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + 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() + + validator = Validators.REQUEST.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + self.assertEqual("Invalid Communication Status Code", status.get_message()) + + # def test_validate_uAttributes_for_rpc_request_message_payload_invalid_request_id(self): + # uuid_java = java.util.UUID.randomUUID() + # attributes = UAttributesBuilder.request(UPriority.UPRIORITY_CS4, build_sink(), 1000).withReqId( + # UUID.newBuilder().setMsb(uuid_java.getMostSignificantBits()).setLsb(uuid_java.getLeastSignificantBits()) + # .build()).build() + # + # validator = Validators.REQUEST.validator() + # status = validator.validate(attributes) + # self.assertTrue(status.is_failure()) + # 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(), + Factories.UPROTOCOL.create()).build() + + validator = Validators.RESPONSE.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_success()) + self.assertEqual("", status.get_message()) + + def test_validate_uAttributes_for_rpc_response_message_payload_alls(self): + attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS4, build_sink(), + Factories.UPROTOCOL.create()).withPermissionLevel(2).withCommStatus( + 3).build() + + validator = Validators.RESPONSE.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_success()) + 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() + + validator = Validators.RESPONSE.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + 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(), + Factories.UPROTOCOL.create()).withTtl(-1).build() + + validator = Validators.RESPONSE.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + 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() + + validator = Validators.RESPONSE.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + 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(), + Factories.UPROTOCOL.create()).withPermissionLevel(-42).build() + + validator = Validators.RESPONSE.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + 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(), + Factories.UPROTOCOL.create()).withCommStatus(-42).build() + + validator = Validators.RESPONSE.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + 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() + + validator = Validators.RESPONSE.validator() + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + self.assertEqual("Missing correlationId", status.get_message()) + + # def test_validate_uAttributes_for_rpc_response_message_payload_invalid_request_id(self): + # uuid_java = java.util.UUID.randomUUID() + # reqid = UUID.newBuilder().setMsb(uuid_java.getMostSignificantBits()).setLsb( + # uuid_java.getLeastSignificantBits()).build() + # attributes = UAttributesBuilder.response(UPriority.UPRIORITY_CS4, build_sink(), reqid).build() + # + # validator = Validators.RESPONSE.validator() + # status = validator.validate(attributes) + # self.assertTrue(status.is_failure()) + # self.assertEqual(f"Invalid correlationId [{reqid}]", status.get_message()) + + # ---- + def test_validate_uAttributes_for_publish_message_payload_not_expired(self): + attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).build() + + validator = Validators.PUBLISH.validator() + status = validator.is_expired(attributes) + self.assertTrue(status.is_success()) + self.assertEqual("", status.get_message()) + + def test_validate_uAttributes_for_publish_message_payload_not_expired_withTtl_zero(self): + attributes = UAttributesBuilder.publish(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()) + + def test_validate_uAttributes_for_publish_message_payload_not_expired_withTtl(self): + attributes = UAttributesBuilder.publish(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()) + + def test_validate_uAttributes_for_publish_message_payload_expired_withTtl(self): + attributes = UAttributesBuilder.publish(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()) + + # ---- + + def test_validating_publish_invalid_ttl_attribute(self): + attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withTtl(-1).build() + + validator = Validators.PUBLISH.validator() + status = validator.validate_ttl(attributes) + self.assertTrue(status.is_failure()) + self.assertEqual("Invalid TTL [-1]", status.get_message()) + + def test_validating_valid_ttl_attribute(self): + attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withTtl(100).build() + + validator = Validators.PUBLISH.validator() + status = validator.validate_ttl(attributes) + self.assertEqual(ValidationResult.success(), status) + + def test_validating_invalid_sink_attribute(self): + uri = LongUriSerializer().deserialize("//") + attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withSink(uri).build() + validator = Validators.PUBLISH.validator() + status = validator.validate_sink(attributes) + self.assertTrue(status.is_failure()) + self.assertEqual("Uri is empty.", status.get_message()) + + 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() + + validator = Validators.PUBLISH.validator() + status = validator.validate_sink(attributes) + self.assertEqual(ValidationResult.success(), status) + + # def test_validating_invalid_ReqId_attribute(self): + # uuid_java = java.util.UUID.randomUUID() + # + # attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).with_req_id( + # UUID.newBuilder().setMsb(uuid_java.getMostSignificantBits()).setLsb(uuid_java.getLeastSignificantBits()) + # .build()).build() + # + # validator = Validators.PUBLISH.validator() + # status = validator.validate_req_id(attributes) + # self.assertTrue(status.is_failure()) + # self.assertEqual("Invalid UUID", status.get_message()) + + def test_validating_valid_ReqId_attribute(self): + attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withReqId( + Factories.UPROTOCOL.create()).build() + + validator = Validators.PUBLISH.validator() + status = validator.validate_req_id(attributes) + self.assertEqual(ValidationResult.success(), status) + + def test_validating_invalid_PermissionLevel_attribute(self): + attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withPermissionLevel(-1).build() + + validator = Validators.PUBLISH.validator() + status = validator.validate_permission_level(attributes) + self.assertTrue(status.is_failure()) + self.assertEqual("Invalid Permission Level", status.get_message()) + + def test_validating_valid_PermissionLevel_attribute(self): + attributes = UAttributesBuilder.publish(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() + + validator = Validators.PUBLISH.validator() + status = validator.validate_permission_level(attributes) + self.assertTrue(status.is_failure()) + self.assertEqual("Invalid Permission Level", status.get_message()) + + def test_validating_invalid_commstatus_attribute(self): + attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withCommStatus(100).build() + + validator = Validators.PUBLISH.validator() + status = validator.validate_comm_status(attributes) + self.assertTrue(status.is_failure()) + 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() + + 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() + + validator = UAttributesValidator.get_validator(attributes) + self.assertEqual("UAttributesValidator.Request", str(validator)) + status = validator.validate(attributes) + self.assertTrue(status.is_success()) + self.assertEqual("", status.get_message()) + + def test_validating_request_validator_with_wrong_messagetype(self): + attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS6).build() + + validator = Validators.REQUEST.validator() + self.assertEqual("UAttributesValidator.Request", str(validator)) + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + 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() + + validator = Validators.REQUEST.validator() + self.assertEqual("UAttributesValidator.Request", str(validator)) + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + 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() + + validator = Validators.RESPONSE.validator() + self.assertEqual("UAttributesValidator.Response", str(validator)) + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + 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() + validator = Validators.PUBLISH.validator() + self.assertEqual("UAttributesValidator.Publish", str(validator)) + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + 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() + + validator = Validators.RESPONSE.validator() + self.assertEqual("UAttributesValidator.Response", str(validator)) + status = validator.validate(attributes) + self.assertTrue(status.is_failure()) + self.assertEqual("Wrong Attribute Type [UMESSAGE_TYPE_PUBLISH],Missing Sink,Missing correlationId", + status.get_message()) + + def test_validating_request_containing_token(self): + attributes = UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).withToken("null").build() + + validator = UAttributesValidator.get_validator(attributes) + self.assertEqual("UAttributesValidator.Publish", str(validator)) + status = validator.validate(attributes) + self.assertEqual(ValidationResult.success(), status) + + +if __name__ == '__main__': + unittest.main() From c1d6adb4c8dfd17a51dfad46d5f499a6f3041b7c Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Fri, 24 Nov 2023 00:03:53 -0500 Subject: [PATCH 33/47] Add test cases for the Calloption class --- org_eclipse_uprotocol/rpc/calloptions.py | 16 ++-- .../rpc/calloptionstest.py | 81 +++++++++++++++++++ 2 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 org_eclipse_uprotocol_tests/rpc/calloptionstest.py diff --git a/org_eclipse_uprotocol/rpc/calloptions.py b/org_eclipse_uprotocol/rpc/calloptions.py index 11e57f1..954335c 100644 --- a/org_eclipse_uprotocol/rpc/calloptions.py +++ b/org_eclipse_uprotocol/rpc/calloptions.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------- - +# # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -21,7 +21,7 @@ # SPDX-FileType: SOURCE # SPDX-FileCopyrightText: 2023 General Motors GTO LLC # SPDX-License-Identifier: Apache-2.0 - +# # ------------------------------------------------------------------------- @@ -34,24 +34,20 @@ class CallOptions: Default timeout of a call in milliseconds. """ + def __init__(self, timeout=TIMEOUT_DEFAULT, token=""): self.mTimeout = timeout self.mToken = token if token else "" - @classmethod - def default(cls): - return cls() - @property - def timeout(self): + def get_timeout(self): """ Get a timeout.

@return: A timeout in milliseconds. """ return self.mTimeout - @property - def token(self): + def get_token(self): """ Get an OAuth2 access token.

@return: An Optional OAuth2 access token. @@ -75,7 +71,7 @@ class CallOptionsBuilder: Builder for constructing CallOptions. """ TIMEOUT_DEFAULT = 10000 - + DEFAULT= CallOptions(TIMEOUT_DEFAULT,'') def __init__(self): self.mTimeout = self.TIMEOUT_DEFAULT self.mToken = "" diff --git a/org_eclipse_uprotocol_tests/rpc/calloptionstest.py b/org_eclipse_uprotocol_tests/rpc/calloptionstest.py new file mode 100644 index 0000000..1c55818 --- /dev/null +++ b/org_eclipse_uprotocol_tests/rpc/calloptionstest.py @@ -0,0 +1,81 @@ +# ------------------------------------------------------------------------- +# +# 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 unittest + +from org_eclipse_uprotocol.rpc.calloptions import CallOptions, CallOptionsBuilder + + +class CallOptionsTest(unittest.TestCase): + + def test_hash_code_equals(self): + call_options = CallOptionsBuilder().build() + self.assertEqual(hash(call_options), hash(call_options)) + + def test_to_string(self): + call_options = CallOptionsBuilder().with_timeout(30).with_token("someToken").build() + expected = "CallOptions{mTimeout=30, mToken='someToken'}" + self.assertEqual(expected, str(call_options)) + + def test_creating_call_options_default(self): + call_options = CallOptionsBuilder.DEFAULT + self.assertEqual(CallOptions.TIMEOUT_DEFAULT, call_options.get_timeout()) + self.assertTrue(call_options.get_token() is None or call_options.get_token() == "") + + def test_creating_call_options_with_a_token(self): + call_options = CallOptionsBuilder().with_token("someToken").build() + self.assertEqual(CallOptions.TIMEOUT_DEFAULT, call_options.get_timeout()) + self.assertTrue(call_options.get_token() == "someToken") + + def test_creating_call_options_with_a_null_token(self): + call_options = CallOptionsBuilder().with_token(None).build() + self.assertEqual(CallOptions.TIMEOUT_DEFAULT, call_options.get_timeout()) + self.assertTrue(call_options.get_token() is None or call_options.get_token() == "") + + def test_creating_call_options_with_an_empty_string_token(self): + call_options = CallOptionsBuilder().with_token("").build() + self.assertEqual(CallOptions.TIMEOUT_DEFAULT, call_options.get_timeout()) + self.assertTrue(call_options.get_token() is None or call_options.get_token() == "") + + def test_creating_call_options_with_a_token_with_only_spaces(self): + call_options = CallOptionsBuilder().with_token(" ").build() + self.assertEqual(CallOptions.TIMEOUT_DEFAULT, call_options.get_timeout()) + self.assertTrue(call_options.get_token() is None or call_options.get_token().isspace()) + + def test_creating_call_options_with_a_timeout(self): + call_options = CallOptionsBuilder().with_timeout(30).build() + self.assertEqual(30, call_options.get_timeout()) + self.assertTrue(call_options.get_token() is None or call_options.get_token() == "") + + def test_creating_call_options_with_a_negative_timeout(self): + call_options = CallOptionsBuilder().with_timeout(-3).build() + self.assertEqual(CallOptions.TIMEOUT_DEFAULT, call_options.get_timeout()) + self.assertTrue(call_options.get_token() is None or call_options.get_token() == "") + + +if __name__ == '__main__': + unittest.main() From 50b727fa0f2dec3bbbd007b1d2577001f9b8c74e Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Fri, 24 Nov 2023 11:12:59 -0500 Subject: [PATCH 34/47] Add test cases for RpcResult class and fix bugs discovered while testing --- org_eclipse_uprotocol/rpc/rpcresult.py | 21 +- .../rpc/rpcresulttest.py | 209 ++++++++++++++++++ 2 files changed, 223 insertions(+), 7 deletions(-) create mode 100644 org_eclipse_uprotocol_tests/rpc/rpcresulttest.py diff --git a/org_eclipse_uprotocol/rpc/rpcresult.py b/org_eclipse_uprotocol/rpc/rpcresult.py index bb4676b..9200cab 100644 --- a/org_eclipse_uprotocol/rpc/rpcresult.py +++ b/org_eclipse_uprotocol/rpc/rpcresult.py @@ -49,7 +49,7 @@ def isFailure(self) -> bool: pass @abstractmethod - def getOrElse(self, default_value: T) -> T: + def getOrElse(self, default_value: Callable[[], T]) -> T: pass @abstractmethod @@ -72,13 +72,16 @@ def failureValue(self) -> UStatus: def successValue(self) -> T: pass + @staticmethod def success(value: T) -> 'RpcResult': return Success(value) - def failure(value: Union[UStatus, Exception, None] = None, code: UCode = UCode.UNKNOWN, + @staticmethod + def failure(value: Union[UStatus,'Failure', Exception,] = None, code: UCode = UCode.UNKNOWN, message: str = '') -> 'RpcResult': return Failure(value, code, message) + @staticmethod def flatten(result: 'RpcResult') -> 'RpcResult': return result.flatMap(lambda x: x) @@ -94,12 +97,12 @@ def isSuccess(self) -> bool: def isFailure(self) -> bool: return False - def getOrElse(self, default_value: T) -> T: + def getOrElse(self, default_value: Callable[[], T]) -> T: return self.successValue() def map(self, f: Callable[[T], T]) -> RpcResult: try: - return self.success() + return self.success(f(self.successValue())) except Exception as e: return self.failure(e) @@ -111,7 +114,7 @@ def flatMap(self, f: Callable[[T], RpcResult]) -> RpcResult: def filter(self, f: Callable[[T], bool]) -> RpcResult: try: - return self if f(self.successValue()) else self.failure(UCode.FAILED_PRECONDITION, "filtered out") + return self if f(self.successValue()) else self.failure(code=UCode.FAILED_PRECONDITION, message="filtered out") except Exception as e: return self.failure(e) @@ -127,11 +130,13 @@ def __str__(self) -> str: class Failure(RpcResult): - def __init__(self, value: Union[UStatus, Exception, None] = None, code: UCode = UCode.UNKNOWN, message: str = ''): + def __init__(self, value: Union[UStatus,'Failure', Exception, None] = None, code: UCode = UCode.UNKNOWN, message: str = ''): if isinstance(value, UStatus): self.value = value elif isinstance(value, Exception): self.value = UStatus(code=code, message=str(value)) + elif isinstance(value, Failure): + self.value = value.failureValue() else: self.value = UStatus(code=code, message=message) @@ -141,7 +146,9 @@ def isSuccess(self) -> bool: def isFailure(self) -> bool: return True - def getOrElse(self, default_value: T) -> T: + def getOrElse(self, default_value: Callable[[], T]) -> T: + if callable(default_value): + return default_value() return default_value def map(self, f: Callable[[T], T]) -> RpcResult: diff --git a/org_eclipse_uprotocol_tests/rpc/rpcresulttest.py b/org_eclipse_uprotocol_tests/rpc/rpcresulttest.py new file mode 100644 index 0000000..bfe68ee --- /dev/null +++ b/org_eclipse_uprotocol_tests/rpc/rpcresulttest.py @@ -0,0 +1,209 @@ +# ------------------------------------------------------------------------- +# +# 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 unittest + +from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode, UStatus +from org_eclipse_uprotocol.rpc.rpcresult import RpcResult + + +def getDefault(): + return 5 + + +def multiply_by_2(x): + return RpcResult.success(x * 2) + + +class RpcResultTest(unittest.TestCase): + + def test_isSuccess_on_Success(self): + result = RpcResult.success(2) + self.assertTrue(result.isSuccess()) + + def test_isSuccess_on_Failure(self): + result = RpcResult.failure(code=UCode.INVALID_ARGUMENT, message="boom") + self.assertFalse(result.isSuccess()) + + def test_isFailure_on_Success(self): + result = RpcResult.success(2) + self.assertFalse(result.isFailure()) + + def test_isFailure_on_Failure(self): + result = RpcResult.failure(code=UCode.INVALID_ARGUMENT, message="boom") + self.assertTrue(result.isFailure()) + + def testGetOrElseOnSuccess(self): + result = RpcResult.success(2) + self.assertEqual(2, result.getOrElse(getDefault())) + + def testGetOrElseOnFailure(self): + result = RpcResult.failure(code=UCode.INVALID_ARGUMENT, message="boom") + self.assertEqual(getDefault(), result.getOrElse(getDefault)) + + def testGetOrElseOnSuccess_(self): + result = RpcResult.success(2) + self.assertEqual(2, result.getOrElse(5)) + + def testGetOrElseOnFailure_(self): + result = RpcResult.failure(code=UCode.INVALID_ARGUMENT, message="boom") + self.assertEqual(5, result.getOrElse(5)) + + def testSuccessValue_onSuccess(self): + result = RpcResult.success(2) + self.assertEqual(2, result.successValue()) + + def testSuccessValue_onFailure_(self): + result = RpcResult.failure(code=UCode.INVALID_ARGUMENT, message="boom") + with self.assertRaises(Exception) as context: + result.successValue() + self.assertEqual(str(context.exception), "Method successValue() called on a Failure instance") + + def testFailureValue_onSuccess(self): + result = RpcResult.success(2) + with self.assertRaises(Exception) as context: + result.failureValue() + self.assertEqual(str(context.exception), "Method failureValue() called on a Success instance") + + def testFailureValue_onFailure_(self): + result = RpcResult.failure(code=UCode.INVALID_ARGUMENT, message="boom") + result_value = result.failureValue() + expected_result = UStatus(code=UCode.INVALID_ARGUMENT, message="boom") + self.assertEqual(expected_result, result_value) + + def testMapOnSuccess(self): + result = RpcResult.success(2) + mapped = result.map(lambda x: x * 2) + self.assertTrue(mapped.isSuccess()) + self.assertEqual(4, mapped.successValue()) + + def test_map_success_when_function_throws_exception(self): + result = RpcResult.success(2) + mapped = result.map(self.fun_that_throws_exception_for_map) + self.assertTrue(mapped.isFailure()) + self.assertEqual(UCode.UNKNOWN, mapped.failureValue().code) + self.assertEqual("2 went boom", mapped.failureValue().message) + + def test_map_on_failure(self): + result = RpcResult.failure(code=UCode.INVALID_ARGUMENT, message="boom") + mapped = result.map(lambda x: x * 2) + self.assertTrue(mapped.isFailure()) + expected_status = UStatus(code=UCode.INVALID_ARGUMENT, message="boom") + self.assertEqual(expected_status, mapped.failureValue()) + + def test_flat_map_success_when_function_throws_exception(self): + result = RpcResult.success(2) + flat_mapped = result.flatMap(self.fun_that_throws_exception_for_flat_map) + self.assertTrue(flat_mapped.isFailure()) + self.assertEqual(UCode.UNKNOWN, flat_mapped.failureValue().code) + self.assertEqual("2 went boom", flat_mapped.failureValue().message) + + def fun_that_throws_exception_for_flat_map(self, x): + raise ValueError(f"{x} went boom") + + def fun_that_throws_exception_for_map(self, x): + raise ValueError(f"{x} went boom") + + def test_flat_map_on_success(self): + result = RpcResult.success(2) + flat_mapped = result.flatMap(lambda x: RpcResult.success(x * 2)) + self.assertTrue(flat_mapped.isSuccess()) + self.assertEqual(4, flat_mapped.successValue()) + + def test_flat_map_on_failure(self): + result = RpcResult.failure(code=UCode.INVALID_ARGUMENT, message="boom") + flat_mapped = result.flatMap(lambda x: RpcResult.success(x * 2)) + self.assertTrue(flat_mapped.isFailure()) + expected_status = UStatus(code=UCode.INVALID_ARGUMENT, message="boom") + self.assertEqual(expected_status, flat_mapped.failureValue()) + + def test_filter_on_success_that_fails(self): + result = RpcResult.success(2) + filter_result = result.filter(lambda i: i > 5) + self.assertTrue(filter_result.isFailure()) + expected_status = UStatus(code=UCode.FAILED_PRECONDITION, message="filtered out") + self.assertEqual(expected_status, filter_result.failureValue()) + + def test_filter_on_success_that_succeeds(self): + result = RpcResult.success(2) + filter_result = result.filter(lambda i: i < 5) + self.assertTrue(filter_result.isSuccess()) + self.assertEqual(2, filter_result.successValue()) + + def test_filter_on_success_when_function_throws_exception(self): + result = RpcResult.success(2) + filter_result = result.filter(self.predicate_that_throws_exception) + self.assertTrue(filter_result.isFailure()) + expected_status = UStatus(code=UCode.UNKNOWN, message="2 went boom") + self.assertEqual(expected_status, filter_result.failureValue()) + + def predicate_that_throws_exception(self, x): + raise ValueError(f"{x} went boom") + + def test_filter_on_failure(self): + result = RpcResult.failure(code=UCode.INVALID_ARGUMENT, message="boom") + filter_result = result.filter(lambda i: i > 5) + self.assertTrue(filter_result.isFailure()) + expected_status = UStatus(code=UCode.INVALID_ARGUMENT, message="boom") + self.assertEqual(expected_status, filter_result.failureValue()) + + def test_flatten_on_success(self): + result = RpcResult.success(2) + mapped = result.map(multiply_by_2) + mapped_flattened = RpcResult.flatten(mapped) + self.assertTrue(mapped_flattened.isSuccess()) + self.assertEqual(4, mapped_flattened.successValue()) + + def test_flatten_on_success_with_function_that_fails(self): + result = RpcResult.success(2) + mapped = result.map(self.fun_that_throws_exception_for_flat_map) + mapped_flattened = RpcResult.flatten(mapped) + self.assertTrue(mapped_flattened.isFailure()) + expected_status = UStatus(code=UCode.UNKNOWN, message="2 went boom") + self.assertEqual(expected_status, mapped_flattened.failureValue()) + + def test_flatten_on_failure(self): + result = RpcResult.failure(code=UCode.INVALID_ARGUMENT, message="boom") + mapped = result.map(multiply_by_2) + mapped_flattened = RpcResult.flatten(mapped) + self.assertTrue(mapped_flattened.isFailure()) + expected_status = UStatus(code=UCode.INVALID_ARGUMENT, message="boom") + self.assertEqual(expected_status, mapped_flattened.failureValue()) + + def test_to_string_success(self): + result = RpcResult.success(2) + self.assertEqual("Success(2)", str(result)) + + def test_to_string_failure(self): + result = RpcResult.failure(code=UCode.INVALID_ARGUMENT, message="boom") + expected_string = ("Failure(code: INVALID_ARGUMENT\n" + "message: \"boom\"\n)") + self.assertEqual(expected_string, str(result)) + + +if __name__ == '__main__': + unittest.main() From 2e34cd7cc8de1a0968b6de0713e2b1504b68d5e4 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Fri, 24 Nov 2023 13:48:20 -0500 Subject: [PATCH 35/47] Rename test classes according to the Python standard. --- org_eclipse_uprotocol/rpc/rpcmapper.py | 6 +- org_eclipse_uprotocol/rpc/rpcresult.py | 1 + .../test_ucloudeventattributes.py} | 2 +- .../test_factory}/cloudevent.json | 0 .../test_factory/test_cloudeventfactory.py} | 2 +- .../test_calloptions.py} | 2 +- .../test_rpc/test_rpc.py | 191 ++++++++++++++++++ .../test_rpcresult.py} | 2 +- .../test_builder/test_uattributesbuilder.py} | 2 +- .../test_uattributesvalidator.py} | 2 +- .../test_longuriserializer.py} | 2 +- .../test_microuriserializer.py} | 2 +- .../test_serializer/test_uriserializer.py} | 2 +- .../test_validator/test_urivalidator.py} | 0 .../test_validator}/uris.json | 0 .../test_factory/test_uuidfactory.py} | 0 .../test_validator/test_uuidvalidator.py} | 0 org_eclipse_uprotocol_tests/uri/__init__.py | 0 18 files changed, 205 insertions(+), 11 deletions(-) rename org_eclipse_uprotocol_tests/{cloudevent/datamodel/ucloudeventattributestest.py => test_cloudevent/test_datamodel/test_ucloudeventattributes.py} (98%) rename org_eclipse_uprotocol_tests/{cloudevent/factory => test_cloudevent/test_factory}/cloudevent.json (100%) rename org_eclipse_uprotocol_tests/{cloudevent/factory/cloudeventfactorytest.py => test_cloudevent/test_factory/test_cloudeventfactory.py} (99%) rename org_eclipse_uprotocol_tests/{rpc/calloptionstest.py => test_rpc/test_calloptions.py} (98%) create mode 100644 org_eclipse_uprotocol_tests/test_rpc/test_rpc.py rename org_eclipse_uprotocol_tests/{rpc/rpcresulttest.py => test_rpc/test_rpcresult.py} (99%) rename org_eclipse_uprotocol_tests/{transport/builder/uattributesbuildertest.py => test_transport/test_builder/test_uattributesbuilder.py} (98%) rename org_eclipse_uprotocol_tests/{transport/validate/uattributesvalidatortest.py => test_transport/test_validate/test_uattributesvalidator.py} (99%) rename org_eclipse_uprotocol_tests/{uri/serializer/longuriserializertest.py => test_uri/test_serializer/test_longuriserializer.py} (99%) rename org_eclipse_uprotocol_tests/{uri/serializer/microuriserializertest.py => test_uri/test_serializer/test_microuriserializer.py} (99%) rename org_eclipse_uprotocol_tests/{uri/serializer/uriserializertest.py => test_uri/test_serializer/test_uriserializer.py} (98%) rename org_eclipse_uprotocol_tests/{uri/validator/urivalidatortest.py => test_uri/test_validator/test_urivalidator.py} (100%) rename org_eclipse_uprotocol_tests/{uri/validator => test_uri/test_validator}/uris.json (100%) rename org_eclipse_uprotocol_tests/{uuid/factory/uuidfactorytest.py => test_uuid/test_factory/test_uuidfactory.py} (100%) rename org_eclipse_uprotocol_tests/{uuid/validator/uuidvalidatortest.py => test_uuid/test_validator/test_uuidvalidator.py} (100%) delete mode 100644 org_eclipse_uprotocol_tests/uri/__init__.py diff --git a/org_eclipse_uprotocol/rpc/rpcmapper.py b/org_eclipse_uprotocol/rpc/rpcmapper.py index 953077b..2889652 100644 --- a/org_eclipse_uprotocol/rpc/rpcmapper.py +++ b/org_eclipse_uprotocol/rpc/rpcmapper.py @@ -139,7 +139,9 @@ def unpack_payload(payload, expected_cls): object. """ try: - payload.Unpack(expected_cls) - return expected_cls + value=expected_cls() + value.ParseFromString(payload.value) + # payload.Unpack(expected_cls) + return value except Exception as e: raise RuntimeError(f"{str(e)} [{UStatus.__name__}]") from e diff --git a/org_eclipse_uprotocol/rpc/rpcresult.py b/org_eclipse_uprotocol/rpc/rpcresult.py index 9200cab..f620c73 100644 --- a/org_eclipse_uprotocol/rpc/rpcresult.py +++ b/org_eclipse_uprotocol/rpc/rpcresult.py @@ -102,6 +102,7 @@ def getOrElse(self, default_value: Callable[[], T]) -> T: def map(self, f: Callable[[T], T]) -> RpcResult: try: + return self.success(f(self.successValue())) except Exception as e: return self.failure(e) diff --git a/org_eclipse_uprotocol_tests/cloudevent/datamodel/ucloudeventattributestest.py b/org_eclipse_uprotocol_tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py similarity index 98% rename from org_eclipse_uprotocol_tests/cloudevent/datamodel/ucloudeventattributestest.py rename to org_eclipse_uprotocol_tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py index 967229e..652b52a 100644 --- a/org_eclipse_uprotocol_tests/cloudevent/datamodel/ucloudeventattributestest.py +++ b/org_eclipse_uprotocol_tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py @@ -32,7 +32,7 @@ from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority -class UCloudEventAttributesTest(unittest.TestCase): +class TestUCloudEventAttributes(unittest.TestCase): def test_to_string(self): diff --git a/org_eclipse_uprotocol_tests/cloudevent/factory/cloudevent.json b/org_eclipse_uprotocol_tests/test_cloudevent/test_factory/cloudevent.json similarity index 100% rename from org_eclipse_uprotocol_tests/cloudevent/factory/cloudevent.json rename to org_eclipse_uprotocol_tests/test_cloudevent/test_factory/cloudevent.json diff --git a/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py b/org_eclipse_uprotocol_tests/test_cloudevent/test_factory/test_cloudeventfactory.py similarity index 99% rename from org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py rename to org_eclipse_uprotocol_tests/test_cloudevent/test_factory/test_cloudeventfactory.py index 3abbfa1..ec0f83c 100644 --- a/org_eclipse_uprotocol_tests/cloudevent/factory/cloudeventfactorytest.py +++ b/org_eclipse_uprotocol_tests/test_cloudevent/test_factory/test_cloudeventfactory.py @@ -68,7 +68,7 @@ def build_proto_payload_for_test(): return any_obj -class CloudEventFactoryTest(unittest.TestCase): +class TestCloudEventFactory(unittest.TestCase): DATA_CONTENT_TYPE = CloudEventFactory.PROTOBUF_CONTENT_TYPE def test_all_cloud_events_from_json(self): diff --git a/org_eclipse_uprotocol_tests/rpc/calloptionstest.py b/org_eclipse_uprotocol_tests/test_rpc/test_calloptions.py similarity index 98% rename from org_eclipse_uprotocol_tests/rpc/calloptionstest.py rename to org_eclipse_uprotocol_tests/test_rpc/test_calloptions.py index 1c55818..09a061d 100644 --- a/org_eclipse_uprotocol_tests/rpc/calloptionstest.py +++ b/org_eclipse_uprotocol_tests/test_rpc/test_calloptions.py @@ -30,7 +30,7 @@ from org_eclipse_uprotocol.rpc.calloptions import CallOptions, CallOptionsBuilder -class CallOptionsTest(unittest.TestCase): +class TestCallOptions(unittest.TestCase): def test_hash_code_equals(self): call_options = CallOptionsBuilder().build() diff --git a/org_eclipse_uprotocol_tests/test_rpc/test_rpc.py b/org_eclipse_uprotocol_tests/test_rpc/test_rpc.py new file mode 100644 index 0000000..ab43a1d --- /dev/null +++ b/org_eclipse_uprotocol_tests/test_rpc/test_rpc.py @@ -0,0 +1,191 @@ +# ------------------------------------------------------------------------- + +# 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 unittest +from concurrent.futures import Future +from google.protobuf.any_pb2 import Any +from google.protobuf.wrappers_pb2 import Int32Value +from org_eclipse_uprotocol.proto.cloudevents_pb2 import CloudEvent +from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority +from org_eclipse_uprotocol.proto.upayload_pb2 import UPayload, UPayloadFormat +from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UEntity +from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus, UCode +from org_eclipse_uprotocol.rpc.rpcclient import RpcClient +from org_eclipse_uprotocol.rpc.rpcmapper import RpcMapper +from org_eclipse_uprotocol.rpc.rpcresult import RpcResult +from org_eclipse_uprotocol.transport.builder.uattributesbuilder import UAttributesBuilder +from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer + + +def build_cloud_event(): + return CloudEvent(spec_version="1.0", source="https://example.com", id="HARTLEY IS THE BEST") + + +def build_upayload(): + any_obj = Any() + any_obj.Pack(build_cloud_event()) + return UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, value=any_obj.SerializeToString()) + + +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() + + +class ReturnsNumber3(RpcClient): + def invoke_method(self, topic, payload, attributes): + 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) + return future + + +class HappyPath(RpcClient): + def invoke_method(self, topic, payload, attributes): + future = Future() + data = build_upayload() + future.set_result(data) + return future + + +class WithUStatusCodeInsteadOfHappyPath(RpcClient): + def invoke_method(self, topic, payload, attributes): + 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) + return future + + +class WithUStatusCodeHappyPath(RpcClient): + def invoke_method(self, topic, payload, attributes): + 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) + return future + + +class ThatBarfsCrapyPayload(RpcClient): + def invoke_method(self, topic, payload, attributes): + future = Future() + response = UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_RAW, value=bytes([0])) + future.set_result(response) + return future + + +class ThatCompletesWithAnException(RpcClient): + def invoke_method(self, topic, payload, attributes): + future = Future() + future.set_exception(RuntimeError("Boom")) + return future + + +class ThatReturnsTheWrongProto(RpcClient): + def invoke_method(self, topic, payload, attributes): + 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) + return future + + +class WithNullInPayload(RpcClient): + def invoke_method(self, topic, payload, attributes): + future = Future() + future.set_result(None) + return future + + +def rpc_response(invoke_method_response: Future): + payload, exception = invoke_method_response.result() + + any_value = Any() + try: + any_value.ParseFromString(payload.value) + except Exception as e: + raise RuntimeError(str(e)) from e + + # invoke method had some unexpected problem. + if exception is not None: + raise RuntimeError(str(exception)) from exception + + # test to see if we have the expected type + if any_value.Is(CloudEvent.DESCRIPTOR): + try: + cloud_event = CloudEvent() + any_value.Unpack(cloud_event) + return cloud_event + except Exception as e: + raise RuntimeError(str(e)) from e + + # this will be called only if the expected return type is not status, but status was returned to + # indicate a problem. + if any_value.Is(UStatus.DESCRIPTOR): + try: + status = UStatus() + any_value.Unpack(status) + raise RuntimeError(f"Error returned, status code: [{status.code}], message: [{status.message}]") + except Exception as e: + raise RuntimeError(f"{str(e)} [com.google.grpc.UStatus]") from e + + raise RuntimeError(f"Unknown payload type [{any_value.type_url}]") + + +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) + mapped = rpc_response.map(lambda x: x.value + 5) + self.assertTrue(rpc_response.isSuccess()) + self.assertEquals(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()), Int32Value) + mapped = rpc_response.map(lambda x: x.value + 5) + self.assertTrue(rpc_response.isFailure()) + self.assertEquals(UCode.INVALID_ARGUMENT, mapped.failureValue().code) + self.assertEquals("boom", mapped.failureValue().message) + + + + + + + diff --git a/org_eclipse_uprotocol_tests/rpc/rpcresulttest.py b/org_eclipse_uprotocol_tests/test_rpc/test_rpcresult.py similarity index 99% rename from org_eclipse_uprotocol_tests/rpc/rpcresulttest.py rename to org_eclipse_uprotocol_tests/test_rpc/test_rpcresult.py index bfe68ee..31f821f 100644 --- a/org_eclipse_uprotocol_tests/rpc/rpcresulttest.py +++ b/org_eclipse_uprotocol_tests/test_rpc/test_rpcresult.py @@ -39,7 +39,7 @@ def multiply_by_2(x): return RpcResult.success(x * 2) -class RpcResultTest(unittest.TestCase): +class TestRpcResult(unittest.TestCase): def test_isSuccess_on_Success(self): result = RpcResult.success(2) diff --git a/org_eclipse_uprotocol_tests/transport/builder/uattributesbuildertest.py b/org_eclipse_uprotocol_tests/test_transport/test_builder/test_uattributesbuilder.py similarity index 98% rename from org_eclipse_uprotocol_tests/transport/builder/uattributesbuildertest.py rename to org_eclipse_uprotocol_tests/test_transport/test_builder/test_uattributesbuilder.py index c749bb5..c65070a 100644 --- a/org_eclipse_uprotocol_tests/transport/builder/uattributesbuildertest.py +++ b/org_eclipse_uprotocol_tests/test_transport/test_builder/test_uattributesbuilder.py @@ -43,7 +43,7 @@ def get_uuid(): return Factories.UPROTOCOL.create() -class UAttributesBuilderTest(unittest.TestCase): +class TestUAttributesBuilder(unittest.TestCase): def test_publish(self): builder = UAttributesBuilder.publish(UPriority.UPRIORITY_CS1) diff --git a/org_eclipse_uprotocol_tests/transport/validate/uattributesvalidatortest.py b/org_eclipse_uprotocol_tests/test_transport/test_validate/test_uattributesvalidator.py similarity index 99% rename from org_eclipse_uprotocol_tests/transport/validate/uattributesvalidatortest.py rename to org_eclipse_uprotocol_tests/test_transport/test_validate/test_uattributesvalidator.py index 19504b0..1d0011d 100644 --- a/org_eclipse_uprotocol_tests/transport/validate/uattributesvalidatortest.py +++ b/org_eclipse_uprotocol_tests/test_transport/test_validate/test_uattributesvalidator.py @@ -45,7 +45,7 @@ def build_sink(): resource=UResourceBuilder.for_rpc_response()) -class UAttributesValidatorTest(unittest.TestCase): +class TestUAttributesValidator(unittest.TestCase): def test_fetching_validator_for_valid_types(self): publish = UAttributesValidator.get_validator(UAttributesBuilder.publish(UPriority.UPRIORITY_CS0).build()) diff --git a/org_eclipse_uprotocol_tests/uri/serializer/longuriserializertest.py b/org_eclipse_uprotocol_tests/test_uri/test_serializer/test_longuriserializer.py similarity index 99% rename from org_eclipse_uprotocol_tests/uri/serializer/longuriserializertest.py rename to org_eclipse_uprotocol_tests/test_uri/test_serializer/test_longuriserializer.py index 13de27e..7cddd13 100644 --- a/org_eclipse_uprotocol_tests/uri/serializer/longuriserializertest.py +++ b/org_eclipse_uprotocol_tests/test_uri/test_serializer/test_longuriserializer.py @@ -33,7 +33,7 @@ from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator -class LongUriSerializerTest(unittest.TestCase): +class TestLongUriSerializer(unittest.TestCase): def test_using_the_serializers(self): uri = UUri(entity=UEntity(name="hartley"), resource=UResourceBuilder.for_rpc_request("raise")) diff --git a/org_eclipse_uprotocol_tests/uri/serializer/microuriserializertest.py b/org_eclipse_uprotocol_tests/test_uri/test_serializer/test_microuriserializer.py similarity index 99% rename from org_eclipse_uprotocol_tests/uri/serializer/microuriserializertest.py rename to org_eclipse_uprotocol_tests/test_uri/test_serializer/test_microuriserializer.py index 2da4564..19beff7 100644 --- a/org_eclipse_uprotocol_tests/uri/serializer/microuriserializertest.py +++ b/org_eclipse_uprotocol_tests/test_uri/test_serializer/test_microuriserializer.py @@ -33,7 +33,7 @@ from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator -class MicroUriSerializerTest(unittest.TestCase): +class TestMicroUriSerializer(unittest.TestCase): def test_empty(self): bytes_uuri = MicroUriSerializer().serialize(UUri()) diff --git a/org_eclipse_uprotocol_tests/uri/serializer/uriserializertest.py b/org_eclipse_uprotocol_tests/test_uri/test_serializer/test_uriserializer.py similarity index 98% rename from org_eclipse_uprotocol_tests/uri/serializer/uriserializertest.py rename to org_eclipse_uprotocol_tests/test_uri/test_serializer/test_uriserializer.py index 10e5134..3e418c6 100644 --- a/org_eclipse_uprotocol_tests/uri/serializer/uriserializertest.py +++ b/org_eclipse_uprotocol_tests/test_uri/test_serializer/test_uriserializer.py @@ -33,7 +33,7 @@ from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator -class UriSerializerTest(unittest.TestCase): +class TestUriSerializer(unittest.TestCase): def test_build_resolved_valid_long_micro_uri(self): long_uuri = UUri(authority=UAuthority(name="testauth"), entity=UEntity(name="neelam"), diff --git a/org_eclipse_uprotocol_tests/uri/validator/urivalidatortest.py b/org_eclipse_uprotocol_tests/test_uri/test_validator/test_urivalidator.py similarity index 100% rename from org_eclipse_uprotocol_tests/uri/validator/urivalidatortest.py rename to org_eclipse_uprotocol_tests/test_uri/test_validator/test_urivalidator.py diff --git a/org_eclipse_uprotocol_tests/uri/validator/uris.json b/org_eclipse_uprotocol_tests/test_uri/test_validator/uris.json similarity index 100% rename from org_eclipse_uprotocol_tests/uri/validator/uris.json rename to org_eclipse_uprotocol_tests/test_uri/test_validator/uris.json diff --git a/org_eclipse_uprotocol_tests/uuid/factory/uuidfactorytest.py b/org_eclipse_uprotocol_tests/test_uuid/test_factory/test_uuidfactory.py similarity index 100% rename from org_eclipse_uprotocol_tests/uuid/factory/uuidfactorytest.py rename to org_eclipse_uprotocol_tests/test_uuid/test_factory/test_uuidfactory.py diff --git a/org_eclipse_uprotocol_tests/uuid/validator/uuidvalidatortest.py b/org_eclipse_uprotocol_tests/test_uuid/test_validator/test_uuidvalidator.py similarity index 100% rename from org_eclipse_uprotocol_tests/uuid/validator/uuidvalidatortest.py rename to org_eclipse_uprotocol_tests/test_uuid/test_validator/test_uuidvalidator.py diff --git a/org_eclipse_uprotocol_tests/uri/__init__.py b/org_eclipse_uprotocol_tests/uri/__init__.py deleted file mode 100644 index e69de29..0000000 From 832d0a4141d676f02a39343b868cf8f6bfab2873 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Tue, 28 Nov 2023 16:25:33 -0500 Subject: [PATCH 36/47] Add few more test cases and fix bugs identified while testing --- .../cloudevent/factory/ucloudevent.py | 7 +- org_eclipse_uprotocol/rpc/rpcmapper.py | 25 +- .../uri/validator/urivalidator.py | 2 - org_eclipse_uprotocol_tests/__init__.py | 0 .../test_cloudevent/__init__.py | 0 .../test_datamodel/__init__.py | 0 .../test_cloudevent/test_factory/__init__.py | 0 .../test_factory/test_cloudeventfactory.py | 2 +- .../test_serialize/__init__.py | 0 .../test_base64protobufserializer.py | 77 +++ .../test_cloudeventtojsonserializer.py | 129 +++++ .../test_validator/__init__.py | 0 .../test_cloudeventvalidator.py | 470 ++++++++++++++++++ .../test_validator/test_validationresult.py | 67 +++ .../test_rpc/__init__.py | 0 .../test_rpc/test_rpc.py | 52 +- .../test_transport/__init__.py | 0 .../test_transport/test_builder/__init__.py | 0 .../test_transport/test_validate/__init__.py | 0 .../test_uri/__init__.py | 0 .../test_uri/test_serializer/__init__.py | 0 .../test_validator/test_urivalidator.py | 2 +- .../test_uuid/__init__.py | 0 .../test_uuid/test_factory/__init__.py | 0 .../test_uuid/test_validator/__init__.py | 0 25 files changed, 817 insertions(+), 16 deletions(-) create mode 100644 org_eclipse_uprotocol_tests/__init__.py create mode 100644 org_eclipse_uprotocol_tests/test_cloudevent/__init__.py create mode 100644 org_eclipse_uprotocol_tests/test_cloudevent/test_datamodel/__init__.py create mode 100644 org_eclipse_uprotocol_tests/test_cloudevent/test_factory/__init__.py create mode 100644 org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/__init__.py create mode 100644 org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_base64protobufserializer.py create mode 100644 org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py create mode 100644 org_eclipse_uprotocol_tests/test_cloudevent/test_validator/__init__.py create mode 100644 org_eclipse_uprotocol_tests/test_cloudevent/test_validator/test_cloudeventvalidator.py create mode 100644 org_eclipse_uprotocol_tests/test_cloudevent/test_validator/test_validationresult.py create mode 100644 org_eclipse_uprotocol_tests/test_rpc/__init__.py create mode 100644 org_eclipse_uprotocol_tests/test_transport/__init__.py create mode 100644 org_eclipse_uprotocol_tests/test_transport/test_builder/__init__.py create mode 100644 org_eclipse_uprotocol_tests/test_transport/test_validate/__init__.py create mode 100644 org_eclipse_uprotocol_tests/test_uri/__init__.py create mode 100644 org_eclipse_uprotocol_tests/test_uri/test_serializer/__init__.py create mode 100644 org_eclipse_uprotocol_tests/test_uuid/__init__.py create mode 100644 org_eclipse_uprotocol_tests/test_uuid/test_factory/__init__.py create mode 100644 org_eclipse_uprotocol_tests/test_uuid/test_validator/__init__.py diff --git a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py index 662397f..06945e6 100644 --- a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py +++ b/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py @@ -249,7 +249,7 @@ def is_expired(ce: CloudEvent) -> bool: uuid = LongUuidSerializer.instance().deserialize(cloud_event_id) if uuid is None: return False - delta = datetime.utcnow().timestamp() - UUIDUtils.getTime(uuid).timestamp() + delta = datetime.utcnow().timestamp() - UUIDUtils.getTime(uuid) except ValueError: # Invalid UUID, handle accordingly delta = 0 @@ -295,7 +295,10 @@ def unpack(ce: CloudEvent, clazz): @return: Returns a {@link Message} payload of the class type that is provided. """ try: - return UCloudEvent.get_payload(ce).Unpack(clazz) + any_obj=UCloudEvent.get_payload(ce) + value = clazz() + value.ParseFromString(any_obj.value) + return value except DecodeError: return None diff --git a/org_eclipse_uprotocol/rpc/rpcmapper.py b/org_eclipse_uprotocol/rpc/rpcmapper.py index 2889652..46f8fb8 100644 --- a/org_eclipse_uprotocol/rpc/rpcmapper.py +++ b/org_eclipse_uprotocol/rpc/rpcmapper.py @@ -77,6 +77,7 @@ def callbackwrapper(payload, exception=None): result = handle_response(payload, exception) response_future.add_done_callback(callbackwrapper) + return result @staticmethod @@ -90,12 +91,15 @@ def map_response_to_result(response_future: Future, expected_cls): or a UStatus containing any errors. """ - def handle_response(payload, exception=None): - if exception: - raise exception + def handle_response(payload): + if payload.exception(): + exception = payload.exception() + return RpcResult.failure(value=exception, message=str(exception)) + payload = payload.result() if not payload: - raise RuntimeError(f"Server returned a null payload. Expected {expected_cls.__name__}") + 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() @@ -110,15 +114,18 @@ def handle_response(payload, exception=None): if any_message.Is(UStatus.DESCRIPTOR): return RpcMapper.calculate_status_result(any_message) except Exception as e: - raise RuntimeError(f"{str(e)} [{UStatus.__name__}]") from e + exception = RuntimeError(f"{str(e)} [{UStatus.__name__}]") + return RpcResult.failure(value=exception, message=str(exception)) - raise RuntimeError(f"Unknown payload type [{any_message.type_url}]. Expected [{expected_cls.__name__}]") + exception = RuntimeError( + f"Unknown payload type [{any_message.type_url}]. Expected [{expected_cls.DESCRIPTOR.full_name}]") + return RpcResult.failure(value=exception, message=str(exception)) result = None # Initialize result - def callback_wrapper(payload, exception=None): + def callback_wrapper(payload): nonlocal result - result = handle_response(payload, exception) + result = handle_response(payload) response_future.add_done_callback(callback_wrapper) return result @@ -139,7 +146,7 @@ def unpack_payload(payload, expected_cls): object. """ try: - value=expected_cls() + value = expected_cls() value.ParseFromString(payload.value) # payload.Unpack(expected_cls) return value diff --git a/org_eclipse_uprotocol/uri/validator/urivalidator.py b/org_eclipse_uprotocol/uri/validator/urivalidator.py index 1c809e1..84fc285 100644 --- a/org_eclipse_uprotocol/uri/validator/urivalidator.py +++ b/org_eclipse_uprotocol/uri/validator/urivalidator.py @@ -102,8 +102,6 @@ def is_rpc_method(uri: UUri) -> bool: """ Returns true if this resource specifies an RPC method call or RPC response.

@param uri: - @param uuri: - @param uresource: UResource protobuf message @return:Returns true if this resource specifies an RPC method call or RPC response. """ if uri is None: diff --git a/org_eclipse_uprotocol_tests/__init__.py b/org_eclipse_uprotocol_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/__init__.py b/org_eclipse_uprotocol_tests/test_cloudevent/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_datamodel/__init__.py b/org_eclipse_uprotocol_tests/test_cloudevent/test_datamodel/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_factory/__init__.py b/org_eclipse_uprotocol_tests/test_cloudevent/test_factory/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_factory/test_cloudeventfactory.py b/org_eclipse_uprotocol_tests/test_cloudevent/test_factory/test_cloudeventfactory.py index ec0f83c..a547f12 100644 --- a/org_eclipse_uprotocol_tests/test_cloudevent/test_factory/test_cloudeventfactory.py +++ b/org_eclipse_uprotocol_tests/test_cloudevent/test_factory/test_cloudeventfactory.py @@ -45,7 +45,7 @@ def get_json_object(): current_directory = os.getcwd() - json_file_path = os.path.join(current_directory, "cloudevent.json") + json_file_path = os.path.join(current_directory, "org_eclipse_uprotocol_tests","test_cloudevent","test_factory","cloudevent.json") with open(json_file_path, 'r') as json_file: json_data = json.load(json_file) diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/__init__.py b/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_base64protobufserializer.py b/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_base64protobufserializer.py new file mode 100644 index 0000000..1a798f7 --- /dev/null +++ b/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_base64protobufserializer.py @@ -0,0 +1,77 @@ +# ------------------------------------------------------------------------- + +# 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 unittest + +from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from org_eclipse_uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory +from org_eclipse_uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer +from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializers import CloudEventSerializers + + +class TestBase64ProtobufSerializer(unittest.TestCase): + + def test_deserialize_bytes_to_string(self): + ce = CloudEventFactory.build_base_cloud_event("hello", "http://localhost", bytearray(), "", + UCloudEventAttributesBuilder().build(), "example.vertx") + ce.__delitem__("time") + bytes_data = CloudEventSerializers.JSON.serializer().serialize(ce) + payload = Base64ProtobufSerializer().deserialize(bytes_data) + self.assertEquals( + "eyJzcGVjdmVyc2lvbiI6ICIxLjAiLCAiaWQiOiAiaGVsbG8iLCAic291cmNlIjogImh0dHA6Ly9sb2NhbGhvc3QiLCAidHlwZSI6ICJleGFtcGxlLnZlcnR4IiwgImRhdGFfYmFzZTY0IjogIiJ9", + payload) + + def test_deserialize_bytes_to_string_when_bytes_is_null(self): + payload = Base64ProtobufSerializer().deserialize(None) + self.assertEquals("", payload) + + def test_deserialize_bytes_to_string_when_bytes_is_empty(self): + payload = Base64ProtobufSerializer().deserialize(bytearray()) + self.assertEquals("", payload) + + def test_serialize_string_into_bytes(self): + json_str = "eyJzcGVjdmVyc2lvbiI6ICIxLjAiLCAiaWQiOiAiaGVsbG8iLCAic291cmNlIjogImh0dHA6Ly9sb2NhbGhvc3QiLCAidHlwZSI6ICJleGFtcGxlLnZlcnR4IiwgImRhdGFfYmFzZTY0IjogIiJ9" + bytes_json = Base64ProtobufSerializer().serialize(json_str) + + ce = CloudEventFactory.build_base_cloud_event("hello", "http://localhost", bytearray(), "", + UCloudEventAttributesBuilder().build(), "example.vertx") + ce.__delitem__("time") + + bytes_data = CloudEventSerializers.JSON.serializer().serialize(ce) + self.assertEquals(bytes_json, bytes_data) + + def test_serialize_string_into_bytes_when_string_is_null(self): + bytes_data = Base64ProtobufSerializer().serialize(None) + self.assertEquals(bytearray(), bytes_data) + + def test_serialize_string_into_bytes_when_string_is_empty(self): + bytes_data = Base64ProtobufSerializer().serialize('') + self.assertEquals(bytearray(), bytes_data) + + +if __name__ == '__main__': + unittest.main() diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py b/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py new file mode 100644 index 0000000..e8b8617 --- /dev/null +++ b/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py @@ -0,0 +1,129 @@ +# ------------------------------------------------------------------------- + +# 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 unittest + +from google.protobuf import any_pb2 + +from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from org_eclipse_uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory +from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent +from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializers import CloudEventSerializers +from org_eclipse_uprotocol.cloudevent.serialize.cloudeventtojsonserializer import CloudEventToJsonSerializer +from org_eclipse_uprotocol.proto.cloudevents_pb2 import CloudEvent +from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority, UMessageType + +protoContentType = CloudEventFactory.PROTOBUF_CONTENT_TYPE +serializer = CloudEventSerializers.JSON.serializer() + + +def build_proto_payload_for_test(): + ce_proto = CloudEvent(spec_version="1.0", source="https://example.com", id="hello", type="example.demo", + proto_data=any_pb2.Any(), + attributes={"ttl": CloudEvent.CloudEventAttributeValue(ce_string="3")}) + + any_obj = any_pb2.Any() + any_obj.Pack(ce_proto) + return any_obj + + +class TestCloudEventToJsonSerializer(unittest.TestCase): + + def test_serialize_cloud_event_to_json(self): + proto_payload = build_proto_payload_for_test() + # additional attributes + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_priority( + UPriority.UPRIORITY_CS1).with_ttl(3).build() + + # build the cloud event + cloud_event = CloudEventFactory.build_base_cloud_event("hello", "/body.access/1/door.front_left", + proto_payload.SerializeToString(), + proto_payload.type_url, u_cloud_event_attributes, + UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_PUBLISH)) + cloud_event.__setitem__('datacontenttype', protoContentType) + cloud_event.__setitem__('dataschema', proto_payload.type_url) + cloud_event.__delitem__("time") + bytes_data = serializer.serialize(cloud_event) + json_str = bytes_data.decode('utf-8') + expected = ('{"specversion": "1.0", "id": "hello", "source": "/body.access/1/door.front_left", ' + '"type": "pub.v1", "datacontenttype": "application/x-protobuf", "dataschema": ' + '"type.googleapis.com/io.cloudevents.v1.CloudEvent", "data_base64": ' + '"CjB0eXBlLmdvb2dsZWFwaXMuY29tL2lvLmNsb3VkZXZlbnRzLnYxLkNsb3VkRXZlbnQSPQoFaGVsbG8SE2h0dHBzOi8vZXhhbXBsZS5jb20aAzEuMCIMZXhhbXBsZS5kZW1vKgoKA3R0bBIDGgEzQgA=", "ttl": 3, "priority": "UPRIORITY_CS1"}') + self.assertEquals(expected, json_str) + + def test_serialize_and_deserialize_cloud_event_to_json(self): + proto_payload = build_proto_payload_for_test() + # additional attributes + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_priority( + UPriority.UPRIORITY_CS1).with_ttl(3).build() + + # build the cloud event + cloud_event = CloudEventFactory.build_base_cloud_event("hello", "/body.access/1/door.front_left", + proto_payload.SerializeToString(), + proto_payload.type_url, u_cloud_event_attributes, + UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_PUBLISH)) + cloud_event.__setitem__('datacontenttype', protoContentType) + cloud_event.__setitem__('dataschema', proto_payload.type_url) + cloud_event.__delitem__("time") + serialized_data = serializer.serialize(cloud_event) + deserialized_data = serializer.deserialize(serialized_data) + deserialized_data.__delitem__("time") + + self.assertEquals(cloud_event, deserialized_data) + + def test_double_serialization_protobuf_when_creating_cloud_event_with_factory_methods(self): + proto_payload = build_proto_payload_for_test() + # additional attributes + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_priority( + UPriority.UPRIORITY_CS1).with_ttl(3).build() + + # build the cloud event + cloud_event1 = CloudEventFactory.build_base_cloud_event("hello", "/body.access/1/door.front_left", + proto_payload.SerializeToString(), + proto_payload.type_url, u_cloud_event_attributes, + UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_PUBLISH)) + cloud_event1.__setitem__('datacontenttype', protoContentType) + cloud_event1.__setitem__('dataschema', proto_payload.type_url) + cloud_event1.__delitem__("time") + serialized_data1 = serializer.serialize(cloud_event1) + cloud_event2 = serializer.deserialize(serialized_data1) + cloud_event2.__delitem__("time") + self.assertEquals(cloud_event2, cloud_event1) + serialized_data2 = serializer.serialize(cloud_event2) + self.assertEquals(serialized_data1, serialized_data2) + cloud_event3 = serializer.deserialize(serialized_data2) + cloud_event3.__delitem__("time") + + payload1 = UCloudEvent.unpack(cloud_event3, CloudEvent) + self.assertEquals(cloud_event2, cloud_event3) + payload2 = CloudEvent() + payload2.ParseFromString(proto_payload.value) + self.assertEquals(payload1, payload2) + self.assertEquals(cloud_event1, cloud_event3) diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_validator/__init__.py b/org_eclipse_uprotocol_tests/test_cloudevent/test_validator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_validator/test_cloudeventvalidator.py b/org_eclipse_uprotocol_tests/test_cloudevent/test_validator/test_cloudeventvalidator.py new file mode 100644 index 0000000..73c73d7 --- /dev/null +++ b/org_eclipse_uprotocol_tests/test_cloudevent/test_validator/test_cloudeventvalidator.py @@ -0,0 +1,470 @@ +# ------------------------------------------------------------------------- + +# 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 unittest + +from google.protobuf import any_pb2 + +from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from org_eclipse_uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory +from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent +from org_eclipse_uprotocol.cloudevent.validate.cloudeventvalidator import CloudEventValidator, Validators +from org_eclipse_uprotocol.proto.cloudevents_pb2 import CloudEvent +from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority, UMessageType +from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UEntity, UResource +from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode +from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from org_eclipse_uprotocol.uuid.factory.uuidfactory import Factories +from org_eclipse_uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer +from org_eclipse_uprotocol.validation.validationresult import ValidationResult + + +def build_base_cloud_event_for_test(): + # uri + source = build_long_uri_for_test() + + # fake payload + proto_payload = build_proto_payload_for_test() + # additional attributes + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( + UPriority.UPRIORITY_CS1).with_ttl(3).with_token("someOAuthToken").build() + # build the cloud event + cloud_event = CloudEventFactory.build_base_cloud_event("testme", source, proto_payload.SerializeToString(), + proto_payload.type_url, u_cloud_event_attributes, + UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_PUBLISH)) + return cloud_event + + pass + + +def build_proto_payload_for_test(): + ce_proto = CloudEvent(spec_version="1.0", source="https://example.com", id="hello", type="example.demo", + proto_data=any_pb2.Any()) + + any_obj = any_pb2.Any() + any_obj.Pack(ce_proto) + return any_obj + + +def build_uuri_for_test(): + return UUri(entity=UEntity(name="body.access"), + resource=UResource(name="door", instance="front_left", message="Door")) + + +def build_long_uri_for_test(): + return LongUriSerializer().serialize(build_uuri_for_test()) + + +class TestCloudEventValidator(unittest.TestCase): + + def test_get_a_publish_cloud_event_validator(self): + cloud_event = build_base_cloud_event_for_test() + validator = CloudEventValidator.get_validator(cloud_event) + status = validator.validate_type(cloud_event).to_status() + self.assertEqual(status, ValidationResult.STATUS_SUCCESS) + self.assertEqual("CloudEventValidator.Publish", str(validator)) + + def test_get_a_notification_cloud_event_validator(self): + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("sink", "//bo.cloud/petapp") + validator = Validators.NOTIFICATION.validator() + status = validator.validate_type(cloud_event).to_status() + self.assertEqual(status, ValidationResult.STATUS_SUCCESS) + self.assertEqual("CloudEventValidator.Notification", str(validator)) + + def test_publish_cloud_event_type(self): + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("type", "res.v1") + validator = Validators.PUBLISH.validator() + status = validator.validate_type(cloud_event).to_status() + self.assertEquals(UCode.INVALID_ARGUMENT, status.code) + self.assertEquals("Invalid CloudEvent type [res.v1]. CloudEvent of type Publish must have a type of 'pub.v1'", + status.message) + + def test_notification_cloud_event_type(self): + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("type", "res.v1") + validator = Validators.NOTIFICATION.validator() + status = validator.validate_type(cloud_event).to_status() + self.assertEquals(UCode.INVALID_ARGUMENT, status.code) + self.assertEquals("Invalid CloudEvent type [res.v1]. CloudEvent of type Publish must have a type of 'pub.v1'", + status.message) + + def test_get_a_request_cloud_event_validator(self): + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("type", "req.v1") + validator = CloudEventValidator.get_validator(cloud_event) + status = validator.validate_type(cloud_event).to_status() + self.assertEqual(status, ValidationResult.STATUS_SUCCESS) + self.assertEqual("CloudEventValidator.Request", str(validator)) + + def test_request_cloud_event_type(self): + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("type", "pub.v1") + validator = Validators.REQUEST.validator() + status = validator.validate_type(cloud_event).to_status() + self.assertEquals(UCode.INVALID_ARGUMENT, status.code) + self.assertEquals("Invalid CloudEvent type [pub.v1]. CloudEvent of type Request must have a type of 'req.v1'", + status.message) + + def test_get_a_response_cloud_event_validator(self): + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("type", "res.v1") + validator = CloudEventValidator.get_validator(cloud_event) + status = validator.validate_type(cloud_event).to_status() + self.assertEqual(status, ValidationResult.STATUS_SUCCESS) + self.assertEqual("CloudEventValidator.Response", str(validator)) + + def test_response_cloud_event_type(self): + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("type", "pub.v1") + validator = Validators.RESPONSE.validator() + status = validator.validate_type(cloud_event).to_status() + self.assertEquals(UCode.INVALID_ARGUMENT, status.code) + self.assertEquals("Invalid CloudEvent type [pub.v1]. CloudEvent of type Response must have a type of 'res.v1'", + status.message) + + def test_get_a_publish_cloud_event_validator_when_cloud_event_type_is_unknown(self): + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("type", "lala.v1") + validator = CloudEventValidator.get_validator(cloud_event) + status = validator.validate_type(cloud_event).to_status() + self.assertEqual("CloudEventValidator.Publish", str(validator)) + + def test_validate_cloud_event_version_when_valid(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("type", "pub.v1") + cloud_event.__setitem__("id", str_uuid) + status = CloudEventValidator.validate_version(cloud_event).to_status() + self.assertEqual(status, ValidationResult.STATUS_SUCCESS) + + def test_validate_cloud_event_version_when_not_valid(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("specversion", "0.3") + cloud_event.__setitem__("id", str_uuid) + status = CloudEventValidator.validate_version(cloud_event).to_status() + self.assertEquals(UCode.INVALID_ARGUMENT, status.code) + self.assertEquals("Invalid CloudEvent version [0.3]. CloudEvent version must be 1.0.", status.message) + + def test_validate_cloud_event_id_when_valid(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "pub.v1") + status = CloudEventValidator.validate_version(cloud_event).to_status() + self.assertEqual(status, ValidationResult.STATUS_SUCCESS) + + def test_validate_cloud_event_id_when_not_uuidv8_type_id(self): + str_uuid = "1dd9200c-d41b-4658-8102-3101f0b91378" + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "pub.v1") + status = CloudEventValidator.validate_id(cloud_event).to_status() + self.assertEquals(UCode.INVALID_ARGUMENT, status.code) + self.assertEquals("Invalid CloudEvent Id [" + str_uuid + "]. CloudEvent Id must be of type UUIDv8.", + status.message) + + def test_validate_cloud_event_id_when_not_valid(self): + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", "testme") + cloud_event.__setitem__("type", "pub.v1") + status = CloudEventValidator.validate_id(cloud_event).to_status() + self.assertEquals(UCode.INVALID_ARGUMENT, status.code) + self.assertEquals("Invalid CloudEvent Id [testme]. CloudEvent Id must be of type UUIDv8.", status.message) + + def test_publish_type_cloudevent_is_valid_when_everything_is_valid_local(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "pub.v1") + cloud_event.__setitem__("source", "/body.access/1/door.front_left#Door") + validator = Validators.PUBLISH.validator() + result = validator.validate(cloud_event) + self.assertEquals(ValidationResult.success(), result) + + def test_publish_type_cloudevent_is_valid_when_everything_is_valid_remote(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "pub.v1") + cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/door.front_left#Door") + validator = Validators.PUBLISH.validator() + result = validator.validate(cloud_event) + self.assertEquals(ValidationResult.success(), result) + + def test_publish_type_cloudevent_is_valid_when_everything_is_valid_remote_with_a_sink(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "pub.v1") + cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/door.front_left#Door") + cloud_event.__setitem__("sink", "//bo.cloud/petapp") + validator = Validators.PUBLISH.validator() + result = validator.validate(cloud_event) + self.assertEquals(ValidationResult.success(), result) + + def test_publish_type_cloudevent_is_not_valid_when_remote_with_invalid_sink(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "pub.v1") + cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/door.front_left#Door") + cloud_event.__setitem__("sink", "//bo.cloud") + validator = Validators.PUBLISH.validator() + result = validator.validate(cloud_event) + self.assertEquals("Invalid CloudEvent sink [//bo.cloud]. Uri is missing uSoftware Entity name.", + result.get_message()) + + def test_publish_type_cloudevent_is_not_valid_when_source_is_empty(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("source", "/") + validator = Validators.PUBLISH.validator() + result = validator.validate(cloud_event) + self.assertEquals("Invalid Publish type CloudEvent source [/]. Uri is empty.", result.get_message()) + + def test_publish_type_cloudevent_is_not_valid_when_source_is_missing_authority(self): + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", "testme") + cloud_event.__setitem__("type", "pub.v1") + cloud_event.__setitem__("source", "/body.access") + validator = Validators.PUBLISH.validator() + result = validator.validate(cloud_event) + self.assertEquals( + "Invalid CloudEvent Id [testme]. CloudEvent Id must be of type UUIDv8.," + "Invalid Publish type " + + "CloudEvent source [/body.access]. UriPart is missing uResource name.", + result.get_message()) + + def test_publish_type_cloudevent_is_not_valid_when_source_is_missing_message_info(self): + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", "testme") + cloud_event.__setitem__("type", "pub.v1") + cloud_event.__setitem__("source", "/body.access/1/door.front_left") + validator = Validators.PUBLISH.validator() + result = validator.validate(cloud_event) + self.assertEquals( + "Invalid CloudEvent Id [testme]. CloudEvent Id must be of type UUIDv8.," + "Invalid Publish type " + + "CloudEvent source [/body.access/1/door.front_left]. UriPart is missing Message information.", + result.get_message()) + + def test_notification_type_cloudevent_is_valid_when_everything_is_valid(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "pub.v1") + cloud_event.__setitem__("source", "/body.access/1/door.front_left#Door") + cloud_event.__setitem__("sink", "//bo.cloud/petapp") + validator = Validators.NOTIFICATION.validator() + result = validator.validate(cloud_event) + self.assertEquals(ValidationResult.success(), result) + + def test_notification_type_cloudevent_is_not_valid_missing_sink(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "pub.v1") + cloud_event.__setitem__("source", "/body.access/1/door.front_left#Door") + validator = Validators.NOTIFICATION.validator() + result = validator.validate(cloud_event) + self.assertEquals("Invalid CloudEvent sink. Notification CloudEvent sink must be an uri.", + result.get_message()) + + def test_notification_type_cloudevent_is_not_valid_invalid_sink(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "pub.v1") + cloud_event.__setitem__("sink", "//bo.cloud") + cloud_event.__setitem__("source", "/body.access/1/door.front_left#Door") + validator = Validators.NOTIFICATION.validator() + result = validator.validate(cloud_event) + self.assertEquals( + "Invalid Notification type CloudEvent sink [//bo.cloud]. Uri is missing uSoftware Entity name.", + result.get_message()) + + def test_request_type_cloudevent_is_valid_when_everything_is_valid(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "req.v1") + cloud_event.__setitem__("sink", "//VCU.myvin/body.access/1/rpc.UpdateDoor") + cloud_event.__setitem__("source", "//bo.cloud/petapp//rpc.response") + validator = Validators.REQUEST.validator() + result = validator.validate(cloud_event) + self.assertEquals(ValidationResult.success(), result) + + def test_request_type_cloudevent_is_not_valid_invalid_source(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "req.v1") + cloud_event.__setitem__("sink", "//VCU.myvin/body.access/1/rpc.UpdateDoor") + cloud_event.__setitem__("source", "//bo.cloud/petapp//dog") + validator = Validators.REQUEST.validator() + result = validator.validate(cloud_event) + self.assertEquals( + "Invalid RPC Request CloudEvent source [//bo.cloud/petapp//dog]. " + "Invalid RPC uri application " + + "response topic. UriPart is missing rpc.response.", + result.get_message()) + + def test_request_type_cloudevent_is_not_valid_missing_sink(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "req.v1") + cloud_event.__setitem__("source", "//bo.cloud/petapp//rpc.response") + validator = Validators.REQUEST.validator() + result = validator.validate(cloud_event) + self.assertEquals( + "Invalid RPC Request CloudEvent sink. Request CloudEvent sink must be uri for the method to be called.", + result.get_message()) + + def test_request_type_cloudevent_is_not_valid_invalid_sink_not_rpc_command(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "req.v1") + cloud_event.__setitem__("source", "//bo.cloud/petapp//rpc.response") + cloud_event.__setitem__("sink", "//VCU.myvin/body.access/1/UpdateDoor") + validator = Validators.REQUEST.validator() + result = validator.validate(cloud_event) + self.assertEquals( + "Invalid RPC Request CloudEvent sink [//VCU.myvin/body.access/1/UpdateDoor]. " + "Invalid RPC method " + + "uri. UriPart should be the method to be called, or method from response.", + result.get_message()) + + def test_response_type_cloudevent_is_valid_when_everything_is_valid(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "res.v1") + cloud_event.__setitem__("sink", "//bo.cloud/petapp//rpc.response") + cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/rpc.UpdateDoor") + validator = Validators.RESPONSE.validator() + result = validator.validate(cloud_event) + self.assertEquals(ValidationResult.success(), result) + + def test_response_type_cloudevent_is_not_valid_invalid_source(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "res.v1") + cloud_event.__setitem__("sink", "//bo.cloud/petapp//rpc.response") + cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/UpdateDoor") + validator = Validators.RESPONSE.validator() + result = validator.validate(cloud_event) + self.assertEquals( + "Invalid RPC Response CloudEvent source [//VCU.myvin/body.access/1/UpdateDoor]. " + "Invalid RPC " + + "method uri. UriPart should be the method to be called, or method from response.", + result.get_message()) + + def test_response_type_cloudevent_is_not_valid_missing_sink_and_invalid_source(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "res.v1") + cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/UpdateDoor") + validator = Validators.RESPONSE.validator() + result = validator.validate(cloud_event) + self.assertEquals( + "Invalid RPC Response CloudEvent source [//VCU.myvin/body.access/1/UpdateDoor]. " + "Invalid RPC " + + "method uri. UriPart should be the method to be called, or method from response.," + "Invalid" + " CloudEvent sink. Response CloudEvent sink must be uri the destination of the response.", + result.get_message()) + + def test_response_type_cloudevent_is_not_valid_invalid_sink(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "res.v1") + cloud_event.__setitem__("sink", "//bo.cloud") + cloud_event.__setitem__("source", "//VCU.myvin") + validator = Validators.RESPONSE.validator() + result = validator.validate(cloud_event) + self.assertEquals( + "Invalid RPC Response CloudEvent source [//VCU.myvin]. Invalid RPC method uri. Uri is missing " + + "uSoftware Entity name.,Invalid RPC Response CloudEvent sink [//bo.cloud]. Invalid RPC uri " + + "application response topic. Uri is missing uSoftware Entity name.", + result.get_message()) + + def test_response_type_cloudevent_is_not_valid_invalid_source_not_rpc_command(self): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("type", "res.v1") + cloud_event.__setitem__("source", "//bo.cloud/petapp/1/dog") + cloud_event.__setitem__("sink", "//VCU.myvin/body.access/1/UpdateDoor") + validator = Validators.RESPONSE.validator() + result = validator.validate(cloud_event) + self.assertEquals( + "Invalid RPC Response CloudEvent source [//bo.cloud/petapp/1/dog]. Invalid RPC method uri. UriPart " + + "should be the method to be called, or method from response.," + "Invalid RPC Response " + "CloudEvent " + "sink [" + "//VCU.myvin/body.access/1/UpdateDoor]. " + "Invalid RPC uri application " + "response topic. UriPart is missing rpc.response.", + result.get_message()) + + def test_create_a_v6_cloudevent_and_validate_it_against_sdk(self): + source = build_long_uri_for_test() + uuid = Factories.UUIDV6.create() + id = LongUuidSerializer.instance().serialize(uuid) + proto_payload=build_proto_payload_for_test() + # additional attributes + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_priority( + UPriority.UPRIORITY_CS0).with_ttl(1000).build() + # build the cloud event + cloud_event = CloudEventFactory.build_base_cloud_event(id, source, proto_payload.SerializeToString(), + proto_payload.type_url, u_cloud_event_attributes, + UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_PUBLISH)) + validator = Validators.PUBLISH.validator() + result = validator.validate(cloud_event) + self.assertTrue(result.is_success()) + self.assertFalse(UCloudEvent.is_expired(cloud_event)) diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_validator/test_validationresult.py b/org_eclipse_uprotocol_tests/test_cloudevent/test_validator/test_validationresult.py new file mode 100644 index 0000000..8cebd76 --- /dev/null +++ b/org_eclipse_uprotocol_tests/test_cloudevent/test_validator/test_validationresult.py @@ -0,0 +1,67 @@ +# ------------------------------------------------------------------------- + +# 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 unittest + +from org_eclipse_uprotocol.validation.validationresult import ValidationResult +from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus, UCode + + +class TestValidationResultTest(unittest.TestCase): + + def test_success_validation_result_to_string(self): + success = ValidationResult.success() + self.assertEqual("ValidationResult.Success()", str(success)) + + def test_failure_validation_result_to_string(self): + failure = ValidationResult.failure("boom") + self.assertEqual("ValidationResult.Failure(message='boom')", str(failure)) + + def test_success_validation_result_is_success(self): + success = ValidationResult.success() + self.assertTrue(success.is_success()) + + def test_failure_validation_result_is_success(self): + failure = ValidationResult.failure("boom") + self.assertFalse(failure.is_success()) + + def test_success_validation_result_get_message(self): + success = ValidationResult.success() + self.assertTrue(success.get_message() == '') + + def test_failure_validation_result_get_message(self): + failure = ValidationResult.failure("boom") + self.assertEqual("boom", failure.get_message()) + + def test_success_validation_result_to_status(self): + success = ValidationResult.success() + self.assertEqual(ValidationResult.STATUS_SUCCESS, success.to_status()) + + def test_failure_validation_result_to_status(self): + failure = ValidationResult.failure("boom") + status = UStatus(code=UCode.INVALID_ARGUMENT, message="boom") + self.assertEqual(status, failure.to_status()) diff --git a/org_eclipse_uprotocol_tests/test_rpc/__init__.py b/org_eclipse_uprotocol_tests/test_rpc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/test_rpc/test_rpc.py b/org_eclipse_uprotocol_tests/test_rpc/test_rpc.py index ab43a1d..e239c04 100644 --- a/org_eclipse_uprotocol_tests/test_rpc/test_rpc.py +++ b/org_eclipse_uprotocol_tests/test_rpc/test_rpc.py @@ -177,15 +177,65 @@ def test_compose_happy_path(self): def test_compose_that_returns_status(self): rpc_response = RpcMapper.map_response_to_result( - WithUStatusCodeInsteadOfHappyPath().invoke_method(build_topic(), build_upayload(), build_uattributes()), Int32Value) + WithUStatusCodeInsteadOfHappyPath().invoke_method(build_topic(), build_upayload(), build_uattributes()), + Int32Value) mapped = rpc_response.map(lambda x: x.value + 5) self.assertTrue(rpc_response.isFailure()) self.assertEquals(UCode.INVALID_ARGUMENT, mapped.failureValue().code) self.assertEquals("boom", mapped.failureValue().message) + def test_compose_with_failure(self): + rpc_response = RpcMapper.map_response_to_result( + ThatCompletesWithAnException().invoke_method(build_topic(), build_upayload(), build_uattributes()), + Int32Value) + mapped = rpc_response.map(lambda x: x.value + 5) + self.assertTrue(rpc_response.isFailure()) + status = UStatus(code=UCode.UNKNOWN, message="Boom") + self.assertEquals(status, mapped.failureValue()) + + 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()), + CloudEvent) + self.assertTrue(rpc_response.isSuccess()) + self.assertEquals(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()), + CloudEvent) + self.assertTrue(rpc_response.isFailure()) + self.assertEquals(UCode.INVALID_ARGUMENT, rpc_response.failureValue().code) + self.assertEquals("boom", rpc_response.failureValue().message) + + 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()), + CloudEvent) + self.assertTrue(rpc_response.isFailure()) + self.assertEquals(UCode.UNKNOWN, rpc_response.failureValue().code) + self.assertEquals("Boom", rpc_response.failureValue().message) + + 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()), + CloudEvent) + self.assertTrue(rpc_response.isFailure()) + self.assertEquals(UCode.UNKNOWN, rpc_response.failureValue().code) + self.assertEquals("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()), + CloudEvent) + self.assertEquals(build_cloud_event(), rpc_response) + 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()), + CloudEvent) + self.assertFalse(rpc_response) diff --git a/org_eclipse_uprotocol_tests/test_transport/__init__.py b/org_eclipse_uprotocol_tests/test_transport/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/test_transport/test_builder/__init__.py b/org_eclipse_uprotocol_tests/test_transport/test_builder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/test_transport/test_validate/__init__.py b/org_eclipse_uprotocol_tests/test_transport/test_validate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/test_uri/__init__.py b/org_eclipse_uprotocol_tests/test_uri/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/test_uri/test_serializer/__init__.py b/org_eclipse_uprotocol_tests/test_uri/test_serializer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/test_uri/test_validator/test_urivalidator.py b/org_eclipse_uprotocol_tests/test_uri/test_validator/test_urivalidator.py index d2cadcf..978467d 100644 --- a/org_eclipse_uprotocol_tests/test_uri/test_validator/test_urivalidator.py +++ b/org_eclipse_uprotocol_tests/test_uri/test_validator/test_urivalidator.py @@ -349,7 +349,7 @@ def test_all_invalid_rpc_response_uris(self): @staticmethod def get_json_object(): current_directory = os.getcwd() - json_file_path = os.path.join(current_directory, "uris.json") + json_file_path = os.path.join(current_directory,"org_eclipse_uprotocol_tests","test_uri","test_validator", "uris.json") with open(json_file_path, 'r') as json_file: json_data = json.load(json_file) diff --git a/org_eclipse_uprotocol_tests/test_uuid/__init__.py b/org_eclipse_uprotocol_tests/test_uuid/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/test_uuid/test_factory/__init__.py b/org_eclipse_uprotocol_tests/test_uuid/test_factory/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/org_eclipse_uprotocol_tests/test_uuid/test_validator/__init__.py b/org_eclipse_uprotocol_tests/test_uuid/test_validator/__init__.py new file mode 100644 index 0000000..e69de29 From 923d8c2095c435b8d905217bc652873134395b91 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Tue, 28 Nov 2023 16:26:06 -0500 Subject: [PATCH 37/47] Bug fix --- .../validate/cloudeventvalidator.py | 54 ++++++++----------- .../test_uri/test_validator/__init__.py | 0 2 files changed, 22 insertions(+), 32 deletions(-) create mode 100644 org_eclipse_uprotocol_tests/test_uri/test_validator/__init__.py diff --git a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py index 4ad15b0..c6675f6 100644 --- a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py +++ b/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py @@ -29,20 +29,16 @@ from enum import Enum from cloudevents.http import CloudEvent -from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent from org_eclipse_uprotocol.proto.uattributes_pb2 import UMessageType from org_eclipse_uprotocol.proto.uri_pb2 import UUri from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator from org_eclipse_uprotocol.validation.validationresult import ValidationResult class CloudEventValidator(ABC): - """ - Validates a CloudEvent using google.grpc.UStatus
- google.grpc.UStatus - """ @staticmethod def get_validator(ce: CloudEvent): @@ -65,27 +61,28 @@ def get_validator(ce: CloudEvent): else: return Validators.PUBLISH.validator() - def validate(self, ce: CloudEvent) -> UStatus: + def validate(self, ce: CloudEvent) -> ValidationResult: """ Validate the CloudEvent. A CloudEventValidator instance is obtained according to the type attribute on the CloudEvent.

@param ce:The CloudEvent to validate. - @return:Returns a UStatus with success or a UStatus with failure containing all the + @return:Returns a ValidationResult with success or a ValidationResult with failure containing all the errors that were found. """ validation_results = [self.validate_version(ce), self.validate_id(ce), self.validate_source(ce), self.validate_type(ce), self.validate_sink(ce)] error_messages = [result.get_message() for result in validation_results if not result.is_success()] - error_message = ", ".join(error_messages) + error_message = ",".join(error_messages) if not error_message: - return ValidationResult.success().to_status() + return ValidationResult.success() else: - return ValidationResult.failure(", ".join(error_messages)) + return ValidationResult.failure(",".join(error_messages)) - def validate_version(self, ce: CloudEvent) -> ValidationResult: - return self.validate_version_spec(ce.get_attributes().get("specversion")) + @staticmethod + def validate_version(ce: CloudEvent) -> ValidationResult: + return CloudEventValidator.validate_version_spec(ce.get_attributes().get("specversion")) @staticmethod def validate_version_spec(version) -> ValidationResult: @@ -94,7 +91,8 @@ def validate_version_spec(version) -> ValidationResult: else: return ValidationResult.failure(f"Invalid CloudEvent version [{version}]. CloudEvent version must be 1.0.") - def validate_id(self, ce: CloudEvent) -> ValidationResult: + @staticmethod + def validate_id(ce: CloudEvent) -> ValidationResult: id = UCloudEvent.extract_string_value_from_attributes("id", ce) return (ValidationResult.success() if UCloudEvent.is_cloud_event_id(ce) else ValidationResult.failure( f"Invalid CloudEvent Id [{id}]. CloudEvent Id must be of type UUIDv8.")) @@ -145,15 +143,8 @@ def validate_u_entity_uri(uri: str) -> ValidationResult: @staticmethod def validate_u_entity_uri_from_UURI(uri: UUri) -> ValidationResult: - u_authority = uri.get_u_authority() - if u_authority.is_marked_remote: - if not u_authority.device: - return ValidationResult.failure("Uri is configured to be remote and is missing uAuthority device name.") + return UriValidator.validate(uri) - if not uri.get_u_entity().name: - return ValidationResult.failure("Uri is missing uSoftware Entity name.") - - return ValidationResult.success() @staticmethod def validate_topic_uri(uri: str) -> ValidationResult: @@ -175,15 +166,15 @@ def validate_topic_uri_from_UURI(uri: UUri) -> ValidationResult: @return:Returns the ValidationResult containing a success or a failure with the error message. """ validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI(uri) - if validationResult.is_success(): + if validationResult.is_failure(): return validationResult - u_resource = uri.get_u_resource() + u_resource = uri.resource if not u_resource.name: - return ValidationResult.failure("Uri is missing uResource name.") + return ValidationResult.failure("UriPart is missing uResource name.") if not u_resource.message: - return ValidationResult.failure("Uri is missing Message information.") + return ValidationResult.failure("UriPart is missing Message information.") return ValidationResult.success() @@ -211,10 +202,10 @@ def validate_rpc_topic_uri_from_uuri(uri: UUri) -> ValidationResult: return ValidationResult.failure( f"Invalid RPC uri application response topic. {validationResult.get_message()}") - u_resource = uri.get_u_resource() + u_resource = uri.resource topic = f"{u_resource.name}.{u_resource.instance}" if u_resource.instance else f"{u_resource.name}" if topic != "rpc.response": - return ValidationResult.failure("Invalid RPC uri application response topic. Uri is missing rpc.response.") + return ValidationResult.failure("Invalid RPC uri application response topic. UriPart is missing rpc.response.") return ValidationResult.success() @@ -226,15 +217,14 @@ def validate_rpc_method(uri: str) -> ValidationResult: @param uri: String UriPart to validate @return:Returns the ValidationResult containing a success or a failure with the error message. """ - Uri = LongUriSerializer().deserialize(uri) - validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI(Uri) + uuri = LongUriSerializer().deserialize(uri) + validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI(uuri) if validationResult.is_failure(): return ValidationResult.failure(f"Invalid RPC method uri. {validationResult.get_message()}") - u_resource = Uri.get_u_resource() - if not u_resource.is_rpc_method: + if not UriValidator.is_rpc_method(uuri): return ValidationResult.failure( - "Invalid RPC method uri. Uri should be the method to be called, or method from response.") + "Invalid RPC method uri. UriPart should be the method to be called, or method from response.") return ValidationResult.success() diff --git a/org_eclipse_uprotocol_tests/test_uri/test_validator/__init__.py b/org_eclipse_uprotocol_tests/test_uri/test_validator/__init__.py new file mode 100644 index 0000000..e69de29 From eb86e3972b431e2bc9ac020a5b595eb39816d4c3 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Wed, 29 Nov 2023 13:32:10 -0500 Subject: [PATCH 38/47] Add cloudevent to protobuf serializer --- .../cloudeventtoprotobufserializer.py | 68 +++++++- .../cloudevent_to_protobuf.json | 86 ++++++++++ .../test_base64protobufserializer.py | 4 +- .../test_cloudeventtoprotobufserializer.py | 157 ++++++++++++++++++ 4 files changed, 308 insertions(+), 7 deletions(-) create mode 100644 org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/cloudevent_to_protobuf.json create mode 100644 org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_cloudeventtoprotobufserializer.py diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py index 41f488c..c704ff6 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py +++ b/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py @@ -1,5 +1,4 @@ # ------------------------------------------------------------------------- - # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -21,13 +20,14 @@ # SPDX-FileType: SOURCE # SPDX-FileCopyrightText: 2023 General Motors GTO LLC # SPDX-License-Identifier: Apache-2.0 - +# # ------------------------------------------------------------------------- from cloudevents.http import CloudEvent from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializer import CloudEventSerializer +from org_eclipse_uprotocol.proto import cloudevents_pb2 # ToDo- convert cloud event to cloudevent proto @@ -39,8 +39,66 @@ class CloudEventToProtobufSerializer(CloudEventSerializer): def __init__(self): pass - def serialize(self, ce: CloudEvent) -> bytes: - pass + def serialize(self, http_event: CloudEvent) -> bytes: + proto_event = cloudevents_pb2.CloudEvent() + # Set required attributes + proto_event.id = http_event['id'] + proto_event.source = http_event['source'] + proto_event.spec_version = http_event['specversion'] + proto_event.type = http_event['type'] + + + # Set optional & extension attributes + for key, value in http_event.get_attributes().items(): + if key not in ['specversion', 'id', 'source', 'type', 'data']: + attribute_value = proto_event.attributes[key] + if isinstance(value, bool): + attribute_value.ce_boolean = value + elif isinstance(value, int): + attribute_value.ce_integer = value + elif isinstance(value, str): + attribute_value.ce_string = value + elif isinstance(value, bytes): + attribute_value.ce_bytes = value + + # Set data + data = http_event.get_data() + if isinstance(data, bytes): + proto_event.binary_data = data + elif isinstance(data, str): + proto_event.text_data = data + + return proto_event.SerializeToString() def deserialize(self, bytes_data: bytes) -> CloudEvent: - pass + proto_event = cloudevents_pb2.CloudEvent() + proto_event.ParseFromString(bytes_data) + + json_attributes = {"id": proto_event.id, "source": proto_event.source, "type": proto_event.type, + "specversion": proto_event.spec_version} + + + + # Set optional & extension attributes + for key in proto_event.attributes: + if key not in ['specversion', 'id', 'source', 'type', 'data']: + attribute_value = proto_event.attributes[key] + if attribute_value.HasField('ce_boolean'): + json_attributes[key] = attribute_value.ce_boolean + elif attribute_value.HasField('ce_integer'): + json_attributes[key] = attribute_value.ce_integer + elif attribute_value.HasField('ce_string'): + json_attributes[key] = attribute_value.ce_string + elif attribute_value.HasField('ce_bytes'): + json_attributes[key] = attribute_value.ce_bytes + + # Set data + data = bytearray() + if proto_event.HasField('binary_data'): + data = proto_event.binary_data + elif proto_event.HasField('text_data'): + data = proto_event.text_data + + cloud_event = CloudEvent(json_attributes, data) + + return cloud_event diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/cloudevent_to_protobuf.json b/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/cloudevent_to_protobuf.json new file mode 100644 index 0000000..bd10a51 --- /dev/null +++ b/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/cloudevent_to_protobuf.json @@ -0,0 +1,86 @@ +[ + { + "serialized_ce": "CgZ0ZXN0bWUSIi9ib2R5LmFjY2Vzcy8vZG9vci5mcm9udF9sZWZ0I0Rvb3IaAzEuMCIGcHViLnYxKhsKCHByaW9yaXR5Eg8aDVVQUklPUklUWV9DUzEqGQoFdG9rZW4SEBoOc29tZU9BdXRoVG9rZW4qKgoEdGltZRIiGiAyMDIzLTExLTI5VDE4OjE0OjQyLjQ2NDQwMSswMDowMCoSCgRoYXNoEgoaCHNvbWVoYXNoKgkKA3R0bBICEAMyZQowdHlwZS5nb29nbGVhcGlzLmNvbS9pby5jbG91ZGV2ZW50cy52MS5DbG91ZEV2ZW50EjEKBWhlbGxvEhNodHRwczovL2V4YW1wbGUuY29tGgMxLjAiDGV4YW1wbGUuZGVtb0IA", + "id": "testme", + "specversion": "1.0", + "source": "/body.access//door.front_left#Door", + "type": "pub.v1", + "priority": "UPRIORITY_CS1", + "ttl": 3, + "hash": "somehash", + "token": "someOAuthToken", + "test_detail": "test_create_base_cloud_event" + }, + { + "serialized_ce": "CgZ0ZXN0bWUSIi9ib2R5LmFjY2Vzcy8vZG9vci5mcm9udF9sZWZ0I0Rvb3IaAzEuMCIGcHViLnYxKhsKCHByaW9yaXR5Eg8aDVVQUklPUklUWV9DUzEqEgoEaGFzaBIKGghzb21laGFzaCoJCgN0dGwSAhADKisKD2RhdGFjb250ZW50dHlwZRIYGhZhcHBsaWNhdGlvbi94LXByb3RvYnVmKhkKBXRva2VuEhAaDnNvbWVPQXV0aFRva2VuKioKBHRpbWUSIhogMjAyMy0xMS0yOVQxODoxNDo0Mi40NjUzOTYrMDA6MDAqQAoKZGF0YXNjaGVtYRIyGjB0eXBlLmdvb2dsZWFwaXMuY29tL2lvLmNsb3VkZXZlbnRzLnYxLkNsb3VkRXZlbnQyZQowdHlwZS5nb29nbGVhcGlzLmNvbS9pby5jbG91ZGV2ZW50cy52MS5DbG91ZEV2ZW50EjEKBWhlbGxvEhNodHRwczovL2V4YW1wbGUuY29tGgMxLjAiDGV4YW1wbGUuZGVtb0IA", + "id": "testme", + "specversion": "1.0", + "source": "/body.access//door.front_left#Door", + "type": "pub.v1", + "priority": "UPRIORITY_CS1", + "ttl": 3, + "hash": "somehash", + "token": "someOAuthToken", + "dataschema": "type.googleapis.com/io.cloudevents.v1.CloudEvent", + "datacontenttype": "application/x-protobuf", + "test_detail": "test_create_base_cloud_event_with_datacontenttype_and_schema" + }, + { + "serialized_ce": "CgZ0ZXN0bWUSIi9ib2R5LmFjY2Vzcy8vZG9vci5mcm9udF9sZWZ0I0Rvb3IaAzEuMCIGcHViLnYxKioKBHRpbWUSIhogMjAyMy0xMS0yOVQxODoxNDo0Mi40NjcxMjQrMDA6MDAyZQowdHlwZS5nb29nbGVhcGlzLmNvbS9pby5jbG91ZGV2ZW50cy52MS5DbG91ZEV2ZW50EjEKBWhlbGxvEhNodHRwczovL2V4YW1wbGUuY29tGgMxLjAiDGV4YW1wbGUuZGVtb0IA", + "id": "testme", + "specversion": "1.0", + "source": "/body.access//door.front_left#Door", + "type": "pub.v1", + "test_detail": "test_create_base_cloud_event_without_attributes" + }, + { + "serialized_ce": "CiQwMThiZjliNi01YzFjLTgwMDAtOTgyZi04YzcxMjU2NDY5Y2MSIi9ib2R5LmFjY2Vzcy8vZG9vci5mcm9udF9sZWZ0I0Rvb3IaAzEuMCIGcHViLnYxKhsKCHByaW9yaXR5Eg8aDVVQUklPUklUWV9DUzEqKgoEdGltZRIiGiAyMDIzLTExLTI5VDE4OjE0OjQyLjQ2NzEyNCswMDowMCoSCgRoYXNoEgoaCHNvbWVoYXNoKgkKA3R0bBICEAMyZQowdHlwZS5nb29nbGVhcGlzLmNvbS9pby5jbG91ZGV2ZW50cy52MS5DbG91ZEV2ZW50EjEKBWhlbGxvEhNodHRwczovL2V4YW1wbGUuY29tGgMxLjAiDGV4YW1wbGUuZGVtb0IA", + "id": "018bf9b6-5c1c-8000-982f-8c71256469cc", + "specversion": "1.0", + "source": "/body.access//door.front_left#Door", + "type": "pub.v1", + "priority": "UPRIORITY_CS1", + "ttl": 3, + "hash": "somehash", + "test_detail": "test_create_publish_cloud_event" + }, + { + "serialized_ce": "CiQwMThiZjliOS01YWQ5LTgwMDAtODNlNS02YTQ4NDE3M2Q2OWYSIi9ib2R5LmFjY2Vzcy8vZG9vci5mcm9udF9sZWZ0I0Rvb3IaAzEuMCIGcHViLnYxKhsKCHByaW9yaXR5Eg8aDVVQUklPUklUWV9DUzIqLAoEc2luaxIkGiIvYm9keS5hY2Nlc3MvL2Rvb3IuZnJvbnRfbGVmdCNEb29yKioKBHRpbWUSIhogMjAyMy0xMS0yOVQxODoxNDo0Mi40NjcxMjQrMDA6MDAqEgoEaGFzaBIKGghzb21laGFzaCoJCgN0dGwSAhADMmUKMHR5cGUuZ29vZ2xlYXBpcy5jb20vaW8uY2xvdWRldmVudHMudjEuQ2xvdWRFdmVudBIxCgVoZWxsbxITaHR0cHM6Ly9leGFtcGxlLmNvbRoDMS4wIgxleGFtcGxlLmRlbW9CAA==", + "id": "018bf9b9-5ad9-8000-83e5-6a484173d69f", + "specversion": "1.0", + "source": "/body.access//door.front_left#Door", + "type": "pub.v1", + "priority": "UPRIORITY_CS2", + "ttl": 3, + "hash": "somehash", + "sink": "/body.access//door.front_left#Door", + "test_detail": "test_create_notification_cloud_event" + }, + { + "serialized_ce": "CiQwMThiZjliZC0zYTNiLTgwMDAtYThjYi1lOTU1NjgxZTg1MzASIi9ib2R5LmFjY2Vzcy8vZG9vci5mcm9udF9sZWZ0I0Rvb3IaAzEuMCIGcmVzLnYxKhsKCHByaW9yaXR5Eg8aDVVQUklPUklUWV9DUzIqLAoEc2luaxIkGiIvYm9keS5hY2Nlc3MvL2Rvb3IuZnJvbnRfbGVmdCNEb29yKhIKBGhhc2gSChoIc29tZWhhc2gqKgoEdGltZRIiGiAyMDIzLTExLTI5VDE4OjE0OjQyLjQ2ODE0MSswMDowMCopCgVyZXFpZBIgGh5yZXF1ZXN0SWRGcm9tUmVxdWVzdENsb3VkRXZlbnQqCQoDdHRsEgIQAzJlCjB0eXBlLmdvb2dsZWFwaXMuY29tL2lvLmNsb3VkZXZlbnRzLnYxLkNsb3VkRXZlbnQSMQoFaGVsbG8SE2h0dHBzOi8vZXhhbXBsZS5jb20aAzEuMCIMZXhhbXBsZS5kZW1vQgA=", + "id": "018bf9bd-3a3b-8000-a8cb-e955681e8530", + "specversion": "1.0", + "source": "/body.access//door.front_left#Door", + "type": "res.v1", + "priority": "UPRIORITY_CS2", + "ttl": 3, + "hash": "somehash", + "reqid": "requestIdFromRequestCloudEvent", + "sink": "/body.access//door.front_left#Door", + "test_detail": "test_create_response_cloud_event_originating_from_local_use" + }, + { + "serialized_ce": "CiQwMThiZjliZi0yN2U0LTgwMDAtOGRkMC0yNjI3N2M3ODc5MGUSIi9ib2R5LmFjY2Vzcy8vZG9vci5mcm9udF9sZWZ0I0Rvb3IaAzEuMCIGcmVzLnYxKhsKCHByaW9yaXR5Eg8aDVVQUklPUklUWV9DUzIqEgoEaGFzaBIKGghzb21laGFzaCoJCgN0dGwSAhADKhAKCmNvbW1zdGF0dXMSAhADKiwKBHNpbmsSJBoiL2JvZHkuYWNjZXNzLy9kb29yLmZyb250X2xlZnQjRG9vcioqCgR0aW1lEiIaIDIwMjMtMTEtMjlUMTg6MTQ6NDIuNDY4MTQxKzAwOjAwKikKBXJlcWlkEiAaHnJlcXVlc3RJZEZyb21SZXF1ZXN0Q2xvdWRFdmVudDIrCil0eXBlLmdvb2dsZWFwaXMuY29tL2dvb2dsZS5wcm90b2J1Zi5FbXB0eQ==", + "id": "018bf9bf-27e4-8000-8dd0-26277c78790e", + "specversion": "1.0", + "source": "/body.access//door.front_left#Door", + "type": "res.v1", + "priority": "UPRIORITY_CS2", + "ttl": 3, + "hash": "somehash", + "reqid": "requestIdFromRequestCloudEvent", + "sink": "/body.access//door.front_left#Door", + "commstatus": 3, + "test_detail": "test_create_a_failed_response_cloud_event" + } +] \ No newline at end of file diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_base64protobufserializer.py b/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_base64protobufserializer.py index 1a798f7..4845972 100644 --- a/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_base64protobufserializer.py +++ b/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_base64protobufserializer.py @@ -39,10 +39,10 @@ def test_deserialize_bytes_to_string(self): ce = CloudEventFactory.build_base_cloud_event("hello", "http://localhost", bytearray(), "", UCloudEventAttributesBuilder().build(), "example.vertx") ce.__delitem__("time") - bytes_data = CloudEventSerializers.JSON.serializer().serialize(ce) + bytes_data = CloudEventSerializers.PROTOBUF.serializer().serialize(ce) payload = Base64ProtobufSerializer().deserialize(bytes_data) self.assertEquals( - "eyJzcGVjdmVyc2lvbiI6ICIxLjAiLCAiaWQiOiAiaGVsbG8iLCAic291cmNlIjogImh0dHA6Ly9sb2NhbGhvc3QiLCAidHlwZSI6ICJleGFtcGxlLnZlcnR4IiwgImRhdGFfYmFzZTY0IjogIiJ9", + "CgVoZWxsbxIQaHR0cDovL2xvY2FsaG9zdBoDMS4wIg1leGFtcGxlLnZlcnR4", payload) def test_deserialize_bytes_to_string_when_bytes_is_null(self): diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_cloudeventtoprotobufserializer.py b/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_cloudeventtoprotobufserializer.py new file mode 100644 index 0000000..a0558e8 --- /dev/null +++ b/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_cloudeventtoprotobufserializer.py @@ -0,0 +1,157 @@ +# ------------------------------------------------------------------------- +# 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 json +import os +import unittest + +from cloudevents.http import CloudEvent as ApacheCloudEvent +from google.protobuf import any_pb2 + +from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from org_eclipse_uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory +from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent +from org_eclipse_uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer +from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializers import CloudEventSerializers +from org_eclipse_uprotocol.cloudevent.serialize.cloudeventtoprotobufserializer import CloudEventToProtobufSerializer +from org_eclipse_uprotocol.proto.cloudevents_pb2 import CloudEvent +from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority, UMessageType +from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UEntity, UResource +from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer + +serializer = CloudEventSerializers.PROTOBUF.serializer() + + +def build_uuri_for_test(): + uri = UUri(entity=UEntity(name="body.access"), + resource=UResource(name="door", instance="front_left", message="Door")) + return LongUriSerializer().serialize(uri) + + +def build_proto_payload_for_test(): + ce_proto = CloudEvent(spec_version="1.0", source="https://example.com", id="hello", type="example.demo", + proto_data=any_pb2.Any(), + attributes={"ttl": CloudEvent.CloudEventAttributeValue(ce_string="3")}) + + any_obj = any_pb2.Any() + any_obj.Pack(ce_proto) + return any_obj + + +def get_json_object(): + current_directory = os.getcwd() + json_file_path = os.path.join(current_directory, "org_eclipse_uprotocol_tests","test_cloudevent", + "test_serialize","cloudevent_to_protobuf.json") + with open(json_file_path, 'r') as json_file: + json_data = json.load(json_file) + + return json_data + + +class TestCloudEventToProtobufSerializer(unittest.TestCase): + + def test_all_cloud_events_from_json(self): + cloudevents = get_json_object() + for ce_json in cloudevents: + bytes_ce = Base64ProtobufSerializer().serialize(ce_json['serialized_ce']) + cloudevent = CloudEventToProtobufSerializer().deserialize(bytes_ce) + self.assertEqual(UCloudEvent.get_id(cloudevent), ce_json['id']) + self.assertEqual(UCloudEvent.get_specversion(cloudevent), ce_json['specversion']) + if 'source' in ce_json: + self.assertEqual(UCloudEvent.get_source(cloudevent), ce_json['source']) + if 'sink' in ce_json: + self.assertEqual(UCloudEvent.get_sink(cloudevent), ce_json['sink']) + if 'type' in ce_json: + self.assertEqual(UCloudEvent.get_type(cloudevent), ce_json['type']) + if 'priority' in ce_json: + self.assertEqual(UCloudEvent.get_priority(cloudevent), ce_json['priority']) + if 'ttl' in ce_json: + self.assertEqual(UCloudEvent.get_ttl(cloudevent), ce_json['ttl']) + if 'hash' in ce_json: + self.assertEqual(UCloudEvent.get_hash(cloudevent), ce_json['hash']) + if 'token' in ce_json: + self.assertEqual(UCloudEvent.get_token(cloudevent), ce_json['token']) + if 'dataschema' in ce_json: + self.assertEqual(UCloudEvent.get_data_schema(cloudevent), ce_json['dataschema']) + if 'datacontenttype' in ce_json: + self.assertEqual(UCloudEvent.get_data_content_type(cloudevent), ce_json['datacontenttype']) + if 'commstatus' in ce_json: + self.assertEqual(UCloudEvent.get_communication_status(cloudevent), ce_json['commstatus']) + + def test_serialize_and_deserialize_cloud_event_to_protobuf(self): + source = build_uuri_for_test() + proto_payload = build_proto_payload_for_test() + # additional attributes + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( + UPriority.UPRIORITY_CS0).with_ttl(3).build() + + # build the cloud event + cloud_event = CloudEventFactory.build_base_cloud_event("hello", "/body.access/1/door.front_left", + proto_payload.SerializeToString(), + proto_payload.type_url, u_cloud_event_attributes, + UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_PUBLISH)) + + cloud_event.__delitem__("time") + serialized_data = serializer.serialize(cloud_event) + + deserialized_data = serializer.deserialize(serialized_data) + deserialized_data.__delitem__("time") + self.assertEqual(cloud_event, deserialized_data) + + def test_serialize_two_different_cloud_event_are_not_the_same(self): + proto_payload = build_proto_payload_for_test() + json_attributes1 = {"id": "hello", "source": "/body.access/1/door.front_left", "type": "pub.v1"} + cloud_event1 = ApacheCloudEvent(json_attributes1, proto_payload.SerializeToString()) + cloud_event1.__setitem__('datacontenttype', 'application/protobuf') + cloud_event1.__setitem__('dataschema', proto_payload.type_url) + cloud_event1.__delitem__("time") + + json_attributes2 = {"source": "/body.access/1/door.front_left", "type": "file.v1"} + cloud_event2 = ApacheCloudEvent(json_attributes2, proto_payload.SerializeToString()) + cloud_event2.__delitem__("time") + serialized1 = serializer.serialize(cloud_event1) + serialized2 = serializer.serialize(cloud_event2) + self.assertNotEqual(serialized1, serialized2) + + def test_double_serialization_protobuf_when_creating_cloud_event_with_factory_methods(self): + proto_payload = build_proto_payload_for_test() + source = build_uuri_for_test() + # additional attributes + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( + UPriority.UPRIORITY_CS1).with_ttl(3).with_token("someOAuthToken").build() + + # build the cloud event + cloud_event = CloudEventFactory.build_base_cloud_event("testme", source, proto_payload.SerializeToString(), + proto_payload.type_url, u_cloud_event_attributes, + UCloudEvent.get_event_type( + UMessageType.UMESSAGE_TYPE_PUBLISH)) + cloud_event.__delitem__("time") + + serialized_data = serializer.serialize(cloud_event) + deserialized_data = serializer.deserialize(serialized_data) + deserialized_data.__delitem__("time") + self.assertEqual(cloud_event, deserialized_data) + From 0844fb5ab9696db70a3fdc888093b2a68f473015 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 30 Nov 2023 14:27:47 -0500 Subject: [PATCH 39/47] Add Readme files --- README.adoc | 94 ++++++++----------- org_eclipse_uprotocol/cloudevent/README.adoc | 67 +++++++++++++ .../cloudevent/factory/cloudeventfactory.py | 2 +- org_eclipse_uprotocol/rpc/README.adoc | 8 ++ org_eclipse_uprotocol/transport/README.adoc | 7 ++ org_eclipse_uprotocol/uri/README.adoc | 45 +++++++++ org_eclipse_uprotocol/uuid/README.adoc | 33 +++++++ 7 files changed, 202 insertions(+), 54 deletions(-) create mode 100644 org_eclipse_uprotocol/cloudevent/README.adoc create mode 100644 org_eclipse_uprotocol/rpc/README.adoc create mode 100644 org_eclipse_uprotocol/transport/README.adoc create mode 100644 org_eclipse_uprotocol/uri/README.adoc create mode 100644 org_eclipse_uprotocol/uuid/README.adoc diff --git a/README.adoc b/README.adoc index 6937853..9afe348 100644 --- a/README.adoc +++ b/README.adoc @@ -1,83 +1,71 @@ -image:https://github.com/eclipse-uprotocol/.github/raw/main/logo/uprotocol_logo.png[alt=uProtocol,640] - -image:https://img.shields.io/badge/License-Apache%202.0-blue.svg[License,link=https://opensource.org/licenses/Apache-2.0] - -= Eclipse uProtocol Python SDK += Eclipse uProtocol Python Library :toc: -*_IMPORTANT NOTE:_ This project is under active development* - == Overview -The main object of this module is to enable constructing and deconstructing uProtocol CloudEvents. +This library implements the https://github.com/eclipse-uprotocol/uprotocol-spec/blob/main/languages.adoc[uProtocol Language Specific Library Requirements] for Python defined in https://github.com/eclipse-uprotocol/uprotocol-spec/tree/main[uProtocol Specifications]. The library is organized into 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. -The core module contains functional factory methods for creating CloudEvents as well as functional factory methods that make it more intuitive to create URIs that are used to configure source and sink (destination) elements in the uProtocol CloudEvents. +The module contains the factory methods, serializers, and validators for all data types defined in the specifications, and any data models that either haven't or couldn't be defined in uprotocol-core-api yet (ex. UPayload) This library fits into the big picture of the uProtocol SDK as seen in <> below. -This module contains the data model structures as well as core functionality for building uProtocol CloudEvents and URIs for sink and source attributes. +.uProtocol SDK +image:https://raw.githubusercontent.com/eclipse-uprotocol/uprotocol-spec/main/uprotocol_sdk.drawio.svg[#uprotocol-sdk,width=100%,align="center"] -The SDKs are then used by the code generators to auto-populate service stubs with generated code that builds CloudEvents. For more information on auto-generating service stubs, please refer to http://github.com/eclipse-uprotocol/uprotocol[uProtocol Main Project] == Getting Started - -=== Requirements - -- Python 3.8 or above -- pip - -=== Setup SDK local repository and install - -``` -$ git clone https://github.com/eclipse-uprotocol/uprotocol-sdk-python.git -$ cd uprotocol-sdk-python +=== Importing the sdk + +Setup SDK local repository and install +[source] +---- +$ git clone https://github.com/eclipse-uprotocol/uprotocol-python.git +$ cd uprotocol-python $ pip install . +---- +*This will install the uprotocol-python, making its classes and modules available for import in your python code.* -``` -*This will install the uprotocol-sdk-python, making its classes and modules available for import in your python code.* - -=== UriFactory - -Matches the uProtocol URI Format. and is used to define source and sink (destination) attributes of uProtocol CloudEvents. -The factory builds URIs. - -URI is used as a method to uniquely identify devices, services, and resources on the network. - -*An Uri is built from the following elements:* -* *UAuthority* - represents the device and domain of the software, the deployment. You can specify local or remote options. -* *UEntity* - The Software Entity defines the software name and version. -* *UResource* - The resource of the software can be a service name, and instance in the service and the name of the protobuf IDL message. +=== Using The Sdk -==== UAuthority +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: -An Authority consists of a device and a domain per uProtocol URI format. +.Package Folders +[#pkg-folders,width=100%,cols="20%,80%",options="header"] +|=== -An Authority represents the deployment location of a specific Software Entity. +| Folder | Purpose -==== UEntity - uE +| `*builder*` or `*factory*` +| Contains factory methods for creating uProtocol data types -An Software Entity is a piece of software deployed somewhere on a device. The uE is used in the source and sink parts of communicating software. +| `*serializer*` +| Contains serializers to convert the objects into byte or string form representation of said object -A uE that *publishes* events is a *Service* role. +| `*validator*` +| Contains validators to validate the data types and report errors if the objects are missing or incorrect -A uE that *consumes* events is an *Application* role. +|=== -A uE may combine bother Service and Application roles. +.SDK Packages +[#sdk-packages,width=100%,cols="20%,80%",options="header"] +|=== -==== UResource +| Package | Purpose -A service API - defined in the uE - has Resources and Methods. Both of these are represented by the UResource class. +| link:org_eclipse_uprotocol/uri/README.adoc[`*uuri*`] +| Uniform Resource Identifier (RFC3986), how uProtocol addresses things (devices, software, methods, topics, etc...) on the network -An UResource is something that can be manipulated/controlled/exposed by a service. -Resources are unique when prepended with UAuthority that represents the device and Software Entity that represents the service. +| link:org_eclipse_uprotocol/uuid/README.adoc[`*uuid*`] +| Identifier used to uniquely identify (and timestamp) messages that are sent -An Resource represents a resource from a Service such as "door" and an optional specific instance such as "front_left". -In addition, it can optionally contain the name of the resource Message type, such as "Door". +| link:org_eclipse_uprotocol/utransport/README.adoc[`*utransport*`] +| Interface and data model declaration used for bidirectional point-2-point communication between uEs. This interface is then implemented by https://github.com/eclipse-uprotocol/uprotocol-spec/blob/main/ulink.adoc[ulink] libraries for a given underlining transport (ex. Binder, MQTT, Zenoh, SOME/IP, DDS, HTTP, etc…​) -The Message type matches the protobuf service IDL that defines structured data types. A message is a data structure type used to define data that is passed in events and rpc methods. +| link:org_eclipse_uprotocol/cloudevent/README.adoc[`*cloudevent*`] +| Common way to represent uProtocol messages using CloudEvent data model used by some transports (ex. MQTT, HTTP, etc…​) -=== CloudEventFactory -Factory class that builds the various types of CloudEvents for uProtocol (publish, notification, request, response) +|=== +NOTE: Please visit the READMEs in <> for examples of how to use the different data types and their factories, validators, and serializers. diff --git a/org_eclipse_uprotocol/cloudevent/README.adoc b/org_eclipse_uprotocol/cloudevent/README.adoc new file mode 100644 index 0000000..a3b932a --- /dev/null +++ b/org_eclipse_uprotocol/cloudevent/README.adoc @@ -0,0 +1,67 @@ += uProtocol CloudEvents +:toc: +:sectnums: + + +== Overview + +https://github.com/eclipse-uprotocol/uprotocol-spec/blob/main/up-l1/cloudevents.adoc[uProtocol CloudEvents] is a common message envelope that could be used to carry way to represent uProtocol transport layer information `UUri` (source), `UPayload`, and `UAttributes`. `CloudEvents` are used by a number of Device-2-Cloud and Cloud-2-Device based transports such as MQTT and HTTP, however it could also be used by any transport (ex. Binder). + + +=== CloudEventFactory +Factory class that builds the various types of CloudEvents for uProtocol (publish, notification, request, response) + +== Examples + +=== Building an uuri +[source,python] +---- + uri = UUri(entity=UEntity(name="body.access"), + resource=UResource(name="door", instance="front_left", message="Door")) + source= LongUriSerializer().serialize(uri) +---- + +== Build proto payload +[source,python] + +---- +ce_proto = CloudEvent(spec_version="1.0", source="https://example.com", id="hello", type="example.demo", + proto_data=any_pb2.Any()) + +any_obj = any_pb2.Any() +any_obj.Pack(ce_proto) +proto_payload = any_obj + +---- + +== Build UCloudEvent Attributes +[source,python] + +---- +u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( +UPriority.UPRIORITY_CS1).with_ttl(3).build() + +---- + +== Build publish cloud event +[source,python] + +---- +cloud_event = CloudEventFactory.publish(source, proto_payload, u_cloud_event_attributes) +# test all attributes +assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) +assertIsNotNone(UCloudEvent.get_id(cloud_event)) +assertEqual(source, UCloudEvent.get_source(cloud_event)) +assertEqual(UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + UCloudEvent.get_type(cloud_event)) +assertNotIn("sink", cloud_event.get_attributes()) +assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) +assertEqual(UPriority.Name(UPriority.UPRIORITY_CS1), UCloudEvent.get_priority(cloud_event)) +assertEqual(3, UCloudEvent.get_ttl(cloud_event)) +assertEqual(proto_payload.SerializeToString(), cloud_event.get_data()) + +---- + + + + diff --git a/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py b/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py index 603804a..5fbff91 100644 --- a/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py +++ b/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py @@ -39,7 +39,7 @@ # A factory is a part of the software has methods to generate concrete objects, usually of the same type or # interface. CloudEvents is a specification for describing events in a common way. We will use CloudEvents to # formulate all kinds of events (messages) that will be sent to and from devices. The CloudEvent factory knows how -# to generate CloudEvents of the 4 core types: req.v1, res.v1, pub.v1, and file.v1 +# to generate CloudEvents of the 4 core types: req.v1, res.v1 and pub.v1 class CloudEventFactory: PROTOBUF_CONTENT_TYPE = "application/x-protobuf" diff --git a/org_eclipse_uprotocol/rpc/README.adoc b/org_eclipse_uprotocol/rpc/README.adoc new file mode 100644 index 0000000..eb3a6cb --- /dev/null +++ b/org_eclipse_uprotocol/rpc/README.adoc @@ -0,0 +1,8 @@ += uProtocol Rpc Interfaces +:toc: +:sectnums: + +== Overview + +The following module declares the https://github.com/eclipse-uprotocol/uprotocol-spec/blob/main/up-l2/rpcclient.adoc[RpcClient interface] defined in uProtocol specification. The interface is used by code generators to build client and service stubs for uServices. + diff --git a/org_eclipse_uprotocol/transport/README.adoc b/org_eclipse_uprotocol/transport/README.adoc new file mode 100644 index 0000000..3f2c834 --- /dev/null +++ b/org_eclipse_uprotocol/transport/README.adoc @@ -0,0 +1,7 @@ += uProtocol Transport Interface & Data Model +:toc: +:sectnums: +:source-highlighter: prettify + +== Overview +The purpose of this module is to provide the Python implementation of https://github.com/eclipse-uprotocol/uprotocol-spec/blob/main/up-l1/README.adoc[uTransport API & Data Model]. The transport API is used by all uE developers to send and receive messages across any transport. The interface is to be implemented by communication transport developers (i.e. developing a uTransport for SOME/IP, DDS, Zenoh, MQTT, etc...). diff --git a/org_eclipse_uprotocol/uri/README.adoc b/org_eclipse_uprotocol/uri/README.adoc new file mode 100644 index 0000000..579a6f9 --- /dev/null +++ b/org_eclipse_uprotocol/uri/README.adoc @@ -0,0 +1,45 @@ += uProtocol URI (UUri) +:toc: +:sectnums: + + +== Overview + +The following folder contains everything but the data model for UUri (builders, serializers, validators, etc...) per https://github.com/eclipse-uprotocol/uprotocol-spec/blob/main/basics/uri.adoc[uProtocol URI Specifications]. +The data model is defined in https://github.com/eclipse-uprotocol/uprotocol-core-api/blob/main/src/main/proto/uri.proto[uri.proto] and included as a dependency for this project. + +IMPORTANT: For more details about the data model, various formats (object, long, micro) and their uses, please refer to https://github.com/eclipse-uprotocol/uprotocol-spec/blob/main/basics/uri.adoc[uProtocol URI Specifications]. + + +== Using the SDK + +When building UUri, you can choose to populate it with only names, only numbers, or both (resolved). When you should use each is described the best practice section of https://github.com/eclipse-uprotocol/uprotocol-spec/blob/main/basics/uri.adoc[uProtocol URI Specifications]. + +=== Building an RPC Method +[,python] +---- +uri: UUri = UUri( + authority=UAuthority(name="MyDevice", ip=bytes(socket.inet_pton(socket.AF_INET, "192.168.1.100")), + id=bytes.fromhex("3GTU2NEC8HG403825")), + entity=UEntity(name="HartleyService", id=10203, version_major=1), + resource=UResourceBuilder.for_rpc_request("Raise", 10)) +---- + +=== Validating +[,python] +---- +status : ValidationResult = UriValidator.validate_rpc_method(uuri) +assertTrue(status.is_success()); +---- + +=== Serializing & Deserializing +[,python] +---- + uri = .../* UUri example above */ + micro = MicroUriSerializer().serialize(uri) + long = LongUriSerializer().serialize(uri) + deserialized_micro_uuri = MicroUriSerializer().deserialize(uri) + deserialized_long_uuri = LongUriSerializer().deserialize(uri) + uri2 = UriSerializer.build_resolved(long, micro) + +---- \ No newline at end of file diff --git a/org_eclipse_uprotocol/uuid/README.adoc b/org_eclipse_uprotocol/uuid/README.adoc new file mode 100644 index 0000000..a598900 --- /dev/null +++ b/org_eclipse_uprotocol/uuid/README.adoc @@ -0,0 +1,33 @@ += uProtocol UUID +:toc: +:sectnums: + +== Overview + +Implementation of https://github.com/eclipse-uprotocol/uprotocol-spec/blob/main/basics/uuid.adoc[uProtocol UUID specifications]. + +== Examples + +[source,python] +---- + uuid = Factories.UPROTOCOL.create() + version = UUIDUtils.getVersion(uuid) + time = UUIDUtils.getTime(uuid) + bytes_uuid = MicroUuidSerializer.instance().serialize(uuid) + str_uuid = LongUuidSerializer.instance().serialize(uuid) + + assertTrue(UUIDUtils.isUProtocol(uuid)) + assertTrue(UUIDUtils.isuuid(uuid)) + assertFalse(UUIDUtils.isUuidv6(uuid)) + assertTrue(version) + assertTrue(time) + assertGreater(len(bytes_uuid), 0) + assertFalse(str_uuid.isspace()) + + uuid1 = MicroUuidSerializer.instance().deserialize(bytes_data) + uuid2 = LongUuidSerializer.instance().deserialize(uuid_string) + assertNotEqual(uuid1, UUID()) + assertNotEqual(uuid2, UUID()) + assertEqual(uuid, uuid1) + assertEqual(uuid, uuid2) +---- \ No newline at end of file From fbf109495422064f1395fde5d5fb682822319d37 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 30 Nov 2023 14:31:40 -0500 Subject: [PATCH 40/47] Update Readme file --- README.adoc | 2 +- org_eclipse_uprotocol/cloudevent/README.adoc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.adoc b/README.adoc index 9afe348..3d7a747 100644 --- a/README.adoc +++ b/README.adoc @@ -60,7 +60,7 @@ The SDK is broken up into different packages that are described in < Date: Thu, 30 Nov 2023 14:44:45 -0500 Subject: [PATCH 41/47] Add rpc read me reference in main read me doc --- README.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 3d7a747..754c301 100644 --- a/README.adoc +++ b/README.adoc @@ -56,10 +56,12 @@ The SDK is broken up into different packages that are described in < Date: Thu, 30 Nov 2023 15:48:50 -0500 Subject: [PATCH 42/47] Adding .vscode to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index de40452..844ec77 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ **/build/ **/dist/ **/*.egg-info/ -**/.DS_Store \ No newline at end of file +**/.DS_Store +.vscode From 728877c0006771bfd75250210d6a9b64e055f581 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 30 Nov 2023 16:54:23 -0500 Subject: [PATCH 43/47] Rename folders --- {org_eclipse_uprotocol => tests}/__init__.py | 0 .../test_cloudevent}/__init__.py | 0 .../test_datamodel}/__init__.py | 0 .../test_ucloudeventattributes.py | 4 ++-- .../test_cloudevent/test_factory}/__init__.py | 0 .../test_factory/cloudevent.json | 0 .../test_factory/test_cloudeventfactory.py | 22 ++++++++--------- .../test_serialize}/__init__.py | 0 .../cloudevent_to_protobuf.json | 0 .../test_base64protobufserializer.py | 8 +++---- .../test_cloudeventtojsonserializer.py | 14 +++++------ .../test_cloudeventtoprotobufserializer.py | 22 ++++++++--------- .../test_validator}/__init__.py | 0 .../test_cloudeventvalidator.py | 24 +++++++++---------- .../test_validator/test_validationresult.py | 4 ++-- .../proto => tests/test_rpc}/__init__.py | 0 .../test_rpc/test_calloptions.py | 2 +- .../test_rpc/test_rpc.py | 20 ++++++++-------- .../test_rpc/test_rpcresult.py | 4 ++-- .../rpc => tests/test_transport}/__init__.py | 0 .../test_transport/test_builder}/__init__.py | 0 .../test_builder/test_uattributesbuilder.py | 10 ++++---- .../test_transport/test_validate}/__init__.py | 0 .../test_uattributesvalidator.py | 20 ++++++++-------- .../validate => tests/test_uri}/__init__.py | 0 .../test_uri/test_serializer}/__init__.py | 0 .../test_serializer/test_longuriserializer.py | 8 +++---- .../test_microuriserializer.py | 8 +++---- .../test_serializer/test_uriserializer.py | 8 +++---- .../test_uri/test_validator}/__init__.py | 0 .../test_validator/test_urivalidator.py | 10 ++++---- .../test_uri/test_validator/uris.json | 0 .../test_uuid}/__init__.py | 0 .../test_uuid/test_factory}/__init__.py | 0 .../test_factory/test_uuidfactory.py | 10 ++++---- .../test_uuid/test_validator}/__init__.py | 0 .../test_validator/test_uuidvalidator.py | 16 ++++++------- .../uuid/serializer => uprotocol}/__init__.py | 0 .../cloudevent/README.adoc | 0 .../cloudevent}/__init__.py | 0 .../cloudevent/datamodel}/__init__.py | 0 .../datamodel/ucloudeventattributes.py | 4 ++-- .../cloudevent/factory}/__init__.py | 0 .../cloudevent/factory/cloudeventfactory.py | 10 ++++---- .../cloudevent/factory/ucloudevent.py | 8 +++---- .../cloudevent/serialize}/__init__.py | 0 .../serialize/base64protobufserializer.py | 2 +- .../serialize/cloudeventserializer.py | 0 .../serialize/cloudeventserializers.py | 4 ++-- .../serialize/cloudeventtojsonserializer.py | 2 +- .../cloudeventtoprotobufserializer.py | 4 ++-- .../cloudevent/validate}/__init__.py | 0 .../validate/cloudeventvalidator.py | 12 +++++----- .../proto}/__init__.py | 0 .../proto/cloudevents_pb2.py | 0 .../proto/uattributes_pb2.py | 4 ++-- .../proto/upayload_pb2.py | 0 .../proto/uri_pb2.py | 0 .../proto/ustatus_pb2.py | 0 .../proto/uuid_pb2.py | 0 .../rpc/README.adoc | 0 .../rpc}/__init__.py | 0 .../rpc/calloptions.py | 0 .../rpc/rpcclient.py | 6 ++--- .../rpc/rpcmapper.py | 8 +++---- .../rpc/rpcresult.py | 4 ++-- .../transport/README.adoc | 0 .../transport}/__init__.py | 0 .../transport/builder}/__init__.py | 0 .../transport/builder/uattributesbuilder.py | 8 +++---- .../transport/ulistener.py | 8 +++---- .../transport/utransport.py | 12 +++++----- .../transport/validate}/__init__.py | 0 .../validate/uattributesvalidator.py | 10 ++++---- .../uri/README.adoc | 0 .../uri}/__init__.py | 0 .../uri/builder}/__init__.py | 0 .../uri/builder/uresource_builder.py | 2 +- .../uri/serializer}/__init__.py | 0 .../uri/serializer/longuriserializer.py | 12 +++++----- .../uri/serializer/microuriserializer.py | 12 +++++----- .../uri/serializer/uriserializer.py | 8 +++---- .../uri/validator}/__init__.py | 0 .../uri/validator/urivalidator.py | 10 ++++---- .../uuid/README.adoc | 0 .../uuid}/__init__.py | 0 .../uuid/factory/__init__.py | 0 .../uuid/factory/uuidfactory.py | 6 ++--- .../uuid/factory/uuidutils.py | 6 ++--- .../uuid/serializer}/__init__.py | 0 .../uuid/serializer/longuuidserializer.py | 8 +++---- .../uuid/serializer/microuuidserializer.py | 6 ++--- .../uuid/serializer/uuidserializer.py | 2 +- .../uuid/validate}/__init__.py | 0 .../uuid/validate/uuidvalidator.py | 8 +++---- .../validation}/__init__.py | 0 .../validation/validationresult.py | 2 +- 97 files changed, 201 insertions(+), 201 deletions(-) rename {org_eclipse_uprotocol => tests}/__init__.py (100%) rename {org_eclipse_uprotocol/cloudevent => tests/test_cloudevent}/__init__.py (100%) rename {org_eclipse_uprotocol/cloudevent/datamodel => tests/test_cloudevent/test_datamodel}/__init__.py (100%) rename {org_eclipse_uprotocol_tests => tests}/test_cloudevent/test_datamodel/test_ucloudeventattributes.py (95%) rename {org_eclipse_uprotocol/cloudevent/factory => tests/test_cloudevent/test_factory}/__init__.py (100%) rename {org_eclipse_uprotocol_tests => tests}/test_cloudevent/test_factory/cloudevent.json (100%) rename {org_eclipse_uprotocol_tests => tests}/test_cloudevent/test_factory/test_cloudeventfactory.py (94%) rename {org_eclipse_uprotocol/cloudevent/serialize => tests/test_cloudevent/test_serialize}/__init__.py (100%) rename {org_eclipse_uprotocol_tests => tests}/test_cloudevent/test_serialize/cloudevent_to_protobuf.json (100%) rename {org_eclipse_uprotocol_tests => tests}/test_cloudevent/test_serialize/test_base64protobufserializer.py (88%) rename {org_eclipse_uprotocol_tests => tests}/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py (91%) rename {org_eclipse_uprotocol_tests => tests}/test_cloudevent/test_serialize/test_cloudeventtoprotobufserializer.py (87%) rename {org_eclipse_uprotocol/cloudevent/validate => tests/test_cloudevent/test_validator}/__init__.py (100%) rename {org_eclipse_uprotocol_tests => tests}/test_cloudevent/test_validator/test_cloudeventvalidator.py (96%) rename {org_eclipse_uprotocol_tests => tests}/test_cloudevent/test_validator/test_validationresult.py (94%) rename {org_eclipse_uprotocol/proto => tests/test_rpc}/__init__.py (100%) rename {org_eclipse_uprotocol_tests => tests}/test_rpc/test_calloptions.py (97%) rename {org_eclipse_uprotocol_tests => tests}/test_rpc/test_rpc.py (93%) rename {org_eclipse_uprotocol_tests => tests}/test_rpc/test_rpcresult.py (98%) rename {org_eclipse_uprotocol/rpc => tests/test_transport}/__init__.py (100%) rename {org_eclipse_uprotocol/transport => tests/test_transport/test_builder}/__init__.py (100%) rename {org_eclipse_uprotocol_tests => tests}/test_transport/test_builder/test_uattributesbuilder.py (91%) rename {org_eclipse_uprotocol/transport/builder => tests/test_transport/test_validate}/__init__.py (100%) rename {org_eclipse_uprotocol_tests => tests}/test_transport/test_validate/test_uattributesvalidator.py (96%) rename {org_eclipse_uprotocol/transport/validate => tests/test_uri}/__init__.py (100%) rename {org_eclipse_uprotocol/uri => tests/test_uri/test_serializer}/__init__.py (100%) rename {org_eclipse_uprotocol_tests => tests}/test_uri/test_serializer/test_longuriserializer.py (99%) rename {org_eclipse_uprotocol_tests => tests}/test_uri/test_serializer/test_microuriserializer.py (95%) rename {org_eclipse_uprotocol_tests => tests}/test_uri/test_serializer/test_uriserializer.py (89%) rename {org_eclipse_uprotocol/uri/builder => tests/test_uri/test_validator}/__init__.py (100%) rename {org_eclipse_uprotocol_tests => tests}/test_uri/test_validator/test_urivalidator.py (97%) rename {org_eclipse_uprotocol_tests => tests}/test_uri/test_validator/uris.json (100%) rename {org_eclipse_uprotocol/uri/serializer => tests/test_uuid}/__init__.py (100%) rename {org_eclipse_uprotocol/uri/validator => tests/test_uuid/test_factory}/__init__.py (100%) rename {org_eclipse_uprotocol_tests => tests}/test_uuid/test_factory/test_uuidfactory.py (96%) rename {org_eclipse_uprotocol/uuid => tests/test_uuid/test_validator}/__init__.py (100%) rename {org_eclipse_uprotocol_tests => tests}/test_uuid/test_validator/test_uuidvalidator.py (91%) rename {org_eclipse_uprotocol/uuid/serializer => uprotocol}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/cloudevent/README.adoc (100%) rename {org_eclipse_uprotocol/uuid/validate => uprotocol/cloudevent}/__init__.py (100%) rename {org_eclipse_uprotocol/validation => uprotocol/cloudevent/datamodel}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/cloudevent/datamodel/ucloudeventattributes.py (98%) rename {org_eclipse_uprotocol_tests => uprotocol/cloudevent/factory}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/cloudevent/factory/cloudeventfactory.py (96%) rename {org_eclipse_uprotocol => uprotocol}/cloudevent/factory/ucloudevent.py (98%) rename {org_eclipse_uprotocol_tests/test_cloudevent => uprotocol/cloudevent/serialize}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/cloudevent/serialize/base64protobufserializer.py (95%) rename {org_eclipse_uprotocol => uprotocol}/cloudevent/serialize/cloudeventserializer.py (100%) rename {org_eclipse_uprotocol => uprotocol}/cloudevent/serialize/cloudeventserializers.py (85%) rename {org_eclipse_uprotocol => uprotocol}/cloudevent/serialize/cloudeventtojsonserializer.py (94%) rename {org_eclipse_uprotocol => uprotocol}/cloudevent/serialize/cloudeventtoprotobufserializer.py (96%) rename {org_eclipse_uprotocol_tests/test_cloudevent/test_datamodel => uprotocol/cloudevent/validate}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/cloudevent/validate/cloudeventvalidator.py (97%) rename {org_eclipse_uprotocol_tests/test_cloudevent/test_factory => uprotocol/proto}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/proto/cloudevents_pb2.py (100%) rename {org_eclipse_uprotocol => uprotocol}/proto/uattributes_pb2.py (95%) rename {org_eclipse_uprotocol => uprotocol}/proto/upayload_pb2.py (100%) rename {org_eclipse_uprotocol => uprotocol}/proto/uri_pb2.py (100%) rename {org_eclipse_uprotocol => uprotocol}/proto/ustatus_pb2.py (100%) rename {org_eclipse_uprotocol => uprotocol}/proto/uuid_pb2.py (100%) rename {org_eclipse_uprotocol => uprotocol}/rpc/README.adoc (100%) rename {org_eclipse_uprotocol_tests/test_cloudevent/test_serialize => uprotocol/rpc}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/rpc/calloptions.py (100%) rename {org_eclipse_uprotocol => uprotocol}/rpc/rpcclient.py (92%) rename {org_eclipse_uprotocol => uprotocol}/rpc/rpcmapper.py (96%) rename {org_eclipse_uprotocol => uprotocol}/rpc/rpcresult.py (97%) rename {org_eclipse_uprotocol => uprotocol}/transport/README.adoc (100%) rename {org_eclipse_uprotocol_tests/test_cloudevent/test_validator => uprotocol/transport}/__init__.py (100%) rename {org_eclipse_uprotocol_tests/test_rpc => uprotocol/transport/builder}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/transport/builder/uattributesbuilder.py (96%) rename {org_eclipse_uprotocol => uprotocol}/transport/ulistener.py (87%) rename {org_eclipse_uprotocol => uprotocol}/transport/utransport.py (91%) rename {org_eclipse_uprotocol_tests/test_transport => uprotocol/transport/validate}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/transport/validate/uattributesvalidator.py (97%) rename {org_eclipse_uprotocol => uprotocol}/uri/README.adoc (100%) rename {org_eclipse_uprotocol_tests/test_transport/test_builder => uprotocol/uri}/__init__.py (100%) rename {org_eclipse_uprotocol_tests/test_transport/test_validate => uprotocol/uri/builder}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/uri/builder/uresource_builder.py (96%) rename {org_eclipse_uprotocol_tests/test_uri => uprotocol/uri/serializer}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/uri/serializer/longuriserializer.py (95%) rename {org_eclipse_uprotocol => uprotocol}/uri/serializer/microuriserializer.py (94%) rename {org_eclipse_uprotocol => uprotocol}/uri/serializer/uriserializer.py (90%) rename {org_eclipse_uprotocol_tests/test_uri/test_serializer => uprotocol/uri/validator}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/uri/validator/urivalidator.py (95%) rename {org_eclipse_uprotocol => uprotocol}/uuid/README.adoc (100%) rename {org_eclipse_uprotocol_tests/test_uri/test_validator => uprotocol/uuid}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/uuid/factory/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/uuid/factory/uuidfactory.py (93%) rename {org_eclipse_uprotocol => uprotocol}/uuid/factory/uuidutils.py (96%) rename {org_eclipse_uprotocol_tests/test_uuid => uprotocol/uuid/serializer}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/uuid/serializer/longuuidserializer.py (90%) rename {org_eclipse_uprotocol => uprotocol}/uuid/serializer/microuuidserializer.py (91%) rename {org_eclipse_uprotocol => uprotocol}/uuid/serializer/uuidserializer.py (97%) rename {org_eclipse_uprotocol_tests/test_uuid/test_factory => uprotocol/uuid/validate}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/uuid/validate/uuidvalidator.py (93%) rename {org_eclipse_uprotocol_tests/test_uuid/test_validator => uprotocol/validation}/__init__.py (100%) rename {org_eclipse_uprotocol => uprotocol}/validation/validationresult.py (97%) diff --git a/org_eclipse_uprotocol/__init__.py b/tests/__init__.py similarity index 100% rename from org_eclipse_uprotocol/__init__.py rename to tests/__init__.py diff --git a/org_eclipse_uprotocol/cloudevent/__init__.py b/tests/test_cloudevent/__init__.py similarity index 100% rename from org_eclipse_uprotocol/cloudevent/__init__.py rename to tests/test_cloudevent/__init__.py diff --git a/org_eclipse_uprotocol/cloudevent/datamodel/__init__.py b/tests/test_cloudevent/test_datamodel/__init__.py similarity index 100% rename from org_eclipse_uprotocol/cloudevent/datamodel/__init__.py rename to tests/test_cloudevent/test_datamodel/__init__.py diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py b/tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py similarity index 95% rename from org_eclipse_uprotocol_tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py rename to tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py index 652b52a..44449fc 100644 --- a/org_eclipse_uprotocol_tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py +++ b/tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py @@ -27,9 +27,9 @@ import unittest -from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder, \ +from uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder, \ UCloudEventAttributes -from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority +from uprotocol.proto.uattributes_pb2 import UPriority class TestUCloudEventAttributes(unittest.TestCase): diff --git a/org_eclipse_uprotocol/cloudevent/factory/__init__.py b/tests/test_cloudevent/test_factory/__init__.py similarity index 100% rename from org_eclipse_uprotocol/cloudevent/factory/__init__.py rename to tests/test_cloudevent/test_factory/__init__.py diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_factory/cloudevent.json b/tests/test_cloudevent/test_factory/cloudevent.json similarity index 100% rename from org_eclipse_uprotocol_tests/test_cloudevent/test_factory/cloudevent.json rename to tests/test_cloudevent/test_factory/cloudevent.json diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_factory/test_cloudeventfactory.py b/tests/test_cloudevent/test_factory/test_cloudeventfactory.py similarity index 94% rename from org_eclipse_uprotocol_tests/test_cloudevent/test_factory/test_cloudeventfactory.py rename to tests/test_cloudevent/test_factory/test_cloudeventfactory.py index a547f12..fd04a4a 100644 --- a/org_eclipse_uprotocol_tests/test_cloudevent/test_factory/test_cloudeventfactory.py +++ b/tests/test_cloudevent/test_factory/test_cloudeventfactory.py @@ -29,23 +29,23 @@ import json import os from google.protobuf import any_pb2 -from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder, \ +from uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder, \ UCloudEventAttributes -from org_eclipse_uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory -from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent -from org_eclipse_uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer -from org_eclipse_uprotocol.cloudevent.serialize.cloudeventtojsonserializer import CloudEventToJsonSerializer -from org_eclipse_uprotocol.proto.cloudevents_pb2 import CloudEvent -from org_eclipse_uprotocol.proto.uattributes_pb2 import UMessageType, UPriority -from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UEntity, UResource -from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode +from uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory +from uprotocol.cloudevent.factory.ucloudevent import UCloudEvent +from uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer +from uprotocol.cloudevent.serialize.cloudeventtojsonserializer import CloudEventToJsonSerializer +from uprotocol.proto.cloudevents_pb2 import CloudEvent +from uprotocol.proto.uattributes_pb2 import UMessageType, UPriority +from uprotocol.proto.uri_pb2 import UUri, UEntity, UResource +from uprotocol.proto.ustatus_pb2 import UCode -from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from uprotocol.uri.serializer.longuriserializer import LongUriSerializer def get_json_object(): current_directory = os.getcwd() - json_file_path = os.path.join(current_directory, "org_eclipse_uprotocol_tests","test_cloudevent","test_factory","cloudevent.json") + json_file_path = os.path.join(current_directory, "tests","test_cloudevent","test_factory","cloudevent.json") with open(json_file_path, 'r') as json_file: json_data = json.load(json_file) diff --git a/org_eclipse_uprotocol/cloudevent/serialize/__init__.py b/tests/test_cloudevent/test_serialize/__init__.py similarity index 100% rename from org_eclipse_uprotocol/cloudevent/serialize/__init__.py rename to tests/test_cloudevent/test_serialize/__init__.py diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/cloudevent_to_protobuf.json b/tests/test_cloudevent/test_serialize/cloudevent_to_protobuf.json similarity index 100% rename from org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/cloudevent_to_protobuf.json rename to tests/test_cloudevent/test_serialize/cloudevent_to_protobuf.json diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_base64protobufserializer.py b/tests/test_cloudevent/test_serialize/test_base64protobufserializer.py similarity index 88% rename from org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_base64protobufserializer.py rename to tests/test_cloudevent/test_serialize/test_base64protobufserializer.py index 4845972..97df806 100644 --- a/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_base64protobufserializer.py +++ b/tests/test_cloudevent/test_serialize/test_base64protobufserializer.py @@ -27,10 +27,10 @@ import unittest -from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder -from org_eclipse_uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory -from org_eclipse_uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer -from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializers import CloudEventSerializers +from uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory +from uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer +from uprotocol.cloudevent.serialize.cloudeventserializers import CloudEventSerializers class TestBase64ProtobufSerializer(unittest.TestCase): diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py b/tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py similarity index 91% rename from org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py rename to tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py index e8b8617..77c21c3 100644 --- a/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py +++ b/tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py @@ -29,13 +29,13 @@ from google.protobuf import any_pb2 -from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder -from org_eclipse_uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory -from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent -from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializers import CloudEventSerializers -from org_eclipse_uprotocol.cloudevent.serialize.cloudeventtojsonserializer import CloudEventToJsonSerializer -from org_eclipse_uprotocol.proto.cloudevents_pb2 import CloudEvent -from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority, UMessageType +from uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory +from uprotocol.cloudevent.factory.ucloudevent import UCloudEvent +from uprotocol.cloudevent.serialize.cloudeventserializers import CloudEventSerializers +from uprotocol.cloudevent.serialize.cloudeventtojsonserializer import CloudEventToJsonSerializer +from uprotocol.proto.cloudevents_pb2 import CloudEvent +from uprotocol.proto.uattributes_pb2 import UPriority, UMessageType protoContentType = CloudEventFactory.PROTOBUF_CONTENT_TYPE serializer = CloudEventSerializers.JSON.serializer() diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_cloudeventtoprotobufserializer.py b/tests/test_cloudevent/test_serialize/test_cloudeventtoprotobufserializer.py similarity index 87% rename from org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_cloudeventtoprotobufserializer.py rename to tests/test_cloudevent/test_serialize/test_cloudeventtoprotobufserializer.py index a0558e8..638cb92 100644 --- a/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/test_cloudeventtoprotobufserializer.py +++ b/tests/test_cloudevent/test_serialize/test_cloudeventtoprotobufserializer.py @@ -30,16 +30,16 @@ from cloudevents.http import CloudEvent as ApacheCloudEvent from google.protobuf import any_pb2 -from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder -from org_eclipse_uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory -from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent -from org_eclipse_uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer -from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializers import CloudEventSerializers -from org_eclipse_uprotocol.cloudevent.serialize.cloudeventtoprotobufserializer import CloudEventToProtobufSerializer -from org_eclipse_uprotocol.proto.cloudevents_pb2 import CloudEvent -from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority, UMessageType -from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UEntity, UResource -from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory +from uprotocol.cloudevent.factory.ucloudevent import UCloudEvent +from uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer +from uprotocol.cloudevent.serialize.cloudeventserializers import CloudEventSerializers +from uprotocol.cloudevent.serialize.cloudeventtoprotobufserializer import CloudEventToProtobufSerializer +from uprotocol.proto.cloudevents_pb2 import CloudEvent +from uprotocol.proto.uattributes_pb2 import UPriority, UMessageType +from uprotocol.proto.uri_pb2 import UUri, UEntity, UResource +from uprotocol.uri.serializer.longuriserializer import LongUriSerializer serializer = CloudEventSerializers.PROTOBUF.serializer() @@ -62,7 +62,7 @@ def build_proto_payload_for_test(): def get_json_object(): current_directory = os.getcwd() - json_file_path = os.path.join(current_directory, "org_eclipse_uprotocol_tests","test_cloudevent", + json_file_path = os.path.join(current_directory, "tests","test_cloudevent", "test_serialize","cloudevent_to_protobuf.json") with open(json_file_path, 'r') as json_file: json_data = json.load(json_file) diff --git a/org_eclipse_uprotocol/cloudevent/validate/__init__.py b/tests/test_cloudevent/test_validator/__init__.py similarity index 100% rename from org_eclipse_uprotocol/cloudevent/validate/__init__.py rename to tests/test_cloudevent/test_validator/__init__.py diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_validator/test_cloudeventvalidator.py b/tests/test_cloudevent/test_validator/test_cloudeventvalidator.py similarity index 96% rename from org_eclipse_uprotocol_tests/test_cloudevent/test_validator/test_cloudeventvalidator.py rename to tests/test_cloudevent/test_validator/test_cloudeventvalidator.py index 73c73d7..f83c43a 100644 --- a/org_eclipse_uprotocol_tests/test_cloudevent/test_validator/test_cloudeventvalidator.py +++ b/tests/test_cloudevent/test_validator/test_cloudeventvalidator.py @@ -30,18 +30,18 @@ from google.protobuf import any_pb2 -from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder -from org_eclipse_uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory -from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent -from org_eclipse_uprotocol.cloudevent.validate.cloudeventvalidator import CloudEventValidator, Validators -from org_eclipse_uprotocol.proto.cloudevents_pb2 import CloudEvent -from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority, UMessageType -from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UEntity, UResource -from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode -from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer -from org_eclipse_uprotocol.uuid.factory.uuidfactory import Factories -from org_eclipse_uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer -from org_eclipse_uprotocol.validation.validationresult import ValidationResult +from uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory +from uprotocol.cloudevent.factory.ucloudevent import UCloudEvent +from uprotocol.cloudevent.validate.cloudeventvalidator import CloudEventValidator, Validators +from uprotocol.proto.cloudevents_pb2 import CloudEvent +from uprotocol.proto.uattributes_pb2 import UPriority, UMessageType +from uprotocol.proto.uri_pb2 import UUri, UEntity, UResource +from uprotocol.proto.ustatus_pb2 import UCode +from uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from uprotocol.uuid.factory.uuidfactory import Factories +from uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer +from uprotocol.validation.validationresult import ValidationResult def build_base_cloud_event_for_test(): diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_validator/test_validationresult.py b/tests/test_cloudevent/test_validator/test_validationresult.py similarity index 94% rename from org_eclipse_uprotocol_tests/test_cloudevent/test_validator/test_validationresult.py rename to tests/test_cloudevent/test_validator/test_validationresult.py index 8cebd76..61d9dcf 100644 --- a/org_eclipse_uprotocol_tests/test_cloudevent/test_validator/test_validationresult.py +++ b/tests/test_cloudevent/test_validator/test_validationresult.py @@ -27,8 +27,8 @@ import unittest -from org_eclipse_uprotocol.validation.validationresult import ValidationResult -from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus, UCode +from uprotocol.validation.validationresult import ValidationResult +from uprotocol.proto.ustatus_pb2 import UStatus, UCode class TestValidationResultTest(unittest.TestCase): diff --git a/org_eclipse_uprotocol/proto/__init__.py b/tests/test_rpc/__init__.py similarity index 100% rename from org_eclipse_uprotocol/proto/__init__.py rename to tests/test_rpc/__init__.py diff --git a/org_eclipse_uprotocol_tests/test_rpc/test_calloptions.py b/tests/test_rpc/test_calloptions.py similarity index 97% rename from org_eclipse_uprotocol_tests/test_rpc/test_calloptions.py rename to tests/test_rpc/test_calloptions.py index 09a061d..18fa71c 100644 --- a/org_eclipse_uprotocol_tests/test_rpc/test_calloptions.py +++ b/tests/test_rpc/test_calloptions.py @@ -27,7 +27,7 @@ import unittest -from org_eclipse_uprotocol.rpc.calloptions import CallOptions, CallOptionsBuilder +from uprotocol.rpc.calloptions import CallOptions, CallOptionsBuilder class TestCallOptions(unittest.TestCase): diff --git a/org_eclipse_uprotocol_tests/test_rpc/test_rpc.py b/tests/test_rpc/test_rpc.py similarity index 93% rename from org_eclipse_uprotocol_tests/test_rpc/test_rpc.py rename to tests/test_rpc/test_rpc.py index e239c04..138807c 100644 --- a/org_eclipse_uprotocol_tests/test_rpc/test_rpc.py +++ b/tests/test_rpc/test_rpc.py @@ -29,16 +29,16 @@ from concurrent.futures import Future from google.protobuf.any_pb2 import Any from google.protobuf.wrappers_pb2 import Int32Value -from org_eclipse_uprotocol.proto.cloudevents_pb2 import CloudEvent -from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority -from org_eclipse_uprotocol.proto.upayload_pb2 import UPayload, UPayloadFormat -from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UEntity -from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus, UCode -from org_eclipse_uprotocol.rpc.rpcclient import RpcClient -from org_eclipse_uprotocol.rpc.rpcmapper import RpcMapper -from org_eclipse_uprotocol.rpc.rpcresult import RpcResult -from org_eclipse_uprotocol.transport.builder.uattributesbuilder import UAttributesBuilder -from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from uprotocol.proto.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.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 def build_cloud_event(): diff --git a/org_eclipse_uprotocol_tests/test_rpc/test_rpcresult.py b/tests/test_rpc/test_rpcresult.py similarity index 98% rename from org_eclipse_uprotocol_tests/test_rpc/test_rpcresult.py rename to tests/test_rpc/test_rpcresult.py index 31f821f..9365fcd 100644 --- a/org_eclipse_uprotocol_tests/test_rpc/test_rpcresult.py +++ b/tests/test_rpc/test_rpcresult.py @@ -27,8 +27,8 @@ import unittest -from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode, UStatus -from org_eclipse_uprotocol.rpc.rpcresult import RpcResult +from uprotocol.proto.ustatus_pb2 import UCode, UStatus +from uprotocol.rpc.rpcresult import RpcResult def getDefault(): diff --git a/org_eclipse_uprotocol/rpc/__init__.py b/tests/test_transport/__init__.py similarity index 100% rename from org_eclipse_uprotocol/rpc/__init__.py rename to tests/test_transport/__init__.py diff --git a/org_eclipse_uprotocol/transport/__init__.py b/tests/test_transport/test_builder/__init__.py similarity index 100% rename from org_eclipse_uprotocol/transport/__init__.py rename to tests/test_transport/test_builder/__init__.py diff --git a/org_eclipse_uprotocol_tests/test_transport/test_builder/test_uattributesbuilder.py b/tests/test_transport/test_builder/test_uattributesbuilder.py similarity index 91% rename from org_eclipse_uprotocol_tests/test_transport/test_builder/test_uattributesbuilder.py rename to tests/test_transport/test_builder/test_uattributesbuilder.py index c65070a..0baefbf 100644 --- a/org_eclipse_uprotocol_tests/test_transport/test_builder/test_uattributesbuilder.py +++ b/tests/test_transport/test_builder/test_uattributesbuilder.py @@ -26,11 +26,11 @@ import unittest -from org_eclipse_uprotocol.transport.builder.uattributesbuilder import UAttributesBuilder -from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority, UMessageType -from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UAuthority, UEntity -from org_eclipse_uprotocol.uri.builder.uresource_builder import UResourceBuilder -from org_eclipse_uprotocol.uuid.factory.uuidfactory import Factories +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.uuid.factory.uuidfactory import Factories def build_sink(): diff --git a/org_eclipse_uprotocol/transport/builder/__init__.py b/tests/test_transport/test_validate/__init__.py similarity index 100% rename from org_eclipse_uprotocol/transport/builder/__init__.py rename to tests/test_transport/test_validate/__init__.py diff --git a/org_eclipse_uprotocol_tests/test_transport/test_validate/test_uattributesvalidator.py b/tests/test_transport/test_validate/test_uattributesvalidator.py similarity index 96% rename from org_eclipse_uprotocol_tests/test_transport/test_validate/test_uattributesvalidator.py rename to tests/test_transport/test_validate/test_uattributesvalidator.py index 1d0011d..4ada52d 100644 --- a/org_eclipse_uprotocol_tests/test_transport/test_validate/test_uattributesvalidator.py +++ b/tests/test_transport/test_validate/test_uattributesvalidator.py @@ -27,16 +27,16 @@ import time import unittest -from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority -from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UAuthority, UEntity -from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode -from org_eclipse_uprotocol.proto.uuid_pb2 import UUID -from org_eclipse_uprotocol.transport.builder.uattributesbuilder import UAttributesBuilder -from org_eclipse_uprotocol.transport.validate.uattributesvalidator import UAttributesValidator, Validators -from org_eclipse_uprotocol.uri.builder.uresource_builder import UResourceBuilder -from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer -from org_eclipse_uprotocol.uuid.factory.uuidfactory import Factories -from org_eclipse_uprotocol.validation.validationresult import ValidationResult +from uprotocol.proto.uattributes_pb2 import UPriority +from uprotocol.proto.uri_pb2 import UUri, UAuthority, UEntity +from uprotocol.proto.ustatus_pb2 import UCode +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.serializer.longuriserializer import LongUriSerializer +from uprotocol.uuid.factory.uuidfactory import Factories +from uprotocol.validation.validationresult import ValidationResult def build_sink(): diff --git a/org_eclipse_uprotocol/transport/validate/__init__.py b/tests/test_uri/__init__.py similarity index 100% rename from org_eclipse_uprotocol/transport/validate/__init__.py rename to tests/test_uri/__init__.py diff --git a/org_eclipse_uprotocol/uri/__init__.py b/tests/test_uri/test_serializer/__init__.py similarity index 100% rename from org_eclipse_uprotocol/uri/__init__.py rename to tests/test_uri/test_serializer/__init__.py diff --git a/org_eclipse_uprotocol_tests/test_uri/test_serializer/test_longuriserializer.py b/tests/test_uri/test_serializer/test_longuriserializer.py similarity index 99% rename from org_eclipse_uprotocol_tests/test_uri/test_serializer/test_longuriserializer.py rename to tests/test_uri/test_serializer/test_longuriserializer.py index 7cddd13..466a351 100644 --- a/org_eclipse_uprotocol_tests/test_uri/test_serializer/test_longuriserializer.py +++ b/tests/test_uri/test_serializer/test_longuriserializer.py @@ -27,10 +27,10 @@ import unittest -from org_eclipse_uprotocol.proto.uri_pb2 import UEntity, UUri, UAuthority, UResource -from org_eclipse_uprotocol.uri.builder.uresource_builder import UResourceBuilder -from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer -from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator +from uprotocol.proto.uri_pb2 import UEntity, UUri, UAuthority, UResource +from uprotocol.uri.builder.uresource_builder import UResourceBuilder +from uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from uprotocol.uri.validator.urivalidator import UriValidator class TestLongUriSerializer(unittest.TestCase): diff --git a/org_eclipse_uprotocol_tests/test_uri/test_serializer/test_microuriserializer.py b/tests/test_uri/test_serializer/test_microuriserializer.py similarity index 95% rename from org_eclipse_uprotocol_tests/test_uri/test_serializer/test_microuriserializer.py rename to tests/test_uri/test_serializer/test_microuriserializer.py index 19beff7..5bd9849 100644 --- a/org_eclipse_uprotocol_tests/test_uri/test_serializer/test_microuriserializer.py +++ b/tests/test_uri/test_serializer/test_microuriserializer.py @@ -27,10 +27,10 @@ import socket import unittest -from org_eclipse_uprotocol.proto.uri_pb2 import UEntity, UUri, UAuthority, UResource -from org_eclipse_uprotocol.uri.builder.uresource_builder import UResourceBuilder -from org_eclipse_uprotocol.uri.serializer.microuriserializer import MicroUriSerializer -from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator +from uprotocol.proto.uri_pb2 import UEntity, UUri, UAuthority, UResource +from uprotocol.uri.builder.uresource_builder import UResourceBuilder +from uprotocol.uri.serializer.microuriserializer import MicroUriSerializer +from uprotocol.uri.validator.urivalidator import UriValidator class TestMicroUriSerializer(unittest.TestCase): diff --git a/org_eclipse_uprotocol_tests/test_uri/test_serializer/test_uriserializer.py b/tests/test_uri/test_serializer/test_uriserializer.py similarity index 89% rename from org_eclipse_uprotocol_tests/test_uri/test_serializer/test_uriserializer.py rename to tests/test_uri/test_serializer/test_uriserializer.py index 3e418c6..18122df 100644 --- a/org_eclipse_uprotocol_tests/test_uri/test_serializer/test_uriserializer.py +++ b/tests/test_uri/test_serializer/test_uriserializer.py @@ -27,10 +27,10 @@ import unittest -from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority, UEntity, UResource, UUri -from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer -from org_eclipse_uprotocol.uri.serializer.microuriserializer import MicroUriSerializer -from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator +from uprotocol.proto.uri_pb2 import UAuthority, UEntity, UResource, UUri +from uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from uprotocol.uri.serializer.microuriserializer import MicroUriSerializer +from uprotocol.uri.validator.urivalidator import UriValidator class TestUriSerializer(unittest.TestCase): diff --git a/org_eclipse_uprotocol/uri/builder/__init__.py b/tests/test_uri/test_validator/__init__.py similarity index 100% rename from org_eclipse_uprotocol/uri/builder/__init__.py rename to tests/test_uri/test_validator/__init__.py diff --git a/org_eclipse_uprotocol_tests/test_uri/test_validator/test_urivalidator.py b/tests/test_uri/test_validator/test_urivalidator.py similarity index 97% rename from org_eclipse_uprotocol_tests/test_uri/test_validator/test_urivalidator.py rename to tests/test_uri/test_validator/test_urivalidator.py index 978467d..3ada9eb 100644 --- a/org_eclipse_uprotocol_tests/test_uri/test_validator/test_urivalidator.py +++ b/tests/test_uri/test_validator/test_urivalidator.py @@ -30,10 +30,10 @@ import os import unittest -from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer -from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator -from org_eclipse_uprotocol.validation.validationresult import ValidationResult -from org_eclipse_uprotocol.proto.uri_pb2 import UUri, UEntity, UResource, UAuthority +from uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from uprotocol.uri.validator.urivalidator import UriValidator +from uprotocol.validation.validationresult import ValidationResult +from uprotocol.proto.uri_pb2 import UUri, UEntity, UResource, UAuthority class TestUriValidator(unittest.TestCase): @@ -349,7 +349,7 @@ def test_all_invalid_rpc_response_uris(self): @staticmethod def get_json_object(): current_directory = os.getcwd() - json_file_path = os.path.join(current_directory,"org_eclipse_uprotocol_tests","test_uri","test_validator", "uris.json") + json_file_path = os.path.join(current_directory,"tests","test_uri","test_validator", "uris.json") with open(json_file_path, 'r') as json_file: json_data = json.load(json_file) diff --git a/org_eclipse_uprotocol_tests/test_uri/test_validator/uris.json b/tests/test_uri/test_validator/uris.json similarity index 100% rename from org_eclipse_uprotocol_tests/test_uri/test_validator/uris.json rename to tests/test_uri/test_validator/uris.json diff --git a/org_eclipse_uprotocol/uri/serializer/__init__.py b/tests/test_uuid/__init__.py similarity index 100% rename from org_eclipse_uprotocol/uri/serializer/__init__.py rename to tests/test_uuid/__init__.py diff --git a/org_eclipse_uprotocol/uri/validator/__init__.py b/tests/test_uuid/test_factory/__init__.py similarity index 100% rename from org_eclipse_uprotocol/uri/validator/__init__.py rename to tests/test_uuid/test_factory/__init__.py diff --git a/org_eclipse_uprotocol_tests/test_uuid/test_factory/test_uuidfactory.py b/tests/test_uuid/test_factory/test_uuidfactory.py similarity index 96% rename from org_eclipse_uprotocol_tests/test_uuid/test_factory/test_uuidfactory.py rename to tests/test_uuid/test_factory/test_uuidfactory.py index 8bebff7..0ac27a9 100644 --- a/org_eclipse_uprotocol_tests/test_uuid/test_factory/test_uuidfactory.py +++ b/tests/test_uuid/test_factory/test_uuidfactory.py @@ -27,11 +27,11 @@ from datetime import datetime, timedelta import unittest -from org_eclipse_uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer -from org_eclipse_uprotocol.uuid.serializer.microuuidserializer import MicroUuidSerializer -from org_eclipse_uprotocol.uuid.factory.uuidfactory import UUIDFactory, Factories -from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils, Version -from org_eclipse_uprotocol.proto.uuid_pb2 import UUID +from uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer +from uprotocol.uuid.serializer.microuuidserializer import MicroUuidSerializer +from uprotocol.uuid.factory.uuidfactory import UUIDFactory, Factories +from uprotocol.uuid.factory.uuidutils import UUIDUtils, Version +from uprotocol.proto.uuid_pb2 import UUID diff --git a/org_eclipse_uprotocol/uuid/__init__.py b/tests/test_uuid/test_validator/__init__.py similarity index 100% rename from org_eclipse_uprotocol/uuid/__init__.py rename to tests/test_uuid/test_validator/__init__.py diff --git a/org_eclipse_uprotocol_tests/test_uuid/test_validator/test_uuidvalidator.py b/tests/test_uuid/test_validator/test_uuidvalidator.py similarity index 91% rename from org_eclipse_uprotocol_tests/test_uuid/test_validator/test_uuidvalidator.py rename to tests/test_uuid/test_validator/test_uuidvalidator.py index 1afb0da..c0752b6 100644 --- a/org_eclipse_uprotocol_tests/test_uuid/test_validator/test_uuidvalidator.py +++ b/tests/test_uuid/test_validator/test_uuidvalidator.py @@ -28,14 +28,14 @@ import unittest from datetime import datetime, timezone -from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils -from org_eclipse_uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer - -from org_eclipse_uprotocol.validation.validationresult import ValidationResult -from org_eclipse_uprotocol.proto.uuid_pb2 import UUID -from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode -from org_eclipse_uprotocol.uuid.factory.uuidfactory import Factories -from org_eclipse_uprotocol.uuid.validate.uuidvalidator import UuidValidator, Validators +from uprotocol.uuid.factory.uuidutils import UUIDUtils +from uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer + +from uprotocol.validation.validationresult import ValidationResult +from uprotocol.proto.uuid_pb2 import UUID +from uprotocol.proto.ustatus_pb2 import UCode +from uprotocol.uuid.factory.uuidfactory import Factories +from uprotocol.uuid.validate.uuidvalidator import UuidValidator, Validators class TestUuidValidator(unittest.TestCase): diff --git a/org_eclipse_uprotocol/uuid/serializer/__init__.py b/uprotocol/__init__.py similarity index 100% rename from org_eclipse_uprotocol/uuid/serializer/__init__.py rename to uprotocol/__init__.py diff --git a/org_eclipse_uprotocol/cloudevent/README.adoc b/uprotocol/cloudevent/README.adoc similarity index 100% rename from org_eclipse_uprotocol/cloudevent/README.adoc rename to uprotocol/cloudevent/README.adoc diff --git a/org_eclipse_uprotocol/uuid/validate/__init__.py b/uprotocol/cloudevent/__init__.py similarity index 100% rename from org_eclipse_uprotocol/uuid/validate/__init__.py rename to uprotocol/cloudevent/__init__.py diff --git a/org_eclipse_uprotocol/validation/__init__.py b/uprotocol/cloudevent/datamodel/__init__.py similarity index 100% rename from org_eclipse_uprotocol/validation/__init__.py rename to uprotocol/cloudevent/datamodel/__init__.py diff --git a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py b/uprotocol/cloudevent/datamodel/ucloudeventattributes.py similarity index 98% rename from org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py rename to uprotocol/cloudevent/datamodel/ucloudeventattributes.py index 111c9a5..20f82e3 100644 --- a/org_eclipse_uprotocol/cloudevent/datamodel/ucloudeventattributes.py +++ b/uprotocol/cloudevent/datamodel/ucloudeventattributes.py @@ -26,8 +26,8 @@ from typing import Optional -from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority -from org_eclipse_uprotocol.proto.uattributes_pb2 import UPriority +from uprotocol.proto.uattributes_pb2 import UPriority +from uprotocol.proto.uattributes_pb2 import UPriority class UCloudEventAttributes: diff --git a/org_eclipse_uprotocol_tests/__init__.py b/uprotocol/cloudevent/factory/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/__init__.py rename to uprotocol/cloudevent/factory/__init__.py diff --git a/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py b/uprotocol/cloudevent/factory/cloudeventfactory.py similarity index 96% rename from org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py rename to uprotocol/cloudevent/factory/cloudeventfactory.py index 5fbff91..c80ed74 100644 --- a/org_eclipse_uprotocol/cloudevent/factory/cloudeventfactory.py +++ b/uprotocol/cloudevent/factory/cloudeventfactory.py @@ -29,11 +29,11 @@ from google.protobuf import empty_pb2 from google.protobuf.any_pb2 import Any -from org_eclipse_uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributes -from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent -from org_eclipse_uprotocol.proto.uattributes_pb2 import UMessageType -from org_eclipse_uprotocol.uuid.factory.uuidfactory import Factories -from org_eclipse_uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer +from uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributes +from uprotocol.cloudevent.factory.ucloudevent import UCloudEvent +from uprotocol.proto.uattributes_pb2 import UMessageType +from uprotocol.uuid.factory.uuidfactory import Factories +from uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer # A factory is a part of the software has methods to generate concrete objects, usually of the same type or diff --git a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py b/uprotocol/cloudevent/factory/ucloudevent.py similarity index 98% rename from org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py rename to uprotocol/cloudevent/factory/ucloudevent.py index 06945e6..c85ff02 100644 --- a/org_eclipse_uprotocol/cloudevent/factory/ucloudevent.py +++ b/uprotocol/cloudevent/factory/ucloudevent.py @@ -30,10 +30,10 @@ from cloudevents.http import CloudEvent from google.protobuf import any_pb2 from google.protobuf.message import DecodeError -from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode -from org_eclipse_uprotocol.proto.uattributes_pb2 import UMessageType -from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils -from org_eclipse_uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer +from uprotocol.proto.ustatus_pb2 import UCode +from uprotocol.proto.uattributes_pb2 import UMessageType +from uprotocol.uuid.factory.uuidutils import UUIDUtils +from uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer class UCloudEvent: diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/__init__.py b/uprotocol/cloudevent/serialize/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/test_cloudevent/__init__.py rename to uprotocol/cloudevent/serialize/__init__.py diff --git a/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py b/uprotocol/cloudevent/serialize/base64protobufserializer.py similarity index 95% rename from org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py rename to uprotocol/cloudevent/serialize/base64protobufserializer.py index 73b32ea..1bdf627 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/base64protobufserializer.py +++ b/uprotocol/cloudevent/serialize/base64protobufserializer.py @@ -28,7 +28,7 @@ import base64 from builtins import str -from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializer import CloudEventSerializer +from uprotocol.cloudevent.serialize.cloudeventserializer import CloudEventSerializer class Base64ProtobufSerializer(CloudEventSerializer): diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializer.py b/uprotocol/cloudevent/serialize/cloudeventserializer.py similarity index 100% rename from org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializer.py rename to uprotocol/cloudevent/serialize/cloudeventserializer.py diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py b/uprotocol/cloudevent/serialize/cloudeventserializers.py similarity index 85% rename from org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py rename to uprotocol/cloudevent/serialize/cloudeventserializers.py index c647ad0..f812006 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventserializers.py +++ b/uprotocol/cloudevent/serialize/cloudeventserializers.py @@ -27,8 +27,8 @@ from enum import Enum -from org_eclipse_uprotocol.cloudevent.serialize.cloudeventtojsonserializer import CloudEventToJsonSerializer -from org_eclipse_uprotocol.cloudevent.serialize.cloudeventtoprotobufserializer import CloudEventToProtobufSerializer +from uprotocol.cloudevent.serialize.cloudeventtojsonserializer import CloudEventToJsonSerializer +from uprotocol.cloudevent.serialize.cloudeventtoprotobufserializer import CloudEventToProtobufSerializer class CloudEventSerializers(Enum): diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py b/uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py similarity index 94% rename from org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py rename to uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py index e3bc13c..c7aa57b 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py +++ b/uprotocol/cloudevent/serialize/cloudeventtojsonserializer.py @@ -28,7 +28,7 @@ from cloudevents.conversion import to_json from cloudevents.http import CloudEvent, from_json -from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializer import CloudEventSerializer +from uprotocol.cloudevent.serialize.cloudeventserializer import CloudEventSerializer class CloudEventToJsonSerializer(CloudEventSerializer): diff --git a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py b/uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py similarity index 96% rename from org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py rename to uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py index c704ff6..b594871 100644 --- a/org_eclipse_uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py +++ b/uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py @@ -26,8 +26,8 @@ from cloudevents.http import CloudEvent -from org_eclipse_uprotocol.cloudevent.serialize.cloudeventserializer import CloudEventSerializer -from org_eclipse_uprotocol.proto import cloudevents_pb2 +from uprotocol.cloudevent.serialize.cloudeventserializer import CloudEventSerializer +from uprotocol.proto import cloudevents_pb2 # ToDo- convert cloud event to cloudevent proto diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_datamodel/__init__.py b/uprotocol/cloudevent/validate/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/test_cloudevent/test_datamodel/__init__.py rename to uprotocol/cloudevent/validate/__init__.py diff --git a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py b/uprotocol/cloudevent/validate/cloudeventvalidator.py similarity index 97% rename from org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py rename to uprotocol/cloudevent/validate/cloudeventvalidator.py index c6675f6..06f74b9 100644 --- a/org_eclipse_uprotocol/cloudevent/validate/cloudeventvalidator.py +++ b/uprotocol/cloudevent/validate/cloudeventvalidator.py @@ -30,12 +30,12 @@ from cloudevents.http import CloudEvent -from org_eclipse_uprotocol.cloudevent.factory.ucloudevent import UCloudEvent -from org_eclipse_uprotocol.proto.uattributes_pb2 import UMessageType -from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer -from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator -from org_eclipse_uprotocol.validation.validationresult import ValidationResult +from uprotocol.cloudevent.factory.ucloudevent import UCloudEvent +from uprotocol.proto.uattributes_pb2 import UMessageType +from uprotocol.proto.uri_pb2 import UUri +from uprotocol.uri.serializer.longuriserializer import LongUriSerializer +from uprotocol.uri.validator.urivalidator import UriValidator +from uprotocol.validation.validationresult import ValidationResult class CloudEventValidator(ABC): diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_factory/__init__.py b/uprotocol/proto/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/test_cloudevent/test_factory/__init__.py rename to uprotocol/proto/__init__.py diff --git a/org_eclipse_uprotocol/proto/cloudevents_pb2.py b/uprotocol/proto/cloudevents_pb2.py similarity index 100% rename from org_eclipse_uprotocol/proto/cloudevents_pb2.py rename to uprotocol/proto/cloudevents_pb2.py diff --git a/org_eclipse_uprotocol/proto/uattributes_pb2.py b/uprotocol/proto/uattributes_pb2.py similarity index 95% rename from org_eclipse_uprotocol/proto/uattributes_pb2.py rename to uprotocol/proto/uattributes_pb2.py index 1f9eb49..c98292d 100644 --- a/org_eclipse_uprotocol/proto/uattributes_pb2.py +++ b/uprotocol/proto/uattributes_pb2.py @@ -11,8 +11,8 @@ _sym_db = _symbol_database.Default() -import org_eclipse_uprotocol.proto.uri_pb2 as uri__pb2 -import org_eclipse_uprotocol.proto.uuid_pb2 as uuid__pb2 +import uprotocol.proto.uri_pb2 as uri__pb2 +import uprotocol.proto.uuid_pb2 as uuid__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11uattributes.proto\x12\x0cuprotocol.v1\x1a\turi.proto\x1a\nuuid.proto\"\xf8\x02\n\x0bUAttributes\x12\x1e\n\x02id\x18\x01 \x01(\x0b\x32\x12.uprotocol.v1.UUID\x12(\n\x04type\x18\x02 \x01(\x0e\x32\x1a.uprotocol.v1.UMessageType\x12%\n\x04sink\x18\x03 \x01(\x0b\x32\x12.uprotocol.v1.UUriH\x00\x88\x01\x01\x12)\n\x08priority\x18\x04 \x01(\x0e\x32\x17.uprotocol.v1.UPriority\x12\x10\n\x03ttl\x18\x05 \x01(\x05H\x01\x88\x01\x01\x12\x1d\n\x10permission_level\x18\x06 \x01(\x05H\x02\x88\x01\x01\x12\x17\n\ncommstatus\x18\x07 \x01(\x05H\x03\x88\x01\x01\x12&\n\x05reqid\x18\x08 \x01(\x0b\x32\x12.uprotocol.v1.UUIDH\x04\x88\x01\x01\x12\x12\n\x05token\x18\t \x01(\tH\x05\x88\x01\x01\x42\x07\n\x05_sinkB\x06\n\x04_ttlB\x13\n\x11_permission_levelB\r\n\x0b_commstatusB\x08\n\x06_reqidB\x08\n\x06_token*\x7f\n\x0cUMessageType\x12\x1d\n\x19UMESSAGE_TYPE_UNSPECIFIED\x10\x00\x12\x19\n\x15UMESSAGE_TYPE_PUBLISH\x10\x01\x12\x19\n\x15UMESSAGE_TYPE_REQUEST\x10\x02\x12\x1a\n\x16UMESSAGE_TYPE_RESPONSE\x10\x03*\xab\x01\n\tUPriority\x12\x19\n\x15UPRIORITY_UNSPECIFIED\x10\x00\x12\x11\n\rUPRIORITY_CS0\x10\x01\x12\x11\n\rUPRIORITY_CS1\x10\x02\x12\x11\n\rUPRIORITY_CS2\x10\x03\x12\x11\n\rUPRIORITY_CS3\x10\x04\x12\x11\n\rUPRIORITY_CS4\x10\x05\x12\x11\n\rUPRIORITY_CS5\x10\x06\x12\x11\n\rUPRIORITY_CS6\x10\x07\x42.\n\x18org.eclipse.uprotocol.v1B\x10UAttributesProtoP\x01\x62\x06proto3') diff --git a/org_eclipse_uprotocol/proto/upayload_pb2.py b/uprotocol/proto/upayload_pb2.py similarity index 100% rename from org_eclipse_uprotocol/proto/upayload_pb2.py rename to uprotocol/proto/upayload_pb2.py diff --git a/org_eclipse_uprotocol/proto/uri_pb2.py b/uprotocol/proto/uri_pb2.py similarity index 100% rename from org_eclipse_uprotocol/proto/uri_pb2.py rename to uprotocol/proto/uri_pb2.py diff --git a/org_eclipse_uprotocol/proto/ustatus_pb2.py b/uprotocol/proto/ustatus_pb2.py similarity index 100% rename from org_eclipse_uprotocol/proto/ustatus_pb2.py rename to uprotocol/proto/ustatus_pb2.py diff --git a/org_eclipse_uprotocol/proto/uuid_pb2.py b/uprotocol/proto/uuid_pb2.py similarity index 100% rename from org_eclipse_uprotocol/proto/uuid_pb2.py rename to uprotocol/proto/uuid_pb2.py diff --git a/org_eclipse_uprotocol/rpc/README.adoc b/uprotocol/rpc/README.adoc similarity index 100% rename from org_eclipse_uprotocol/rpc/README.adoc rename to uprotocol/rpc/README.adoc diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/__init__.py b/uprotocol/rpc/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/test_cloudevent/test_serialize/__init__.py rename to uprotocol/rpc/__init__.py diff --git a/org_eclipse_uprotocol/rpc/calloptions.py b/uprotocol/rpc/calloptions.py similarity index 100% rename from org_eclipse_uprotocol/rpc/calloptions.py rename to uprotocol/rpc/calloptions.py diff --git a/org_eclipse_uprotocol/rpc/rpcclient.py b/uprotocol/rpc/rpcclient.py similarity index 92% rename from org_eclipse_uprotocol/rpc/rpcclient.py rename to uprotocol/rpc/rpcclient.py index 12cdc21..1f16833 100644 --- a/org_eclipse_uprotocol/rpc/rpcclient.py +++ b/uprotocol/rpc/rpcclient.py @@ -28,9 +28,9 @@ from abc import ABC, abstractmethod from concurrent.futures import Future -from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes -from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.proto.upayload_pb2 import UPayload +from uprotocol.proto.uattributes_pb2 import UAttributes +from uprotocol.proto.uri_pb2 import UUri +from uprotocol.proto.upayload_pb2 import UPayload class RpcClient(ABC): diff --git a/org_eclipse_uprotocol/rpc/rpcmapper.py b/uprotocol/rpc/rpcmapper.py similarity index 96% rename from org_eclipse_uprotocol/rpc/rpcmapper.py rename to uprotocol/rpc/rpcmapper.py index 46f8fb8..72d58e8 100644 --- a/org_eclipse_uprotocol/rpc/rpcmapper.py +++ b/uprotocol/rpc/rpcmapper.py @@ -28,11 +28,11 @@ from concurrent.futures import Future from google.protobuf import any_pb2 -from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode -from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus +from uprotocol.proto.ustatus_pb2 import UCode +from uprotocol.proto.ustatus_pb2 import UStatus -from org_eclipse_uprotocol.rpc.rpcresult import RpcResult -from org_eclipse_uprotocol.proto.upayload_pb2 import UPayload +from uprotocol.rpc.rpcresult import RpcResult +from uprotocol.proto.upayload_pb2 import UPayload class RpcMapper: diff --git a/org_eclipse_uprotocol/rpc/rpcresult.py b/uprotocol/rpc/rpcresult.py similarity index 97% rename from org_eclipse_uprotocol/rpc/rpcresult.py rename to uprotocol/rpc/rpcresult.py index f620c73..67a6582 100644 --- a/org_eclipse_uprotocol/rpc/rpcresult.py +++ b/uprotocol/rpc/rpcresult.py @@ -28,8 +28,8 @@ from abc import ABC, abstractmethod from typing import Callable, TypeVar, Union -from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode -from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus +from uprotocol.proto.ustatus_pb2 import UCode +from uprotocol.proto.ustatus_pb2 import UStatus T = TypeVar('T') diff --git a/org_eclipse_uprotocol/transport/README.adoc b/uprotocol/transport/README.adoc similarity index 100% rename from org_eclipse_uprotocol/transport/README.adoc rename to uprotocol/transport/README.adoc diff --git a/org_eclipse_uprotocol_tests/test_cloudevent/test_validator/__init__.py b/uprotocol/transport/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/test_cloudevent/test_validator/__init__.py rename to uprotocol/transport/__init__.py diff --git a/org_eclipse_uprotocol_tests/test_rpc/__init__.py b/uprotocol/transport/builder/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/test_rpc/__init__.py rename to uprotocol/transport/builder/__init__.py diff --git a/org_eclipse_uprotocol/transport/builder/uattributesbuilder.py b/uprotocol/transport/builder/uattributesbuilder.py similarity index 96% rename from org_eclipse_uprotocol/transport/builder/uattributesbuilder.py rename to uprotocol/transport/builder/uattributesbuilder.py index 4e795f4..5a55ee1 100644 --- a/org_eclipse_uprotocol/transport/builder/uattributesbuilder.py +++ b/uprotocol/transport/builder/uattributesbuilder.py @@ -25,10 +25,10 @@ # ------------------------------------------------------------------------- -from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes, UPriority, UMessageType -from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.proto.uuid_pb2 import UUID -from org_eclipse_uprotocol.uuid.factory.uuidfactory import * +from uprotocol.proto.uattributes_pb2 import UAttributes, UPriority, UMessageType +from uprotocol.proto.uri_pb2 import UUri +from uprotocol.proto.uuid_pb2 import UUID +from uprotocol.uuid.factory.uuidfactory import * class UAttributesBuilder: diff --git a/org_eclipse_uprotocol/transport/ulistener.py b/uprotocol/transport/ulistener.py similarity index 87% rename from org_eclipse_uprotocol/transport/ulistener.py rename to uprotocol/transport/ulistener.py index fd68c9b..1e69b69 100644 --- a/org_eclipse_uprotocol/transport/ulistener.py +++ b/uprotocol/transport/ulistener.py @@ -27,10 +27,10 @@ from abc import ABC, abstractmethod -from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes -from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.proto.upayload_pb2 import UPayload -from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus +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 class UListener(ABC): diff --git a/org_eclipse_uprotocol/transport/utransport.py b/uprotocol/transport/utransport.py similarity index 91% rename from org_eclipse_uprotocol/transport/utransport.py rename to uprotocol/transport/utransport.py index b2901af..eaad0c2 100644 --- a/org_eclipse_uprotocol/transport/utransport.py +++ b/uprotocol/transport/utransport.py @@ -27,12 +27,12 @@ from abc import ABC, abstractmethod -from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes -from org_eclipse_uprotocol.proto.uri_pb2 import UEntity -from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.transport.ulistener import UListener -from org_eclipse_uprotocol.proto.upayload_pb2 import UPayload -from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus +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 class UTransport(ABC): """ diff --git a/org_eclipse_uprotocol_tests/test_transport/__init__.py b/uprotocol/transport/validate/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/test_transport/__init__.py rename to uprotocol/transport/validate/__init__.py diff --git a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py b/uprotocol/transport/validate/uattributesvalidator.py similarity index 97% rename from org_eclipse_uprotocol/transport/validate/uattributesvalidator.py rename to uprotocol/transport/validate/uattributesvalidator.py index d20315c..3017fb0 100644 --- a/org_eclipse_uprotocol/transport/validate/uattributesvalidator.py +++ b/uprotocol/transport/validate/uattributesvalidator.py @@ -29,11 +29,11 @@ from datetime import datetime from enum import Enum -from org_eclipse_uprotocol.proto.uattributes_pb2 import UAttributes, UMessageType -from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode -from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator -from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils -from org_eclipse_uprotocol.validation.validationresult import ValidationResult +from uprotocol.proto.uattributes_pb2 import UAttributes, UMessageType +from uprotocol.proto.ustatus_pb2 import UCode +from uprotocol.uri.validator.urivalidator import UriValidator +from uprotocol.uuid.factory.uuidutils import UUIDUtils +from uprotocol.validation.validationresult import ValidationResult class UAttributesValidator: diff --git a/org_eclipse_uprotocol/uri/README.adoc b/uprotocol/uri/README.adoc similarity index 100% rename from org_eclipse_uprotocol/uri/README.adoc rename to uprotocol/uri/README.adoc diff --git a/org_eclipse_uprotocol_tests/test_transport/test_builder/__init__.py b/uprotocol/uri/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/test_transport/test_builder/__init__.py rename to uprotocol/uri/__init__.py diff --git a/org_eclipse_uprotocol_tests/test_transport/test_validate/__init__.py b/uprotocol/uri/builder/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/test_transport/test_validate/__init__.py rename to uprotocol/uri/builder/__init__.py diff --git a/org_eclipse_uprotocol/uri/builder/uresource_builder.py b/uprotocol/uri/builder/uresource_builder.py similarity index 96% rename from org_eclipse_uprotocol/uri/builder/uresource_builder.py rename to uprotocol/uri/builder/uresource_builder.py index f2ffb61..6df1be5 100644 --- a/org_eclipse_uprotocol/uri/builder/uresource_builder.py +++ b/uprotocol/uri/builder/uresource_builder.py @@ -25,7 +25,7 @@ # ------------------------------------------------------------------------- -from org_eclipse_uprotocol.proto.uri_pb2 import UResource +from uprotocol.proto.uri_pb2 import UResource class UResourceBuilder: diff --git a/org_eclipse_uprotocol_tests/test_uri/__init__.py b/uprotocol/uri/serializer/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/test_uri/__init__.py rename to uprotocol/uri/serializer/__init__.py diff --git a/org_eclipse_uprotocol/uri/serializer/longuriserializer.py b/uprotocol/uri/serializer/longuriserializer.py similarity index 95% rename from org_eclipse_uprotocol/uri/serializer/longuriserializer.py rename to uprotocol/uri/serializer/longuriserializer.py index c35363d..a0afe26 100644 --- a/org_eclipse_uprotocol/uri/serializer/longuriserializer.py +++ b/uprotocol/uri/serializer/longuriserializer.py @@ -27,12 +27,12 @@ import re -from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority -from org_eclipse_uprotocol.proto.uri_pb2 import UEntity -from org_eclipse_uprotocol.proto.uri_pb2 import UResource -from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.uri.serializer.uriserializer import UriSerializer -from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator +from uprotocol.proto.uri_pb2 import UAuthority +from uprotocol.proto.uri_pb2 import UEntity +from uprotocol.proto.uri_pb2 import UResource +from uprotocol.proto.uri_pb2 import UUri +from uprotocol.uri.serializer.uriserializer import UriSerializer +from uprotocol.uri.validator.urivalidator import UriValidator class LongUriSerializer(UriSerializer): diff --git a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py b/uprotocol/uri/serializer/microuriserializer.py similarity index 94% rename from org_eclipse_uprotocol/uri/serializer/microuriserializer.py rename to uprotocol/uri/serializer/microuriserializer.py index 69e66ad..80ab4c6 100644 --- a/org_eclipse_uprotocol/uri/serializer/microuriserializer.py +++ b/uprotocol/uri/serializer/microuriserializer.py @@ -29,12 +29,12 @@ import struct from enum import Enum -from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority -from org_eclipse_uprotocol.proto.uri_pb2 import UEntity -from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.uri.builder.uresource_builder import UResourceBuilder -from org_eclipse_uprotocol.uri.serializer.uriserializer import UriSerializer -from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator +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.serializer.uriserializer import UriSerializer +from uprotocol.uri.validator.urivalidator import UriValidator class AddressType(Enum): diff --git a/org_eclipse_uprotocol/uri/serializer/uriserializer.py b/uprotocol/uri/serializer/uriserializer.py similarity index 90% rename from org_eclipse_uprotocol/uri/serializer/uriserializer.py rename to uprotocol/uri/serializer/uriserializer.py index f667d74..49c527e 100644 --- a/org_eclipse_uprotocol/uri/serializer/uriserializer.py +++ b/uprotocol/uri/serializer/uriserializer.py @@ -28,8 +28,8 @@ from abc import ABC, abstractmethod from re import T from typing import Optional -from org_eclipse_uprotocol.proto.uri_pb2 import UUri,UAuthority,UEntity,UResource -from org_eclipse_uprotocol.uri.validator.urivalidator import UriValidator +from uprotocol.proto.uri_pb2 import UUri,UAuthority,UEntity,UResource +from uprotocol.uri.validator.urivalidator import UriValidator class UriSerializer(ABC): @@ -67,8 +67,8 @@ def build_resolved(self, long_uri: str, micro_uri: bytes) -> UUri: """ if (not long_uri or long_uri.isspace()) and (not micro_uri or len(micro_uri) == 0): return UUri() - from org_eclipse_uprotocol.uri.serializer.longuriserializer import LongUriSerializer - from org_eclipse_uprotocol.uri.serializer.microuriserializer import MicroUriSerializer + from uprotocol.uri.serializer.longuriserializer import LongUriSerializer + from uprotocol.uri.serializer.microuriserializer import MicroUriSerializer long_u_uri = LongUriSerializer().deserialize(long_uri) micro_u_uri = MicroUriSerializer().deserialize(micro_uri) u_authority = UAuthority() diff --git a/org_eclipse_uprotocol_tests/test_uri/test_serializer/__init__.py b/uprotocol/uri/validator/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/test_uri/test_serializer/__init__.py rename to uprotocol/uri/validator/__init__.py diff --git a/org_eclipse_uprotocol/uri/validator/urivalidator.py b/uprotocol/uri/validator/urivalidator.py similarity index 95% rename from org_eclipse_uprotocol/uri/validator/urivalidator.py rename to uprotocol/uri/validator/urivalidator.py index 84fc285..ca3a7f8 100644 --- a/org_eclipse_uprotocol/uri/validator/urivalidator.py +++ b/uprotocol/uri/validator/urivalidator.py @@ -25,11 +25,11 @@ # ------------------------------------------------------------------------- -from org_eclipse_uprotocol.proto.uri_pb2 import UAuthority -from org_eclipse_uprotocol.proto.uri_pb2 import UEntity -from org_eclipse_uprotocol.proto.uri_pb2 import UResource -from org_eclipse_uprotocol.proto.uri_pb2 import UUri -from org_eclipse_uprotocol.validation.validationresult import ValidationResult +from uprotocol.proto.uri_pb2 import UAuthority +from uprotocol.proto.uri_pb2 import UEntity +from uprotocol.proto.uri_pb2 import UResource +from uprotocol.proto.uri_pb2 import UUri +from uprotocol.validation.validationresult import ValidationResult class UriValidator: diff --git a/org_eclipse_uprotocol/uuid/README.adoc b/uprotocol/uuid/README.adoc similarity index 100% rename from org_eclipse_uprotocol/uuid/README.adoc rename to uprotocol/uuid/README.adoc diff --git a/org_eclipse_uprotocol_tests/test_uri/test_validator/__init__.py b/uprotocol/uuid/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/test_uri/test_validator/__init__.py rename to uprotocol/uuid/__init__.py diff --git a/org_eclipse_uprotocol/uuid/factory/__init__.py b/uprotocol/uuid/factory/__init__.py similarity index 100% rename from org_eclipse_uprotocol/uuid/factory/__init__.py rename to uprotocol/uuid/factory/__init__.py diff --git a/org_eclipse_uprotocol/uuid/factory/uuidfactory.py b/uprotocol/uuid/factory/uuidfactory.py similarity index 93% rename from org_eclipse_uprotocol/uuid/factory/uuidfactory.py rename to uprotocol/uuid/factory/uuidfactory.py index eafe697..2b62b97 100644 --- a/org_eclipse_uprotocol/uuid/factory/uuidfactory.py +++ b/uprotocol/uuid/factory/uuidfactory.py @@ -30,9 +30,9 @@ from enum import Enum -from org_eclipse_uprotocol.proto.uuid_pb2 import UUID -from org_eclipse_uprotocol.uuid.factory import * -from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils +from uprotocol.proto.uuid_pb2 import UUID +from uprotocol.uuid.factory import * +from uprotocol.uuid.factory.uuidutils import UUIDUtils class UUIDFactory: diff --git a/org_eclipse_uprotocol/uuid/factory/uuidutils.py b/uprotocol/uuid/factory/uuidutils.py similarity index 96% rename from org_eclipse_uprotocol/uuid/factory/uuidutils.py rename to uprotocol/uuid/factory/uuidutils.py index e3ab558..8ef0c0e 100644 --- a/org_eclipse_uprotocol/uuid/factory/uuidutils.py +++ b/uprotocol/uuid/factory/uuidutils.py @@ -31,8 +31,8 @@ from typing import Optional -from org_eclipse_uprotocol.proto.uuid_pb2 import UUID -from org_eclipse_uprotocol.uuid.factory import PythonUUID +from uprotocol.proto.uuid_pb2 import UUID +from uprotocol.uuid.factory import PythonUUID class Version(Enum): @@ -162,5 +162,5 @@ def get_msb_lsb(uuid: PythonUUID): def create_pythonuuid_from_eclipseuuid(uuid:UUID) -> PythonUUID: combined_int = (uuid.msb << 64) + uuid.lsb return PythonUUID(int=combined_int) - # from org_eclipse_uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer + # from uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer # return PythonUUID(hex=LongUuidSerializer.instance().serialize(uuid)) diff --git a/org_eclipse_uprotocol_tests/test_uuid/__init__.py b/uprotocol/uuid/serializer/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/test_uuid/__init__.py rename to uprotocol/uuid/serializer/__init__.py diff --git a/org_eclipse_uprotocol/uuid/serializer/longuuidserializer.py b/uprotocol/uuid/serializer/longuuidserializer.py similarity index 90% rename from org_eclipse_uprotocol/uuid/serializer/longuuidserializer.py rename to uprotocol/uuid/serializer/longuuidserializer.py index 556f61e..239bb65 100644 --- a/org_eclipse_uprotocol/uuid/serializer/longuuidserializer.py +++ b/uprotocol/uuid/serializer/longuuidserializer.py @@ -25,10 +25,10 @@ # ------------------------------------------------------------------------- -from org_eclipse_uprotocol.proto.uuid_pb2 import UUID -from org_eclipse_uprotocol.uuid.factory import PythonUUID -from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils -from org_eclipse_uprotocol.uuid.serializer.uuidserializer import UuidSerializer +from uprotocol.proto.uuid_pb2 import UUID +from uprotocol.uuid.factory import PythonUUID +from uprotocol.uuid.factory.uuidutils import UUIDUtils +from uprotocol.uuid.serializer.uuidserializer import UuidSerializer class LongUuidSerializer(UuidSerializer): diff --git a/org_eclipse_uprotocol/uuid/serializer/microuuidserializer.py b/uprotocol/uuid/serializer/microuuidserializer.py similarity index 91% rename from org_eclipse_uprotocol/uuid/serializer/microuuidserializer.py rename to uprotocol/uuid/serializer/microuuidserializer.py index e557940..0b55bfe 100644 --- a/org_eclipse_uprotocol/uuid/serializer/microuuidserializer.py +++ b/uprotocol/uuid/serializer/microuuidserializer.py @@ -27,9 +27,9 @@ import struct -from org_eclipse_uprotocol.proto.uuid_pb2 import UUID -from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils -from org_eclipse_uprotocol.uuid.serializer.uuidserializer import UuidSerializer +from uprotocol.proto.uuid_pb2 import UUID +from uprotocol.uuid.factory.uuidutils import UUIDUtils +from uprotocol.uuid.serializer.uuidserializer import UuidSerializer class MicroUuidSerializer(UuidSerializer): diff --git a/org_eclipse_uprotocol/uuid/serializer/uuidserializer.py b/uprotocol/uuid/serializer/uuidserializer.py similarity index 97% rename from org_eclipse_uprotocol/uuid/serializer/uuidserializer.py rename to uprotocol/uuid/serializer/uuidserializer.py index ebc1111..44356d4 100644 --- a/org_eclipse_uprotocol/uuid/serializer/uuidserializer.py +++ b/uprotocol/uuid/serializer/uuidserializer.py @@ -28,7 +28,7 @@ from abc import ABC, abstractmethod from typing import TypeVar, Generic -from org_eclipse_uprotocol.proto.uuid_pb2 import UUID +from uprotocol.proto.uuid_pb2 import UUID T = TypeVar('T') diff --git a/org_eclipse_uprotocol_tests/test_uuid/test_factory/__init__.py b/uprotocol/uuid/validate/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/test_uuid/test_factory/__init__.py rename to uprotocol/uuid/validate/__init__.py diff --git a/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py b/uprotocol/uuid/validate/uuidvalidator.py similarity index 93% rename from org_eclipse_uprotocol/uuid/validate/uuidvalidator.py rename to uprotocol/uuid/validate/uuidvalidator.py index 33d2246..dce36cd 100644 --- a/org_eclipse_uprotocol/uuid/validate/uuidvalidator.py +++ b/uprotocol/uuid/validate/uuidvalidator.py @@ -28,10 +28,10 @@ from collections import namedtuple from enum import Enum -from org_eclipse_uprotocol.proto.uuid_pb2 import UUID -from org_eclipse_uprotocol.uuid.factory.uuidutils import UUIDUtils, Version -from org_eclipse_uprotocol.validation.validationresult import ValidationResult -from org_eclipse_uprotocol.proto.ustatus_pb2 import UStatus, UCode +from uprotocol.proto.uuid_pb2 import UUID +from uprotocol.uuid.factory.uuidutils import UUIDUtils, Version +from uprotocol.validation.validationresult import ValidationResult +from uprotocol.proto.ustatus_pb2 import UStatus, UCode class UuidVariant(Enum): diff --git a/org_eclipse_uprotocol_tests/test_uuid/test_validator/__init__.py b/uprotocol/validation/__init__.py similarity index 100% rename from org_eclipse_uprotocol_tests/test_uuid/test_validator/__init__.py rename to uprotocol/validation/__init__.py diff --git a/org_eclipse_uprotocol/validation/validationresult.py b/uprotocol/validation/validationresult.py similarity index 97% rename from org_eclipse_uprotocol/validation/validationresult.py rename to uprotocol/validation/validationresult.py index 75fd98d..0d8e37c 100644 --- a/org_eclipse_uprotocol/validation/validationresult.py +++ b/uprotocol/validation/validationresult.py @@ -27,7 +27,7 @@ from abc import ABC, abstractmethod -from org_eclipse_uprotocol.proto.ustatus_pb2 import UCode, UStatus +from uprotocol.proto.ustatus_pb2 import UCode, UStatus From be96dd6e8925d04a03be5a2f210509a3b1e20eba Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Thu, 30 Nov 2023 16:57:16 -0500 Subject: [PATCH 44/47] Fix link in read me file --- README.adoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.adoc b/README.adoc index 754c301..dd5a136 100644 --- a/README.adoc +++ b/README.adoc @@ -53,19 +53,19 @@ The SDK is broken up into different packages that are described in < Date: Fri, 1 Dec 2023 11:45:34 -0500 Subject: [PATCH 45/47] Document clean and test and add file to gitignore --- .gitignore | 1 + README.adoc | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/.gitignore b/.gitignore index 844ec77..0235885 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ **/*.egg-info/ **/.DS_Store .vscode +.coverage \ No newline at end of file diff --git a/README.adoc b/README.adoc index dd5a136..641a20c 100644 --- a/README.adoc +++ b/README.adoc @@ -71,3 +71,16 @@ The SDK is broken up into different packages that are described in <> for examples of how to use the different data types and their factories, validators, and serializers. + + +=== Cleaning Up + +Clean up by running the command: +`python clean_project.py` + +=== Running the Tests + +Requires coverage to be installed first, that can be done by running `pip install coverage` + +then you run: +`python -m coverage run --source tests/ -m unittest discover` From e194a002ba7293361b04a0289f1e3dd3575c3a0c Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Fri, 1 Dec 2023 12:41:34 -0500 Subject: [PATCH 46/47] Remove all warnings --- .../test_base64protobufserializer.py | 12 ++-- .../test_cloudeventtojsonserializer.py | 14 ++-- .../test_cloudeventvalidator.py | 66 +++++++++---------- tests/test_rpc/test_rpc.py | 28 ++++---- .../datamodel/ucloudeventattributes.py | 2 +- uprotocol/rpc/rpcmapper.py | 32 +++++---- 6 files changed, 76 insertions(+), 78 deletions(-) diff --git a/tests/test_cloudevent/test_serialize/test_base64protobufserializer.py b/tests/test_cloudevent/test_serialize/test_base64protobufserializer.py index 97df806..3148ab9 100644 --- a/tests/test_cloudevent/test_serialize/test_base64protobufserializer.py +++ b/tests/test_cloudevent/test_serialize/test_base64protobufserializer.py @@ -41,17 +41,17 @@ def test_deserialize_bytes_to_string(self): ce.__delitem__("time") bytes_data = CloudEventSerializers.PROTOBUF.serializer().serialize(ce) payload = Base64ProtobufSerializer().deserialize(bytes_data) - self.assertEquals( + self.assertEqual( "CgVoZWxsbxIQaHR0cDovL2xvY2FsaG9zdBoDMS4wIg1leGFtcGxlLnZlcnR4", payload) def test_deserialize_bytes_to_string_when_bytes_is_null(self): payload = Base64ProtobufSerializer().deserialize(None) - self.assertEquals("", payload) + self.assertEqual("", payload) def test_deserialize_bytes_to_string_when_bytes_is_empty(self): payload = Base64ProtobufSerializer().deserialize(bytearray()) - self.assertEquals("", payload) + self.assertEqual("", payload) def test_serialize_string_into_bytes(self): json_str = "eyJzcGVjdmVyc2lvbiI6ICIxLjAiLCAiaWQiOiAiaGVsbG8iLCAic291cmNlIjogImh0dHA6Ly9sb2NhbGhvc3QiLCAidHlwZSI6ICJleGFtcGxlLnZlcnR4IiwgImRhdGFfYmFzZTY0IjogIiJ9" @@ -62,15 +62,15 @@ def test_serialize_string_into_bytes(self): ce.__delitem__("time") bytes_data = CloudEventSerializers.JSON.serializer().serialize(ce) - self.assertEquals(bytes_json, bytes_data) + self.assertEqual(bytes_json, bytes_data) def test_serialize_string_into_bytes_when_string_is_null(self): bytes_data = Base64ProtobufSerializer().serialize(None) - self.assertEquals(bytearray(), bytes_data) + self.assertEqual(bytearray(), bytes_data) def test_serialize_string_into_bytes_when_string_is_empty(self): bytes_data = Base64ProtobufSerializer().serialize('') - self.assertEquals(bytearray(), bytes_data) + self.assertEqual(bytearray(), bytes_data) if __name__ == '__main__': diff --git a/tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py b/tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py index 77c21c3..a4a58e2 100644 --- a/tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py +++ b/tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py @@ -74,7 +74,7 @@ def test_serialize_cloud_event_to_json(self): '"type": "pub.v1", "datacontenttype": "application/x-protobuf", "dataschema": ' '"type.googleapis.com/io.cloudevents.v1.CloudEvent", "data_base64": ' '"CjB0eXBlLmdvb2dsZWFwaXMuY29tL2lvLmNsb3VkZXZlbnRzLnYxLkNsb3VkRXZlbnQSPQoFaGVsbG8SE2h0dHBzOi8vZXhhbXBsZS5jb20aAzEuMCIMZXhhbXBsZS5kZW1vKgoKA3R0bBIDGgEzQgA=", "ttl": 3, "priority": "UPRIORITY_CS1"}') - self.assertEquals(expected, json_str) + self.assertEqual(expected, json_str) def test_serialize_and_deserialize_cloud_event_to_json(self): proto_payload = build_proto_payload_for_test() @@ -95,7 +95,7 @@ def test_serialize_and_deserialize_cloud_event_to_json(self): deserialized_data = serializer.deserialize(serialized_data) deserialized_data.__delitem__("time") - self.assertEquals(cloud_event, deserialized_data) + self.assertEqual(cloud_event, deserialized_data) def test_double_serialization_protobuf_when_creating_cloud_event_with_factory_methods(self): proto_payload = build_proto_payload_for_test() @@ -115,15 +115,15 @@ def test_double_serialization_protobuf_when_creating_cloud_event_with_factory_me serialized_data1 = serializer.serialize(cloud_event1) cloud_event2 = serializer.deserialize(serialized_data1) cloud_event2.__delitem__("time") - self.assertEquals(cloud_event2, cloud_event1) + self.assertEqual(cloud_event2, cloud_event1) serialized_data2 = serializer.serialize(cloud_event2) - self.assertEquals(serialized_data1, serialized_data2) + self.assertEqual(serialized_data1, serialized_data2) cloud_event3 = serializer.deserialize(serialized_data2) cloud_event3.__delitem__("time") payload1 = UCloudEvent.unpack(cloud_event3, CloudEvent) - self.assertEquals(cloud_event2, cloud_event3) + self.assertEqual(cloud_event2, cloud_event3) payload2 = CloudEvent() payload2.ParseFromString(proto_payload.value) - self.assertEquals(payload1, payload2) - self.assertEquals(cloud_event1, cloud_event3) + self.assertEqual(payload1, payload2) + self.assertEqual(cloud_event1, cloud_event3) diff --git a/tests/test_cloudevent/test_validator/test_cloudeventvalidator.py b/tests/test_cloudevent/test_validator/test_cloudeventvalidator.py index f83c43a..228cf2f 100644 --- a/tests/test_cloudevent/test_validator/test_cloudeventvalidator.py +++ b/tests/test_cloudevent/test_validator/test_cloudeventvalidator.py @@ -103,8 +103,8 @@ def test_publish_cloud_event_type(self): cloud_event.__setitem__("type", "res.v1") validator = Validators.PUBLISH.validator() status = validator.validate_type(cloud_event).to_status() - self.assertEquals(UCode.INVALID_ARGUMENT, status.code) - self.assertEquals("Invalid CloudEvent type [res.v1]. CloudEvent of type Publish must have a type of 'pub.v1'", + self.assertEqual(UCode.INVALID_ARGUMENT, status.code) + self.assertEqual("Invalid CloudEvent type [res.v1]. CloudEvent of type Publish must have a type of 'pub.v1'", status.message) def test_notification_cloud_event_type(self): @@ -112,8 +112,8 @@ def test_notification_cloud_event_type(self): cloud_event.__setitem__("type", "res.v1") validator = Validators.NOTIFICATION.validator() status = validator.validate_type(cloud_event).to_status() - self.assertEquals(UCode.INVALID_ARGUMENT, status.code) - self.assertEquals("Invalid CloudEvent type [res.v1]. CloudEvent of type Publish must have a type of 'pub.v1'", + self.assertEqual(UCode.INVALID_ARGUMENT, status.code) + self.assertEqual("Invalid CloudEvent type [res.v1]. CloudEvent of type Publish must have a type of 'pub.v1'", status.message) def test_get_a_request_cloud_event_validator(self): @@ -129,8 +129,8 @@ def test_request_cloud_event_type(self): cloud_event.__setitem__("type", "pub.v1") validator = Validators.REQUEST.validator() status = validator.validate_type(cloud_event).to_status() - self.assertEquals(UCode.INVALID_ARGUMENT, status.code) - self.assertEquals("Invalid CloudEvent type [pub.v1]. CloudEvent of type Request must have a type of 'req.v1'", + self.assertEqual(UCode.INVALID_ARGUMENT, status.code) + self.assertEqual("Invalid CloudEvent type [pub.v1]. CloudEvent of type Request must have a type of 'req.v1'", status.message) def test_get_a_response_cloud_event_validator(self): @@ -146,8 +146,8 @@ def test_response_cloud_event_type(self): cloud_event.__setitem__("type", "pub.v1") validator = Validators.RESPONSE.validator() status = validator.validate_type(cloud_event).to_status() - self.assertEquals(UCode.INVALID_ARGUMENT, status.code) - self.assertEquals("Invalid CloudEvent type [pub.v1]. CloudEvent of type Response must have a type of 'res.v1'", + self.assertEqual(UCode.INVALID_ARGUMENT, status.code) + self.assertEqual("Invalid CloudEvent type [pub.v1]. CloudEvent of type Response must have a type of 'res.v1'", status.message) def test_get_a_publish_cloud_event_validator_when_cloud_event_type_is_unknown(self): @@ -173,8 +173,8 @@ def test_validate_cloud_event_version_when_not_valid(self): cloud_event.__setitem__("specversion", "0.3") cloud_event.__setitem__("id", str_uuid) status = CloudEventValidator.validate_version(cloud_event).to_status() - self.assertEquals(UCode.INVALID_ARGUMENT, status.code) - self.assertEquals("Invalid CloudEvent version [0.3]. CloudEvent version must be 1.0.", status.message) + self.assertEqual(UCode.INVALID_ARGUMENT, status.code) + self.assertEqual("Invalid CloudEvent version [0.3]. CloudEvent version must be 1.0.", status.message) def test_validate_cloud_event_id_when_valid(self): uuid = Factories.UPROTOCOL.create() @@ -191,8 +191,8 @@ def test_validate_cloud_event_id_when_not_uuidv8_type_id(self): cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "pub.v1") status = CloudEventValidator.validate_id(cloud_event).to_status() - self.assertEquals(UCode.INVALID_ARGUMENT, status.code) - self.assertEquals("Invalid CloudEvent Id [" + str_uuid + "]. CloudEvent Id must be of type UUIDv8.", + self.assertEqual(UCode.INVALID_ARGUMENT, status.code) + self.assertEqual("Invalid CloudEvent Id [" + str_uuid + "]. CloudEvent Id must be of type UUIDv8.", status.message) def test_validate_cloud_event_id_when_not_valid(self): @@ -200,8 +200,8 @@ def test_validate_cloud_event_id_when_not_valid(self): cloud_event.__setitem__("id", "testme") cloud_event.__setitem__("type", "pub.v1") status = CloudEventValidator.validate_id(cloud_event).to_status() - self.assertEquals(UCode.INVALID_ARGUMENT, status.code) - self.assertEquals("Invalid CloudEvent Id [testme]. CloudEvent Id must be of type UUIDv8.", status.message) + self.assertEqual(UCode.INVALID_ARGUMENT, status.code) + self.assertEqual("Invalid CloudEvent Id [testme]. CloudEvent Id must be of type UUIDv8.", status.message) def test_publish_type_cloudevent_is_valid_when_everything_is_valid_local(self): uuid = Factories.UPROTOCOL.create() @@ -212,7 +212,7 @@ def test_publish_type_cloudevent_is_valid_when_everything_is_valid_local(self): cloud_event.__setitem__("source", "/body.access/1/door.front_left#Door") validator = Validators.PUBLISH.validator() result = validator.validate(cloud_event) - self.assertEquals(ValidationResult.success(), result) + self.assertEqual(ValidationResult.success(), result) def test_publish_type_cloudevent_is_valid_when_everything_is_valid_remote(self): uuid = Factories.UPROTOCOL.create() @@ -223,7 +223,7 @@ def test_publish_type_cloudevent_is_valid_when_everything_is_valid_remote(self): cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/door.front_left#Door") validator = Validators.PUBLISH.validator() result = validator.validate(cloud_event) - self.assertEquals(ValidationResult.success(), result) + self.assertEqual(ValidationResult.success(), result) def test_publish_type_cloudevent_is_valid_when_everything_is_valid_remote_with_a_sink(self): uuid = Factories.UPROTOCOL.create() @@ -235,7 +235,7 @@ def test_publish_type_cloudevent_is_valid_when_everything_is_valid_remote_with_a cloud_event.__setitem__("sink", "//bo.cloud/petapp") validator = Validators.PUBLISH.validator() result = validator.validate(cloud_event) - self.assertEquals(ValidationResult.success(), result) + self.assertEqual(ValidationResult.success(), result) def test_publish_type_cloudevent_is_not_valid_when_remote_with_invalid_sink(self): uuid = Factories.UPROTOCOL.create() @@ -247,7 +247,7 @@ def test_publish_type_cloudevent_is_not_valid_when_remote_with_invalid_sink(self cloud_event.__setitem__("sink", "//bo.cloud") validator = Validators.PUBLISH.validator() result = validator.validate(cloud_event) - self.assertEquals("Invalid CloudEvent sink [//bo.cloud]. Uri is missing uSoftware Entity name.", + self.assertEqual("Invalid CloudEvent sink [//bo.cloud]. Uri is missing uSoftware Entity name.", result.get_message()) def test_publish_type_cloudevent_is_not_valid_when_source_is_empty(self): @@ -258,7 +258,7 @@ def test_publish_type_cloudevent_is_not_valid_when_source_is_empty(self): cloud_event.__setitem__("source", "/") validator = Validators.PUBLISH.validator() result = validator.validate(cloud_event) - self.assertEquals("Invalid Publish type CloudEvent source [/]. Uri is empty.", result.get_message()) + self.assertEqual("Invalid Publish type CloudEvent source [/]. Uri is empty.", result.get_message()) def test_publish_type_cloudevent_is_not_valid_when_source_is_missing_authority(self): cloud_event = build_base_cloud_event_for_test() @@ -267,7 +267,7 @@ def test_publish_type_cloudevent_is_not_valid_when_source_is_missing_authority(s cloud_event.__setitem__("source", "/body.access") validator = Validators.PUBLISH.validator() result = validator.validate(cloud_event) - self.assertEquals( + self.assertEqual( "Invalid CloudEvent Id [testme]. CloudEvent Id must be of type UUIDv8.," + "Invalid Publish type " + "CloudEvent source [/body.access]. UriPart is missing uResource name.", result.get_message()) @@ -279,7 +279,7 @@ def test_publish_type_cloudevent_is_not_valid_when_source_is_missing_message_inf cloud_event.__setitem__("source", "/body.access/1/door.front_left") validator = Validators.PUBLISH.validator() result = validator.validate(cloud_event) - self.assertEquals( + self.assertEqual( "Invalid CloudEvent Id [testme]. CloudEvent Id must be of type UUIDv8.," + "Invalid Publish type " + "CloudEvent source [/body.access/1/door.front_left]. UriPart is missing Message information.", result.get_message()) @@ -294,7 +294,7 @@ def test_notification_type_cloudevent_is_valid_when_everything_is_valid(self): cloud_event.__setitem__("sink", "//bo.cloud/petapp") validator = Validators.NOTIFICATION.validator() result = validator.validate(cloud_event) - self.assertEquals(ValidationResult.success(), result) + self.assertEqual(ValidationResult.success(), result) def test_notification_type_cloudevent_is_not_valid_missing_sink(self): uuid = Factories.UPROTOCOL.create() @@ -305,7 +305,7 @@ def test_notification_type_cloudevent_is_not_valid_missing_sink(self): cloud_event.__setitem__("source", "/body.access/1/door.front_left#Door") validator = Validators.NOTIFICATION.validator() result = validator.validate(cloud_event) - self.assertEquals("Invalid CloudEvent sink. Notification CloudEvent sink must be an uri.", + self.assertEqual("Invalid CloudEvent sink. Notification CloudEvent sink must be an uri.", result.get_message()) def test_notification_type_cloudevent_is_not_valid_invalid_sink(self): @@ -318,7 +318,7 @@ def test_notification_type_cloudevent_is_not_valid_invalid_sink(self): cloud_event.__setitem__("source", "/body.access/1/door.front_left#Door") validator = Validators.NOTIFICATION.validator() result = validator.validate(cloud_event) - self.assertEquals( + self.assertEqual( "Invalid Notification type CloudEvent sink [//bo.cloud]. Uri is missing uSoftware Entity name.", result.get_message()) @@ -332,7 +332,7 @@ def test_request_type_cloudevent_is_valid_when_everything_is_valid(self): cloud_event.__setitem__("source", "//bo.cloud/petapp//rpc.response") validator = Validators.REQUEST.validator() result = validator.validate(cloud_event) - self.assertEquals(ValidationResult.success(), result) + self.assertEqual(ValidationResult.success(), result) def test_request_type_cloudevent_is_not_valid_invalid_source(self): uuid = Factories.UPROTOCOL.create() @@ -344,7 +344,7 @@ def test_request_type_cloudevent_is_not_valid_invalid_source(self): cloud_event.__setitem__("source", "//bo.cloud/petapp//dog") validator = Validators.REQUEST.validator() result = validator.validate(cloud_event) - self.assertEquals( + self.assertEqual( "Invalid RPC Request CloudEvent source [//bo.cloud/petapp//dog]. " + "Invalid RPC uri application " + "response topic. UriPart is missing rpc.response.", result.get_message()) @@ -358,7 +358,7 @@ def test_request_type_cloudevent_is_not_valid_missing_sink(self): cloud_event.__setitem__("source", "//bo.cloud/petapp//rpc.response") validator = Validators.REQUEST.validator() result = validator.validate(cloud_event) - self.assertEquals( + self.assertEqual( "Invalid RPC Request CloudEvent sink. Request CloudEvent sink must be uri for the method to be called.", result.get_message()) @@ -372,7 +372,7 @@ def test_request_type_cloudevent_is_not_valid_invalid_sink_not_rpc_command(self) cloud_event.__setitem__("sink", "//VCU.myvin/body.access/1/UpdateDoor") validator = Validators.REQUEST.validator() result = validator.validate(cloud_event) - self.assertEquals( + self.assertEqual( "Invalid RPC Request CloudEvent sink [//VCU.myvin/body.access/1/UpdateDoor]. " + "Invalid RPC method " + "uri. UriPart should be the method to be called, or method from response.", result.get_message()) @@ -387,7 +387,7 @@ def test_response_type_cloudevent_is_valid_when_everything_is_valid(self): cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/rpc.UpdateDoor") validator = Validators.RESPONSE.validator() result = validator.validate(cloud_event) - self.assertEquals(ValidationResult.success(), result) + self.assertEqual(ValidationResult.success(), result) def test_response_type_cloudevent_is_not_valid_invalid_source(self): uuid = Factories.UPROTOCOL.create() @@ -399,7 +399,7 @@ def test_response_type_cloudevent_is_not_valid_invalid_source(self): cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/UpdateDoor") validator = Validators.RESPONSE.validator() result = validator.validate(cloud_event) - self.assertEquals( + self.assertEqual( "Invalid RPC Response CloudEvent source [//VCU.myvin/body.access/1/UpdateDoor]. " + "Invalid RPC " + "method uri. UriPart should be the method to be called, or method from response.", result.get_message()) @@ -413,7 +413,7 @@ def test_response_type_cloudevent_is_not_valid_missing_sink_and_invalid_source(s cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/UpdateDoor") validator = Validators.RESPONSE.validator() result = validator.validate(cloud_event) - self.assertEquals( + self.assertEqual( "Invalid RPC Response CloudEvent source [//VCU.myvin/body.access/1/UpdateDoor]. " + "Invalid RPC " + "method uri. UriPart should be the method to be called, or method from response.," + "Invalid" + " CloudEvent sink. Response CloudEvent sink must be uri the destination of the response.", result.get_message()) @@ -428,7 +428,7 @@ def test_response_type_cloudevent_is_not_valid_invalid_sink(self): cloud_event.__setitem__("source", "//VCU.myvin") validator = Validators.RESPONSE.validator() result = validator.validate(cloud_event) - self.assertEquals( + self.assertEqual( "Invalid RPC Response CloudEvent source [//VCU.myvin]. Invalid RPC method uri. Uri is missing " + "uSoftware Entity name.,Invalid RPC Response CloudEvent sink [//bo.cloud]. Invalid RPC uri " + "application response topic. Uri is missing uSoftware Entity name.", @@ -444,7 +444,7 @@ def test_response_type_cloudevent_is_not_valid_invalid_source_not_rpc_command(se cloud_event.__setitem__("sink", "//VCU.myvin/body.access/1/UpdateDoor") validator = Validators.RESPONSE.validator() result = validator.validate(cloud_event) - self.assertEquals( + self.assertEqual( "Invalid RPC Response CloudEvent source [//bo.cloud/petapp/1/dog]. Invalid RPC method uri. UriPart " + "should be the method to be called, or method from response.," + "Invalid RPC Response " + "CloudEvent " "sink [" diff --git a/tests/test_rpc/test_rpc.py b/tests/test_rpc/test_rpc.py index 138807c..6c1001e 100644 --- a/tests/test_rpc/test_rpc.py +++ b/tests/test_rpc/test_rpc.py @@ -173,7 +173,7 @@ def test_compose_happy_path(self): ReturnsNumber3().invoke_method(build_topic(), build_upayload(), build_uattributes()), Int32Value) mapped = rpc_response.map(lambda x: x.value + 5) self.assertTrue(rpc_response.isSuccess()) - self.assertEquals(8, mapped.successValue()) + self.assertEqual(8, mapped.successValue()) def test_compose_that_returns_status(self): rpc_response = RpcMapper.map_response_to_result( @@ -181,8 +181,8 @@ def test_compose_that_returns_status(self): Int32Value) mapped = rpc_response.map(lambda x: x.value + 5) self.assertTrue(rpc_response.isFailure()) - self.assertEquals(UCode.INVALID_ARGUMENT, mapped.failureValue().code) - self.assertEquals("boom", mapped.failureValue().message) + self.assertEqual(UCode.INVALID_ARGUMENT, mapped.failureValue().code) + self.assertEqual("boom", mapped.failureValue().message) def test_compose_with_failure(self): rpc_response = RpcMapper.map_response_to_result( @@ -191,51 +191,51 @@ def test_compose_with_failure(self): mapped = rpc_response.map(lambda x: x.value + 5) self.assertTrue(rpc_response.isFailure()) status = UStatus(code=UCode.UNKNOWN, message="Boom") - self.assertEquals(status, mapped.failureValue()) + self.assertEqual(status, mapped.failureValue()) 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()), CloudEvent) self.assertTrue(rpc_response.isSuccess()) - self.assertEquals(build_cloud_event(), rpc_response.successValue()) + 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()), CloudEvent) self.assertTrue(rpc_response.isFailure()) - self.assertEquals(UCode.INVALID_ARGUMENT, rpc_response.failureValue().code) - self.assertEquals("boom", rpc_response.failureValue().message) + self.assertEqual(UCode.INVALID_ARGUMENT, rpc_response.failureValue().code) + self.assertEqual("boom", rpc_response.failureValue().message) 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()), CloudEvent) self.assertTrue(rpc_response.isFailure()) - self.assertEquals(UCode.UNKNOWN, rpc_response.failureValue().code) - self.assertEquals("Boom", rpc_response.failureValue().message) + self.assertEqual(UCode.UNKNOWN, rpc_response.failureValue().code) + self.assertEqual("Boom", rpc_response.failureValue().message) 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()), CloudEvent) self.assertTrue(rpc_response.isFailure()) - self.assertEquals(UCode.UNKNOWN, rpc_response.failureValue().code) - self.assertEquals("Unknown payload type [type.googleapis.com/google.protobuf.Int32Value]. Expected [io.cloudevents.v1.CloudEvent]", rpc_response.failureValue().message) + 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) def test_success_invoke_method_happy_flow_using_mapResponse(self): rpc_response = RpcMapper.map_response( HappyPath().invoke_method(build_topic(), build_upayload(), build_uattributes()), CloudEvent) - self.assertEquals(build_cloud_event(), rpc_response) + 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()), CloudEvent) - - self.assertFalse(rpc_response) + exception=RuntimeError("Unknown payload type [type.googleapis.com/uprotocol.v1.UStatus]. Expected [CloudEvent]") + self.assertEqual(str(exception),str(rpc_response.exception())) diff --git a/uprotocol/cloudevent/datamodel/ucloudeventattributes.py b/uprotocol/cloudevent/datamodel/ucloudeventattributes.py index 20f82e3..d30f3e3 100644 --- a/uprotocol/cloudevent/datamodel/ucloudeventattributes.py +++ b/uprotocol/cloudevent/datamodel/ucloudeventattributes.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------- - +# # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one diff --git a/uprotocol/rpc/rpcmapper.py b/uprotocol/rpc/rpcmapper.py index 72d58e8..dcee680 100644 --- a/uprotocol/rpc/rpcmapper.py +++ b/uprotocol/rpc/rpcmapper.py @@ -43,7 +43,7 @@ class RpcMapper: """ @staticmethod - def map_response(response_future: Future, expected_cls): + def map_response(payload_future: Future, expected_cls): """ Map a response of CompletableFuture<UPayload> from Link into a CompletableFuture containing the declared expected return type of the RPC method or an exception.

@@ -52,33 +52,31 @@ def map_response(response_future: Future, expected_cls): @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, exception): - if exception: - raise exception + def handle_response(payload): + nonlocal response_future payload = payload.result() if not payload: - raise RuntimeError(f"Server returned a null payload. Expected {expected_cls.__name__}") + 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) if any_message.Is(expected_cls.DESCRIPTOR): - return RpcMapper.unpack_payload(any_message, expected_cls) - except Exception as e: - raise RuntimeError(f"{str(e)} [{UStatus.__name__}]") from e - - raise RuntimeError(f"Unknown payload type [{any_message.type_url}]. Expected [{expected_cls.__name__}]") - - result = None # Initialize result + response_future.set_result(RpcMapper.unpack_payload(any_message, expected_cls)) + else: + response_future.set_exception( + RuntimeError( + f"Unknown payload type [{any_message.type_url}]. Expected [{expected_cls.__name__}]")) - def callbackwrapper(payload, exception=None): - nonlocal result - result = handle_response(payload, exception) + except Exception as e: + response_future.set_exception(RuntimeError(f"{str(e)} [{UStatus.__name__}]")) - response_future.add_done_callback(callbackwrapper) + payload_future.add_done_callback(handle_response) - return result + return response_future @staticmethod def map_response_to_result(response_future: Future, expected_cls): From dbc6b4150b23e58eb1579a8a307d088b5636fd48 Mon Sep 17 00:00:00 2001 From: Neelam Kushwah Date: Fri, 1 Dec 2023 12:45:29 -0500 Subject: [PATCH 47/47] Remove all print statements from test folder --- .../test_datamodel/test_ucloudeventattributes.py | 1 - .../test_factory/test_cloudeventfactory.py | 2 +- tests/test_uri/test_serializer/test_longuriserializer.py | 4 ++-- .../test_uri/test_serializer/test_microuriserializer.py | 4 ++-- tests/test_uri/test_serializer/test_uriserializer.py | 4 ++-- tests/test_uri/test_validator/test_urivalidator.py | 5 ++--- tests/test_uuid/test_factory/test_uuidfactory.py | 9 ++++----- tests/test_uuid/test_validator/test_uuidvalidator.py | 4 ++-- 8 files changed, 15 insertions(+), 18 deletions(-) diff --git a/tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py b/tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py index 44449fc..1c16de9 100644 --- a/tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py +++ b/tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py @@ -34,7 +34,6 @@ class TestUCloudEventAttributes(unittest.TestCase): - def test_to_string(self): u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( UPriority.UPRIORITY_CS1).with_ttl(3).with_token("someOAuthToken").build() diff --git a/tests/test_cloudevent/test_factory/test_cloudeventfactory.py b/tests/test_cloudevent/test_factory/test_cloudeventfactory.py index fd04a4a..a4b1e2c 100644 --- a/tests/test_cloudevent/test_factory/test_cloudeventfactory.py +++ b/tests/test_cloudevent/test_factory/test_cloudeventfactory.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------- - +# # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one diff --git a/tests/test_uri/test_serializer/test_longuriserializer.py b/tests/test_uri/test_serializer/test_longuriserializer.py index 466a351..7b89c8f 100644 --- a/tests/test_uri/test_serializer/test_longuriserializer.py +++ b/tests/test_uri/test_serializer/test_longuriserializer.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------- - +# # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -21,7 +21,7 @@ # SPDX-FileType: SOURCE # SPDX-FileCopyrightText: 2023 General Motors GTO LLC # SPDX-License-Identifier: Apache-2.0 - +# # ------------------------------------------------------------------------- diff --git a/tests/test_uri/test_serializer/test_microuriserializer.py b/tests/test_uri/test_serializer/test_microuriserializer.py index 5bd9849..2b66f8e 100644 --- a/tests/test_uri/test_serializer/test_microuriserializer.py +++ b/tests/test_uri/test_serializer/test_microuriserializer.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------- - +# # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -21,7 +21,7 @@ # SPDX-FileType: SOURCE # SPDX-FileCopyrightText: 2023 General Motors GTO LLC # SPDX-License-Identifier: Apache-2.0 - +# # ------------------------------------------------------------------------- import socket diff --git a/tests/test_uri/test_serializer/test_uriserializer.py b/tests/test_uri/test_serializer/test_uriserializer.py index 18122df..5f0ef32 100644 --- a/tests/test_uri/test_serializer/test_uriserializer.py +++ b/tests/test_uri/test_serializer/test_uriserializer.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------- - +# # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -21,7 +21,7 @@ # SPDX-FileType: SOURCE # SPDX-FileCopyrightText: 2023 General Motors GTO LLC # SPDX-License-Identifier: Apache-2.0 - +# # ------------------------------------------------------------------------- diff --git a/tests/test_uri/test_validator/test_urivalidator.py b/tests/test_uri/test_validator/test_urivalidator.py index 3ada9eb..c674235 100644 --- a/tests/test_uri/test_validator/test_urivalidator.py +++ b/tests/test_uri/test_validator/test_urivalidator.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------- - +# # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -21,7 +21,7 @@ # SPDX-FileType: SOURCE # SPDX-FileCopyrightText: 2023 General Motors GTO LLC # SPDX-License-Identifier: Apache-2.0 - +# # ------------------------------------------------------------------------- @@ -320,7 +320,6 @@ def test_all_invalid_rpc_uris(self): invalid_rpc_uris = self.get_json_object()["invalidRpcUris"] for invalid_rpc_uri in invalid_rpc_uris: uuri = LongUriSerializer().deserialize(invalid_rpc_uri["uri"]) - print(invalid_rpc_uri['uri']) status = UriValidator.validate_rpc_method(uuri) self.assertTrue(status.is_failure()) self.assertEqual(status.get_message(), invalid_rpc_uri["status_message"]) diff --git a/tests/test_uuid/test_factory/test_uuidfactory.py b/tests/test_uuid/test_factory/test_uuidfactory.py index 0ac27a9..badf2f4 100644 --- a/tests/test_uuid/test_factory/test_uuidfactory.py +++ b/tests/test_uuid/test_factory/test_uuidfactory.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------- - +# # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -21,7 +21,7 @@ # SPDX-FileType: SOURCE # SPDX-FileCopyrightText: 2023 General Motors GTO LLC # SPDX-License-Identifier: Apache-2.0 - +# # ------------------------------------------------------------------------- @@ -275,9 +275,8 @@ def test_create_both_uuidv6_and_v8_to_compare_performance(self): for _ in range(max_count): uuidv6_list.append(Factories.UUIDV6.create()) v6_diff = datetime.utcnow() - start - - print( - f"UUIDv8: [{v8_diff.total_seconds() / max_count}s] UUIDv6: [{v6_diff.total_seconds() / max_count}s]") + # print( + # f"UUIDv8: [{v8_diff.total_seconds() / max_count}s] UUIDv6: [{v6_diff.total_seconds() / max_count}s]") if __name__ == '__main__': diff --git a/tests/test_uuid/test_validator/test_uuidvalidator.py b/tests/test_uuid/test_validator/test_uuidvalidator.py index c0752b6..45e0455 100644 --- a/tests/test_uuid/test_validator/test_uuidvalidator.py +++ b/tests/test_uuid/test_validator/test_uuidvalidator.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------- - +# # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -21,7 +21,7 @@ # SPDX-FileType: SOURCE # SPDX-FileCopyrightText: 2023 General Motors GTO LLC # SPDX-License-Identifier: Apache-2.0 - +# # -------------------------------------------------------------------------