From b704efb5b6c93eded3330d4ac633e128e57da162 Mon Sep 17 00:00:00 2001 From: wangshouqi Date: Thu, 8 Aug 2024 08:37:17 +0000 Subject: [PATCH 1/2] update README.md. Signed-off-by: wangshouqi --- README.md | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/README.md b/README.md index e56af47..bc5b660 100644 --- a/README.md +++ b/README.md @@ -3,21 +3,7 @@ #### 介绍 鸿蒙高性能序列化对象代码生成工具为开发者提供便捷的代码转换服务。 -#### 软件架构 -软件架构说明 - - -#### 安装教程 - -1. xxxx -2. xxxx -3. xxxx - -#### 使用说明 - -1. xxxx -2. xxxx -3. xxxx +其中,tts-transformer分支是基于tts文件生成鸿蒙高性能序列化对象代码;proto-transformer分支是基于protobuf文件生成鸿蒙高性能序列化对象代码。 #### 参与贡献 -- Gitee From 4946271fabde9b4ee166a75dc87b78515cd75743 Mon Sep 17 00:00:00 2001 From: Gennji <3296766995@qq.com> Date: Tue, 13 Aug 2024 10:20:28 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 201 ++++ README.en.md | 4 +- README.md | 279 +++++- protobuf-bitfun/README.md | 10 + protobuf-bitfun/src/binary-encoding.ets | 782 ++++++++++++++++ protobuf-bitfun/src/binary-format.ets | 163 ++++ protobuf-bitfun/src/codegen-info.ets | 152 +++ protobuf-bitfun/src/descriptor-set.ets | 871 ++++++++++++++++++ protobuf-bitfun/src/enum.ts | 62 ++ protobuf-bitfun/src/field-list.ets | 58 ++ protobuf-bitfun/src/field.ets | 419 +++++++++ .../src/google/protobuf/any_pb.ets | 219 +++++ .../src/google/protobuf/duration_pb.ets | 97 ++ .../src/google/protobuf/empty_pb.ets | 85 ++ .../src/google/protobuf/timestamp_pb.ets | 92 ++ .../src/google/protobuf/wrappers_pb.ets | 769 ++++++++++++++++ protobuf-bitfun/src/google/varint.ets | 375 ++++++++ protobuf-bitfun/src/index.ets | 71 ++ protobuf-bitfun/src/is-message.ets | 66 ++ protobuf-bitfun/src/json-format.ets | 173 ++++ protobuf-bitfun/src/message-type.ets | 89 ++ protobuf-bitfun/src/message.ets | 157 ++++ protobuf-bitfun/src/private/assert.ts | 58 ++ .../src/private/binary-format-imp.ets | 123 +++ protobuf-bitfun/src/private/binary-format.ets | 603 ++++++++++++ protobuf-bitfun/src/private/common.ets | 64 ++ protobuf-bitfun/src/private/enum.ts | 135 +++ protobuf-bitfun/src/private/field-list.ets | 103 +++ .../src/private/field-normalize.ets | 75 ++ protobuf-bitfun/src/private/field-wrapper.ets | 83 ++ protobuf-bitfun/src/private/field.ets | 67 ++ .../src/private/json-format-imp.ets | 146 +++ protobuf-bitfun/src/private/json-format.ets | 650 +++++++++++++ protobuf-bitfun/src/private/message-type.ets | 32 + protobuf-bitfun/src/private/names.ets | 303 ++++++ protobuf-bitfun/src/private/proto-runtime.ets | 99 ++ protobuf-bitfun/src/private/reflect.ets | 102 ++ protobuf-bitfun/src/private/reify-wkt.ets | 93 ++ protobuf-bitfun/src/private/scalars.ets | 109 +++ protobuf-bitfun/src/private/text-format.ets | 214 +++++ protobuf-bitfun/src/private/type.ets | 27 + protobuf-bitfun/src/private/util-common.ets | 67 ++ protobuf-bitfun/src/private/util.ets | 429 +++++++++ protobuf-bitfun/src/proto-base64.ets | 137 +++ protobuf-bitfun/src/proto-double.ets | 33 + protobuf-bitfun/src/proto-int64.ets | 228 +++++ protobuf-bitfun/src/proto2.ets | 85 ++ protobuf-bitfun/src/proto3.ets | 87 ++ protobuf-bitfun/src/scalar.ets | 87 ++ protobuf-bitfun/src/service-type.ets | 148 +++ protobuf-bitfun/src/service/call-options.ets | 4 + protobuf-bitfun/src/service/transport.ets | 31 + protobuf-bitfun/src/type-registry.ets | 77 ++ protobuf-ets-main/.eslintrc.cjs | 103 +++ protobuf-ets-main/.gitattributes | 9 + protobuf-ets-main/.github/CODE_OF_CONDUCT.md | 133 +++ protobuf-ets-main/.github/CONTRIBUTING.md | 71 ++ protobuf-ets-main/.github/buf-logo.svg | 1 + protobuf-ets-main/.github/dependabot.yaml | 42 + .../.github/workflows/add-to-project.yaml | 21 + protobuf-ets-main/.github/workflows/ci.yaml | 47 + .../workflows/emergency-review-bypass.yaml | 12 + .../workflows/notify-approval-bypass.yaml | 12 + .../.github/workflows/pr-title.yaml | 18 + protobuf-ets-main/.nvmrc | 1 + protobuf-ets-main/.prettierignore | 6 + protobuf-ets-main/Makefile | 191 ++++ protobuf-ets-main/README.md | 80 ++ protobuf-ets-main/buf.gen.yaml | 6 + protobuf-ets-main/package.json | 32 + .../bin/protoc-gen-connect-ets | 6 + .../protoc-gen-connect-ets/package.json | 38 + .../src/protoc-gen-connect-ets-plugin.ts | 23 + .../src/runtime-import/names.ts | 312 +++++++ .../src/runtime-import/rumtime-imports.ts | 302 ++++++ .../src/runtime-import/scalars.ts | 59 ++ .../protoc-gen-connect-ets/src/typescript.ts | 111 +++ .../protoc-gen-connect-ets/src/util.ts | 30 + .../protoc-gen-connect-ets/tsconfig.json | 12 + protobuf-ets-main/protoc-gen-ets/.ohpmignore | 2 + .../protoc-gen-ets/bin/protoc-gen-ets | 6 + protobuf-ets-main/protoc-gen-ets/package.json | 38 + .../protoc-gen-ets/src/editions.ts | 48 + .../protoc-gen-ets/src/javascript.ts | 109 +++ .../src/protoc-gen-ets-plugin.ts | 23 + .../src/runtime-import/names.ts | 312 +++++++ .../src/runtime-import/rumtime-imports.ts | 302 ++++++ .../src/runtime-import/scalars.ts | 59 ++ .../protoc-gen-ets/src/typescript.ts | 484 ++++++++++ protobuf-ets-main/protoc-gen-ets/src/util.ts | 324 +++++++ .../protoc-gen-ets/tsconfig.json | 12 + .../scripts/get-workspace-publish-tag.js | 85 ++ .../scripts/set-workspace-version.js | 189 ++++ protobuf-ets-main/tsconfig.base.json | 24 + 94 files changed, 13370 insertions(+), 18 deletions(-) create mode 100644 LICENSE create mode 100644 protobuf-bitfun/README.md create mode 100644 protobuf-bitfun/src/binary-encoding.ets create mode 100644 protobuf-bitfun/src/binary-format.ets create mode 100644 protobuf-bitfun/src/codegen-info.ets create mode 100644 protobuf-bitfun/src/descriptor-set.ets create mode 100644 protobuf-bitfun/src/enum.ts create mode 100644 protobuf-bitfun/src/field-list.ets create mode 100644 protobuf-bitfun/src/field.ets create mode 100644 protobuf-bitfun/src/google/protobuf/any_pb.ets create mode 100644 protobuf-bitfun/src/google/protobuf/duration_pb.ets create mode 100644 protobuf-bitfun/src/google/protobuf/empty_pb.ets create mode 100644 protobuf-bitfun/src/google/protobuf/timestamp_pb.ets create mode 100644 protobuf-bitfun/src/google/protobuf/wrappers_pb.ets create mode 100644 protobuf-bitfun/src/google/varint.ets create mode 100644 protobuf-bitfun/src/index.ets create mode 100644 protobuf-bitfun/src/is-message.ets create mode 100644 protobuf-bitfun/src/json-format.ets create mode 100644 protobuf-bitfun/src/message-type.ets create mode 100644 protobuf-bitfun/src/message.ets create mode 100644 protobuf-bitfun/src/private/assert.ts create mode 100644 protobuf-bitfun/src/private/binary-format-imp.ets create mode 100644 protobuf-bitfun/src/private/binary-format.ets create mode 100644 protobuf-bitfun/src/private/common.ets create mode 100644 protobuf-bitfun/src/private/enum.ts create mode 100644 protobuf-bitfun/src/private/field-list.ets create mode 100644 protobuf-bitfun/src/private/field-normalize.ets create mode 100644 protobuf-bitfun/src/private/field-wrapper.ets create mode 100644 protobuf-bitfun/src/private/field.ets create mode 100644 protobuf-bitfun/src/private/json-format-imp.ets create mode 100644 protobuf-bitfun/src/private/json-format.ets create mode 100644 protobuf-bitfun/src/private/message-type.ets create mode 100644 protobuf-bitfun/src/private/names.ets create mode 100644 protobuf-bitfun/src/private/proto-runtime.ets create mode 100644 protobuf-bitfun/src/private/reflect.ets create mode 100644 protobuf-bitfun/src/private/reify-wkt.ets create mode 100644 protobuf-bitfun/src/private/scalars.ets create mode 100644 protobuf-bitfun/src/private/text-format.ets create mode 100644 protobuf-bitfun/src/private/type.ets create mode 100644 protobuf-bitfun/src/private/util-common.ets create mode 100644 protobuf-bitfun/src/private/util.ets create mode 100644 protobuf-bitfun/src/proto-base64.ets create mode 100644 protobuf-bitfun/src/proto-double.ets create mode 100644 protobuf-bitfun/src/proto-int64.ets create mode 100644 protobuf-bitfun/src/proto2.ets create mode 100644 protobuf-bitfun/src/proto3.ets create mode 100644 protobuf-bitfun/src/scalar.ets create mode 100644 protobuf-bitfun/src/service-type.ets create mode 100644 protobuf-bitfun/src/service/call-options.ets create mode 100644 protobuf-bitfun/src/service/transport.ets create mode 100644 protobuf-bitfun/src/type-registry.ets create mode 100644 protobuf-ets-main/.eslintrc.cjs create mode 100644 protobuf-ets-main/.gitattributes create mode 100644 protobuf-ets-main/.github/CODE_OF_CONDUCT.md create mode 100644 protobuf-ets-main/.github/CONTRIBUTING.md create mode 100644 protobuf-ets-main/.github/buf-logo.svg create mode 100644 protobuf-ets-main/.github/dependabot.yaml create mode 100644 protobuf-ets-main/.github/workflows/add-to-project.yaml create mode 100644 protobuf-ets-main/.github/workflows/ci.yaml create mode 100644 protobuf-ets-main/.github/workflows/emergency-review-bypass.yaml create mode 100644 protobuf-ets-main/.github/workflows/notify-approval-bypass.yaml create mode 100644 protobuf-ets-main/.github/workflows/pr-title.yaml create mode 100644 protobuf-ets-main/.nvmrc create mode 100644 protobuf-ets-main/.prettierignore create mode 100644 protobuf-ets-main/Makefile create mode 100644 protobuf-ets-main/README.md create mode 100644 protobuf-ets-main/buf.gen.yaml create mode 100644 protobuf-ets-main/package.json create mode 100644 protobuf-ets-main/protoc-gen-connect-ets/bin/protoc-gen-connect-ets create mode 100644 protobuf-ets-main/protoc-gen-connect-ets/package.json create mode 100644 protobuf-ets-main/protoc-gen-connect-ets/src/protoc-gen-connect-ets-plugin.ts create mode 100644 protobuf-ets-main/protoc-gen-connect-ets/src/runtime-import/names.ts create mode 100644 protobuf-ets-main/protoc-gen-connect-ets/src/runtime-import/rumtime-imports.ts create mode 100644 protobuf-ets-main/protoc-gen-connect-ets/src/runtime-import/scalars.ts create mode 100644 protobuf-ets-main/protoc-gen-connect-ets/src/typescript.ts create mode 100644 protobuf-ets-main/protoc-gen-connect-ets/src/util.ts create mode 100644 protobuf-ets-main/protoc-gen-connect-ets/tsconfig.json create mode 100644 protobuf-ets-main/protoc-gen-ets/.ohpmignore create mode 100644 protobuf-ets-main/protoc-gen-ets/bin/protoc-gen-ets create mode 100644 protobuf-ets-main/protoc-gen-ets/package.json create mode 100644 protobuf-ets-main/protoc-gen-ets/src/editions.ts create mode 100644 protobuf-ets-main/protoc-gen-ets/src/javascript.ts create mode 100644 protobuf-ets-main/protoc-gen-ets/src/protoc-gen-ets-plugin.ts create mode 100644 protobuf-ets-main/protoc-gen-ets/src/runtime-import/names.ts create mode 100644 protobuf-ets-main/protoc-gen-ets/src/runtime-import/rumtime-imports.ts create mode 100644 protobuf-ets-main/protoc-gen-ets/src/runtime-import/scalars.ts create mode 100644 protobuf-ets-main/protoc-gen-ets/src/typescript.ts create mode 100644 protobuf-ets-main/protoc-gen-ets/src/util.ts create mode 100644 protobuf-ets-main/protoc-gen-ets/tsconfig.json create mode 100644 protobuf-ets-main/scripts/get-workspace-publish-tag.js create mode 100644 protobuf-ets-main/scripts/set-workspace-version.js create mode 100644 protobuf-ets-main/tsconfig.base.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c95a26d --- /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 compiles 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/README.en.md b/README.en.md index 71c3b65..8284003 100644 --- a/README.en.md +++ b/README.en.md @@ -1,7 +1,7 @@ -# serialization_codegen +# BitFun_ProtoTransformer #### Description -鸿蒙高性能序列化对象代码生成工具为开发者提供便捷的代码转换服务。 +{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} #### Software Architecture Software architecture description diff --git a/README.md b/README.md index bc5b660..7a0e058 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,270 @@ -# serialization_codegen +# BitFun_ProtoTransformer -#### 介绍 -鸿蒙高性能序列化对象代码生成工具为开发者提供便捷的代码转换服务。 +## 工具介绍 -其中,tts-transformer分支是基于tts文件生成鸿蒙高性能序列化对象代码;proto-transformer分支是基于protobuf文件生成鸿蒙高性能序列化对象代码。 +#### 概述 +本工具是基于[protobuf-es](https://github.com/bufbuild/protobuf-es)改造的鸿蒙高性能序列化对象代码生成和运行工具,支持将proto文件和消息转换为鸿蒙[Sendable](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/arkts-utils/arkts-sendable.md)代码和对象 -#### 参与贡献 +#### 示例代码 -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +- ###### proto原始文件 +```proto +message User { + string first_name = 1; + string last_name = 2; + bool active = 3; + User manager = 4; + repeated string locations = 5; + map projects =6; +} +``` -#### 特技 +- ###### 生成的结果 -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +```tsx +import { BinaryReadOptions, FieldInfo, FieldList, FieldWrapper, +fiMapInterface, getFieldListsFromArray, JsonReadOptions, JsonValue, Message, +MessageType, proto3, ProtoRuntime } from "@bitfun/protobuf_ets"; + +import collections from "@arkts.collections" + +@Sendable +export class User extends Message { + /** + * @generated from field: string first_name = 1; + */ + firstName: string = ""; + + /** + * @generated from field: string last_name = 2; + */ + lastName: string = ""; + + /** + * @generated from field: bool active = 3; + */ + active: boolean = false; + + /** + * @generated from field: User manager = 4; + */ + manager?: User; + + /** + * @generated from field: repeated string locations = 5; + */ + locations: collections.Array = new collections.Array(); + + /** + * @generated from field: map projects = 6; + */ + projects: collections.Map = new collections.Map(); + + static fieldWrapper: FieldWrapper | undefined; + + static readonly typeName: string = "User"; + + static readonly runtime: ProtoRuntime = proto3; + + static readonly fieldList: collections.Map = new collections.Map(); + + static fields: FieldList = proto3.util.newFieldList(getFieldListsFromArray(() => { + let res = new collections.Array(); + res.push(new FieldInfo().buildNo(1).buildName("first_name").buildKind("scalar").buildT(9)); + res.push(new FieldInfo().buildNo(2).buildName("last_name").buildKind("scalar").buildT(9)); + res.push(new FieldInfo().buildNo(3).buildName("active").buildKind("scalar").buildT(8)); + res.push(new FieldInfo().buildNo(4).buildName("manager").buildKind("message").buildT(UserType_$1.instance)); + res.push(new FieldInfo().buildNo(5).buildName("locations").buildKind("scalar").buildT(9).buildRepeated(true)); + res.push(new FieldInfo().buildNo(6).buildName("projects").buildKind("map").buildK(9).buildV(new fiMapInterface("scalar", 9))); + return res; + })); + + constructor(data?: Message) { + super(); + proto3.util.initPartial(data, this); + } + + getType(): MessageType { + return UserType_$1.instance; + } + + static fromBinary(bytes: Uint8Array, options?: Partial): User { + return new User().getType().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): User { + return new User().getType().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): User { + return new User().getType().fromJson(jsonString, options); + } + + static equals(a: User | undefined, b: User | undefined): boolean { + return proto3.util.equals(UserType_$1.instance, a, b); + } +} + +@Sendable +export class UserType_$1 extends MessageType { + static instance: UserType_$1 = new UserType_$1(); + + constructor() { + super(); + } + + create(data?: Message): User { + return new User(data); + } + + equals(a: User | Message | undefined | null, b: User | Message | undefined | null): boolean { + return proto3.util.equals(this, a, b); + } + + clone(message: User): User { + return proto3.util.clone(message); + } + + getFieldWrapper(): FieldWrapper | undefined { + return User.fieldWrapper; + } + + getTypeName(): string { + return User.typeName; + } + + getFields(): FieldList { + return User.fields; + } + + getProtoRuntime(): ProtoRuntime { + return User.runtime; + } +} +``` + +- ###### 使用方式 + +``` +import { User } from User; + +let user = new User(); + +const bytes = user.toBinary(); +user = User.fromBinary(bytes); +user = User.fromJsonString('{"firstName": "Homer", "lastName": "Simpson"}'); +``` + + + +## QuickStart + +### 使用流程 + +- 根据环境准备安装好适配版本的node +- 根据**gen使用指导**,在node环境中使用gen生成对应的ets代码 +- 根据**runtime库构建**,在鸿蒙项目中安装protobuf_ets依赖包 +- 将第二步中生成的代码拖入鸿蒙工程中即可直接使用 + +### 环境准备 + +| **node** | **>=16** | +|------|------| +| **npm** | **>=8** | + +### runtime库构建: + +- ##### 公仓下载使用(目前正在审核中) + +```bash +ohpm i @bitfun/protobuf_ets +``` + + +- ##### ohpm私仓构建 + + 详细操作流程请见[ohpm上传指导](https://gitee.com/BitFun4HarmonyOS/bitfunprototransformer/wikis/%E4%BD%BF%E7%94%A8ohpm-repo%E4%B8%8A%E4%BC%A0%26%E4%B8%8B%E8%BD%BD%E4%B8%89%E6%96%B9%E5%8C%85%E6%8C%87%E5%8D%97) + +### gen使用指导: + +- ##### 离线引入方式 + +1. 生成tgz包 + + ```shell + cd protoc-gen-ets + npm run pack + ``` + + ```shell + cd protoc-gen-connect-ets + npm run pack + ``` + +2. 如何在自己的项目中使用? + + ```shell + npm install @bufbuild/buf @bufbuild/protobuf @bufbuild/protoplugin + npm install $path/bufbuild-protoc-gen-ets-1.0.0.tgz + npm install $path/bufbuild-protoc-gen-connect-ets-1.0.0.tgz + ``` + +3. 创建一个 `buf.gen.yaml` 文件: + + ```yaml + version: v1 + plugins: + - plugin: ets + opt: target=ts + out: src/gen + #如果需要生成server,加上配置项: + - plugin: connect-ets + opt: target=ts + out: src/gen + ``` + +4. 生成您的代码: + + ```shell + npx buf generate + ``` + +- ##### 镜像仓库引入 + +1. publish到仓库 + ```shell + cd protoc-gen-ets + npm publish + ``` + + ```shell + cd protoc-gen-connect-ets + npm publish + ``` + +2. 如何在自己的项目中使用? + + ```shell + npm install @bufbuild/buf @bufbuild/protobuf @bufbuild/protoplugin + npm install @bufbuild/protoc-gen-ets @bufbuild/protoc-gen-connect-ets + ``` + +3. 创建一个 `buf.gen.yaml` 文件: + + ```yaml + version: v1 + plugins: + - plugin: ets + opt: target=ts + out: src/gen + #如果需要生成server,加上配置项: + - plugin: connect-ets + opt: target=ts + out: src/gen + ``` +4. 生成您的代码: + + ```shell + npx buf generate + ``` diff --git a/protobuf-bitfun/README.md b/protobuf-bitfun/README.md new file mode 100644 index 0000000..1f4c86f --- /dev/null +++ b/protobuf-bitfun/README.md @@ -0,0 +1,10 @@ +1、runtime支持静态库引用的方式 +1)运行时需要依赖两个静态库,分别是protobufbitfun, runtime; +2)protobufbitfun包含protobuf-runtime ArkTS和Sendable改造,runtime包含service的sendable改造; +3)详情可以参考protobuf-runtime-BitFun分支下的demo工程。 + +2、gen生成的对象需要搭配上述两个staic library使用,在package.json配置相关依赖。 +"dependencies": { +"@bitfun/protobuf_ets": "file:../protobufbitfun", +"@ohos/runtime-bitfun": "file:../runtime" +} diff --git a/protobuf-bitfun/src/binary-encoding.ets b/protobuf-bitfun/src/binary-encoding.ets new file mode 100644 index 0000000..f9df908 --- /dev/null +++ b/protobuf-bitfun/src/binary-encoding.ets @@ -0,0 +1,782 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. +import { protoInt64 } from "./proto-int64"; +import { assertFloat32, assertInt32, assertUInt32 } from "./private/assert"; +import { varint32write, varint64write } from './google/varint'; + +import { util } from '@kit.ArkTS'; +import collections from "@arkts.collections"; + +/* eslint-disable prefer-const,no-case-declarations,@typescript-eslint/restrict-plus-operands */ + +/** + * Protobuf binary format wire types. + * + * A wire type provides just enough information to find the length of the + * following value. + * + * See https://developers.google.com/protocol-buffers/docs/encoding#structure + */ +export const enum WireType { + /** + * Used for int32, int64, uint32, uint64, sint32, sint64, bool, enum + */ + Varint = 0, + + /** + * Used for fixed64, sfixed64, double. + * Always 8 bytes with little-endian byte order. + */ + Bit64 = 1, + + /** + * Used for string, bytes, embedded messages, packed repeated fields + * + * Only repeated numeric types (types which use the varint, 32-bit, + * or 64-bit wire types) can be packed. In proto3, such fields are + * packed by default. + */ + LengthDelimited = 2, + + /** + * Start of a tag-delimited aggregate, such as a proto2 group, or a message + * in editions with message_encoding = DELIMITED. + */ + StartGroup = 3, + + /** + * End of a tag-delimited aggregate. + */ + EndGroup = 4, + + /** + * Used for fixed32, sfixed32, float. + * Always 4 bytes with little-endian byte order. + */ + Bit32 = 5, +} + +interface TextEncoderLike { + encode(input?: string): collections.Uint8Array +}; + +interface TextDecoderLike { + decode(input?: collections.Uint8Array): string +}; + +export interface IBinaryReader { + /** + * Current position. + */ + readonly pos: number; + + /** + * Number of bytes available in this reader. + */ + readonly len: number; + + /** + * Reads a tag - field number and wire type. + */ + tag(): [number, WireType]; + + /** + * Skip one element on the wire and return the skipped data. + */ + skip(wireType: WireType, fieldNo?: number): Uint8Array; + + /** + * Read a `uint32` field, an unsigned 32 bit varint. + */ + uint32(): number; + + /** + * Read a `int32` field, a signed 32 bit varint. + */ + int32(): number; + + /** + * Read a `sint32` field, a signed, zigzag-encoded 32-bit varint. + */ + sint32(): number; + + /** + * Read a `int64` field, a signed 64-bit varint. + */ + int64(): bigint | string; + + /** + * Read a `sint64` field, a signed, zig-zag-encoded 64-bit varint. + */ + sint64(): bigint | string; + + /** + * Read a `fixed64` field, a signed, fixed-length 64-bit integer. + */ + sfixed64(): bigint | string; + + /** + * Read a `uint64` field, an unsigned 64-bit varint. + */ + uint64(): bigint | string; + + /** + * Read a `fixed64` field, an unsigned, fixed-length 64 bit integer. + */ + fixed64(): bigint | string; + + /** + * Read a `bool` field, a variant. + */ + bool(): boolean; + + /** + * Read a `fixed32` field, an unsigned, fixed-length 32-bit integer. + */ + fixed32(): number; + + /** + * Read a `sfixed32` field, a signed, fixed-length 32-bit integer. + */ + sfixed32(): number; + + /** + * Read a `float` field, 32-bit floating point number. + */ + float(): number; + + /** + * Read a `double` field, a 64-bit floating point number. + */ + double(): number; + + /** + * Read a `bytes` field, length-delimited arbitrary data. + */ + bytes(): Uint8Array; + + /** + * Read a `string` field, length-delimited data converted to UTF-8 text. + */ + string(): string; +} + +export interface IBinaryWriter { + /** + * Return all bytes written and reset this writer. + */ + finish: () => Uint8Array; + + fork: () => IBinaryWriter; + + join: () => IBinaryWriter; + + tag: (fieldNo: number, type: WireType) => IBinaryWriter; + + raw: (chunk: Uint8Array) => IBinaryWriter; + + uint32: (value: number) => IBinaryWriter; + + int32: (value: number) => IBinaryWriter; + + sint32: (value: number) => IBinaryWriter; + + int64: (value: string | number | bigint) => IBinaryWriter; + + uint64: (value: string | number | bigint) => IBinaryWriter; + + sint64: (value: string | number | bigint) => IBinaryWriter; + + fixed64: (value: string | number | bigint) => IBinaryWriter; + + sfixed64: (value: string | number | bigint) => IBinaryWriter; + + bool: (value: boolean) => IBinaryWriter; + + fixed32: (value: number) => IBinaryWriter; + + sfixed32: (value: number) => IBinaryWriter; + + float: (value: number) => IBinaryWriter; + + double: (value: number) => IBinaryWriter; + + bytes: (value: Uint8Array) => IBinaryWriter; + + string: (value: string) => IBinaryWriter; +} + +export interface chunkAndBuf { + chunks: Uint8Array[]; + buf: number[]; +} + +export class BinaryWriter implements IBinaryWriter { + /** + * We cannot allocate a buffer for the entire output + * because we don't know it's size. + * + * So we collect smaller chunks of known size and + * concat them later. + * + * Use `raw()` to push data to this array. It will flush + * `buf` first. + */ + private chunks: Uint8Array[]; + /** + * A growing buffer for byte values. If you don't know + * the size of the data you are writing, push to this + * array. + */ + protected buf: number[]; + /** + * Previous fork states. + */ + private stack: Array = []; + /** + * Text encoder instance to convert UTF-8 to bytes. + */ + private readonly textEncoder: util.TextEncoder | undefined; + + constructor(textEncoder?: util.TextEncoder) { + this.textEncoder = textEncoder ?? new util.TextEncoder(); + this.chunks = []; + this.buf = []; + } + + /** + * Return all bytes written and reset this writer. + */ + finish(): Uint8Array { + this.chunks.push(new Uint8Array(this.buf)); // flush the buffer + let len = 0; + for (let i = 0; i < this.chunks.length; i++) { + len += this.chunks[i].length; + } + let bytes = new Uint8Array(len); + let offset = 0; + for (let i = 0; i < this.chunks.length; i++) { + bytes.set(this.chunks[i], offset); + offset += this.chunks[i].length; + } + this.chunks = []; + return bytes; + } + + /** + * Start a new fork for length-delimited data like a message + * or a packed repeated field. + * + * Must be joined later with `join()`. + */ + fork(): IBinaryWriter { + this.stack.push({ chunks: this.chunks, buf: this.buf }); + this.chunks = []; + this.buf = []; + return this; + } + + /** + * Join the last fork. Write its length and bytes, then + * return to the previous state. + */ + join(): IBinaryWriter { + // get chunk of fork + let chunk = this.finish(); + + // restore previous state + let prev = this.stack.pop(); + if (!prev) { + throw new Error("invalid state, fork stack empty"); + } + this.chunks = prev.chunks; + this.buf = prev.buf; + + // write length of chunk as varint + this.uint32(chunk.byteLength); + return this.raw(chunk); + } + + /** + * Writes a tag (field number and wire type). + * + * Equivalent to `uint32( (fieldNo << 3 | type) >>> 0 )`. + * + * Generated code should compute the tag ahead of time and call `uint32()`. + */ + tag(fieldNo: number, type: WireType): IBinaryWriter { + return this.uint32(((fieldNo << 3) | type) >>> 0); + } + + /** + * Write a chunk of raw bytes. + */ + raw(chunk: Uint8Array): IBinaryWriter { + if (this.buf.length) { + this.chunks.push(new Uint8Array(this.buf)); + this.buf = []; + } + this.chunks.push(chunk); + return this; + } + + /** + * Write a `uint32` value, an unsigned 32 bit varint. + */ + uint32(value: number): IBinaryWriter { + assertUInt32(value); + + // write value as varint 32, inlined for speed + while (value > 0x7f) { + this.buf.push((value & 0x7f) | 0x80); + value = value >>> 7; + } + this.buf.push(value); + + return this; + } + + /** + * Write a `int32` value, a signed 32 bit varint. + */ + int32(value: number): IBinaryWriter { + assertInt32(value); + varint32write(value, this.buf); + return this; + } + + /** + * Write a `bool` value, a variant. + */ + bool(value: boolean): IBinaryWriter { + this.buf.push(value ? 1 : 0); + return this; + } + + /** + * Write a `bytes` value, length-delimited arbitrary data. + */ + bytes(value: Uint8Array): IBinaryWriter { + this.uint32(value.byteLength); // write length of chunk as varint + return this.raw(value); + } + + /** + * Write a `string` value, length-delimited data converted to UTF-8 text. + */ + string(value: string): IBinaryWriter { + let chunk: Uint8Array = this.textEncoder?.encode(value) ?? new Uint8Array(); + this.uint32(chunk.byteLength); // write length of chunk as varint + return this.raw(chunk); + } + + /** + * Write a `float` value, 32-bit floating point number. + */ + float(value: number): IBinaryWriter { + assertFloat32(value); + let chunk = new Uint8Array(4); + new DataView(chunk.buffer).setFloat32(0, value, true); + return this.raw(chunk); + } + + /** + * Write a `double` value, a 64-bit floating point number. + */ + double(value: number): IBinaryWriter { + let chunk = new Uint8Array(8); + new DataView(chunk.buffer).setFloat64(0, value, true); + return this.raw(chunk); + } + + /** + * Write a `fixed32` value, an unsigned, fixed-length 32-bit integer. + */ + fixed32(value: number): IBinaryWriter { + assertUInt32(value); + let chunk = new Uint8Array(4); + new DataView(chunk.buffer).setUint32(0, value, true); + return this.raw(chunk); + } + + /** + * Write a `sfixed32` value, a signed, fixed-length 32-bit integer. + */ + sfixed32(value: number): IBinaryWriter { + assertInt32(value); + let chunk = new Uint8Array(4); + new DataView(chunk.buffer).setInt32(0, value, true); + return this.raw(chunk); + } + + /** + * Write a `sint32` value, a signed, zigzag-encoded 32-bit varint. + */ + sint32(value: number): IBinaryWriter { + assertInt32(value); + // zigzag encode + value = ((value << 1) ^ (value >> 31)) >>> 0; + varint32write(value, this.buf); + return this; + } + + /** + * Write a `fixed64` value, a signed, fixed-length 64-bit integer. + */ + sfixed64(value: string | number | bigint): IBinaryWriter { + let chunk = new Uint8Array(8), + view = new DataView(chunk.buffer), + tc = protoInt64.enc(value); + view.setInt32(0, tc.lo, true); + view.setInt32(4, tc.hi, true); + return this.raw(chunk); + } + + /** + * Write a `fixed64` value, an unsigned, fixed-length 64 bit integer. + */ + fixed64(value: string | number | bigint): IBinaryWriter { + let chunk = new Uint8Array(8), + view = new DataView(chunk.buffer), + tc = protoInt64.uEnc(value); + view.setInt32(0, tc.lo, true); + view.setInt32(4, tc.hi, true); + return this.raw(chunk); + } + + /** + * Write a `int64` value, a signed 64-bit varint. + */ + int64(value: string | number | bigint): IBinaryWriter { + let tc = protoInt64.enc(value); + varint64write(tc.lo, tc.hi, this.buf); + return this; + } + + /** + * Write a `sint64` value, a signed, zig-zag-encoded 64-bit varint. + */ + sint64(value: string | number | bigint): IBinaryWriter { + let tc = protoInt64.enc(value), + // zigzag encode + sign = tc.hi >> 31, + lo = (tc.lo << 1) ^ sign, + hi = ((tc.hi << 1) | (tc.lo >>> 31)) ^ sign; + varint64write(lo, hi, this.buf); + return this; + } + + /** + * Write a `uint64` value, an unsigned 64-bit varint. + */ + uint64(value: string | number | bigint): IBinaryWriter { + let tc = protoInt64.uEnc(value); + varint64write(tc.lo, tc.hi, this.buf); + return this; + } +} + +export class BinaryReader implements IBinaryReader { + /** + * Current position. + */ + pos: number; + /** + * Number of bytes available in this reader. + */ + readonly len: number; + private readonly buf: Uint8Array; + private readonly view: DataView; + private readonly textDecoder: util.TextDecoder | undefined; + + constructor(buf: Uint8Array, textDecoder?: util.TextDecoder) { + this.buf = buf; + this.len = buf.length; + this.pos = 0; + this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength); + this.textDecoder = textDecoder ?? new util.TextDecoder(); + } + + /** + * Reads a tag - field number and wire type. + */ + tag(): [number, WireType] { + let tag = this.uint32(), + fieldNo = tag >>> 3, + wireType = tag & 7; + if (fieldNo <= 0 || wireType < 0 || wireType > 5) { + throw new Error( + "illegal tag: field no " + fieldNo + " wire type " + wireType, + ); + } + return [fieldNo, wireType]; + } + + /** + * Skip one element and return the skipped data. + * + * When skipping StartGroup, provide the tags field number to check for + * matching field number in the EndGroup tag. + */ + skip(wireType: WireType, fieldNo?: number): Uint8Array { + let start = this.pos; + switch (wireType) { + case WireType.Varint: + while (this.buf[this.pos++] & 0x80) { + // ignore + } + break; + // eslint-disable-next-line + case WireType.Bit64: + this.pos += 4; + // eslint-disable-next-line + case WireType.Bit32: + this.pos += 4; + break; + case WireType.LengthDelimited: + let len = this.uint32(); + this.pos += len; + break; + case WireType.StartGroup: + for (;; ) { + const tag = this.tag() + const fn = tag[0]; + const wt = tag[1]; + if (wt === WireType.EndGroup) { + if (fieldNo !== undefined && fn !== fieldNo) { + throw new Error("invalid end group tag"); + } + break; + } + this.skip(wt, fn); + } + break; + default: + throw new Error("can't skip wire type " + wireType); + } + this.assertBounds(); + return this.buf.subarray(start, this.pos); + } + + protected varint64(): [number, number] { + let lowBits = 0; + let highBits = 0; + + for (let shift = 0; shift < 28; shift++) { + let b = this.buf[this.pos++]; + lowBits |= (b & 0x7f) << shift; + if ((b & 0x80) == 0) { + this.assertBounds(); + return [lowBits, highBits]; + } + } + let middleByte = this.buf[this.pos++]; + + lowBits |= (middleByte & 0x70) << 28; + + highBits = (middleByte & 0x70) >> 4; + + if ((middleByte & 0x80) == 0) { + this.assertBounds(); + return [lowBits, highBits]; + } + + for (let shift = 3; shift <= 31; shift += 7) { + let b = this.buf[this.pos++]; + lowBits |= (b & 0x7f) << shift; + if ((b & 0x80) == 0) { + this.assertBounds(); + return [lowBits, highBits]; + } + } + throw new Error("invalid varint"); + } + + /** + * Throws error if position in byte array is out of range. + */ + protected assertBounds(): void { + if (this.pos > this.len) { + throw new RangeError("premature EOF"); + } + } + + /** + * Read a `uint32` field, an unsigned 32 bit varint. + */ + uint32(): number { + let b = this.buf[this.pos++]; + let result = b & 0x7f; + if ((b & 0x80) == 0) { + this.assertBounds(); + return result; + } + + b = this.buf[this.pos++]; + result |= (b & 0x7f) << 7; + if ((b & 0x80) == 0) { + this.assertBounds(); + return result; + } + + b = this.buf[this.pos++]; + result |= (b & 0x7f) << 14; + if ((b & 0x80) == 0) { + this.assertBounds(); + return result; + } + + b = this.buf[this.pos++]; + result |= (b & 0x7f) << 21; + if ((b & 0x80) == 0) { + this.assertBounds(); + return result; + } + + b = this.buf[this.pos++]; + result |= (b & 0x7f) << 28; + + for (let readBytes = 5; (b & 0x80) != 0 && readBytes < 10; readBytes++) { + b = this.buf[this.pos++]; + } + + if ((b & 0x80) != 0) { + throw new Error('invalid varint'); + } + + this.assertBounds(); + + return result >>> 0; + } + + /** + * Read a `int32` field, a signed 32 bit varint. + */ + int32(): number { + return this.uint32() | 0; + } + + /** + * Read a `sint32` field, a signed, zigzag-encoded 32-bit varint. + */ + sint32(): number { + let zze = this.uint32(); + // decode zigzag + return (zze >>> 1) ^ -(zze & 1); + } + + /** + * Read a `int64` field, a signed 64-bit varint. + */ + int64(): bigint | string { + let varint64 = this.varint64(); + let lo = varint64[0]; + let hi = varint64[1]; + return protoInt64.dec(lo, hi); + } + + /** + * Read a `uint64` field, an unsigned 64-bit varint. + */ + uint64(): bigint | string { + let varint64 = this.varint64(); + let lo = varint64[0]; + let hi = varint64[1]; + return protoInt64.uDec(lo, hi); + } + + /** + * Read a `sint64` field, a signed, zig-zag-encoded 64-bit varint. + */ + sint64(): bigint | string { + let varint64 = this.varint64(); + let lo = varint64[0]; + let hi = varint64[1]; + // decode zig zag + let s = -(lo & 1); + lo = ((lo >>> 1) | ((hi & 1) << 31)) ^ s; + hi = (hi >>> 1) ^ s; + return protoInt64.dec(lo, hi); + } + + /** + * Read a `bool` field, a variant. + */ + bool(): boolean { + let varint64 = this.varint64(); + let lo = varint64[0]; + let hi = varint64[1]; + return lo !== 0 || hi !== 0; + } + + /** + * Read a `fixed32` field, an unsigned, fixed-length 32-bit integer. + */ + fixed32(): number { + return this.view.getUint32((this.pos += 4) - 4, true); + } + + /** + * Read a `sfixed32` field, a signed, fixed-length 32-bit integer. + */ + sfixed32(): number { + return this.view.getInt32((this.pos += 4) - 4, true); + } + + /** + * Read a `fixed64` field, an unsigned, fixed-length 64 bit integer. + */ + fixed64(): bigint | string { + return protoInt64.uDec(this.sfixed32(), this.sfixed32()); + } + + /** + * Read a `fixed64` field, a signed, fixed-length 64-bit integer. + */ + sfixed64(): bigint | string { + return protoInt64.dec(this.sfixed32(), this.sfixed32()); + } + + /** + * Read a `float` field, 32-bit floating point number. + */ + float(): number { + return this.view.getFloat32((this.pos += 4) - 4, true); + } + + /** + * Read a `double` field, a 64-bit floating point number. + */ + double(): number { + return this.view.getFloat64((this.pos += 8) - 8, true); + } + + /** + * Read a `bytes` field, length-delimited arbitrary data. + */ + bytes(): Uint8Array { + let len = this.uint32(), + start = this.pos; + this.pos += len; + this.assertBounds(); + return this.buf.subarray(start, start + len); + } + + /** + * Read a `string` field, length-delimited data converted to UTF-8 text. + */ + string(): string { + return this.textDecoder?.decode(this.bytes()) ?? ''; + } +} \ No newline at end of file diff --git a/protobuf-bitfun/src/binary-format.ets b/protobuf-bitfun/src/binary-format.ets new file mode 100644 index 0000000..c8c6a63 --- /dev/null +++ b/protobuf-bitfun/src/binary-format.ets @@ -0,0 +1,163 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import type { Message } from "./message"; +import type { IBinaryReader, + IBinaryWriter, + WireType, +} from "./binary-encoding"; +import type { FieldInfo } from "./field"; + +export interface numWriteTypeData { no: number; wireType: WireType; data: Uint8Array } + +/** + * BinaryFormat is the contract for serializing messages to and from binary + * data. Implementations may be specific to a proto syntax, and can be + * reflection based, or delegate to speed optimized generated code. + */ +@Sendable +export class BinaryFormat { + constructor() { + } + + /** + * Provide options for parsing binary data. + */ + makeReadOptions( + options?: Partial, + ): Readonly { + throw Error(); + }; + + /** + * Provide options for serializing binary data. + */ + makeWriteOptions( + options?: Partial, + ): Readonly { + throw Error(); + }; + + /** + * Parse a message from binary data, merging fields. + * + * Supports two message encodings: + * - length-prefixed: delimitedMessageEncoding is false or omitted, and + * lengthOrEndTagFieldNo is the expected length of the message in the reader. + * - delimited: delimitedMessageEncoding is true, and lengthOrEndTagFieldNo is + * the field number in a tag with wire type end-group signalling the end of + * the message in the reader. + * + * delimitedMessageEncoding is optional for backwards compatibility. + */ + readMessage>( + message: Message, + reader: IBinaryReader, + lengthOrEndTagFieldNo: number, + options: BinaryReadOptions, + delimitedMessageEncoding?: boolean, + ): void { + throw Error(); + }; + + /** + * Parse a field from binary data, and store it in the given target. + * + * The target must be an initialized message object, with oneof groups, + * repeated fields and maps already present. + */ + readField>( + target: Record>, // eslint-disable-line @typescript-eslint/no-explicit-any -- `any` is the best choice for dynamic access + reader: IBinaryReader, + field: FieldInfo, + wireType: WireType, + options: BinaryReadOptions, + ): void { + throw Error(); + }; + + /** + * Serialize a message to binary data. + */ + writeMessage>( + message: Message, + writer: IBinaryWriter, + options: BinaryWriteOptions, + ): void{ + throw Error(); + }; + + /** + * Serialize a field value to binary data. + * + * The value must be an array for repeated fields, a record object for map + * fields. Only selected oneof fields should be passed to this method. + */ + writeField, V extends Message>( + field: FieldInfo, + value: Message, // eslint-disable-line @typescript-eslint/no-explicit-any -- `any` is the best choice for dynamic access + writer: IBinaryWriter, + options: BinaryWriteOptions, + ): void{ + throw Error(); + }; +} + +export interface BinaryOptions { + readUnknownFields: boolean; + + readerFactory: (bytes: Uint8Array) => IBinaryReader; + + writeUnknownFields: boolean; + + writerFactory: () => IBinaryWriter; +} + + + +/** + * Options for parsing binary data. + */ +export interface BinaryReadOptions { + /** + * Retain unknown fields during parsing? The default behavior is to retain + * unknown fields and include them in the serialized output. + * + * For more details see https://developers.google.com/protocol-buffers/docs/proto3#unknowns + */ + readUnknownFields: boolean; + + /** + * Allows to use a custom implementation to decode binary data. + */ + readerFactory: (bytes: Uint8Array) => IBinaryReader; +} + +/** + * Options for serializing to binary data. + */ +export interface BinaryWriteOptions { + /** + * Include unknown fields in the serialized output? The default behavior + * is to retain unknown fields and include them in the serialized output. + * + * For more details see https://developers.google.com/protocol-buffers/docs/proto3#unknowns + */ + writeUnknownFields: boolean; + + /** + * Allows to use a custom implementation to encode binary data. + */ + writerFactory: () => IBinaryWriter; +} diff --git a/protobuf-bitfun/src/codegen-info.ets b/protobuf-bitfun/src/codegen-info.ets new file mode 100644 index 0000000..e44535e --- /dev/null +++ b/protobuf-bitfun/src/codegen-info.ets @@ -0,0 +1,152 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { + localName, + safeIdentifier, + safeObjectProperty, +} from "./private/names"; +import { getUnwrappedFieldType } from "./private/field-wrapper"; +import { scalarZeroValue } from "./private/scalars"; +import { DescWkt, reifyWkt } from "./private/reify-wkt"; +import type { + DescEnum, + DescEnumValue, + DescField, + DescExtension, + DescMessage, + DescMethod, + DescOneof, + DescService, +} from "./descriptor-set"; +import type { ScalarValue } from "./scalar"; +import { LongType, ScalarType } from "./scalar"; + +interface CodegenInfo { + /** + * Name of the runtime library NPM package. + */ + readonly packageName: string; + readonly localName: ( + desc: + | DescEnum + | DescEnumValue + | DescMessage + | DescExtension + | DescOneof + | DescField + | DescService + | DescMethod, + ) => string | undefined; + readonly symbols: Record; + readonly getUnwrappedFieldType: ( + field: DescField | DescExtension, + ) => ScalarType | undefined; + readonly wktSourceFiles: readonly string[]; + /** + * @deprecated please use scalarZeroValue instead + */ + readonly scalarDefaultValue: (type: ScalarType, longType: LongType) => ScalarValue; // eslint-disable-line @typescript-eslint/no-explicit-any + readonly scalarZeroValue: ( + type: T, + longType: L, + ) => ScalarValue; + /** + * @deprecated please use reifyWkt from @bufbuild/protoplugin/ecmascript instead + */ + readonly reifyWkt: (message: DescMessage) => DescWkt | undefined; + readonly safeIdentifier: (name: string) => string; + readonly safeObjectProperty: (name: string) => string; +} + +type RuntimeSymbolName = + | "proto2" + | "proto3" + | "Message" + | "PartialMessage" + | "PlainMessage" + | "FieldList" + | "MessageType" + | "Extension" + | "BinaryReadOptions" + | "BinaryWriteOptions" + | "JsonReadOptions" + | "JsonWriteOptions" + | "JsonValue" + | "JsonObject" + | "protoDouble" + | "protoInt64" + | "ScalarType" + | "LongType" + | "MethodKind" + | "MethodIdempotency" + | "IMessageTypeRegistry"; + +interface RuntimeSymbolInfo { + typeOnly: boolean; + publicImportPath: string; + privateImportPath: string; +}; + + +const packageName = "@bufbuild/protobuf"; + +export const codegenInfo: CodegenInfo = { + packageName: "@bufbuild/protobuf", + localName, + reifyWkt, + getUnwrappedFieldType, + scalarDefaultValue: scalarZeroValue, + scalarZeroValue, + safeIdentifier, + safeObjectProperty, + // prettier-ignore + symbols: { + 'proto2': {typeOnly: false, privateImportPath: "./proto2.js", publicImportPath: packageName}, + 'proto3': {typeOnly: false, privateImportPath: "./proto3.js", publicImportPath: packageName}, + 'Message': {typeOnly: false, privateImportPath: "./message.js", publicImportPath: packageName}, + 'PartialMessage': {typeOnly: true, privateImportPath: "./message.js", publicImportPath: packageName}, + 'PlainMessage': {typeOnly: true, privateImportPath: "./message.js", publicImportPath: packageName}, + 'FieldList': {typeOnly: true, privateImportPath: "./field-list.js", publicImportPath: packageName}, + 'MessageType': {typeOnly: true, privateImportPath: "./message-type.js", publicImportPath: packageName}, + 'Extension': {typeOnly: true, privateImportPath: "./extension.js", publicImportPath: packageName}, + 'BinaryReadOptions': {typeOnly: true, privateImportPath: "./binary-format.js", publicImportPath: packageName}, + 'BinaryWriteOptions': {typeOnly: true, privateImportPath: "./binary-format.js", publicImportPath: packageName}, + 'JsonReadOptions': {typeOnly: true, privateImportPath: "./json-format.js", publicImportPath: packageName}, + 'JsonWriteOptions': {typeOnly: true, privateImportPath: "./json-format.js", publicImportPath: packageName}, + 'JsonValue': {typeOnly: true, privateImportPath: "./json-format.js", publicImportPath: packageName}, + 'JsonObject': {typeOnly: true, privateImportPath: "./json-format.js", publicImportPath: packageName}, + 'protoDouble': {typeOnly: false, privateImportPath: "./proto-double.js", publicImportPath: packageName}, + 'protoInt64': {typeOnly: false, privateImportPath: "./proto-int64.js", publicImportPath: packageName}, + 'ScalarType': {typeOnly: false, privateImportPath: "./scalar.js", publicImportPath: packageName}, + 'LongType': {typeOnly: false, privateImportPath: "./scalar.js", publicImportPath: packageName}, + 'MethodKind': {typeOnly: false, privateImportPath: "./service-type.js", publicImportPath: packageName}, + 'MethodIdempotency': {typeOnly: false, privateImportPath: "./service-type.js", publicImportPath: packageName}, + 'IMessageTypeRegistry': {typeOnly: true, privateImportPath: "./type-registry.js", publicImportPath: packageName}, + }, + wktSourceFiles: [ + "google/protobuf/compiler/plugin.proto", + "google/protobuf/any.proto", + "google/protobuf/api.proto", + "google/protobuf/descriptor.proto", + "google/protobuf/duration.proto", + "google/protobuf/empty.proto", + "google/protobuf/field_mask.proto", + "google/protobuf/source_context.proto", + "google/protobuf/struct.proto", + "google/protobuf/timestamp.proto", + "google/protobuf/type.proto", + "google/protobuf/wrappers.proto", + ], +}; diff --git a/protobuf-bitfun/src/descriptor-set.ets b/protobuf-bitfun/src/descriptor-set.ets new file mode 100644 index 0000000..1f52431 --- /dev/null +++ b/protobuf-bitfun/src/descriptor-set.ets @@ -0,0 +1,871 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { LongType, ScalarType, ScalarTypeExcept } from "./scalar"; +import type { MethodIdempotency, MethodKind } from "./service-type"; +//import type { MergedFeatureSet } from "./private/feature-set"; + +/** + * DescriptorSet provides a convenient interface for working with a set + * of google.protobuf.FileDescriptorProto. + * + * When protobuf sources are compiled, each file is parsed into a + * google.protobuf.FileDescriptorProto. Those messages describe all parts + * of the source file that are required to generate code for them. + * + * DescriptorSet resolves references between the descriptors, hides + * implementation details like synthetic map entry messages, and provides + * simple access to comments. + */ +export interface DescriptorSet { + /** + * All files, in the order they were added to the set. + */ + readonly files: DescFile[]; + /** + * All enumerations, indexed by their fully qualified type name. + * (We omit the leading dot.) + */ + readonly enums: ReadonlyMap; + /** + * All messages, indexed by their fully qualified type name. + * (We omit the leading dot.) + */ + readonly messages: ReadonlyMap; + /** + * All services, indexed by their fully qualified type name. + * (We omit the leading dot.) + */ + readonly services: ReadonlyMap; + /** + * All extensions, indexed by their fully qualified type name. + */ + readonly extensions: ReadonlyMap; +} + +/** + * A union of all descriptors, discriminated by a `kind` property. + */ +export type AnyDesc = + | DescFile + | DescEnum + | DescEnumValue + | DescMessage + | DescField + | DescExtension + | DescOneof + | DescService + | DescMethod; + +/** + * Describes a protobuf source file. + */ +export interface DescFile { + readonly kind: "file"; + /** + * The syntax specified in the protobuf source. + */ + readonly syntax: "proto3" | "proto2" | "editions"; + + /** + * The name of the file, excluding the .proto suffix. + * For a protobuf file `foo/bar.proto`, this is `foo/bar`. + */ + readonly name: string; + /** + * Files imported by this file. + */ + readonly dependencies: DescFile[]; + /** + * Top-level enumerations declared in this file. + * Note that more enumerations might be declared within message declarations. + */ + readonly enums: DescEnum[]; + /** + * Top-level messages declared in this file. + * Note that more messages might be declared within message declarations. + */ + readonly messages: DescMessage[]; + /** + * Top-level extensions declared in this file. + * Note that more extensions might be declared within message declarations. + */ + readonly extensions: DescExtension[]; + /** + * Services declared in this file. + */ + readonly services: DescService[]; + /** + * Marked as deprecated in the protobuf source. + */ + readonly deprecated: boolean; + /** + * The compiler-generated descriptor. + */ + //readonly proto: FileDescriptorProto; + + /** + * Get comments on the syntax element in the protobuf source. + */ + getSyntaxComments(): DescComments; + + /** + * Get comments on the package element in the protobuf source. + */ + getPackageComments(): DescComments; + + /** + * Get the edition features for this protobuf element. + */ + // getFeatures(): MergedFeatureSet; + + toString(): string; +} + +/** + * Describes an enumeration in a protobuf source file. + */ +export interface DescEnum { + readonly kind: "enum"; + /** + * The fully qualified name of the enumeration. (We omit the leading dot.) + */ + readonly typeName: string; + /** + * The name of the enumeration, as declared in the protobuf source. + */ + readonly name: string; + /** + * The file this enumeration was declared in. + */ + readonly file: DescFile; + /** + * The parent message, if this enumeration was declared inside a message declaration. + */ + readonly parent: DescMessage | undefined; + /** + * Values declared for this enumeration. + */ + readonly values: DescEnumValue[]; + /** + * A prefix shared by all enum values. + * For example, `MY_ENUM_` for `enum MyEnum {MY_ENUM_A=0; MY_ENUM_B=1;}` + */ + readonly sharedPrefix?: string; + /** + * Marked as deprecated in the protobuf source. + */ + readonly deprecated: boolean; + /** + * The compiler-generated descriptor. + */ + //readonly proto: EnumDescriptorProto; + + /** + * Get comments on the element in the protobuf source. + */ + getComments(): DescComments; + + /** + * Get the edition features for this protobuf element. + */ + //getFeatures(): MergedFeatureSet; + + toString(): string; +} + +/** + * Describes an individual value of an enumeration in a protobuf source file. + */ +export interface DescEnumValue { + kind: "enum_value"; + /** + * The name of the enumeration value, as specified in the protobuf source. + */ + readonly name: string; + /** + * The enumeration this value belongs to. + */ + readonly parent: DescEnum; + /** + * The numeric enumeration value, as specified in the protobuf source. + */ + readonly number: number; + /** + * Marked as deprecated in the protobuf source. + */ + readonly deprecated: boolean; + /** + * The compiler-generated descriptor. + */ + //readonly proto: EnumValueDescriptorProto; + + /** + * Return a string that (closely) matches the definition of the enumeration + * value in the protobuf source. + */ + declarationString(): string; + + /** + * Get comments on the element in the protobuf source. + */ + getComments(): DescComments; + + /** + * Get the edition features for this protobuf element. + */ + //getFeatures(): MergedFeatureSet; + + toString(): string; +} + +/** + * Describes a message declaration in a protobuf source file. + */ +export interface DescMessage { + readonly kind: "message"; + /** + * The fully qualified name of the message. (We omit the leading dot.) + */ + readonly typeName: string; + /** + * The name of the message, as specified in the protobuf source. + */ + readonly name: string; + /** + * The file this message was declared in. + */ + readonly file: DescFile; + /** + * The parent message, if this message was declared inside a message declaration. + */ + readonly parent: DescMessage | undefined; + /** + * Fields declared for this message, including fields declared in a oneof + * group. + */ + readonly fields: DescField[]; + /** + * Oneof groups declared for this message. + * This does not include synthetic oneofs for proto3 optionals. + */ + readonly oneofs: DescOneof[]; + /** + * Fields and oneof groups for this message, ordered by their appearance in the + * protobuf source. + */ + readonly members: (DescField | DescOneof)[]; + /** + * Enumerations declared within the message, if any. + */ + readonly nestedEnums: DescEnum[]; + /** + * Messages declared within the message, if any. + * This does not include synthetic messages like map entries. + */ + readonly nestedMessages: DescMessage[]; + /** + * Extensions declared within the message, if any. + */ + readonly nestedExtensions: DescExtension[]; + /** + * Marked as deprecated in the protobuf source. + */ + readonly deprecated: boolean; + /** + * The compiler-generated descriptor. + */ + //readonly proto: DescriptorProto; + + /** + * Get comments on the element in the protobuf source. + */ + getComments(): DescComments; + + /** + * Get the edition features for this protobuf element. + */ + //getFeatures(): MergedFeatureSet; + + toString(): string; +} + +/** + * Describes a field declaration in a protobuf source file. + */ +export interface DescField { + readonly fieldKind: "map" | "enum" | "scalar" | "message"; + + readonly scalar: ScalarType | undefined; + + readonly longType: LongType | undefined; + + readonly mapValue: DescFieldMapValueEnum | DescFieldMapValueMessage | DescFieldMapValueScalar | undefined; + + readonly repeated: boolean; + + readonly message: DescMessage | undefined; + + readonly enum: DescEnum | undefined; + + readonly mapKey: ScalarTypeExcept | undefined; + + getDefaultValue(): number | boolean | string | bigint | Uint8Array | undefined; + + + + + readonly name: string; + readonly number: number; + readonly oneof: DescOneof | undefined; + readonly optional: boolean; + readonly packed: boolean; + + readonly packedByDefault: boolean; + readonly jsonName: string | undefined; + + readonly deprecated: boolean; + + getComments(): DescComments; + declarationString():string; + + toString(): string; + getDefaultValue(): number | boolean | string | bigint | Uint8Array | undefined; + + readonly kind: "field"; + readonly parent: DescMessage; + getComments(): DescComments; + declarationString():string; + toString(): string; +}; +/** + * Describes an extension in a protobuf source file. + */ +export interface DescExtension { + + readonly fieldKind: "map" | "enum" | "scalar" | "message"; + + readonly scalar: ScalarType | undefined; + + readonly longType: LongType | undefined; + + readonly mapValue: DescFieldMapValueEnum | DescFieldMapValueMessage | DescFieldMapValueScalar | undefined; + + readonly repeated: boolean; + + readonly message: DescMessage | undefined; + + readonly enum: DescEnum | undefined; + + readonly mapKey: ScalarTypeExcept | undefined; + + getDefaultValue(): number | boolean | string | bigint | Uint8Array | undefined; + + readonly kind: "extension"; + /** + * The fully qualified name of the extension. + */ + readonly typeName: string; + /** + * The file this extension was declared in. + */ + readonly file: DescFile; + /** + * The parent message, if this extension was declared inside a message declaration. + */ + readonly parent: DescMessage | undefined; + /** + * The message that this extension extends. + */ + readonly extendee: DescMessage; + /** + * The field name, as specified in the protobuf source + */ + readonly name: string; + /** + * The field number, as specified in the protobuf source. + */ + readonly number: number; + /** + * The `oneof` group this field belongs to, if any. + */ + readonly oneof: DescOneof | undefined; + /** + * Whether this field was declared with `optional` in the protobuf source. + */ + readonly optional: boolean; + /** + * Pack this repeated field? + */ + readonly packed: boolean; + /** + * Is this field packed by default? Only valid for repeated enum fields, and + * for repeated scalar fields except BYTES and STRING. + * + * In proto3 syntax, fields are packed by default. In proto2 syntax, fields + * are unpacked by default. + * + * With editions, the default is whatever the edition specifies as a default. + * In edition 2023, fields are packed by default. + */ + readonly packedByDefault: boolean; + /** + * A user-defined name for the JSON format, set with the field option + * [json_name="foo"]. + */ + readonly jsonName: string | undefined; + /** + * Marked as deprecated in the protobuf source. + */ + readonly deprecated: boolean; + /** + * The compiler-generated descriptor. + */ + + /** + * Get comments on the element in the protobuf source. + */ + getComments(): DescComments; + + /** + * Return a string that (closely) matches the definition of the field in the + * protobuf source. + */ + declarationString(): string; + + /** + * Get the edition features for this protobuf element. + */ + //getFeatures(): MergedFeatureSet; + + toString(): string; + + getDefaultValue(): number | boolean | string | bigint | Uint8Array | undefined; + } + + +interface DescFieldCommon { + /** + * The field name, as specified in the protobuf source + */ + readonly name: string; + /** + * The field number, as specified in the protobuf source. + */ + readonly number: number; + /** + * The `oneof` group this field belongs to, if any. + */ + readonly oneof: DescOneof | undefined; + /** + * Whether this field was declared with `optional` in the protobuf source. + */ + readonly optional: boolean; + /** + * Pack this repeated field? + */ + readonly packed: boolean; + /** + * Is this field packed by default? Only valid for repeated enum fields, and + * for repeated scalar fields except BYTES and STRING. + * + * In proto3 syntax, fields are packed by default. In proto2 syntax, fields + * are unpacked by default. + * + * With editions, the default is whatever the edition specifies as a default. + * In edition 2023, fields are packed by default. + */ + readonly packedByDefault: boolean; + /** + * A user-defined name for the JSON format, set with the field option + * [json_name="foo"]. + */ + readonly jsonName: string | undefined; + /** + * Marked as deprecated in the protobuf source. + */ + readonly deprecated: boolean; + + /** + * Get comments on the element in the protobuf source. + */ + getComments(): DescComments; + + /** + * Return a string that (closely) matches the definition of the field in the + * protobuf source. + */ + declarationString(): string; + + /** + * Get the edition features for this protobuf element. + */ + //getFeatures(): MergedFeatureSet; + + toString(): string; +} + +interface DescFieldScalar { + readonly fieldKind: "scalar"; + /** + * Is the field repeated? + */ + readonly repeated: boolean; + /** + * Scalar type, if it is a scalar field. + */ + readonly scalar: ScalarType; + /** + * JavaScript type for 64 bit integral types (int64, uint64, + * sint64, fixed64, sfixed64). + */ + readonly longType: LongType; + /** + * The message type, if it is a message field. + */ + readonly message: undefined; + /** + * The enum type, if it is an enum field. + */ + readonly enum: undefined; + /** + * The map key type, if this is a map field. + */ + readonly mapKey: undefined; + /** + * The map value type, if this is a map field. + */ + readonly mapValue: undefined; + + /** + * Return the default value specified in the protobuf source. + * Only valid for proto2 syntax. + */ + getDefaultValue(): + | number + | boolean + | string + | bigint + | Uint8Array + | undefined; +} + +interface DescFieldMessage { + readonly fieldKind: "message"; + /** + * Is the field repeated? + */ + readonly repeated: boolean; + /** + * Scalar type, if it is a scalar field. + */ + readonly scalar: undefined; + /** + * JavaScript type for 64 bit integral types (int64, uint64, + * sint64, fixed64, sfixed64). + */ + readonly longType: undefined; + /** + * The message type, if it is a message field. + */ + readonly message: DescMessage; + /** + * The enum type, if it is an enum field. + */ + readonly enum: undefined; + /** + * The map key type, if this is a map field. + */ + readonly mapKey: undefined; + /** + * The map value type, if this is a map field. + */ + readonly mapValue: undefined; +} + +interface DescFieldEnum { + readonly fieldKind: "enum"; + /** + * Is the field repeated? + */ + readonly repeated: boolean; + /** + * Scalar type, if it is a scalar field. + */ + readonly scalar: undefined; + /** + * JavaScript type for 64 bit integral types (int64, uint64, + * sint64, fixed64, sfixed64). + */ + readonly longType: undefined; + + /** + * The message type, if it is a message field. + */ + readonly message: undefined; + /** + * The enum type, if it is an enum field. + */ + readonly enum: DescEnum; + /** + * The map key type, if this is a map field. + */ + readonly mapKey: undefined; + /** + * The map value type, if this is a map field. + */ + readonly mapValue: undefined; + + /** + * Return the default value specified in the protobuf source. + * Only valid for proto2 syntax. + */ + getDefaultValue(): + | number + | boolean + | string + | bigint + | Uint8Array + | undefined; +} + +interface DescFieldMap { + readonly fieldKind: "map"; + /** + * Is the field repeated? + */ + readonly repeated: false; + /** + * Scalar type, if it is a scalar field. + */ + readonly scalar: undefined; + /** + * JavaScript type for 64 bit integral types (int64, uint64, + * sint64, fixed64, sfixed64). + */ + readonly longType: undefined; + /** + * The message type, if it is a message field. + */ + readonly message: undefined; + /** + * The enum type, if it is an enum field. + */ + readonly enum: undefined; + /** + * The map key type, if this is a map field. + */ + readonly mapKey: ScalarTypeExcept; + /** + * The map value type, if this is a map field. + */ + readonly mapValue: + | DescFieldMapValueEnum + | DescFieldMapValueMessage + | DescFieldMapValueScalar; +} + +interface DescFieldMapValueEnum { + readonly kind: "enum"; + /** + * The enum type, if this is a map field with enum values. + */ + readonly enum: DescEnum; + /** + * The message this message field uses. + */ + readonly message: undefined; + /** + * Scalar type, if this is a map field with scalar values. + */ + readonly scalar: undefined; +} + +interface DescFieldMapValueMessage { + readonly kind: "message"; + /** + * The enum type, if this is a map field with enum values. + */ + readonly enum: undefined; + /** + * The message type, if this is a map field with message values. + */ + readonly message: DescMessage; + /** + * Scalar type, if this is a map field with scalar values. + */ + readonly scalar: undefined; +} + +interface DescFieldMapValueScalar { + readonly kind: "scalar"; + /** + * The enum type, if this is a map field with enum values. + */ + readonly enum: undefined; + /** + * The message type, if this is a map field with message values. + */ + readonly message: undefined; + /** + * Scalar type, if this is a map field with scalar values. + */ + readonly scalar: ScalarType; +} + +/** + * Describes a oneof group in a protobuf source file. + */ +export interface DescOneof { + readonly kind: "oneof"; + /** + * The name of the oneof group, as specified in the protobuf source. + */ + readonly name: string; + /** + * The message this oneof group was declared in. + */ + readonly parent: DescMessage; + /** + * The fields declared in this oneof group. + */ + readonly fields: DescField[]; + /** + * Marked as deprecated in the protobuf source. + * Note that oneof groups cannot be marked as deprecated, this property + * only exists for consistency and will always be false. + */ + readonly deprecated: boolean; + /** + * The compiler-generated descriptor. + */ + + /** + * Get comments on the element in the protobuf source. + */ + getComments(): DescComments; + + /** + * Get the edition features for this protobuf element. + */ + //getFeatures(): MergedFeatureSet; + + toString(): string; +} + +/** + * Describes a service declaration in a protobuf source file. + */ +export interface DescService { + readonly kind: "service"; + /** + * The fully qualified name of the service. (We omit the leading dot.) + */ + readonly typeName: string; + /** + * The name of the service, as specified in the protobuf source. + */ + readonly name: string; + /** + * The file this service was declared in. + */ + readonly file: DescFile; + + readonly parent: DescService; + /** + * The RPCs this service declares. + */ + readonly methods: DescMethod[]; + /** + * Marked as deprecated in the protobuf source. + */ + readonly deprecated: boolean; + /** + * The compiler-generated descriptor. + */ + + + /** + * Get comments on the element in the protobuf source. + */ + getComments(): DescComments; + + /** + * Get the edition features for this protobuf element. + */ + //getFeatures(): MergedFeatureSet; + + toString(): string; +} + +/** + * Describes an RPC declaration in a protobuf source file. + */ +export interface DescMethod { + readonly kind: "rpc"; + /** + * The name of the RPC, as specified in the protobuf source. + */ + readonly name: string; + /** + * The parent service. + */ + readonly parent: DescService; + /** + * One of the four available method types. + */ + readonly methodKind: MethodKind; + /** + * The message type for requests. + */ + readonly input: DescMessage; + /** + * The message type for responses. + */ + readonly output: DescMessage; + /** + * The idempotency level declared in the protobuf source, if any. + */ + readonly idempotency?: MethodIdempotency; + /** + * Marked as deprecated in the protobuf source. + */ + readonly deprecated: boolean; + /** + * The compiler-generated descriptor. + */ + + + /** + * Get comments on the element in the protobuf source. + */ + getComments(): DescComments; + + /** + * Get the edition features for this protobuf element. + */ + //getFeatures(): MergedFeatureSet; + + toString(): string; +} + +/** + * Comments on an element in a protobuf source file. + */ +export interface DescComments { + readonly leadingDetached: readonly string[]; + readonly leading?: string; + readonly trailing?: string; + readonly sourcePath: readonly number[]; +} diff --git a/protobuf-bitfun/src/enum.ts b/protobuf-bitfun/src/enum.ts new file mode 100644 index 0000000..f9af9ac --- /dev/null +++ b/protobuf-bitfun/src/enum.ts @@ -0,0 +1,62 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +import { lang } from '@kit.ArkTS' ; + +// +// 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. + +type ISendable = lang.ISendable; +/** + * Reflection information for a protobuf enumeration. + */ +export interface EnumType extends ISendable { + /** + * The fully qualified name of the enumeration. + */ + readonly typeName: string; + + readonly values: readonly EnumValueInfo[]; + + /** + * Find an enum value by its (protobuf) name. + */ + findName(name: string): EnumValueInfo | undefined; + + /** + * Find an enum value by its number. + */ + findNumber(no: number): EnumValueInfo | undefined; + + // We do not surface options at this time + // readonly options: OptionsMap; +} + +/** + * Reflection information for a protobuf enumeration value. + */ +export interface BaseEnumValueInfo extends ISendable{ + /** + * The numeric enumeration value, as specified in the protobuf source. + */ + readonly no: number; + + /** + * The name of the enumeration value, as specified in the protobuf source. + */ + readonly name: string; +} + +export interface EnumValueInfo extends BaseEnumValueInfo{ + readonly localName: string; +} + +export interface enumsOptions {} \ No newline at end of file diff --git a/protobuf-bitfun/src/field-list.ets b/protobuf-bitfun/src/field-list.ets new file mode 100644 index 0000000..2dd91e6 --- /dev/null +++ b/protobuf-bitfun/src/field-list.ets @@ -0,0 +1,58 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { FieldInfo, OneofInfo } from "./field"; +import { collections } from '@kit.ArkTS'; + +@Sendable +export class FieldList { + constructor(){ + + } + /** + * Find field information by field name or json_name. + */ + findJsonName(jsonName: string): FieldInfo | undefined { + throw Error(); + } + + /** + * Find field information by proto field number. + */ + find(fieldNo: number): FieldInfo | undefined { + throw Error(); + } + + /** + * Return field information in the order they appear in the source. + */ + list(): collections.Array { + throw Error(); + } + + /** + * Return field information ordered by field number ascending. + */ + byNumber(): collections.Array | undefined { + throw Error(); + } + + /** + * In order of appearance in the source, list fields and + * oneof groups. + */ + byMember(): collections.Array { + throw Error(); + } +} diff --git a/protobuf-bitfun/src/field.ets b/protobuf-bitfun/src/field.ets new file mode 100644 index 0000000..d386a59 --- /dev/null +++ b/protobuf-bitfun/src/field.ets @@ -0,0 +1,419 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + + +import { BinaryReadOptions } from "./binary-format"; +import { EnumType } from "./private/common"; +import { FieldList } from './field-list'; +import { Message } from "./message"; +import { MessageType, EmptyMessage } from "./message-type"; +import { FieldWrapper } from "./private/field-wrapper"; +import { ProtoRuntime } from "./private/proto-runtime"; +import { LongType, ScalarType, ScalarTypeExcept } from "./scalar"; + +import { lang, collections } from '@kit.ArkTS' +import json from '@ohos.util.json'; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * FieldInfo describes a field of a protobuf message for runtime reflection. We + * distinguish between the following kinds of fields: + * + * - "scalar": string, bool, float, int32, etc. The scalar type is "T". + * - "enum": The field was declared with an enum type. The enum type is "T". + * - "message": The field was declared with a message type. The message type is "T". + * - "map": The field was declared with map. The key type is "K", the value type is "V". + * + * Every field always has the following properties: + * + * - "no": The field number of the protobuf field. + * - "name": The original name of the protobuf field. + * - "localName": The name of the field as used in generated code. + * - "jsonName": The name for JSON serialization / deserialization. + * - "opt": Whether the field is optional. + * - "req": Whether the field is required (a legacy proto2 feature). + * - "repeated": Whether the field is repeated. + * - "packed": Whether the repeated field is packed. + * + * Additionally, fields may have the following properties: + * + * - "oneof": If the field is member of a oneof group. + * - "default": Only proto2: An explicit default value. + * - "delimited": Only proto2: Use the tag-delimited group encoding. + */ + + + + + +export type ISendable = lang.ISendable; + +export interface OneofInfo = EmptyMessage> extends ISendable { + kind: string; + name?: string; + localName: string; + repeated: boolean; + packed: boolean; + opt: boolean; + req: boolean; + default: undefined; + delimited?: undefined; + fields?: collections.Array>; + T?: ScalarType; + L?: LongType; + + /** + * Return field information by local name. + */ + findField(localName: string): FieldInfo | undefined; +} + +@Sendable +abstract class fiShared> { + /** + * The field number of the .proto field. + */ + abstract no: number; + + /** + * The original name of the .proto field. + */ + abstract name: string; + + /** + * The name of the field as used in generated code. + */ + abstract localName: string; + + /** + * The name for JSON serialization / deserialization. + */ + abstract jsonName: string; + + /** + * The `oneof` group, if this field belongs to one. + */ + abstract oneof: OneofInfo | undefined; + + // We do not surface options at this time + // readonly options: OptionsMap; +} + +@Sendable +export class FieldInfo = EmptyMessage> extends fiShared { + + constructor(){ + super(); + } + + buildNo(no: number):FieldInfo{ + this.no = no; + return this; + } + + buildName(name: string):FieldInfo{ + this.name = name; + if (this.jsonName === undefined || this.jsonName === "") { + this.jsonName = name; + } + return this; + } + + buildKind(kind: string):FieldInfo{ + this.kind = kind; + return this; + } + + buildLocalName(localName:string):FieldInfo{ + this.localName = localName; + return this; + } + + buildJsonName(jsonName: string):FieldInfo{ + this.jsonName = jsonName; + return this; + } + + buildOneOf(oneof: OneofInfo ):FieldInfo{ + this.oneof = oneof; + return this; + } + + buildT(T: MessageType | ScalarType | EnumType):FieldInfo{ + this.T = T; + return this; + } + + buildE(E: EnumType):FieldInfo{ + this.E = E ; + return this; + } + + buildK(K: ScalarType):FieldInfo{ + this.K = K; + return this; + } + + buildV(V: fiMapInterface):FieldInfo{ + this.V = V; + return this; + } + + buildL(L:LongType):FieldInfo{ + this.L = L; + return this; + } + + buildRepeated(repeated: boolean):FieldInfo{ + this.repeated = repeated; + return this; + } + + buildPacked(packed: boolean):FieldInfo{ + this.packed = packed; + return this; + } + + buildOpt(opt: boolean):FieldInfo{ + this.opt = opt; + return this; + } + + buildReq(req: boolean):FieldInfo{ + this.req = req; + return this; + } + + buildDelimited(delimited: boolean):FieldInfo{ + this.delimited = delimited ; + return this; + } + + buildDefaultValue(defaultValue: boolean):FieldInfo{ + this.defaultValue = defaultValue; + return this; + } + + no: number = 0; + name: string = ""; + localName: string = ""; + jsonName: string = ""; + oneof: OneofInfo | undefined; + kind: string = "message"; + + T?: MessageType | ScalarType | EnumType; + + E?: EnumType; + + K: ScalarType = ScalarType.DOUBLE; + + V: fiMapInterface = new fiMapInterface(); + L?: LongType; + + repeated?: boolean; + + packed?: boolean; + + opt?: boolean; + + req?: boolean; + + defaultValue: number | boolean | string | bigint | collections.Uint8Array | undefined; + + delimited?: boolean; +} + +@Sendable +class BaseEnumValueInfo{ + + no: number = 0; + + + name: string = ''; + +} + + + +@Sendable +class EnumValueInfo extends BaseEnumValueInfo { + + constructor() { + super(); + } + + localName: string = ''; + +} + +export interface JsonReadOptions{ + + ignoreUnknownFields: boolean; + +} + +@Sendable +export class fiMapType> extends MessageType { + clone(message: O): O { + throw new Error('Method not implemented.'); + } + + create(): O { + return new Object() as O; + } + + instance: MessageType = new fiMapType(); + + typeName: string = ''; + + values: collections.Array = new collections.Array(); + + findName(name: string): EnumValueInfo | undefined { + return ; + }; + + findNumber(no: number): EnumValueInfo | undefined { + return ; + }; + + constructor(){ + super(); + } + fieldWrapper: FieldWrapper = new FieldWrapper(); + + runtime: ProtoRuntime = new ProtoRuntime(); + + getFieldWrapper(): FieldWrapper | undefined { + throw new Error('Method not implemented.'); + } + + getTypeName(): string { + throw new Error('Method not implemented.'); + } + + getFields(): FieldList { + throw new Error('Method not implemented.'); + } + + getProtoRuntime(): ProtoRuntime { + throw new Error('Method not implemented.'); + } + + + fromBinary(data: Uint8Array, options?: Partial | undefined): O { + throw new Error('Method not implemented.'); + } + fromJson(jsonValue: Object, options?: Partial | undefined): O { + throw new Error('Method not implemented.'); + } + fromJsonString(jsonString: string, options?: Partial | undefined): O { + throw new Error('Method not implemented.'); + } + equals(a: O | Message | null | undefined, b: O | Message | null | undefined): boolean { + throw new Error('Method not implemented.'); + } + fieldWrappe: FieldWrapper = new FieldWrapper(); + fields: FieldList = new FieldList(); +} + + +@Sendable +export class fiMapInterface>{ + constructor(kind?: string, T?: fiMapType | ScalarType) { + if(kind !== undefined){ + this.kind = kind; + this.T = T; + } + } + kind: string = ""; + T: fiMapType | undefined | ScalarType; +} + + +@Sendable +export class fiMapInterface_1{ + constructor() { + + } + kind: string = "scalar"; + T: ScalarType = ScalarType.DOUBLE; +} + +@Sendable +export class fiMapInterface_2{ + constructor() { + + } + kind: string = "enum"; + T?: EnumType; +} + +@Sendable +export class fiMapInterface_3{ + constructor() { + + } + kind: string = "message"; + T?: MessageType; +} + + +export interface FieldValueInfo>{ + + no?: number; + + name?: string; + + localName?: string; + + jsonName?: string; + + oneof?: OneofInfo; + + kind?: string; + + req?: boolean; + + L?: LongType; + + T?: number; + + K?: ScalarTypeExcept; + + V?: FieldValueInfo; + + opt?: boolean; + + repeated?: boolean; + + packed?:boolean; + + default?:undefined; + + delimited?:undefined; + +} + +@Sendable +export class OneofClass{ + type?: string; + value?: M; + + constructor(type?: string, value?: M){ + this.type = type; + this.value = value; + } +}; + + diff --git a/protobuf-bitfun/src/google/protobuf/any_pb.ets b/protobuf-bitfun/src/google/protobuf/any_pb.ets new file mode 100644 index 0000000..b1fff4f --- /dev/null +++ b/protobuf-bitfun/src/google/protobuf/any_pb.ets @@ -0,0 +1,219 @@ +import { proto3 } from '../../proto3'; +import { BinaryReadOptions } from "../../binary-format"; +import { JsonReadOptions, JsonValue } from "../../json-format"; +import { FieldList } from "../../field-list"; +import { FieldInfo } from '../../field'; +import { Message } from '../../message'; +import { MessageType } from '../../message-type'; +import { getFieldListsFromArray } from '../../private/field'; +import { FieldWrapper } from '../../private/field-wrapper'; +import { ProtoRuntime } from '../../private/proto-runtime'; +import { IMessageTypeRegistry } from '../../type-registry'; + +import collections from '@arkts.collections'; + +@Sendable +export class Any extends Message { + /** + * @generated from field: string typeUrl = 1; + */ + typeUrl: string = ""; + + /** + * @generated from field: bytes value = 2; + */ + value: collections.Uint8Array = new collections.Uint8Array(0); + + static fieldWrapper: FieldWrapper | undefined; + + static readonly typeName: string = "google.protobuf.Any"; + + static readonly runtime: ProtoRuntime = proto3; + + static readonly fieldList: collections.Map = new collections.Map(); + + static fields: FieldList = proto3.util.newFieldList(getFieldListsFromArray(() => { + let res = new collections.Array(); + res.push(new FieldInfo().buildNo(1).buildName("typeUrl").buildKind("scalar").buildT(9)); + res.push(new FieldInfo().buildNo(2).buildName("value").buildKind("scalar").buildT(12)); + return res + })); + + constructor() { + super(); + } + + getType(): MessageType{ + return AnyType_$1.instance + } + + + static fromBinary(bytes: Uint8Array, options?: Partial): Any { + return new Any().getType().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): Any { + return new Any().getType().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): Any { + return new Any().getType().fromJson(jsonString, options); + } + + static equals(a: Any | undefined, b: Any | undefined): boolean { + return proto3.util.equals(AnyType_$1.instance, a, b); + } + + override toJson(options?: Partial): JsonValue { + if (this.typeUrl === "") { + return new Object(); + }; + const typeName = this.typeUrlToName(this.typeUrl); + const messageType : MessageType | undefined = options?.typeRegistry?.findMessage(typeName); + if (!messageType) { + throw new Error('cannot encode message google.protobuf.Any to JSON: "${this.typeUrl}" is not int the type registry') + } + const message = messageType.fromBinary(Uint8Array.from(this.value)); + let json = message.toJson(options); + if (typeName.startsWith("google.protobuf") || (json === null || Array.isArray(json) || typeof json !== "object")) { + (json as Record)["value"] = json; + } + (json as Record)["@type"] = this.typeUrl; + return json; + } + override fromJson(json: JsonValue, options?: Partial): Any { + if (json === null || Array.isArray(json) || typeof json != "object") { + throw new Error(`cannot decode message google.protobuf.Any from JSON: expected object but got ${json === null ? "null" : Array.isArray(json) ? "array" : typeof json}`); + } + if (Object.keys(json).length === 0) { + return this; + } + + const typeUrl = (json as Record)["@type"]; + if (typeof typeUrl !== "string" || typeUrl === "") { + throw new Error(`cannot decodze message google.protobuf.Any from JSON: "@type" is empty`); + } + + const typeName = this.typeUrlToName(typeUrl), messageType: MessageType | undefined = options?.typeRegistry?.findMessage(typeName); + if (!messageType) { + throw new Error(`cannot decodze message google.protobuf.Any from JSON: ${typeUrl}} is not in the type registry`); + } + + let message: ESObject; + if (typeName.startsWith("google.protobuf")) { + for (let item of Object.keys(json)) { + if (item === "value") { + message = messageType.fromJson((json as Record)["value"], options); + } + break; + } + } else { + const copy: Record = {}; + this.assign(copy, (json as Record)); + (copy as Record)["@type"] = ""; + message = messageType.fromJson(copy, options); + } + this.packFrom(message); + return this; + } + + packFrom(message: Message): void { + this.value = collections.Uint8Array.from(message.toBinary()); + this.typeUrl = this.typeNameToUrl(message.getType().getTypeName()); + } + + unpackTo(target: Message) : boolean { + if (!this.is(target.getType())) { + return false; + } + target.fromBinary(Uint8Array.from(this.value)); + return true; + } + + unpack(registry: IMessageTypeRegistry): Message | undefined { + if(this.typeUrl === "") { + return undefined; + } + const messageType: MessageType | undefined = registry.findMessage(this.typeUrlToName(this.typeUrl)); + if (!messageType) { + return undefined; + } + return messageType.fromBinary(Uint8Array.from(this.value)); + } + + is(type: MessageType | string): boolean { + if (this.typeUrl === '') { + return false; + } + const name = this.typeNameToUrl(this.typeUrl); + let typeName = ''; + if (typeof type === 'string') { + typeName = type; + } else { + typeName = type.getTypeName(); + } + return name === typeName; + } + + private typeNameToUrl(name: string) { + return `type.googleapis.com/${name}`; + } + + private typeUrlToName(url: string) { + if (!url.length) { + throw new Error(`invalid type url: ${url}`); + } + const slash = url.lastIndexOf("/"); + const name = slash >= 0 ? url.substring(slash + 1) : url; + if (!name.length) { + throw new Error(`invalid type url: ${url}`); + } + return name; + } + + private assign(target: Record, ...source: Object[]): Record { + for (let s of source) { + for (let k of Object.keys(s)) { + target[k] = Reflect.get(s, k); + } + } + return target; + } +} + +@Sendable +export class AnyType_$1 extends MessageType { + static instance: AnyType_$1 = new AnyType_$1(); + + constructor() { + super(); + } + + create(data?: Message): Any { + return new Any(); + } + + equals(a: Any | Message | undefined | null, b: Any | Message | undefined | null): boolean { + return proto3.util.equals(this, a, b); + } + + clone(message: Any): Any { + return proto3.util.clone(message); + } + + getFieldWrapper(): FieldWrapper | undefined { + return Any.fieldWrapper; + } + + getTypeName(): string { + return Any.typeName; + } + + getFields(): FieldList { + return Any.fields; + } + + getProtoRuntime(): ProtoRuntime { + return Any.runtime; + } +} \ No newline at end of file diff --git a/protobuf-bitfun/src/google/protobuf/duration_pb.ets b/protobuf-bitfun/src/google/protobuf/duration_pb.ets new file mode 100644 index 0000000..24d1a05 --- /dev/null +++ b/protobuf-bitfun/src/google/protobuf/duration_pb.ets @@ -0,0 +1,97 @@ +import { proto3 } from '../../proto3'; +import { protoInt64 } from '../../proto-int64'; +import { BinaryReadOptions } from "../../binary-format"; +import { JsonReadOptions, JsonValue } from "../../json-format"; +import { FieldList } from "../../field-list"; +import { FieldInfo } from '../../field'; +import { Message } from '../../message'; +import { MessageType } from '../../message-type'; +import { getFieldListsFromArray } from '../../private/field'; +import { FieldWrapper } from '../../private/field-wrapper'; +import { ProtoRuntime } from '../../private/proto-runtime'; + +import collections from "@arkts.collections"; + +@Sendable +export class Duration extends Message { + /** + * @generated from field: int64 seconds = 1; + */ + seconds: bigint = protoInt64.zero; + + /** + * @generated from field: int32 nanos = 2; + */ + nanos: number = 0; + + constructor() { + super(); + } + + getType(): MessageType{ + return DurationType_$1.instance + } + + static fieldWrapper: FieldWrapper | undefined; + static fields: FieldList = proto3.util.newFieldList(getFieldListsFromArray(() => { + let res = new collections.Array(); + res.push(new FieldInfo().buildNo(1).buildName("seconds").buildKind("scalar").buildT(3)); + res.push(new FieldInfo().buildNo(2).buildName("nanos").buildKind("scalar").buildT(5)); + return res + })); + static readonly typeName: string = "Duration"; + static readonly runtime: ProtoRuntime = proto3; + static readonly fieldList: collections.Map= new collections.Map(); + + static fromBinary(bytes: Uint8Array, options?: Partial): Message { + return new Duration().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial< JsonReadOptions>): Message { + return new Duration().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): Message { + return new Duration().fromJson(jsonString, options); + } + + static equals(a: Duration | undefined, b: Duration | undefined): boolean { + return proto3.util.equals(DurationType_$1.instance, a, b); + } +} + +@Sendable +export class DurationType_$1 extends MessageType { + fieldWrapper: FieldWrapper | undefined; + constructor() { + super(); + } + create():Duration{ + return new Duration(); + } + equals(a: Duration | Message | undefined | null, b: Duration | Message | undefined | null): boolean { + return proto3.util.equals(this, a, b); + } + clone(message: Duration): Duration { + return proto3.util.clone(message); + } + + getFieldWrapper(): FieldWrapper | undefined { + return Duration.fieldWrapper; + } + + getTypeName(): string { + return Duration.typeName; + } + + getFields(): FieldList { + return Duration.fields; + } + + getProtoRuntime(): ProtoRuntime { + return Duration.runtime; + } + + static instance: DurationType_$1 = new DurationType_$1(); +} + diff --git a/protobuf-bitfun/src/google/protobuf/empty_pb.ets b/protobuf-bitfun/src/google/protobuf/empty_pb.ets new file mode 100644 index 0000000..6be3990 --- /dev/null +++ b/protobuf-bitfun/src/google/protobuf/empty_pb.ets @@ -0,0 +1,85 @@ +import { proto3 } from '../../proto3'; +import { BinaryReadOptions } from "../../binary-format"; +import { JsonReadOptions, JsonValue } from "../../json-format"; +import { FieldList } from "../../field-list"; +import { FieldInfo } from '../../field'; +import { Message } from '../../message'; +import { MessageType } from '../../message-type'; +import { getFieldListsFromArray } from '../../private/field'; +import { FieldWrapper } from '../../private/field-wrapper'; +import { ProtoRuntime } from '../../private/proto-runtime'; + +import collections from "@arkts.collections"; + +@Sendable +export class Empty extends Message { + constructor() { + super(); + } + + getType(): MessageType{ + return EmptyType_$1.instance + } + + static fieldWrapper: FieldWrapper | undefined; + static fields: FieldList = proto3.util.newFieldList(getFieldListsFromArray(() => { + let res = new collections.Array(); + return res; + })); + static readonly typeName: string = "Empty"; + static readonly runtime: ProtoRuntime = proto3; + static readonly fieldList: collections.Map= new collections.Map(); + + static fromBinary(bytes: Uint8Array, options?: Partial): Message { + return new Empty().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial< JsonReadOptions>): Message { + return new Empty().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): Message { + return new Empty().fromJson(jsonString, options); + } + + static equals(a: Empty | undefined, b: Empty | undefined): boolean { + return proto3.util.equals(EmptyType_$1.instance, a, b); + } +} + +@Sendable +export class EmptyType_$1 extends MessageType { + static instance: EmptyType_$1 = new EmptyType_$1(); + + constructor() { + super(); + } + + create():Empty{ + return new Empty(); + } + + equals(a: Empty | Message | undefined | null, b: Empty | Message | undefined | null): boolean { + return proto3.util.equals(this, a, b); + } + + clone(message: Empty): Empty { + return proto3.util.clone(message); + } + + getFieldWrapper(): FieldWrapper | undefined { + return Empty.fieldWrapper; + } + + getTypeName(): string { + return Empty.typeName; + } + + getFields(): FieldList { + return Empty.fields; + } + + getProtoRuntime(): ProtoRuntime { + return Empty.runtime; + } +} diff --git a/protobuf-bitfun/src/google/protobuf/timestamp_pb.ets b/protobuf-bitfun/src/google/protobuf/timestamp_pb.ets new file mode 100644 index 0000000..e20242e --- /dev/null +++ b/protobuf-bitfun/src/google/protobuf/timestamp_pb.ets @@ -0,0 +1,92 @@ +import { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, Message, proto3, protoInt64 } from '../..'; +import { FieldInfo } from '../../field'; +import { MessageType } from '../../message-type'; +import { getFieldListsFromArray } from '../../private/field'; +import { FieldWrapper } from '../../private/field-wrapper'; +import { collections } from '@kit.ArkTS' +import { ProtoRuntime } from '../../private/proto-runtime'; + +@Sendable +export class Timestamp extends Message { + /** + * @generated from field: int64 seconds = 1; + */ + seconds: bigint = protoInt64.zero; + + /** + * @generated from field: int32 nanos = 2; + */ + nanos: number = 0; + + constructor() { + super(); + } + + getType(): MessageType{ + return TimestampType_$1.instance + } + + static fields: FieldList = proto3.util.newFieldList(getFieldListsFromArray(() => { + let res = new collections.Array(); + res.push(new FieldInfo().buildNo(1).buildName("seconds").buildKind("scalar").buildT(3)); + res.push(new FieldInfo().buildNo(2).buildName("nanos").buildKind("scalar").buildT(5)); + return res + })); + static readonly typeName: string = "Timestamp"; + static readonly runtime: ProtoRuntime = proto3; + static readonly fieldList: collections.Map= new collections.Map(); + static fieldWrapper: FieldWrapper | undefined; + + static fromBinary(bytes: Uint8Array, options?: Partial): Message { + return new Timestamp().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial< JsonReadOptions>): Message { + return new Timestamp().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): Message { + return new Timestamp().fromJson(jsonString, options); + } + + static equals(a: Timestamp | undefined, b: Timestamp | undefined): boolean { + return proto3.util.equals(TimestampType_$1.instance, a, b); + } +} + +@Sendable +export class TimestampType_$1 extends MessageType { + static instance: TimestampType_$1 = new TimestampType_$1(); + + constructor() { + super(); + } + + create():Timestamp{ + return new Timestamp(); + } + + equals(a: Timestamp | Message | undefined | null, b: Timestamp | Message | undefined | null): boolean { + return proto3.util.equals(this, a, b); + } + + clone(message: Timestamp): Timestamp { + return proto3.util.clone(message); + } + + getFieldWrapper(): FieldWrapper | undefined { + return Timestamp.fieldWrapper; + } + + getTypeName(): string { + return Timestamp.typeName; + } + + getFields(): FieldList { + return Timestamp.fields; + } + + getProtoRuntime(): ProtoRuntime { + return Timestamp.runtime; + } +} diff --git a/protobuf-bitfun/src/google/protobuf/wrappers_pb.ets b/protobuf-bitfun/src/google/protobuf/wrappers_pb.ets new file mode 100644 index 0000000..ae9489e --- /dev/null +++ b/protobuf-bitfun/src/google/protobuf/wrappers_pb.ets @@ -0,0 +1,769 @@ +import { proto3 } from '../../proto3'; +import { BinaryReadOptions } from "../../binary-format"; +import { JsonReadOptions, JsonValue } from "../../json-format"; +import { FieldList } from "../../field-list"; +import { FieldInfo } from '../../field'; +import { Message } from '../../message'; +import { MessageType } from '../../message-type'; +import { getFieldListsFromArray } from '../../private/field'; +import { FieldWrapper } from '../../private/field-wrapper'; +import { ProtoRuntime } from '../../private/proto-runtime'; +import { protoInt64 } from '../../proto-int64'; + +import collections from '@arkts.collections'; + +@Sendable +export class DoubleValue extends Message { + /** + * @generated from field: double value = 1; + */ + value: number = 0; + + static fieldWrapper: FieldWrapper | undefined; + + static readonly typeName: string = "google.protobuf.DoubleValue"; + + static readonly runtime: ProtoRuntime = proto3; + + static readonly fieldList: collections.Map= new collections.Map(); + + static fields: FieldList = proto3.util.newFieldList(getFieldListsFromArray(() => { + let res = new collections.Array(); + res.push(new FieldInfo().buildNo(1).buildName("value").buildKind("scalar").buildT(1)); + return res + })); + + constructor() { + super(); + } + + getType(): MessageType{ + return DoubleValueType.instance; + } + + + static fromBinary(bytes: Uint8Array, options?: Partial): DoubleValue { + return new DoubleValue().getType().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial< JsonReadOptions>): DoubleValue { + return new DoubleValue().getType().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): DoubleValue { + return new DoubleValue().getType().fromJson(jsonString, options); + } + + static equals(a: DoubleValue | undefined, b: DoubleValue | undefined): boolean { + return proto3.util.equals(DoubleValueType.instance, a, b); + } +} + +@Sendable +export class DoubleValueType extends MessageType { + static instance: DoubleValueType = new DoubleValueType(); + + constructor() { + super(); + } + + create():DoubleValue{ + return new DoubleValue(); + } + + equals(a: DoubleValue | Message | undefined | null, b: DoubleValue | Message | undefined | null): boolean { + return proto3.util.equals(this, a, b); + } + + clone(message: DoubleValue): DoubleValue { + return proto3.util.clone(message); + } + + getFieldWrapper(): FieldWrapper | undefined { + return DoubleValue.fieldWrapper; + } + + getTypeName(): string { + return DoubleValue.typeName; + } + + getFields(): FieldList { + return DoubleValue.fields; + } + + getProtoRuntime(): ProtoRuntime { + return DoubleValue.runtime; + } +} + +@Sendable +export class FloatValue extends Message { + /** + * @generated from field: float value = 1; + */ + value: number = 0; + + static fieldWrapper: FieldWrapper | undefined; + + static readonly typeName: string = "google.protobuf.FloatValue"; + + static readonly runtime: ProtoRuntime = proto3; + + static readonly fieldList: collections.Map= new collections.Map(); + + static fields: FieldList = proto3.util.newFieldList(getFieldListsFromArray(() => { + let res = new collections.Array(); + res.push(new FieldInfo().buildNo(1).buildName("value").buildKind("scalar").buildT(2)); + return res + })); + + constructor() { + super(); + } + + getType(): MessageType{ + return FloatValueType.instance; + } + + + static fromBinary(bytes: Uint8Array, options?: Partial): FloatValue { + return new FloatValue().getType().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial< JsonReadOptions>): FloatValue { + return new FloatValue().getType().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): FloatValue { + return new FloatValue().getType().fromJson(jsonString, options); + } + + static equals(a: FloatValue | undefined, b: FloatValue | undefined): boolean { + return proto3.util.equals(FloatValueType.instance, a, b); + } +} + +@Sendable +export class FloatValueType extends MessageType { + static instance: FloatValueType = new FloatValueType(); + + constructor() { + super(); + } + + create():FloatValue{ + return new FloatValue(); + } + + equals(a: FloatValue | Message | undefined | null, b: FloatValue | Message | undefined | null): boolean { + return proto3.util.equals(this, a, b); + } + + clone(message: FloatValue): FloatValue { + return proto3.util.clone(message); + } + + getFieldWrapper(): FieldWrapper | undefined { + return FloatValue.fieldWrapper; + } + + getTypeName(): string { + return FloatValue.typeName; + } + + getFields(): FieldList { + return FloatValue.fields; + } + + getProtoRuntime(): ProtoRuntime { + return FloatValue.runtime; + } +} + +@Sendable +export class Int64Value extends Message { + /** + * @generated from field: int64 value = 1; + */ + value: bigint = protoInt64.zero; + + static fieldWrapper: FieldWrapper | undefined; + + static readonly typeName: string = "google.protobuf.Int64Value"; + + static readonly runtime: ProtoRuntime = proto3; + + static readonly fieldList: collections.Map= new collections.Map(); + + static fields: FieldList = proto3.util.newFieldList(getFieldListsFromArray(() => { + let res = new collections.Array(); + res.push(new FieldInfo().buildNo(1).buildName("value").buildKind("scalar").buildT(3)); + return res + })); + + constructor() { + super(); + } + + getType(): MessageType{ + return Int64ValueType.instance; + } + + + static fromBinary(bytes: Uint8Array, options?: Partial): Int64Value { + return new Int64Value().getType().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial< JsonReadOptions>): Int64Value { + return new Int64Value().getType().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): Int64Value { + return new Int64Value().getType().fromJson(jsonString, options); + } + + static equals(a: Int64Value | undefined, b: Int64Value | undefined): boolean { + return proto3.util.equals(Int64ValueType.instance, a, b); + } +} + +@Sendable +export class Int64ValueType extends MessageType { + static instance: Int64ValueType = new Int64ValueType(); + + constructor() { + super(); + } + + create():Int64Value{ + return new Int64Value(); + } + + equals(a: Int64Value | Message | undefined | null, b: Int64Value | Message | undefined | null): boolean { + return proto3.util.equals(this, a, b); + } + + clone(message: Int64Value): Int64Value { + return proto3.util.clone(message); + } + + getFieldWrapper(): FieldWrapper | undefined { + return Int64Value.fieldWrapper; + } + + getTypeName(): string { + return Int64Value.typeName; + } + + getFields(): FieldList { + return Int64Value.fields; + } + + getProtoRuntime(): ProtoRuntime { + return Int64Value.runtime; + } +} + +@Sendable +export class UInt64Value extends Message { + /** + * @generated from field: uint64 value = 1; + */ + value: bigint = protoInt64.zero; + + static fieldWrapper: FieldWrapper | undefined; + + static readonly typeName: string = "google.protobuf.UInt64Value"; + + static readonly runtime: ProtoRuntime = proto3; + + static readonly fieldList: collections.Map= new collections.Map(); + + static fields: FieldList = proto3.util.newFieldList(getFieldListsFromArray(() => { + let res = new collections.Array(); + res.push(new FieldInfo().buildNo(1).buildName("value").buildKind("scalar").buildT(4)); + return res + })); + + constructor() { + super(); + } + + getType(): MessageType{ + return UInt64ValueType.instance; + } + + + static fromBinary(bytes: Uint8Array, options?: Partial): UInt64Value { + return new UInt64Value().getType().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial< JsonReadOptions>): UInt64Value { + return new UInt64Value().getType().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): UInt64Value { + return new UInt64Value().getType().fromJson(jsonString, options); + } + + static equals(a: UInt64Value | undefined, b: UInt64Value | undefined): boolean { + return proto3.util.equals(UInt64ValueType.instance, a, b); + } +} + +@Sendable +export class UInt64ValueType extends MessageType { + static instance: UInt64ValueType = new UInt64ValueType(); + + constructor() { + super(); + } + + create():UInt64Value{ + return new UInt64Value(); + } + + equals(a: UInt64Value | Message | undefined | null, b: UInt64Value | Message | undefined | null): boolean { + return proto3.util.equals(this, a, b); + } + + clone(message: UInt64Value): UInt64Value { + return proto3.util.clone(message); + } + + getFieldWrapper(): FieldWrapper | undefined { + return UInt64Value.fieldWrapper + } + + getTypeName(): string { + return UInt64Value.typeName; + } + + getFields(): FieldList { + return UInt64Value.fields; + } + + getProtoRuntime(): ProtoRuntime { + return UInt64Value.runtime; + } +} + +@Sendable +export class Int32Value extends Message { + /** + * @generated from field: int32 value = 1; + */ + value: number = 0; + + static fieldWrapper: FieldWrapper | undefined; + + static readonly typeName: string = "google.protobuf.Int32Value"; + + static readonly runtime: ProtoRuntime = proto3; + + static readonly fieldList: collections.Map= new collections.Map(); + + static fields: FieldList = proto3.util.newFieldList(getFieldListsFromArray(() => { + let res = new collections.Array(); + res.push(new FieldInfo().buildNo(1).buildName("value").buildKind("scalar").buildT(5)); + return res + })); + + constructor() { + super(); + } + + getType(): MessageType{ + return Int32ValueType.instance; + } + + + static fromBinary(bytes: Uint8Array, options?: Partial): Int32Value { + return new Int32Value().getType().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial< JsonReadOptions>): Int32Value { + return new Int32Value().getType().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): Int32Value { + return new Int32Value().getType().fromJson(jsonString, options); + } + + static equals(a: Int32Value | undefined, b: Int32Value | undefined): boolean { + return proto3.util.equals(Int32ValueType.instance, a, b); + } +} + +@Sendable +export class Int32ValueType extends MessageType { + static instance: Int32ValueType = new Int32ValueType(); + + constructor() { + super(); + } + + create():Int32Value{ + return new Int32Value(); + } + + equals(a: Int32Value | Message | undefined | null, b: Int32Value | Message | undefined | null): boolean { + return proto3.util.equals(this, a, b); + } + + clone(message: Int32Value): Int32Value { + return proto3.util.clone(message); + } + + getFieldWrapper(): FieldWrapper | undefined { + return Int32Value.fieldWrapper; + } + + getTypeName(): string { + return Int32Value.typeName; + } + + getFields(): FieldList { + return Int32Value.fields; + } + + getProtoRuntime(): ProtoRuntime { + return Int32Value.runtime; + } +} + +@Sendable +export class UInt32Value extends Message { + /** + * @generated from field: uint32 value = 1; + */ + value: number = 0; + + static fieldWrapper: FieldWrapper | undefined; + + static readonly typeName: string = "google.protobuf.UInt32Value"; + + static readonly runtime: ProtoRuntime = proto3; + + static readonly fieldList: collections.Map= new collections.Map(); + + static fields: FieldList = proto3.util.newFieldList(getFieldListsFromArray(() => { + let res = new collections.Array(); + res.push(new FieldInfo().buildNo(1).buildName("value").buildKind("scalar").buildT(13)); + return res + })); + + constructor() { + super(); + } + + getType(): MessageType{ + return UInt32ValueType.instance; + } + + + static fromBinary(bytes: Uint8Array, options?: Partial): UInt32Value { + return new UInt32Value().getType().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial< JsonReadOptions>): UInt32Value { + return new UInt32Value().getType().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): UInt32Value { + return new UInt32Value().getType().fromJson(jsonString, options); + } + + static equals(a: UInt32Value | undefined, b: UInt32Value | undefined): boolean { + return proto3.util.equals(UInt32ValueType.instance, a, b); + } +} + +@Sendable +export class UInt32ValueType extends MessageType { + static instance: UInt32ValueType = new UInt32ValueType(); + + constructor() { + super(); + } + + create():UInt32Value{ + return new UInt32Value(); + } + + equals(a: UInt32Value | Message | undefined | null, b: UInt32Value | Message | undefined | null): boolean { + return proto3.util.equals(this, a, b); + } + + clone(message: UInt32Value): UInt32Value { + return proto3.util.clone(message); + } + + getFieldWrapper(): FieldWrapper | undefined { + return UInt32Value.fieldWrapper; + } + + getTypeName(): string { + return UInt32Value.typeName; + } + + getFields(): FieldList { + return UInt32Value.fields; + } + + getProtoRuntime(): ProtoRuntime { + return UInt32Value.runtime; + } +} + +@Sendable +export class BoolValue extends Message { + /** + * @generated from field: bool value = 1; + */ + value: boolean = false; + + static fieldWrapper: FieldWrapper | undefined; + + static readonly typeName: string = "google.protobuf.BoolValue"; + + static readonly runtime: ProtoRuntime = proto3; + + static readonly fieldList: collections.Map= new collections.Map(); + + static fields: FieldList = proto3.util.newFieldList(getFieldListsFromArray(() => { + let res = new collections.Array(); + res.push(new FieldInfo().buildNo(1).buildName("value").buildKind("scalar").buildT(8)); + return res + })); + + constructor() { + super(); + } + + getType(): MessageType{ + return BoolValueType.instance; + } + + + static fromBinary(bytes: Uint8Array, options?: Partial): BoolValue { + return new BoolValue().getType().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial< JsonReadOptions>): BoolValue { + return new BoolValue().getType().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): BoolValue { + return new BoolValue().getType().fromJson(jsonString, options); + } + + static equals(a: BoolValue | undefined, b: BoolValue | undefined): boolean { + return proto3.util.equals(BoolValueType.instance, a, b); + } +} + +@Sendable +export class BoolValueType extends MessageType { + static instance: BoolValueType = new BoolValueType(); + + constructor() { + super(); + } + + create():BoolValue{ + return new BoolValue(); + } + + equals(a: BoolValue | Message | undefined | null, b: BoolValue | Message | undefined | null): boolean { + return proto3.util.equals(this, a, b); + } + + clone(message: BoolValue): BoolValue { + return proto3.util.clone(message); + } + + getFieldWrapper(): FieldWrapper | undefined { + return BoolValue.fieldWrapper; + } + + getTypeName(): string { + return BoolValue.typeName; + } + + getFields(): FieldList { + return BoolValue.fields; + } + + getProtoRuntime(): ProtoRuntime { + return BoolValue.runtime; + } +} + +@Sendable +export class StringValue extends Message { + /** + * @generated from field: string value = 1; + */ + value: string = ""; + + static fieldWrapper: FieldWrapper | undefined; + + static readonly typeName: string = "google.protobuf.StringValue"; + + static readonly runtime: ProtoRuntime = proto3; + + static readonly fieldList: collections.Map= new collections.Map(); + + static fields: FieldList = proto3.util.newFieldList(getFieldListsFromArray(() => { + let res = new collections.Array(); + res.push(new FieldInfo().buildNo(1).buildName("value").buildKind("scalar").buildT(9)); + return res + })); + + constructor() { + super(); + } + + getType(): MessageType{ + return StringValueType.instance; + } + + + static fromBinary(bytes: Uint8Array, options?: Partial): StringValue { + return new StringValue().getType().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial< JsonReadOptions>): StringValue { + return new StringValue().getType().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): StringValue { + return new StringValue().getType().fromJson(jsonString, options); + } + + static equals(a: StringValue | undefined, b: StringValue | undefined): boolean { + return proto3.util.equals(StringValueType.instance, a, b); + } +} + +@Sendable +export class StringValueType extends MessageType { + static instance: StringValueType = new StringValueType(); + + constructor() { + super(); + } + + create():StringValue{ + return new StringValue(); + } + + equals(a: StringValue | Message | undefined | null, b: StringValue | Message | undefined | null): boolean { + return proto3.util.equals(this, a, b); + } + + clone(message: StringValue): StringValue { + return proto3.util.clone(message); + } + + getFieldWrapper(): FieldWrapper | undefined { + return StringValue.fieldWrapper; + } + + getTypeName(): string { + return StringValue.typeName; + } + + getFields(): FieldList { + return StringValue.fields; + } + + getProtoRuntime(): ProtoRuntime { + return StringValue.runtime; + } +} + +@Sendable +export class BytesValue extends Message { + /** + * @generated from field: bytes value = 1; + */ + value: collections.Uint8Array = new collections.Uint8Array(0); + + static fieldWrapper: FieldWrapper | undefined; + + static readonly typeName: string = "google.protobuf.BytesValue"; + + static readonly runtime: ProtoRuntime = proto3; + + static readonly fieldList: collections.Map= new collections.Map(); + + static fields: FieldList = proto3.util.newFieldList(getFieldListsFromArray(() => { + let res = new collections.Array(); + res.push(new FieldInfo().buildNo(1).buildName("value").buildKind("scalar").buildT(12)); + return res + })); + + constructor() { + super(); + } + + getType(): MessageType{ + return BytesValueType.instance; + } + + + static fromBinary(bytes: Uint8Array, options?: Partial): BytesValue { + return new BytesValue().getType().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial< JsonReadOptions>): BytesValue { + return new BytesValue().getType().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): BytesValue { + return new BytesValue().getType().fromJson(jsonString, options); + } + + static equals(a: BytesValue | undefined, b: BytesValue | undefined): boolean { + return proto3.util.equals(BytesValueType.instance, a, b); + } +} + +@Sendable +export class BytesValueType extends MessageType { + static instance: BytesValueType = new BytesValueType(); + + constructor() { + super(); + } + + create():BytesValue{ + return new BytesValue(); + } + + equals(a: BytesValue | Message | undefined | null, b: BytesValue | Message | undefined | null): boolean { + return proto3.util.equals(this, a, b); + } + + clone(message: BytesValue): BytesValue { + return proto3.util.clone(message); + } + + getFieldWrapper(): FieldWrapper | undefined { + return BytesValue.fieldWrapper; + } + + getTypeName(): string { + return BytesValue.typeName; + } + + getFields(): FieldList { + return BytesValue.fields; + } + + getProtoRuntime(): ProtoRuntime { + return BytesValue.runtime; + } +} \ No newline at end of file diff --git a/protobuf-bitfun/src/google/varint.ets b/protobuf-bitfun/src/google/varint.ets new file mode 100644 index 0000000..03c13f2 --- /dev/null +++ b/protobuf-bitfun/src/google/varint.ets @@ -0,0 +1,375 @@ +// Copyright 2008 Google Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Code generated by the Protocol Buffer compiler is owned by the owner +// of the input file used when generating it. This code is not +// standalone and requires a support library to be linked with it. This +// support library is itself covered by the above license. + +/* eslint-disable prefer-const,@typescript-eslint/restrict-plus-operands */ + +/** + * Read a 64 bit varint as two JS numbers. + * + * Returns tuple: + * [0]: low bits + * [1]: high bits + * + * Copyright 2008 Google Inc. All rights reserved. + * + * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/buffer_decoder.js#L175 + */ +export function varint64read(readerLike: ReaderLike): [number, number] { + let lowBits = 0; + let highBits = 0; + + for (let shift = 0; shift < 28; shift += 7) { + let b = readerLike.buf[readerLike.pos++]; + lowBits |= (b & 0x7f) << shift; + if ((b & 0x80) == 0) { + readerLike.assertBounds(); + return [lowBits, highBits]; + } + } + + let middleByte = readerLike.buf[readerLike.pos++]; + + // last four bits of the first 32 bit number + lowBits |= (middleByte & 0x0f) << 28; + + // 3 upper bits are part of the next 32 bit number + highBits = (middleByte & 0x70) >> 4; + + if ((middleByte & 0x80) == 0) { + readerLike.assertBounds(); + return [lowBits, highBits]; + } + + for (let shift = 3; shift <= 31; shift += 7) { + let b = readerLike.buf[readerLike.pos++]; + highBits |= (b & 0x7f) << shift; + if ((b & 0x80) == 0) { + readerLike.assertBounds(); + return [lowBits, highBits]; + } + } + + throw new Error("invalid varint"); +} + +/** + * Write a 64 bit varint, given as two JS numbers, to the given bytes array. + * + * Copyright 2008 Google Inc. All rights reserved. + * + * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/writer.js#L344 + */ +export function varint64write(lo: number, hi: number, bytes: number[]): void { + for (let i = 0; i < 28; i = i + 7) { + const shift = lo >>> i; + const hasNext = !(shift >>> 7 == 0 && hi == 0); + const byte = (hasNext ? shift | 0x80 : shift) & 0xff; + bytes.push(byte); + if (!hasNext) { + return; + } + } + + const splitBits = ((lo >>> 28) & 0x0f) | ((hi & 0x07) << 4); + const hasMoreBits = !(hi >> 3 == 0); + bytes.push((hasMoreBits ? splitBits | 0x80 : splitBits) & 0xff); + + if (!hasMoreBits) { + return; + } + + for (let i = 3; i < 31; i = i + 7) { + const shift = hi >>> i; + const hasNext = !(shift >>> 7 == 0); + const byte = (hasNext ? shift | 0x80 : shift) & 0xff; + bytes.push(byte); + if (!hasNext) { + return; + } + } + + bytes.push((hi >>> 31) & 0x01); +} + +// constants for binary math +const TWO_PWR_32_DBL = 0x100000000; + +/** + * Parse decimal string of 64 bit integer value as two JS numbers. + * + * Copyright 2008 Google Inc. All rights reserved. + * + * See https://github.com/protocolbuffers/protobuf-javascript/blob/a428c58273abad07c66071d9753bc4d1289de426/experimental/runtime/int64.js#L10 + */ +interface DecimalBits { + lowBits: number; + highBits: number; +} + +export function int64FromString(dec: string): BitsType { + // Check for minus sign. + const minus = dec[0] === "-"; + if (minus) { + dec = dec.slice(1); + } + + // Work 6 decimal digits at a time, acting like we're converting base 1e6 + // digits to binary. This is safe to do with floating point math because + // Number.isSafeInteger(ALL_32_BITS * 1e6) == true. + + let bits: DecimalBits = { + lowBits: 0, + highBits: 0, + }; + + add1e6digit(dec, bits, -24, -18); + add1e6digit(dec, bits, -18, -12); + add1e6digit(dec, bits, -12, -6); + add1e6digit(dec, bits, -6); + return minus ? negate(bits.lowBits, bits.highBits) : newBits(bits.lowBits, bits.highBits); +} + +function add1e6digit(dec: string, bits: DecimalBits, begin: number, end?: number) { + const base = 1e6; + // Note: Number('') is 0. + const digit1e6 = Number(dec.slice(begin, end)); + bits.highBits *= base; + bits.lowBits = bits.lowBits * base + digit1e6; + // Carry bits from lowBits to + if (bits.lowBits >= TWO_PWR_32_DBL) { + bits.highBits = bits.highBits + ((bits.lowBits / TWO_PWR_32_DBL) | 0); + bits.lowBits = bits.lowBits % TWO_PWR_32_DBL; + } +} + +/** + * Losslessly converts a 64-bit signed integer in 32:32 split representation + * into a decimal string. + * + * Copyright 2008 Google Inc. All rights reserved. + * + * See https://github.com/protocolbuffers/protobuf-javascript/blob/a428c58273abad07c66071d9753bc4d1289de426/experimental/runtime/int64.js#L10 + */ +export function int64ToString(lo: number, hi: number): string { + let bits = newBits(lo, hi); + // If we're treating the input as a signed value and the high bit is set, do + // a manual two's complement conversion before the decimal conversion. + const negative = (bits.hi & 0x80000000); + if (negative) { + bits = negate(bits.lo, bits.hi); + } + const result = uInt64ToString(bits.lo, bits.hi); + return negative ? "-" + result : result; +} + +/** + * Losslessly converts a 64-bit unsigned integer in 32:32 split representation + * into a decimal string. + * + * Copyright 2008 Google Inc. All rights reserved. + * + * See https://github.com/protocolbuffers/protobuf-javascript/blob/a428c58273abad07c66071d9753bc4d1289de426/experimental/runtime/int64.js#L10 + */ +export function uInt64ToString(l: number, h: number): string { + let unsignedInt = toUnsigned(l, h); + let lo = unsignedInt.lo; + let hi = unsignedInt.hi; + // Skip the expensive conversion if the number is small enough to use the + // built-in conversions. + // Number.MAX_SAFE_INTEGER = 0x001FFFFF FFFFFFFF, thus any number with + // highBits <= 0x1FFFFF can be safely expressed with a double and retain + // integer precision. + // Proven by: Number.isSafeInteger(0x1FFFFF * 2**32 + 0xFFFFFFFF) == true. + if (hi <= 0x1FFFFF) { + return String(TWO_PWR_32_DBL * hi + lo); + } + + // What this code is doing is essentially converting the input number from + // base-2 to base-1e7, which allows us to represent the 64-bit range with + // only 3 (very large) digits. Those digits are then trivial to convert to + // a base-10 string. + + // The magic numbers used here are - + // 2^24 = 16777216 = (1,6777216) in base-1e7. + // 2^48 = 281474976710656 = (2,8147497,6710656) in base-1e7. + + // Split 32:32 representation into 16:24:24 representation so our + // intermediate digits don't overflow. + const low = lo & 0xFFFFFF; + const mid = ((lo >>> 24) | (hi << 8)) & 0xFFFFFF; + const high = (hi >> 16) & 0xFFFF; + + // Assemble our three base-1e7 digits, ignoring carries. The maximum + // value in a digit at this step is representable as a 48-bit integer, which + // can be stored in a 64-bit floating point number. + let digitA = low + (mid * 6777216) + (high * 6710656); + let digitB = mid + (high * 8147497); + let digitC = (high * 2); + + // Apply carries from A to B and from B to C. + const base = 10000000; + if (digitA >= base) { + digitB += Math.floor(digitA / base); + digitA %= base; + } + + if (digitB >= base) { + digitC += Math.floor(digitB / base); + digitB %= base; + } + + // If digitC is 0, then we should have returned in the trivial code path + // at the top for non-safe integers. Given this, we can assume both digitB + // and digitA need leading zeros. + return digitC.toString() + decimalFrom1e7WithLeadingZeros(digitB) + + decimalFrom1e7WithLeadingZeros(digitA); +} + +interface BitsType { + lo: number; + hi: number; +} + +function toUnsigned(lo: number, hi: number): BitsType { + return { lo: lo >>> 0, hi: hi >>> 0 }; +} + +function newBits(lo: number, hi: number): BitsType { + return { lo: lo | 0, hi: hi | 0 }; +} + +/** + * Returns two's compliment negation of input. + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Signed_32-bit_integers + */ +function negate(lowBits: number, highBits: number) { + highBits = ~highBits; + if (lowBits) { + lowBits = ~lowBits + 1; + } else { + // If lowBits is 0, then bitwise-not is 0xFFFFFFFF, + // adding 1 to that, results in 0x100000000, which leaves + // the low bits 0x0 and simply adds one to the high bits. + highBits += 1; + } + return newBits(lowBits, highBits); +} + +/** + * Returns decimal representation of digit1e7 with leading zeros. + */ +const decimalFrom1e7WithLeadingZeros = (digit1e7: number) => { + const partial = String(digit1e7); + return "0000000".slice(partial.length) + partial; +}; + +/** + * Write a 32 bit varint, signed or unsigned. Same as `varint64write(0, value, bytes)` + * + * Copyright 2008 Google Inc. All rights reserved. + * + * See https://github.com/protocolbuffers/protobuf/blob/1b18833f4f2a2f681f4e4a25cdf3b0a43115ec26/js/binary/encoder.js#L144 + */ +export function varint32write(value: number, bytes: number[]): void { + if (value >= 0) { + // write value as varint 32 + while (value > 0x7f) { + bytes.push((value & 0x7f) | 0x80); + value = value >>> 7; + } + bytes.push(value); + } else { + for (let i = 0; i < 9; i++) { + bytes.push((value & 127) | 128); + value = value >> 7; + } + bytes.push(1); + } +} + +/** + * Read an unsigned 32 bit varint. + * + * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/buffer_decoder.js#L220 + */ +export function varint32read(readerLike: ReaderLike): number { + let b = readerLike.buf[readerLike.pos++]; + let result = b & 0x7f; + if ((b & 0x80) == 0) { + readerLike.assertBounds(); + return result; + } + + b = readerLike.buf[readerLike.pos++]; + result |= (b & 0x7f) << 7; + if ((b & 0x80) == 0) { + readerLike.assertBounds(); + return result; + } + + b = readerLike.buf[readerLike.pos++]; + result |= (b & 0x7f) << 14; + if ((b & 0x80) == 0) { + readerLike.assertBounds(); + return result; + } + + b = readerLike.buf[readerLike.pos++]; + result |= (b & 0x7f) << 21; + if ((b & 0x80) == 0) { + readerLike.assertBounds(); + return result; + } + + // Extract only last 4 bits + b = readerLike.buf[readerLike.pos++]; + result |= (b & 0x0f) << 28; + + for (let readBytes = 5; (b & 0x80) !== 0 && readBytes < 10; readBytes++) + b = readerLike.buf[readerLike.pos++]; + + if ((b & 0x80) != 0) throw new Error("invalid varint"); + + readerLike.assertBounds(); + + // Result can have 32 bits, convert it to unsigned + return result >>> 0; +} + +interface ReaderLike { + buf: Uint8Array; + pos: number; + len: number; + + assertBounds(): void; +}; diff --git a/protobuf-bitfun/src/index.ets b/protobuf-bitfun/src/index.ets new file mode 100644 index 0000000..5d67fb1 --- /dev/null +++ b/protobuf-bitfun/src/index.ets @@ -0,0 +1,71 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +export { proto3 } from "./proto3"; +export { proto2 } from "./proto2"; +export { protoDouble } from "./proto-double"; +export { protoInt64 } from "./proto-int64"; +export { protoBase64 } from "./proto-base64"; +export { codegenInfo } from "./codegen-info"; +export { isFieldSet } from "./private/reflect"; + +export { Message } from "./message"; +export { isMessage } from "./is-message"; + +export { FieldList } from "./field-list"; +export { LongType, ScalarType } from "./scalar"; +export type { ScalarValue } from "./scalar"; +export type { EnumType, EnumValueInfo } from "./enum"; +export type { + ServiceType, + MethodInfo, + MethodInfoUnary, + MethodInfoServerStreaming, + MethodInfoClientStreaming, + MethodInfoBiDiStreaming, +} from "./service-type"; +export { MethodKind, MethodIdempotency } from "./service-type"; + +export { WireType, BinaryWriter, BinaryReader } from "./binary-encoding"; +export type { IBinaryReader, IBinaryWriter } from "./binary-encoding"; +export type { + BinaryWriteOptions, + BinaryReadOptions, +} from "./binary-format"; +export { BinaryFormat } from "./binary-format"; +export { readField } from './private/binary-format'; + +export type { + JsonObject, + JsonValue, + JsonReadOptions, + JsonWriteOptions, + JsonWriteStringOptions, +} from "./json-format"; + +export type { + DescriptorSet, + AnyDesc, + DescFile, + DescEnum, + DescEnumValue, + DescMessage, + DescOneof, + DescField, + DescService, + DescMethod, + DescExtension, + DescComments, +} from "./descriptor-set"; + diff --git a/protobuf-bitfun/src/is-message.ets b/protobuf-bitfun/src/is-message.ets new file mode 100644 index 0000000..9918d1a --- /dev/null +++ b/protobuf-bitfun/src/is-message.ets @@ -0,0 +1,66 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import type { MessageType } from "./message-type"; +import { Message } from "./message"; + +/** + * Check whether the given object is any subtype of Message or is a specific + * Message by passing the type. + * + * Just like `instanceof`, `isMessage` narrows the type. The advantage of + * `isMessage` is that it compares identity by the message type name, not by + * class identity. This makes it robust against the dual package hazard and + * similar situations, where the same message is duplicated. + * + * This function is _mostly_ equivalent to the `instanceof` operator. For + * example, `isMessage(foo, MyMessage)` is the same as `foo instanceof MyMessage`, + * and `isMessage(foo)` is the same as `foo instanceof Message`. In most cases, + * `isMessage` should be preferred over `instanceof`. + * + * However, due to the fact that `isMessage` does not use class identity, there + * are subtle differences between this function and `instanceof`. Notably, + * calling `isMessage` on an explicit type of Message will return false. + */ +interface GeneratedTypeLiteralInterface_1{ + getType(): ESObject; +} + +export function isMessage >( + arg: T, + type?: MessageType, +): boolean { + if (arg === null || typeof arg != "object") { + return false; + } + if ( + !Object.getOwnPropertyNames(Message).every( + (m) =>{ + (arg as Object as Record)[m] !== undefined && typeof (arg as Object as Record)[m] == "function" + } + ) + ) { + return false; + } + const actualType: ESObject = (arg as GeneratedTypeLiteralInterface_1).getType(); + if ( + actualType === null || + typeof actualType != "function" || + !(actualType.typeName !== undefined) || + typeof actualType.typeName != "string" + ) { + return false; + } + return type === undefined ? true : actualType.typeName == type.getTypeName(); +} diff --git a/protobuf-bitfun/src/json-format.ets b/protobuf-bitfun/src/json-format.ets new file mode 100644 index 0000000..bb2d2cc --- /dev/null +++ b/protobuf-bitfun/src/json-format.ets @@ -0,0 +1,173 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import type { Message } from "./message"; +import type { MessageType } from "./message-type"; +import type { ScalarType, LongType, ScalarValue } from "./scalar"; +import type { + IMessageTypeRegistry, +} from "./type-registry"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * JsonFormat is the contract for serializing messages to and from JSON. + * Implementations may be specific to a proto syntax, and can be reflection + * based, or delegate to speed optimized generated code. + */ +@Sendable +export class JsonFormat { + /** + * Provide options for parsing JSON data. + */ + makeReadOptions( + options?: Partial, + ): Readonly{ + throw Error(); + } + + /** + * Provide options for serializing to JSON. + */ + makeWriteOptions( + options?: Partial, + ): Readonly{ + throw Error(); + } + + /** + * Parse a message from JSON. + */ + readMessage>( + type: MessageType, + jsonValue: JsonValue, + options: JsonReadOptions, + message?: T, + ): T{ + throw Error(); + }; + + /** + * Serialize a message to JSON. + */ + writeMessage>(message: Message, options: JsonWriteOptions): JsonValue{ + throw Error(); + }; + + /** + * Parse a single scalar value from JSON. + * + * This method may throw an error, but it may have a blank error message. + * Callers are expected to provide context. + */ + readScalar(type: ScalarType, json: JsonValue, longType?: LongType): ScalarValue | Symbol{ + throw Error(); + }; + + /** + * Serialize a single scalar value to JSON. + */ + writeScalar( + type: ScalarType, + value: boolean | string | number | Uint8Array, + emitDefaultValues: boolean, + ): JsonValue | undefined{ + throw Error(); + }; + + /** + * Returns a short string representation of a JSON value, suitable for error messages. + */ + debug(json: JsonValue): string{ + throw Error(); + } + + constructor(){ + } +} + +/** + * Options for parsing JSON data. + */ +export interface JsonReadOptions { + /** + * Ignore unknown fields: Proto3 JSON parser should reject unknown fields + * by default. This option ignores unknown fields in parsing, as well as + * unrecognized enum string representations. + */ + ignoreUnknownFields: boolean; + + /** + * This option is required to read `google.protobuf.Any` and extensions + * from JSON format. + */ + typeRegistry?: IMessageTypeRegistry; +} + +/** + * Options for serializing to JSON. + */ +export interface JsonWriteOptions { + /** + * Emit fields with default values: Fields with default values are omitted + * by default in proto3 JSON output. This option overrides this behavior + * and outputs fields with their default values. + */ + emitDefaultValues: boolean; + + /** + * Emit enum values as integers instead of strings: The name of an enum + * value is used by default in JSON output. An option may be provided to + * use the numeric value of the enum value instead. + */ + enumAsInteger: boolean; + + /** + * Use proto field name instead of lowerCamelCase name: By default proto3 + * JSON printer should convert the field name to lowerCamelCase and use + * that as the JSON name. An implementation may provide an option to use + * proto field name as the JSON name instead. Proto3 JSON parsers are + * required to accept both the converted lowerCamelCase name and the proto + * field name. + */ + useProtoFieldName: boolean; + + /** + * This option is required to write `google.protobuf.Any` and extensions + * to JSON format. + */ + typeRegistry?: IMessageTypeRegistry ; +} + +/** + * Options for serializing to JSON. + */ +export interface JsonWriteStringOptions extends JsonWriteOptions { + prettySpaces: number; +} + +/** + * Represents any possible JSON value: + * - number + * - string + * - boolean + * - null + * - object (with any JSON value as property) + * - array (with any JSON value as element) + */ +export type JsonValue = Object + +/** + * Represents a JSON object. + */ +export type JsonObject = Record; diff --git a/protobuf-bitfun/src/message-type.ets b/protobuf-bitfun/src/message-type.ets new file mode 100644 index 0000000..9c8a3f9 --- /dev/null +++ b/protobuf-bitfun/src/message-type.ets @@ -0,0 +1,89 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { BinaryReadOptions, BinaryWriteOptions, JsonReadOptions, JsonValue, JsonWriteOptions, + JsonWriteStringOptions } from "."; +import type { FieldList } from "./field-list"; +import type { ProtoRuntime } from "./private/proto-runtime"; +import { Message } from "./message"; +import type { FieldWrapper } from "./private/field-wrapper"; + +/** + * MessageType represents a protobuf message. It provides: + * - a constructor that produces an instance of the message + * - metadata for reflection-based operations + * - common functionality like serialization + */ + +@Sendable +export abstract class MessageType = EmptyMessage> { + + abstract create(data?: Message): T; + + abstract clone(message: T): T; + + abstract equals(a: T | Message | null | undefined, b: T | Message | null | undefined): boolean; + + fromBinary(bytes: Uint8Array, options?: Partial): T { + let message = this.create(); + message.fromBinary(bytes, options); + return message; + } + + fromJson(jsonValue: JsonValue, options?: Partial): T { + let message = this.create(); + message.fromJson(jsonValue, options); + return message; + } + + fromJsonString(jsonString: string, options?: Partial): T { + let message = this.create(); + message.fromJsonString(jsonString, options); + return message; + } + + toBinary(options?: Partial): Uint8Array { + let message = this.create(); + return message.toBinary(options); + } + + toJson(options?: Partial): JsonValue { + let message = this.create(); + return message.toJson(options); + } + + toJsonString(options?: Partial): string { + let message = this.create(); + return message.toJsonString(options); + } + + abstract getFieldWrapper(): FieldWrapper | undefined; + + abstract getTypeName(): string; + + abstract getFields(): FieldList; + + abstract getProtoRuntime(): ProtoRuntime; +} + +@Sendable +export class EmptyMessage extends Message{ + constructor() { + super(); + } + + getType(): MessageType { + throw new Error("Method not implemented") + } +} diff --git a/protobuf-bitfun/src/message.ets b/protobuf-bitfun/src/message.ets new file mode 100644 index 0000000..a95808f --- /dev/null +++ b/protobuf-bitfun/src/message.ets @@ -0,0 +1,157 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import type { BinaryReadOptions, BinaryWriteOptions } from "./binary-format"; +import { MessageType } from "./message-type"; +import { ProtoRuntime } from './private/proto-runtime'; +import type{ + JsonReadOptions, + JsonValue, + JsonWriteOptions, + JsonWriteStringOptions, +} from "./json-format"; +import { EmptyMessage } from "./message-type"; + +/** + * Message is the base class of every message, generated, or created at + * runtime. + * + * It is _not_ safe to extend this class. If you want to create a message at + * run time, use proto3.makeMessageType(). + */ + +export type AnyMessage = Record + +@Sendable +export abstract class Message = EmptyMessage> { + /** + * Compare with a message of the same type. + * Note that this function disregards extensions and unknown fields. + */ + equals(other: T | undefined | null): boolean { + return this.getType().equals(this,other); + } + + /** + * Create a deep copy. + */ + clone(this: T): T { + return this.getType().clone(this); + } + + /** + * Parse from binary data, merging fields. + * + * Repeated fields are appended. Map entries are added, overwriting + * existing keys. + * + * If a message field is already present, it will be merged with the + * new data. + */ + fromBinary(this: T, bytes: Uint8Array, options?: Partial): T { + const type = this.getType(), + format = type.getProtoRuntime().bin, + opt = format.makeReadOptions(options); + format.readMessage(this, opt.readerFactory(bytes), bytes.byteLength, opt); + return this; + } + + /** + * Parse a message from a JSON value. + */ + fromJson(this: T, jsonValue: JsonValue, options?: Partial): T { + const type = this.getType(), + format = type.getProtoRuntime().json, + opt = format.makeReadOptions(options); + format.readMessage(type, jsonValue, opt, this); + return this; + } + + /** + * Parse a message from a JSON string. + */ + fromJsonString(this: T, jsonString: string, options?: Partial): T { + let json: JsonValue; + try { + json = JSON.parse(jsonString) as JsonValue; + } catch (e) { + throw new Error( + `cannot decode ${this.getType().getTypeName()} from JSON: ${ + e instanceof Error ? e.message : String(e) + }`, + ); + } + return this.fromJson(json, options); + } + + /** + * Serialize the message to binary data. + */ + toBinary(options?: Partial): Uint8Array { + const type = this.getType(), + bin = type.getProtoRuntime().bin, + opt = bin.makeWriteOptions(options), + writer = opt.writerFactory(); + bin.writeMessage(this, writer, opt); + return writer.finish(); + } + + /** + * Serialize the message to a JSON value, a JavaScript value that can be + * passed to JSON.stringify(). + */ + toJson(options?: Partial): JsonValue { + const type = this.getType(), + json = type.getProtoRuntime().json, + opt = json.makeWriteOptions(options); + return json.writeMessage(this, opt); + } + + /** + * Serialize the message to a JSON string. + */ + toJsonString(options?: Partial): string { + const value: JsonValue = this.toJson(options); + return JSON.stringify(value, null, options?.prettySpaces ?? 0); + } + + /** + * Override for serialization behavior. This will be invoked when calling + * JSON.stringify on this message (i.e. JSON.stringify(msg)). + * + * Note that this will not serialize google.protobuf.Any with a packed + * message because the protobuf JSON format specifies that it needs to be + * unpacked, and this is only possible with a type registry to look up the + * message type. As a result, attempting to serialize a message with this + * type will throw an Error. + * + * This method is protected because you should not need to invoke it + * directly -- instead use JSON.stringify or toJsonString for + * stringified JSON. Alternatively, if actual JSON is desired, you should + * use toJson. + */ + protected toJSON(): JsonValue { + return this.toJson({ + emitDefaultValues: true, + }); + } + + /** + * Retrieve the MessageType of this message - a singleton that represents + * the protobuf message declaration and provides metadata for reflection- + * based operations. + */ + abstract getType(): MessageType ; + +} diff --git a/protobuf-bitfun/src/private/assert.ts b/protobuf-bitfun/src/private/assert.ts new file mode 100644 index 0000000..c83aaf1 --- /dev/null +++ b/protobuf-bitfun/src/private/assert.ts @@ -0,0 +1,58 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +/** + * Assert that condition is truthy or throw error (with message) + */ +export function assert(condition: unknown, msg?: string): asserts condition { + if (!condition) { + throw new Error(msg); + } +} + +const FLOAT32_MAX = 3.4028234663852886e38, + FLOAT32_MIN = -3.4028234663852886e38, + UINT32_MAX = 0xffffffff, + INT32_MAX = 0x7fffffff, + INT32_MIN = -0x80000000; + +/** + * Assert a valid signed protobuf 32-bit integer. + */ +export function assertInt32(arg: unknown): asserts arg is number { + if (typeof arg !== "number") throw new Error("invalid int 32: " + typeof arg); + if (!Number.isInteger(arg) || arg > INT32_MAX || arg < INT32_MIN) + throw new Error("invalid int 32: " + arg); // eslint-disable-line @typescript-eslint/restrict-plus-operands -- we want the implicit conversion to string +} + +/** + * Assert a valid unsigned protobuf 32-bit integer. + */ +export function assertUInt32(arg: unknown): asserts arg is number { + if (typeof arg !== "number") + throw new Error("invalid uint 32: " + typeof arg); + if (!Number.isInteger(arg) || arg > UINT32_MAX || arg < 0) + throw new Error("invalid uint 32: " + arg); // eslint-disable-line @typescript-eslint/restrict-plus-operands -- we want the implicit conversion to string +} + +/** + * Assert a valid protobuf float value. + */ +export function assertFloat32(arg: unknown): asserts arg is number { + if (typeof arg !== "number") + throw new Error("invalid float 32: " + typeof arg); + if (!Number.isFinite(arg)) return; + if (arg > FLOAT32_MAX || arg < FLOAT32_MIN) + throw new Error("invalid float 32: " + arg); // eslint-disable-line @typescript-eslint/restrict-plus-operands -- we want the implicit conversion to string +} diff --git a/protobuf-bitfun/src/private/binary-format-imp.ets b/protobuf-bitfun/src/private/binary-format-imp.ets new file mode 100644 index 0000000..fd80959 --- /dev/null +++ b/protobuf-bitfun/src/private/binary-format-imp.ets @@ -0,0 +1,123 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import type { IBinaryReader,IBinaryWriter } from "../binary-encoding"; +import { WireType } from "../binary-encoding"; +import { BinaryFormat } from "../binary-format"; +import type { BinaryReadOptions,BinaryWriteOptions } from "../binary-format"; +import { readField, writeField, makeReadOptions, makeWriteOptions } from "./binary-format"; +import type { FieldInfo } from "../field"; +import { Message } from "../message"; +import { isFieldSet } from "./reflect" +import { getMessageValue } from "./enum"; + +import collections from "@arkts.collections"; + +@Sendable +export class BinaryFormatImp extends BinaryFormat{ + constructor(){ + super(); + } + + readMessage>(message: T, reader: IBinaryReader, lengthOrEndTagFieldNo: number, + options: BinaryReadOptions, delimitedMessageEncoding?: boolean): void { + const type = message.getType(); + + const end = delimitedMessageEncoding + ? reader.len + : reader.pos + lengthOrEndTagFieldNo; + let fieldNo: number | undefined, wireType: WireType | undefined; + while (reader.pos < end){ + const tag = reader.tag(); + const fieldNo = tag[0]; + const wireType = tag[1]; + if ( + delimitedMessageEncoding === true && + wireType == WireType.EndGroup + ){ + break; + } + const field: FieldInfo | undefined = type.getFields().find(fieldNo); + if (!field){ + const data = reader.skip(wireType,fieldNo); + if (options.readUnknownFields){ + + } + continue; + } + readField(message, reader, field, wireType, options); + } + if ( + delimitedMessageEncoding && + (wireType != WireType.EndGroup || fieldNo !== lengthOrEndTagFieldNo) + ){ + throw new Error('invalid end group tag'); + } + } + + readField>(target: Record>, reader: IBinaryReader, field: FieldInfo, + wireType: WireType, options: BinaryReadOptions): void { + readField(target, reader, field, wireType, options); + } + + writeMessage>(message: Message, writer: IBinaryWriter, options: BinaryWriteOptions): void { + const type = message.getType(); + const list: collections.Array | undefined = type.getFields().byNumber(); + if (list === undefined){ + return; + } + for (let i = 0 ; i< list?.length; i++){ + const field = list.at(i); + if (field == undefined){ + continue; + } + if (!isFieldSet(field, message)){ + if(field.req){ + throw new Error('cannot encode field to binary: required field not set'); + } + continue; + } + let localName: string = field.oneof ? field.oneof.localName : field.localName; + const value: ESObject = + getMessageValue(message, localName, field.oneof !== null && field.oneof !== undefined); + if (value !== undefined){ + writeField(field, value, writer, options); + } + } + return; + } + + writeField, V extends Message>(message: FieldInfo, value: Message, writer: IBinaryWriter, + options: BinaryWriteOptions): void { + writeField( message, value, writer, options ) + } + + makeReadOptions( + options?: Partial + ): Readonly { + return makeReadOptions(options); + } + + makeWriteOptions( + options?: Partial + ): Readonly { + return makeWriteOptions(options); + } + +} + +export function makeBinaryFormat(): BinaryFormat{ + return new BinaryFormatImp(); +} + diff --git a/protobuf-bitfun/src/private/binary-format.ets b/protobuf-bitfun/src/private/binary-format.ets new file mode 100644 index 0000000..b79336e --- /dev/null +++ b/protobuf-bitfun/src/private/binary-format.ets @@ -0,0 +1,603 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import type { IBinaryReader, IBinaryWriter } from "../binary-encoding"; +import { BinaryReader, BinaryWriter, WireType } from "../binary-encoding"; +import type { BinaryReadOptions, BinaryWriteOptions} from "../binary-format"; +import type { Message } from "../message"; +import type { FieldInfo, fiMapInterface_1 } from "../field"; +import { scalarZeroValue } from "./scalars"; +import { assert } from "./assert"; +import type { ScalarValue } from "../scalar"; +import { LongType, ScalarType } from "../scalar"; +import { isMessage } from "../is-message"; +import { MessageType } from '../message-type'; +import { wrapField } from "./field-wrapper"; +import { EnumType } from "../enum"; +import collections from '@arkts.collections'; + +// Default options for parsing binary data. +const readDefaults: Readonly = { + readUnknownFields: true, + readerFactory: (bytes) => new BinaryReader(bytes), +}; + +// Default options for serializing binary data. +const writeDefaults: Readonly = { + writeUnknownFields: true, + writerFactory: () => new BinaryWriter(), +}; + +export function makeReadOptions( + options?: Partial): Readonly { + if (options) { + return { + readUnknownFields: options.readUnknownFields ?? readDefaults.readUnknownFields, + readerFactory: options.readerFactory ?? readDefaults.readerFactory + }; + } else { + return readDefaults; + } +} + +export function makeWriteOptions( + options?: Partial, +): Readonly { + if (options) { + return { + writeUnknownFields: options.writeUnknownFields ?? writeDefaults.writeUnknownFields, + writerFactory: options.writerFactory ?? writeDefaults.writerFactory + }; + } + return writeDefaults; +} + +export function readField>( + target: Record, // eslint-disable-line @typescript-eslint/no-explicit-any -- `any` is the best choice for dynamic access + reader: IBinaryReader, + field: FieldInfo, + wireType: WireType, + options: BinaryReadOptions, +): void { + let repeated = field.repeated; + let localName = field.localName; + if (field.oneof) { + if(field.oneof.localName === undefined){ + return; + } + target = target[field.oneof.localName ]; + if (target.case != localName) { + target.value = undefined; + } + target.case = localName; + localName = "value"; + } + switch (field.kind) { + case "scalar": + case "enum": + const scalarType = field.kind == "enum" ? ScalarType.INT32 : field.T; + let read: Function = readScalar; + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison -- acceptable since it's covered by tests + if (field.kind == "scalar" && field.L !== undefined && field.L > 0) { + read = readScalarLTString; + } + if (repeated) { + let arr = target[localName] as Object[]; // safe to assume presence of array, oneof cannot contain repeated values + const isPacked = + wireType == WireType.LengthDelimited && + scalarType != ScalarType.STRING && + scalarType != ScalarType.BYTES; + if (isPacked) { + let e = reader.uint32() + reader.pos; + while (reader.pos < e) { + arr.push(read(reader, scalarType)); + } + } else { + arr.push(read(reader, scalarType)); + } + } else { + target[localName] = scalarType !== ScalarType.BYTES ? + read(reader, scalarType) : collections.Uint8Array.from(read(reader, scalarType)); + } + break; + case "message": + const messageType = field.T as Object as MessageType; + if (repeated) { + if (messageType === undefined) { + break; + } + // safe to assume presence of array, oneof cannot contain repeated values + (target[localName] as Object[]).push( + readMessageField(reader, (messageType ).create(), options, field), + ); + } else { + if (isMessage(target["localName"] as Message)) { + readMessageField(reader, target[localName], options, field); + } else if (messageType.create() !== undefined){ + target[localName] = readMessageField( + reader, + messageType.create(), + options, + field, + ); + } + } + break; + case "map": + const mapEntry = readMapEntry(field, reader, options) as [string | number, ScalarValue | Message]; + let mapKey = mapEntry[0]; + let mapVal = mapEntry[1]; + // safe to assume presence of map object, oneof cannot contain repeated values + target[localName][mapKey] = mapVal; + break; + } +} + +// Read a message, avoiding MessageType.fromBinary() to re-use the +// BinaryReadOptions and the IBinaryReader. +function readMessageField>( + reader: IBinaryReader, + message: T, + options: BinaryReadOptions, + field: FieldInfo | undefined, +): T { + const format = message.getType().getProtoRuntime().bin; + const delimited = field?.delimited; + format.readMessage( + message, + reader, + delimited ? field.no : reader.uint32(), // eslint-disable-line @typescript-eslint/strict-boolean-expressions + options, + delimited, + ); + return message; +} + +// Read a map field, expecting key field = 1, value field = 2 +function readMapEntry>( + field: FieldInfo, + reader: IBinaryReader, + options: BinaryReadOptions, +): [string | number, ScalarValue | Message] { + const f = field; + const length = reader.uint32(), + end = reader.pos + length; + let key: ScalarValue | undefined, val: ScalarValue | Message | undefined; + while (reader.pos < end) { + const fieldNo = reader.tag()[0]; + switch (fieldNo) { + case 1: + key = readScalar(reader, f.K); + break; + case 2: + switch (f.V.kind) { + case "scalar": + if (field.V === undefined){ + break; + } + const f = field.V as Object as fiMapInterface_1; + val = readScalar(reader, f.T); + break; + case "enum": + val = reader.int32(); + break; + case "message": + let v = field.V; + if (v === undefined){ + break; + } + if (v.T !== undefined){ + val = readMessageField(reader, (v.T as Object as MessageType).create(), options, undefined); + } + break; + } + break; + } + } + + + if (key === undefined) { + key = scalarZeroValue(f.K, LongType.BIGINT); + } + if (typeof key != "string" && typeof key != "number") { + key = key.toString(); + } + if (val === undefined) { + switch (f.V.kind) { + case "scalar": + if (field === undefined || f.V.T === undefined){ + break; + } + val = scalarZeroValue(f.V.T as ScalarType, LongType.BIGINT); + break; + case "enum": + val = (field.V.T as Object as EnumType).values[0].no; + break; + case "message": + if (field === undefined || field.V === undefined || field.V.T === undefined ){ + break; + } + const fieldT = field.V.T as Object as MessageType; + val = fieldT.create(); + break; + } + } + return [key, val as ScalarValue | Message]; +} + +// Read a scalar value, but return 64 bit integral types (int64, uint64, +// sint64, fixed64, sfixed64) as string instead of bigint. +function readScalarLTString( + reader: IBinaryReader, + type: ScalarType, +): ScalarValue | undefined { + const v = readScalar(reader, type); + return typeof v == "bigint" ? v.toString() : v; +} + +// Does not use scalarTypeInfo() for better performance. +function readScalar(reader?: IBinaryReader, type?: ScalarType): ScalarValue | undefined{ + switch (type) { + case ScalarType.STRING: + return reader?.string(); + case ScalarType.BOOL: + return reader?.bool(); + case ScalarType.DOUBLE: + return reader?.double(); + case ScalarType.FLOAT: + return reader?.float(); + case ScalarType.INT32: + return reader?.int32(); + case ScalarType.INT64: + return reader?.int64(); + case ScalarType.UINT64: + return reader?.uint64(); + case ScalarType.FIXED64: + return reader?.fixed64(); + case ScalarType.BYTES: + return reader?.bytes(); + case ScalarType.FIXED32: + return reader?.fixed32(); + case ScalarType.SFIXED32: + return reader?.sfixed32(); + case ScalarType.SFIXED64: + return reader?.sfixed64(); + case ScalarType.SINT64: + return reader?.sint64(); + case ScalarType.UINT32: + return reader?.uint32(); + case ScalarType.SINT32: + return reader?.sint32(); + default: + return reader?.fixed32() + } +} + +export function writeField>( + field: FieldInfo, + value: Object, + writer: IBinaryWriter, + options: BinaryWriteOptions, +) { + assert(value !== undefined); + const repeated = field.repeated; + switch (field.kind) { + case "scalar": + case "enum": + let scalarType = field.kind == "enum" ? ScalarType.INT32 : field.T as ScalarType; + if (repeated) { + if (field.packed) { + writePacked(writer, scalarType, field.no, value as ((number | boolean | string )[] | Uint8Array)); + } else { + for (const item of value as ((number | boolean | string )[] | Uint8Array)) { + writeScalar(writer, scalarType, field.no, item); + } + } + } else { + writeScalar(writer, scalarType, field.no, value as number | boolean | string | Uint8Array); + } + break; + case "message": + if (repeated) { + for (const item of value as Object[]) { + writeMessageField(writer, options, field, item); + } + } else { + writeMessageField(writer, options, field, value); + } + break; + case "map": + assert(value != null); + for (const item of Object.entries(value) as [string, Object][]) { + writeMapEntry(writer, options, field, item[0],item[1]); + } + break; + } +} + +export function writeMapEntry>( + writer: IBinaryWriter, + options: BinaryWriteOptions, + field: FieldInfo, + key: string, + value: Object, +): void { + writer?.tag(field.no, WireType.LengthDelimited); + writer?.fork(); + + // javascript only allows number or string for object properties + // we convert from our representation to the protobuf type + let keyValue: ScalarValue = key; + // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- we deliberately handle just the special cases for map keys + switch (field.K) { + case ScalarType.INT32: + case ScalarType.FIXED32: + case ScalarType.UINT32: + case ScalarType.SFIXED32: + case ScalarType.SINT32: + keyValue = Number.parseInt(key); + break; + case ScalarType.BOOL: + assert(key == "true" || key == "false"); + keyValue = key == "true"; + break; + } + + // write key, expecting key field number = 1 + const f = field; + writeScalar(writer, f.K, 1, keyValue); + + // write value, expecting value field number = 2 + switch (field.V?.kind) { + case "scalar": + writeScalar(writer, field.V.T as ScalarType, 2, value as number | boolean | string | Uint8Array); + break; + case "enum": + writeScalar(writer, ScalarType.INT32, 2, value as number | boolean | string | Uint8Array); + break; + case "message": + assert(value !== undefined); + writer.tag(2, WireType.LengthDelimited).bytes((value as Message).toBinary(options)); + break; + } + + writer?.join(); +} + +// Value must not be undefined +function writeMessageField>( + writer: IBinaryWriter, + options: BinaryWriteOptions, + field: FieldInfo, + value: Object, +): void { + const message = wrapField(field.T as Object as MessageType, value); + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + if (field.delimited) + writer + .tag(field.no ?? 0, WireType.StartGroup) + .raw(message.toBinary(options)) + .tag(field.no ?? 0, WireType.EndGroup); + else + writer + .tag(field.no ?? 0, WireType.LengthDelimited) + .bytes(message.toBinary(options)); +} + +function writeScalar( + writer: IBinaryWriter, + type: ScalarType, + fieldNo: number, + value: number | boolean | string | Uint8Array, +): void { + assert(value !== undefined); + const scalarType = scalarTypeInfo(type); + let wireType = scalarType[0] + let method = scalarType[1]; + + switch (method){ + case "raw" : + writer?.tag(fieldNo,wireType).raw(value as Uint8Array); + break; + case "uint32": + writer?.tag(fieldNo,wireType).uint32(value as number); + break; + case "int32": + writer?.tag(fieldNo,wireType).int32(value as number); + break; + case "sint32": + writer?.tag(fieldNo,wireType).sint32(value as number); + break; + case "uint64": + writer?.tag(fieldNo,wireType).uint64(value as number); + break; + case "int64": + writer?.tag(fieldNo,wireType).int64(value as number); + break; + case "sint64": + writer?.tag(fieldNo,wireType).sint64(value as number); + break; + case "fixed64": + writer?.tag(fieldNo,wireType).fixed64(value as number); + break; + case "sfixed64": + writer?.tag(fieldNo,wireType).sfixed64(value as number); + break; + case "bool": + writer?.tag(fieldNo,wireType).bool(value as boolean); + break; + case "fixed32": + writer?.tag(fieldNo,wireType).fixed32(value as number); + break; + case "sfixed32": + writer?.tag(fieldNo,wireType).sfixed32(value as number); + break; + case "float": + writer?.tag(fieldNo,wireType).float(value as number); + break; + case "double": + writer?.tag(fieldNo,wireType).double(value as number); + break; + case "bytes": + writer?.tag(fieldNo,wireType).bytes(value as Uint8Array); + break; + case "string": + writer?.tag(fieldNo,wireType).string(value as string); + break; + } +} + +function writePacked( + writer: IBinaryWriter | undefined, + type: ScalarType, + fieldNo: number, + value: (number | boolean | string)[] | Uint8Array, +): void { + if (!value.length) { + return; + } + writer?.tag(fieldNo, WireType.LengthDelimited).fork(); + let method = scalarTypeInfo(type)[1]; + for (let i = 0; i < value.length; i++) { + switch (method) { + case "uint32" : + writer?.uint32(value[i] as number); + break; + case "int32" : + writer?.int32(value[i] as number); + break; + case "sint32" : + writer?.sint32(value[i] as number); + break; + case "uint64" : + writer?.uint64(value[i] as number); + break; + case "int64" : + writer?.int64(value[i] as number); + break; + case "sint64" : + writer?.sint64(value[i] as number); + break; + case "fixed64" : + writer?.fixed64(value[i] as number); + break; + case "sfixed64" : + writer?.sfixed64(value[i] as number); + break; + case "bool" : + writer?.bool(value[i] as boolean); + break; + case "fixed32" : + writer?.fixed32(value[i] as number); + break; + case "sfixed32" : + writer?.sfixed32(value[i] as number); + break; + case "float": + writer?.float(value[i] as number); + break; + case "double": + writer?.double(value[i] as number); + break; + case "bytes": + writer?.bytes(value[i] as Object as Uint8Array); + break; + case "string": + writer?.string(value[i] as string); + break; + } + } + writer?.join(); +} + +/** + * Get information for writing a scalar value. + * + * Returns tuple: + * [0]: appropriate WireType + * [1]: name of the appropriate method of IBinaryWriter + * [2]: whether the given value is a default value for proto3 semantics + * + * If argument `value` is omitted, [2] is always false. + */ +// TODO replace call-sites writeScalar() and writePacked(), then remove +function scalarTypeInfo( + type: ScalarType, +): [ + WireType, + string +] { + let wireType = WireType.Varint; + let method = ''; + // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- INT32, UINT32, SINT32 are covered by the defaults + switch (type) { + case ScalarType.BYTES: + wireType = WireType.LengthDelimited; + method = 'BYTES'.toLowerCase(); + break; + case ScalarType.STRING: + wireType = WireType.LengthDelimited; + method = 'STRING'.toLowerCase(); + break; + + case ScalarType.DOUBLE: + method = 'DOUBLE'.toLowerCase(); + wireType = WireType.Bit64; + break; + case ScalarType.FIXED64: + method = 'FIXED64'.toLowerCase(); + wireType = WireType.Bit64; + break; + case ScalarType.SFIXED64: + method = 'SFIXED64'.toLowerCase(); + wireType = WireType.Bit64; + break; + + case ScalarType.FIXED32: + method = 'FIXED32'.toLowerCase(); + wireType = WireType.Bit32; + break; + case ScalarType.SFIXED32: + method = 'SFIXED32'.toLowerCase(); + wireType = WireType.Bit32; + break; + case ScalarType.FLOAT: + method = 'FLOAT'.toLowerCase(); + wireType = WireType.Bit32; + break; + + case ScalarType.INT64: + method = 'INT64'.toLowerCase(); + break; + case ScalarType.UINT64: + method = 'UINT64'.toLowerCase(); + break; + case ScalarType.INT32: + method = 'INT32'.toLowerCase(); + break; + case ScalarType.BOOL: + method = 'BOOL'.toLowerCase(); + break; + case ScalarType.UINT32: + method = 'UINT32'.toLowerCase(); + break; + case ScalarType.SINT32: + method = 'SINT32'.toLowerCase(); + break; + case ScalarType.SINT64: + method = 'SINT64'.toLowerCase(); + break; + } + return [wireType, method]; +} diff --git a/protobuf-bitfun/src/private/common.ets b/protobuf-bitfun/src/private/common.ets new file mode 100644 index 0000000..1b3362e --- /dev/null +++ b/protobuf-bitfun/src/private/common.ets @@ -0,0 +1,64 @@ +import { EnumValueInfo} from "../enum"; + +import collections from "@arkts.collections"; +import { lang } from '@kit.ArkTS'; + +type ISendable = lang.ISendable; + +export interface EnumType extends ISendable{ + readonly typeName: string; + + values: collections.Array; + + names: collections.Map; + + numbers: collections.Map; + + findName(name: string): EnumValueInfo | undefined; + + findNumber(no: number): EnumValueInfo | undefined; +} + +@Sendable +export class EnumTypeInstance implements EnumType { + typeName: string = ""; + + values: collections.Array = new collections.Array(); + + names: collections.Map = new collections.Map(); + + numbers: collections.Map = new collections.Map(); + + findName(name: string): EnumValueInfo | undefined { + return this.names.get(name); + } + + findNumber(no: number): EnumValueInfo | undefined { + return this.numbers.get(no); + } + + constructor(typeName: string, values: collections.Array){ + this.typeName = typeName; + this.values = values; + values.forEach((value) => { + this.names.set(value.name, value); + this.numbers.set(value.no, value); + }) + } +} + +@Sendable +export class EnumValueInfoInstance implements EnumValueInfo{ + no: number = 0; + localName: string = ""; + name: string = ""; + constructor(no: number, localName: string, name: string){ + this.no = no; + this.localName = localName; + this.name = name; + } +} + +export function makeEnumTypeInstance(typeName: string, values: collections.Array): EnumType{ + return new EnumTypeInstance(typeName,values); +} \ No newline at end of file diff --git a/protobuf-bitfun/src/private/enum.ts b/protobuf-bitfun/src/private/enum.ts new file mode 100644 index 0000000..7795ce5 --- /dev/null +++ b/protobuf-bitfun/src/private/enum.ts @@ -0,0 +1,135 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import type { EnumType, EnumValueInfo } from "../enum"; +import { assert } from "./assert"; + +interface EnumValueInfoOmitLocalName { + readonly no: number; + + readonly name: string; +} +/** + * Represents a generated enum. + */ +export interface EnumObject { + [key: number]: string; + + [k: string]: number | string; +} + +const enumTypeSymbol = Symbol("@bufbuild/protobuf/enum-type"); + +/** + * Get reflection information from a generated enum. + * If this function is called on something other than a generated + * enum, it raises an error. + */ +export function getEnumType(enumObject: EnumObject): EnumType { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-explicit-any + const t = (enumObject as any)[enumTypeSymbol]; + assert(t, "missing enum type on enum object"); + return t; // eslint-disable-line @typescript-eslint/no-unsafe-return +} + +export function setEnumType( + enumObject: EnumObject, + typeName: string, + values: EnumValueInfoOmitLocalName[], + opt?: {}, +): void { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any + (enumObject as any)[enumTypeSymbol] = makeEnumType( + typeName, + values.map((v) => ({ + no: v.no, + name: v.name, + localName: enumObject[v.no], + })), + opt, + ); +} + +/** + * Create a new EnumType with the given values. + */ +export function makeEnumType( + typeName: string, + values: (EnumValueInfo | Omit)[], + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _opt?: { + // We do not surface options at this time + // options?: { readonly [extensionName: string]: JsonValue }; + }, +): EnumType { + const names = Object.create(null) as Record; + const numbers = Object.create(null) as Record; + const normalValues: EnumValueInfo[] = []; + for (const value of values) { + // We do not surface options at this time + // const value: EnumValueInfo = {...v, options: v.options ?? emptyReadonlyObject}; + const n = normalizeEnumValue(value); + normalValues.push(n); + names[value.name] = n; + numbers[value.no] = n; + } + return { + typeName, + values: normalValues, + // We do not surface options at this time + // options: opt?.options ?? Object.create(null), + findName(name: string): EnumValueInfo | undefined { + return names[name]; + }, + findNumber(no: number): EnumValueInfo | undefined { + return numbers[no]; + }, + }; +} + +/** + * Create a new enum object with the given values. + * Sets reflection information. + */ +export function makeEnum( + typeName: string, + values: (EnumValueInfo | Omit)[], + opt?: { + // We do not surface options at this time + // options?: { readonly [extensionName: string]: JsonValue }; + }, +): EnumObject { + const enumObject: EnumObject = {}; + for (const value of values) { + const n = normalizeEnumValue(value); + enumObject[n.localName] = n.no; + enumObject[n.no] = n.localName; + } + setEnumType(enumObject, typeName, values, opt); + return enumObject; +} + +function normalizeEnumValue( + value: EnumValueInfo | Omit, +): EnumValueInfo { + if ("localName" in value) { + return value; + } + return { ...value, localName: value.name }; +} + +export function getMessageValue(any: ESObject, localName: string, isOneOf: boolean) { + const message = any as [string, Object]; + return isOneOf ? message[localName].value : message[localName]; +} \ No newline at end of file diff --git a/protobuf-bitfun/src/private/field-list.ets b/protobuf-bitfun/src/private/field-list.ets new file mode 100644 index 0000000..857dad0 --- /dev/null +++ b/protobuf-bitfun/src/private/field-list.ets @@ -0,0 +1,103 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { FieldInfo, OneofInfo } from "../field"; +import { FieldList } from "../field-list"; +import { normalizeFieldInfos } from "./field-normalize"; + +import collections from "@arkts.collections"; + +type FieldListSource = collections.Array; +export type FieldListSourceFun = (p: FieldListSource) => collections.Array; + +@Sendable +export class InternalFieldList extends FieldList { + private _fields: FieldListSource; + private all?: collections.Array; + private numbersAsc?: collections.Array; + private jsonNames?: collections.Map; + private numbers?: collections.Map; + private members?: collections.Array; + + constructor( + fields: FieldListSource, + ) { + super(); + this._fields = fields; + } + + findJsonName(jsonName: string): FieldInfo | undefined { + if (!this.jsonNames) { + this.jsonNames = new collections.Map(); + for (let i =0; i < this.list().length; i++) { + const f = this.list().at(i); + if (f === undefined || f.jsonName === undefined){ + continue; + } + this.jsonNames.set(f.jsonName, f); + this.jsonNames.set(f.name, f); + } + } + return this.jsonNames.get(jsonName); + } + + find(fieldNo: number): FieldInfo | undefined { + if (this.numbers === undefined) { + this.numbers = new collections.Map(); + for (let i =0; i < this.list().length; i++) { + const f = this.list().at(i); + if (f === undefined){ + continue; + } + this.numbers.set(f.no, f); + } + } + return this.numbers.get(fieldNo); + } + + list(): collections.Array { + if (!this.all) { + this.all = normalizeFieldInfos(this._fields, true); + } + return this.all; + } + + byNumber(): collections.Array | undefined{ + if (!this.numbersAsc) { + this.numbersAsc = this.list().concat().sort( + (a, b) => (a.no - b.no)); + } + return this.numbersAsc; + } + + byMember(): collections.Array{ + if (!this.members) { + this.members = new collections.Array(); + const a = this.members; + let o: OneofInfo | undefined; + for (let i =0; i < this.list().length; i++) { + const f = this.list().at(i) as FieldInfo; + if (f.oneof){ + if (f.oneof !== o){ + o = f.oneof; + a.push(o); + } + } else { + a.push(f); + } + } + } + return this.members; + } +} diff --git a/protobuf-bitfun/src/private/field-normalize.ets b/protobuf-bitfun/src/private/field-normalize.ets new file mode 100644 index 0000000..82da679 --- /dev/null +++ b/protobuf-bitfun/src/private/field-normalize.ets @@ -0,0 +1,75 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import type { FieldInfo } from "../field"; +import { InternalOneofInfo } from "./field"; +import { fieldJsonName, localFieldName } from "./names"; +import { LongType, ScalarType } from "../scalar"; +import type { FieldListSource } from "./type"; + +import collections from "@arkts.collections"; + +/** + * Convert a collection of field info to an array of normalized FieldInfo. + * + * The argument `packedByDefault` specifies whether fields that do not specify + * `packed` should be packed (proto3) or unpacked (proto2). + */ +export function normalizeFieldInfos( + fieldInfos: FieldListSource, + packedByDefault: boolean, +): collections.Array { + const r: collections.Array = new collections.Array(); + let o: InternalOneofInfo | undefined; + for (let i =0; i < fieldInfos.length; i++) { + const item = fieldInfos.at(i); + if (item === undefined){ + continue; + } + const field = item as Record; + const f = item; + f.localName = localFieldName(field.name, field.oneof !== undefined); + f.jsonName = field.jsonName ?? fieldJsonName(field.name); + f.repeated = field.repeated ?? false; + if (field.kind == "scalar") { + f.L = field.L ?? LongType.BIGINT; + } + f.delimited = field.delimited ?? false; + f.req = field.req ?? false; + f.opt = field.opt ?? false; + if (field.packed === undefined) { + if (packedByDefault) { + f.packed = + field.kind == "enum" || + (field.kind == "scalar" && + field.T != ScalarType.BYTES && + field.T != ScalarType.STRING); + } else { + f.packed = false; + } + } + // We do not surface options at this time + // f.options = field.options ?? emptyReadonlyObject; + if (field.oneof !== undefined) { + const ooname: string = field.oneof.name; + if (!o || o.name != ooname) { + o = new InternalOneofInfo(ooname); + } + f.oneof = o; + o.addField(item); + } + r.push(item); + } + return r; +} diff --git a/protobuf-bitfun/src/private/field-wrapper.ets b/protobuf-bitfun/src/private/field-wrapper.ets new file mode 100644 index 0000000..ce389fd --- /dev/null +++ b/protobuf-bitfun/src/private/field-wrapper.ets @@ -0,0 +1,83 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { Message } from "../message"; +import type { EmptyMessage, MessageType } from "../message-type"; +import type { DescExtension, DescField } from "../descriptor-set"; +import { ScalarType } from "../scalar"; +import { isMessage } from "../is-message"; + +/** + * A field wrapper unwraps a message to a primitive value that is more + * ergonomic for use as a message field. + * + * Note that this feature exists for google/protobuf/wrappers.proto + * and cannot be used to arbitrarily modify types in generated code. + */ +@Sendable +export class FieldWrapper = EmptyMessage, U = string> { + constructor(){ + } + wrapField(value: U): T{ + throw Error(); + }; + + unwrapField(value: T): U{ + throw Error(); + }; +} + +export function wrapField>( + type : MessageType, + value: ESObject, +): T { + if (isMessage(value) || !type.getFieldWrapper()){ + return value as T; + } + return new Object() as T; +} + +/** + * If the given field uses one of the well-known wrapper types, return + * the primitive type it wraps. + */ +export function getUnwrappedFieldType( + field: DescField | DescExtension, +): ScalarType | undefined { + if (field.fieldKind !== "message") { + return undefined; + } + if (field.repeated) { + return undefined; + } + if (field.oneof !== undefined) { + return undefined; + } + if (field.message === undefined) { + return undefined; + } + return wktWrapperToScalarType[field.message.typeName]; +} + +const wktWrapperToScalarType: Record = { + "google.protobuf.DoubleValue": ScalarType.DOUBLE, + "google.protobuf.FloatValue": ScalarType.FLOAT, + "google.protobuf.Int64Value": ScalarType.INT64, + "google.protobuf.UInt64Value": ScalarType.UINT64, + "google.protobuf.Int32Value": ScalarType.INT32, + "google.protobuf.UInt32Value": ScalarType.UINT32, + "google.protobuf.BoolValue": ScalarType.BOOL, + "google.protobuf.StringValue": ScalarType.STRING, + "google.protobuf.BytesValue": ScalarType.BYTES, +}; diff --git a/protobuf-bitfun/src/private/field.ets b/protobuf-bitfun/src/private/field.ets new file mode 100644 index 0000000..140780a --- /dev/null +++ b/protobuf-bitfun/src/private/field.ets @@ -0,0 +1,67 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { Message } from "../message"; +import { FieldInfo, OneofInfo } from "../field"; +import { EmptyMessage } from "../message-type"; +import { localOneofName } from "./names"; +import { FieldListSource } from "./type"; + +import collections from "@arkts.collections"; + +@Sendable +export class InternalOneofInfo = EmptyMessage> implements OneofInfo { + kind: string = "oneof"; + + name: string; + + localName: string; + + repeated: boolean = false; + + packed: boolean = false; + + opt: boolean = false; + + req: boolean = false; + + default: undefined = undefined; + + fields: collections.Array> = new collections.Array>(); + + private _lookup?: collections.Map | undefined>; + + constructor(name: string) { + this.name = name; + this.localName = localOneofName(name); + } + + addField(field: FieldInfo) { + this.fields.push(field); + } + + findField(localName: string): FieldInfo | undefined { + if (!this._lookup) { + this._lookup = new collections.Map(); + for (let i = 0; i < this.fields.length; i++) { + this._lookup.set(this.fields.at(i)?.localName ?? "", this.fields.at(i)); + } + } + return this._lookup.get(localName); + } +} + +export function getFieldListsFromArray(array: () => collections.Array): FieldListSource{ + return array(); +} \ No newline at end of file diff --git a/protobuf-bitfun/src/private/json-format-imp.ets b/protobuf-bitfun/src/private/json-format-imp.ets new file mode 100644 index 0000000..c8a1bfc --- /dev/null +++ b/protobuf-bitfun/src/private/json-format-imp.ets @@ -0,0 +1,146 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { JsonFormat, JsonObject, JsonReadOptions, JsonValue, JsonWriteOptions, JsonWriteStringOptions } from "../json-format"; +import { Message } from "../message"; +import type { MessageType } from "../message-type"; +import type { FieldInfo,OneofInfo } from "../field"; +import { isScalarZeroValue } from "./scalars"; +import type { ScalarValue } from "../scalar"; +import { LongType,ScalarType } from "../scalar"; +import { canEmitFieldDefaultValue, makeReadOptions, makeWriteOptions, readField, readScalar, writeField, writeScalar } from './json-format'; +import { isFieldSet } from "./reflect"; +import { getMessageValue } from "./enum"; + +import collections from "@arkts.collections"; + +@Sendable +export class JsonFormatImp extends JsonFormat{ + constructor(){ + super(); + } + + makeReadOptions( + options?: Partial, + ): Readonly { + return makeReadOptions(options); + } + + makeWriteOptions( + options?: Partial + ): Readonly { + return makeWriteOptions(options); + } + + readMessage>(type: MessageType, json: JsonValue, options: JsonReadOptions, + message?: T | undefined): T { + if(json == null || Array.isArray(json)){ + throw new Error( + `cannot decode message ${type.getTypeName()} from JSON` + ); + } + message = message ?? type.create(); + const oneofSeen = new Map(); + const entries: [string, Object][] = Object.entries(json); + for (const item of entries){ + const jsonKey = item[0]; + const jsonValue = item[1]; + const field: FieldInfo | undefined = type.getFields().findJsonName(jsonKey); + if (field){ + if(field.oneof){ + if (jsonValue === null && field.kind == "scalar"){ + continue; + } + const seen = oneofSeen.get(field.oneof); + if (seen !== undefined){ + throw new Error( + `cannot decode message ${type.getTypeName()} from JSON` + ); + } + oneofSeen.set(field.oneof, item[0]); + } + readField( + message as Record, + jsonValue, + field, + options, + type, + ); + } else { + // Extension + } + } + return message; + } + + writeMessage>(message: Message, options: JsonWriteOptions): JsonValue { + const type = message.getType(); + const json: JsonObject = {}; + + try { + const list: collections.Array | undefined = type.getFields().byNumber(); + if (list === undefined){ + return new Object(); + } + for (let i = 0 ; i< list?.length; i++){ + const field = list.at(i); + if (field == undefined){ + continue; + } + if (!isFieldSet(field, message)){ + if(field.req){ + throw new Error('required field not set'); + } + if (!options.emitDefaultValues){ + continue; + } + if (!canEmitFieldDefaultValue(field)){ + continue; + } + } + let localName: string = field.oneof ? field.oneof.localName : field.localName; + const res: ESObject = + getMessageValue(message, localName, field.oneof !== null && field.oneof !== undefined); + const jsonValue: JsonValue | undefined = writeField(field, res, options); + if( jsonValue !== undefined){ + json[options.useProtoFieldName === undefined ? field.name : field.jsonName] = + jsonValue; + } + } + // Extension + } catch(e){ + throw new Error(); + } + return json; + } + + readScalar(type: ScalarType, json: Object, longType?: LongType | undefined): ScalarValue | Symbol { + return readScalar(type, json, longType ?? LongType.BIGINT, true); + } + + writeScalar(type: ScalarType, value: string | number| boolean | Uint8Array , + emitDefaultValues: boolean): Object | undefined { + if (value === undefined){ + return undefined; + } + if (emitDefaultValues || isScalarZeroValue(type, value)){ + return writeScalar(type, value); + } + return undefined; + } +} + +export function makeJsonFormat > () : JsonFormat{ + return new JsonFormatImp(); +} diff --git a/protobuf-bitfun/src/private/json-format.ets b/protobuf-bitfun/src/private/json-format.ets new file mode 100644 index 0000000..9c055a6 --- /dev/null +++ b/protobuf-bitfun/src/private/json-format.ets @@ -0,0 +1,650 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { JsonObject,JsonReadOptions,JsonValue, JsonWriteOptions,JsonWriteStringOptions } from "../json-format"; +import { Message } from "../message"; +import type { FieldInfo } from "../field"; +import { assert, assertFloat32, assertInt32, assertUInt32 } from "./assert"; +import { protoInt64 } from "../proto-int64"; +import { protoBase64 } from "../proto-base64"; +import type { EnumType } from "../enum"; +import { scalarZeroValue } from "./scalars"; +import type { ScalarValue } from "../scalar"; +import { LongType, ScalarType } from "../scalar"; +import { MessageType } from "../message-type"; +import { isMessage } from "../is-message"; + +import collections from "@arkts.collections"; +import { wrapField } from './field-wrapper'; + +// Default options for parsing JSON. +const jsonReadDefaults: Readonly = { + ignoreUnknownFields: false, +}; + +// Default options for serializing to JSON. +const jsonWriteDefaults: Readonly = { + emitDefaultValues: false, + enumAsInteger: false, + useProtoFieldName: false, + prettySpaces: 0, +}; + +export function makeReadOptions( + options?: Partial, +): Readonly { + if (options){ + return { + ignoreUnknownFields: options.ignoreUnknownFields ?? jsonReadDefaults.ignoreUnknownFields + }; + }else { + return jsonReadDefaults; + } +} + +export function makeWriteOptions( + options?: Partial, +): Readonly { + if (options){ + return { + emitDefaultValues: options.emitDefaultValues ?? jsonWriteDefaults.emitDefaultValues, + prettySpaces: options.prettySpaces ?? jsonWriteDefaults.prettySpaces, + enumAsInteger: options.enumAsInteger ?? jsonWriteDefaults.enumAsInteger, + useProtoFieldName: options.useProtoFieldName ?? jsonWriteDefaults.useProtoFieldName, + }; + }else { + return jsonWriteDefaults; + } +} + +function debugJsonValue(json: Object): string { + if (json === null){ + return "null"; + } + switch (typeof json){ + case "object": + return Array.isArray(json) ? "array" : "object"; + case "string": + return json.length > 100 ? "string" : `"${json.split('"').join('\\"')}"`; + default: + return String(json); + } +} + +export function readField( + target: Record, + jsonValue: JsonValue, + field: FieldInfo, + options: JsonReadOptions, + parentType: MessageType, +) { + let localName = field.localName; + if (field.repeated) { + if (field.kind === "map") { + return; + } + if (jsonValue === null) { + return; + } + // if (!Array.isArray(jsonValue)) { + // throw new Error( + // `cannot decode field ${parentType.typeName}.${ + // field.name + // } from JSON: ${debugJsonValue(jsonValue)}`, + // ); + // } + const targetArray = target[localName] as Object[]; + for (const jsonItem of jsonValue as Object[]) { + if (jsonItem === null) { + throw new Error( + `cannot decode field ${parentType.getTypeName()}.${ + field.name + } from JSON: ${debugJsonValue(jsonItem)}`, + ); + } + switch (field.kind) { + case "message": + const f = field.T as Object as Message; + targetArray.push(f.fromJson(jsonItem, options)); + break; + case "enum": + const enumValue = readEnum( + field.T as Object as EnumType, + jsonItem, + options.ignoreUnknownFields, + true, + ); + targetArray.push(enumValue); + break; + case "scalar": + try { + targetArray.push(readScalar(field.T as Object as ScalarType, jsonItem, field.L as LongType, true)); + } catch (e) { + let m = `cannot decode field ${parentType.getTypeName()}.${ + field.name + } from JSON: ${debugJsonValue(jsonItem)}`; + if (e instanceof Error && e.message.length > 0) { + m += `: ${e.message}`; + } + throw new Error(m); + } + break; + } + } + } else if (field.kind == "map") { + if (jsonValue === null) { + return; + } + if (Array.isArray(jsonValue)) { + throw new Error( + `cannot decode field ${parentType.getTypeName()}.${ + field.name + } from JSON: ${debugJsonValue(jsonValue)}`, + ); + } + const targetMap = target[localName] as Record; + const entries: [string, Object][] = Object.entries(jsonValue); + for (const item of entries) { + const jsonMapKey = item[0]; + const jsonMapValue = item[1]; + if (jsonMapValue === null) { + throw new Error( + `cannot decode field ${parentType.getTypeName()}.${field.name} from JSON: map value null`, + ); + } + let key: string; + try { + key = readMapKey(field.K, jsonMapKey); + } catch (e) { + let m = `cannot decode map key for field ${parentType.getTypeName()}.${ + field.name + } from JSON: ${debugJsonValue(jsonValue)}`; + if (e instanceof Error && e.message.length > 0) { + m += `: ${e.message}`; + } + throw new Error(m); + } + switch (field.V.kind) { + case "message": + targetMap[key] = (field.V.T as Object as Message).fromJson(jsonMapValue, options); + break; + case "enum": + const enumValue = readEnum( + field.V.T as Object as EnumType, + jsonMapValue, + options.ignoreUnknownFields, + true, + ); + targetMap[key] = enumValue; + break; + case "scalar": + try { + targetMap[key] = readScalar( + field.V.T as ScalarType, + jsonMapValue, + LongType.BIGINT, + true, + ); + } catch (e) { + let m = `cannot decode map value for field ${parentType.getTypeName()}.${ + field.name + } from JSON: ${debugJsonValue(jsonValue)}`; + if (e instanceof Error && e.message.length > 0) { + m += `: ${e.message}`; + } + throw new Error(m); + } + break; + } + } + } else { + if (field.oneof) { + if (field.oneof.localName !== undefined) { + target = target[field.oneof.localName] = { case: localName }; + localName = "value"; + } + } + switch (field.kind) { + case "message": + const messageType = field.T; + if (jsonValue === null && (messageType as Object as MessageType).getTypeName() != "google.protobuf.Value" + ) { + return; + } + let currentValue = target[localName] as Message; + if (isMessage(currentValue)) { + currentValue.fromJson(jsonValue, options); + } else { + if (messageType !== undefined) { + target[localName] = currentValue = (messageType as Object as Message).fromJson( + jsonValue, + options, + ); + } + //if (messageType.fieldWrapper && !field.oneof) { + // target[localName] = + // messageType.fieldWrapper.unwrapField(currentValue); + //} + } + break; + case "enum": + const enumValue = readEnum( + field.T as Object as EnumType, + jsonValue, + options.ignoreUnknownFields, + false, + ); + switch (enumValue) { + //case tokenNull: + // clearField(field, target); + // break; + //case tokenIgnoredUnknownEnum: + // break; + default: + target[localName] = enumValue; + break; + } + break; + case "scalar": + try { + const scalarValue = readScalar(field.T as ScalarType, jsonValue, field.L as LongType, false); + switch (scalarValue) { + // case tokenNull: + // clearField(field, target); + // break; + default: + target[localName] = field.T as ScalarType !== ScalarType.BYTES ? + scalarValue : collections.Uint8Array.from(scalarValue as Object as collections.Uint8Array); + break; + } + } catch (e) { + let m = `cannot decode field ${parentType.getTypeName()}.${ + field.name + } from JSON: ${debugJsonValue(jsonValue)}`; + if (e instanceof Error && e.message.length > 0) { + m += `: ${e.message}`; + } + throw new Error(m); + } + break; + } + } +} + +export function readMapKey(type: ScalarType, json: JsonValue) { + if (type === ScalarType.BOOL) { + // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check + switch (json) { + case "true": + json = true; + break; + case "false": + json = false; + break; + } + } + return readScalar(type, json, LongType.BIGINT, true).toString(); +} + +export function readScalar( + type: ScalarType, + json: JsonValue, + longType: LongType, + nullAsZeroValue: true, +): ScalarValue; +export function readScalar( + type: ScalarType, + json: JsonValue, + longType: LongType, + nullAsZeroValue: false, +): ScalarValue ; +export function readScalar( + type: ScalarType, + json: JsonValue, + longType: LongType, + nullAsZeroValue: boolean, +): ScalarValue { + if (json === null) { + if (nullAsZeroValue) { + return scalarZeroValue(type, longType); + } + return 0; + } + // every valid case in the switch below returns, and every fall + // through is regarded as a failure. + switch (type) { + // float, double: JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity". + // Either numbers or strings are accepted. Exponent notation is also accepted. + case ScalarType.DOUBLE: + case ScalarType.FLOAT: + if (json === "NaN") return Number.NaN; + if (json === "Infinity") return Number.POSITIVE_INFINITY; + if (json === "-Infinity") return Number.NEGATIVE_INFINITY; + if (json === "") { + // empty string is not a number + break; + } + if (typeof json == "string" && json.trim().length !== json.length) { + // extra whitespace + break; + } + if (typeof json != "string" && typeof json != "number") { + break; + } + const float = Number(json); + if (Number.isNaN(float)) { + // not a number + break; + } + if (!Number.isFinite(float)) { + // infinity and -infinity are handled by string representation above, so this is an error + break; + } + if (type == ScalarType.FLOAT) { + assertFloat32(float); + } + return float; + + // int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted. + case ScalarType.INT32: + case ScalarType.FIXED32: + case ScalarType.SFIXED32: + case ScalarType.SINT32: + case ScalarType.UINT32: + let int32: number | undefined; + if (typeof json == "number") { + int32 = json; + } else if (typeof json == "string" && json.length > 0) { + if (json.trim().length === json.length) { + int32 = Number(json); + } + } + if (int32 === undefined) { + break; + } + if (type == ScalarType.UINT32 || type == ScalarType.FIXED32) { + assertUInt32(int32); + } else { + assertInt32(int32); + } + return int32; + + // int64, fixed64, uint64: JSON value will be a decimal string. Either numbers or strings are accepted. + case ScalarType.INT64: + case ScalarType.SFIXED64: + case ScalarType.SINT64: + if (typeof json != "number" && typeof json != "string") break; + const long = protoInt64.parse(json); + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + return longType ? long.toString() : long; + case ScalarType.FIXED64: + case ScalarType.UINT64: + if (typeof json != "number" && typeof json != "string") break; + const uLong = protoInt64.uParse(json); + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + return longType ? uLong.toString() : uLong; + + // bool: + case ScalarType.BOOL: + if (typeof json !== "boolean") break; + return json; + + // string: + case ScalarType.STRING: + if (typeof json !== "string") { + break; + } + // A string must always contain UTF-8 encoded or 7-bit ASCII. + // We validate with encodeURIComponent, which appears to be the fastest widely available option. + try { + encodeURIComponent(json); + } catch (e) { + throw new Error("invalid UTF8"); + } + return json; + + // bytes: JSON value will be the data encoded as a string using standard base64 encoding with paddings. + // Either standard or URL-safe base64 encoding with/without paddings are accepted. + case ScalarType.BYTES: + if (json === "") return new Uint8Array(0); + if (typeof json !== "string") break; + return protoBase64.dec(json); + } + throw new Error(); +} + +export function readEnum( + type: EnumType, + json: JsonValue, + ignoreUnknownFields: boolean, + nullAsZeroValue: false, +): number; +export function readEnum( + type: EnumType, + json: JsonValue, + ignoreUnknownFields: boolean, + nullAsZeroValue: true, +): number ; +export function readEnum( + type: EnumType, + json: JsonValue, + ignoreUnknownFields: boolean, + nullAsZeroValue: boolean, +): number { + if (json === null) { + if (type.typeName == "google.protobuf.NullValue") { + return 0; // google.protobuf.NullValue.NULL_VALUE = 0 + } + return nullAsZeroValue ? type.values[0].no : 0; + } + // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check + switch (typeof json) { + case "number": + if (Number.isInteger(json)) { + return json; + } + break; + case "string": + const value = type.findName(json); + if (value !== undefined) { + return value.no; + } + if (ignoreUnknownFields) { + return 0; + } + break; + } + throw new Error( + `cannot decode enum ${type.typeName} from JSON: ${debugJsonValue(json)}`, + ); +} + +// Decide whether an unset field should be emitted with JSON write option `emitDefaultValues` +export function canEmitFieldDefaultValue>(fieldInfo: FieldInfo) { + const field = fieldInfo; + if (field.repeated || field.kind == "map") { + // maps are {}, repeated fields are [] + return true; + } + if (field.oneof) { + // oneof fields are never emitted + return false; + } + if (field.kind == "message") { + // singular message field are allowed to emit JSON null, but we do not + return false; + } + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + if (field.opt || field.req) { + // the field uses explicit presence, so we cannot emit a zero value + return false; + } + return true; +} + +export function writeField>( + fieldInfo: FieldInfo, + valueObj: Object, + options: JsonWriteOptions, +): JsonValue | undefined { + const field = fieldInfo; + if (field.kind == "map") { + const jsonObj: JsonObject = {}; + const entries: [string, Object][] = Object.entries(valueObj); + if (field.V === undefined) { + return undefined; + } + switch (field.V.kind) { + case "scalar": + for (const item of entries) { + const entryKey = item[0]; + const entryValue = item[1]; + if (field.V === undefined || field.V.T === undefined) { + continue; + } + jsonObj[entryKey.toString()] = writeScalar(field.V.T as ScalarType, + entryValue); // JSON standard allows only (double quoted) string as property key + } + + break; + case "message": + for (const item of entries) { + const entryKey = item[0]; + const entryValue = item[1]; + jsonObj[entryKey.toString()] = (entryValue as Message).toJson( + options, + ); + } + break; + case "enum": + const enumType = field.V.T; + for (const item of entries) { + // JSON standard allows only (double quoted) string as property key + const entryKey = item[0]; + const entryValue = item[1]; + jsonObj[entryKey.toString()] = writeEnum( + enumType as Object as EnumType, + entryValue, + options.enumAsInteger, + ) as Object; + } + break; + } + return options.emitDefaultValues || entries.length > 0 + ? jsonObj + : undefined; + } + if (field.repeated) { + const jsonArr: JsonValue[] = []; + const value = valueObj as Object as []; + switch (field.kind) { + case "scalar": + for (let i = 0; i < value.length; i++) { + if (field.T === undefined) { + continue; + } + jsonArr.push(writeScalar(field.T as ScalarType, value[i]) as JsonValue); + } + break; + case "enum": + for (let i = 0; i < value.length; i++) { + if (field.T === undefined) { + continue; + } + jsonArr.push( + writeEnum(field.T as Object as EnumType, value[i], options.enumAsInteger) as JsonValue, + ); + } + break; + case "message": + for (let i = 0; i < value.length; i++) { + jsonArr.push((value[i] as Object as Message).toJson(options)); + } + break; + } + return options.emitDefaultValues || jsonArr.length > 0 + ? jsonArr + : undefined; + } + switch (field.kind) { + case "scalar": + return writeScalar(field.T as Object as ScalarType, valueObj); + case "enum": + return writeEnum(field.T as Object as EnumType, valueObj, options.enumAsInteger) as Object; + case "message": + return wrapField(field.T as Object as MessageType, valueObj).toJson(options) as Object; + } + return undefined; +} + +export function writeEnum( + type: EnumType, + value: Object, + enumAsInteger: boolean, +): string | number | null { + assert(typeof value == "number"); + if (type.typeName == "google.protobuf.NullValue") { + return null; + } + if (enumAsInteger) { + return value; + } + const val = type.findNumber(value); + return val?.name ?? value; // if we don't know the enum value, just return the number +} + +export function writeScalar( + type: ScalarType, + value: Object, +): string | number | boolean { + switch (type) { + // int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted. + case ScalarType.INT32: + case ScalarType.SFIXED32: + case ScalarType.SINT32: + case ScalarType.FIXED32: + case ScalarType.UINT32: + return value as number; + + // float, double: JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity". + // Either numbers or strings are accepted. Exponent notation is also accepted. + case ScalarType.FLOAT: + case ScalarType.DOUBLE: // eslint-disable-line no-fallthrough + if (Number.isNaN(value)) return "NaN"; + if (value === Number.POSITIVE_INFINITY) return "Infinity"; + if (value === Number.NEGATIVE_INFINITY) return "-Infinity"; + return value as string; + + // string: + case ScalarType.STRING: + return value as string; + + // bool: + case ScalarType.BOOL: + return value as boolean; + + // JSON value will be a decimal string. Either numbers or strings are accepted. + case ScalarType.UINT64: + case ScalarType.FIXED64: + case ScalarType.INT64: + case ScalarType.SFIXED64: + case ScalarType.SINT64: + return value.toString(); + + // bytes: JSON value will be the data encoded as a string using standard base64 encoding with paddings. + // Either standard or URL-safe base64 encoding with/without paddings are accepted. + case ScalarType.BYTES: + return protoBase64.enc(value as Uint8Array); + } +} diff --git a/protobuf-bitfun/src/private/message-type.ets b/protobuf-bitfun/src/private/message-type.ets new file mode 100644 index 0000000..6f5e60a --- /dev/null +++ b/protobuf-bitfun/src/private/message-type.ets @@ -0,0 +1,32 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { Message } from "../message"; +import type { FieldListSource } from "./type"; +import { EmptyMessage,MessageType } from "../message-type"; +import type { ProtoRuntime } from "./proto-runtime"; +import { LocalNameOpt } from "./util"; +import { fiMapType } from "../field"; + +/** + * Create a new message type using the given runtime. + */ +export function makeMessageType = EmptyMessage>( + runtime: ProtoRuntime, + typeName: string, + fields: FieldListSource, + opt?: LocalNameOpt +): MessageType { + return new fiMapType(); +} diff --git a/protobuf-bitfun/src/private/names.ets b/protobuf-bitfun/src/private/names.ets new file mode 100644 index 0000000..5d8d49c --- /dev/null +++ b/protobuf-bitfun/src/private/names.ets @@ -0,0 +1,303 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import type { + DescEnum, + DescEnumValue, + DescExtension, + DescField, + DescMessage, + DescService, +} from "../descriptor-set"; +import type { DescMethod, DescOneof } from "../descriptor-set"; + +/** + * Returns the name of a protobuf element in generated code. + * + * Field names - including oneofs - are converted to lowerCamelCase. For + * messages, enumerations and services, the package name is stripped from + * the type name. For nested messages and enumerations, the names are joined + * with an underscore. For methods, the first character is made lowercase. + */ +export function localName( + desc: + | DescEnum + | DescEnumValue + | DescMessage + | DescExtension + | DescOneof + | DescField + | DescService + | DescMethod, +): string | undefined{ + switch (desc.kind) { + case "field": + return localFieldName(desc.name, desc.oneof !== undefined); + case "oneof": + return localOneofName(desc.name); + case "enum": + case "message": + case "service": + case "enum_value":{ + let name = desc.name; + const sharedPrefix = (desc.parent as DescEnum).sharedPrefix; + if (sharedPrefix !== undefined){ + name = name.substring(sharedPrefix.length); + } + return safeObjectProperty(name); + } + case "rpc": { + let name = desc.name; + if (name.length == 0) { + return name; + } + name = name[0].toLowerCase() + name.substring(1); + return safeObjectProperty(name); + } + } + return; +} + +/** + * Returns the name of a field in generated code. + */ +export function localFieldName(protoName: string, inOneof: boolean) { + const name = protoCamelCase(protoName); + if (inOneof) { + // oneof member names are not properties, but values of the `case` property. + return name; + } + return safeObjectProperty(safeMessageProperty(name)); +} + +/** + * Returns the name of a oneof group in generated code. + */ +export function localOneofName(protoName: string): string { + return localFieldName(protoName, false); +} + +/** + * Returns the JSON name for a protobuf field, exactly like protoc does. + */ +export const fieldJsonName = protoCamelCase; + +/** + * Finds a prefix shared by enum values, for example `MY_ENUM_` for + * `enum MyEnum {MY_ENUM_A=0; MY_ENUM_B=1;}`. + */ +export function findEnumSharedPrefix( + enumName: string, + valueNames: string[], +): string | undefined { + const prefix = camelToSnakeCase(enumName) + "_"; + for (const name of valueNames) { + if (!name.toLowerCase().startsWith(prefix)) { + return undefined; + } + const shortName = name.substring(prefix.length); + if (shortName.length == 0) { + return undefined; + } + if (/^\d/.test(shortName)) { + // identifiers must not start with numbers + return undefined; + } + } + return prefix; +} + +/** + * Converts lowerCamelCase or UpperCamelCase into lower_snake_case. + * This is used to find shared prefixes in an enum. + */ +function camelToSnakeCase(camel: string): string { + return ( + camel.substring(0, 1) + camel.substring(1).replace(/[A-Z]/g, (c) => "_" + c) + ).toLowerCase(); +} + +/** + * Converts snake_case to protoCamelCase according to the convention + * used by protoc to convert a field name to a JSON name. + */ +function protoCamelCase(snakeCase: string): string { + let capNext = false; + const b: string[] = []; + for (let i = 0; i < snakeCase.length; i++) { + let c = snakeCase.charAt(i); + switch (c) { + case "_": + capNext = true; + break; + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + case "9": + b.push(c); + capNext = false; + break; + default: + if (capNext) { + capNext = false; + c = c.toUpperCase(); + } + b.push(c); + break; + } + } + return b.join(""); +} + +/** + * Names that cannot be used for identifiers, such as class names, + * but _can_ be used for object properties. + */ +const reservedIdentifiers = new Set([ + // ECMAScript 2015 keywords + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + "yield", + + // ECMAScript 2015 future reserved keywords + "enum", + "implements", + "interface", + "let", + "package", + "private", + "protected", + "public", + "static", + + // Class name cannot be 'Object' when targeting ES5 with module CommonJS + "Object", + + // TypeScript keywords that cannot be used for types (as opposed to variables) + "bigint", + "number", + "boolean", + "string", + "object", + + // Identifiers reserved for the runtime, so we can generate legible code + "globalThis", + "Uint8Array", + "Partial", +]); + +/** + * Names that cannot be used for object properties because they are reserved + * by built-in JavaScript properties. + */ +const reservedObjectProperties = new Set([ + // names reserved by JavaScript + "constructor", + "toString", + "toJSON", + "valueOf", +]); + +/** + * Names that cannot be used for object properties because they are reserved + * by the runtime. + */ +const reservedMessageProperties = new Set([ + // names reserved by the runtime + "getType", + "clone", + "equals", + "fromBinary", + "fromJson", + "fromJsonString", + "toBinary", + "toJson", + "toJsonString", + + // names reserved by the runtime for the future + "toObject", +]); + +const fallback = (name: T) => `${name}$`; + +/** + * Will wrap names that are Object prototype properties or names reserved + * for `Message`s. + */ +const safeMessageProperty = (name: string): string => { + if (reservedMessageProperties.has(name)) { + return fallback(name); + } + return name; +}; + +/** + * Names that cannot be used for object properties because they are reserved + * by built-in JavaScript properties. + */ +export const safeObjectProperty = (name: string): string => { + if (reservedObjectProperties.has(name)) { + return fallback(name); + } + return name; +}; + +/** + * Names that can be used for identifiers or class properties + */ +export const safeIdentifier = (name: string): string => { + if (reservedIdentifiers.has(name)) { + return fallback(name); + } + return name; +}; diff --git a/protobuf-bitfun/src/private/proto-runtime.ets b/protobuf-bitfun/src/private/proto-runtime.ets new file mode 100644 index 0000000..84d093a --- /dev/null +++ b/protobuf-bitfun/src/private/proto-runtime.ets @@ -0,0 +1,99 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { JsonFormat } from "../json-format"; +import { BinaryFormat } from "../binary-format"; +import type { Message } from "../message"; +import type { EnumType, EnumValueInfo } from "../enum"; +import type { EmptyMessage,MessageType } from "../message-type"; +import { FieldList } from "../field-list"; +import type { FieldListSource, EnumValueInfoOmitLocalName, EmptyOpt } from "./type"; +import type { EnumObject } from "./enum"; +import { getEnumType, makeEnum, makeEnumType } from "./enum"; +import { LocalNameOpt, Util } from "./util"; +import { makeMessageType } from "./message-type"; +import { BinaryFormatImp } from "./binary-format-imp"; +import { JsonFormatImp } from "./json-format-imp"; + +/** + * A facade that provides serialization and other internal functionality. + */ +@Sendable +export class ProtoRuntime { + syntax: string = ""; + json: JsonFormat = new JsonFormatImp(); + bin: BinaryFormat = new BinaryFormatImp(); + util: Util = new Util(); + + constructor(){ + } + + /** + * Create a message type at runtime, without generating code. + */ + makeMessageType = EmptyMessage>( + typeName: string, + fields: FieldListSource, + opt?: LocalNameOpt, + ): MessageType{ + return makeMessageType(this, typeName, fields, opt); + } + + /** + * Create an enum object at runtime, without generating code. + * + * The object conforms to TypeScript enums, and comes with + * mapping from name to value, and from value to name. + * + * The type name and other reflection information is accessible + * via getEnumType(). + */ + makeEnum( + typeName: string, + values: (EnumValueInfo | EnumValueInfoOmitLocalName)[], + opt?: EmptyOpt, + ): EnumObject{ + return makeEnum(typeName, values, opt); + } + + /** + * Create an enum type at runtime, without generating code. + * Note that this only creates the reflection information, not an + * actual enum object. + */ + makeEnumType( + typeName: string, + values: (EnumValueInfo | EnumValueInfoOmitLocalName)[], + opt?: EmptyOpt, + ): EnumType{ + return makeEnumType(typeName, values, opt); + } + + /** + * Get reflection information - the EnumType - from an enum object. + * If this function is called on something other than a generated + * enum, or an enum constructed with makeEnum(), it raises an error. + */ + getEnumType(enumObject: EnumObject): EnumType{ + return getEnumType(enumObject); + } +} + +export function makeProtoRuntime( + syntax: string, + newFieldList: (fields: FieldListSource) => FieldList, + initFields: (target: Message) => void, +): ProtoRuntime { + return new ProtoRuntime(); +} diff --git a/protobuf-bitfun/src/private/reflect.ets b/protobuf-bitfun/src/private/reflect.ets new file mode 100644 index 0000000..58c6f8e --- /dev/null +++ b/protobuf-bitfun/src/private/reflect.ets @@ -0,0 +1,102 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import type { FieldInfo } from "../field"; +import { isScalarZeroValue, scalarZeroValue } from "./scalars"; +import { Message } from "../message"; +import { EnumType } from "../enum"; +import { ScalarType, ScalarTypeExcept } from "../scalar"; + +/** + * Returns true if the field is set. + */ +export function isFieldSet >( + fieldInfo: FieldInfo, + target: Record, // eslint-disable-line @typescript-eslint/no-explicit-any -- `any` is the best choice for dynamic access +) { + const field = fieldInfo; + const localName: string | undefined = field.localName; + if (localName === undefined){ + return false; + } + if (field.repeated) { + return (target[localName] as []).length > 0; + } + if (field.oneof && field.oneof.localName !== undefined) { + return target[field.oneof.localName]?.case === localName; // eslint-disable-line @typescript-eslint/no-unsafe-member-access + } + switch (field.kind) { + case "enum": + case "scalar": + if (field.opt || field.req) { + // explicit presence + return target[localName] !== undefined; + } + // implicit presence + if (field.kind == "enum" && field.T !== undefined) { + const f = field.T as Object as EnumType; + return target[localName] !== f.values[0].no; + } + if (field.T === undefined){ + return false; + } + return !isScalarZeroValue(field.T as ScalarType, target[localName]); + case "message": + return target[localName] !== undefined; + case "map": + return Object.keys(target[localName]).length > 0; // eslint-disable-line @typescript-eslint/no-unsafe-argument + } + return false; +} + +/** + * Resets the field, so that isFieldSet() will return false. + */ +export function clearField>( + fieldInfo: FieldInfo, + target: Record, // eslint-disable-line @typescript-eslint/no-explicit-any -- `any` is the best choice for dynamic access +) { + const field = fieldInfo; + const localName = field.localName; + if (localName === undefined){ + return; + } + const implicitPresence = !field.opt && !field.req; + if (field.repeated) { + target[localName] = []; + } else if (field.oneof && field.oneof.localName !== undefined) { + target[field.oneof.localName] = { case: undefined }; + } else { + switch (field.kind) { + case "map": + target[localName] = {}; + break; + case "enum": + const f = field.T as Object as EnumType; + target[localName] = implicitPresence ? (field.T === undefined ? undefined : f.values[0].no) : undefined; + break; + case "scalar": + if (field.T === undefined || field.L === undefined){ + return; + } + target[localName] = implicitPresence + ? scalarZeroValue(field.T as ScalarType, field.L) + : undefined; + break; + case "message": + target[localName] = undefined; + break; + } + } +} diff --git a/protobuf-bitfun/src/private/reify-wkt.ets b/protobuf-bitfun/src/private/reify-wkt.ets new file mode 100644 index 0000000..0b566b2 --- /dev/null +++ b/protobuf-bitfun/src/private/reify-wkt.ets @@ -0,0 +1,93 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import type { DescField, DescMessage, DescOneof } from "../descriptor-set"; +import { ScalarType } from "../scalar"; + +interface GoogleProtobufTimestamp{ + typeName: "google.protobuf.Timestamp"; + seconds: DescField; + nanos: DescField; +} + +interface GoogleProtobufDuration{ + typeName: "google.protobuf.Duration"; + seconds: DescField; + nanos: DescField; +} + +export type DescWkt = GoogleProtobufTimestamp | GoogleProtobufDuration; + +/** + * @deprecated please use reifyWkt from @bufbuild/protoplugin/ecmascript instead + * + * Reifies a given DescMessage into a more concrete object representing its + * respective well-known type. The returned object will contain properties + * representing the WKT's defined fields. + * + * Useful during code generation when immediate access to a particular field + * is needed without having to search the object's typename and DescField list. + * + * Returns undefined if the WKT cannot be completely constructed via the + * DescMessage. + */ +export function reifyWkt(message: DescMessage): DescWkt | undefined { + switch (message.typeName) { + case "google.protobuf.Timestamp": { + const seconds = message.fields.find( + (f) => + f.number == 1 && + f.fieldKind == "scalar" && + f.scalar === ScalarType.INT64, + ); + const nanos = message.fields.find( + (f) => + f.number == 2 && + f.fieldKind == "scalar" && + f.scalar === ScalarType.INT32, + ); + if (seconds && nanos) { + return { + typeName: message.typeName, + seconds, + nanos, + }; + } + break; + } + case "google.protobuf.Duration": { + const seconds = message.fields.find( + (f) => + f.number == 1 && + f.fieldKind == "scalar" && + f.scalar === ScalarType.INT64, + ); + const nanos = message.fields.find( + (f) => + f.number == 2 && + f.fieldKind == "scalar" && + f.scalar === ScalarType.INT32, + ); + if (seconds && nanos) { + return { + typeName: message.typeName, + seconds, + nanos, + }; + } + break; + } + } + return undefined; +} diff --git a/protobuf-bitfun/src/private/scalars.ets b/protobuf-bitfun/src/private/scalars.ets new file mode 100644 index 0000000..851ea01 --- /dev/null +++ b/protobuf-bitfun/src/private/scalars.ets @@ -0,0 +1,109 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. +import { protoInt64 } from "../proto-int64"; +import { LongType, ScalarType, ScalarTypeExcept } from "../scalar"; + +/** + * Returns true if both scalar values are equal. + */ +export function scalarEquals( + type: ScalarType, + a: string | boolean | number | bigint | Uint8Array | undefined, + b: string | boolean | number | bigint | Uint8Array | undefined, +): boolean { + if (a === b) { + // This correctly matches equal values except BYTES and (possibly) 64-bit integers. + return true; + } + // Special case BYTES - we need to compare each byte individually + if (type == ScalarType.BYTES) { + if (!(a instanceof Uint8Array) || !(b instanceof Uint8Array)) { + return false; + } + if (a.length !== b.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + return true; + } + // Special case 64-bit integers - we support number, string and bigint representation. + // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check + switch (type) { + case ScalarType.UINT64: + case ScalarType.FIXED64: + case ScalarType.INT64: + case ScalarType.SFIXED64: + case ScalarType.SINT64: + // Loose comparison will match between 0n, 0 and "0". + return a == b; + } + // Anything that hasn't been caught by strict comparison or special cased + // BYTES and 64-bit integers is not equal. + return false; +} + +/** + * Returns the zero value for the given scalar type. + */ +export function scalarZeroValue( + type: ScalarType, + longType: LongType, +) { + switch (type) { + case ScalarType.BOOL: + return false; + case ScalarType.UINT64: + case ScalarType.FIXED64: + case ScalarType.INT64: + case ScalarType.SFIXED64: + case ScalarType.SINT64: + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison -- acceptable since it's covered by tests + return (longType == 0 ? protoInt64.zero : "0"); + case ScalarType.DOUBLE: + case ScalarType.FLOAT: + return 0.0 ; + case ScalarType.BYTES: + return new Uint8Array(); + case ScalarType.STRING: + return "" ; + default: + // Handles INT32, UINT32, SINT32, FIXED32, SFIXED32. + // We do not use individual cases to save a few bytes code size. + return 0 ; + } +} + +/** + * Returns true for a zero-value. For example, an integer has the zero-value `0`, + * a boolean is `false`, a string is `""`, and bytes is an empty Uint8Array. + * + * In proto3, zero-values are not written to the wire, unless the field is + * optional or repeated. + */ +export function isScalarZeroValue(type: ScalarType, value: boolean | string | Uint8Array | number): boolean { + switch (type) { + case ScalarType.BOOL: + return value === false; + case ScalarType.STRING: + return value === ""; + case ScalarType.BYTES: + return value instanceof Uint8Array && !value.byteLength; + default: + return value == 0; // Loose comparison matches 0n, 0 and "0" + } +} diff --git a/protobuf-bitfun/src/private/text-format.ets b/protobuf-bitfun/src/private/text-format.ets new file mode 100644 index 0000000..e69379b --- /dev/null +++ b/protobuf-bitfun/src/private/text-format.ets @@ -0,0 +1,214 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import type { DescEnum } from "../descriptor-set"; +import { assert } from "./assert"; +import { protoInt64 } from "../proto-int64"; +import { ScalarType } from "../scalar"; + +export function parseTextFormatEnumValue( + descEnum: DescEnum, + value: string, +): number { + const enumValue = descEnum.values.find((v) => v.name === value); + assert(enumValue, `cannot parse ${descEnum.name} default value: ${value}`); + return enumValue.number; +} + +export function parseTextFormatScalarValue( + type: ScalarType, + value: string, +): number | boolean | string | bigint | Uint8Array | undefined{ + switch (type) { + case ScalarType.STRING: + return value; + case ScalarType.BYTES: { + const u = unescapeBytesDefaultValue(value); + if (u === false) { + throw new Error( + //`cannot parse ${ScalarType[type]} default value: ${value}`, + ); + } + return u; + } + case ScalarType.INT64: + case ScalarType.SFIXED64: + case ScalarType.SINT64: + return protoInt64.parse(value); + case ScalarType.UINT64: + case ScalarType.FIXED64: + return protoInt64.uParse(value); + case ScalarType.DOUBLE: + case ScalarType.FLOAT: + switch (value) { + case "inf": + return Number.POSITIVE_INFINITY; + case "-inf": + return Number.NEGATIVE_INFINITY; + case "nan": + return Number.NaN; + default: + return parseFloat(value); + } + case ScalarType.BOOL: + return value === "true"; + case ScalarType.INT32: + case ScalarType.UINT32: + case ScalarType.SINT32: + case ScalarType.FIXED32: + case ScalarType.SFIXED32: + return parseInt(value, 10); + } +} + +/** + * Parses a text-encoded default value (proto2) of a BYTES field. + */ +function unescapeBytesDefaultValue(str: string): Uint8Array | false { + const b: number[] = []; + const input = new Input(str); + while (input.next()) { + switch (input.c) { + case "\\": + if (input.next()) { + switch (input.c as string) { + case "\\": + b.push(input.c.charCodeAt(0)); + break; + case "b": + b.push(0x08); + break; + case "f": + b.push(0x0c); + break; + case "n": + b.push(0x0a); + break; + case "r": + b.push(0x0d); + break; + case "t": + b.push(0x09); + break; + case "v": + b.push(0x0b); + break; + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": { + const s = input.c; + const t = input.take(2); + if (t === false) { + return false; + } + const n = parseInt(s + t, 8); + if (isNaN(n)) { + return false; + } + b.push(n); + break; + } + case "x": { + const s = input.c; + const t = input.take(2); + if (t === false) { + return false; + } + const n = parseInt(s + t, 16); + if (isNaN(n)) { + return false; + } + b.push(n); + break; + } + case "u": { + const s = input.c; + const t = input.take(4); + if (t === false) { + return false; + } + const n = parseInt(s + t, 16); + if (isNaN(n)) { + return false; + } + const chunk = new Uint8Array(4); + const view = new DataView(chunk.buffer); + view.setInt32(0, n, true); + b.push(chunk[0], chunk[1], chunk[2], chunk[3]); + break; + } + case "U": { + const s = input.c; + const t = input.take(8); + if (t === false) { + return false; + } + const tc = protoInt64.uEnc(s + t); + const chunk = new Uint8Array(8); + const view = new DataView(chunk.buffer); + view.setInt32(0, tc.lo, true); + view.setInt32(4, tc.hi, true); + b.push( + chunk[0], + chunk[1], + chunk[2], + chunk[3], + chunk[4], + chunk[5], + chunk[6], + chunk[7], + ); + break; + } + } + } + break; + default: + b.push(input.c.charCodeAt(0)); + } + } + return new Uint8Array(b); +} + +class Input{ + tail: string; + c: string = ''; + + next(): boolean{ + if(this.tail.length == 0){ + return false; + } + this.c = this.tail[0]; + this.tail = this.tail.substring(1); + return true; + }; + + take(n:number):string | false { + if (this.tail.length >= n){ + const r = this.tail.substring(0,n); + this.tail = this.tail.substring(n); + return r; + } + return false; + }; + + constructor(str:string){ + this.tail = str; + } +} diff --git a/protobuf-bitfun/src/private/type.ets b/protobuf-bitfun/src/private/type.ets new file mode 100644 index 0000000..bc3264c --- /dev/null +++ b/protobuf-bitfun/src/private/type.ets @@ -0,0 +1,27 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { FieldInfo } from '../field'; + +import collections from "@arkts.collections"; + +export type FieldListSource = collections.Array; + +export interface EnumValueInfoOmitLocalName { + readonly no: number; + + readonly name: string; +} + +export interface EmptyOpt {} \ No newline at end of file diff --git a/protobuf-bitfun/src/private/util-common.ets b/protobuf-bitfun/src/private/util-common.ets new file mode 100644 index 0000000..1e57ce2 --- /dev/null +++ b/protobuf-bitfun/src/private/util-common.ets @@ -0,0 +1,67 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { Message } from "../message"; +import { Util } from "./util"; +import { isMessage } from "../is-message"; +import { MessageType } from '../message-type'; +import { ScalarType } from '../scalar' +import { scalarEquals } from './scalars'; + +export function cloneSingularField(value: ESObject): ESObject{ + if (value === undefined) { + return value; + } + if (value instanceof Uint8Array) { + const c = new Uint8Array(value.byteLength); + c.set(value); + return c; + } + if (isMessage(value)) { + return (value as Record).clone(); + } + return value; +} + +export function messageArrayCompare(va: Object, vb: Object, mT: Object) : boolean { + const arrayVa = (va as Message[]); + const arrayVb = (vb as Message[]); + const messageType = mT as Object as MessageType; + for (let i = 0; i < arrayVa.length; i++) { + if (!messageType.equals(arrayVa[i], arrayVb[i])) { + return false; + } + } + return true; +} + +export function scalarArrayCompare(va: Object, vb: Object, mT: ScalarType) : boolean { + const arrayVa = (va as string[] | boolean[] | number[] | bigint[] | Uint8Array); + const arrayVb = (vb as string[] | boolean[] | number[] | bigint[] | Uint8Array); + for (let i = 0; i < arrayVa.length; i++) { + if (!scalarEquals(mT, arrayVa[i], arrayVb[i])) { + return false; + } + } + return true; +} + +export function makeUtilCommon >(): Util { + return new Util(); +} + +// converts any ArrayLike to Uint8Array if necessary. +function toU8Arr(input: ArrayLike) { + return input instanceof Uint8Array ? input : new Uint8Array(input); +} \ No newline at end of file diff --git a/protobuf-bitfun/src/private/util.ets b/protobuf-bitfun/src/private/util.ets new file mode 100644 index 0000000..e55dcf7 --- /dev/null +++ b/protobuf-bitfun/src/private/util.ets @@ -0,0 +1,429 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { EmptyOpt, EnumValueInfoOmitLocalName, FieldListSource } from "./type"; +import { InternalFieldList } from "./field-list"; +import type { FieldList } from "../field-list"; +import { EnumObject, setEnumType } from "./enum"; +import { MessageType } from "../message-type"; +import type { BaseEnumValueInfo, enumsOptions, EnumValueInfo } from "../enum"; +import { IBinaryReader, IBinaryWriter } from "../binary-encoding"; +import { Message } from "../message"; +import { FieldInfo, fiMapType, OneofClass, OneofInfo } from "../field"; +import { cloneSingularField, messageArrayCompare, scalarArrayCompare } from "./util-common" +import { ScalarType } from "../scalar"; +import { scalarEquals } from "./scalars"; +import { isMessage } from '../is-message'; + +import collections from "@arkts.collections"; + +/** + * Provides utilities used by generated code. + * All methods are internal and are not safe to use, they may break with a + * future release. + */ +@Sendable +export class Util { + enumValuesMap: collections.Map> = new collections.Map(); + + constructor() { + } + + /** + * Create a field list + */ + newFieldList(fields: FieldListSource): FieldList { + return new InternalFieldList(fields); + } + + /** + * Sets reflection information on a generated enum. + */ + setEnumType( + enumObject: EnumObject, + typeName: string, + values: BaseEnumValueInfo[], + opt?: enumsOptions, + ): void { + setEnumType(enumObject, typeName, values, opt); + } + + + putEnumType(key: string, value: () => collections.Array) { + this.enumValuesMap.set(key, value()); + } + + getEnumType(key: string): collections.Array { + return this.enumValuesMap.get(key) ?? new collections.Array; + } + + /** + * Set default field values on the target message. + */ + initFields>(target: Message): void { + const array: collections.Array = target.getType().getFields().byMember(); + for (let i = 0; i < array.length; i++) { + const member = array.at(i) as Object as FieldInfo; + if (member === undefined) { + continue; + } + if (member.oneof?.opt !== undefined) { + continue; + } + if (member.oneof?.repeated) { + continue; + } + + switch (member.kind) { + case "oneof": + break; + + case "enum": + break; + + case "map": + break; + + case "scalar": + break; + + case "message": + break; + } + } + } + + /** + * Set specified field values on the target message, recursively. + */ + initPartial>( + source: Message | undefined, + target: T, + ): void { + if (source === undefined) { + return; + } + const type = target.getType(); + const memList: collections.Array | undefined = type.getFields().byMember(); + for (let i = 0; i < memList.length; i++) { + const member = memList.at(i) as Object as FieldInfo; + const localName = member.localName; + const t = target as Object as Record; + const s = source as Object as Record; + if (s[localName] == null) { + continue; + } + switch (member.kind) { + case "oneof": + const sk = (s[localName] as Object as OneofClass).type; + if (sk === undefined) { + continue; + } + const sourceField = (member as Object as OneofInfo).findField(sk); + let val : T | Uint8Array | undefined = (s[localName] as Object as OneofClass).value; + if (val === undefined || sourceField === undefined || sourceField?.T === undefined) { + continue; + } + if (sourceField && sourceField.kind == "message" && isMessage(val, sourceField.T as Object as MessageType)) { + val = (sourceField.T as Object as MessageType).create(); + } else if (sourceField && sourceField.kind == "scalar" && sourceField.T == ScalarType.BYTES) { + val = this.toU8Arr(val); + } + t[localName] = new OneofClass(sk, val); + break; + case "scalar": + case "enum": + let copy = s[localName]; + if (member.T === ScalarType.BYTES) { + copy = member.repeated ? (copy as ArrayLike[]).map(this.toU8Arr) : this.toU8Arr(copy as Uint8Array); + } + t[localName] = copy; + break; + case "map": + switch (member.V.kind) { + case "scalar": + case "enum": + if (member.V.T === ScalarType.BYTES) { + const entries: [string, Object][] = Object.entries(s[localName]); + for (const item of entries) { + const k = item[0]; + (t[localName] as Record)[k] = this.toU8Arr(item[1]); + } + } else { + this.assign(t[localName] as Record, s[localName]); + } + break; + case "message": + const messageType = member.V.T as Object as MessageType; + for (const k of Object.keys(s[localName])) { + let val = (s[localName]as Record)[k]; + if (!messageType.getFieldWrapper()) { + val = messageType.create(val as Message); + } + (t[localName] as Record)[k] = val; + } + break; + } + break; + case "message": + const mt = member.T as Object as MessageType; + if (member.repeated) { + t[localName] = (s[localName] as []).map((val) => isMessage(val, mt) ? val : mt.create()); + } else { + const val = s[localName]; + if (mt.getFieldWrapper() !== undefined) { + if (mt.getTypeName() === "google.protobuf.BytesValue") { + t[localName] = this.toU8Arr(val); + } else { + t[localName] = val; + } + } else { + t[localName] = isMessage(val as Object as T) ? val : mt.create(val as Message); + } + } + break; + } + } + } + + /** + * Compares two messages of the same type recursively. + * Will also return true if both messages are `undefined` or `null`. + */ + equals>( + type: MessageType, a: T | Message | null | undefined, + b: T | Message | null | undefined): boolean { + if (a === b) { + return true; + } + if (!a || !b) { + return false; + } + const memList: collections.Array | undefined = type.getFields().byMember(); + if (memList === undefined) { + return true; + } + for (let i = 0; i < memList.length; i++) { + const member = memList.at(i) as Object as FieldInfo; + const m = member; + // messageType + let va = (a as Object as Record)[m.localName]; + const vb = (b as Object as Record)[m.localName]; + if (m.repeated) { + if ((va as Object[]).length !== (vb as Object[]).length) { + return false; + } + switch (m.kind) { + case "message": + if (!messageArrayCompare(va, vb, m.T as Object as MessageType)) { + return false; + } + break; + case "scalar": + if (!scalarArrayCompare(va, vb, m.T as ScalarType)) { + return false; + } + break; + case "enum": + if (!scalarArrayCompare(va, vb, ScalarType.INT32)) { + return false; + } + break; + } + continue; + } + switch (m.kind) { + case "message": + if (!(m.T as Object as MessageType).equals(va as Message, vb as Message)) { + return false; + } + break; + case "enum": + if (!scalarEquals(ScalarType.INT32, va as string | boolean | number | bigint | Uint8Array | undefined, + vb as string | boolean | number | bigint | Uint8Array | undefined)) { + return false; + } + break; + case "scalar": + if (!scalarEquals(m.K, va as string | boolean | number | bigint | Uint8Array | undefined, + vb as string | boolean | number | bigint | Uint8Array | undefined)) { + return false; + } + break; + case "oneof": + const s: FieldInfo | undefined = + (m as Object as OneofInfo).findField((va as Object as OneofClass).type ?? ""); + if (s === undefined) { + return true; + } + const vaa = (va as Object as OneofClass); + const vbb = (vb as Object as OneofClass); + switch (s.kind) { + case "message": + if (!(s.T as Object as MessageType).equals(vaa.value, vbb.value)) { + return false; + } + break; + case "enum": + if (!scalarEquals(ScalarType.INT32, + vaa.value as Object as string | boolean | number | bigint | Uint8Array | undefined, + vbb.value as Object as string | boolean | number | bigint | Uint8Array | undefined)) { + return false; + } + break; + case "message": + if (!scalarEquals(s.T as ScalarType, + vaa.value as Object as string | boolean | number | bigint | Uint8Array | undefined, + vbb.value as Object as string | boolean | number | bigint | Uint8Array | undefined)) { + return false; + } + break; + } + case "map": + if (va instanceof collections.Map && vb instanceof collections.Map) { + if (va.size !== vb.size) { + return false; + } + const keysVa: propertyKey [] = []; + const iterVa: IterableIterator<[propertyKey, Object]> = va.entries(); + for (const entry of iterVa) { + keysVa.push(entry[0]); + } + + const keysVb: propertyKey [] = []; + const iterVb: IterableIterator<[propertyKey, Object]> = vb.entries(); + for (const entry of iterVb) { + keysVb.push(entry[0] as propertyKey); + } + + const keys = keysVa.concat(keysVb); + switch (m.V.kind) { + case "message": + const messageType = m.V.T as Object as MessageType; + const messageVa: collections.Map = va; + const messageVb: collections.Map = vb; + if (!keys.every((k) => messageType.equals(messageVa.get(k), messageVb.get(k)))) { + return false; + } + break; + case "enum": + const enumVa: collections.Map = va; + const enumVb: collections.Map = vb; + if (!keys.every((k) => scalarEquals(ScalarType.INT32, enumVa.get(k), enumVb.get(k)))) { + return false; + } + break; + case "scalar": + const scalarType = m.V.T; + const scalarVa: collections.Map = va; + const scalarVb: collections.Map = vb; + if (!keys.every((k) => scalarEquals(scalarType as ScalarType, scalarVa.get(k), scalarVb.get(k)))) { + return false; + } + break; + } + } else { + return false; + } + } + } + return true; + } + + clone>(message: T): T { + const type = message.getType(); + let target: T = type.create() + let any = target as Object as Record; + const memList: collections.Array = type.getFields().byMember(); + if (memList === undefined) { + return new Object as T; + } + for (let i = 0; i < memList.length; i++) { + const item = memList.at(i); + const member = item as Object as FieldInfo; + const source: Object = (message as Object as Record)[member.localName]; + + if (member.repeated) { + let copy: Object[] = (source as []).map(cloneSingularField) + any[member.localName] = copy; + } else if (member.kind === "map") { + if (source instanceof collections.Map) { + let copy = any[member.localName]; + if (copy instanceof collections.Map) { + const entriesIter: IterableIterator<[PropertyKey, Object]> = source.entries(); + for (const entry of entriesIter) { + copy.set(entry[0], entry[1]); + } + } + any[member.localName] = copy; + } + } else if (member.kind == "oneof") { + const sourceOneOf = source as object as OneofClass; + const f = (item as Object as OneofInfo).findField(sourceOneOf.type ?? ""); + const value = sourceOneOf.value; + const copy = f ? new OneofClass(sourceOneOf.type, value) : new OneofClass(); + any[member.localName] = copy; + } else { + let copy: ESObject = cloneSingularField(source); + any[member.localName] = copy; + } + } + return target; + } + + private toU8Arr(input: ESObject): Uint8Array { + return input instanceof Uint8Array ? input : new Uint8Array(); + } + + private assign(target: Record, ...source: Object[]): Record { + for (let s of source) { + for (let k of Object.keys(s)) { + target[k] = Reflect.get(s, k); + } + } + return target; + } +} + +type propertyKey = string | number; + +export interface BinaryReadAndWriteOptions { + readUnknownFields: boolean; + readerFactory: (bytes: Uint8Array) => IBinaryReader; + writeUnknownFields: boolean; + writerFactory: () => IBinaryWriter; +} + +export interface LocalNameOpt { + localName?: string; +} + +export interface UtilOmit { + setEnumType: ( + enumObject: EnumObject, + typeName: string, + values: EnumValueInfoOmitLocalName[], + opt?: EmptyOpt, + ) => void; + + initPartial: >( + target: T, + ) => void; + + equals: >( + type: MessageType, + ) => boolean; + + clone: >(message: T) => T; +} diff --git a/protobuf-bitfun/src/proto-base64.ets b/protobuf-bitfun/src/proto-base64.ets new file mode 100644 index 0000000..e0df1f9 --- /dev/null +++ b/protobuf-bitfun/src/proto-base64.ets @@ -0,0 +1,137 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +/* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unnecessary-condition, prefer-const */ + +// lookup table from base64 character to byte +let encTable = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""); + +// lookup table from base64 character *code* to byte because lookup by number is fast +let decTable: number[] = []; +for (let i = 0; i < encTable.length; i++) + decTable[encTable[i].charCodeAt(0)] = i; + +// support base64url variants +decTable["-".charCodeAt(0)] = encTable.indexOf("+"); +decTable["_".charCodeAt(0)] = encTable.indexOf("/"); + +export const protoBase64: protoBase64Interface = { + /** + * Decodes a base64 string to a byte array. + * + * - ignores white-space, including line breaks and tabs + * - allows inner padding (can decode concatenated base64 strings) + * - does not require padding + * - understands base64url encoding: + * "-" instead of "+", + * "_" instead of "/", + * no padding + */ + dec(base64Str: string): Uint8Array { + // estimate byte size, not accounting for inner padding and whitespace + let es = (base64Str.length * 3) / 4; + if (base64Str[base64Str.length - 2] == "=") es -= 2; + else if (base64Str[base64Str.length - 1] == "=") es -= 1; + + let bytes = new Uint8Array(es), + bytePos = 0, // position in byte array + groupPos = 0, // position in base64 group + b: number, // current byte + p = 0; // previous byte + for (let i = 0; i < base64Str.length; i++) { + b = decTable[base64Str.charCodeAt(i)]; + if (b === undefined) { + switch (base64Str[i]) { + case "=": + groupPos = 0; // reset state when padding found + case "\n": + case "\r": + case "\t": + case " ": + continue; // skip white-space, and padding + default: + throw Error("invalid base64 string."); + } + } + switch (groupPos) { + case 0: + p = b; + groupPos = 1; + break; + case 1: + bytes[bytePos++] = (p << 2) | ((b & 48) >> 4); + p = b; + groupPos = 2; + break; + case 2: + bytes[bytePos++] = ((p & 15) << 4) | ((b & 60) >> 2); + p = b; + groupPos = 3; + break; + case 3: + bytes[bytePos++] = ((p & 3) << 6) | b; + groupPos = 0; + break; + } + } + if (groupPos == 1) throw Error("invalid base64 string."); + return bytes.subarray(0, bytePos); + }, + /** + * Encode a byte array to a base64 string. + */ + enc(bytes: Uint8Array): string { + let base64 = "", + groupPos = 0, // position in base64 group + b: number, // current byte + p = 0; // carry over from previous byte + + for (let i = 0; i < bytes.length; i++) { + b = bytes[i]; + switch (groupPos) { + case 0: + base64 += encTable[b >> 2]; + p = (b & 3) << 4; + groupPos = 1; + break; + case 1: + base64 += encTable[p | (b >> 4)]; + p = (b & 15) << 2; + groupPos = 2; + break; + case 2: + base64 += encTable[p | (b >> 6)]; + base64 += encTable[b & 63]; + groupPos = 0; + break; + } + } + + // add output padding + if (groupPos) { + base64 += encTable[p]; + base64 += "="; + if (groupPos == 1) base64 += "="; + } + + return base64; + }, +} ; + +export interface protoBase64Interface { + dec: (base64Str: string) => Uint8Array; + + enc: (bytes: Uint8Array) => string; +} \ No newline at end of file diff --git a/protobuf-bitfun/src/proto-double.ets b/protobuf-bitfun/src/proto-double.ets new file mode 100644 index 0000000..461499f --- /dev/null +++ b/protobuf-bitfun/src/proto-double.ets @@ -0,0 +1,33 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +// Export global Number constants. This is done so that we can safely use +// these global constants when generating code and be assured we're using +// the correct values. We cannot rely on globalThis since we support ES2017 +// and globalThis was introduced in ES2020. We also don't want to explicitly +// generate code using, for example, Number.NaN, since this could clash with +// a message name of Number. Instead we can export them here since this will +// be in a different scope as the generated code and we are guaranteed to use +// the intended global values. +export interface protoDoubleInterface { + NaN: number, + POSITIVE_INFINITY: number, + NEGATIVE_INFINITY: number, +}; + +export const protoDouble: protoDoubleInterface = { + NaN: Number.NaN, + POSITIVE_INFINITY: Number.POSITIVE_INFINITY, + NEGATIVE_INFINITY: Number.NEGATIVE_INFINITY, +} ; diff --git a/protobuf-bitfun/src/proto-int64.ets b/protobuf-bitfun/src/proto-int64.ets new file mode 100644 index 0000000..ae6d7ef --- /dev/null +++ b/protobuf-bitfun/src/proto-int64.ets @@ -0,0 +1,228 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { assert } from "./private/assert"; +import { + int64FromString, + int64ToString, + uInt64ToString, +} from "./google/varint"; + +/** + * We use the `bigint` primitive to represent 64-bit integral types. If bigint + * is unavailable, we fall back to a string representation, which means that + * all values typed as `bigint` will actually be strings. + * + * If your code is intended to run in an environment where bigint may be + * unavailable, it must handle both the bigint and the string representation. + * For presenting values, this is straight-forward with implicit or explicit + * conversion to string: + * + * ```ts + * let el = document.createElement("span"); + * el.innerText = message.int64Field; // assuming a protobuf int64 field + * + * console.log(`int64: ${message.int64Field}`); + * + * let str: string = message.int64Field.toString(); + * ``` + * + * If you need to manipulate 64-bit integral values and are sure the values + * can be safely represented as an IEEE-754 double precision number, you can + * convert to a JavaScript Number: + * + * ```ts + * console.log(message.int64Field.toString()) + * let num = Number(message.int64Field); + * num = num + 1; + * message.int64Field = protoInt64.parse(num); + * ``` + * + * If you need to manipulate 64-bit integral values that are outside the + * range of safe representation as a JavaScript Number, we recommend you + * use a third party library, for example the npm package "long": + * + * ```ts + * // convert the field value to a Long + * const bits = protoInt64.enc(message.int64Field); + * const longValue = Long.fromBits(bits.lo, bits.hi); + * + * // perform arithmetic + * const longResult = longValue.subtract(1); + * + * // set the result in the field + * message.int64Field = protoInt64.dec(longResult.low, longResult.high); + * + * // Assuming int64Field contains 9223372036854775807: + * console.log(message.int64Field); // 9223372036854775806 + * ``` + */ + +export interface Enc{ + lo: number; + hi: number; +} + +interface Int64Support { + /** + * 0n if bigint is available, "0" if unavailable. + */ + readonly zero: bigint; + + /** + * Is bigint available? + */ + readonly supported: boolean; + + /** + * Parse a signed 64-bit integer. + * Returns a bigint if available, a string otherwise. + */ + parse: (value: string | number | bigint) => bigint; + + /** + * Parse an unsigned 64-bit integer. + * Returns a bigint if available, a string otherwise. + */ + uParse: (value: string | number | bigint) => bigint; + + /** + * Convert a signed 64-bit integral value to a two's complement. + */ + enc:(value: string | number | bigint) => Enc ; + + /** + * Convert an unsigned 64-bit integral value to a two's complement. + */ + uEnc:(value: string | number | bigint) => Enc ; + + /** + * Convert a two's complement to a signed 64-bit integral value. + * Returns a bigint if available, a string otherwise. + */ + dec:(lo: number, hi: number) => bigint; + + /** + * Convert a two's complement to an unsigned 64-bit integral value. + * Returns a bigint if available, a string otherwise. + */ + uDec:(lo: number, hi: number) => bigint; +} + +function makeInt64Support(): Int64Support { + const dv = new DataView(new ArrayBuffer(8)); + // note that Safari 14 implements BigInt, but not the DataView methods + const ok = + typeof BigInt === "function" && + typeof dv.getBigInt64 === "function" && + typeof dv.getBigUint64 === "function" && + typeof dv.setBigInt64 === "function" && + typeof dv.setBigUint64 === "function" ; + //&& + //(typeof process != "object" || + // typeof process.env != "object" || + // process.env.BUF_BIGINT_DISABLE !== "1"); + if (ok) { + const MIN = BigInt("-9223372036854775808"), + MAX = BigInt("9223372036854775807"), + UMIN = BigInt("0"), + UMAX = BigInt("18446744073709551615"); + return { + zero: BigInt(0), + supported: true, + parse(value: string | number | bigint): bigint { + const bi = typeof value == "bigint" ? value : BigInt(value); + if (bi > MAX || bi < MIN) { + throw new Error(`int64 invalid: ${value}`); + } + return bi; + }, + uParse(value: string | number | bigint): bigint { + const bi = typeof value == "bigint" ? value : BigInt(value); + if (bi > UMAX || bi < UMIN) { + throw new Error(`uint64 invalid: ${value}`); + } + return bi; + }, + enc(value: string | number | bigint): Enc { + dv.setBigInt64(0, protoInt64.parse(value), true); + return { + lo: dv.getInt32(0, true), + hi: dv.getInt32(4, true), + }; + }, + uEnc(value: string | number | bigint): Enc { + dv.setBigInt64(0, protoInt64.uParse(value), true); + return { + lo: dv.getInt32(0, true), + hi: dv.getInt32(4, true), + }; + }, + dec(lo: number, hi: number): bigint { + dv.setInt32(0, lo, true); + dv.setInt32(4, hi, true); + return dv.getBigInt64(0, true); + }, + uDec(lo: number, hi: number): bigint { + dv.setInt32(0, lo, true); + dv.setInt32(4, hi, true); + return dv.getBigUint64(0, true); + }, + } as Int64Support + } + const assertInt64String = (value: string) => + assert(/^-?[0-9]+$/.test(value), `int64 invalid: ${value}`); + const assertUInt64String = (value: string) => + assert(/^[0-9]+$/.test(value), `uint64 invalid: ${value}`); + return { + zero: "0" as Object as 0n, + supported: false, + parse(value: string | number | bigint): bigint { + if (typeof value != "string") { + value = value.toString(); + } + assertInt64String(value); + return value as Object as bigint; + }, + uParse(value: string | number | bigint): bigint { + if (typeof value != "string") { + value = value.toString(); + } + assertUInt64String(value); + return value as Object as bigint; + }, + enc(value: string | number | bigint): Enc { + if (typeof value != "string") { + value = value.toString(); + } + assertInt64String(value); + return int64FromString(value); + }, + uEnc(value: string | number | bigint): Enc { + if (typeof value != "string") { + value = value.toString(); + } + assertUInt64String(value); + return int64FromString(value); + }, + dec(lo: number, hi: number): bigint { + return int64ToString(lo, hi) as Object as bigint; + }, + uDec(lo: number, hi: number): bigint { + return uInt64ToString(lo, hi) as Object as bigint; + }, + } as Int64Support; +} + +export const protoInt64: Int64Support = makeInt64Support(); diff --git a/protobuf-bitfun/src/proto2.ets b/protobuf-bitfun/src/proto2.ets new file mode 100644 index 0000000..7836448 --- /dev/null +++ b/protobuf-bitfun/src/proto2.ets @@ -0,0 +1,85 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { makeProtoRuntime, ProtoRuntime } from "./private/proto-runtime"; +import type { FieldListSource } from "./private/type"; +import { InternalFieldList } from "./private/field-list"; +import { FieldList } from "./field-list"; +import { AnyMessage, Message } from "./message"; +import { FieldInfo, OneofInfo } from "./field"; +import { EmptyMessage, MessageType } from "./message-type"; +import { collections } from '@kit.ArkTS'; +import { FieldWrapper } from './private/field-wrapper'; + +/** + * Provides functionality for messages defined with the proto2 syntax. + */ +@Sendable +class U extends Message { + constructor(){ + super(); + } + + getType(): MessageType { + throw new Error('Method not implemented'); + } +} + +export const proto2 = makeProtoRuntime( + "proto2", + (fields: FieldListSource): FieldList => { + return new InternalFieldList(fields); + }, + + // TODO merge with proto3 and initExtensionField, also see initPartial, equals, clone + (target: Message): void => { + const memberList: collections.Array | OneofInfo> | undefined = target.getType().getFields().byMember(); + if (memberList === undefined){ + return; + } + for (let i =0; i < memberList?.length; i++) { + const member = memberList.at(i) as object as OneofInfo; + const name = member.localName as string, + t = target as Object as AnyMessage; + if (member.repeated) { + t[name] = []; + continue; + } + switch (member.kind) { + case "oneof": + t[name] = { case: undefined } as caseObj; + break; + case "map": + t[name] = {} as nullObj; + break; + case "scalar": + case "enum": + case "message": + // In contrast to proto3, enum and scalar fields have no intrinsic default value, + // only an optional explicit default value. + // Unlike proto3 intrinsic default values, proto2 explicit default values are not + // set on construction, because they are not omitted on the wire. If we did set + // default values on construction, a deserialize-serialize round-trip would add + // fields to a message. + break; + } + } + }, +); + +interface caseObj{ + case: undefined; +} + +interface nullObj{} \ No newline at end of file diff --git a/protobuf-bitfun/src/proto3.ets b/protobuf-bitfun/src/proto3.ets new file mode 100644 index 0000000..44a3205 --- /dev/null +++ b/protobuf-bitfun/src/proto3.ets @@ -0,0 +1,87 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { makeProtoRuntime, ProtoRuntime } from "./private/proto-runtime"; +import type { FieldListSource } from "./private/type"; +import { InternalFieldList } from "./private/field-list"; +import type { FieldList } from "./field-list"; +import { AnyMessage, Message } from "./message"; +import { FieldInfo, OneofInfo } from "./field"; +import { EmptyMessage, MessageType } from "./message-type"; +import { collections } from '@kit.ArkTS'; +import { scalarZeroValue } from "./private/scalars"; + +@Sendable +class U extends Message { + constructor(){ + super(); + } + + getType(): MessageType { + throw new Error('Method not implemented'); + } +} + +/** + * Provides functionality for messages defined with the proto3 syntax. + */ +export const proto3 = makeProtoRuntime( + "proto3", + (fields: FieldListSource): FieldList => { + return new InternalFieldList(fields); + }, + // TODO merge with proto3 and initExtensionField, also see initPartial, equals, clone + (target: Message): void => { + const memberList: collections.Array | OneofInfo> | undefined = target.getType().getFields().byMember(); + if (memberList === undefined){ + return; + } + for (let i =0; i < memberList?.length; i++) { + const member = memberList.at(i) as object as OneofInfo; + if (member.opt) { + continue; + } + const name = member.localName as string, + t = target as Object as AnyMessage; + if (member.repeated) { + t[name] = []; + continue; + } + switch (member.kind) { + case "oneof": + t[name] = { case: undefined } as caseObj; + break; + case "enum": + t[name] = 0; + break; + case "map": + t[name] = {} as nullObj; + break; + case "scalar": + t[name] = scalarZeroValue(member.T ?? 0, member.L ?? 0); + break; + case "message": + // message fields are always optional in proto3 + break; + } + } + }, +); + + +interface caseObj{ + case: undefined; +} + +interface nullObj{} diff --git a/protobuf-bitfun/src/scalar.ets b/protobuf-bitfun/src/scalar.ets new file mode 100644 index 0000000..401bf85 --- /dev/null +++ b/protobuf-bitfun/src/scalar.ets @@ -0,0 +1,87 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +export const enum ScalarTypeExcept { + INT64 = 3, + UINT64 = 4, + INT32 = 5, + FIXED64 = 6, + FIXED32 = 7, + BOOL = 8, + STRING = 9, + UINT32 = 13, + SFIXED32 = 15, + SFIXED64 = 16, + SINT32 = 17, // Uses ZigZag encoding. + SINT64 = 18, +} + + + + + + +export const enum ScalarType { + DOUBLE = 1, + FLOAT = 2, + INT64 = 3, + UINT64 = 4, + INT32 = 5, + FIXED64 = 6, + FIXED32 = 7, + BOOL = 8, + STRING = 9, + BYTES = 12, + UINT32 = 13, + SFIXED32 = 15, + SFIXED64 = 16, + SINT32 = 17, // Uses ZigZag encoding. + SINT64 = 18, // Uses ZigZag encoding. +} + +/** + * JavaScript representation of fields with 64 bit integral types (int64, uint64, + * sint64, fixed64, sfixed64). + * + * This is a subset of google.protobuf.FieldOptions.JSType, which defines JS_NORMAL, + * JS_STRING, and JS_NUMBER. Protobuf-ES uses BigInt by default, but will use + * String if `[jstype = JS_STRING]` is specified. + * + * ```protobuf + * uint64 field_a = 1; // BigInt + * uint64 field_b = 2 [jstype = JS_NORMAL]; // BigInt + * uint64 field_b = 2 [jstype = JS_NUMBER]; // BigInt + * uint64 field_b = 2 [jstype = JS_STRING]; // String + * ``` + */ +export const enum LongType { + /** + * Use JavaScript BigInt. + */ + BIGINT = 0, + + /** + * Use JavaScript String. + * + * Field option `[jstype = JS_STRING]`. + */ + STRING = 1, +} + +// prettier-ignore +/** + * ScalarValue maps from a scalar field type to a TypeScript value type. + */ +export type ScalarValue = + string | number | bigint | boolean | Uint8Array | never; diff --git a/protobuf-bitfun/src/service-type.ets b/protobuf-bitfun/src/service-type.ets new file mode 100644 index 0000000..c1c3303 --- /dev/null +++ b/protobuf-bitfun/src/service-type.ets @@ -0,0 +1,148 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { Message } from "./message"; +import { MessageType } from "./message-type"; + +/** + * ServiceType represents a protobuf services. It provides metadata for + * reflection-based operations. + */ + +export interface ServiceType , O extends Message>{ + /** + * The fully qualified name of the service. + */ + readonly typeName: string; + + // We do not surface options at this time + // readonly options: OptionsMap; + + /** + * A map of local name (safe to use in ECMAScript) to method. + */ + readonly methods: localName; +} + +interface localName , O extends Message>{ + localName : Record> +} + +/** + * MethodInfo represents a method of a protobuf service, a remote procedure + * call. All methods provide the following properties: + * + * - "name": The original name of the protobuf rpc. + * - "I": The input message type. + * - "O": The output message type. + * - "kind": The method type. + * - "idempotency": User-provided indication whether the method will cause + * the same effect every time it is called. + */ +export type MethodInfo< + I extends Message , + O extends Message , +> = + | MethodInfoUnary + | MethodInfoServerStreaming + | MethodInfoClientStreaming + | MethodInfoBiDiStreaming; + +/** + * A unary method: rpc (Input) returns (Output) + */ +export interface MethodInfoUnary, O extends Message> + extends miShared { + readonly kind: MethodKind.Unary; +} + +/** + * A server streaming method: rpc (Input) returns (stream Output) + */ +export interface MethodInfoServerStreaming< + I extends Message, + O extends Message, +> extends miShared { + readonly kind: MethodKind.ServerStreaming; +} + +/** + * A client streaming method: rpc (stream Input) returns (Output) + */ +export interface MethodInfoClientStreaming< + I extends Message, + O extends Message, +> extends miShared { + readonly kind: MethodKind.ClientStreaming; +} + +/** + * A method that streams bi-directionally: rpc (stream Input) returns (stream Output) + */ +export interface MethodInfoBiDiStreaming< + I extends Message, + O extends Message, +> extends miShared { + readonly kind: MethodKind.BiDiStreaming; +} + +interface miShared< + I extends Message , + O extends Message , +> { + readonly name: string; + readonly I: MessageType; + readonly O: MessageType; + readonly idempotency?: MethodIdempotency; + // We do not surface options at this time + // options: OptionsMap; +} + +/** + * MethodKind represents the four method types that can be declared in + * protobuf with the `stream` keyword: + * + * 1. Unary: rpc (Input) returns (Output) + * 2. ServerStreaming: rpc (Input) returns (stream Output) + * 3. ClientStreaming: rpc (stream Input) returns (Output) + * 4. BiDiStreaming: rpc (stream Input) returns (stream Output) + */ +export enum MethodKind { + Unary, + ServerStreaming, + ClientStreaming, + BiDiStreaming, +} + +/** + * Is this method side-effect-free (or safe in HTTP parlance), or just + * idempotent, or neither? HTTP based RPC implementation may choose GET verb + * for safe methods, and PUT verb for idempotent methods instead of the + * default POST. + * + * This enum matches the protobuf enum google.protobuf.MethodOptions.IdempotencyLevel, + * defined in the well-known type google/protobuf/descriptor.proto, but + * drops UNKNOWN. + */ +export enum MethodIdempotency { + /** + * Idempotent, no side effects. + */ + NoSideEffects = 1, + + /** + * Idempotent, but may have side effects. + */ + Idempotent = 2, +} diff --git a/protobuf-bitfun/src/service/call-options.ets b/protobuf-bitfun/src/service/call-options.ets new file mode 100644 index 0000000..2202b9f --- /dev/null +++ b/protobuf-bitfun/src/service/call-options.ets @@ -0,0 +1,4 @@ +// TODO: Developer could update definition of this interface to add customized logic +export interface CallOptions { + timeouMs?: number +} \ No newline at end of file diff --git a/protobuf-bitfun/src/service/transport.ets b/protobuf-bitfun/src/service/transport.ets new file mode 100644 index 0000000..807abd9 --- /dev/null +++ b/protobuf-bitfun/src/service/transport.ets @@ -0,0 +1,31 @@ +import { CallOptions } from "./call-options"; +import { Message } from "../message"; +import { MessageType } from "../message-type"; + +export interface Transport { + + unary, O extends Message>( + serviceName: string, + methodName: string, + input: Message, + outputType: MessageType, + callOptions?: CallOptions): Promise; + clientStream, O extends Message>( + serviceName: string, + methodName: string, + input: AsyncIterable>, + outputType: MessageType, + callOptions?: CallOptions): Promise; + serverStream, O extends Message>( + serviceName: string, + methodName: string, + input: Message, + outputType: MessageType, + callOptions?: CallOptions): AsyncIterable; + bitStream, O extends Message>( + serviceName: string, + methodName: string, + input: AsyncIterable, + outputType: MessageType, + callOptions?: CallOptions): AsyncIterable; +} \ No newline at end of file diff --git a/protobuf-bitfun/src/type-registry.ets b/protobuf-bitfun/src/type-registry.ets new file mode 100644 index 0000000..e99a603 --- /dev/null +++ b/protobuf-bitfun/src/type-registry.ets @@ -0,0 +1,77 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import type { MessageType } from "./message-type"; +import type { EnumType } from "./enum"; +import type { ServiceType } from "./service-type"; +import { Message } from "./message"; + +/** + * IMessageTypeRegistry provides look-up for message types. + * + * You can conveniently create a registry using the createRegistry() + * function: + * + * ```ts + * import { createRegistry } from "@bufbuild/protobuf"; + * import { MyMessage, MyOtherMessage } from "./gen/my_message_pb.js"; + * + * const reg: IMessageTypeRegistry = createRegistry( + * MyMessage, + * MyOtherMessage, + * ); + * ``` + */ +export interface IMessageTypeRegistry { + /** + * Find a message type by its protobuf type name. + */ + findMessage>(typename: string): MessageType | undefined; +} + +/** + * IEnumTypeRegistry provides look-up for enum types. + */ +export interface IEnumTypeRegistry { + /** + * Find an enum type by its protobuf type name. + */ + findEnum(typeName: string): EnumType | undefined; +} + +/** + * IServiceTypeRegistry provides look-up for service types. + */ +export interface IServiceTypeRegistry { + /** + * Find a service type by its protobuf type name. + */ + findService, O extends Message>(typeName: string): ServiceType | undefined; +} + +/** + * A registry that allows + */ +export interface IMutableRegistry + extends IMessageTypeRegistry, + IEnumTypeRegistry, + IServiceTypeRegistry{ + /** + * Adds the type to the registry. + */ + add, I extends Message, O extends Message> + (type: MessageType | EnumType | ServiceType): void; + + findMessage>(typeName: string): MessageType | undefined; +} diff --git a/protobuf-ets-main/.eslintrc.cjs b/protobuf-ets-main/.eslintrc.cjs new file mode 100644 index 0000000..517df33 --- /dev/null +++ b/protobuf-ets-main/.eslintrc.cjs @@ -0,0 +1,103 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +const { readdirSync, existsSync } = require("fs"); +const { join } = require("path"); + +module.exports = { + env: { + browser: true, + es2021: true, + node: true, + }, + ignorePatterns: [ + "packages/typescript-compat/*/dist/**", + "packages/*/dist/**", + "packages/*/.tmp/**", + "node_modules/**", + ], + plugins: ["@typescript-eslint", "n", "import"], + // Rules and settings that do not require a non-default parser + extends: ["eslint:recommended"], + rules: { + "no-console": "error", + "import/no-duplicates": "off", + }, + settings: {}, + overrides: [ + ...readdirSync("packages", { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => join("packages", entry.name)) + .filter((dir) => existsSync(join(dir, "tsconfig.json"))) + .map((dir) => { + return { + files: [join(dir, "src/**/*.ts")], + parser: "@typescript-eslint/parser", + parserOptions: { + project: "./tsconfig.json", + tsconfigRootDir: dir, + }, + settings: { + "import/resolver": { + typescript: { + project: "packages/*/tsconfig.json", + }, + }, + }, + extends: [ + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "plugin:import/recommended", + "plugin:import/typescript", + ], + rules: { + "@typescript-eslint/strict-boolean-expressions": "error", + "@typescript-eslint/no-unnecessary-condition": "error", + "@typescript-eslint/array-type": "off", // we use complex typings, where Array is actually more readable than T[] + "@typescript-eslint/switch-exhaustiveness-check": "error", + "@typescript-eslint/prefer-nullish-coalescing": "error", + "@typescript-eslint/no-unnecessary-boolean-literal-compare": + "error", + "@typescript-eslint/no-invalid-void-type": "error", + "@typescript-eslint/no-base-to-string": "error", + "import/no-cycle": "error", + "import/no-duplicates": "off", + }, + }; + }), + // For scripts and configurations, use Node.js rules + { + files: ["**/*.{js,mjs,cjs}"], + parserOptions: { + ecmaVersion: 13, // ES2022 - https://eslint.org/docs/latest/use/configure/language-options#specifying-environments + }, + extends: ["eslint:recommended", "plugin:n/recommended"], + rules: { + "n/hashbang": "off", + "n/prefer-global/process": "off", + "n/no-process-exit": "off", + "n/exports-style": ["error", "module.exports"], + "n/file-extension-in-import": ["error", "always"], + "n/prefer-global/buffer": ["error", "always"], + "n/prefer-global/console": ["error", "always"], + "n/prefer-global/url-search-params": ["error", "always"], + "n/prefer-global/url": ["error", "always"], + "n/prefer-promises/dns": "error", + "n/prefer-promises/fs": "error", + "n/no-unsupported-features/node-builtins": "error", + "n/no-unsupported-features/es-syntax": "error", + }, + }, + ], +}; diff --git a/protobuf-ets-main/.gitattributes b/protobuf-ets-main/.gitattributes new file mode 100644 index 0000000..fe6ad60 --- /dev/null +++ b/protobuf-ets-main/.gitattributes @@ -0,0 +1,9 @@ +# This is similar to the git option core.autocrlf but it applies to all +# users of the repository and therefore doesn't depend on a developers +# local configuration. +* text=auto + +# Ignore generated files in GitHub diffs by default +**/*_pb.ts linguist-generated=true +**/*_pb.js linguist-generated=true +**/*_pb.d.ts linguist-generated=true diff --git a/protobuf-ets-main/.github/CODE_OF_CONDUCT.md b/protobuf-ets-main/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..1900eb4 --- /dev/null +++ b/protobuf-ets-main/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +conduct@buf.build. All complaints will be reviewed and investigated promptly +and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations + diff --git a/protobuf-ets-main/.github/CONTRIBUTING.md b/protobuf-ets-main/.github/CONTRIBUTING.md new file mode 100644 index 0000000..0ca22a1 --- /dev/null +++ b/protobuf-ets-main/.github/CONTRIBUTING.md @@ -0,0 +1,71 @@ +Contributing +============ + +We'd love your help making `protobuf-es` better! + +If you'd like to add new exported APIs, please [open an issue][open-issue] +describing your proposal — discussing API changes ahead of time makes +pull request review much smoother. In your issue, pull request, and any other +communications, please remember to treat your fellow contributors with +respect! + +Note that you'll need to sign [Buf's Contributor License Agreement][cla] +before we can accept any of your contributions. If necessary, a bot will remind +you to accept the CLA when you open your pull request. + +## Setup + +[Fork][fork], then clone the repository: + +``` +git clone git@github.com:your_github_username/protobuf-es.git +cd protobuf-es +git remote add upstream https://github.com/bufbuild/protobuf-es.git +git fetch upstream +``` + +Make sure that the tests and the linters pass (you'll need Node.js in the +version specified in .nvmrc, `bash`, and `make`): + +``` +make +``` + + +## Making Changes + +Start by creating a new branch for your changes: + +``` +git checkout main +git fetch upstream +git rebase upstream/main +git checkout -b cool_new_feature +``` + +Make your changes, then ensure that `make` still passes. +When you're satisfied with your changes, push them to your fork. + +``` +git commit -a +git push origin cool_new_feature +``` + +Then use the GitHub UI to open a pull request. + +At this point, you're waiting on us to review your changes. We *try* to respond +to issues and pull requests within a few business days, and we may suggest some +improvements or alternatives. Once your changes are approved, one of the +project maintainers will merge them. + +We're much more likely to approve your changes if you: + +* Add tests for new functionality. +* Write a [good commit message][commit-message]. +* Maintain backward compatibility. + +[fork]: https://github.com/bufbuild/protobuf-es/fork +[open-issue]: https://github.com/bufbuild/protobuf-es/issues/new +[cla]: https://cla-assistant.io/bufbuild/protobuf-es +[commit-message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html + diff --git a/protobuf-ets-main/.github/buf-logo.svg b/protobuf-ets-main/.github/buf-logo.svg new file mode 100644 index 0000000..73ed095 --- /dev/null +++ b/protobuf-ets-main/.github/buf-logo.svg @@ -0,0 +1 @@ + diff --git a/protobuf-ets-main/.github/dependabot.yaml b/protobuf-ets-main/.github/dependabot.yaml new file mode 100644 index 0000000..217e0a0 --- /dev/null +++ b/protobuf-ets-main/.github/dependabot.yaml @@ -0,0 +1,42 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + day: "monday" + timezone: UTC + time: "07:00" + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "monthly" + day: "monday" + timezone: UTC + time: "07:00" + ignore: + # These dependencies are explicitly pinned for usage in the plugin + # transpilation process and should only be updated manually / intentionally. + - dependency-name: "typescript" + - dependency-name: "@typescript/vfs" + open-pull-requests-limit: 50 + groups: + test: + patterns: + - "jest" + - "benchmark" + - "@types/benchmark" + - "long" + bench: + patterns: + - "google-protobuf" + - "brotli" + - "esbuild" + lint-and-format: + patterns: + - "@typescript-eslint/*" + - "eslint-*" + - "eslint" + - "prettier" + - "@bufbuild/license-header" + - "@arethetypeswrong/*" diff --git a/protobuf-ets-main/.github/workflows/add-to-project.yaml b/protobuf-ets-main/.github/workflows/add-to-project.yaml new file mode 100644 index 0000000..e3c0b42 --- /dev/null +++ b/protobuf-ets-main/.github/workflows/add-to-project.yaml @@ -0,0 +1,21 @@ +name: Add issues and PRs to project + +on: + issues: + types: + - opened + - reopened + - transferred + pull_request_target: + types: + - opened + - reopened + issue_comment: + types: + - created + +jobs: + call-workflow-add-to-project: + name: Call workflow to add issue to project + uses: bufbuild/base-workflows/.github/workflows/add-to-project.yaml@main + secrets: inherit diff --git a/protobuf-ets-main/.github/workflows/ci.yaml b/protobuf-ets-main/.github/workflows/ci.yaml new file mode 100644 index 0000000..b447a94 --- /dev/null +++ b/protobuf-ets-main/.github/workflows/ci.yaml @@ -0,0 +1,47 @@ +name: ci +on: + push: + branches: [main, v2] + tags: ['v*'] + pull_request: + branches: [main, v2] + workflow_dispatch: +jobs: + ci: + runs-on: ubuntu-20.04 + strategy: + matrix: + node-version: [18, 20] + steps: + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - name: checkout + uses: actions/checkout@v4 + - name: Cache + uses: actions/cache@v4 + with: + path: | + ~/.tmp + .tmp + key: ${{ runner.os }}-protobuf-es-ci-${{ hashFiles('Makefile') }} + restore-keys: | + ${{ runner.os }}-protobuf-es-ci- + - name: make + run: make ci + typescript-compat: + runs-on: ubuntu-20.04 + steps: + - name: checkout + uses: actions/checkout@v4 + - name: Cache + uses: actions/cache@v4 + with: + path: | + ~/.tmp + .tmp + key: ${{ runner.os }}-protobuf-es-typescript-compat-${{ hashFiles('Makefile') }} + restore-keys: | + ${{ runner.os }}-protobuf-es-typescript-compat- + - name: make + run: make test-ts-compat diff --git a/protobuf-ets-main/.github/workflows/emergency-review-bypass.yaml b/protobuf-ets-main/.github/workflows/emergency-review-bypass.yaml new file mode 100644 index 0000000..3d0b436 --- /dev/null +++ b/protobuf-ets-main/.github/workflows/emergency-review-bypass.yaml @@ -0,0 +1,12 @@ +name: Bypass review in case of emergency +on: + pull_request: + types: + - labeled +permissions: + pull-requests: write +jobs: + approve: + if: github.event.label.name == 'Emergency Bypass Review' + uses: bufbuild/base-workflows/.github/workflows/emergency-review-bypass.yaml@main + secrets: inherit diff --git a/protobuf-ets-main/.github/workflows/notify-approval-bypass.yaml b/protobuf-ets-main/.github/workflows/notify-approval-bypass.yaml new file mode 100644 index 0000000..0683ed8 --- /dev/null +++ b/protobuf-ets-main/.github/workflows/notify-approval-bypass.yaml @@ -0,0 +1,12 @@ +name: PR Approval Bypass Notifier +on: + pull_request: + types: + - closed + branches: [main, v2] +permissions: + pull-requests: read +jobs: + approval: + uses: bufbuild/base-workflows/.github/workflows/notify-approval-bypass.yaml@main + secrets: inherit diff --git a/protobuf-ets-main/.github/workflows/pr-title.yaml b/protobuf-ets-main/.github/workflows/pr-title.yaml new file mode 100644 index 0000000..b114603 --- /dev/null +++ b/protobuf-ets-main/.github/workflows/pr-title.yaml @@ -0,0 +1,18 @@ +name: Lint PR Title +# Prevent writing to the repository using the CI token. +# Ref: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions +permissions: + pull-requests: read +on: + pull_request: + # By default, a workflow only runs when a pull_request's activity type is opened, + # synchronize, or reopened. We explicity override here so that PR titles are + # re-linted when the PR text content is edited. + types: + - opened + - edited + - reopened + - synchronize +jobs: + lint: + uses: bufbuild/base-workflows/.github/workflows/pr-title.yaml@main diff --git a/protobuf-ets-main/.nvmrc b/protobuf-ets-main/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/protobuf-ets-main/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/protobuf-ets-main/.prettierignore b/protobuf-ets-main/.prettierignore new file mode 100644 index 0000000..9b3c1f9 --- /dev/null +++ b/protobuf-ets-main/.prettierignore @@ -0,0 +1,6 @@ +/node_modules +/packages/*/.tmp +/packages/*/dist +/packages/*/src/gen +/packages/protobuf/src/google +/.tmp diff --git a/protobuf-ets-main/Makefile b/protobuf-ets-main/Makefile new file mode 100644 index 0000000..a614d89 --- /dev/null +++ b/protobuf-ets-main/Makefile @@ -0,0 +1,191 @@ +# See https://tech.davis-hansson.com/p/make/ +SHELL := bash +.DELETE_ON_ERROR: +.SHELLFLAGS := -eu -o pipefail -c +.DEFAULT_GOAL := all +MAKEFLAGS += --warn-undefined-variables +MAKEFLAGS += --no-builtin-rules +MAKEFLAGS += --no-print-directory +TMP = .tmp +BIN = .tmp/bin +BUILD = .tmp/build +GEN = .tmp/gen + +node_modules: package-lock.json + npm ci + +$(BUILD)/upstream-protobuf: node_modules packages/upstream-protobuf/version.txt $(shell find packages/upstream-protobuf -name '*.mjs' -not -path "*/node_modules/*") + @mkdir -p $(@D) + npm run -w packages/upstream-protobuf build + @touch $(@) + +$(BUILD)/protobuf: node_modules tsconfig.base.json packages/protobuf/tsconfig.json $(shell find packages/protobuf/src -name '*.ts') + npm run -w packages/protobuf clean + npm run -w packages/protobuf build + @mkdir -p $(@D) + @touch $(@) + +$(BUILD)/protobuf-test: $(BUILD)/protobuf $(GEN)/protobuf-test node_modules tsconfig.base.json packages/protobuf-test/tsconfig.json $(shell find packages/protobuf-test/src -name '*.ts') + npm run -w packages/protobuf-test clean + npm run -w packages/protobuf-test build + @mkdir -p $(@D) + @touch $(@) + +$(BUILD)/protoplugin: $(BUILD)/protobuf node_modules tsconfig.base.json packages/protoplugin/tsconfig.json $(shell find packages/protoplugin/src -name '*.ts') + npm run -w packages/protoplugin clean + npm run -w packages/protoplugin build + @mkdir -p $(@D) + @touch $(@) + +$(BUILD)/protoplugin-test: $(BUILD)/protoplugin $(GEN)/protoplugin-test $(BUILD)/upstream-protobuf node_modules tsconfig.base.json packages/protoplugin-test/tsconfig.json $(shell find packages/protoplugin-test/src -name '*.ts') + npm run -w packages/protoplugin-test clean + npm run -w packages/protoplugin-test build + @mkdir -p $(@D) + @touch $(@) + +$(BUILD)/protoplugin-example: $(BUILD)/protoc-gen-ets packages/protoplugin-example/buf.gen.yaml node_modules tsconfig.base.json packages/protoplugin-example/tsconfig.json $(shell find packages/protoplugin-example/src -name '*.ts') + npm run -w packages/protoplugin-example generate + npm run -w packages/protoplugin-example build + @mkdir -p $(@D) + @touch $(@) + +$(BUILD)/protoc-gen-ets: $(BUILD)/protoplugin node_modules tsconfig.base.json packages/protoc-gen-ets/tsconfig.json $(shell find packages/protoc-gen-ets/src -name '*.ts') + npm run -w packages/protoc-gen-ets clean + npm run -w packages/protoc-gen-ets build + @mkdir -p $(@D) + @touch $(@) + +$(BUILD)/protobuf-conformance: $(GEN)/protobuf-conformance node_modules tsconfig.base.json packages/protobuf-conformance $(shell find packages/protobuf-conformance/src -name '*.ts') + npm run -w packages/protobuf-conformance clean + npm run -w packages/protobuf-conformance build + @mkdir -p $(@D) + @touch $(@) + +$(BUILD)/protobuf-example: $(BUILD)/protobuf node_modules tsconfig.base.json packages/protobuf-example/tsconfig.json $(shell find packages/protobuf-example/src -name '*.ts') + npm run -w packages/protobuf-example build + @mkdir -p $(@D) + @touch $(@) + +$(GEN)/protobuf-test: $(BUILD)/upstream-protobuf $(BUILD)/protoc-gen-ets $(shell find packages/protobuf-test/extra -name '*.proto') + npm run -w packages/protobuf-test generate + @mkdir -p $(@D) + @touch $(@) + +$(GEN)/protoplugin-test: $(BUILD)/protoc-gen-ets $(shell find packages/protoplugin-test/proto -name '*.proto') + @rm -rf packages/protoplugin-test/src/gen/* packages/protoplugin-test/descriptorset.binpb + @npm run -w packages/protoplugin-test generate + @mkdir -p $(@D) + @touch $(@) + +$(GEN)/protobuf-conformance: $(BUILD)/upstream-protobuf $(BUILD)/protoc-gen-ets + npm run -w packages/protobuf-conformance generate + @mkdir -p $(@D) + @touch $(@) + +$(GEN)/protobuf-example: $(BUILD)/protoc-gen-ets packages/protobuf-example/buf.gen.yaml $(shell find packages/protobuf-example -name '*.proto') + npm run -w packages/protobuf-example generate + @mkdir -p $(@D) + @touch $(@) + +$(GEN)/bundle-size: $(BUILD)/protoplugin $(BUILD)/protoc-gen-ets $(shell find packages/bundle-size/src -name '*.ts' -maxdepth 1) packages/bundle-size/buf.gen.yaml Makefile + npm run -w packages/bundle-size generate + @mkdir -p $(@D) + @touch $(@) + + +.PHONY: help +help: ## Describe useful make targets + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "%-30s %s\n", $$1, $$2}' + +.PHONY: all +all: build test format lint bundle-size bootstrap ## build, test, format, lint, bundle-size, and bootstrap (default) + +.PHONY: ci +ci: build test-protobuf test-protoplugin test-protoplugin-example test-conformance format lint bundle-size bootstrap # + $(MAKE) checkdiff + +.PHONY: clean +clean: ## Delete build artifacts and installed dependencies + @# -X only removes untracked files, -d recurses into directories, -f actually removes files/dirs + git clean -Xdf + +.PHONY: build +build: $(BUILD)/protobuf $(BUILD)/protobuf-test $(BUILD)/protoplugin $(BUILD)/protoplugin-test $(BUILD)/protobuf-conformance $(BUILD)/protoc-gen-ets $(BUILD)/protobuf-example $(BUILD)/protoplugin-example ## Build + +.PHONY: test +test: test-protobuf test-protoplugin test-protoplugin-example test-conformance test-ts-compat ## Run all tests + +.PHONY: test-protobuf +test-protobuf: $(BUILD)/protobuf-test packages/protobuf-test/jest.config.js + npm run -w packages/protobuf-test test + +.PHONY: test-protoplugin +test-protoplugin: $(BUILD)/protoplugin-test packages/protoplugin-test/jest.config.js + npm run -w packages/protoplugin-test test + +.PHONY: test-protoplugin-example +test-protoplugin-example: $(BUILD)/protoplugin-example + npm run -w packages/protoplugin-example test + +.PHONY: test-conformance +test-conformance: $(BUILD)/upstream-protobuf $(BUILD)/protobuf-conformance + npm run -w packages/protobuf-conformance test + +.PHONY: test-ts-compat +test-ts-compat: $(GEN)/protobuf-test $(BUILD)/protobuf node_modules + node packages/typescript-compat/typescript-compat.mjs + +.PHONY: lint +lint: node_modules $(BUILD)/protobuf $(BUILD)/protobuf-test $(BUILD)/protobuf-conformance $(BUILD)/protoplugin $(GEN)/bundle-size $(GEN)/protobuf-example ## Lint all files + npx eslint --max-warnings 0 . + @# Check type exports on npm packages to verify they're correct + npm run -w packages/protobuf attw + npm run -w packages/protoplugin attw + +.PHONY: format +format: node_modules ## Format all files, adding license headers + npx prettier --write '**/*.{json,js,jsx,ts,tsx,css,mjs,cjs}' --log-level error + npx license-header --ignore packages/protobuf/src/google/varint.ts + +.PHONY: bundle-size +bundle-size: node_modules $(GEN)/bundle-size $(BUILD)/protobuf ## Benchmark bundle-size + npm run -w packages/bundle-size report + +.PHONY: perf +perf: $(BUILD)/protobuf-test + npm run -w packages/protobuf-test perf + +.PHONY: flamegraph +flamegraph: $(BUILD)/protobuf-test + npm run -w packages/protobuf-test flamegraph + +.PHONY: bootstrap +bootstrap: $(BUILD)/upstream-protobuf $(BUILD)/protoc-gen-ets node_modules ## Bootstrap well-known types and edition features-set defaults in @bufbuild/protobuf from upstream protobuf + npm run -w packages/protobuf bootstrap:wkt + npm run -w packages/protobuf bootstrap:featureset-defaults + +.PHONY: setversion +setversion: ## Set a new version in for the project, i.e. make setversion SET_VERSION=1.2.3 + node scripts/set-workspace-version.js $(SET_VERSION) + npm ci + $(MAKE) all + +# Recommended procedure: +# 1. Set a new version with the target `setversion` +# 2. Commit and push all changes +# 3. Login with `npm login` +# 4. Run this target, publishing to npmjs.com +# 5. Tag the release +.PHONY: release +release: all ## Release @bufbuild/protobuf + @[ -z "$(shell git status --short)" ] || (echo "Uncommitted changes found." && exit 1); + npm publish \ + --tag $(shell node scripts/get-workspace-publish-tag.js || kill $$PPID;) \ + --workspace packages/protobuf \ + --workspace packages/protoplugin \ + --workspace packages/protoc-gen-ets + +.PHONY: checkdiff +checkdiff: + @# Used in CI to verify that `make` doesn't produce a diff + test -z "$$(git status --porcelain | tee /dev/stderr)" diff --git a/protobuf-ets-main/README.md b/protobuf-ets-main/README.md new file mode 100644 index 0000000..cfe2426 --- /dev/null +++ b/protobuf-ets-main/README.md @@ -0,0 +1,80 @@ +# Protobuf-EtS + +```proto +message User { + string first_name = 1; + string last_name = 2; + bool active = 3; + User manager = 4; + repeated string locations = 5; + map projects = 6; +} +``` +## Quickstart + +### 离线引入方式 +1. 生成tgz包 + ```bash + cd protoc-gen-ets + npm run pack + ``` + ```bash + cd protoc-gen-connect-ets + npm run pack + ``` +2. 如何在自己的项目中使用? + ```bash + npm install @bufbuild/buf @bufbuild/protobuf @bufbuild/protoplugin + npm install $path/bufbuild-protoc-gen-ets-1.0.0.tgz + npm install $path/bufbuild-protoc-gen-connect-ets-1.0.0.tgz + ``` +3. 创建一个 `buf.gen.yaml` 文件: + + ```yaml + version: v1 + plugins: + - plugin: ets + opt: target=ts + out: src/gen + #如果需要生成server,加上配置项: + - plugin: connect-ets + opt: target=ts + out: src/gen + ``` +4. 生成您的代码: + ```bash + npx buf generate + ``` + +### 镜像仓库引入 +1. publish到仓库 + ```bash + cd protoc-gen-ets + npm publish + ``` + ```bash + cd protoc-gen-connect-ets + npm publish + ``` +2. 如何在自己的项目中使用? + ```bash + npm install @bufbuild/buf @bufbuild/protobuf @bufbuild/protoplugin + npm install @bufbuild/protoc-gen-ets @bufbuild/protoc-gen-connect-ets + ``` +3. 创建一个 `buf.gen.yaml` 文件: + + ```yaml + version: v1 + plugins: + - plugin: ets + opt: target=ts + out: src/gen + #如果需要生成server,加上配置项: + - plugin: connect-ets + opt: target=ts + out: src/gen + ``` +4. 生成您的代码: + ```bash + npx buf generate + ``` \ No newline at end of file diff --git a/protobuf-ets-main/buf.gen.yaml b/protobuf-ets-main/buf.gen.yaml new file mode 100644 index 0000000..8aef503 --- /dev/null +++ b/protobuf-ets-main/buf.gen.yaml @@ -0,0 +1,6 @@ +version: v1 +plugins: + - plugin: ets + out: gen + opt: + - target=ts \ No newline at end of file diff --git a/protobuf-ets-main/package.json b/protobuf-ets-main/package.json new file mode 100644 index 0000000..87e5bfa --- /dev/null +++ b/protobuf-ets-main/package.json @@ -0,0 +1,32 @@ +{ + "private": true, + "type": "module", + "engineStrict": true, + "engines": { + "node": ">=16", + "npm": ">=8" + }, + "devDependencies": { + "@arethetypeswrong/cli": "^0.15.3", + "@bufbuild/license-header": "^0.0.4", + "@types/node": "^20.11.19", + "@typescript-eslint/eslint-plugin": "^7.11.0", + "@typescript-eslint/parser": "^7.11.0", + "eslint": "^8.57.0", + "eslint-import-resolver-typescript": "^3.6.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^17.4.0", + "jest": "^29.7.0", + "prettier": "^3.2.4", + "typescript": "^5.3.3" + }, + "//": "avoid hoisting, see packages/protoplugin/src/ecmascript/transpile.ts", + "dependencies": { + "@bufbuild/buf": "^1.10.0", + "@bufbuild/protobuf": "^1.10.0", + "@bufbuild/protoplugin": "^1.10.0", + "@typescript/vfs": "1.0.0", + "@types/node": "^20.14.11", + "unbuild": "^2.0.0" + } +} diff --git a/protobuf-ets-main/protoc-gen-connect-ets/bin/protoc-gen-connect-ets b/protobuf-ets-main/protoc-gen-connect-ets/bin/protoc-gen-connect-ets new file mode 100644 index 0000000..44f75e1 --- /dev/null +++ b/protobuf-ets-main/protoc-gen-connect-ets/bin/protoc-gen-connect-ets @@ -0,0 +1,6 @@ +#!/usr/bin/env node + +const {runNodeJs} = require("@bufbuild/protoplugin"); +const {protocGenConnectEts} = require("../dist/cjs/src/protoc-gen-connect-ets-plugin.js"); + +runNodeJs(protocGenConnectEts); diff --git a/protobuf-ets-main/protoc-gen-connect-ets/package.json b/protobuf-ets-main/protoc-gen-connect-ets/package.json new file mode 100644 index 0000000..f99dbaf --- /dev/null +++ b/protobuf-ets-main/protoc-gen-connect-ets/package.json @@ -0,0 +1,38 @@ +{ + "name": "@bufbuild/protoc-gen-connect-ets", + "version": "1.0.0", + "repository": { + "type": "gitee", + "url": "https://gitee.com/BitFun4HarmonyOS/bitfunprototransformer.git", + "directory": "packages/protoc-gen-connect-ets" + }, + "bin": { + "protoc-gen-connect-ets": "bin/protoc-gen-connect-ets" + }, + "engines": { + "node": ">=18" + }, + "scripts": { + "clean": "rm -rf ./dist/cjs/*", + "build": "tsc --project tsconfig.json --outDir ./dist/cjs", + "pack": "npm i && npm run build && npm pack", + "prepublishOnly": "npm i && npm run build" + }, + "preferUnplugged": true, + "dependencies": { + "@bufbuild/buf": "^1.10.0", + "@bufbuild/protobuf": "^1.10.0", + "@bufbuild/protoplugin": "^1.10.0", + "@typescript/vfs": "1.0.0", + "@types/node": "^20.14.11", + "unbuild": "^2.0.0" + }, + "peerDependencies": { + "@bufbuild/protobuf": "1.10.0" + }, + "peerDependenciesMeta": { + "@bufbuild/protobuf": { + "optional": true + } + } +} diff --git a/protobuf-ets-main/protoc-gen-connect-ets/src/protoc-gen-connect-ets-plugin.ts b/protobuf-ets-main/protoc-gen-connect-ets/src/protoc-gen-connect-ets-plugin.ts new file mode 100644 index 0000000..5f0e3c2 --- /dev/null +++ b/protobuf-ets-main/protoc-gen-connect-ets/src/protoc-gen-connect-ets-plugin.ts @@ -0,0 +1,23 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { createEcmaScriptPlugin } from "@bufbuild/protoplugin"; +import { generateEts } from "./typescript.js"; +import { version } from "../package.json"; + +export const protocGenConnectEts = createEcmaScriptPlugin({ + name: "protoc-gen-connect-ets", + version: `v${String(version)}`, + generateTs: generateEts, +}); diff --git a/protobuf-ets-main/protoc-gen-connect-ets/src/runtime-import/names.ts b/protobuf-ets-main/protoc-gen-connect-ets/src/runtime-import/names.ts new file mode 100644 index 0000000..7fa72a3 --- /dev/null +++ b/protobuf-ets-main/protoc-gen-connect-ets/src/runtime-import/names.ts @@ -0,0 +1,312 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { + DescEnum, + DescEnumValue, + DescExtension, + DescField, + DescMessage, DescMethod, + DescOneof, + DescService, +} from "@bufbuild/protobuf"; +/** + * Returns the name of a protobuf element in generated code. + * + * Field names - including oneofs - are converted to lowerCamelCase. For + * messages, enumerations and services, the package name is stripped from + * the type name. For nested messages and enumerations, the names are joined + * with an underscore. For methods, the first character is made lowercase. + */ +export function localName( + desc: + | DescEnum + | DescEnumValue + | DescMessage + | DescExtension + | DescOneof + | DescField + | DescService + | DescMethod, +): string { + switch (desc.kind) { + case "field": + return localFieldName(desc.name, desc.oneof !== undefined); + case "oneof": + return localOneofName(desc.name); + case "enum": + case "message": + case "service": + case "extension": { + const pkg = desc.file.proto.package; + const offset = pkg === undefined ? 0 : pkg.length + 1; + const name = desc.typeName.substring(offset).replace(/\./g, "_"); + // For services, we only care about safe identifiers, not safe object properties, + // but we have shipped v1 with a bug that respected object properties, and we + // do not want to introduce a breaking change, so we continue to escape for + // safe object properties. + // See https://github.com/bufbuild/protobuf-es/pull/391 + return safeObjectProperty(safeIdentifier(name)); + } + case "enum_value": { + let name = desc.name; + const sharedPrefix = desc.parent.sharedPrefix; + if (sharedPrefix !== undefined) { + name = name.substring(sharedPrefix.length); + } + return safeObjectProperty(name); + } + case "rpc": { + let name = desc.name; + if (name.length == 0) { + return name; + } + name = name[0].toLowerCase() + name.substring(1); + return safeObjectProperty(name); + } + } +} + +/** + * Returns the name of a field in generated code. + */ +export function localFieldName(protoName: string, inOneof: boolean) { + const name = protoCamelCase(protoName); + if (inOneof) { + // oneof member names are not properties, but values of the `case` property. + return name; + } + return safeObjectProperty(safeMessageProperty(name)); +} + +/** + * Returns the name of a oneof group in generated code. + */ +export function localOneofName(protoName: string): string { + return localFieldName(protoName, false); +} + +/** + * Returns the JSON name for a protobuf field, exactly like protoc does. + */ +export const fieldJsonName = protoCamelCase; + +/** + * Finds a prefix shared by enum values, for example `MY_ENUM_` for + * `enum MyEnum {MY_ENUM_A=0; MY_ENUM_B=1;}`. + */ +export function findEnumSharedPrefix( + enumName: string, + valueNames: string[], +): string | undefined { + const prefix = camelToSnakeCase(enumName) + "_"; + for (const name of valueNames) { + if (!name.toLowerCase().startsWith(prefix)) { + return undefined; + } + const shortName = name.substring(prefix.length); + if (shortName.length == 0) { + return undefined; + } + if (/^\d/.test(shortName)) { + // identifiers must not start with numbers + return undefined; + } + } + return prefix; +} + +/** + * Converts lowerCamelCase or UpperCamelCase into lower_snake_case. + * This is used to find shared prefixes in an enum. + */ +function camelToSnakeCase(camel: string): string { + return ( + camel.substring(0, 1) + camel.substring(1).replace(/[A-Z]/g, (c) => "_" + c) + ).toLowerCase(); +} + +/** + * Converts snake_case to protoCamelCase according to the convention + * used by protoc to convert a field name to a JSON name. + */ +function protoCamelCase(snakeCase: string): string { + let capNext = false; + const b = []; + for (let i = 0; i < snakeCase.length; i++) { + let c = snakeCase.charAt(i); + switch (c) { + case "_": + capNext = true; + break; + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + case "9": + b.push(c); + capNext = false; + break; + default: + if (capNext) { + capNext = false; + c = c.toUpperCase(); + } + b.push(c); + break; + } + } + return b.join(""); +} + +/** + * Names that cannot be used for identifiers, such as class names, + * but _can_ be used for object properties. + */ +const reservedIdentifiers = new Set([ + // ECMAScript 2015 keywords + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + "yield", + + // ECMAScript 2015 future reserved keywords + "enum", + "implements", + "interface", + "let", + "package", + "private", + "protected", + "public", + "static", + + // Class name cannot be 'Object' when targeting ES5 with module CommonJS + "Object", + + // TypeScript keywords that cannot be used for types (as opposed to variables) + "bigint", + "number", + "boolean", + "string", + "object", + + // Identifiers reserved for the runtime, so we can generate legible code + "globalThis", + "Uint8Array", + "Partial", +]); + +/** + * Names that cannot be used for object properties because they are reserved + * by built-in JavaScript properties. + */ +const reservedObjectProperties = new Set([ + // names reserved by JavaScript + "constructor", + "toString", + "toJSON", + "valueOf", +]); + +/** + * Names that cannot be used for object properties because they are reserved + * by the runtime. + */ +const reservedMessageProperties = new Set([ + // names reserved by the runtime + "getType", + "clone", + "equals", + "fromBinary", + "fromJson", + "fromJsonString", + "toBinary", + "toJson", + "toJsonString", + + // names reserved by the runtime for the future + "toObject", +]); + +const fallback = (name: T) => `${name}$` as const; + +/** + * Will wrap names that are Object prototype properties or names reserved + * for `Message`s. + */ +const safeMessageProperty = (name: string): string => { + if (reservedMessageProperties.has(name)) { + return fallback(name); + } + return name; +}; + +/** + * Names that cannot be used for object properties because they are reserved + * by built-in JavaScript properties. + */ +export const safeObjectProperty = (name: string): string => { + if (reservedObjectProperties.has(name)) { + return fallback(name); + } + return name; +}; + +/** + * Names that can be used for identifiers or class properties + */ +export const safeIdentifier = (name: string): string => { + if (reservedIdentifiers.has(name)) { + return fallback(name); + } + return name; +}; diff --git a/protobuf-ets-main/protoc-gen-connect-ets/src/runtime-import/rumtime-imports.ts b/protobuf-ets-main/protoc-gen-connect-ets/src/runtime-import/rumtime-imports.ts new file mode 100644 index 0000000..b2996e9 --- /dev/null +++ b/protobuf-ets-main/protoc-gen-connect-ets/src/runtime-import/rumtime-imports.ts @@ -0,0 +1,302 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { + DescEnum, + DescEnumValue, + DescExtension, + DescField, + DescMessage, DescMethod, + DescOneof, + DescService, LongType, ScalarType, ScalarValue +} from "@bufbuild/protobuf"; +import {reifyWkt} from "@bufbuild/protoplugin/ecmascript"; +import {localName, safeIdentifier, safeObjectProperty} from "./names"; +import {getUnwrappedFieldType, scalarZeroValue} from "./scalars"; + +export interface RuntimeImports { + proto2: ImportSymbol; + proto3: ImportSymbol; + Message: ImportSymbol; + FieldList: ImportSymbol; + MessageType: ImportSymbol; + BinaryReadOptions: ImportSymbol; + BinaryWriteOptions: ImportSymbol; + JsonReadOptions: ImportSymbol; + JsonWriteOptions: ImportSymbol; + JsonValue: ImportSymbol; + protoInt64: ImportSymbol; + ScalarType: ImportSymbol; + EnumObject: ImportSymbol; + FieldWrapper: ImportSymbol; + makeEnumTypeInstance: ImportSymbol; + EnumTypeInstance: ImportSymbol; + EnumValueInfoInstance: ImportSymbol; + getFieldListsFromArray: ImportSymbol; + InternalOneofInfo: ImportSymbol; + ProtoRuntime: ImportSymbol; + Transport: ImportSymbol; + CallOptions: ImportSymbol; + WireType: ImportSymbol; + EnumType: ImportSymbol; + EnumValueInfo: ImportSymbol; + FieldInfo: ImportSymbol; + FieldValueInfo: ImportSymbol; + fiMapInterface: ImportSymbol; + OneofClass: ImportSymbol; + EmptyMessage: ImportSymbol; +} + +export function createRuntimeImports(): RuntimeImports { + // prettier-ignore + return { + proto2: infoToSymbol("proto2"), + proto3: infoToSymbol("proto3"), + Message: infoToSymbol("Message"), + FieldList: infoToSymbol("FieldList"), + MessageType: infoToSymbol("MessageType"), + BinaryReadOptions: infoToSymbol("BinaryReadOptions"), + BinaryWriteOptions: infoToSymbol("BinaryWriteOptions"), + JsonReadOptions: infoToSymbol("JsonReadOptions"), + JsonWriteOptions: infoToSymbol("JsonWriteOptions"), + JsonValue: infoToSymbol("JsonValue"), + protoInt64: infoToSymbol("protoInt64"), + ScalarType: infoToSymbol("ScalarType"), + EnumObject: infoToSymbol("EnumObject"), + FieldWrapper: infoToSymbol("FieldWrapper"), + makeEnumTypeInstance: infoToSymbol("makeEnumTypeInstance"), + EnumTypeInstance: infoToSymbol("EnumTypeInstance"), + EnumValueInfoInstance: infoToSymbol("EnumValueInfoInstance"), + getFieldListsFromArray: infoToSymbol("getFieldListsFromArray"), + InternalOneofInfo: infoToSymbol("InternalOneofInfo"), + ProtoRuntime: infoToSymbol("ProtoRuntime"), + Transport: infoToSymbol("Transport"), + CallOptions: infoToSymbol("CallOptions"), + WireType: infoToSymbol("WireType"), + EnumType: infoToSymbol("EnumType"), + EnumValueInfo: infoToSymbol("EnumValueInfo"), + FieldInfo: infoToSymbol("FieldInfo"), + FieldValueInfo: infoToSymbol("FieldValueInfo"), + fiMapInterface: infoToSymbol("fiMapInterface"), + OneofClass: infoToSymbol("OneofClass"), + EmptyMessage: infoToSymbol("EmptyMessage"), + }; +} + +function infoToSymbol( + name: keyof typeof codegenInfo.symbols, +): ImportSymbol { + const info = codegenInfo.symbols[name]; + const symbol = createImportSymbol( + name, + info.publicImportPath, + ); + return info.typeOnly ? symbol.toTypeOnly() : symbol; +} + +export type ImportSymbol = { + readonly kind: "es_symbol"; + + /** + * The name to import. + */ + readonly name: string; + + /** + * The import path. + * + * The path can point to a package, for example `@foo/bar/baz.js`, or + * to a file, for example `./bar/baz.js`. + * + * Note that while paths to a file begin with a `./`, they must be + * relative to the project root. + */ + readonly from: string; + + /** + * Whether this is a type-only import - an import that only exists in + * TypeScript. + */ + readonly typeOnly: boolean; + + /** + * Create a copy of this import, and make it type-only for TypeScript. + */ + toTypeOnly(): ImportSymbol; + + /** + * The unique ID based on name and from, disregarding typeOnly. + */ + readonly id: EsSymbolId; +}; + + +function createImportSymbol( + name: string, + from: string, + typeOnly?: boolean, +): ImportSymbol { + const id = `import("${from}").${name}`; + const s: ImportSymbol = { + kind: "es_symbol", + name, + from, + typeOnly: false, + id, + toTypeOnly() { + return { + ...this, + typeOnly: true, + }; + }, + }; + return typeOnly === true ? s.toTypeOnly() : s; +} + +type EsSymbolId = string; + +interface CodegenInfo { + /** + * Name of the runtime library NPM package. + */ + readonly packageName: string; + readonly localName: ( + desc: + | DescEnum + | DescEnumValue + | DescMessage + | DescExtension + | DescOneof + | DescField + | DescService + | DescMethod, + ) => string; + readonly symbols: Record; + readonly getUnwrappedFieldType: ( + field: DescField | DescExtension, + ) => ScalarType | undefined; + readonly wktSourceFiles: readonly string[]; + /** + * @deprecated please use scalarZeroValue instead + */ + readonly scalarDefaultValue: (type: ScalarType, longType: LongType) => any; // eslint-disable-line @typescript-eslint/no-explicit-any + readonly scalarZeroValue: ( + type: T, + longType: L, + ) => ScalarValue; + /** + * @deprecated please use reifyWkt from @bufbuild/protoplugin/ecmascript instead + */ + readonly reifyWkt: typeof reifyWkt; + readonly safeIdentifier: (name: string) => string; + readonly safeObjectProperty: (name: string) => string; +} + +type RuntimeSymbolName = + | "BinaryReadOptions" + | "BinaryWriteOptions" + | "JsonReadOptions" + | "JsonWriteOptions" + | "JsonValue" + | "Message" + | "MessageType" + | "proto2" + | "proto3" + | "protoInt64" + | "ScalarType" + | "EnumObject" + | "FieldWrapper" + | "makeEnumTypeInstance" + | "EnumTypeInstance" + | "EnumValueInfoInstance" + | "getFieldListsFromArray" + | "InternalOneofInfo" + | "ProtoRuntime" + | "Transport" + | "CallOptions" + | 'WireType' + | "EnumType" + | "EnumValueInfo" + | "FieldInfo" + | "FieldValueInfo" + | "fiMapInterface" + | "OneofClass" + | "FieldList" + | 'EmptyMessage' + + +type RuntimeSymbolInfo = { + typeOnly: boolean; + publicImportPath: string; +}; + +const packageName = "@bitfun/protobuf_ets"; + +const codegenInfo: CodegenInfo = { + packageName: "@bitfun/protobuf_ets", + localName, + reifyWkt, + getUnwrappedFieldType, + scalarDefaultValue: scalarZeroValue, + scalarZeroValue, + safeIdentifier, + safeObjectProperty, + // prettier-ignore + symbols: { + proto2: {typeOnly: false, publicImportPath: packageName}, + proto3: {typeOnly: false, publicImportPath: packageName}, + Message: {typeOnly: false, publicImportPath: packageName}, + FieldList: {typeOnly: false, publicImportPath: packageName}, + MessageType: {typeOnly: false, publicImportPath: packageName}, + BinaryReadOptions: {typeOnly: false, publicImportPath: packageName}, + BinaryWriteOptions: {typeOnly: false, publicImportPath: packageName}, + JsonReadOptions: {typeOnly: false, publicImportPath: packageName}, + JsonWriteOptions: {typeOnly: false, publicImportPath: packageName}, + JsonValue: {typeOnly: false, publicImportPath: packageName}, + protoInt64: {typeOnly: false, publicImportPath: packageName}, + ScalarType: {typeOnly: false, publicImportPath: packageName}, + WireType: {typeOnly: false, publicImportPath: packageName}, + EnumType: {typeOnly: false, publicImportPath: packageName}, + EnumValueInfo: {typeOnly: false, publicImportPath: packageName}, + FieldInfo: {typeOnly: false, publicImportPath: packageName}, + FieldValueInfo: {typeOnly: false, publicImportPath: packageName}, + fiMapInterface: {typeOnly: false, publicImportPath: packageName}, + OneofClass: {typeOnly: false, publicImportPath: packageName}, + EmptyMessage: {typeOnly: false, publicImportPath: packageName}, + EnumObject: {typeOnly: false, publicImportPath: packageName}, + FieldWrapper: {typeOnly: false, publicImportPath: packageName}, + makeEnumTypeInstance: {typeOnly: false, publicImportPath: packageName}, + EnumTypeInstance: {typeOnly: false, publicImportPath: packageName}, + EnumValueInfoInstance: {typeOnly: false, publicImportPath: packageName}, + getFieldListsFromArray: {typeOnly: false, publicImportPath: packageName}, + InternalOneofInfo: {typeOnly: false, publicImportPath: packageName}, + ProtoRuntime: {typeOnly: false, publicImportPath: packageName}, + Transport: {typeOnly: false, publicImportPath: packageName}, + CallOptions: {typeOnly: false, publicImportPath: packageName}, + }, + wktSourceFiles: [ + "google/protobuf/compiler/plugin.proto", + "google/protobuf/any.proto", + "google/protobuf/api.proto", + "google/protobuf/descriptor.proto", + "google/protobuf/duration.proto", + "google/protobuf/empty.proto", + "google/protobuf/field_mask.proto", + "google/protobuf/source_context.proto", + "google/protobuf/struct.proto", + "google/protobuf/timestamp.proto", + "google/protobuf/type.proto", + "google/protobuf/wrappers.proto", + ], +}; diff --git a/protobuf-ets-main/protoc-gen-connect-ets/src/runtime-import/scalars.ts b/protobuf-ets-main/protoc-gen-connect-ets/src/runtime-import/scalars.ts new file mode 100644 index 0000000..5d48ece --- /dev/null +++ b/protobuf-ets-main/protoc-gen-connect-ets/src/runtime-import/scalars.ts @@ -0,0 +1,59 @@ +import {DescExtension, DescField, LongType, protoInt64, ScalarType, ScalarValue} from "@bufbuild/protobuf"; + +/** + * Returns the zero value for the given scalar type. + */ +export function scalarZeroValue( + type: T, + longType: L, +): ScalarValue { + switch (type) { + case ScalarType.BOOL: + return false as ScalarValue; + case ScalarType.UINT64: + case ScalarType.FIXED64: + case ScalarType.INT64: + case ScalarType.SFIXED64: + case ScalarType.SINT64: + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison -- acceptable since it's covered by tests + return (longType == 0 ? protoInt64.zero : "0") as ScalarValue; + case ScalarType.DOUBLE: + case ScalarType.FLOAT: + return 0.0 as ScalarValue; + case ScalarType.BYTES: + return new Uint8Array(0) as ScalarValue; + case ScalarType.STRING: + return "" as ScalarValue; + default: + // Handles INT32, UINT32, SINT32, FIXED32, SFIXED32. + // We do not use individual cases to save a few bytes code size. + return 0 as ScalarValue; + } +} + +export function getUnwrappedFieldType( + field: DescField | DescExtension, +): ScalarType | undefined { + if (field.fieldKind !== "message") { + return undefined; + } + if (field.repeated) { + return undefined; + } + if (field.oneof != undefined) { + return undefined; + } + return wktWrapperToScalarType[field.message.typeName]; +} + +const wktWrapperToScalarType: Record = { + "google.protobuf.DoubleValue": ScalarType.DOUBLE, + "google.protobuf.FloatValue": ScalarType.FLOAT, + "google.protobuf.Int64Value": ScalarType.INT64, + "google.protobuf.UInt64Value": ScalarType.UINT64, + "google.protobuf.Int32Value": ScalarType.INT32, + "google.protobuf.UInt32Value": ScalarType.UINT32, + "google.protobuf.BoolValue": ScalarType.BOOL, + "google.protobuf.StringValue": ScalarType.STRING, + "google.protobuf.BytesValue": ScalarType.BYTES, +}; \ No newline at end of file diff --git a/protobuf-ets-main/protoc-gen-connect-ets/src/typescript.ts b/protobuf-ets-main/protoc-gen-connect-ets/src/typescript.ts new file mode 100644 index 0000000..4cc1e5a --- /dev/null +++ b/protobuf-ets-main/protoc-gen-connect-ets/src/typescript.ts @@ -0,0 +1,111 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { codegenInfo, DescService } from "@bufbuild/protobuf"; +import type { + GeneratedFile, + RuntimeImports, + Schema, +} from "@bufbuild/protoplugin/ecmascript"; +import { + getMessageType, + MethodKind, +} from "./util.js"; +import {localName} from "./runtime-import/names"; +import {createRuntimeImports} from "./runtime-import/rumtime-imports"; +import {Printable} from "@bufbuild/protoplugin/dist/esm/ecmascript/generated-file"; + +export const runtime = createRuntimeImports(); + + +export function generateEts(schema: Schema) { + for (const runtimeKey in schema.runtime) { + schema.runtime[runtimeKey as keyof RuntimeImports] = { + ...schema.runtime[runtimeKey as keyof RuntimeImports], + from: "@bitfun/protobuf_ets", + } + } + (codegenInfo as any).packageName = "@bitfun/protobuf_ets"; + + for (const file of schema.files) { + if (file.services.length > 0) { + const f_connect = schema.generateFile(file.name + "_connect.ts"); + for (const service of file.services) { + generateService(schema, f_connect, service); + } + } + } + const infoArr = (schema as any).getFileInfo(); + infoArr.forEach((info: { name: string; content: string }) => { + info.name = info.name.slice(0, -3) + '.ets'; + info.content = info.content.split(/\r?\n/).map(line => { + if (line.startsWith('import') && line.endsWith('_pb.js";')) { + return line.replace('_pb.js', '_pb'); + } + return line; + }).join('\n'); + }); + (schema as any).getFileInfo = () => infoArr; + (codegenInfo as any).packageName = "@bufbuild/protobuf"; +} + +function generateService( + schema: Schema, + f: GeneratedFile, + service: DescService +){ + const { + Transport, + CallOptions, + } = runtime; + f.print(f.exportDecl("class", localName(service)),"{"); + f.print(" readonly typeName: string = ", f.string(service.typeName), ";"); + f.print(); + f.print(" private transport: ", Transport, ";"); + f.print(); + f.print(" constructor(transport: ", Transport, ") {"); + f.print(` this.transport = transport;`); + f.print(` }`); + for (const method of service.methods){ + let input: Printable; + let output: Printable; + let doMethod: Printable; + switch (method.methodKind as MethodKind ) { + case MethodKind.Unary: + input = method.input; + output = ["Promise<", method.output, ">"]; + doMethod = "unary"; + break; + case MethodKind.ServerStreaming: + input = method.input; + output = ["AsyncIterable<", method.output, ">"]; + doMethod = "serverStream"; + break; + case MethodKind.ClientStreaming: + input = ["AsyncIterable<", method.input, ">"]; + output = ["Promise<", method.output, ">"]; + doMethod = "clientStream"; + break; + case MethodKind.BiDiStreaming: + input = ["AsyncIterable<", method.input, ">"]; + output = ["AsyncIterable<", method.output, ">"]; + doMethod = "bitStream"; + } + f.print(); + f.print(" ", localName(method), "(request: ", input, ", callOptions?: ", CallOptions, "): ", output, " {"); + f.print(" return this.transport.", doMethod, `(this.typeName, "`, localName(method), `", request, `, getMessageType(method.output), ".instance, callOptions);") + f.print(" }"); + } + f.print("};"); +} \ No newline at end of file diff --git a/protobuf-ets-main/protoc-gen-connect-ets/src/util.ts b/protobuf-ets-main/protoc-gen-connect-ets/src/util.ts new file mode 100644 index 0000000..eb516fd --- /dev/null +++ b/protobuf-ets-main/protoc-gen-connect-ets/src/util.ts @@ -0,0 +1,30 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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 + + +import {DescEnum, DescMessage} from "@bufbuild/protobuf"; + +export enum MethodKind { + Unary, + ServerStreaming, + ClientStreaming, + BiDiStreaming, +} + +export function getMessageType(message: DescMessage | DescEnum): DescMessage | DescEnum{ + return { + ...message, + name: message.name + "Type_$1", + typeName: message.typeName + "Type_$1", + } +} \ No newline at end of file diff --git a/protobuf-ets-main/protoc-gen-connect-ets/tsconfig.json b/protobuf-ets-main/protoc-gen-connect-ets/tsconfig.json new file mode 100644 index 0000000..649a130 --- /dev/null +++ b/protobuf-ets-main/protoc-gen-connect-ets/tsconfig.json @@ -0,0 +1,12 @@ +{ + "files": ["src/protoc-gen-connect-ets-plugin.ts"], + "extends": "../tsconfig.base.json", + "compilerOptions": { + // We import the plugin's version number from package.json + "resolveJsonModule": true, + // This package is CommonJS + "verbatimModuleSyntax": false, + "module": "CommonJS", + "moduleResolution": "Node10" + } +} diff --git a/protobuf-ets-main/protoc-gen-ets/.ohpmignore b/protobuf-ets-main/protoc-gen-ets/.ohpmignore new file mode 100644 index 0000000..1014746 --- /dev/null +++ b/protobuf-ets-main/protoc-gen-ets/.ohpmignore @@ -0,0 +1,2 @@ +/src +/*.json diff --git a/protobuf-ets-main/protoc-gen-ets/bin/protoc-gen-ets b/protobuf-ets-main/protoc-gen-ets/bin/protoc-gen-ets new file mode 100644 index 0000000..a7923af --- /dev/null +++ b/protobuf-ets-main/protoc-gen-ets/bin/protoc-gen-ets @@ -0,0 +1,6 @@ +#!/usr/bin/env node + +const {runNodeJs} = require("@bufbuild/protoplugin"); +const {protocGenEts} = require("../dist/cjs/src/protoc-gen-ets-plugin.js"); + +runNodeJs(protocGenEts); diff --git a/protobuf-ets-main/protoc-gen-ets/package.json b/protobuf-ets-main/protoc-gen-ets/package.json new file mode 100644 index 0000000..c51a301 --- /dev/null +++ b/protobuf-ets-main/protoc-gen-ets/package.json @@ -0,0 +1,38 @@ +{ + "name": "@bufbuild/protoc-gen-ets", + "version": "1.0.0", + "repository": { + "type": "gitee", + "url": "https://gitee.com/BitFun4HarmonyOS/bitfunprototransformer.git", + "directory": "packages/protoc-gen-ets" + }, + "bin": { + "protoc-gen-ets": "bin/protoc-gen-ets" + }, + "engines": { + "node": ">=18" + }, + "scripts": { + "clean": "rm -rf ./dist/cjs/*", + "build": "tsc --project tsconfig.json --outDir ./dist/cjs", + "pack": "npm i && npm run build && npm pack", + "prepublishOnly": "npm i && npm run build" + }, + "preferUnplugged": true, + "dependencies": { + "@bufbuild/buf": "^1.10.0", + "@bufbuild/protobuf": "^1.10.0", + "@bufbuild/protoplugin": "^1.10.0", + "@typescript/vfs": "1.0.0", + "@types/node": "^20.14.11", + "unbuild": "^2.0.0" + }, + "peerDependencies": { + "@bufbuild/protobuf": "1.10.0" + }, + "peerDependenciesMeta": { + "@bufbuild/protobuf": { + "optional": true + } + } +} diff --git a/protobuf-ets-main/protoc-gen-ets/src/editions.ts b/protobuf-ets-main/protoc-gen-ets/src/editions.ts new file mode 100644 index 0000000..270b33a --- /dev/null +++ b/protobuf-ets-main/protoc-gen-ets/src/editions.ts @@ -0,0 +1,48 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import type { ImportSymbol, Schema } from "@bufbuild/protoplugin/ecmascript"; +import { DescFile } from "@bufbuild/protobuf"; + +/** + * Temporary function to retrieve the import symbol for the proto2 or proto3 + * runtime. + * + * For syntax "editions", this function raises and error. + * + * Once support for "editions" is implemented in the runtime, this function can + * be removed. + */ +export function getNonEditionRuntime( + schema: Schema, + file: DescFile, +): ImportSymbol { + if (file.syntax === "editions") { + // TODO support editions + throw new Error( + `${file.proto.name ?? ""}: syntax "editions" is not supported yet`, + ); + } + const runtime = schema.runtime[file.syntax]; + return { + kind: runtime.kind, + name: runtime.name, + from: '@bitfun/protobuf_ets', + typeOnly: runtime.typeOnly, + toTypeOnly(): ImportSymbol { + return runtime.toTypeOnly(); + }, + id: runtime.id + }; +} diff --git a/protobuf-ets-main/protoc-gen-ets/src/javascript.ts b/protobuf-ets-main/protoc-gen-ets/src/javascript.ts new file mode 100644 index 0000000..db11054 --- /dev/null +++ b/protobuf-ets-main/protoc-gen-ets/src/javascript.ts @@ -0,0 +1,109 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { + DescExtension, + DescField, + FieldDescriptorProto_Label, +} from "@bufbuild/protobuf"; +import { + FieldDescriptorProto_Type, + LongType, +} from "@bufbuild/protobuf"; +import type { + GeneratedFile, + Printable, + Schema, +} from "@bufbuild/protoplugin/ecmascript"; +import {getFieldDefaultValueExpression, getMessageType} from "./util.js"; +import { runtime } from './typescript'; + +// prettier-ignore +export function generateFieldInfo(schema: Schema, f: GeneratedFile, field: DescField | DescExtension) { + const { + FieldInfo, + } = runtime; + f.print(" res.push(new ", FieldInfo, "().", getFieldInfoLiteral(schema, field, f), ");"); +} + +// prettier-ignore +export function getFieldInfoLiteral(schema: Schema, field: DescField | DescExtension, f: GeneratedFile): Printable { + const e: Printable = []; + const { + fiMapInterface, + makeEnumTypeInstance, + } = runtime; + e.push("buildNo(", field.number, ")."); + if (field.kind == "field") { + e.push(`buildName("`, field.name, `").`); + if (field.jsonName !== undefined) { + e.push(`buildJsonName("`, field.jsonName, `").`); + } + } + switch (field.fieldKind) { + case "scalar": + e.push(`buildKind("scalar").buildT(`, field.scalar, ")."); + if (field.longType != LongType.BIGINT) { + e.push("buildL(", field.longType, ")."); + } + break; + case "map": + e.push(`buildKind("map").`); + e.push("buildK(", field.mapKey, ")."); + switch (field.mapValue.kind) { + case "scalar": + e.push("buildV(new ",fiMapInterface,`("scalar", `, field.mapValue.scalar,"))."); + break; + case "message":`` + e.push("buildV(new ",fiMapInterface,`("message")).`); + break; + case "enum": + e.push("buildV(new ",fiMapInterface,`("enum")).`); + break; + } + break; + case "message": + e.push(`buildKind("message").buildT(`, getMessageType(field.message), ".instance)."); + if (field.proto.type === FieldDescriptorProto_Type.GROUP) { + e.push("buildDelimited(true)."); + } + break; + case "enum": + e.push(`buildKind("enum").buildT(`, makeEnumTypeInstance, `("`, field.enum.typeName, `", `, getMessageType(field.enum), ".enumType))."); + break; + } + if (field.repeated) { + e.push("buildRepeated(true)."); + if (field.packed !== field.packedByDefault) { + e.push(`buildRacked(${field.packed}).`); + } + } + if (field.optional) { + e.push("buildOpt(true)."); + } else if (field.proto.label === FieldDescriptorProto_Label.REQUIRED) { + e.push("buildReq(true)."); + } + const defaultValue = getFieldDefaultValueExpression(field); + if (defaultValue !== undefined) { + e.push("buildDefaultValue(", defaultValue, ")."); + } + if (field.oneof) { + e.push("buildOneOf(new ", runtime.InternalOneofInfo, `("${field.oneof.name}")).`); + } + const lastE = e[e.length - 1]; + if (typeof lastE == "string" && lastE.endsWith(".")) { + e[e.length - 1] = lastE.substring(0, lastE.length - 1); + } + return e; +} \ No newline at end of file diff --git a/protobuf-ets-main/protoc-gen-ets/src/protoc-gen-ets-plugin.ts b/protobuf-ets-main/protoc-gen-ets/src/protoc-gen-ets-plugin.ts new file mode 100644 index 0000000..23ae776 --- /dev/null +++ b/protobuf-ets-main/protoc-gen-ets/src/protoc-gen-ets-plugin.ts @@ -0,0 +1,23 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { createEcmaScriptPlugin } from "@bufbuild/protoplugin"; +import { generateEts } from "./typescript.js"; +import { version } from "../package.json"; + +export const protocGenEts = createEcmaScriptPlugin({ + name: "protoc-gen-ets", + version: `v${String(version)}`, + generateTs: generateEts, +}); diff --git a/protobuf-ets-main/protoc-gen-ets/src/runtime-import/names.ts b/protobuf-ets-main/protoc-gen-ets/src/runtime-import/names.ts new file mode 100644 index 0000000..7fa72a3 --- /dev/null +++ b/protobuf-ets-main/protoc-gen-ets/src/runtime-import/names.ts @@ -0,0 +1,312 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { + DescEnum, + DescEnumValue, + DescExtension, + DescField, + DescMessage, DescMethod, + DescOneof, + DescService, +} from "@bufbuild/protobuf"; +/** + * Returns the name of a protobuf element in generated code. + * + * Field names - including oneofs - are converted to lowerCamelCase. For + * messages, enumerations and services, the package name is stripped from + * the type name. For nested messages and enumerations, the names are joined + * with an underscore. For methods, the first character is made lowercase. + */ +export function localName( + desc: + | DescEnum + | DescEnumValue + | DescMessage + | DescExtension + | DescOneof + | DescField + | DescService + | DescMethod, +): string { + switch (desc.kind) { + case "field": + return localFieldName(desc.name, desc.oneof !== undefined); + case "oneof": + return localOneofName(desc.name); + case "enum": + case "message": + case "service": + case "extension": { + const pkg = desc.file.proto.package; + const offset = pkg === undefined ? 0 : pkg.length + 1; + const name = desc.typeName.substring(offset).replace(/\./g, "_"); + // For services, we only care about safe identifiers, not safe object properties, + // but we have shipped v1 with a bug that respected object properties, and we + // do not want to introduce a breaking change, so we continue to escape for + // safe object properties. + // See https://github.com/bufbuild/protobuf-es/pull/391 + return safeObjectProperty(safeIdentifier(name)); + } + case "enum_value": { + let name = desc.name; + const sharedPrefix = desc.parent.sharedPrefix; + if (sharedPrefix !== undefined) { + name = name.substring(sharedPrefix.length); + } + return safeObjectProperty(name); + } + case "rpc": { + let name = desc.name; + if (name.length == 0) { + return name; + } + name = name[0].toLowerCase() + name.substring(1); + return safeObjectProperty(name); + } + } +} + +/** + * Returns the name of a field in generated code. + */ +export function localFieldName(protoName: string, inOneof: boolean) { + const name = protoCamelCase(protoName); + if (inOneof) { + // oneof member names are not properties, but values of the `case` property. + return name; + } + return safeObjectProperty(safeMessageProperty(name)); +} + +/** + * Returns the name of a oneof group in generated code. + */ +export function localOneofName(protoName: string): string { + return localFieldName(protoName, false); +} + +/** + * Returns the JSON name for a protobuf field, exactly like protoc does. + */ +export const fieldJsonName = protoCamelCase; + +/** + * Finds a prefix shared by enum values, for example `MY_ENUM_` for + * `enum MyEnum {MY_ENUM_A=0; MY_ENUM_B=1;}`. + */ +export function findEnumSharedPrefix( + enumName: string, + valueNames: string[], +): string | undefined { + const prefix = camelToSnakeCase(enumName) + "_"; + for (const name of valueNames) { + if (!name.toLowerCase().startsWith(prefix)) { + return undefined; + } + const shortName = name.substring(prefix.length); + if (shortName.length == 0) { + return undefined; + } + if (/^\d/.test(shortName)) { + // identifiers must not start with numbers + return undefined; + } + } + return prefix; +} + +/** + * Converts lowerCamelCase or UpperCamelCase into lower_snake_case. + * This is used to find shared prefixes in an enum. + */ +function camelToSnakeCase(camel: string): string { + return ( + camel.substring(0, 1) + camel.substring(1).replace(/[A-Z]/g, (c) => "_" + c) + ).toLowerCase(); +} + +/** + * Converts snake_case to protoCamelCase according to the convention + * used by protoc to convert a field name to a JSON name. + */ +function protoCamelCase(snakeCase: string): string { + let capNext = false; + const b = []; + for (let i = 0; i < snakeCase.length; i++) { + let c = snakeCase.charAt(i); + switch (c) { + case "_": + capNext = true; + break; + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + case "9": + b.push(c); + capNext = false; + break; + default: + if (capNext) { + capNext = false; + c = c.toUpperCase(); + } + b.push(c); + break; + } + } + return b.join(""); +} + +/** + * Names that cannot be used for identifiers, such as class names, + * but _can_ be used for object properties. + */ +const reservedIdentifiers = new Set([ + // ECMAScript 2015 keywords + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + "yield", + + // ECMAScript 2015 future reserved keywords + "enum", + "implements", + "interface", + "let", + "package", + "private", + "protected", + "public", + "static", + + // Class name cannot be 'Object' when targeting ES5 with module CommonJS + "Object", + + // TypeScript keywords that cannot be used for types (as opposed to variables) + "bigint", + "number", + "boolean", + "string", + "object", + + // Identifiers reserved for the runtime, so we can generate legible code + "globalThis", + "Uint8Array", + "Partial", +]); + +/** + * Names that cannot be used for object properties because they are reserved + * by built-in JavaScript properties. + */ +const reservedObjectProperties = new Set([ + // names reserved by JavaScript + "constructor", + "toString", + "toJSON", + "valueOf", +]); + +/** + * Names that cannot be used for object properties because they are reserved + * by the runtime. + */ +const reservedMessageProperties = new Set([ + // names reserved by the runtime + "getType", + "clone", + "equals", + "fromBinary", + "fromJson", + "fromJsonString", + "toBinary", + "toJson", + "toJsonString", + + // names reserved by the runtime for the future + "toObject", +]); + +const fallback = (name: T) => `${name}$` as const; + +/** + * Will wrap names that are Object prototype properties or names reserved + * for `Message`s. + */ +const safeMessageProperty = (name: string): string => { + if (reservedMessageProperties.has(name)) { + return fallback(name); + } + return name; +}; + +/** + * Names that cannot be used for object properties because they are reserved + * by built-in JavaScript properties. + */ +export const safeObjectProperty = (name: string): string => { + if (reservedObjectProperties.has(name)) { + return fallback(name); + } + return name; +}; + +/** + * Names that can be used for identifiers or class properties + */ +export const safeIdentifier = (name: string): string => { + if (reservedIdentifiers.has(name)) { + return fallback(name); + } + return name; +}; diff --git a/protobuf-ets-main/protoc-gen-ets/src/runtime-import/rumtime-imports.ts b/protobuf-ets-main/protoc-gen-ets/src/runtime-import/rumtime-imports.ts new file mode 100644 index 0000000..b2996e9 --- /dev/null +++ b/protobuf-ets-main/protoc-gen-ets/src/runtime-import/rumtime-imports.ts @@ -0,0 +1,302 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { + DescEnum, + DescEnumValue, + DescExtension, + DescField, + DescMessage, DescMethod, + DescOneof, + DescService, LongType, ScalarType, ScalarValue +} from "@bufbuild/protobuf"; +import {reifyWkt} from "@bufbuild/protoplugin/ecmascript"; +import {localName, safeIdentifier, safeObjectProperty} from "./names"; +import {getUnwrappedFieldType, scalarZeroValue} from "./scalars"; + +export interface RuntimeImports { + proto2: ImportSymbol; + proto3: ImportSymbol; + Message: ImportSymbol; + FieldList: ImportSymbol; + MessageType: ImportSymbol; + BinaryReadOptions: ImportSymbol; + BinaryWriteOptions: ImportSymbol; + JsonReadOptions: ImportSymbol; + JsonWriteOptions: ImportSymbol; + JsonValue: ImportSymbol; + protoInt64: ImportSymbol; + ScalarType: ImportSymbol; + EnumObject: ImportSymbol; + FieldWrapper: ImportSymbol; + makeEnumTypeInstance: ImportSymbol; + EnumTypeInstance: ImportSymbol; + EnumValueInfoInstance: ImportSymbol; + getFieldListsFromArray: ImportSymbol; + InternalOneofInfo: ImportSymbol; + ProtoRuntime: ImportSymbol; + Transport: ImportSymbol; + CallOptions: ImportSymbol; + WireType: ImportSymbol; + EnumType: ImportSymbol; + EnumValueInfo: ImportSymbol; + FieldInfo: ImportSymbol; + FieldValueInfo: ImportSymbol; + fiMapInterface: ImportSymbol; + OneofClass: ImportSymbol; + EmptyMessage: ImportSymbol; +} + +export function createRuntimeImports(): RuntimeImports { + // prettier-ignore + return { + proto2: infoToSymbol("proto2"), + proto3: infoToSymbol("proto3"), + Message: infoToSymbol("Message"), + FieldList: infoToSymbol("FieldList"), + MessageType: infoToSymbol("MessageType"), + BinaryReadOptions: infoToSymbol("BinaryReadOptions"), + BinaryWriteOptions: infoToSymbol("BinaryWriteOptions"), + JsonReadOptions: infoToSymbol("JsonReadOptions"), + JsonWriteOptions: infoToSymbol("JsonWriteOptions"), + JsonValue: infoToSymbol("JsonValue"), + protoInt64: infoToSymbol("protoInt64"), + ScalarType: infoToSymbol("ScalarType"), + EnumObject: infoToSymbol("EnumObject"), + FieldWrapper: infoToSymbol("FieldWrapper"), + makeEnumTypeInstance: infoToSymbol("makeEnumTypeInstance"), + EnumTypeInstance: infoToSymbol("EnumTypeInstance"), + EnumValueInfoInstance: infoToSymbol("EnumValueInfoInstance"), + getFieldListsFromArray: infoToSymbol("getFieldListsFromArray"), + InternalOneofInfo: infoToSymbol("InternalOneofInfo"), + ProtoRuntime: infoToSymbol("ProtoRuntime"), + Transport: infoToSymbol("Transport"), + CallOptions: infoToSymbol("CallOptions"), + WireType: infoToSymbol("WireType"), + EnumType: infoToSymbol("EnumType"), + EnumValueInfo: infoToSymbol("EnumValueInfo"), + FieldInfo: infoToSymbol("FieldInfo"), + FieldValueInfo: infoToSymbol("FieldValueInfo"), + fiMapInterface: infoToSymbol("fiMapInterface"), + OneofClass: infoToSymbol("OneofClass"), + EmptyMessage: infoToSymbol("EmptyMessage"), + }; +} + +function infoToSymbol( + name: keyof typeof codegenInfo.symbols, +): ImportSymbol { + const info = codegenInfo.symbols[name]; + const symbol = createImportSymbol( + name, + info.publicImportPath, + ); + return info.typeOnly ? symbol.toTypeOnly() : symbol; +} + +export type ImportSymbol = { + readonly kind: "es_symbol"; + + /** + * The name to import. + */ + readonly name: string; + + /** + * The import path. + * + * The path can point to a package, for example `@foo/bar/baz.js`, or + * to a file, for example `./bar/baz.js`. + * + * Note that while paths to a file begin with a `./`, they must be + * relative to the project root. + */ + readonly from: string; + + /** + * Whether this is a type-only import - an import that only exists in + * TypeScript. + */ + readonly typeOnly: boolean; + + /** + * Create a copy of this import, and make it type-only for TypeScript. + */ + toTypeOnly(): ImportSymbol; + + /** + * The unique ID based on name and from, disregarding typeOnly. + */ + readonly id: EsSymbolId; +}; + + +function createImportSymbol( + name: string, + from: string, + typeOnly?: boolean, +): ImportSymbol { + const id = `import("${from}").${name}`; + const s: ImportSymbol = { + kind: "es_symbol", + name, + from, + typeOnly: false, + id, + toTypeOnly() { + return { + ...this, + typeOnly: true, + }; + }, + }; + return typeOnly === true ? s.toTypeOnly() : s; +} + +type EsSymbolId = string; + +interface CodegenInfo { + /** + * Name of the runtime library NPM package. + */ + readonly packageName: string; + readonly localName: ( + desc: + | DescEnum + | DescEnumValue + | DescMessage + | DescExtension + | DescOneof + | DescField + | DescService + | DescMethod, + ) => string; + readonly symbols: Record; + readonly getUnwrappedFieldType: ( + field: DescField | DescExtension, + ) => ScalarType | undefined; + readonly wktSourceFiles: readonly string[]; + /** + * @deprecated please use scalarZeroValue instead + */ + readonly scalarDefaultValue: (type: ScalarType, longType: LongType) => any; // eslint-disable-line @typescript-eslint/no-explicit-any + readonly scalarZeroValue: ( + type: T, + longType: L, + ) => ScalarValue; + /** + * @deprecated please use reifyWkt from @bufbuild/protoplugin/ecmascript instead + */ + readonly reifyWkt: typeof reifyWkt; + readonly safeIdentifier: (name: string) => string; + readonly safeObjectProperty: (name: string) => string; +} + +type RuntimeSymbolName = + | "BinaryReadOptions" + | "BinaryWriteOptions" + | "JsonReadOptions" + | "JsonWriteOptions" + | "JsonValue" + | "Message" + | "MessageType" + | "proto2" + | "proto3" + | "protoInt64" + | "ScalarType" + | "EnumObject" + | "FieldWrapper" + | "makeEnumTypeInstance" + | "EnumTypeInstance" + | "EnumValueInfoInstance" + | "getFieldListsFromArray" + | "InternalOneofInfo" + | "ProtoRuntime" + | "Transport" + | "CallOptions" + | 'WireType' + | "EnumType" + | "EnumValueInfo" + | "FieldInfo" + | "FieldValueInfo" + | "fiMapInterface" + | "OneofClass" + | "FieldList" + | 'EmptyMessage' + + +type RuntimeSymbolInfo = { + typeOnly: boolean; + publicImportPath: string; +}; + +const packageName = "@bitfun/protobuf_ets"; + +const codegenInfo: CodegenInfo = { + packageName: "@bitfun/protobuf_ets", + localName, + reifyWkt, + getUnwrappedFieldType, + scalarDefaultValue: scalarZeroValue, + scalarZeroValue, + safeIdentifier, + safeObjectProperty, + // prettier-ignore + symbols: { + proto2: {typeOnly: false, publicImportPath: packageName}, + proto3: {typeOnly: false, publicImportPath: packageName}, + Message: {typeOnly: false, publicImportPath: packageName}, + FieldList: {typeOnly: false, publicImportPath: packageName}, + MessageType: {typeOnly: false, publicImportPath: packageName}, + BinaryReadOptions: {typeOnly: false, publicImportPath: packageName}, + BinaryWriteOptions: {typeOnly: false, publicImportPath: packageName}, + JsonReadOptions: {typeOnly: false, publicImportPath: packageName}, + JsonWriteOptions: {typeOnly: false, publicImportPath: packageName}, + JsonValue: {typeOnly: false, publicImportPath: packageName}, + protoInt64: {typeOnly: false, publicImportPath: packageName}, + ScalarType: {typeOnly: false, publicImportPath: packageName}, + WireType: {typeOnly: false, publicImportPath: packageName}, + EnumType: {typeOnly: false, publicImportPath: packageName}, + EnumValueInfo: {typeOnly: false, publicImportPath: packageName}, + FieldInfo: {typeOnly: false, publicImportPath: packageName}, + FieldValueInfo: {typeOnly: false, publicImportPath: packageName}, + fiMapInterface: {typeOnly: false, publicImportPath: packageName}, + OneofClass: {typeOnly: false, publicImportPath: packageName}, + EmptyMessage: {typeOnly: false, publicImportPath: packageName}, + EnumObject: {typeOnly: false, publicImportPath: packageName}, + FieldWrapper: {typeOnly: false, publicImportPath: packageName}, + makeEnumTypeInstance: {typeOnly: false, publicImportPath: packageName}, + EnumTypeInstance: {typeOnly: false, publicImportPath: packageName}, + EnumValueInfoInstance: {typeOnly: false, publicImportPath: packageName}, + getFieldListsFromArray: {typeOnly: false, publicImportPath: packageName}, + InternalOneofInfo: {typeOnly: false, publicImportPath: packageName}, + ProtoRuntime: {typeOnly: false, publicImportPath: packageName}, + Transport: {typeOnly: false, publicImportPath: packageName}, + CallOptions: {typeOnly: false, publicImportPath: packageName}, + }, + wktSourceFiles: [ + "google/protobuf/compiler/plugin.proto", + "google/protobuf/any.proto", + "google/protobuf/api.proto", + "google/protobuf/descriptor.proto", + "google/protobuf/duration.proto", + "google/protobuf/empty.proto", + "google/protobuf/field_mask.proto", + "google/protobuf/source_context.proto", + "google/protobuf/struct.proto", + "google/protobuf/timestamp.proto", + "google/protobuf/type.proto", + "google/protobuf/wrappers.proto", + ], +}; diff --git a/protobuf-ets-main/protoc-gen-ets/src/runtime-import/scalars.ts b/protobuf-ets-main/protoc-gen-ets/src/runtime-import/scalars.ts new file mode 100644 index 0000000..5d48ece --- /dev/null +++ b/protobuf-ets-main/protoc-gen-ets/src/runtime-import/scalars.ts @@ -0,0 +1,59 @@ +import {DescExtension, DescField, LongType, protoInt64, ScalarType, ScalarValue} from "@bufbuild/protobuf"; + +/** + * Returns the zero value for the given scalar type. + */ +export function scalarZeroValue( + type: T, + longType: L, +): ScalarValue { + switch (type) { + case ScalarType.BOOL: + return false as ScalarValue; + case ScalarType.UINT64: + case ScalarType.FIXED64: + case ScalarType.INT64: + case ScalarType.SFIXED64: + case ScalarType.SINT64: + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison -- acceptable since it's covered by tests + return (longType == 0 ? protoInt64.zero : "0") as ScalarValue; + case ScalarType.DOUBLE: + case ScalarType.FLOAT: + return 0.0 as ScalarValue; + case ScalarType.BYTES: + return new Uint8Array(0) as ScalarValue; + case ScalarType.STRING: + return "" as ScalarValue; + default: + // Handles INT32, UINT32, SINT32, FIXED32, SFIXED32. + // We do not use individual cases to save a few bytes code size. + return 0 as ScalarValue; + } +} + +export function getUnwrappedFieldType( + field: DescField | DescExtension, +): ScalarType | undefined { + if (field.fieldKind !== "message") { + return undefined; + } + if (field.repeated) { + return undefined; + } + if (field.oneof != undefined) { + return undefined; + } + return wktWrapperToScalarType[field.message.typeName]; +} + +const wktWrapperToScalarType: Record = { + "google.protobuf.DoubleValue": ScalarType.DOUBLE, + "google.protobuf.FloatValue": ScalarType.FLOAT, + "google.protobuf.Int64Value": ScalarType.INT64, + "google.protobuf.UInt64Value": ScalarType.UINT64, + "google.protobuf.Int32Value": ScalarType.INT32, + "google.protobuf.UInt32Value": ScalarType.UINT32, + "google.protobuf.BoolValue": ScalarType.BOOL, + "google.protobuf.StringValue": ScalarType.STRING, + "google.protobuf.BytesValue": ScalarType.BYTES, +}; \ No newline at end of file diff --git a/protobuf-ets-main/protoc-gen-ets/src/typescript.ts b/protobuf-ets-main/protoc-gen-ets/src/typescript.ts new file mode 100644 index 0000000..45e0322 --- /dev/null +++ b/protobuf-ets-main/protoc-gen-ets/src/typescript.ts @@ -0,0 +1,484 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { + codegenInfo, + DescEnum, + DescField, + DescMessage, + DescOneof, +} from "@bufbuild/protobuf"; +import { LongType, ScalarType } from "@bufbuild/protobuf"; +import { + GeneratedFile, + Printable, reifyWkt, RuntimeImports, + Schema, +} from "@bufbuild/protoplugin/ecmascript"; +import { localName } from "./runtime-import/names"; +import { generateFieldInfo} from "./javascript.js"; +import { getNonEditionRuntime } from "./editions.js"; +import { + getFieldTypeInfo, + getFieldZeroValueExpression, getMessageType, +} from "./util.js"; +import { createRuntimeImports } from "./runtime-import/rumtime-imports"; + + +export const runtime = createRuntimeImports(); + +export function generateEts(schema: Schema) { + (schema as any).prepareGenerate('ets'); + for (const runtimeKey in schema.runtime) { + schema.runtime[runtimeKey as keyof RuntimeImports] = { + ...schema.runtime[runtimeKey as keyof RuntimeImports], + from: "@bitfun/protobuf_ets", + } + } + (codegenInfo as any).packageName = "@bitfun/protobuf_ets"; + for (const file of schema.files) { + const f = schema.generateFile(file.name + "_pb.ts"); + f.print(`import collections from "@arkts.collections"`) + f.print(); + for (const enumeration of file.enums) { + generateEnum(schema, f, enumeration); + } + for (const message of file.messages) { + generateMessage(schema, f, message); + } + } + const infoArr = (schema as any).getFileInfo(); + infoArr.forEach((info: { name: string; content: string }) => { + info.name = info.name.slice(0, -3) + '.ets'; + info.content = info.content.split(/\r?\n/).map(line => { + if (line.startsWith('import') && line.endsWith('_pb.js";')) { + return line.replace('_pb.js', '_pb'); + } + return line; + }).join('\n'); + }); + (schema as any).getFileInfo = () => infoArr; + (codegenInfo as any).packageName = "@bufbuild/protobuf"; +} + +// prettier-ignore +function generateEnum(schema: Schema, f: GeneratedFile, enumeration: DescEnum) { + const { + EnumValueInfo, + EnumValueInfoInstance, + } = runtime; + f.print(f.exportDecl("const enum", enumeration), " {"); + for (const value of enumeration.values) { + if (enumeration.values.indexOf(value) > 0) { + f.print(); + } + f.print(f.jsDoc(value, " ")); + f.print(" ", localName(value), " = ", value.number, ","); + } + f.print("}"); + f.print(); + f.print("@Sendable"); + f.print(f.exportDecl("class", getMessageType(enumeration)), " {"); + f.print(" static enumType: collections.Array<", EnumValueInfo, "> = collections.Array.from(["); + for (const value of enumeration.values) { + f.print(" new ", EnumValueInfoInstance, `(${value.number}, "${localName(value)}", "${localName(value)}"),`); + } + f.print(" ]);"); + f.print("}") +} + +// prettier-ignore +function generateMessage(schema: Schema, f: GeneratedFile, message: DescMessage) { + const protoN = getNonEditionRuntime(schema, message.file); + const { + FieldList, + Message, + MessageType, + BinaryReadOptions, + JsonReadOptions, + FieldWrapper, + ProtoRuntime, + FieldInfo, + getFieldListsFromArray, + JsonValue, + } = runtime; + // generate messageType + f.print("@Sendable"); + f.print("export class ", getMessageType(message), " extends ", MessageType, "<", message, "> {"); + f.print(" static instance: ", getMessageType(message), " = new ", getMessageType(message), "();"); + f.print(); + f.print(" constructor() {"); + f.print(" super();"); + f.print(" }"); + f.print(); + f.print(" create(data?: ", Message, "<", message, ">): ", message, " {"); + f.print(" return new ",message,"(data);"); + f.print(" }"); + f.print(); + f.print(" equals(a: ", message, " | Message<", message, "> | undefined | null, b: ", message, " | Message<", message, "> | undefined | null): boolean {") + f.print(" return ", protoN, ".util.equals(this, a, b);") + f.print(" }"); + f.print(); + f.print(" clone(message: ", message, "): ", message," {") + f.print(" return ", protoN, ".util.clone(message);") + f.print(" }"); + f.print(); + + + f.print(" getFieldWrapper(): ", FieldWrapper, " | undefined {") + f.print(" return ", message, ".fieldWrapper;") + f.print(" }") + f.print(); + f.print(" getTypeName(): string {") + f.print(" return ", message, ".typeName;") + f.print(" }") + f.print(); + f.print(" getFields(): ", FieldList, " {") + f.print(" return ", message, ".fields;") + f.print(" }") + f.print(); + f.print(" getProtoRuntime(): ", ProtoRuntime, " {") + f.print(" return ", message, ".runtime;") + f.print(" }") + f.print("}"); + f.print(); + + // generate message + f.print("@Sendable"); + f.print(f.exportDecl("class", message), " extends ", Message, "<", message, "> {"); + for (const member of message.members) { + switch (member.kind) { + case "oneof": + generateOneof(schema, f, member); + break; + default: + generateField(schema, f, member); + break; + } + f.print(); + } + f.print(" static fieldWrapper: ", FieldWrapper, "<", message, ", string> | undefined;"); + f.print(); + f.print(" static readonly typeName: string = ", f.string(message.typeName), ";"); + f.print(); + f.print(" static readonly runtime: ", ProtoRuntime, " = ", protoN, ";"); + f.print(); + f.print(" static readonly fieldList: collections.Map", " = new collections.Map();"); + f.print(); + + f.print(" static fields: ", FieldList, " = ", protoN, ".util.newFieldList(", getFieldListsFromArray, "(() => { "); + f.print(" let res = new collections.Array<", FieldInfo, ">();"); + for (const field of message.fields){ + generateFieldInfo(schema, f, field as DescField); + } + f.print(" return res;"); + f.print(" }));") + f.print(); + + f.print(" constructor(data?: ", Message, "<", message, ">) {"); + f.print(" super();"); + f.print(" ", protoN, ".util.initPartial(data, this);"); + f.print(" }"); + f.print(); + f.print(" getType(): ", MessageType, "<", message,"> {"); + f.print(" return ", getMessageType(message), ".instance;"); + f.print(" }"); + generateWktMethods(schema, f, message); + f.print(); + generateWktStaticMethods(schema, f, message); + f.print(" static fromBinary(bytes: Uint8Array, options?: Partial<", BinaryReadOptions, ">): ", message, " {") + f.print(" return new ", message, "().getType().fromBinary(bytes, options);") + f.print(" }") + f.print() + f.print(" static fromJson(jsonValue: ", JsonValue, ", options?: Partial<", JsonReadOptions, ">): ", message, " {") + f.print(" return new ", message, "().getType().fromJson(jsonValue, options);") + f.print(" }") + f.print() + f.print(" static fromJsonString(jsonString: string, options?: Partial<", JsonReadOptions, ">): ", message, " {") + f.print(" return new ", message, "().getType().fromJsonString(jsonString, options);") + f.print(" }") + f.print() + f.print(" static equals(a: ", message , " | undefined, b: ", message , " | undefined): boolean {") + f.print(" return ", protoN, ".util.equals(", getMessageType(message), ".instance, a, b);") + f.print(" }") + f.print("}") + f.print() + for (const nestedEnum of message.nestedEnums) { + generateEnum(schema, f, nestedEnum); + } + for (const nestedMessage of message.nestedMessages) { + generateMessage(schema, f, nestedMessage); + } +} + +// prettier-ignore +function generateOneof(schema: Schema, f: GeneratedFile, oneof: DescOneof) { + f.print(f.jsDoc(oneof, " ")); + const { + OneofClass + } = runtime; + f.print(" ", localName(oneof), ": ", OneofClass, "<"); + for (const field of oneof.fields) { + const isLast = oneof.fields.indexOf(field) === oneof.fields.length - 1; + const { typing } = getFieldTypeInfo(field); + f.print(" ", typing, isLast ? ['> = new ', OneofClass, "();"] : ' |'); + } +} + +// prettier-ignore +function generateField(schema: Schema, f: GeneratedFile, field: DescField) { + f.print(f.jsDoc(field, " ")); + const e: Printable = []; + const { typing, optional } = getFieldTypeInfo(field); + if (optional) { + e.push(" ", localName(field), "?: ", typing, ";"); + } else { + e.push(" ", localName(field), ": ", typing); + const zeroValue = getFieldZeroValueExpression(field); + if (zeroValue !== undefined) { + e.push(" = ", zeroValue); + } + e.push(";"); + } + f.print(e); +} + +// prettier-ignore +function generateWktMethods(schema: Schema, f: GeneratedFile, message: DescMessage) { + const ref = reifyWkt(message); + if (ref === undefined) { + return; + } + const { + JsonValue, + JsonReadOptions, + JsonWriteOptions, + JsonObject, + ScalarType: rtScalarType, + LongType: rtLongType, + protoInt64, + } = schema.runtime; + const protoN = getNonEditionRuntime(schema, message.file); + switch (ref.typeName) { + case "google.protobuf.Timestamp": + f.print(" override fromJson(json: ", JsonValue, ", options?: Partial<", JsonReadOptions, ">): this {"); + f.print(` if (typeof json !== "string") {`); + f.print(" throw new Error(`cannot decode ", message.typeName, " from JSON: ${", protoN, ".json.debug(json)}`);"); + f.print(" }"); + f.print(` const matches = json.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:Z|\\.([0-9]{3,9})Z|([+-][0-9][0-9]:[0-9][0-9]))$/);`); + f.print(" if (!matches) {"); + f.print(" throw new Error(`cannot decode ", message.typeName, " from JSON: invalid RFC 3339 string`);"); + f.print(" }"); + f.print(` const ms = Date.parse(matches[1] + "-" + matches[2] + "-" + matches[3] + "T" + matches[4] + ":" + matches[5] + ":" + matches[6] + (matches[8] ? matches[8] : "Z"));`); + f.print(" if (Number.isNaN(ms)) {"); + f.print(" throw new Error(`cannot decode ", message.typeName, " from JSON: invalid RFC 3339 string`);"); + f.print(" }"); + f.print(` if (ms < Date.parse("0001-01-01T00:00:00Z") || ms > Date.parse("9999-12-31T23:59:59Z")) {`); + f.print(" throw new Error(`cannot decode message ", message.typeName, " from JSON: must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive`);"); + f.print(" }"); + if (ref.seconds.longType === LongType.STRING) { + f.print(" this.", localName(ref.seconds), " = ", protoInt64, ".parse(ms / 1000).toString();"); + } else { + f.print(" this.", localName(ref.seconds), " = ", protoInt64, ".parse(ms / 1000);"); + } + f.print(" this.", localName(ref.nanos), " = 0;"); + f.print(" if (matches[7]) {"); + f.print(` this.`, localName(ref.nanos), ` = (parseInt("1" + matches[7] + "0".repeat(9 - matches[7].length)) - 1000000000);` ); + f.print(" }"); + f.print(" return this;"); + f.print(" }"); + f.print(); + f.print(" override toJson(options?: Partial<", JsonWriteOptions, ">): JsonValue {"); + f.print(" const ms = Number(this.", localName(ref.seconds), ") * 1000;"); + f.print(` if (ms < Date.parse("0001-01-01T00:00:00Z") || ms > Date.parse("9999-12-31T23:59:59Z")) {`); + f.print(" throw new Error(`cannot encode ", message.typeName, " to JSON: must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive`);"); + f.print(" }"); + f.print(" if (this.", localName(ref.nanos), " < 0) {"); + f.print(" throw new Error(`cannot encode ", message.typeName, " to JSON: nanos must not be negative`);"); + f.print(" }"); + f.print(` let z = "Z";`); + f.print(" if (this.", localName(ref.nanos), " > 0) {"); + f.print(" const nanosStr = (this.", localName(ref.nanos), " + 1000000000).toString().substring(1);"); + f.print(` if (nanosStr.substring(3) === "000000") {`); + f.print(` z = "." + nanosStr.substring(0, 3) + "Z";`); + f.print(` } else if (nanosStr.substring(6) === "000") {`); + f.print(` z = "." + nanosStr.substring(0, 6) + "Z";`); + f.print(" } else {"); + f.print(` z = "." + nanosStr + "Z";`); + f.print(" }"); + f.print(" }"); + f.print(` return new Date(ms).toISOString().replace(".000Z", z);`); + f.print(" }"); + f.print(); + f.print(" toDate(): Date {"); + f.print(" return new Date(Number(this.", localName(ref.seconds), ") * 1000 + Math.ceil(this.", localName(ref.nanos), " / 1000000));"); + f.print(" }"); + f.print(); + break; + case "google.protobuf.Duration": + f.print(" override fromJson(json: ", JsonValue, ", options?: Partial<", JsonReadOptions, ">): this {") + f.print(` if (typeof json !== "string") {`) + f.print(" throw new Error(`cannot decode ", message.typeName, " from JSON: ${proto3.json.debug(json)}`);") + f.print(" }") + f.print(` const match = json.match(/^(-?[0-9]+)(?:\\.([0-9]+))?s/);`) + f.print(" if (match === null) {") + f.print(" throw new Error(`cannot decode ", message.typeName, " from JSON: ${", protoN, ".json.debug(json)}`);") + f.print(" }") + f.print(" const longSeconds = Number(match[1]);") + f.print(" if (longSeconds > 315576000000 || longSeconds < -315576000000) {") + f.print(" throw new Error(`cannot decode ", message.typeName, " from JSON: ${", protoN, ".json.debug(json)}`);") + f.print(" }") + if (ref.seconds.longType === LongType.STRING) { + f.print(" this.", localName(ref.seconds), " = ", protoInt64, ".parse(longSeconds).toString();") + } else { + f.print(" this.", localName(ref.seconds), " = ", protoInt64, ".parse(longSeconds);") + } + f.print(` if (typeof match[2] == "string") {`) + f.print(` const nanosStr = match[2] + "0".repeat(9 - match[2].length);`) + f.print(" this.", localName(ref.nanos), " = parseInt(nanosStr);") + f.print(" if (longSeconds < 0 || Object.is(longSeconds, -0)) {"); + f.print(" this.", localName(ref.nanos), " = -this.", localName(ref.nanos), ";") + f.print(" }") + f.print(" }") + f.print(" return this;") + f.print(" }") + f.print() + f.print(" override toJson(options?: Partial<", JsonWriteOptions, ">): JsonValue {") + f.print(" if (Number(this.", localName(ref.seconds), ") > 315576000000 || Number(this.", localName(ref.seconds), ") < -315576000000) {") + f.print(" throw new Error(`cannot encode ", message.typeName, " to JSON: value out of range`);") + f.print(" }") + f.print(" let text = this.", localName(ref.seconds), ".toString();") + f.print(" if (this.", localName(ref.nanos), " !== 0) {") + f.print(" let nanosStr = Math.abs(this.", localName(ref.nanos), ").toString();") + f.print(` nanosStr = "0".repeat(9 - nanosStr.length) + nanosStr;`) + f.print(` if (nanosStr.substring(3) === "000000") {`) + f.print(" nanosStr = nanosStr.substring(0, 3);") + f.print(` } else if (nanosStr.substring(6) === "000") {`) + f.print(" nanosStr = nanosStr.substring(0, 6);") + f.print(` }`) + f.print(` text += "." + nanosStr;`) + f.print(" if (this.", localName(ref.nanos), " < 0 && Number(this.", localName(ref.seconds), ") == 0) {"); + f.print(` text = "-" + text;`); + f.print(` }`); + f.print(" }") + f.print(` return text + "s";`) + f.print(" }") + f.print() + break; + case "google.protobuf.Struct": + f.print(" override toJson(options?: Partial<", JsonWriteOptions, ">): ", JsonValue, " {") + f.print(" const json: ", JsonObject, " = {}") + f.print(" for (const [k, v] of Object.entries(this.", localName(ref.fields), ")) {") + f.print(" json[k] = v.toJson(options);") + f.print(" }") + f.print(" return json;") + f.print(" }") + f.print() + f.print(" override fromJson(json: ", JsonValue, ", options?: Partial<", JsonReadOptions, ">): this {") + f.print(` if (typeof json != "object" || json == null || Array.isArray(json)) {`) + f.print(` throw new Error("cannot decode `, message.typeName, ` from JSON " + `, protoN, `.json.debug(json));`) + f.print(" }") + f.print(" for (const [k, v] of Object.entries(json)) {") + f.print(" this.", localName(ref.fields), "[k] = ", ref.fields.mapValue.message ?? "", ".fromJson(v);") + f.print(" }") + f.print(" return this;") + f.print(" }") + f.print() + break; + case "google.protobuf.DoubleValue": + case "google.protobuf.FloatValue": + case "google.protobuf.Int64Value": + case "google.protobuf.UInt64Value": + case "google.protobuf.Int32Value": + case "google.protobuf.UInt32Value": + case "google.protobuf.BoolValue": + case "google.protobuf.StringValue": + case "google.protobuf.BytesValue": + f.print(" override toJson(options?: Partial<", JsonWriteOptions, ">): ", JsonValue, " {") + f.print(" return proto3.json.writeScalar(", rtScalarType, ".", ScalarType[ref.value.scalar], ", this.value, true)!;") + f.print(" }") + f.print() + f.print(" override fromJson(json: ", JsonValue, ", options?: Partial<", JsonReadOptions, ">): this {") + f.print(" try {") + if (ref.value.longType === LongType.STRING) { + f.print(" this.value = ", protoN, ".json.readScalar(", rtScalarType, ".", ScalarType[ref.value.scalar], ", json, ", rtLongType, ".", LongType[ref.value.longType] ,");") + } else { + f.print(" this.value = ", protoN, ".json.readScalar(", rtScalarType, ".", ScalarType[ref.value.scalar], ", json);") + } + f.print(" } catch (e) {") + f.print(" let m = `cannot decode message ", message.typeName, " from JSON\"`;") + f.print(" if (e instanceof Error && e.message.length > 0) {") + f.print(" m += `: ${e.message}`") + f.print(" }") + f.print(" throw new Error(m);") + f.print(" }") + f.print(" return this;") + f.print(" }") + f.print() + break; + } +} + +// prettier-ignore +function generateWktStaticMethods(schema: Schema, f: GeneratedFile, message: DescMessage) { + const ref = reifyWkt(message); + if (ref === undefined) { + return; + } + const { + protoInt64, + } = schema.runtime; + switch (ref.typeName) { + case "google.protobuf.Timestamp": + f.print(" static now(): ", message, " {") + f.print(" return ", message, ".fromDate(new Date())") + f.print(" }") + f.print() + f.print(" static fromDate(date: Date): ", message, " {") + f.print(" const ms = date.getTime();") + f.print(" return new ", message, "({") + if (ref.seconds.longType === LongType.STRING) { + f.print(" ", localName(ref.seconds), ": ", protoInt64, ".parse(Math.floor(ms / 1000)).toString(),") + } else { + f.print(" ", localName(ref.seconds), ": ", protoInt64, ".parse(Math.floor(ms / 1000)),") + } + f.print(" ", localName(ref.nanos), ": (ms % 1000) * 1000000,") + f.print(" });") + f.print(" }") + f.print() + break; + case "google.protobuf.DoubleValue": + case "google.protobuf.FloatValue": + case "google.protobuf.Int64Value": + case "google.protobuf.UInt64Value": + case "google.protobuf.Int32Value": + case "google.protobuf.UInt32Value": + case "google.protobuf.BoolValue": + case "google.protobuf.StringValue": + case "google.protobuf.BytesValue": { + const {typing} = getFieldTypeInfo(ref.value); + f.print(" static readonly fieldWrapper = {") + f.print(" wrapField(value: ", typing, "): ", message, " {") + f.print(" return new ", message, "({value});") + f.print(" },") + f.print(" unwrapField(value: ", message, "): ", typing, " {") + f.print(" return value.", localName(ref.value), ";") + f.print(" }") + f.print(" };") + f.print() + break; + } + case "google.protobuf.Duration": + case "google.protobuf.Struct": + } +} diff --git a/protobuf-ets-main/protoc-gen-ets/src/util.ts b/protobuf-ets-main/protoc-gen-ets/src/util.ts new file mode 100644 index 0000000..f34878d --- /dev/null +++ b/protobuf-ets-main/protoc-gen-ets/src/util.ts @@ -0,0 +1,324 @@ +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { + codegenInfo, + DescEnumValue, + DescExtension, + DescField, + FieldDescriptorProto_Label, + LongType, + ScalarType, + ScalarValue, +} from "@bufbuild/protobuf"; +import type { Printable } from "@bufbuild/protoplugin/ecmascript"; +import { localName } from "@bufbuild/protoplugin/ecmascript"; +import { DescEnum, DescMessage } from "@bufbuild/protobuf/dist/cjs/descriptor-set"; + +export function getFieldTypeInfo(field: DescField | DescExtension): { + typing: Printable; + optional: boolean; + typingInferrableFromZeroValue: boolean; +} { + let typing: Printable = []; + let typingInferrableFromZeroValue: boolean; + let optional = false; + switch (field.fieldKind) { + case "scalar": + typing.push(scalarTypeScriptType(field.scalar, field.longType)); + optional = + field.optional || + field.proto.label === FieldDescriptorProto_Label.REQUIRED; + typingInferrableFromZeroValue = true; + break; + case "message": { + const baseType = codegenInfo.getUnwrappedFieldType(field); + if (baseType !== undefined) { + typing.push(scalarTypeScriptType(baseType, LongType.BIGINT)); + } else { + typing.push({ + kind: "es_ref_message", + type: field.message, + typeOnly: true, + }); + } + optional = true; + typingInferrableFromZeroValue = true; + break; + } + case "enum": + typing.push({ + kind: 'es_ref_enum', + type: field.enum, + typeOnly: true + }); + optional = + field.optional || + field.proto.label === FieldDescriptorProto_Label.REQUIRED; + typingInferrableFromZeroValue = true; + break; + case "map": { + let keyType: string; + switch (field.mapKey) { + case ScalarType.INT32: + case ScalarType.FIXED32: + case ScalarType.UINT32: + case ScalarType.SFIXED32: + case ScalarType.SINT32: + keyType = "number"; + break; + default: + keyType = "string"; + break; + } + let valueType: Printable; + switch (field.mapValue.kind) { + case "scalar": + valueType = scalarTypeScriptType( + field.mapValue.scalar, + LongType.BIGINT, + ); + break; + case "message": + valueType = { + kind: "es_ref_message", + type: field.mapValue.message, + typeOnly: true, + }; + break; + case "enum": + valueType = { + kind: "es_ref_enum", + type: field.mapValue.enum, + typeOnly: true, + }; + break; + } + typing.push("collections.Map<", keyType, ", ", valueType, ">"); + typingInferrableFromZeroValue = false; + optional = false; + break; + } + } + if (field.repeated) { + typing = ["collections.Array<", typing, ">"] + optional = false; + typingInferrableFromZeroValue = false; + } + return { typing, optional, typingInferrableFromZeroValue }; +} + +/** + * Return a printable expression for the default value of a field. + * Only applicable for singular scalar and enum fields. + */ +export function getFieldDefaultValueExpression( + field: DescField | DescExtension, + enumAs: + | "enum_value_as_is" + | "enum_value_as_integer" + | "enum_value_as_cast_integer" = "enum_value_as_is", +): Printable | undefined { + if (field.repeated) { + return undefined; + } + if (field.fieldKind !== "enum" && field.fieldKind !== "scalar") { + return undefined; + } + const defaultValue = field.getDefaultValue(); + if (defaultValue === undefined) { + return undefined; + } + switch (field.fieldKind) { + case "enum": { + const enumValue = field.enum.values.find( + (value) => value.number === defaultValue, + ); + if (enumValue === undefined) { + throw new Error( + `invalid enum default value: ${String(defaultValue)} for ${enumValue}`, + ); + } + return literalEnumValue(enumValue, enumAs); + } + case "scalar": + return literalScalarValue(defaultValue, field); + } +} + +/** + * Return a printable expression for the zero value of a field. + * + * Returns either: + * - empty array literal for repeated fields + * - empty object literal for maps + * - undefined for message fields + * - an enums first value + * - scalar zero value + */ +export function getFieldZeroValueExpression( + field: DescField | DescExtension, + enumAs: + | "enum_value_as_is" + | "enum_value_as_integer" + | "enum_value_as_cast_integer" = "enum_value_as_is", +): Printable | undefined { + if (field.repeated) { + return "new collections.Array()"; + } + switch (field.fieldKind) { + case "message": + return undefined; + case "map": + return "new collections.Map()"; + case "enum": { + // In proto3, the first enum value must be zero. + // In proto2, protobuf-go returns the first value as the default. + if (field.enum.values.length < 1) { + throw new Error("invalid enum: missing at least one value"); + } + const zeroValue = field.enum.values[0]; + return literalEnumValue(zeroValue, enumAs) + } + case "scalar": { + const defaultValue = codegenInfo.scalarZeroValue( + field.scalar, + field.longType, + ); + return literalScalarValue(defaultValue, field); + } + } +} + +function literalScalarValue( + value: ScalarValue, + field: (DescField | DescExtension) & { fieldKind: "scalar" }, +): Printable { + switch (field.scalar) { + case ScalarType.DOUBLE: + case ScalarType.FLOAT: + case ScalarType.INT32: + case ScalarType.FIXED32: + case ScalarType.UINT32: + case ScalarType.SFIXED32: + case ScalarType.SINT32: + if (typeof value != "number") { + throw new Error( + `Unexpected value for ${ScalarType[field.scalar]} ${field.toString()}: ${String(value)}`, + ); + } + return value; + case ScalarType.BOOL: + if (typeof value != "boolean") { + throw new Error( + `Unexpected value for ${ScalarType[field.scalar]} ${field.toString()}: ${String(value)}`, + ); + } + return value; + case ScalarType.STRING: + if (typeof value != "string") { + throw new Error( + `Unexpected value for ${ScalarType[field.scalar]} ${field.toString()}: ${String(value)}`, + ); + } + return { kind: "es_string", value }; + case ScalarType.BYTES: + if (!(value instanceof Uint8Array)) { + throw new Error( + `Unexpected value for ${ScalarType[field.scalar]} ${field.toString()}: ${String(value)}`, + ); + } + return `new collections.Uint8Array(0)`; + case ScalarType.INT64: + case ScalarType.SINT64: + case ScalarType.SFIXED64: + case ScalarType.UINT64: + case ScalarType.FIXED64: + if (typeof value != "bigint" && typeof value != "string") { + throw new Error( + `Unexpected value for ${ScalarType[field.scalar]} ${field.toString()}: ${String(value)}`, + ); + } + return { + kind: "es_proto_int64", + type: field.scalar, + longType: field.longType, + value, + }; + } +} + +function literalEnumValue( + value: DescEnumValue, + enumAs: + | "enum_value_as_is" + | "enum_value_as_integer" + | "enum_value_as_cast_integer", +): Printable { + switch (enumAs) { + case "enum_value_as_is": + return [ + { kind: "es_ref_enum", type: value.parent, typeOnly: false }, + ".", + localName(value), + ]; + case "enum_value_as_integer": + return [ + value.number, + " /* ", + value.parent.typeName, + ".", + value.name, + " */", + ]; + case "enum_value_as_cast_integer": + return [ + value.number, + " as ", + { kind: "es_ref_enum", type: value.parent, typeOnly: true }, + ".", + localName(value), + ]; + } +} + +function scalarTypeScriptType(type: ScalarType, longType: LongType): Printable { + switch (type) { + case ScalarType.STRING: + return "string"; + case ScalarType.BOOL: + return "boolean"; + case ScalarType.UINT64: + case ScalarType.SFIXED64: + case ScalarType.FIXED64: + case ScalarType.SINT64: + case ScalarType.INT64: + if (longType === LongType.STRING) { + return "string"; + } + return "bigint"; + case ScalarType.BYTES: + return "collections.Uint8Array"; + default: + return "number"; + } +} + +export function getMessageType(message: DescMessage | DescEnum): DescMessage | DescEnum{ + return { + ...message, + name: message.name + "Type_$1", + typeName: message.typeName + "Type_$1", + } +} \ No newline at end of file diff --git a/protobuf-ets-main/protoc-gen-ets/tsconfig.json b/protobuf-ets-main/protoc-gen-ets/tsconfig.json new file mode 100644 index 0000000..dca18a1 --- /dev/null +++ b/protobuf-ets-main/protoc-gen-ets/tsconfig.json @@ -0,0 +1,12 @@ +{ + "files": ["src/protoc-gen-ets-plugin.ts"], + "extends": "../tsconfig.base.json", + "compilerOptions": { + // We import the plugin's version number from package.json + "resolveJsonModule": true, + // This package is CommonJS + "verbatimModuleSyntax": false, + "module": "CommonJS", + "moduleResolution": "Node10" + } +} diff --git a/protobuf-ets-main/scripts/get-workspace-publish-tag.js b/protobuf-ets-main/scripts/get-workspace-publish-tag.js new file mode 100644 index 0000000..8f36084 --- /dev/null +++ b/protobuf-ets-main/scripts/get-workspace-publish-tag.js @@ -0,0 +1,85 @@ +#!/usr/bin/env node + +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { readdirSync, readFileSync } from "fs"; +import { join } from "path"; +import { existsSync } from "node:fs"; + +/* + +USAGE: node get-workspace-publish-tag.js + +Looks at the version used in the workspace, and returns one of: + - latest - for versions such as 3.1.4, 2.0.0, but also 0.1.2 + - alpha - for versions such as 1.0.0-alpha.8 + - beta - for versions such as 1.0.0-beta.8 + - rc - for versions such as 1.0.0-rc.8 + +*/ + +const version = findWorkspaceVersion("packages"); +const tag = determinePublishTag(version); +process.stdout.write(tag); + +/** + * @param {string} version + * @returns {string} + */ +function determinePublishTag(version) { + if (/^\d+\.\d+\.\d+$/.test(version)) { + return "latest"; + } else if (/^\d+\.\d+\.\d+-alpha.*$/.test(version)) { + return "alpha"; + } else if (/^\d+\.\d+\.\d+-beta.*$/.test(version)) { + return "beta"; + } else if (/^\d+\.\d+\.\d+-rc.*$/.test(version)) { + return "rc"; + } else { + throw new Error(`Unable to determine publish tag from version ${version}`); + } +} + +/** + * @param {string} packagesDir + * @returns {string} + */ +function findWorkspaceVersion(packagesDir) { + let version = undefined; + for (const entry of readdirSync(packagesDir, { withFileTypes: true })) { + if (!entry.isDirectory()) { + continue; + } + const path = join(packagesDir, entry.name, "package.json"); + if (existsSync(path)) { + const pkg = JSON.parse(readFileSync(path, "utf-8")); + if (pkg.private === true) { + continue; + } + if (!pkg.version) { + throw new Error(`${path} is missing "version"`); + } + if (version === undefined) { + version = pkg.version; + } else if (version !== pkg.version) { + throw new Error(`${path} has unexpected version ${pkg.version}`); + } + } + } + if (version === undefined) { + throw new Error(`unable to find workspace version`); + } + return version; +} diff --git a/protobuf-ets-main/scripts/set-workspace-version.js b/protobuf-ets-main/scripts/set-workspace-version.js new file mode 100644 index 0000000..792f4ab --- /dev/null +++ b/protobuf-ets-main/scripts/set-workspace-version.js @@ -0,0 +1,189 @@ +#!/usr/bin/env node + +// Copyright 2021-2024 Buf Technologies, Inc. +// +// 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. + +import { readdirSync, readFileSync, writeFileSync } from "fs"; +import { join } from "path"; +import { existsSync } from "node:fs"; +import assert from "node:assert"; + +if (process.argv.length < 3) { + process.stderr.write( + [ + `USAGE: ${process.argv[1]} `, + "", + "Walks through all packages in the given workspace directory and", + "sets the version of each package to the given version.", + "If a package depends on another package from the workspace, the", + "dependency version is updated as well.", + "", + ].join("\n"), + ); + process.exit(1); +} + +try { + const newVersion = process.argv[2]; + const packagesDir = "packages"; + const lockFile = "package-lock.json"; + const packages = readPackages(packagesDir); + const lock = tryReadLock(lockFile); + const updates = setVersion(packages, lock, newVersion); + if (updates.length > 0) { + writePackages(packagesDir, packages); + if (lock) { + writeLock(lockFile, lock); + } + process.stdout.write(formatUpdates(updates) + "\n"); + } +} catch (e) { + process.stderr.write(String(e) + "\n"); + process.exit(1); +} + +function setVersion(packages, lock, newVersion) { + const updates = []; + for (const pkg of packages) { + if (typeof pkg.version !== "string") { + continue; + } + assert(pkg.name); + if (pkg.version === newVersion) { + continue; + } + pkg.version = newVersion; + if (lock) { + const l = Array.from(Object.values(lock.packages)).find( + (l) => l.name === pkg.name, + ); + assert(l); + l.version = newVersion; + } + updates.push({ + package: pkg, + message: `updated version from ${pkg.version} to ${newVersion}`, + }); + } + updates.push(...syncDeps(packages, packages)); + if (lock) { + syncDeps(Object.values(lock.packages), packages); + } + return updates; +} + +function syncDeps(packages, deps) { + const updates = []; + for (const pkg of packages) { + for (const key of [ + "dependencies", + "devDependencies", + "peerDependencies", + "optionalDependencies", + ]) { + if (!Object.prototype.hasOwnProperty.call(pkg, key)) { + continue; + } + for (const [name, version] of Object.entries(pkg[key])) { + const dep = deps.find((x) => x.name === name); + if (!dep) { + continue; + } + let wantVersion = dep.version; + if (version.startsWith("^")) { + wantVersion = "^" + wantVersion; + } else if (version.startsWith("~")) { + wantVersion = "~" + wantVersion; + } else if (version.startsWith("=")) { + wantVersion = "=" + wantVersion; + } else if (version === "*") { + wantVersion = "*"; + } + if (wantVersion === version) { + continue; + } + pkg[key][name] = wantVersion; + updates.push({ + package: pkg, + message: `updated ${key}["${name}"] from ${version} to ${wantVersion}`, + }); + } + } + } + return updates; +} + +function readPackages(packagesDir) { + const packagesByPath = readPackagesByPath(packagesDir); + return Object.values(packagesByPath); +} + +function writePackages(packagesDir, packages) { + const packagesByPath = readPackagesByPath(packagesDir); + for (const [path, oldPkg] of Object.entries(packagesByPath)) { + const newPkg = packages.find((p) => p.name === oldPkg.name); + writeFileSync(path, JSON.stringify(newPkg, null, 2) + "\n"); + } +} + +function readPackagesByPath(packagesDir) { + const packages = {}; + for (const entry of readdirSync(packagesDir, { withFileTypes: true })) { + if (!entry.isDirectory()) { + continue; + } + const path = join(packagesDir, entry.name, "package.json"); + if (existsSync(path)) { + const pkg = JSON.parse(readFileSync(path, "utf-8")); + if (!pkg.name) { + throw new Error(`${path} is missing "name"`); + } + packages[path] = pkg; + } + } + return packages; +} + +function formatUpdates(updates) { + const lines = []; + const updatesByName = {}; + for (const update of updates) { + if (updatesByName[update.package.name] === undefined) { + updatesByName[update.package.name] = []; + } + updatesByName[update.package.name].push(update); + } + for (const name of Object.keys(updatesByName).sort()) { + lines.push(`${name}:`); + for (const update of updatesByName[name]) { + lines.push(` ${update.message}`); + } + } + return lines.join("\n"); +} + +function tryReadLock(lockFile) { + if (!existsSync(lockFile)) { + return null; + } + const lock = JSON.parse(readFileSync(lockFile, "utf-8")); + assert(lock.lockfileVersion === 3); + assert(typeof lock.packages == "object"); + assert(lock.packages !== null); + return lock; +} + +function writeLock(lockFile, lock) { + writeFileSync(lockFile, JSON.stringify(lock, null, 2) + "\n"); +} diff --git a/protobuf-ets-main/tsconfig.base.json b/protobuf-ets-main/tsconfig.base.json new file mode 100644 index 0000000..c6f21e7 --- /dev/null +++ b/protobuf-ets-main/tsconfig.base.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es2020", + "esModuleInterop": false, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "useUnknownInCatchVariables": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + // We need node's module resolution, so we do not have to skip lib checks + "moduleResolution": "node", + "module": "es2020", +// "verbatimModuleSyntax": true, + "skipLibCheck": false + } +} -- Gitee