├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github ├── .OwlBot.lock.yaml ├── .OwlBot.yaml ├── CODEOWNERS ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── documentation_request.yml │ ├── feature_request.yml │ ├── processs_request.md │ ├── questions.md │ └── support_request.md ├── PULL_REQUEST_TEMPLATE.md ├── auto-approve.yml ├── auto-label.yaml ├── generated-files-bot.yml ├── release-please.yml ├── release-trigger.yml ├── scripts │ ├── close-invalid-link.cjs │ ├── close-unresponsive.cjs │ ├── fixtures │ │ ├── invalidIssueBody.txt │ │ ├── validIssueBody.txt │ │ └── validIssueBodyDifferentLinkLocation.txt │ ├── package.json │ ├── remove-response-label.cjs │ └── tests │ │ ├── close-invalid-link.test.cjs │ │ └── close-or-remove-response-label.test.cjs ├── sync-repo-settings.yaml └── workflows │ ├── ci.yaml │ ├── issues-no-repro.yaml │ └── response.yaml ├── .gitignore ├── .jsdoc.js ├── .kokoro ├── .gitattributes ├── common.cfg ├── continuous │ ├── node12 │ │ ├── common.cfg │ │ ├── lint.cfg │ │ ├── samples-test.cfg │ │ ├── system-test.cfg │ │ └── test.cfg │ └── node18 │ │ ├── common.cfg │ │ ├── lint.cfg │ │ ├── samples-test.cfg │ │ ├── system-test.cfg │ │ └── test.cfg ├── docs.sh ├── lint.sh ├── populate-secrets.sh ├── presubmit │ ├── node12 │ │ ├── common.cfg │ │ ├── samples-test.cfg │ │ ├── system-test.cfg │ │ └── test.cfg │ └── node18 │ │ ├── common.cfg │ │ ├── samples-test.cfg │ │ ├── system-test.cfg │ │ └── test.cfg ├── publish.sh ├── release │ ├── docs-devsite.cfg │ ├── docs-devsite.sh │ ├── docs.cfg │ ├── docs.sh │ └── publish.cfg ├── samples-test.sh ├── system-test.sh ├── test.bat ├── test.sh ├── trampoline.sh └── trampoline_v2.sh ├── .mocharc.js ├── .nycrc ├── .prettierignore ├── .prettierrc.js ├── .repo-metadata.json ├── .trampolinerc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── owlbot.py ├── package.json ├── renovate.json ├── samples ├── README.md ├── package.json ├── quickstart.js └── system-test │ └── test.quickstart.js ├── test-fixtures └── proto │ ├── test.json │ └── test.proto ├── tsconfig.json └── typescript ├── src ├── any.ts ├── bytes.ts ├── duration.ts ├── enum.ts ├── fieldmask.ts ├── fromproto3json.ts ├── index.ts ├── timestamp.ts ├── toproto3json.ts ├── types.ts ├── util.ts ├── value.ts └── wrappers.ts └── test ├── system └── install.ts └── unit ├── any.ts ├── bytes.ts ├── common.ts ├── duration.ts ├── empty.ts ├── enum.ts ├── error-coverage.ts ├── fieldmask.ts ├── map.ts ├── nested.ts ├── oneof.ts ├── primitive.ts ├── repeated.ts ├── timestamp.ts ├── value.ts └── wrappers.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/coverage 3 | test/fixtures 4 | build/ 5 | docs/ 6 | protos/ 7 | samples/generated/ 8 | system-test/**/fixtures 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/gts" 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ts text eol=lf 2 | *.js text eol=lf 3 | protos/* linguist-generated 4 | **/api-extractor.json linguist-language=JSON-with-Comments 5 | -------------------------------------------------------------------------------- /.github/.OwlBot.lock.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | docker: 15 | image: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest 16 | digest: sha256:da69f1fd77b825b0520b1b0a047c270a3f7e3a42e4d46a5321376281cef6e62b 17 | # created: 2025-06-02T21:06:54.667555755Z 18 | -------------------------------------------------------------------------------- /.github/.OwlBot.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | docker: 15 | image: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest 16 | 17 | 18 | begin-after-commit-hash: 397c0bfd367a2427104f988d5329bc117caafd95 19 | 20 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code owners file. 2 | # This file controls who is tagged for review for any given pull request. 3 | # 4 | # For syntax help see: 5 | # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax 6 | 7 | 8 | # Unless specified, the jsteam is the default owner for nodejs repositories. 9 | * @googleapis/jsteam -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 1. 10 | 1. 11 | 1. 12 | 13 | ## Specifications 14 | 15 | - Version: 16 | - Platform: -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a report to help us improve 3 | labels: 4 | - bug 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: > 9 | **PLEASE READ**: If you have a support contract with Google, please 10 | create an issue in the [support 11 | console](https://cloud.google.com/support/) instead of filing on GitHub. 12 | This will ensure a timely response. Otherwise, please make sure to 13 | follow the steps below. 14 | - type: checkboxes 15 | attributes: 16 | label: Please make sure you have searched for information in the following 17 | guides. 18 | options: 19 | - label: "Search the issues already opened: 20 | https://github.com/GoogleCloudPlatform/google-cloud-node/issues" 21 | required: true 22 | - label: "Search StackOverflow: 23 | http://stackoverflow.com/questions/tagged/google-cloud-platform+nod\ 24 | e.js" 25 | required: true 26 | - label: "Check our Troubleshooting guide: 27 | https://github.com/googleapis/google-cloud-node/blob/main/docs/trou\ 28 | bleshooting.md" 29 | required: true 30 | - label: "Check our FAQ: 31 | https://github.com/googleapis/google-cloud-node/blob/main/docs/faq.\ 32 | md" 33 | required: true 34 | - label: "Check our libraries HOW-TO: 35 | https://github.com/googleapis/gax-nodejs/blob/main/client-libraries\ 36 | .md" 37 | required: true 38 | - label: "Check out our authentication guide: 39 | https://github.com/googleapis/google-auth-library-nodejs" 40 | required: true 41 | - label: "Check out handwritten samples for many of our APIs: 42 | https://github.com/GoogleCloudPlatform/nodejs-docs-samples" 43 | required: true 44 | - type: textarea 45 | attributes: 46 | label: > 47 | A screenshot that you have tested with "Try this API". 48 | description: > 49 | As our client libraries are mostly autogenerated, we kindly request 50 | that you test whether your issue is with the client library, or with the 51 | API itself. To do so, please search for your API 52 | here: https://developers.google.com/apis-explorer and attempt to 53 | reproduce the issue in the given method. Please include a screenshot of 54 | the response in "Try this API". This response should NOT match the current 55 | behavior you are experiencing. If the behavior is the same, it means 56 | that you are likely experiencing a bug with the API itself. In that 57 | case, please submit an issue to the API team, either by submitting an 58 | issue in its issue tracker (https://cloud.google.com/support/docs/issue-trackers), or by 59 | submitting an issue in its linked tracker in the .repo-metadata.json 60 | file https://github.com/googleapis/proto3-json-serializer-nodejs/issues/ 61 | validations: 62 | required: true 63 | - type: input 64 | attributes: 65 | label: > 66 | Link to the code that reproduces this issue. A link to a **public** Github Repository or gist with a minimal 67 | reproduction. 68 | description: > 69 | **Skipping this or providing an invalid link will result in the issue being closed** 70 | validations: 71 | required: true 72 | - type: textarea 73 | attributes: 74 | label: > 75 | A step-by-step description of how to reproduce the issue, based on 76 | the linked reproduction. 77 | description: > 78 | Screenshots can be provided in the issue body below. 79 | placeholder: | 80 | 1. Start the application in development (next dev) 81 | 2. Click X 82 | 3. Y will happen 83 | validations: 84 | required: true 85 | - type: textarea 86 | attributes: 87 | label: A clear and concise description of what the bug is, and what you 88 | expected to happen. 89 | placeholder: Following the steps from the previous section, I expected A to 90 | happen, but I observed B instead 91 | validations: 92 | required: true 93 | 94 | - type: textarea 95 | attributes: 96 | label: A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. ** 97 | placeholder: 'Documentation here(link) states that B should happen instead of A' 98 | validations: 99 | required: true 100 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Google Cloud Support 3 | url: https://cloud.google.com/support/ 4 | about: If you have a support contract with Google, please use the Google Cloud Support portal. 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_request.yml: -------------------------------------------------------------------------------- 1 | name: Documentation Requests 2 | description: Requests for more information 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: > 7 | Please use this issue type to log documentation requests against the library itself. 8 | These requests should involve documentation on Github (`.md` files), and should relate to the library 9 | itself. If you have questions or documentation requests for an API, please 10 | reach out to the API tracker itself. 11 | 12 | Please submit an issue to the API team, either by submitting an 13 | issue in its issue tracker https://cloud.google.com/support/docs/issue-trackers), or by 14 | submitting an issue in its linked tracker in the .repo-metadata.json 15 | file in the API under packages/* ([example](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/)). 16 | You can also submit a request to documentation on cloud.google.com itself with the "Send Feedback" 17 | on the bottom of the page. 18 | 19 | 20 | Please note that documentation requests and questions for specific APIs 21 | will be closed. 22 | - type: checkboxes 23 | attributes: 24 | label: Please make sure you have searched for information in the following 25 | guides. 26 | options: 27 | - label: "Search the issues already opened: 28 | https://github.com/GoogleCloudPlatform/google-cloud-node/issues" 29 | required: true 30 | - label: "Check our Troubleshooting guide: 31 | https://googlecloudplatform.github.io/google-cloud-node/#/docs/guid\ 32 | es/troubleshooting" 33 | required: true 34 | - label: "Check our FAQ: 35 | https://googlecloudplatform.github.io/google-cloud-node/#/docs/guid\ 36 | es/faq" 37 | required: true 38 | - label: "Check our libraries HOW-TO: 39 | https://github.com/googleapis/gax-nodejs/blob/main/client-libraries\ 40 | .md" 41 | required: true 42 | - label: "Check out our authentication guide: 43 | https://github.com/googleapis/google-auth-library-nodejs" 44 | required: true 45 | - label: "Check out handwritten samples for many of our APIs: 46 | https://github.com/GoogleCloudPlatform/nodejs-docs-samples" 47 | required: true 48 | - type: textarea 49 | attributes: 50 | label: > 51 | Documentation Request 52 | validations: 53 | required: true 54 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this library 3 | labels: 4 | - feature request 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: > 9 | **PLEASE READ**: If you have a support contract with Google, please 10 | create an issue in the [support 11 | console](https://cloud.google.com/support/) instead of filing on GitHub. 12 | This will ensure a timely response. Otherwise, please make sure to 13 | follow the steps below. 14 | - type: textarea 15 | attributes: 16 | label: > 17 | A screenshot that you have tested with "Try this API". 18 | description: > 19 | As our client libraries are mostly autogenerated, we kindly request 20 | that you test whether your feature request is with the client library, or with the 21 | API itself. To do so, please search for your API 22 | here: https://developers.google.com/apis-explorer and attempt to 23 | reproduce the issue in the given method. Please include a screenshot of 24 | the response in "Try this API". This response should NOT match the current 25 | behavior you are experiencing. If the behavior is the same, it means 26 | that you are likely requesting a feature for the API itself. In that 27 | case, please submit an issue to the API team, either by submitting an 28 | issue in its issue tracker https://cloud.google.com/support/docs/issue-trackers, or by 29 | submitting an issue in its linked tracker in the .repo-metadata.json 30 | file in the API under packages/* ([example](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/)) 31 | 32 | Example of library specific issues would be: retry strategies, authentication questions, or issues with typings. 33 | Examples of API issues would include: expanding method parameter types, adding functionality to an API. 34 | validations: 35 | required: true 36 | - type: textarea 37 | attributes: 38 | label: > 39 | What would you like to see in the library? 40 | description: > 41 | Screenshots can be provided in the issue body below. 42 | placeholder: | 43 | 1. Set up authentication like so 44 | 2. Run the program like so 45 | 3. X would be nice to happen 46 | 47 | - type: textarea 48 | attributes: 49 | label: Describe alternatives you've considered 50 | 51 | - type: textarea 52 | attributes: 53 | label: Additional context/notes -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/processs_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Process Request 3 | about: Submit a process request to the library. Process requests are any requests related to library infrastructure, for example CI/CD, publishing, releasing, broken links. 4 | --- 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: If you have a question, please use Discussions 4 | 5 | --- 6 | 7 | If you have a general question that goes beyond the library itself, we encourage you to use [Discussions](https://github.com//discussions) 8 | to engage with fellow community members! 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support request 3 | about: If you have a support contract with Google, please create an issue in the Google Cloud Support console. 4 | 5 | --- 6 | 7 | **PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response. 8 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | > Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: 2 | 3 | ## Description 4 | 5 | > Please provide a detailed description for the change. 6 | > As much as possible, please try to keep changes separate by purpose. For example, try not to make a one-line bug fix in a feature request, or add an irrelevant README change to a bug fix. 7 | 8 | ## Impact 9 | 10 | > What's the impact of this change? 11 | 12 | ## Testing 13 | 14 | > Have you added unit and integration tests if necessary? 15 | > Were any tests changed? Are any breaking changes necessary? 16 | 17 | ## Additional Information 18 | 19 | > Any additional details that we should be aware of? 20 | 21 | ## Checklist 22 | 23 | - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea 24 | - [ ] Ensure the tests and linter pass 25 | - [ ] Code coverage does not decrease 26 | - [ ] Appropriate docs were updated 27 | - [ ] Appropriate comments were added, particularly in complex areas or places that require background 28 | - [ ] No new warnings or issues will be generated from this change 29 | 30 | Fixes #issue_number_goes_here 🦕 31 | -------------------------------------------------------------------------------- /.github/auto-approve.yml: -------------------------------------------------------------------------------- 1 | processes: 2 | - "NodeDependency" -------------------------------------------------------------------------------- /.github/auto-label.yaml: -------------------------------------------------------------------------------- 1 | requestsize: 2 | enabled: true 3 | -------------------------------------------------------------------------------- /.github/generated-files-bot.yml: -------------------------------------------------------------------------------- 1 | generatedFiles: 2 | - path: '.kokoro/**' 3 | message: '`.kokoro` files are templated and should be updated in [`synthtool`](https://github.com/googleapis/synthtool)' 4 | - path: '.github/CODEOWNERS' 5 | message: 'CODEOWNERS should instead be modified via the `codeowner_team` property in .repo-metadata.json' 6 | - path: '.github/workflows/ci.yaml' 7 | message: '`.github/workflows/ci.yaml` (GitHub Actions) should be updated in [`synthtool`](https://github.com/googleapis/synthtool)' 8 | - path: '.github/generated-files-bot.+(yml|yaml)' 9 | message: '`.github/generated-files-bot.(yml|yaml)` should be updated in [`synthtool`](https://github.com/googleapis/synthtool)' 10 | - path: 'README.md' 11 | message: '`README.md` is managed by [`synthtool`](https://github.com/googleapis/synthtool). However, a partials file can be used to update the README, e.g.: https://github.com/googleapis/nodejs-storage/blob/main/.readme-partials.yaml' 12 | - path: 'samples/README.md' 13 | message: '`samples/README.md` is managed by [`synthtool`](https://github.com/googleapis/synthtool). However, a partials file can be used to update the README, e.g.: https://github.com/googleapis/nodejs-storage/blob/main/.readme-partials.yaml' 14 | ignoreAuthors: 15 | - 'gcf-owl-bot[bot]' 16 | - 'yoshi-automation' 17 | -------------------------------------------------------------------------------- /.github/release-please.yml: -------------------------------------------------------------------------------- 1 | handleGHRelease: true 2 | releaseType: node 3 | -------------------------------------------------------------------------------- /.github/release-trigger.yml: -------------------------------------------------------------------------------- 1 | enabled: true 2 | multiScmName: proto3-json-serializer-nodejs -------------------------------------------------------------------------------- /.github/scripts/close-invalid-link.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const fs = require('fs'); 16 | const yaml = require('js-yaml'); 17 | const path = require('path'); 18 | const TEMPLATE_FILE_PATH = path.resolve(__dirname, '../ISSUE_TEMPLATE/bug_report.yml') 19 | 20 | async function closeIssue(github, owner, repo, number) { 21 | await github.rest.issues.createComment({ 22 | owner: owner, 23 | repo: repo, 24 | issue_number: number, 25 | body: "Issue was opened with an invalid reproduction link. Please make sure the repository is a valid, publicly-accessible github repository, and make sure the url is complete (example: https://github.com/googleapis/google-cloud-node)" 26 | }); 27 | await github.rest.issues.update({ 28 | owner: owner, 29 | repo: repo, 30 | issue_number: number, 31 | state: "closed" 32 | }); 33 | } 34 | module.exports = async ({ github, context }) => { 35 | const owner = context.repo.owner; 36 | const repo = context.repo.repo; 37 | const number = context.issue.number; 38 | 39 | const issue = await github.rest.issues.get({ 40 | owner: owner, 41 | repo: repo, 42 | issue_number: number, 43 | }); 44 | 45 | const yamlData = fs.readFileSync(TEMPLATE_FILE_PATH, 'utf8'); 46 | const obj = yaml.load(yamlData); 47 | const linkMatchingText = (obj.body.find(x => {return x.type === 'input' && x.validations.required === true && x.attributes.label.includes('link')})).attributes.label; 48 | const isBugTemplate = issue.data.body.includes(linkMatchingText); 49 | 50 | if (isBugTemplate) { 51 | console.log(`Issue ${number} is a bug template`) 52 | try { 53 | const text = issue.data.body; 54 | const match = text.indexOf(linkMatchingText); 55 | if (match !== -1) { 56 | const nextLineIndex = text.indexOf('http', match); 57 | if (nextLineIndex == -1) { 58 | await closeIssue(github, owner, repo, number); 59 | return; 60 | } 61 | const link = text.substring(nextLineIndex, text.indexOf('\n', nextLineIndex)); 62 | console.log(`Issue ${number} contains this link: ${link}`); 63 | const isValidLink = (await fetch(link)).ok; 64 | console.log(`Issue ${number} has a ${isValidLink ? "valid" : "invalid"} link`) 65 | if (!isValidLink) { 66 | await closeIssue(github, owner, repo, number); 67 | } 68 | } 69 | } catch (err) { 70 | await closeIssue(github, owner, repo, number); 71 | } 72 | } 73 | }; -------------------------------------------------------------------------------- /.github/scripts/close-unresponsive.cjs: -------------------------------------------------------------------------------- 1 | /// Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | function labeledEvent(data) { 16 | return data.event === "labeled" && data.label.name === "needs more info"; 17 | } 18 | 19 | const numberOfDaysLimit = 15; 20 | const close_message = `This has been closed since a request for information has \ 21 | not been answered for ${numberOfDaysLimit} days. It can be reopened when the \ 22 | requested information is provided.`; 23 | 24 | module.exports = async ({ github, context }) => { 25 | const owner = context.repo.owner; 26 | const repo = context.repo.repo; 27 | 28 | const issues = await github.rest.issues.listForRepo({ 29 | owner: owner, 30 | repo: repo, 31 | labels: "needs more info", 32 | }); 33 | const numbers = issues.data.map((e) => e.number); 34 | 35 | for (const number of numbers) { 36 | const events = await github.paginate( 37 | github.rest.issues.listEventsForTimeline, 38 | { 39 | owner: owner, 40 | repo: repo, 41 | issue_number: number, 42 | }, 43 | (response) => response.data.filter(labeledEvent) 44 | ); 45 | 46 | const latest_response_label = events[events.length - 1]; 47 | 48 | const created_at = new Date(latest_response_label.created_at); 49 | const now = new Date(); 50 | const diff = now - created_at; 51 | const diffDays = diff / (1000 * 60 * 60 * 24); 52 | 53 | if (diffDays > numberOfDaysLimit) { 54 | await github.rest.issues.update({ 55 | owner: owner, 56 | repo: repo, 57 | issue_number: number, 58 | state: "closed", 59 | }); 60 | 61 | await github.rest.issues.createComment({ 62 | owner: owner, 63 | repo: repo, 64 | issue_number: number, 65 | body: close_message, 66 | }); 67 | } 68 | } 69 | }; -------------------------------------------------------------------------------- /.github/scripts/fixtures/invalidIssueBody.txt: -------------------------------------------------------------------------------- 1 | ### Please make sure you have searched for information in the following guides. 2 | 3 | - [X] Search the issues already opened: https://github.com/GoogleCloudPlatform/google-cloud-node/issues 4 | - [X] Search StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-platform+node.js 5 | - [X] Check our Troubleshooting guide: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/troubleshooting 6 | - [X] Check our FAQ: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/faq 7 | - [X] Check our libraries HOW-TO: https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md 8 | - [X] Check out our authentication guide: https://github.com/googleapis/google-auth-library-nodejs 9 | - [X] Check out handwritten samples for many of our APIs: https://github.com/GoogleCloudPlatform/nodejs-docs-samples 10 | 11 | ### A screenshot that you have tested with "Try this API". 12 | 13 | 14 | N/A 15 | 16 | ### Link to the code that reproduces this issue. A link to a **public** Github Repository or gist with a minimal reproduction. 17 | 18 | not-a-link 19 | 20 | ### A step-by-step description of how to reproduce the issue, based on the linked reproduction. 21 | 22 | 23 | Change MY_PROJECT to your project name, add credentials if needed and run. 24 | 25 | ### A clear and concise description of what the bug is, and what you expected to happen. 26 | 27 | The application crashes with the following exception (which there is no way to catch). It should just emit error, and allow graceful handling. 28 | TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Object 29 | at _write (node:internal/streams/writable:474:13) 30 | at Writable.write (node:internal/streams/writable:502:10) 31 | at Duplexify._write (/project/node_modules/duplexify/index.js:212:22) 32 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 33 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 34 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 35 | at Pumpify. (/project/node_modules/@google-cloud/speech/build/src/helpers.js:79:27) 36 | at Object.onceWrapper (node:events:633:26) 37 | at Pumpify.emit (node:events:518:28) 38 | at obj. [as _write] (/project/node_modules/stubs/index.js:28:22) 39 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 40 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 41 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 42 | at PassThrough.ondata (node:internal/streams/readable:1007:22) 43 | at PassThrough.emit (node:events:518:28) 44 | at addChunk (node:internal/streams/readable:559:12) { 45 | code: 'ERR_INVALID_ARG_TYPE' 46 | 47 | 48 | ### A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. ** 49 | 50 | No library should crash an application this way. -------------------------------------------------------------------------------- /.github/scripts/fixtures/validIssueBody.txt: -------------------------------------------------------------------------------- 1 | ### Please make sure you have searched for information in the following guides. 2 | 3 | - [X] Search the issues already opened: https://github.com/GoogleCloudPlatform/google-cloud-node/issues 4 | - [X] Search StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-platform+node.js 5 | - [X] Check our Troubleshooting guide: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/troubleshooting 6 | - [X] Check our FAQ: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/faq 7 | - [X] Check our libraries HOW-TO: https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md 8 | - [X] Check out our authentication guide: https://github.com/googleapis/google-auth-library-nodejs 9 | - [X] Check out handwritten samples for many of our APIs: https://github.com/GoogleCloudPlatform/nodejs-docs-samples 10 | 11 | ### A screenshot that you have tested with "Try this API". 12 | 13 | 14 | N/A 15 | 16 | ### Link to the code that reproduces this issue. A link to a **public** Github Repository or gist with a minimal reproduction. 17 | 18 | https://gist.github.com/orgads/13cbf44c91923da27d8772b5f10489c9 19 | 20 | ### A step-by-step description of how to reproduce the issue, based on the linked reproduction. 21 | 22 | 23 | Change MY_PROJECT to your project name, add credentials if needed and run. 24 | 25 | ### A clear and concise description of what the bug is, and what you expected to happen. 26 | 27 | The application crashes with the following exception (which there is no way to catch). It should just emit error, and allow graceful handling. 28 | TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Object 29 | at _write (node:internal/streams/writable:474:13) 30 | at Writable.write (node:internal/streams/writable:502:10) 31 | at Duplexify._write (/project/node_modules/duplexify/index.js:212:22) 32 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 33 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 34 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 35 | at Pumpify. (/project/node_modules/@google-cloud/speech/build/src/helpers.js:79:27) 36 | at Object.onceWrapper (node:events:633:26) 37 | at Pumpify.emit (node:events:518:28) 38 | at obj. [as _write] (/project/node_modules/stubs/index.js:28:22) 39 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 40 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 41 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 42 | at PassThrough.ondata (node:internal/streams/readable:1007:22) 43 | at PassThrough.emit (node:events:518:28) 44 | at addChunk (node:internal/streams/readable:559:12) { 45 | code: 'ERR_INVALID_ARG_TYPE' 46 | 47 | 48 | ### A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. ** 49 | 50 | No library should crash an application this way. -------------------------------------------------------------------------------- /.github/scripts/fixtures/validIssueBodyDifferentLinkLocation.txt: -------------------------------------------------------------------------------- 1 | ### Please make sure you have searched for information in the following guides. 2 | 3 | - [X] Search the issues already opened: https://github.com/GoogleCloudPlatform/google-cloud-node/issues 4 | - [X] Search StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-platform+node.js 5 | - [X] Check our Troubleshooting guide: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/troubleshooting 6 | - [X] Check our FAQ: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/faq 7 | - [X] Check our libraries HOW-TO: https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md 8 | - [X] Check out our authentication guide: https://github.com/googleapis/google-auth-library-nodejs 9 | - [X] Check out handwritten samples for many of our APIs: https://github.com/GoogleCloudPlatform/nodejs-docs-samples 10 | 11 | ### A screenshot that you have tested with "Try this API". 12 | 13 | 14 | N/A 15 | 16 | ### A step-by-step description of how to reproduce the issue, based on the linked reproduction. 17 | 18 | 19 | Change MY_PROJECT to your project name, add credentials if needed and run. 20 | 21 | ### A clear and concise description of what the bug is, and what you expected to happen. 22 | 23 | The application crashes with the following exception (which there is no way to catch). It should just emit error, and allow graceful handling. 24 | TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Object 25 | at _write (node:internal/streams/writable:474:13) 26 | at Writable.write (node:internal/streams/writable:502:10) 27 | at Duplexify._write (/project/node_modules/duplexify/index.js:212:22) 28 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 29 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 30 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 31 | at Pumpify. (/project/node_modules/@google-cloud/speech/build/src/helpers.js:79:27) 32 | at Object.onceWrapper (node:events:633:26) 33 | at Pumpify.emit (node:events:518:28) 34 | at obj. [as _write] (/project/node_modules/stubs/index.js:28:22) 35 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 36 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 37 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 38 | at PassThrough.ondata (node:internal/streams/readable:1007:22) 39 | at PassThrough.emit (node:events:518:28) 40 | at addChunk (node:internal/streams/readable:559:12) { 41 | code: 'ERR_INVALID_ARG_TYPE' 42 | 43 | ### Link to the code that reproduces this issue. A link to a **public** Github Repository with a minimal reproduction. 44 | 45 | 46 | https://gist.github.com/orgads/13cbf44c91923da27d8772b5f10489c9 47 | 48 | ### A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. ** 49 | 50 | No library should crash an application this way. -------------------------------------------------------------------------------- /.github/scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tests", 3 | "private": true, 4 | "description": "tests for script", 5 | "scripts": { 6 | "test": "mocha tests/close-invalid-link.test.cjs && mocha tests/close-or-remove-response-label.test.cjs" 7 | }, 8 | "author": "Google Inc.", 9 | "license": "Apache-2.0", 10 | "engines": { 11 | "node": ">=18" 12 | }, 13 | "dependencies": { 14 | "js-yaml": "^4.1.0" 15 | }, 16 | "devDependencies": { 17 | "@octokit/rest": "^19.0.0", 18 | "mocha": "^10.0.0", 19 | "sinon": "^18.0.0" 20 | } 21 | } -------------------------------------------------------------------------------- /.github/scripts/remove-response-label.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module.exports = async ({ github, context }) => { 16 | const commenter = context.actor; 17 | const issue = await github.rest.issues.get({ 18 | owner: context.repo.owner, 19 | repo: context.repo.repo, 20 | issue_number: context.issue.number, 21 | }); 22 | const author = issue.data.user.login; 23 | const labels = issue.data.labels.map((e) => e.name); 24 | 25 | if (author === commenter && labels.includes("needs more info")) { 26 | await github.rest.issues.removeLabel({ 27 | owner: context.repo.owner, 28 | repo: context.repo.repo, 29 | issue_number: context.issue.number, 30 | name: "needs more info", 31 | }); 32 | } 33 | }; -------------------------------------------------------------------------------- /.github/scripts/tests/close-invalid-link.test.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | const { describe, it } = require('mocha'); 18 | const closeInvalidLink = require('../close-invalid-link.cjs'); 19 | const fs = require('fs'); 20 | const sinon = require('sinon'); 21 | 22 | describe('close issues with invalid links', () => { 23 | let octokitStub; 24 | let issuesStub; 25 | 26 | beforeEach(() => { 27 | issuesStub = { 28 | get: sinon.stub(), 29 | createComment: sinon.stub(), 30 | update: sinon.stub(), 31 | }; 32 | octokitStub = { 33 | rest: { 34 | issues: issuesStub, 35 | }, 36 | }; 37 | }); 38 | 39 | afterEach(() => { 40 | sinon.restore(); 41 | }); 42 | 43 | it('does not do anything if it is not a bug', async () => { 44 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 45 | issuesStub.get.resolves({ data: { body: "I'm having a problem with this." } }); 46 | 47 | await closeInvalidLink({ github: octokitStub, context }); 48 | 49 | sinon.assert.calledOnce(issuesStub.get); 50 | sinon.assert.notCalled(issuesStub.createComment); 51 | sinon.assert.notCalled(issuesStub.update); 52 | }); 53 | 54 | it('does not do anything if it is a bug with an appropriate link', async () => { 55 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 56 | issuesStub.get.resolves({ data: { body: fs.readFileSync('./fixtures/validIssueBody.txt', 'utf-8') } }); 57 | 58 | await closeInvalidLink({ github: octokitStub, context }); 59 | 60 | sinon.assert.calledOnce(issuesStub.get); 61 | sinon.assert.notCalled(issuesStub.createComment); 62 | sinon.assert.notCalled(issuesStub.update); 63 | }); 64 | 65 | it('does not do anything if it is a bug with an appropriate link and the template changes', async () => { 66 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 67 | issuesStub.get.resolves({ data: { body: fs.readFileSync('./fixtures/validIssueBodyDifferentLinkLocation.txt', 'utf-8') } }); 68 | 69 | await closeInvalidLink({ github: octokitStub, context }); 70 | 71 | sinon.assert.calledOnce(issuesStub.get); 72 | sinon.assert.notCalled(issuesStub.createComment); 73 | sinon.assert.notCalled(issuesStub.update); 74 | }); 75 | 76 | it('closes the issue if the link is invalid', async () => { 77 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 78 | issuesStub.get.resolves({ data: { body: fs.readFileSync('./fixtures/invalidIssueBody.txt', 'utf-8') } }); 79 | 80 | await closeInvalidLink({ github: octokitStub, context }); 81 | 82 | sinon.assert.calledOnce(issuesStub.get); 83 | sinon.assert.calledOnce(issuesStub.createComment); 84 | sinon.assert.calledOnce(issuesStub.update); 85 | }); 86 | }); -------------------------------------------------------------------------------- /.github/scripts/tests/close-or-remove-response-label.test.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | const { describe, it, beforeEach, afterEach } = require('mocha'); 18 | const removeResponseLabel = require('../remove-response-label.cjs'); 19 | const closeUnresponsive = require('../close-unresponsive.cjs'); 20 | const sinon = require('sinon'); 21 | 22 | function getISODateDaysAgo(days) { 23 | const today = new Date(); 24 | const daysAgo = new Date(today.setDate(today.getDate() - days)); 25 | return daysAgo.toISOString(); 26 | } 27 | 28 | describe('close issues or remove needs more info labels', () => { 29 | let octokitStub; 30 | let issuesStub; 31 | let paginateStub; 32 | 33 | beforeEach(() => { 34 | issuesStub = { 35 | listForRepo: sinon.stub(), 36 | update: sinon.stub(), 37 | createComment: sinon.stub(), 38 | get: sinon.stub(), 39 | removeLabel: sinon.stub(), 40 | }; 41 | paginateStub = sinon.stub(); 42 | octokitStub = { 43 | rest: { 44 | issues: issuesStub, 45 | }, 46 | paginate: paginateStub, 47 | }; 48 | }); 49 | 50 | afterEach(() => { 51 | sinon.restore(); 52 | }); 53 | 54 | it('closes the issue if the OP has not responded within the allotted time and there is a needs-more-info label', async () => { 55 | const context = { owner: 'testOrg', repo: 'testRepo' }; 56 | const issuesInRepo = [{ user: { login: 'OP' }, labels: [{ name: 'needs more info' }] }]; 57 | const eventsInIssue = [{ event: 'labeled', label: { name: 'needs more info' }, created_at: getISODateDaysAgo(16) }]; 58 | 59 | issuesStub.listForRepo.resolves({ data: issuesInRepo }); 60 | paginateStub.resolves(eventsInIssue); 61 | 62 | await closeUnresponsive({ github: octokitStub, context }); 63 | 64 | sinon.assert.calledOnce(issuesStub.listForRepo); 65 | sinon.assert.calledOnce(paginateStub); 66 | sinon.assert.calledOnce(issuesStub.update); 67 | sinon.assert.calledOnce(issuesStub.createComment); 68 | }); 69 | 70 | it('does nothing if not enough time has passed and there is a needs-more-info label', async () => { 71 | const context = { owner: 'testOrg', repo: 'testRepo' }; 72 | const issuesInRepo = [{ user: { login: 'OP' }, labels: [{ name: 'needs more info' }] }]; 73 | const eventsInIssue = [{ event: 'labeled', label: { name: 'needs more info' }, created_at: getISODateDaysAgo(14) }]; 74 | 75 | issuesStub.listForRepo.resolves({ data: issuesInRepo }); 76 | paginateStub.resolves(eventsInIssue); 77 | 78 | await closeUnresponsive({ github: octokitStub, context }); 79 | 80 | sinon.assert.calledOnce(issuesStub.listForRepo); 81 | sinon.assert.calledOnce(paginateStub); 82 | sinon.assert.notCalled(issuesStub.update); 83 | sinon.assert.notCalled(issuesStub.createComment); 84 | }); 85 | 86 | it('removes the label if OP responded', async () => { 87 | const context = { actor: 'OP', repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 88 | const issueContext = { user: {login: 'OP'}, labels: [{ name: 'needs more info' }] }; 89 | 90 | issuesStub.get.resolves({ data: issueContext }); 91 | 92 | await removeResponseLabel({ github: octokitStub, context }); 93 | 94 | sinon.assert.calledOnce(issuesStub.get); 95 | sinon.assert.calledOnce(issuesStub.removeLabel); 96 | }); 97 | 98 | it('does not remove the label if author responded', async () => { 99 | const context = { actor: 'repo-maintainer', repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 100 | const issueContext = { user: {login: 'OP'}, labels: [{ name: 'needs more info' }] }; 101 | 102 | issuesStub.get.resolves({ data: issueContext }); 103 | 104 | await removeResponseLabel({ github: octokitStub, context }); 105 | 106 | sinon.assert.calledOnce(issuesStub.get); 107 | sinon.assert.notCalled(issuesStub.removeLabel); 108 | }); 109 | }); -------------------------------------------------------------------------------- /.github/sync-repo-settings.yaml: -------------------------------------------------------------------------------- 1 | branchProtectionRules: 2 | - pattern: main 3 | isAdminEnforced: true 4 | requiredApprovingReviewCount: 1 5 | requiresCodeOwnerReviews: true 6 | requiresStrictStatusChecks: true 7 | requiredStatusCheckContexts: 8 | - "ci/kokoro: Samples test" 9 | - "ci/kokoro: System test" 10 | - lint 11 | - test (18) 12 | - test (20) 13 | - test (22) 14 | - cla/google 15 | - windows 16 | - OwlBot Post Processor 17 | permissionRules: 18 | - team: yoshi-admins 19 | permission: admin 20 | - team: jsteam-admins 21 | permission: admin 22 | - team: jsteam 23 | permission: push 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | name: ci 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node: [18, 20, 22] 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: ${{ matrix.node }} 18 | - run: node --version 19 | # The first installation step ensures that all of our production 20 | # dependencies work on the given Node.js version, this helps us find 21 | # dependencies that don't match our engines field: 22 | - run: npm install --production --engine-strict --ignore-scripts --no-package-lock 23 | # Clean up the production install, before installing dev/production: 24 | - run: rm -rf node_modules 25 | - run: npm install --engine-strict 26 | - run: npm test 27 | env: 28 | MOCHA_THROW_DEPRECATION: false 29 | test-script: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v4 33 | - uses: actions/setup-node@v4 34 | with: 35 | node-version: 18 36 | - run: node --version 37 | - run: npm install --engine-strict 38 | working-directory: .github/scripts 39 | - run: npm test 40 | working-directory: .github/scripts 41 | env: 42 | MOCHA_THROW_DEPRECATION: false 43 | windows: 44 | runs-on: windows-latest 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: actions/setup-node@v4 48 | with: 49 | node-version: 18 50 | - run: npm install --engine-strict 51 | - run: npm test 52 | env: 53 | MOCHA_THROW_DEPRECATION: false 54 | lint: 55 | runs-on: ubuntu-latest 56 | steps: 57 | - uses: actions/checkout@v4 58 | - uses: actions/setup-node@v4 59 | with: 60 | node-version: 18 61 | - run: npm install 62 | - run: npm run lint 63 | docs: 64 | runs-on: ubuntu-latest 65 | steps: 66 | - uses: actions/checkout@v4 67 | - uses: actions/setup-node@v4 68 | with: 69 | node-version: 18 70 | - run: npm install 71 | - run: npm run docs 72 | - uses: JustinBeckwith/linkinator-action@v1 73 | with: 74 | paths: docs/ 75 | -------------------------------------------------------------------------------- /.github/workflows/issues-no-repro.yaml: -------------------------------------------------------------------------------- 1 | name: invalid_link 2 | on: 3 | issues: 4 | types: [opened, reopened] 5 | 6 | jobs: 7 | close: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: 18 17 | - run: npm install 18 | working-directory: ./.github/scripts 19 | - uses: actions/github-script@v7 20 | with: 21 | script: | 22 | const script = require('./.github/scripts/close-invalid-link.cjs') 23 | await script({github, context}) 24 | -------------------------------------------------------------------------------- /.github/workflows/response.yaml: -------------------------------------------------------------------------------- 1 | name: no_response 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' # Run every day at 01:30 5 | workflow_dispatch: 6 | issue_comment: 7 | 8 | jobs: 9 | close: 10 | if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' 11 | runs-on: ubuntu-latest 12 | permissions: 13 | issues: write 14 | pull-requests: write 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/github-script@v7 18 | with: 19 | script: | 20 | const script = require('./.github/scripts/close-unresponsive.cjs') 21 | await script({github, context}) 22 | 23 | remove_label: 24 | if: github.event_name == 'issue_comment' 25 | runs-on: ubuntu-latest 26 | permissions: 27 | issues: write 28 | pull-requests: write 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: actions/github-script@v7 32 | with: 33 | script: | 34 | const script = require('./.github/scripts/remove-response-label.cjs') 35 | await script({github, context}) 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .vscode 4 | package-lock.json 5 | samples/node_modules 6 | samples/package-lock.json 7 | samples/coverage 8 | coverage 9 | .coverage 10 | .DS_Store 11 | __pycache__ 12 | .tgz 13 | -------------------------------------------------------------------------------- /.jsdoc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | 'use strict'; 17 | 18 | module.exports = { 19 | opts: { 20 | readme: './README.md', 21 | package: './package.json', 22 | template: './node_modules/jsdoc-fresh', 23 | recurse: true, 24 | verbose: true, 25 | destination: './docs/' 26 | }, 27 | plugins: [ 28 | 'plugins/markdown', 29 | 'jsdoc-region-tag' 30 | ], 31 | source: { 32 | excludePattern: '(^|\\/|\\\\)[._]', 33 | include: [ 34 | 'build/src' 35 | ], 36 | includePattern: '\\.js$' 37 | }, 38 | templates: { 39 | copyright: 'Copyright 2021 Google, LLC.', 40 | includeDate: false, 41 | sourceFiles: false, 42 | systemName: 'proto3-json-serializer', 43 | theme: 'lumen' 44 | }, 45 | markdown: { 46 | idInHeadings: true 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /.kokoro/.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-generated=true 2 | -------------------------------------------------------------------------------- /.kokoro/common.cfg: -------------------------------------------------------------------------------- 1 | # Format: //devtools/kokoro/config/proto/build.proto 2 | 3 | # Build logs will be here 4 | action { 5 | define_artifacts { 6 | regex: "**/*sponge_log.xml" 7 | } 8 | } 9 | 10 | # Download trampoline resources. 11 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 12 | 13 | # Use the trampoline script to run in docker. 14 | build_file: "proto3-json-serializer-nodejs/.kokoro/trampoline_v2.sh" 15 | 16 | # Configure the docker image for kokoro-trampoline. 17 | env_vars: { 18 | key: "TRAMPOLINE_IMAGE" 19 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user" 20 | } 21 | env_vars: { 22 | key: "TRAMPOLINE_BUILD_FILE" 23 | value: "github/proto3-json-serializer-nodejs/.kokoro/test.sh" 24 | } 25 | -------------------------------------------------------------------------------- /.kokoro/continuous/node12/common.cfg: -------------------------------------------------------------------------------- 1 | # Format: //devtools/kokoro/config/proto/build.proto 2 | 3 | # Build logs will be here 4 | action { 5 | define_artifacts { 6 | regex: "**/*sponge_log.xml" 7 | } 8 | } 9 | 10 | # Download trampoline resources. 11 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 12 | 13 | # Use the trampoline script to run in docker. 14 | build_file: "proto3-json-serializer-nodejs/.kokoro/trampoline_v2.sh" 15 | 16 | # Configure the docker image for kokoro-trampoline. 17 | env_vars: { 18 | key: "TRAMPOLINE_IMAGE" 19 | value: "gcr.io/cloud-devrel-kokoro-resources/node:12-user" 20 | } 21 | env_vars: { 22 | key: "TRAMPOLINE_BUILD_FILE" 23 | value: "github/proto3-json-serializer-nodejs/.kokoro/test.sh" 24 | } 25 | -------------------------------------------------------------------------------- /.kokoro/continuous/node12/lint.cfg: -------------------------------------------------------------------------------- 1 | env_vars: { 2 | key: "TRAMPOLINE_BUILD_FILE" 3 | value: "github/proto3-json-serializer-nodejs/.kokoro/lint.sh" 4 | } 5 | -------------------------------------------------------------------------------- /.kokoro/continuous/node12/samples-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/proto3-json-serializer-nodejs/.kokoro/samples-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.kokoro/continuous/node12/system-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/proto3-json-serializer-nodejs/.kokoro/system-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.kokoro/continuous/node12/test.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/proto3-json-serializer-nodejs/ab2a4ee42ddf344312bd532eda7f0d2abaa445f1/.kokoro/continuous/node12/test.cfg -------------------------------------------------------------------------------- /.kokoro/continuous/node18/common.cfg: -------------------------------------------------------------------------------- 1 | # Format: //devtools/kokoro/config/proto/build.proto 2 | 3 | # Build logs will be here 4 | action { 5 | define_artifacts { 6 | regex: "**/*sponge_log.xml" 7 | } 8 | } 9 | 10 | # Download trampoline resources. 11 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 12 | 13 | # Use the trampoline script to run in docker. 14 | build_file: "proto3-json-serializer-nodejs/.kokoro/trampoline_v2.sh" 15 | 16 | # Configure the docker image for kokoro-trampoline. 17 | env_vars: { 18 | key: "TRAMPOLINE_IMAGE" 19 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user" 20 | } 21 | env_vars: { 22 | key: "TRAMPOLINE_BUILD_FILE" 23 | value: "github/proto3-json-serializer-nodejs/.kokoro/test.sh" 24 | } 25 | -------------------------------------------------------------------------------- /.kokoro/continuous/node18/lint.cfg: -------------------------------------------------------------------------------- 1 | env_vars: { 2 | key: "TRAMPOLINE_BUILD_FILE" 3 | value: "github/proto3-json-serializer-nodejs/.kokoro/lint.sh" 4 | } 5 | -------------------------------------------------------------------------------- /.kokoro/continuous/node18/samples-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/proto3-json-serializer-nodejs/.kokoro/samples-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.kokoro/continuous/node18/system-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/proto3-json-serializer-nodejs/.kokoro/system-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.kokoro/continuous/node18/test.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/proto3-json-serializer-nodejs/ab2a4ee42ddf344312bd532eda7f0d2abaa445f1/.kokoro/continuous/node18/test.cfg -------------------------------------------------------------------------------- /.kokoro/docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | cd $(dirname $0)/.. 22 | 23 | npm install 24 | 25 | npm run docs-test 26 | -------------------------------------------------------------------------------- /.kokoro/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | cd $(dirname $0)/.. 22 | 23 | npm install 24 | 25 | # Install and link samples 26 | if [ -f samples/package.json ]; then 27 | cd samples/ 28 | npm link ../ 29 | npm install 30 | cd .. 31 | fi 32 | 33 | npm run lint 34 | -------------------------------------------------------------------------------- /.kokoro/populate-secrets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2020 Google LLC. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # This file is called in the early stage of `trampoline_v2.sh` to 17 | # populate secrets needed for the CI builds. 18 | 19 | set -eo pipefail 20 | 21 | function now { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n' ;} 22 | function msg { println "$*" >&2 ;} 23 | function println { printf '%s\n' "$(now) $*" ;} 24 | 25 | # Populates requested secrets set in SECRET_MANAGER_KEYS 26 | 27 | # In Kokoro CI builds, we use the service account attached to the 28 | # Kokoro VM. This means we need to setup auth on other CI systems. 29 | # For local run, we just use the gcloud command for retrieving the 30 | # secrets. 31 | 32 | if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then 33 | GCLOUD_COMMANDS=( 34 | "docker" 35 | "run" 36 | "--entrypoint=gcloud" 37 | "--volume=${KOKORO_GFILE_DIR}:${KOKORO_GFILE_DIR}" 38 | "gcr.io/google.com/cloudsdktool/cloud-sdk" 39 | ) 40 | if [[ "${TRAMPOLINE_CI:-}" == "kokoro" ]]; then 41 | SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" 42 | else 43 | echo "Authentication for this CI system is not implemented yet." 44 | exit 2 45 | # TODO: Determine appropriate SECRET_LOCATION and the GCLOUD_COMMANDS. 46 | fi 47 | else 48 | # For local run, use /dev/shm or temporary directory for 49 | # KOKORO_GFILE_DIR. 50 | if [[ -d "/dev/shm" ]]; then 51 | export KOKORO_GFILE_DIR=/dev/shm 52 | else 53 | export KOKORO_GFILE_DIR=$(mktemp -d -t ci-XXXXXXXX) 54 | fi 55 | SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" 56 | GCLOUD_COMMANDS=("gcloud") 57 | fi 58 | 59 | msg "Creating folder on disk for secrets: ${SECRET_LOCATION}" 60 | mkdir -p ${SECRET_LOCATION} 61 | 62 | for key in $(echo ${SECRET_MANAGER_KEYS} | sed "s/,/ /g") 63 | do 64 | msg "Retrieving secret ${key}" 65 | "${GCLOUD_COMMANDS[@]}" \ 66 | secrets versions access latest \ 67 | --project cloud-devrel-kokoro-resources \ 68 | --secret $key > \ 69 | "$SECRET_LOCATION/$key" 70 | if [[ $? == 0 ]]; then 71 | msg "Secret written to ${SECRET_LOCATION}/${key}" 72 | else 73 | msg "Error retrieving secret ${key}" 74 | exit 2 75 | fi 76 | done 77 | -------------------------------------------------------------------------------- /.kokoro/presubmit/node12/common.cfg: -------------------------------------------------------------------------------- 1 | # Format: //devtools/kokoro/config/proto/build.proto 2 | 3 | # Build logs will be here 4 | action { 5 | define_artifacts { 6 | regex: "**/*sponge_log.xml" 7 | } 8 | } 9 | 10 | # Download trampoline resources. 11 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 12 | 13 | # Use the trampoline script to run in docker. 14 | build_file: "proto3-json-serializer-nodejs/.kokoro/trampoline_v2.sh" 15 | 16 | # Configure the docker image for kokoro-trampoline. 17 | env_vars: { 18 | key: "TRAMPOLINE_IMAGE" 19 | value: "gcr.io/cloud-devrel-kokoro-resources/node:12-user" 20 | } 21 | env_vars: { 22 | key: "TRAMPOLINE_BUILD_FILE" 23 | value: "github/proto3-json-serializer-nodejs/.kokoro/test.sh" 24 | } 25 | -------------------------------------------------------------------------------- /.kokoro/presubmit/node12/samples-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/proto3-json-serializer-nodejs/.kokoro/samples-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.kokoro/presubmit/node12/system-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/proto3-json-serializer-nodejs/.kokoro/system-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.kokoro/presubmit/node12/test.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/proto3-json-serializer-nodejs/ab2a4ee42ddf344312bd532eda7f0d2abaa445f1/.kokoro/presubmit/node12/test.cfg -------------------------------------------------------------------------------- /.kokoro/presubmit/node18/common.cfg: -------------------------------------------------------------------------------- 1 | # Format: //devtools/kokoro/config/proto/build.proto 2 | 3 | # Build logs will be here 4 | action { 5 | define_artifacts { 6 | regex: "**/*sponge_log.xml" 7 | } 8 | } 9 | 10 | # Download trampoline resources. 11 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 12 | 13 | # Use the trampoline script to run in docker. 14 | build_file: "proto3-json-serializer-nodejs/.kokoro/trampoline_v2.sh" 15 | 16 | # Configure the docker image for kokoro-trampoline. 17 | env_vars: { 18 | key: "TRAMPOLINE_IMAGE" 19 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user" 20 | } 21 | env_vars: { 22 | key: "TRAMPOLINE_BUILD_FILE" 23 | value: "github/proto3-json-serializer-nodejs/.kokoro/test.sh" 24 | } 25 | -------------------------------------------------------------------------------- /.kokoro/presubmit/node18/samples-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/proto3-json-serializer-nodejs/.kokoro/samples-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.kokoro/presubmit/node18/system-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/proto3-json-serializer-nodejs/.kokoro/system-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.kokoro/presubmit/node18/test.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/proto3-json-serializer-nodejs/ab2a4ee42ddf344312bd532eda7f0d2abaa445f1/.kokoro/presubmit/node18/test.cfg -------------------------------------------------------------------------------- /.kokoro/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | # Start the releasetool reporter 22 | python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source /tmp/publisher-script 23 | 24 | cd $(dirname $0)/.. 25 | 26 | NPM_TOKEN=$(cat $KOKORO_KEYSTORE_DIR/73713_google-cloud-npm-token-1) 27 | echo "//wombat-dressing-room.appspot.com/:_authToken=${NPM_TOKEN}" > ~/.npmrc 28 | 29 | npm install 30 | npm pack . 31 | # npm provides no way to specify, observe, or predict the name of the tarball 32 | # file it generates. We have to look in the current directory for the freshest 33 | # .tgz file. 34 | TARBALL=$(ls -1 -t *.tgz | head -1) 35 | 36 | npm publish --access=public --registry=https://wombat-dressing-room.appspot.com "$TARBALL" 37 | 38 | # Kokoro collects *.tgz and package-lock.json files and stores them in Placer 39 | # so we can generate SBOMs and attestations. 40 | # However, we *don't* want Kokoro to collect package-lock.json and *.tgz files 41 | # that happened to be installed with dependencies. 42 | find node_modules -name package-lock.json -o -name "*.tgz" | xargs rm -f -------------------------------------------------------------------------------- /.kokoro/release/docs-devsite.cfg: -------------------------------------------------------------------------------- 1 | # service account used to publish up-to-date docs. 2 | before_action { 3 | fetch_keystore { 4 | keystore_resource { 5 | keystore_config_id: 73713 6 | keyname: "docuploader_service_account" 7 | } 8 | } 9 | } 10 | 11 | # doc publications use a Python image. 12 | env_vars: { 13 | key: "TRAMPOLINE_IMAGE" 14 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user" 15 | } 16 | 17 | # Download trampoline resources. 18 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 19 | 20 | # Use the trampoline script to run in docker. 21 | build_file: "proto3-json-serializer-nodejs/.kokoro/trampoline_v2.sh" 22 | 23 | env_vars: { 24 | key: "TRAMPOLINE_BUILD_FILE" 25 | value: "github/proto3-json-serializer-nodejs/.kokoro/release/docs-devsite.sh" 26 | } 27 | -------------------------------------------------------------------------------- /.kokoro/release/docs-devsite.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2021 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | if [[ -z "$CREDENTIALS" ]]; then 20 | # if CREDENTIALS are explicitly set, assume we're testing locally 21 | # and don't set NPM_CONFIG_PREFIX. 22 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 23 | export PATH="$PATH:${NPM_CONFIG_PREFIX}/bin" 24 | cd $(dirname $0)/../.. 25 | fi 26 | 27 | npm install 28 | npm install --no-save @google-cloud/cloud-rad@^0.4.0 29 | # publish docs to devsite 30 | npx @google-cloud/cloud-rad . cloud-rad 31 | -------------------------------------------------------------------------------- /.kokoro/release/docs.cfg: -------------------------------------------------------------------------------- 1 | # service account used to publish up-to-date docs. 2 | before_action { 3 | fetch_keystore { 4 | keystore_resource { 5 | keystore_config_id: 73713 6 | keyname: "docuploader_service_account" 7 | } 8 | } 9 | } 10 | 11 | # doc publications use a Python image. 12 | env_vars: { 13 | key: "TRAMPOLINE_IMAGE" 14 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user" 15 | } 16 | 17 | # Download trampoline resources. 18 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 19 | 20 | # Use the trampoline script to run in docker. 21 | build_file: "proto3-json-serializer-nodejs/.kokoro/trampoline_v2.sh" 22 | 23 | env_vars: { 24 | key: "TRAMPOLINE_BUILD_FILE" 25 | value: "github/proto3-json-serializer-nodejs/.kokoro/release/docs.sh" 26 | } 27 | -------------------------------------------------------------------------------- /.kokoro/release/docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | # build jsdocs (Python is installed on the Node 18 docker image). 20 | if [[ -z "$CREDENTIALS" ]]; then 21 | # if CREDENTIALS are explicitly set, assume we're testing locally 22 | # and don't set NPM_CONFIG_PREFIX. 23 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 24 | export PATH="$PATH:${NPM_CONFIG_PREFIX}/bin" 25 | cd $(dirname $0)/../.. 26 | fi 27 | npm install 28 | npm run docs 29 | 30 | # create docs.metadata, based on package.json and .repo-metadata.json. 31 | npm i json@9.0.6 -g 32 | python3 -m docuploader create-metadata \ 33 | --name=$(cat .repo-metadata.json | json name) \ 34 | --version=$(cat package.json | json version) \ 35 | --language=$(cat .repo-metadata.json | json language) \ 36 | --distribution-name=$(cat .repo-metadata.json | json distribution_name) \ 37 | --product-page=$(cat .repo-metadata.json | json product_documentation) \ 38 | --github-repository=$(cat .repo-metadata.json | json repo) \ 39 | --issue-tracker=$(cat .repo-metadata.json | json issue_tracker) 40 | cp docs.metadata ./docs/docs.metadata 41 | 42 | # deploy the docs. 43 | if [[ -z "$CREDENTIALS" ]]; then 44 | CREDENTIALS=${KOKORO_KEYSTORE_DIR}/73713_docuploader_service_account 45 | fi 46 | if [[ -z "$BUCKET" ]]; then 47 | BUCKET=docs-staging 48 | fi 49 | python3 -m docuploader upload ./docs --credentials $CREDENTIALS --staging-bucket $BUCKET 50 | -------------------------------------------------------------------------------- /.kokoro/release/publish.cfg: -------------------------------------------------------------------------------- 1 | before_action { 2 | fetch_keystore { 3 | keystore_resource { 4 | keystore_config_id: 73713 5 | keyname: "docuploader_service_account" 6 | } 7 | } 8 | } 9 | 10 | before_action { 11 | fetch_keystore { 12 | keystore_resource { 13 | keystore_config_id: 73713 14 | keyname: "google-cloud-npm-token-1" 15 | } 16 | } 17 | } 18 | 19 | env_vars: { 20 | key: "SECRET_MANAGER_KEYS" 21 | value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" 22 | } 23 | 24 | # Download trampoline resources. 25 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 26 | 27 | # Use the trampoline script to run in docker. 28 | build_file: "proto3-json-serializer-nodejs/.kokoro/trampoline_v2.sh" 29 | 30 | # Configure the docker image for kokoro-trampoline. 31 | env_vars: { 32 | key: "TRAMPOLINE_IMAGE" 33 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user" 34 | } 35 | 36 | env_vars: { 37 | key: "TRAMPOLINE_BUILD_FILE" 38 | value: "github/proto3-json-serializer-nodejs/.kokoro/publish.sh" 39 | } 40 | 41 | # Store the packages we uploaded to npmjs.org and their corresponding 42 | # package-lock.jsons in Placer. That way, we have a record of exactly 43 | # what we published, and which version of which tools we used to publish 44 | # it, which we can use to generate SBOMs and attestations. 45 | action { 46 | define_artifacts { 47 | regex: "github/**/*.tgz" 48 | regex: "github/**/package-lock.json" 49 | strip_prefix: "github" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.kokoro/samples-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | # Ensure the npm global directory is writable, otherwise rebuild `npm` 20 | mkdir -p $NPM_CONFIG_PREFIX 21 | npm config -g ls || npm i -g npm@`npm --version` 22 | 23 | # Setup service account credentials. 24 | export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/secret_manager/long-door-651-kokoro-system-test-service-account 25 | export GCLOUD_PROJECT=long-door-651 26 | 27 | cd $(dirname $0)/.. 28 | 29 | # Run a pre-test hook, if a pre-samples-test.sh is in the project 30 | if [ -f .kokoro/pre-samples-test.sh ]; then 31 | set +x 32 | . .kokoro/pre-samples-test.sh 33 | set -x 34 | fi 35 | 36 | if [ -f samples/package.json ]; then 37 | npm install 38 | 39 | # Install and link samples 40 | cd samples/ 41 | npm link ../ 42 | npm install 43 | cd .. 44 | # If tests are running against main branch, configure flakybot 45 | # to open issues on failures: 46 | if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]] || [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"nightly"* ]]; then 47 | export MOCHA_REPORTER_OUTPUT=test_output_sponge_log.xml 48 | export MOCHA_REPORTER=xunit 49 | cleanup() { 50 | chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot 51 | $KOKORO_GFILE_DIR/linux_amd64/flakybot 52 | } 53 | trap cleanup EXIT HUP 54 | fi 55 | 56 | npm run samples-test 57 | fi 58 | 59 | # codecov combines coverage across integration and unit tests. Include 60 | # the logic below for any environment you wish to collect coverage for: 61 | COVERAGE_NODE=18 62 | if npx check-node-version@3.3.0 --silent --node $COVERAGE_NODE; then 63 | NYC_BIN=./node_modules/nyc/bin/nyc.js 64 | if [ -f "$NYC_BIN" ]; then 65 | $NYC_BIN report || true 66 | fi 67 | bash $KOKORO_GFILE_DIR/codecov.sh 68 | else 69 | echo "coverage is only reported for Node $COVERAGE_NODE" 70 | fi 71 | -------------------------------------------------------------------------------- /.kokoro/system-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | # Setup service account credentials. 22 | export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/secret_manager/long-door-651-kokoro-system-test-service-account 23 | export GCLOUD_PROJECT=long-door-651 24 | 25 | cd $(dirname $0)/.. 26 | 27 | # Run a pre-test hook, if a pre-system-test.sh is in the project 28 | if [ -f .kokoro/pre-system-test.sh ]; then 29 | set +x 30 | . .kokoro/pre-system-test.sh 31 | set -x 32 | fi 33 | 34 | npm install 35 | 36 | # If tests are running against main branch, configure flakybot 37 | # to open issues on failures: 38 | if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]] || [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"nightly"* ]]; then 39 | export MOCHA_REPORTER_OUTPUT=test_output_sponge_log.xml 40 | export MOCHA_REPORTER=xunit 41 | cleanup() { 42 | chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot 43 | $KOKORO_GFILE_DIR/linux_amd64/flakybot 44 | } 45 | trap cleanup EXIT HUP 46 | fi 47 | 48 | npm run system-test 49 | 50 | # codecov combines coverage across integration and unit tests. Include 51 | # the logic below for any environment you wish to collect coverage for: 52 | COVERAGE_NODE=18 53 | if npx check-node-version@3.3.0 --silent --node $COVERAGE_NODE; then 54 | NYC_BIN=./node_modules/nyc/bin/nyc.js 55 | if [ -f "$NYC_BIN" ]; then 56 | $NYC_BIN report || true 57 | fi 58 | bash $KOKORO_GFILE_DIR/codecov.sh 59 | else 60 | echo "coverage is only reported for Node $COVERAGE_NODE" 61 | fi 62 | -------------------------------------------------------------------------------- /.kokoro/test.bat: -------------------------------------------------------------------------------- 1 | @rem Copyright 2018 Google LLC. All rights reserved. 2 | @rem 3 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 4 | @rem you may not use this file except in compliance with the License. 5 | @rem You may obtain a copy of the License at 6 | @rem 7 | @rem http://www.apache.org/licenses/LICENSE-2.0 8 | @rem 9 | @rem Unless required by applicable law or agreed to in writing, software 10 | @rem distributed under the License is distributed on an "AS IS" BASIS, 11 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | @rem See the License for the specific language governing permissions and 13 | @rem limitations under the License. 14 | 15 | @echo "Starting Windows build" 16 | 17 | cd /d %~dp0 18 | cd .. 19 | 20 | @rem npm path is not currently set in our image, we should fix this next time 21 | @rem we upgrade Node.js in the image: 22 | SET PATH=%PATH%;/cygdrive/c/Program Files/nodejs/npm 23 | 24 | call nvm use 18 25 | call which node 26 | 27 | call npm install || goto :error 28 | call npm run test || goto :error 29 | 30 | goto :EOF 31 | 32 | :error 33 | exit /b 1 34 | -------------------------------------------------------------------------------- /.kokoro/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | cd $(dirname $0)/.. 22 | 23 | npm install 24 | # If tests are running against main branch, configure flakybot 25 | # to open issues on failures: 26 | if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]] || [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"nightly"* ]]; then 27 | export MOCHA_REPORTER_OUTPUT=test_output_sponge_log.xml 28 | export MOCHA_REPORTER=xunit 29 | cleanup() { 30 | chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot 31 | $KOKORO_GFILE_DIR/linux_amd64/flakybot 32 | } 33 | trap cleanup EXIT HUP 34 | fi 35 | # Unit tests exercise the entire API surface, which may include 36 | # deprecation warnings: 37 | export MOCHA_THROW_DEPRECATION=false 38 | npm test 39 | 40 | # codecov combines coverage across integration and unit tests. Include 41 | # the logic below for any environment you wish to collect coverage for: 42 | COVERAGE_NODE=18 43 | if npx check-node-version@3.3.0 --silent --node $COVERAGE_NODE; then 44 | NYC_BIN=./node_modules/nyc/bin/nyc.js 45 | if [ -f "$NYC_BIN" ]; then 46 | $NYC_BIN report || true 47 | fi 48 | bash $KOKORO_GFILE_DIR/codecov.sh 49 | else 50 | echo "coverage is only reported for Node $COVERAGE_NODE" 51 | fi 52 | -------------------------------------------------------------------------------- /.kokoro/trampoline.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2017 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # This file is not used any more, but we keep this file for making it 17 | # easy to roll back. 18 | # TODO: Remove this file from the template. 19 | 20 | set -eo pipefail 21 | 22 | # Always run the cleanup script, regardless of the success of bouncing into 23 | # the container. 24 | function cleanup() { 25 | chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh 26 | ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh 27 | echo "cleanup"; 28 | } 29 | trap cleanup EXIT 30 | 31 | $(dirname $0)/populate-secrets.sh # Secret Manager secrets. 32 | python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" 33 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | const config = { 15 | "enable-source-maps": true, 16 | "throw-deprecation": true, 17 | "timeout": 10000, 18 | "recursive": true 19 | } 20 | if (process.env.MOCHA_THROW_DEPRECATION === 'false') { 21 | delete config['throw-deprecation']; 22 | } 23 | if (process.env.MOCHA_REPORTER) { 24 | config.reporter = process.env.MOCHA_REPORTER; 25 | } 26 | if (process.env.MOCHA_REPORTER_OUTPUT) { 27 | config['reporter-option'] = `output=${process.env.MOCHA_REPORTER_OUTPUT}`; 28 | } 29 | module.exports = config 30 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "report-dir": "./.coverage", 3 | "reporter": ["text", "lcov"], 4 | "exclude": [ 5 | "**/*-test", 6 | "**/.coverage", 7 | "**/apis", 8 | "**/benchmark", 9 | "**/conformance", 10 | "**/docs", 11 | "**/samples", 12 | "**/scripts", 13 | "**/protos", 14 | "**/test", 15 | "**/*.d.ts", 16 | ".jsdoc.js", 17 | "**/.jsdoc.js", 18 | "karma.conf.js", 19 | "webpack-tests.config.js", 20 | "webpack.config.js" 21 | ], 22 | "exclude-after-remap": false, 23 | "all": true 24 | } 25 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/coverage 3 | test/fixtures 4 | build/ 5 | docs/ 6 | protos/ 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module.exports = { 16 | ...require('gts/.prettierrc.json') 17 | } 18 | -------------------------------------------------------------------------------- /.repo-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proto3-json-serializer", 3 | "name_pretty": "Proto3 JSON serializer", 4 | "product_documentation": "https://googleapis.github.io/proto3-json-serializer-nodejs/", 5 | "client_documentation": "https://cloud.google.com/nodejs/docs/reference/proto3-json-serializer/latest", 6 | "issue_tracker": "https://github.com/googleapis/proto3-json-serializer-nodejs/issues/", 7 | "release_level": "preview", 8 | "language": "nodejs", 9 | "repo": "googleapis/proto3-json-serializer-nodejs", 10 | "distribution_name": "proto3-json-serializer", 11 | "api_id": "", 12 | "requires_billing": false, 13 | "library_type": "OTHER" 14 | } 15 | -------------------------------------------------------------------------------- /.trampolinerc: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Template for .trampolinerc 16 | 17 | # Add required env vars here. 18 | required_envvars+=( 19 | ) 20 | 21 | # Add env vars which are passed down into the container here. 22 | pass_down_envvars+=( 23 | "AUTORELEASE_PR" 24 | "VERSION" 25 | ) 26 | 27 | # Prevent unintentional override on the default image. 28 | if [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]] && \ 29 | [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then 30 | echo "Please set TRAMPOLINE_IMAGE if you want to upload the Docker image." 31 | exit 1 32 | fi 33 | 34 | # Define the default value if it makes sense. 35 | if [[ -z "${TRAMPOLINE_IMAGE_UPLOAD:-}" ]]; then 36 | TRAMPOLINE_IMAGE_UPLOAD="" 37 | fi 38 | 39 | if [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then 40 | TRAMPOLINE_IMAGE="" 41 | fi 42 | 43 | if [[ -z "${TRAMPOLINE_DOCKERFILE:-}" ]]; then 44 | TRAMPOLINE_DOCKERFILE="" 45 | fi 46 | 47 | if [[ -z "${TRAMPOLINE_BUILD_FILE:-}" ]]; then 48 | TRAMPOLINE_BUILD_FILE="" 49 | fi 50 | 51 | # Secret Manager secrets. 52 | source ${PROJECT_ROOT}/.kokoro/populate-secrets.sh 53 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [3.0.0](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v2.0.2...v3.0.0) (2025-02-15) 4 | 5 | 6 | ### ⚠ BREAKING CHANGES 7 | 8 | * upgrade to node 18 ([#113](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/113)) 9 | 10 | ### Miscellaneous Chores 11 | 12 | * Upgrade to node 18 ([#113](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/113)) ([f28a826](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/f28a826b838faaddb9ab93a52179e251517fad4d)) 13 | 14 | ## [2.0.2](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v2.0.1...v2.0.2) (2024-05-22) 15 | 16 | 17 | ### Bug Fixes 18 | 19 | * Properly convert repeated int64 and maps of int64 ([#96](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/96)) ([1ec05fb](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/1ec05fb59edfdff7531b9372dcfe14c0fe36562c)) 20 | 21 | ## [2.0.1](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v2.0.0...v2.0.1) (2024-01-16) 22 | 23 | 24 | ### Bug Fixes 25 | 26 | * **deps:** Update dependency google-proto-files to v4 ([#82](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/82)) ([72623e0](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/72623e04e1043353ef952178714ced733001a06d)) 27 | * **deps:** Update protobufjs to 7.2.5 ([a0f5c83](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/a0f5c833cba654949ec5e624fad1849020cf899d)) 28 | 29 | ## [2.0.0](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v1.1.1...v2.0.0) (2023-08-07) 30 | 31 | 32 | ### ⚠ BREAKING CHANGES 33 | 34 | * require Node 14 ([#72](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/72)) 35 | 36 | ### Miscellaneous Chores 37 | 38 | * Require Node 14 ([#72](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/72)) ([8681834](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/8681834d8bc204c40857e299570e1e5df4bc5618)) 39 | 40 | ## [1.1.1](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v1.1.0...v1.1.1) (2023-04-25) 41 | 42 | 43 | ### Bug Fixes 44 | 45 | * Repeated field can be null in JSON ([#66](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/66)) ([f81d3ab](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/f81d3ab202e2a674be73db8a9b74d3eecf3bbed4)) 46 | 47 | ## [1.1.0](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v1.0.3...v1.1.0) (2022-08-26) 48 | 49 | 50 | ### Features 51 | 52 | * option to serialize enum values as numbers ([#60](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/60)) ([456b771](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/456b771d5fef06d914c6e201fd9f17251e55d4d9)) 53 | 54 | 55 | ### Bug Fixes 56 | 57 | * remove pip install statements ([#1546](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/1546)) ([#58](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/58)) ([741d070](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/741d0704f49332dd5d66206fcdf2111464fb8759)) 58 | 59 | ## [1.0.3](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v1.0.2...v1.0.3) (2022-07-10) 60 | 61 | 62 | ### Bug Fixes 63 | 64 | * **deps:** update dependency protobufjs to v7 ([#56](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/56)) ([038fea5](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/038fea537c8809dc272c2352b832b5301c7b79d2)) 65 | 66 | ## [1.0.2](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v1.0.1...v1.0.2) (2022-06-15) 67 | 68 | 69 | ### Bug Fixes 70 | 71 | * **deps:** update dependency google-proto-files to v3 ([#53](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/53)) ([40fd527](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/40fd527109838a21887f7a5058406244acabe938)) 72 | 73 | ## [1.0.1](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v1.0.0...v1.0.1) (2022-06-03) 74 | 75 | 76 | ### Bug Fixes 77 | 78 | * **deps:** bump protobuf.js to ^6.11.3 ([#46](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/46)) ([af8a14a](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/af8a14a35c04cbef49bff806b7d906287d1d2c0d)) 79 | 80 | ## [1.0.0](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.9...v1.0.0) (2022-05-12) 81 | 82 | 83 | ### ⚠ BREAKING CHANGES 84 | 85 | * make Node 12 minimum language version (#38) 86 | 87 | ### Features 88 | 89 | * make Node 12 minimum language version ([#38](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/38)) ([658d29e](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/658d29e531c2d04d4007e5843aa62d9d8ee0dae8)) 90 | 91 | ### [0.1.9](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.8...v0.1.9) (2022-05-11) 92 | 93 | 94 | ### Bug Fixes 95 | 96 | * do not use Node.js assert ([#37](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/37)) ([dccfeca](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/dccfeca6f3bbeec29d88319f375a734ec48aadf7)) 97 | 98 | ### [0.1.8](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.7...v0.1.8) (2022-01-21) 99 | 100 | 101 | ### Bug Fixes 102 | 103 | * timestamp without millisecond ([#30](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/30)) ([a55d0b6](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/a55d0b6f98f6d1c8b7d971d0a583bbd82ea66983)) 104 | 105 | ### [0.1.7](https://github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.6...v0.1.7) (2022-01-14) 106 | 107 | 108 | ### Bug Fixes 109 | 110 | * keep nano second precision when maps between JSON and proto3 ([#28](https://github.com/googleapis/proto3-json-serializer-nodejs/issues/28)) ([eaa01ce](https://github.com/googleapis/proto3-json-serializer-nodejs/commit/eaa01ce92c4eefa816d1d6f8ef6ed11bd2a6364b)) 111 | 112 | ### [0.1.6](https://www.github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.5...v0.1.6) (2021-11-15) 113 | 114 | 115 | ### Bug Fixes 116 | 117 | * **deps:** protobufjs is a dependency for the types ([#23](https://www.github.com/googleapis/proto3-json-serializer-nodejs/issues/23)) ([06470c1](https://www.github.com/googleapis/proto3-json-serializer-nodejs/commit/06470c1df501439ec3f8bc546cd23d798604f3bd)) 118 | 119 | ### [0.1.5](https://www.github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.4...v0.1.5) (2021-10-26) 120 | 121 | 122 | ### Bug Fixes 123 | 124 | * JSON accept special string for NaN, Infinity ([#19](https://www.github.com/googleapis/proto3-json-serializer-nodejs/issues/19)) ([01a345b](https://www.github.com/googleapis/proto3-json-serializer-nodejs/commit/01a345b7b1d62ee65a8673737975980d274fa22a)) 125 | 126 | ### [0.1.4](https://www.github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.3...v0.1.4) (2021-09-20) 127 | 128 | 129 | ### Bug Fixes 130 | 131 | * do not emit empty lists to JSON ([#15](https://www.github.com/googleapis/proto3-json-serializer-nodejs/issues/15)) ([af9dfd6](https://www.github.com/googleapis/proto3-json-serializer-nodejs/commit/af9dfd65efb84cfb31af0faca805f53b0ffa9874)) 132 | 133 | ### [0.1.3](https://www.github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.2...v0.1.3) (2021-08-18) 134 | 135 | 136 | ### Bug Fixes 137 | 138 | * do not fail for unknown enum values ([#11](https://www.github.com/googleapis/proto3-json-serializer-nodejs/issues/11)) ([ff9f0f1](https://www.github.com/googleapis/proto3-json-serializer-nodejs/commit/ff9f0f1881b1aafacd693b4e24eaee9e56aff79c)) 139 | 140 | ### [0.1.2](https://www.github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.1...v0.1.2) (2021-08-17) 141 | 142 | 143 | ### Bug Fixes 144 | 145 | * use imported protobufjs in toproto3json.ts ([#9](https://www.github.com/googleapis/proto3-json-serializer-nodejs/issues/9)) ([f6c86c7](https://www.github.com/googleapis/proto3-json-serializer-nodejs/commit/f6c86c777d567d8430b09dea3282e52af24d890f)) 146 | 147 | ### [0.1.1](https://www.github.com/googleapis/proto3-json-serializer-nodejs/compare/v0.1.0...v0.1.1) (2021-08-04) 148 | 149 | 150 | ### Bug Fixes 151 | 152 | * accept and return strings for int64 and uint64 ([#7](https://www.github.com/googleapis/proto3-json-serializer-nodejs/issues/7)) ([35689ec](https://www.github.com/googleapis/proto3-json-serializer-nodejs/commit/35689ecee55dbe6e4cf3327c535514d7fcb8332d)) 153 | 154 | ## 0.1.0 (2021-08-03) 155 | 156 | 157 | ### ⚠ BREAKING CHANGES 158 | 159 | * proto3 JSON serializer and deserializer (#2) 160 | 161 | ### Features 162 | 163 | * proto3 JSON serializer and deserializer ([#2](https://www.github.com/googleapis/proto3-json-serializer-nodejs/issues/2)) ([96255a7](https://www.github.com/googleapis/proto3-json-serializer-nodejs/commit/96255a77c7714f33cae547db9160615d7f80a233)) 164 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | In the interest of fostering an open and welcoming environment, we as 7 | contributors and maintainers pledge to making participation in our project and 8 | our community a harassment-free experience for everyone, regardless of age, body 9 | size, disability, ethnicity, gender identity and expression, level of 10 | experience, education, socio-economic status, nationality, personal appearance, 11 | race, religion, or sexual identity and orientation. 12 | 13 | ## Our Standards 14 | 15 | Examples of behavior that contributes to creating a positive environment 16 | include: 17 | 18 | * Using welcoming and inclusive language 19 | * Being respectful of differing viewpoints and experiences 20 | * Gracefully accepting constructive criticism 21 | * Focusing on what is best for the community 22 | * Showing empathy towards other community members 23 | 24 | Examples of unacceptable behavior by participants include: 25 | 26 | * The use of sexualized language or imagery and unwelcome sexual attention or 27 | advances 28 | * Trolling, insulting/derogatory comments, and personal or political attacks 29 | * Public or private harassment 30 | * Publishing others' private information, such as a physical or electronic 31 | address, without explicit permission 32 | * Other conduct which could reasonably be considered inappropriate in a 33 | professional setting 34 | 35 | ## Our Responsibilities 36 | 37 | Project maintainers are responsible for clarifying the standards of acceptable 38 | behavior and are expected to take appropriate and fair corrective action in 39 | response to any instances of unacceptable behavior. 40 | 41 | Project maintainers have the right and responsibility to remove, edit, or reject 42 | comments, commits, code, wiki edits, issues, and other contributions that are 43 | not aligned to this Code of Conduct, or to ban temporarily or permanently any 44 | contributor for other behaviors that they deem inappropriate, threatening, 45 | offensive, or harmful. 46 | 47 | ## Scope 48 | 49 | This Code of Conduct applies both within project spaces and in public spaces 50 | when an individual is representing the project or its community. Examples of 51 | representing a project or community include using an official project e-mail 52 | address, posting via an official social media account, or acting as an appointed 53 | representative at an online or offline event. Representation of a project may be 54 | further defined and clarified by project maintainers. 55 | 56 | This Code of Conduct also applies outside the project spaces when the Project 57 | Steward has a reasonable belief that an individual's behavior may have a 58 | negative impact on the project or its community. 59 | 60 | ## Conflict Resolution 61 | 62 | We do not believe that all conflict is bad; healthy debate and disagreement 63 | often yield positive results. However, it is never okay to be disrespectful or 64 | to engage in behavior that violates the project’s code of conduct. 65 | 66 | If you see someone violating the code of conduct, you are encouraged to address 67 | the behavior directly with those involved. Many issues can be resolved quickly 68 | and easily, and this gives people more control over the outcome of their 69 | dispute. If you are unable to resolve the matter for any reason, or if the 70 | behavior is threatening or harassing, report it. We are dedicated to providing 71 | an environment where participants feel welcome and safe. 72 | 73 | Reports should be directed to *googleapis-stewards@google.com*, the 74 | Project Steward(s) for *Google Cloud Client Libraries*. It is the Project Steward’s duty to 75 | receive and address reported violations of the code of conduct. They will then 76 | work with a committee consisting of representatives from the Open Source 77 | Programs Office and the Google Open Source Strategy team. If for any reason you 78 | are uncomfortable reaching out to the Project Steward, please email 79 | opensource@google.com. 80 | 81 | We will investigate every complaint, but you may not receive a direct response. 82 | We will use our discretion in determining when and how to follow up on reported 83 | incidents, which may range from not taking action to permanent expulsion from 84 | the project and project-sponsored spaces. We will notify the accused of the 85 | report and provide them an opportunity to discuss it before any action is taken. 86 | The identity of the reporter will be omitted from the details of the report 87 | supplied to the accused. In potentially harmful situations, such as ongoing 88 | harassment or threats to anyone's safety, we may take action without notice. 89 | 90 | ## Attribution 91 | 92 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 93 | available at 94 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | **Table of contents** 4 | 5 | * [Contributor License Agreements](#contributor-license-agreements) 6 | * [Contributing a patch](#contributing-a-patch) 7 | * [Running the tests](#running-the-tests) 8 | * [Releasing the library](#releasing-the-library) 9 | 10 | ## Contributor License Agreements 11 | 12 | We'd love to accept your sample apps and patches! Before we can take them, we 13 | have to jump a couple of legal hurdles. 14 | 15 | Please fill out either the individual or corporate Contributor License Agreement 16 | (CLA). 17 | 18 | * If you are an individual writing original source code and you're sure you 19 | own the intellectual property, then you'll need to sign an [individual CLA](https://developers.google.com/open-source/cla/individual). 20 | * If you work for a company that wants to allow you to contribute your work, 21 | then you'll need to sign a [corporate CLA](https://developers.google.com/open-source/cla/corporate). 22 | 23 | Follow either of the two links above to access the appropriate CLA and 24 | instructions for how to sign and return it. Once we receive it, we'll be able to 25 | accept your pull requests. 26 | 27 | ## Contributing A Patch 28 | 29 | 1. Submit an issue describing your proposed change to the repo in question. 30 | 1. The repo owner will respond to your issue promptly. 31 | 1. If your proposed change is accepted, and you haven't already done so, sign a 32 | Contributor License Agreement (see details above). 33 | 1. Fork the desired repo, develop and test your code changes. 34 | 1. Ensure that your code adheres to the existing style in the code to which 35 | you are contributing. 36 | 1. Ensure that your code has an appropriate set of tests which all pass. 37 | 1. Title your pull request following [Conventional Commits](https://www.conventionalcommits.org/) styling. 38 | 1. Submit a pull request. 39 | 40 | ### Before you begin 41 | 42 | 1. [Select or create a Cloud Platform project][projects]. 43 | 1. [Set up authentication with a service account][auth] so you can access the 44 | API from your local workstation. 45 | 46 | 47 | ## Running the tests 48 | 49 | 1. [Prepare your environment for Node.js setup][setup]. 50 | 51 | 1. Install dependencies: 52 | 53 | npm install 54 | 55 | 1. Run the tests: 56 | 57 | # Run unit tests. 58 | npm test 59 | 60 | # Run sample integration tests. 61 | npm run samples-test 62 | 63 | # Run all system tests. 64 | npm run system-test 65 | 66 | 1. Lint (and maybe fix) any changes: 67 | 68 | npm run fix 69 | 70 | [setup]: https://cloud.google.com/nodejs/docs/setup 71 | [projects]: https://console.cloud.google.com/project 72 | [billing]: https://support.google.com/cloud/answer/6293499#enable-billing 73 | 74 | [auth]: https://cloud.google.com/docs/authentication/getting-started -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # proto3 JSON serializer for TypeScript / JavaScript 2 | 3 | This library implements proto3 JSON serialization and deserialization for 4 | [protobuf.js](https://www.npmjs.com/package/protobufjs) protobuf objects 5 | according to the [spec](https://developers.google.com/protocol-buffers/docs/proto3#json). 6 | 7 | Note that the spec requires special representation of some `google.protobuf.*` types 8 | (`Value`, `Struct`, `Timestamp`, `Duration`, etc.), so you cannot just use `.toObject()` 9 | since the result won't be understood by protobuf in other languages. Hence this module. 10 | 11 | JavaScript: 12 | 13 | ```js 14 | const serializer = require('proto3-json-serializer'); 15 | ``` 16 | 17 | TypeScript: 18 | 19 | ```ts 20 | import * as serializer from 'proto3-json-serializer'; 21 | ``` 22 | 23 | ## Serialization: protobuf.js object to proto3 JSON 24 | 25 | ```js 26 | const root = protobuf.loadSync('test.proto'); 27 | const Type = root.lookupType('test.Message'); 28 | const message = Type.fromObject({...}); 29 | 30 | const serialized = serializer.toProto3JSON(message); 31 | ``` 32 | 33 | Serialization works with any object created by calling `.create()`, `.decode()`, or `.fromObject()` 34 | for a loaded protobuf type. It relies on the `$type` field so it will not work with a static object. 35 | 36 | ## Deserialization: proto3 JSON to protobuf.js object 37 | 38 | To deserialize an object from proto3 JSON, we must know its type (as returned by `root.lookupType('...')`). 39 | Pass this type as the first parameter to `.fromProto3JSON`: 40 | 41 | ```js 42 | const root = protobuf.loadSync('test.proto'); 43 | const Type = root.lookupType('test.Message'); 44 | const json = {...}; 45 | 46 | const deserialized = serializer.fromProto3JSON(Type, json); 47 | ``` 48 | 49 | ## Complete example 50 | ```js 51 | const assert = require('assert'); 52 | const path = require('path'); 53 | const protobuf = require('protobufjs'); 54 | const serializer = require('proto3-json-serializer'); 55 | 56 | // We'll take sample protos from google-proto-files but the code will work with any protos 57 | const protos = require('google-proto-files'); 58 | 59 | // Load some proto file 60 | const rpcProtos = protos.getProtoPath('rpc'); 61 | const root = protobuf.loadSync([ 62 | path.join(rpcProtos, 'status.proto'), 63 | path.join(rpcProtos, 'error_details.proto'), 64 | ]); 65 | const Status = root.lookupType('google.rpc.Status'); 66 | 67 | // If you have a protobuf object that follows proto3 JSON syntax 68 | // https://developers.google.com/protocol-buffers/docs/proto3#json 69 | // (this is an example of google.rpc.Status message in JSON) 70 | const json = { 71 | code: 3, 72 | message: 'Test error message', 73 | details: [ 74 | { 75 | '@type': 'google.rpc.BadRequest', 76 | fieldViolations: [ 77 | { 78 | field: 'field', 79 | description: 'must not be null', 80 | }, 81 | ], 82 | }, 83 | ], 84 | }; 85 | 86 | // You can deserialize it into a protobuf.js object: 87 | const deserialized = serializer.fromProto3JSON(Status, json); 88 | console.log(deserialized); 89 | 90 | // And serialize it back 91 | const serialized = serializer.toProto3JSON(deserialized); 92 | assert.deepStrictEqual(serialized, json); 93 | ``` 94 | 95 | ## Disclaimer 96 | 97 | This is not an officially supported Google project. 98 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). 4 | 5 | The Google Security Team will respond within 5 working days of your report on g.co/vulnz. 6 | 7 | We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue. 8 | -------------------------------------------------------------------------------- /owlbot.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import synthtool.languages.node as node 16 | 17 | node.owlbot_main(templates_excludes=["LICENSE", "README.md"]) 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proto3-json-serializer", 3 | "version": "3.0.0", 4 | "repository": "googleapis/proto3-json-serializer-nodejs", 5 | "description": "Support for proto3 JSON serialiazation/deserialization for protobuf.js", 6 | "main": "build/src/index.js", 7 | "types": "build/src/index.d.ts", 8 | "files": [ 9 | "build/src" 10 | ], 11 | "license": "Apache-2.0", 12 | "keywords": [ 13 | "protobufjs", 14 | "protobuf.js", 15 | "protobuf", 16 | "proto3", 17 | "json", 18 | "serialization", 19 | "deserialization" 20 | ], 21 | "scripts": { 22 | "test": "c8 mocha build/test/unit", 23 | "system-test": "mocha build/test/system", 24 | "lint": "gts lint", 25 | "clean": "gts clean", 26 | "compile": "tsc", 27 | "fix": "gts fix", 28 | "prepare": "npm run compile", 29 | "pretest": "npm run compile", 30 | "posttest": "npm run lint", 31 | "compile-test-protos": "cd test-fixtures/proto && pbjs -t json test.proto > test.json", 32 | "docs": "jsdoc -c .jsdoc.js", 33 | "docs-test": "linkinator docs", 34 | "predocs-test": "npm run docs", 35 | "samples-test": "cd samples/ && npm link ../ && npm test && cd ../", 36 | "prelint": "cd samples && npm link ../ && npm install" 37 | }, 38 | "dependencies": { 39 | "protobufjs": "^7.4.0" 40 | }, 41 | "devDependencies": { 42 | "@types/mocha": "^10.0.10", 43 | "@types/node": "^22.13.1", 44 | "c8": "^10.1.3", 45 | "google-proto-files": "^4.2.0", 46 | "gts": "^6.0.2", 47 | "jsdoc": "^4.0.4", 48 | "jsdoc-fresh": "^3.0.0", 49 | "jsdoc-region-tag": "^3.0.0", 50 | "linkinator": "^6.1.2", 51 | "mocha": "^11.1.0", 52 | "pack-n-play": "^3.0.0", 53 | "protobufjs-cli": "^1.1.3", 54 | "typescript": "^5.7.3" 55 | }, 56 | "engines": { 57 | "node": ">=18" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | "docker:disable", 5 | ":disableDependencyDashboard" 6 | ], 7 | "constraintsFiltering": "strict", 8 | "pinVersions": false, 9 | "rebaseStalePrs": true, 10 | "schedule": [ 11 | "after 9am and before 3pm" 12 | ], 13 | "gitAuthor": null, 14 | "packageRules": [ 15 | { 16 | "extends": "packages:linters", 17 | "groupName": "linters" 18 | } 19 | ], 20 | "ignoreDeps": ["typescript"] 21 | } 22 | -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | [//]: # "This README.md file is auto-generated, all changes to this file will be lost." 2 | [//]: # "To regenerate it, use `python -m synthtool`." 3 | Google Cloud Platform logo 4 | 5 | # [Proto3 JSON serializer: Node.js Samples](https://github.com/googleapis/proto3-json-serializer-nodejs) 6 | 7 | [![Open in Cloud Shell][shell_img]][shell_link] 8 | 9 | 10 | 11 | ## Table of Contents 12 | 13 | * [Before you begin](#before-you-begin) 14 | * [Samples](#samples) 15 | * [Quickstart](#quickstart) 16 | 17 | ## Before you begin 18 | 19 | Before running the samples, make sure you've followed the steps outlined in 20 | [Using the client library](https://github.com/googleapis/proto3-json-serializer-nodejs#using-the-client-library). 21 | 22 | `cd samples` 23 | 24 | `npm install` 25 | 26 | `cd ..` 27 | 28 | ## Samples 29 | 30 | 31 | 32 | ### Quickstart 33 | 34 | View the [source code](https://github.com/googleapis/proto3-json-serializer-nodejs/blob/main/samples/quickstart.js). 35 | 36 | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/proto3-json-serializer-nodejs&page=editor&open_in_editor=samples/quickstart.js,samples/README.md) 37 | 38 | __Usage:__ 39 | 40 | 41 | `node samples/quickstart.js` 42 | 43 | 44 | 45 | 46 | 47 | 48 | [shell_img]: https://gstatic.com/cloudssh/images/open-btn.png 49 | [shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/proto3-json-serializer-nodejs&page=editor&open_in_editor=samples/README.md 50 | [product-docs]: https://googleapis.github.io/proto3-json-serializer-nodejs/ 51 | -------------------------------------------------------------------------------- /samples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Samples for proto3-json-serializer.", 3 | "license": "Apache-2.0", 4 | "author": "Google LLC", 5 | "engines": { 6 | "node": ">=18" 7 | }, 8 | "repository": "googleapis/proto3-json-serializer-nodejs", 9 | "private": true, 10 | "scripts": { 11 | "test": "c8 mocha system-test" 12 | }, 13 | "files": [ 14 | "*.js" 15 | ], 16 | "dependencies": { 17 | "google-proto-files": "^4.0.0", 18 | "proto3-json-serializer": "^3.0.0", 19 | "protobufjs": "^7.0.0" 20 | }, 21 | "devDependencies": { 22 | "c8": "^9.0.0", 23 | "mocha": "^9.0.0" 24 | } 25 | } -------------------------------------------------------------------------------- /samples/quickstart.js: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | function main() { 18 | // [START proto3_json_serializer_quickstart] 19 | function quickstart() { 20 | const assert = require('assert'); 21 | const path = require('path'); 22 | const protobuf = require('protobufjs'); 23 | const serializer = require('proto3-json-serializer'); 24 | 25 | // We'll take sample protos from google-proto-files but the code will work with any protos 26 | const protos = require('google-proto-files'); 27 | 28 | // Load some proto file 29 | const rpcProtos = protos.getProtoPath('rpc'); 30 | const root = protobuf.loadSync([ 31 | path.join(rpcProtos, 'status.proto'), 32 | path.join(rpcProtos, 'error_details.proto'), 33 | ]); 34 | const Status = root.lookupType('google.rpc.Status'); 35 | 36 | // If you have a protobuf object that follows proto3 JSON syntax 37 | // https://developers.google.com/protocol-buffers/docs/proto3#json 38 | // (this is an example of google.rpc.Status message in JSON) 39 | const json = { 40 | code: 3, 41 | message: 'Test error message', 42 | details: [ 43 | { 44 | '@type': 'google.rpc.BadRequest', 45 | fieldViolations: [ 46 | { 47 | field: 'field', 48 | description: 'must not be null', 49 | }, 50 | ], 51 | }, 52 | ], 53 | }; 54 | 55 | // You can deserialize it into a protobuf.js object: 56 | const deserialized = serializer.fromProto3JSON(Status, json); 57 | console.log(deserialized); 58 | 59 | // And serialize it back 60 | const serialized = serializer.toProto3JSON(deserialized); 61 | assert.deepStrictEqual(serialized, json); 62 | 63 | console.log('Quickstart completed'); 64 | } 65 | // [END proto3_json_serializer_quickstart] 66 | 67 | quickstart(); 68 | } 69 | 70 | main(); 71 | -------------------------------------------------------------------------------- /samples/system-test/test.quickstart.js: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | const path = require('path'); 18 | const assert = require('assert'); 19 | const {describe, it} = require('mocha'); 20 | const cp = require('child_process'); 21 | 22 | const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); 23 | 24 | const cwd = path.join(__dirname, '..'); 25 | 26 | describe('Quickstart', () => { 27 | it('should run quickstart sample', async () => { 28 | const stdout = execSync('node quickstart.js', {cwd}); 29 | assert(/Quickstart completed/.test(stdout)); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test-fixtures/proto/test.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | import "google/protobuf/any.proto"; 18 | import "google/protobuf/duration.proto"; 19 | import "google/protobuf/empty.proto"; 20 | import "google/protobuf/field_mask.proto"; 21 | import "google/protobuf/struct.proto"; 22 | import "google/protobuf/timestamp.proto"; 23 | import "google/protobuf/wrappers.proto"; 24 | 25 | package test; 26 | 27 | message PrimitiveTypes { 28 | int32 integer_field = 1; 29 | uint32 unsigned_integer_field = 2; 30 | fixed32 fixed_integer_field = 3; 31 | string string_field = 4; 32 | bool bool_field = 5; 33 | int64 int64_field = 6; 34 | uint64 uint64_field = 7; 35 | double double_field = 8; 36 | } 37 | 38 | message MessageWithNestedMessage { 39 | AnyContent inner_message = 1; 40 | } 41 | 42 | message MessageWithAny { 43 | google.protobuf.Any any_field = 3; 44 | } 45 | 46 | message AnyContent { 47 | string string_field = 1; 48 | } 49 | 50 | message MessageWithBytesField { 51 | bytes bytes_field = 3; 52 | } 53 | 54 | message MessageWithMap { 55 | map map_field = 3; 56 | map string_map_field = 4; 57 | map long_map_field = 5; 58 | } 59 | 60 | message MapValue { 61 | string string_field = 1; 62 | } 63 | 64 | message MessageWithRepeated { 65 | repeated string repeated_string = 3; 66 | repeated RepeatedValue repeated_message = 4; 67 | repeated string one_more_repeated_string = 5; 68 | repeated int64 repeated_long = 6; 69 | } 70 | 71 | message RepeatedValue { 72 | string string_field = 1; 73 | } 74 | 75 | enum Enum { 76 | UNKNOWN = 0; 77 | KNOWN = 1; 78 | } 79 | 80 | message MessageWithEnum { 81 | Enum enum_field = 3; 82 | } 83 | 84 | message MessageWithOneof { 85 | oneof _oneof_field { 86 | string string_field = 1; 87 | int32 integer_field = 2; 88 | AnyContent message_field = 3; 89 | } 90 | } 91 | 92 | message MessageWithValue { 93 | google.protobuf.Value value_field = 1; 94 | } 95 | 96 | message MessageWithStruct { 97 | google.protobuf.Struct struct_field = 1; 98 | } 99 | 100 | message MessageWithListValue { 101 | google.protobuf.ListValue list_value_field = 1; 102 | } 103 | 104 | message MessageWithNullValue { 105 | google.protobuf.NullValue null_value_field = 1; 106 | } 107 | 108 | message MessageWithDuration { 109 | google.protobuf.Duration duration_field = 1; 110 | } 111 | 112 | message MessageWithTimestamp { 113 | google.protobuf.Timestamp timestamp_field = 1; 114 | } 115 | 116 | message MessageWithEmpty { 117 | google.protobuf.Empty empty_field = 1; 118 | } 119 | 120 | message MessageWithWrappers { 121 | google.protobuf.DoubleValue double_value_field = 1; 122 | google.protobuf.FloatValue float_value_field = 2; 123 | google.protobuf.Int64Value int64_value_field = 3; 124 | google.protobuf.UInt64Value uint64_value_field = 4; 125 | google.protobuf.Int32Value int32_value_field = 5; 126 | google.protobuf.UInt32Value uint32_value_field = 6; 127 | google.protobuf.BoolValue bool_value_field = 7; 128 | google.protobuf.StringValue string_value_field = 8; 129 | google.protobuf.BytesValue bytes_value_field = 9; 130 | google.protobuf.DoubleValue nan_value_field = 10; 131 | google.protobuf.DoubleValue infinity_value_field = 11; 132 | google.protobuf.DoubleValue negative_infinity_value_field = 12; 133 | } 134 | 135 | message MessageWithFieldMask { 136 | google.protobuf.FieldMask field_mask_field = 1; 137 | } 138 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/gts/tsconfig-google.json", 3 | "compilerOptions": { 4 | "rootDir": "typescript", 5 | "outDir": "build", 6 | "lib": ["dom"] 7 | }, 8 | "include": [ 9 | "typescript/**/*.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /typescript/src/any.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Types that have special JSON representation according to https://developers.google.com/protocol-buffers/docs/proto3#json 16 | // (this set is used for encoding google.protobuf.Any to/from proto3 JSON properly) 17 | 18 | import * as protobuf from 'protobufjs'; 19 | import {fromProto3JSON} from './fromproto3json'; 20 | import {toProto3JSON, ToProto3JSONOptions} from './toproto3json'; 21 | import {JSONObject, JSONValue} from './types'; 22 | 23 | // https://github.com/protocolbuffers/protobuf/blob/ba3836703b4a9e98e474aea2bac8c5b49b6d3b5c/python/google/protobuf/json_format.py#L850 24 | const specialJSON = new Set([ 25 | 'google.protobuf.Any', 26 | 'google.protobuf.Duration', 27 | 'google.protobuf.FieldMask', 28 | 'google.protobuf.ListValue', 29 | 'google.protobuf.Struct', 30 | 'google.protobuf.Timestamp', 31 | 'google.protobuf.Value', 32 | ]); 33 | 34 | export interface Any { 35 | type_url: string; 36 | value: Buffer | Uint8Array; 37 | } 38 | 39 | export function googleProtobufAnyToProto3JSON( 40 | obj: protobuf.Message & Any, 41 | options?: ToProto3JSONOptions, 42 | ): JSONObject { 43 | // https://developers.google.com/protocol-buffers/docs/proto3#json 44 | // If the Any contains a value that has a special JSON mapping, it will be converted as follows: 45 | // {"@type": xxx, "value": yyy}. 46 | // Otherwise, the value will be converted into a JSON object, and the "@type" field will be inserted 47 | // to indicate the actual data type. 48 | 49 | const typeName = obj.type_url.replace(/^.*\//, ''); 50 | let type: protobuf.Type; 51 | try { 52 | type = obj.$type.root.lookupType(typeName); 53 | } catch (err) { 54 | throw new Error( 55 | `googleProtobufAnyToProto3JSON: cannot find type ${typeName}: ${err}`, 56 | ); 57 | } 58 | const valueMessage = type.decode(obj.value); 59 | const valueProto3JSON = toProto3JSON(valueMessage, options); 60 | if (specialJSON.has(typeName)) { 61 | return { 62 | '@type': obj.type_url, 63 | value: valueProto3JSON, 64 | }; 65 | } 66 | (valueProto3JSON as JSONObject)['@type'] = obj.type_url; 67 | return valueProto3JSON as JSONObject; 68 | } 69 | 70 | export function googleProtobufAnyFromProto3JSON( 71 | root: protobuf.Root, 72 | json: JSONValue, 73 | ) { 74 | // Not all possible JSON values can hold Any, only real objects. 75 | if (json === null || typeof json !== 'object' || Array.isArray(json)) { 76 | throw new Error( 77 | 'googleProtobufAnyFromProto3JSON: must be an object to decode google.protobuf.Any', 78 | ); 79 | } 80 | 81 | const typeUrl = json['@type']; 82 | if (!typeUrl || typeof typeUrl !== 'string') { 83 | throw new Error( 84 | 'googleProtobufAnyFromProto3JSON: JSON serialization of google.protobuf.Any must contain @type field', 85 | ); 86 | } 87 | 88 | const typeName = typeUrl.replace(/^.*\//, ''); 89 | let type: protobuf.Type; 90 | try { 91 | type = root.lookupType(typeName); 92 | } catch (err) { 93 | throw new Error( 94 | `googleProtobufAnyFromProto3JSON: cannot find type ${typeName}: ${err}`, 95 | ); 96 | } 97 | 98 | let value: JSONValue = json; 99 | if (specialJSON.has(typeName)) { 100 | if (!('value' in json)) { 101 | throw new Error( 102 | `googleProtobufAnyFromProto3JSON: JSON representation of google.protobuf.Any with type ${typeName} must contain the value field`, 103 | ); 104 | } 105 | value = json.value; 106 | } 107 | 108 | const valueMessage = fromProto3JSON(type, value); 109 | if (valueMessage === null) { 110 | return { 111 | type_url: typeUrl, 112 | value: null, 113 | }; 114 | } 115 | 116 | const uint8array = type.encode(valueMessage).finish(); 117 | const buffer = Buffer.from(uint8array, 0, uint8array.byteLength); 118 | const base64 = buffer.toString('base64'); 119 | 120 | return { 121 | type_url: typeUrl, 122 | value: base64, 123 | }; 124 | } 125 | -------------------------------------------------------------------------------- /typescript/src/bytes.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export function bytesToProto3JSON(obj: Buffer | Uint8Array) { 16 | if (Buffer.isBuffer(obj)) { 17 | return obj.toString('base64'); 18 | } else { 19 | return Buffer.from(obj.buffer, 0, obj.byteLength).toString('base64'); 20 | } 21 | } 22 | 23 | export function bytesFromProto3JSON(json: string) { 24 | return Buffer.from(json, 'base64'); 25 | } 26 | -------------------------------------------------------------------------------- /typescript/src/duration.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as protobuf from 'protobufjs'; 16 | import {FromObjectValue} from './types'; 17 | 18 | export interface Duration { 19 | seconds: number; // it's technically Long but we don't want to bring it as a dependency for now 20 | nanos?: number; 21 | } 22 | 23 | export function googleProtobufDurationToProto3JSON( 24 | obj: protobuf.Message & Duration, 25 | ) { 26 | // seconds is an instance of Long so it won't be undefined 27 | let durationSeconds = obj.seconds.toString(); 28 | if (typeof obj.nanos === 'number' && obj.nanos > 0) { 29 | // nanosStr should contain 3, 6, or 9 fractional digits. 30 | const nanosStr = obj.nanos 31 | .toString() 32 | .padStart(9, '0') 33 | .replace(/^((?:\d\d\d)+?)(?:0*)$/, '$1'); 34 | durationSeconds += '.' + nanosStr; 35 | } 36 | durationSeconds += 's'; 37 | return durationSeconds; 38 | } 39 | 40 | export function googleProtobufDurationFromProto3JSON(json: string) { 41 | const match = json.match(/^(\d*)(?:\.(\d*))?s$/); 42 | if (!match) { 43 | throw new Error( 44 | `googleProtobufDurationFromProto3JSON: incorrect value ${json} passed as google.protobuf.Duration`, 45 | ); 46 | } 47 | 48 | let seconds = 0; 49 | let nanos = 0; 50 | 51 | if (typeof match[1] === 'string' && match[1].length > 0) { 52 | seconds = parseInt(match[1]); 53 | } 54 | 55 | if (typeof match[2] === 'string' && match[2].length > 0) { 56 | nanos = parseInt(match[2].padEnd(9, '0')); 57 | } 58 | 59 | const result: FromObjectValue = {}; 60 | if (seconds !== 0) { 61 | result.seconds = seconds; 62 | } 63 | if (nanos !== 0) { 64 | result.nanos = nanos; 65 | } 66 | return result; 67 | } 68 | -------------------------------------------------------------------------------- /typescript/src/enum.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as protobuf from 'protobufjs'; 16 | import {JSONValue} from './types'; 17 | 18 | export function resolveEnumValueToString( 19 | enumType: protobuf.Enum, 20 | enumValue: JSONValue, 21 | ) { 22 | // for unknown enum values, do not fail and try to do the best we could. 23 | // protobuf.js fromObject() will likely ignore unknown values, but at least 24 | // we won't fail. 25 | if (typeof enumValue === 'number') { 26 | const value = enumType.valuesById[enumValue]; 27 | if (typeof value === 'undefined') { 28 | // unknown value, cannot convert to string, returning number as is 29 | return enumValue; 30 | } 31 | return value; 32 | } 33 | if (typeof enumValue === 'string') { 34 | // for strings, just accept what we got 35 | return enumValue; 36 | } 37 | throw new Error( 38 | 'resolveEnumValueToString: enum value must be a string or a number', 39 | ); 40 | } 41 | 42 | export function resolveEnumValueToNumber( 43 | enumType: protobuf.Enum, 44 | enumValue: JSONValue, 45 | ) { 46 | if (typeof enumValue === 'number') { 47 | // return as is 48 | return enumValue; 49 | } 50 | if (typeof enumValue === 'string') { 51 | const num = enumType.values[enumValue]; 52 | if (typeof num === 'undefined') { 53 | // unknown value, cannot convert to number, returning string as is 54 | return enumValue; 55 | } 56 | return num; 57 | } 58 | throw new Error( 59 | 'resolveEnumValueToNumber: enum value must be a string or a number', 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /typescript/src/fieldmask.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as protobuf from 'protobufjs'; 16 | 17 | export interface FieldMask { 18 | paths: string[]; 19 | } 20 | 21 | export function googleProtobufFieldMaskToProto3JSON( 22 | obj: protobuf.Message & FieldMask, 23 | ) { 24 | return obj.paths.join(','); 25 | } 26 | 27 | export function googleProtobufFieldMaskFromProto3JSON(json: string) { 28 | return { 29 | paths: json.split(','), 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /typescript/src/fromproto3json.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as protobuf from 'protobufjs'; 16 | import {googleProtobufAnyFromProto3JSON} from './any'; 17 | import {bytesFromProto3JSON} from './bytes'; 18 | import {resolveEnumValueToString} from './enum'; 19 | import {FromObjectValue, JSONValue} from './types'; 20 | import { 21 | googleProtobufListValueFromProto3JSON, 22 | googleProtobufStructFromProto3JSON, 23 | googleProtobufValueFromProto3JSON, 24 | } from './value'; 25 | import {assert, getFullyQualifiedTypeName, wrapperTypes} from './util'; 26 | import {googleProtobufDurationFromProto3JSON} from './duration'; 27 | import {googleProtobufTimestampFromProto3JSON} from './timestamp'; 28 | import {wrapperFromProto3JSON} from './wrappers'; 29 | import {googleProtobufFieldMaskFromProto3JSON} from './fieldmask'; 30 | 31 | export function fromProto3JSONToInternalRepresentation( 32 | type: protobuf.Type | protobuf.Enum | string, 33 | json: JSONValue, 34 | ): FromObjectValue { 35 | const fullyQualifiedTypeName = 36 | typeof type === 'string' ? type : getFullyQualifiedTypeName(type); 37 | 38 | if (typeof type !== 'string' && 'values' in type) { 39 | // type is an Enum 40 | if (fullyQualifiedTypeName === '.google.protobuf.NullValue') { 41 | return 'NULL_VALUE'; 42 | } 43 | 44 | return resolveEnumValueToString(type, json); 45 | } 46 | 47 | if (typeof type !== 'string') { 48 | type.resolveAll(); 49 | } 50 | 51 | if (typeof type === 'string') { 52 | return json; 53 | } 54 | 55 | // Types that require special handling according to 56 | // https://developers.google.com/protocol-buffers/docs/proto3#json 57 | 58 | // Types that can have meaningful "null" value 59 | if (fullyQualifiedTypeName === '.google.protobuf.Value') { 60 | return googleProtobufValueFromProto3JSON(json); 61 | } 62 | 63 | if (wrapperTypes.has(fullyQualifiedTypeName)) { 64 | if ((json !== null && typeof json === 'object') || Array.isArray(json)) { 65 | throw new Error( 66 | `fromProto3JSONToInternalRepresentation: JSON representation for ${fullyQualifiedTypeName} expects a string, a number, or a boolean, but got ${typeof json}`, 67 | ); 68 | } 69 | return wrapperFromProto3JSON(fullyQualifiedTypeName, json); 70 | } 71 | 72 | if (json === null) { 73 | return null; 74 | } 75 | 76 | // Types that cannot be "null" 77 | if (fullyQualifiedTypeName === '.google.protobuf.Any') { 78 | return googleProtobufAnyFromProto3JSON(type.root, json); 79 | } 80 | 81 | if (fullyQualifiedTypeName === '.google.protobuf.Struct') { 82 | if (typeof json !== 'object') { 83 | throw new Error( 84 | `fromProto3JSONToInternalRepresentation: google.protobuf.Struct must be an object but got ${typeof json}`, 85 | ); 86 | } 87 | if (Array.isArray(json)) { 88 | throw new Error( 89 | 'fromProto3JSONToInternalRepresentation: google.protobuf.Struct must be an object but got an array', 90 | ); 91 | } 92 | return googleProtobufStructFromProto3JSON(json); 93 | } 94 | 95 | if (fullyQualifiedTypeName === '.google.protobuf.ListValue') { 96 | if (!Array.isArray(json)) { 97 | throw new Error( 98 | `fromProto3JSONToInternalRepresentation: google.protobuf.ListValue must be an array but got ${typeof json}`, 99 | ); 100 | } 101 | return googleProtobufListValueFromProto3JSON(json); 102 | } 103 | 104 | if (fullyQualifiedTypeName === '.google.protobuf.Duration') { 105 | if (typeof json !== 'string') { 106 | throw new Error( 107 | `fromProto3JSONToInternalRepresentation: google.protobuf.Duration must be a string but got ${typeof json}`, 108 | ); 109 | } 110 | return googleProtobufDurationFromProto3JSON(json); 111 | } 112 | 113 | if (fullyQualifiedTypeName === '.google.protobuf.Timestamp') { 114 | if (typeof json !== 'string') { 115 | throw new Error( 116 | `fromProto3JSONToInternalRepresentation: google.protobuf.Timestamp must be a string but got ${typeof json}`, 117 | ); 118 | } 119 | return googleProtobufTimestampFromProto3JSON(json); 120 | } 121 | 122 | if (fullyQualifiedTypeName === '.google.protobuf.FieldMask') { 123 | if (typeof json !== 'string') { 124 | throw new Error( 125 | `fromProto3JSONToInternalRepresentation: google.protobuf.FieldMask must be a string but got ${typeof json}`, 126 | ); 127 | } 128 | return googleProtobufFieldMaskFromProto3JSON(json); 129 | } 130 | 131 | const result: FromObjectValue = {}; 132 | for (const [key, value] of Object.entries(json)) { 133 | const field = type.fields[key]; 134 | if (!field) { 135 | continue; 136 | } 137 | 138 | const resolvedType = field.resolvedType; 139 | const fieldType = field.type; 140 | 141 | if (field.repeated) { 142 | if (value === null) { 143 | result[key] = []; 144 | } else { 145 | if (!Array.isArray(value)) { 146 | throw new Error( 147 | `fromProto3JSONToInternalRepresentation: expected an array for field ${key}`, 148 | ); 149 | } 150 | result[key] = value.map(element => 151 | fromProto3JSONToInternalRepresentation( 152 | resolvedType || fieldType, 153 | element, 154 | ), 155 | ); 156 | } 157 | } else if (field.map) { 158 | const map: FromObjectValue = {}; 159 | for (const [mapKey, mapValue] of Object.entries(value)) { 160 | map[mapKey] = fromProto3JSONToInternalRepresentation( 161 | resolvedType || fieldType, 162 | mapValue as JSONValue, 163 | ); 164 | } 165 | result[key] = map; 166 | } else if ( 167 | fieldType.match(/^(?:(?:(?:u?int|fixed)(?:32|64))|float|double)$/) 168 | ) { 169 | if (typeof value !== 'number' && typeof value !== 'string') { 170 | throw new Error( 171 | `fromProto3JSONToInternalRepresentation: field ${key} of type ${field.type} cannot contain value ${value}`, 172 | ); 173 | } 174 | result[key] = value; 175 | } else if (fieldType === 'string') { 176 | if (typeof value !== 'string') { 177 | throw new Error( 178 | `fromProto3JSONToInternalRepresentation: field ${key} of type ${field.type} cannot contain value ${value}`, 179 | ); 180 | } 181 | result[key] = value; 182 | } else if (fieldType === 'bool') { 183 | if (typeof value !== 'boolean') { 184 | throw new Error( 185 | `fromProto3JSONToInternalRepresentation: field ${key} of type ${field.type} cannot contain value ${value}`, 186 | ); 187 | } 188 | result[key] = value; 189 | } else if (fieldType === 'bytes') { 190 | if (typeof value !== 'string') { 191 | throw new Error( 192 | `fromProto3JSONToInternalRepresentation: field ${key} of type ${field.type} cannot contain value ${value}`, 193 | ); 194 | } 195 | result[key] = bytesFromProto3JSON(value); 196 | } else { 197 | // Message type 198 | assert( 199 | resolvedType !== null, 200 | `Expected to be able to resolve type for field ${field.name}`, 201 | ); 202 | const deserializedValue = fromProto3JSONToInternalRepresentation( 203 | resolvedType!, 204 | value, 205 | ); 206 | result[key] = deserializedValue; 207 | } 208 | } 209 | 210 | return result; 211 | } 212 | 213 | export function fromProto3JSON(type: protobuf.Type, json: JSONValue) { 214 | const internalRepr = fromProto3JSONToInternalRepresentation(type, json); 215 | if (internalRepr === null) { 216 | return null; 217 | } 218 | // We only expect a real object here sine all special cases should be already resolved. Everything else is an internal error 219 | assert( 220 | typeof internalRepr === 'object' && !Array.isArray(internalRepr), 221 | `fromProto3JSON: expected an object, not ${json}`, 222 | ); 223 | return type.fromObject(internalRepr as {}); 224 | } 225 | -------------------------------------------------------------------------------- /typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export {JSONObject, JSONValue} from './types'; 16 | export {toProto3JSON, ToProto3JSONOptions} from './toproto3json'; 17 | export {fromProto3JSON} from './fromproto3json'; 18 | -------------------------------------------------------------------------------- /typescript/src/timestamp.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as protobuf from 'protobufjs'; 16 | import {FromObjectValue} from './types'; 17 | 18 | export interface Timestamp { 19 | seconds: number; // it's technically Long but we don't want to bring it as a dependency for now 20 | nanos?: number; 21 | } 22 | 23 | export function googleProtobufTimestampToProto3JSON( 24 | obj: protobuf.Message & Timestamp, 25 | ) { 26 | // seconds is an instance of Long so it won't be undefined 27 | const durationSeconds = obj.seconds; 28 | const date = new Date(durationSeconds * 1000).toISOString(); 29 | // Pad leading zeros if nano string length is less than 9. 30 | let nanos = obj.nanos?.toString().padStart(9, '0'); 31 | // Trim the unsignificant zeros and keep 3, 6, or 9 decimal digits. 32 | while (nanos && nanos.length > 3 && nanos.endsWith('000')) { 33 | nanos = nanos.slice(0, -3); 34 | } 35 | return date.replace(/(?:\.\d{0,9})/, '.' + nanos); 36 | } 37 | 38 | export function googleProtobufTimestampFromProto3JSON(json: string) { 39 | const match = json.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?/); 40 | if (!match) { 41 | throw new Error( 42 | `googleProtobufDurationFromProto3JSON: incorrect value ${json} passed as google.protobuf.Duration`, 43 | ); 44 | } 45 | const date = new Date(json); 46 | const millisecondsSinceEpoch = date.getTime(); 47 | const seconds = Math.floor(millisecondsSinceEpoch / 1000); 48 | // The fractional seconds in the JSON timestamps can go up to 9 digits (i.e. up to 1 nanosecond resolution). 49 | // However, Javascript Date object represent any date and time to millisecond precision. 50 | // To keep the precision, we extract the fractional seconds and append 0 until the length is equal to 9. 51 | let nanos = 0; 52 | const secondsFromDate = json.split('.')[1]; 53 | if (secondsFromDate) { 54 | nanos = parseInt(secondsFromDate.slice(0, -1).padEnd(9, '0')); 55 | } 56 | const result: FromObjectValue = {}; 57 | if (seconds !== 0) { 58 | result.seconds = seconds; 59 | } 60 | if (nanos !== 0) { 61 | result.nanos = nanos; 62 | } 63 | return result; 64 | } 65 | -------------------------------------------------------------------------------- /typescript/src/toproto3json.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as protobuf from 'protobufjs'; 16 | import {JSONObject, JSONValue, LongStub} from './types'; 17 | import {Any, googleProtobufAnyToProto3JSON} from './any'; 18 | import {bytesToProto3JSON} from './bytes'; 19 | import {getFullyQualifiedTypeName, wrapperTypes} from './util'; 20 | import {resolveEnumValueToNumber, resolveEnumValueToString} from './enum'; 21 | import { 22 | googleProtobufListValueToProto3JSON, 23 | googleProtobufStructToProto3JSON, 24 | googleProtobufValueToProto3JSON, 25 | ListValue, 26 | Struct, 27 | Value, 28 | } from './value'; 29 | import {Duration, googleProtobufDurationToProto3JSON} from './duration'; 30 | import {googleProtobufTimestampToProto3JSON, Timestamp} from './timestamp'; 31 | import { 32 | BoolValue, 33 | BytesValue, 34 | NumberValue, 35 | StringValue, 36 | wrapperToProto3JSON, 37 | } from './wrappers'; 38 | import {FieldMask, googleProtobufFieldMaskToProto3JSON} from './fieldmask'; 39 | 40 | export interface ToProto3JSONOptions { 41 | numericEnums: boolean; 42 | } 43 | 44 | // Convert a single value, which might happen to be an instance of Long, to JSONValue 45 | function convertSingleValue(value: JSONValue | object): JSONValue { 46 | if (typeof value === 'object') { 47 | if (value?.constructor?.name === 'Long') { 48 | return (value as LongStub).toString(); 49 | } 50 | throw new Error(`toProto3JSON: don't know how to convert value ${value}`); 51 | } 52 | return value; 53 | } 54 | 55 | export function toProto3JSON( 56 | obj: protobuf.Message, 57 | options?: ToProto3JSONOptions, 58 | ): JSONValue { 59 | const objType = obj.$type; 60 | if (!objType) { 61 | throw new Error( 62 | 'Cannot serialize object to proto3 JSON since its .$type is unknown. Use Type.fromObject(obj) before calling toProto3JSON.', 63 | ); 64 | } 65 | 66 | objType.resolveAll(); 67 | const typeName = getFullyQualifiedTypeName(objType); 68 | 69 | // Types that require special handling according to 70 | // https://developers.google.com/protocol-buffers/docs/proto3#json 71 | if (typeName === '.google.protobuf.Any') { 72 | return googleProtobufAnyToProto3JSON( 73 | obj as protobuf.Message & Any, 74 | options, 75 | ); 76 | } 77 | 78 | if (typeName === '.google.protobuf.Value') { 79 | return googleProtobufValueToProto3JSON(obj as protobuf.Message & Value); 80 | } 81 | 82 | if (typeName === '.google.protobuf.Struct') { 83 | return googleProtobufStructToProto3JSON(obj as protobuf.Message & Struct); 84 | } 85 | 86 | if (typeName === '.google.protobuf.ListValue') { 87 | return googleProtobufListValueToProto3JSON( 88 | obj as protobuf.Message & ListValue, 89 | ); 90 | } 91 | 92 | if (typeName === '.google.protobuf.Duration') { 93 | return googleProtobufDurationToProto3JSON( 94 | obj as protobuf.Message & Duration, 95 | ); 96 | } 97 | 98 | if (typeName === '.google.protobuf.Timestamp') { 99 | return googleProtobufTimestampToProto3JSON( 100 | obj as protobuf.Message & Timestamp, 101 | ); 102 | } 103 | 104 | if (typeName === '.google.protobuf.FieldMask') { 105 | return googleProtobufFieldMaskToProto3JSON( 106 | obj as protobuf.Message & FieldMask, 107 | ); 108 | } 109 | 110 | if (wrapperTypes.has(typeName)) { 111 | return wrapperToProto3JSON( 112 | obj as protobuf.Message & 113 | (NumberValue | StringValue | BoolValue | BytesValue), 114 | ); 115 | } 116 | 117 | const result: JSONObject = {}; 118 | for (const [key, value] of Object.entries(obj)) { 119 | const field = objType.fields[key]; 120 | const fieldResolvedType = field.resolvedType; 121 | const fieldFullyQualifiedTypeName = fieldResolvedType 122 | ? getFullyQualifiedTypeName(fieldResolvedType) 123 | : null; 124 | if (value === null) { 125 | result[key] = null; 126 | continue; 127 | } 128 | if (Array.isArray(value)) { 129 | if (value.length === 0) { 130 | // ignore repeated fields with no values 131 | continue; 132 | } 133 | // if the repeated value has a complex type, convert it to proto3 JSON, otherwise use as is 134 | result[key] = value.map( 135 | fieldResolvedType 136 | ? element => { 137 | return toProto3JSON(element, options); 138 | } 139 | : convertSingleValue, 140 | ); 141 | continue; 142 | } 143 | if (field.map) { 144 | const map: JSONObject = {}; 145 | for (const [mapKey, mapValue] of Object.entries(value)) { 146 | // if the map value has a complex type, convert it to proto3 JSON, otherwise use as is 147 | map[mapKey] = fieldResolvedType 148 | ? toProto3JSON(mapValue as protobuf.Message, options) 149 | : convertSingleValue(mapValue as JSONValue); 150 | } 151 | result[key] = map; 152 | continue; 153 | } 154 | if (fieldFullyQualifiedTypeName === '.google.protobuf.NullValue') { 155 | result[key] = null; 156 | continue; 157 | } 158 | if (fieldResolvedType && 'values' in fieldResolvedType && value !== null) { 159 | if (options?.numericEnums) { 160 | result[key] = resolveEnumValueToNumber(fieldResolvedType, value); 161 | } else { 162 | result[key] = resolveEnumValueToString(fieldResolvedType, value); 163 | } 164 | continue; 165 | } 166 | if (fieldResolvedType) { 167 | result[key] = toProto3JSON(value, options); 168 | continue; 169 | } 170 | if ( 171 | typeof value === 'string' || 172 | typeof value === 'number' || 173 | typeof value === 'boolean' || 174 | value === null 175 | ) { 176 | if (typeof value === 'number' && !Number.isFinite(value)) { 177 | result[key] = value.toString(); 178 | continue; 179 | } 180 | result[key] = value; 181 | continue; 182 | } 183 | if (Buffer.isBuffer(value) || value instanceof Uint8Array) { 184 | result[key] = bytesToProto3JSON(value); 185 | continue; 186 | } 187 | result[key] = convertSingleValue(value); 188 | continue; 189 | } 190 | return result; 191 | } 192 | -------------------------------------------------------------------------------- /typescript/src/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export type JSONValue = 16 | | string 17 | | number 18 | | boolean 19 | | null 20 | | JSONValue[] 21 | | {[key: string]: JSONValue}; 22 | 23 | export interface JSONObject { 24 | [key: string]: JSONValue; 25 | } 26 | 27 | export type FromObjectValue = 28 | | string 29 | | number 30 | | boolean 31 | | null 32 | | FromObjectValue[] 33 | | Buffer 34 | | Uint8Array 35 | | {[key: string]: FromObjectValue}; 36 | 37 | // We don't want to import long here, we only need .toString() from it 38 | export interface LongStub { 39 | toString: () => string; 40 | } 41 | -------------------------------------------------------------------------------- /typescript/src/util.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export function getFullyQualifiedTypeName( 16 | type: protobuf.Type | protobuf.Namespace | protobuf.Enum, 17 | ) { 18 | // We assume that the protobuf package tree cannot have cycles. 19 | let fullyQualifiedTypeName = ''; 20 | while (type.parent) { 21 | fullyQualifiedTypeName = `.${type.name}${fullyQualifiedTypeName}`; 22 | type = type.parent; 23 | } 24 | return fullyQualifiedTypeName; 25 | } 26 | 27 | export const wrapperTypes = new Set([ 28 | '.google.protobuf.DoubleValue', 29 | '.google.protobuf.FloatValue', 30 | '.google.protobuf.Int64Value', 31 | '.google.protobuf.UInt64Value', 32 | '.google.protobuf.Int32Value', 33 | '.google.protobuf.UInt32Value', 34 | '.google.protobuf.BoolValue', 35 | '.google.protobuf.StringValue', 36 | '.google.protobuf.BytesValue', 37 | ]); 38 | 39 | export function assert(assertion: boolean, message: string) { 40 | if (!assertion) { 41 | throw new Error(message); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /typescript/src/value.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import {assert} from './util'; 16 | import {FromObjectValue, JSONObject, JSONValue} from './types'; 17 | 18 | export interface Struct { 19 | fields: { 20 | [key: string]: Value; 21 | }; 22 | } 23 | 24 | export interface ListValue { 25 | values: Array; 26 | } 27 | 28 | export interface Value { 29 | nullValue?: 0; 30 | numberValue?: number; 31 | stringValue?: string; 32 | boolValue?: boolean; 33 | listValue?: ListValue; 34 | structValue?: Struct; 35 | } 36 | 37 | export function googleProtobufStructToProto3JSON( 38 | obj: protobuf.Message & Struct, 39 | ) { 40 | const result: JSONObject = {}; 41 | const fields = obj.fields; 42 | for (const [key, value] of Object.entries(fields)) { 43 | result[key] = googleProtobufValueToProto3JSON( 44 | value as protobuf.Message & Value, 45 | ); 46 | } 47 | return result; 48 | } 49 | 50 | export function googleProtobufListValueToProto3JSON( 51 | obj: protobuf.Message & ListValue, 52 | ) { 53 | assert( 54 | Array.isArray(obj.values), 55 | 'ListValue internal representation must contain array of values', 56 | ); 57 | return (obj.values as Array).map( 58 | googleProtobufValueToProto3JSON, 59 | ); 60 | } 61 | 62 | export function googleProtobufValueToProto3JSON( 63 | obj: protobuf.Message & Value, 64 | ): JSONValue { 65 | if (Object.prototype.hasOwnProperty.call(obj, 'nullValue')) { 66 | return null; 67 | } 68 | 69 | if ( 70 | Object.prototype.hasOwnProperty.call(obj, 'numberValue') && 71 | typeof obj.numberValue === 'number' 72 | ) { 73 | if (!Number.isFinite(obj.numberValue)) { 74 | return obj.numberValue.toString(); 75 | } 76 | return obj.numberValue; 77 | } 78 | 79 | if ( 80 | Object.prototype.hasOwnProperty.call(obj, 'stringValue') && 81 | typeof obj.stringValue === 'string' 82 | ) { 83 | return obj.stringValue; 84 | } 85 | 86 | if ( 87 | Object.prototype.hasOwnProperty.call(obj, 'boolValue') && 88 | typeof obj.boolValue === 'boolean' 89 | ) { 90 | return obj.boolValue; 91 | } 92 | 93 | if ( 94 | Object.prototype.hasOwnProperty.call(obj, 'structValue') && 95 | typeof obj.structValue === 'object' 96 | ) { 97 | return googleProtobufStructToProto3JSON( 98 | obj.structValue as protobuf.Message & Struct, 99 | ); 100 | } 101 | 102 | if ( 103 | Object.prototype.hasOwnProperty.call(obj, 'listValue') && 104 | typeof obj === 'object' && 105 | typeof obj.listValue === 'object' 106 | ) { 107 | return googleProtobufListValueToProto3JSON( 108 | obj.listValue as protobuf.Message & ListValue, 109 | ); 110 | } 111 | 112 | // Assuming empty Value to be null 113 | return null; 114 | } 115 | 116 | export function googleProtobufStructFromProto3JSON( 117 | json: JSONObject, 118 | ): FromObjectValue { 119 | const fields: FromObjectValue = {}; 120 | for (const [key, value] of Object.entries(json)) { 121 | fields[key] = googleProtobufValueFromProto3JSON(value); 122 | } 123 | return {fields}; 124 | } 125 | 126 | export function googleProtobufListValueFromProto3JSON( 127 | json: JSONValue[], 128 | ): FromObjectValue { 129 | return { 130 | values: json.map(element => googleProtobufValueFromProto3JSON(element)), 131 | }; 132 | } 133 | 134 | export function googleProtobufValueFromProto3JSON( 135 | json: JSONValue, 136 | ): FromObjectValue { 137 | if (json === null) { 138 | return {nullValue: 'NULL_VALUE'}; 139 | } 140 | 141 | if (typeof json === 'number') { 142 | return {numberValue: json}; 143 | } 144 | 145 | if (typeof json === 'string') { 146 | return {stringValue: json}; 147 | } 148 | 149 | if (typeof json === 'boolean') { 150 | return {boolValue: json}; 151 | } 152 | 153 | if (Array.isArray(json)) { 154 | return { 155 | listValue: googleProtobufListValueFromProto3JSON(json), 156 | }; 157 | } 158 | 159 | if (typeof json === 'object') { 160 | return { 161 | structValue: googleProtobufStructFromProto3JSON(json), 162 | }; 163 | } 164 | 165 | throw new Error( 166 | `googleProtobufValueFromProto3JSON: incorrect parameter type: ${typeof json}`, 167 | ); 168 | } 169 | -------------------------------------------------------------------------------- /typescript/src/wrappers.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import {bytesFromProto3JSON, bytesToProto3JSON} from './bytes'; 16 | import {LongStub} from './types'; 17 | import {assert} from './util'; 18 | 19 | export interface NumberValue { 20 | value: number | object; // Long can be passed here 21 | } 22 | 23 | export interface StringValue { 24 | value: string; 25 | } 26 | 27 | export interface BoolValue { 28 | value: boolean; 29 | } 30 | 31 | export interface BytesValue { 32 | value: Buffer | Uint8Array; 33 | } 34 | 35 | export function wrapperToProto3JSON( 36 | obj: protobuf.Message & (NumberValue | StringValue | BoolValue | BytesValue), 37 | ) { 38 | if (!Object.prototype.hasOwnProperty.call(obj, 'value')) { 39 | return null; 40 | } 41 | if (Buffer.isBuffer(obj.value) || obj.value instanceof Uint8Array) { 42 | return bytesToProto3JSON(obj.value); 43 | } 44 | if (typeof obj.value === 'object') { 45 | assert( 46 | obj.value.constructor.name === 'Long', 47 | `wrapperToProto3JSON: expected to see a number, a string, a boolean, or a Long, but got ${obj.value}`, 48 | ); 49 | return (obj.value as LongStub).toString(); 50 | } 51 | // JSON accept special string values "NaN", "Infinity", and "-Infinity". 52 | if (typeof obj.value === 'number' && !Number.isFinite(obj.value)) { 53 | return obj.value.toString(); 54 | } 55 | return obj.value; 56 | } 57 | 58 | export function wrapperFromProto3JSON( 59 | typeName: string, 60 | json: number | string | boolean | null, 61 | ) { 62 | if (json === null) { 63 | return { 64 | value: null, 65 | }; 66 | } 67 | if (typeName === '.google.protobuf.BytesValue') { 68 | if (typeof json !== 'string') { 69 | throw new Error( 70 | `numberWrapperFromProto3JSON: expected to get a string for google.protobuf.BytesValue but got ${typeof json}`, 71 | ); 72 | } 73 | return { 74 | value: bytesFromProto3JSON(json), 75 | }; 76 | } 77 | return { 78 | value: json, 79 | }; 80 | } 81 | -------------------------------------------------------------------------------- /typescript/test/system/install.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import {packNTest} from 'pack-n-play'; 16 | import {describe, it} from 'mocha'; 17 | 18 | describe('Packing test', () => { 19 | it('should work in TypeScript code', async function () { 20 | this.timeout(300000); 21 | const options = { 22 | packageDir: process.cwd(), 23 | sample: { 24 | description: 'runs serializer and deserializer in TypeScript', 25 | ts: ` 26 | import * as assert from 'assert'; 27 | import * as protobuf from 'protobufjs'; 28 | import * as serializer from 'proto3-json-serializer'; 29 | const root = protobuf.Root.fromJSON({ 30 | "nested": { 31 | "Test": { 32 | "fields": { 33 | "field": { 34 | "type": "string", 35 | "id": 1 36 | } 37 | } 38 | } 39 | } 40 | }); 41 | const Test = root.lookupType('Test'); 42 | const json = { 43 | field: 'test', 44 | }; 45 | const message = serializer.fromProto3JSON(Test, json); 46 | assert(message); 47 | const serialized = serializer.toProto3JSON(message); 48 | assert.deepStrictEqual(serialized, json); 49 | `, 50 | dependencies: ['protobufjs@^7.4.0'], 51 | }, 52 | }; 53 | await packNTest(options).catch(err => { 54 | console.error(err); 55 | throw err; 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /typescript/test/unit/any.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testRegularAny(root: protobuf.Root) { 23 | const MessageWithAny = root.lookupType('test.MessageWithAny'); 24 | const AnyContent = root.lookupType('test.AnyContent'); 25 | const any = AnyContent.fromObject({ 26 | stringField: 'string', 27 | }); 28 | const message = MessageWithAny.fromObject({ 29 | anyField: { 30 | type_url: 'types.googleapis.com/test.AnyContent', // note: snake case here because of protobuf.js bug/feature 31 | value: Buffer.from(AnyContent.encode(any).finish()), 32 | }, 33 | }); 34 | const json = { 35 | anyField: { 36 | '@type': 'types.googleapis.com/test.AnyContent', 37 | stringField: 'string', 38 | }, 39 | }; 40 | 41 | it('serializes to proto3 JSON', () => { 42 | const serialized = toProto3JSON(message); 43 | assert.deepStrictEqual(serialized, json); 44 | }); 45 | 46 | it('deserializes from proto3 JSON', () => { 47 | const deserialized = fromProto3JSON(MessageWithAny, json); 48 | assert.deepStrictEqual(deserialized, message); 49 | }); 50 | } 51 | 52 | function testAnyWithSpecialType(root: protobuf.Root) { 53 | const MessageWithAny = root.lookupType('test.MessageWithAny'); 54 | const Value = root.lookupType('google.protobuf.Value'); 55 | const value = Value.fromObject({ 56 | stringValue: 'test', 57 | }); 58 | const message = MessageWithAny.fromObject({ 59 | anyField: { 60 | type_url: 'types.googleapis.com/google.protobuf.Value', 61 | value: Buffer.from(Value.encode(value).finish()), 62 | }, 63 | }); 64 | const json = { 65 | anyField: { 66 | '@type': 'types.googleapis.com/google.protobuf.Value', 67 | value: 'test', 68 | }, 69 | }; 70 | 71 | it('serializes google.protobuf.Any with a special type to proto3 JSON', () => { 72 | const serialized = toProto3JSON(message); 73 | assert.deepStrictEqual(serialized, json); 74 | }); 75 | 76 | it('deserializes google.protobuf.Any with a special type from proto3 JSON', () => { 77 | const deserialized = fromProto3JSON(MessageWithAny, json); 78 | assert.deepStrictEqual(deserialized, message); 79 | }); 80 | } 81 | 82 | testTwoTypesOfLoad('google.protobuf.Any', [ 83 | testRegularAny, 84 | testAnyWithSpecialType, 85 | ]); 86 | -------------------------------------------------------------------------------- /typescript/test/unit/bytes.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testBytes(root: protobuf.Root) { 23 | const MessageWithBytesField = root.lookupType('test.MessageWithBytesField'); 24 | const buffer = Buffer.from('buffer'); 25 | const message = MessageWithBytesField.fromObject({ 26 | bytesField: buffer, 27 | }); 28 | const uint8Array = new Uint8Array(buffer, 0, buffer.length); 29 | const messageWithUint8Array = MessageWithBytesField.fromObject({ 30 | bytesField: uint8Array, 31 | }); 32 | const json = { 33 | bytesField: buffer.toString('base64'), 34 | }; 35 | 36 | it('serializes bytes as Buffer to proto3 JSON', () => { 37 | const serialized = toProto3JSON(message); 38 | assert.deepStrictEqual(serialized, json); 39 | }); 40 | 41 | it('serializes bytes as Uint8Array to proto3 JSON', () => { 42 | const serialized = toProto3JSON(messageWithUint8Array); 43 | assert.deepStrictEqual(serialized, json); 44 | }); 45 | 46 | it('deserializes from proto3 JSON', () => { 47 | const deserialized = fromProto3JSON(MessageWithBytesField, json); 48 | assert.deepStrictEqual(deserialized, message); 49 | }); 50 | } 51 | 52 | testTwoTypesOfLoad('bytes', testBytes); 53 | -------------------------------------------------------------------------------- /typescript/test/unit/common.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as path from 'path'; 16 | import * as protobuf from 'protobufjs'; 17 | import {describe} from 'mocha'; 18 | 19 | const fixtures = path.join(__dirname, '..', '..', '..', 'test-fixtures'); 20 | 21 | interface TestFunction { 22 | (root: protobuf.Root): void; 23 | } 24 | 25 | export function testTwoTypesOfLoad( 26 | name: string, 27 | testFunctions: TestFunction | Array, 28 | ) { 29 | const functions: Array = Array.isArray(testFunctions) 30 | ? testFunctions 31 | : [testFunctions]; 32 | describe(name, () => { 33 | describe('loadSync test', () => { 34 | const root = protobuf.loadSync( 35 | path.join(fixtures, 'proto', 'test.proto'), 36 | ); 37 | functions.map(func => { 38 | func(root); 39 | }); 40 | }); 41 | describe('fromJSON test', () => { 42 | const root = protobuf.Root.fromJSON( 43 | require(path.join(fixtures, 'proto', 'test.json')), 44 | ); 45 | functions.map(func => { 46 | func(root); 47 | }); 48 | }); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /typescript/test/unit/duration.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testDuration(root: protobuf.Root) { 23 | const MessageWithDuration = root.lookupType('test.MessageWithDuration'); 24 | // note the zero padding according to the spec: 25 | // the number of the digits in the fractional part must be 0, 3, 6, or 9 26 | const testMapping = [ 27 | {duration: {seconds: 3}, value: '3s'}, 28 | {duration: {nanos: 100000000}, value: '0.100s'}, 29 | {duration: {}, value: '0s'}, 30 | {duration: {seconds: 3, nanos: 5}, value: '3.000000005s'}, 31 | {duration: {seconds: 3, nanos: 5000}, value: '3.000005s'}, 32 | {duration: {seconds: 3, nanos: 5000000}, value: '3.005s'}, 33 | {duration: {nanos: 1}, value: '0.000000001s'}, 34 | {duration: {nanos: 10}, value: '0.000000010s'}, 35 | {duration: {nanos: 100}, value: '0.000000100s'}, 36 | {duration: {nanos: 1000}, value: '0.000001s'}, 37 | {duration: {nanos: 10000}, value: '0.000010s'}, 38 | {duration: {nanos: 100000}, value: '0.000100s'}, 39 | {duration: {nanos: 1000000}, value: '0.001s'}, 40 | {duration: {nanos: 10000000}, value: '0.010s'}, 41 | {duration: {nanos: 100000000}, value: '0.100s'}, 42 | ]; 43 | 44 | for (const mapping of testMapping) { 45 | const message = MessageWithDuration.fromObject({ 46 | durationField: mapping.duration, 47 | }); 48 | const json = { 49 | durationField: mapping.value, 50 | }; 51 | 52 | it(`serializes ${JSON.stringify(mapping.duration)} to proto3 JSON`, () => { 53 | const serialized = toProto3JSON(message); 54 | assert.deepStrictEqual(serialized, json); 55 | }); 56 | 57 | it(`deserializes "${mapping.value}" from proto3 JSON`, () => { 58 | const deserialized = fromProto3JSON(MessageWithDuration, json); 59 | assert.deepStrictEqual(deserialized, message); 60 | }); 61 | } 62 | } 63 | 64 | testTwoTypesOfLoad('google.protobuf.Duration', testDuration); 65 | -------------------------------------------------------------------------------- /typescript/test/unit/empty.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testEmpty(root: protobuf.Root) { 23 | const MessageWithEmpty = root.lookupType('test.MessageWithEmpty'); 24 | const message = MessageWithEmpty.fromObject({ 25 | emptyField: {}, 26 | }); 27 | const json = { 28 | emptyField: {}, 29 | }; 30 | 31 | it('serializes to proto3 JSON', () => { 32 | const serialized = toProto3JSON(message); 33 | assert.deepStrictEqual(serialized, json); 34 | }); 35 | 36 | it('deserializes from proto3 JSON', () => { 37 | const deserialized = fromProto3JSON(MessageWithEmpty, json); 38 | assert.deepStrictEqual(deserialized, message); 39 | }); 40 | } 41 | 42 | testTwoTypesOfLoad('google.protobuf.Empty', testEmpty); 43 | -------------------------------------------------------------------------------- /typescript/test/unit/enum.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testEnum(root: protobuf.Root) { 23 | const MessageWithEnum = root.lookupType('test.MessageWithEnum'); 24 | const message = MessageWithEnum.fromObject({ 25 | enumField: 'KNOWN', 26 | }); 27 | const json = { 28 | enumField: 'KNOWN', 29 | }; 30 | const jsonWithNumber = { 31 | enumField: 1, 32 | }; 33 | 34 | it('serializes to proto3 JSON', () => { 35 | const serialized = toProto3JSON(message); 36 | assert.deepStrictEqual(serialized, json); 37 | }); 38 | 39 | it('serializes to proto3 JSON with numeric enum values', () => { 40 | const serialized = toProto3JSON(message, {numericEnums: true}); 41 | assert.deepStrictEqual(serialized, jsonWithNumber); 42 | console.log(serialized); 43 | }); 44 | 45 | it('deserializes from proto3 JSON', () => { 46 | const deserialized = fromProto3JSON(MessageWithEnum, json); 47 | assert.deepStrictEqual(deserialized, message); 48 | }); 49 | 50 | it('fromProto3JSON allows unknown enum string value', () => { 51 | // the following call should not fail 52 | fromProto3JSON(MessageWithEnum, { 53 | enumField: 'WRONG VALUE', 54 | }); 55 | }); 56 | 57 | it('fromProto3JSON allows unknown enum number value', () => { 58 | // the following call should not fail 59 | fromProto3JSON(MessageWithEnum, {enumField: 42}); 60 | }); 61 | } 62 | 63 | testTwoTypesOfLoad('enum field', testEnum); 64 | -------------------------------------------------------------------------------- /typescript/test/unit/error-coverage.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | import {JSONValue} from '../../src'; 22 | 23 | function testAnyErrorCoverage(root: protobuf.Root) { 24 | const Any = root.lookupType('google.protobuf.Any'); 25 | const message = Any.create({ 26 | type_url: 'types.googleapis.com/UnknownType', 27 | value: Buffer.from(''), 28 | }); 29 | const json = { 30 | '@type': 'types.googleapis.com/UnknownType', 31 | stringField: 'string', 32 | }; 33 | const noTypeUrlJson = { 34 | stringField: 'string', 35 | }; 36 | const incorrectSpecialValueJson = { 37 | '@type': 'types.googleapis.com/google.protobuf.Value', 38 | stringField: 'string', 39 | }; 40 | 41 | it('Any catches unknown type when deserializing', () => { 42 | assert.throws(() => { 43 | fromProto3JSON(Any, json); 44 | }); 45 | }); 46 | 47 | it('Any catches unknown type when serializing', () => { 48 | assert.throws(() => { 49 | toProto3JSON(message); 50 | }); 51 | }); 52 | 53 | it('Any requires @type field when deserializing', () => { 54 | assert.throws(() => { 55 | fromProto3JSON(Any, noTypeUrlJson); 56 | }); 57 | }); 58 | 59 | it('Any does not accept incorrect special JSON', () => { 60 | assert.throws(() => { 61 | fromProto3JSON(Any, incorrectSpecialValueJson); 62 | }); 63 | }); 64 | } 65 | 66 | function testTypeMismatch(root: protobuf.Root) { 67 | const PrimitiveTypes = root.lookupType('test.PrimitiveTypes'); 68 | const MessageWithBytesField = root.lookupType('test.MessageWithBytesField'); 69 | const MessageWithRepeated = root.lookupType('test.MessageWithRepeated'); 70 | const MessageWithAny = root.lookupType('test.MessageWithAny'); 71 | const MessageWithEnum = root.lookupType('test.MessageWithEnum'); 72 | const MessageWithStruct = root.lookupType('test.MessageWithStruct'); 73 | const MessageWithValue = root.lookupType('test.MessageWithValue'); 74 | const MessageWithListValue = root.lookupType('test.MessageWithListValue'); 75 | const MessageWithDuration = root.lookupType('test.MessageWithDuration'); 76 | const MessageWithTimestamp = root.lookupType('test.MessageWithTimestamp'); 77 | const MessageWithWrappers = root.lookupType('test.MessageWithWrappers'); 78 | const MessageWithFieldMask = root.lookupType('test.MessageWithFieldMask'); 79 | 80 | it('fromProto3JSON catches wrong value for integer fields', () => { 81 | assert.throws(() => { 82 | fromProto3JSON(PrimitiveTypes, {integerField: true}); 83 | }); 84 | }); 85 | 86 | it('fromProto3JSON catches wrong value for string fields', () => { 87 | assert.throws(() => { 88 | fromProto3JSON(PrimitiveTypes, {stringField: 42}); 89 | }); 90 | }); 91 | 92 | it('fromProto3JSON catches wrong value for bool fields', () => { 93 | assert.throws(() => { 94 | fromProto3JSON(PrimitiveTypes, {boolField: 'true'}); 95 | }); 96 | }); 97 | 98 | it('fromProto3JSON catches wrong value for bytes fields', () => { 99 | assert.throws(() => { 100 | fromProto3JSON(MessageWithBytesField, {bytesField: 1234}); 101 | }); 102 | }); 103 | 104 | it('fromProto3JSON requires an array for repeated fields', () => { 105 | assert.throws(() => { 106 | fromProto3JSON(MessageWithRepeated, {repeatedString: 'test'}); 107 | }); 108 | }); 109 | 110 | it('fromProto3JSON requires Any to be an object', () => { 111 | assert.throws(() => { 112 | fromProto3JSON(MessageWithAny, {anyField: 'zzz'}); 113 | }); 114 | }); 115 | 116 | it('fromProto3JSON requires enum to be a string or a number', () => { 117 | assert.throws(() => { 118 | fromProto3JSON(MessageWithEnum, {enumField: {}}); 119 | }); 120 | }); 121 | 122 | it('fromProto3JSON does not allow primitive types for Struct values', () => { 123 | assert.throws(() => { 124 | fromProto3JSON(MessageWithStruct, {structField: 42}); 125 | }); 126 | }); 127 | 128 | it('fromProto3JSON does not allow arrays for Struct values', () => { 129 | assert.throws(() => { 130 | fromProto3JSON(MessageWithStruct, {structField: [41, 42]}); 131 | }); 132 | }); 133 | 134 | it('fromProto3JSON does not allow objects for ListValue values', () => { 135 | assert.throws(() => { 136 | fromProto3JSON(MessageWithListValue, {listValueField: {}}); 137 | }); 138 | }); 139 | 140 | it('fromProto3JSON does not allow non-JSON types', () => { 141 | const func = () => {}; 142 | // calling it just for coverage :) 143 | func(); 144 | assert.throws(() => { 145 | fromProto3JSON(MessageWithValue, { 146 | valueField: func, 147 | } as unknown as JSONValue); 148 | }); 149 | }); 150 | 151 | it('fromProto3JSON does not allow non-string types for google.protobuf.Duration', () => { 152 | assert.throws(() => { 153 | fromProto3JSON(MessageWithDuration, { 154 | durationField: {seconds: 3, nanos: 0}, 155 | }); 156 | }); 157 | }); 158 | 159 | it('fromProto3JSON does not allow incorrect strings for google.protobuf.Duration', () => { 160 | assert.throws(() => { 161 | fromProto3JSON(MessageWithDuration, { 162 | durationField: '12345', 163 | }); 164 | }); 165 | }); 166 | 167 | it('fromProto3JSON does not allow non-string types for google.protobuf.Timestamp', () => { 168 | assert.throws(() => { 169 | fromProto3JSON(MessageWithTimestamp, { 170 | timestampField: {seconds: 3, nanos: 0}, 171 | }); 172 | }); 173 | }); 174 | 175 | it('fromProto3JSON does not allow incorrect strings for google.protobuf.Timestamp', () => { 176 | assert.throws(() => { 177 | fromProto3JSON(MessageWithTimestamp, { 178 | timestampField: '12345', 179 | }); 180 | }); 181 | }); 182 | 183 | it('fromProto3JSON does not allow object for wrapper types', () => { 184 | assert.throws(() => { 185 | fromProto3JSON(MessageWithWrappers, { 186 | doubleValueField: {}, 187 | }); 188 | }); 189 | }); 190 | 191 | it('fromProto3JSON does not allow non-strings for BytesValue', () => { 192 | assert.throws(() => { 193 | fromProto3JSON(MessageWithWrappers, { 194 | bytesValueField: 42, 195 | }); 196 | }); 197 | }); 198 | 199 | it('fromProto3JSON does not allow non-strings for FieldMask', () => { 200 | assert.throws(() => { 201 | fromProto3JSON(MessageWithFieldMask, { 202 | fieldMaskField: 42, 203 | }); 204 | }); 205 | }); 206 | } 207 | 208 | function testNull(root: protobuf.Root) { 209 | const PrimitiveTypes = root.lookupType('test.PrimitiveTypes'); 210 | const MessageWithValue = root.lookupType('test.MessageWithValue'); 211 | const MessageWithAny = root.lookupType('test.MessageWithAny'); 212 | 213 | it('fromProto3JSON returns null for null input', () => { 214 | assert.strictEqual(fromProto3JSON(PrimitiveTypes, null), null); 215 | }); 216 | 217 | it('toProto3JSON returns null for empty Value', () => { 218 | const emptyValue = MessageWithValue.fromObject({ 219 | valueField: {}, 220 | }); 221 | const emptyValueJson = { 222 | valueField: null, 223 | }; 224 | assert.deepStrictEqual(toProto3JSON(emptyValue), emptyValueJson); 225 | }); 226 | 227 | it('fromProto3JSON returns null for empty google.protobuf.Any', () => { 228 | const emptyAny = MessageWithAny.fromObject({}); 229 | const emptyAnyJson = { 230 | anyField: null, 231 | }; 232 | assert.deepStrictEqual( 233 | fromProto3JSON(MessageWithAny, emptyAnyJson), 234 | emptyAny, 235 | ); 236 | }); 237 | 238 | it('fromProto3JSON returns null for google.protobuf.Any with null value', () => { 239 | const nullAny = MessageWithAny.fromObject({ 240 | anyField: { 241 | type_url: 'types.googleapis.com/google.protobuf.Struct', 242 | value: null, 243 | }, 244 | }); 245 | const nullAnyJson = { 246 | anyField: { 247 | '@type': 'types.googleapis.com/google.protobuf.Struct', 248 | value: null, 249 | }, 250 | }; 251 | assert.deepStrictEqual( 252 | fromProto3JSON(MessageWithAny, nullAnyJson), 253 | nullAny, 254 | ); 255 | }); 256 | } 257 | 258 | function testRegularObject() { 259 | it('toProto3JSON does not accept non-Message object', () => { 260 | assert.throws(() => { 261 | toProto3JSON({ 262 | stringField: 'test', 263 | } as unknown as protobuf.Message); 264 | }); 265 | }); 266 | } 267 | 268 | testTwoTypesOfLoad('error coverage', [ 269 | testAnyErrorCoverage, 270 | testTypeMismatch, 271 | testNull, 272 | testRegularObject, 273 | ]); 274 | -------------------------------------------------------------------------------- /typescript/test/unit/fieldmask.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testFieldMask(root: protobuf.Root) { 23 | const MessageWithFieldMask = root.lookupType('test.MessageWithFieldMask'); 24 | 25 | const message = MessageWithFieldMask.fromObject({ 26 | fieldMaskField: { 27 | paths: ['a.b', 'c.d.e'], 28 | }, 29 | }); 30 | const json = { 31 | fieldMaskField: 'a.b,c.d.e', 32 | }; 33 | 34 | it('serializes to proto3 JSON', () => { 35 | const serialized = toProto3JSON(message); 36 | assert.deepStrictEqual(serialized, json); 37 | }); 38 | 39 | it('deserializes from proto3 JSON', () => { 40 | const deserialized = fromProto3JSON(MessageWithFieldMask, json); 41 | assert.deepStrictEqual(deserialized, message); 42 | }); 43 | } 44 | 45 | testTwoTypesOfLoad('google.protobuf.Timestamp', testFieldMask); 46 | -------------------------------------------------------------------------------- /typescript/test/unit/map.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testMap(root: protobuf.Root) { 23 | const MessageWithMap = root.lookupType('test.MessageWithMap'); 24 | const message = MessageWithMap.fromObject({ 25 | mapField: { 26 | key1: { 27 | stringField: 'value1', 28 | }, 29 | key2: { 30 | stringField: 'value2', 31 | }, 32 | }, 33 | stringMapField: { 34 | key1: 'string value 1', 35 | key2: 'string value 2', 36 | }, 37 | longMapField: { 38 | '2^63-1': '9223372036854775807', 39 | one: '1', 40 | 'minus one': '-1', 41 | zero: '0', 42 | }, 43 | }); 44 | const json = { 45 | mapField: { 46 | key1: { 47 | stringField: 'value1', 48 | }, 49 | key2: { 50 | stringField: 'value2', 51 | }, 52 | }, 53 | stringMapField: { 54 | key1: 'string value 1', 55 | key2: 'string value 2', 56 | }, 57 | longMapField: { 58 | '2^63-1': '9223372036854775807', 59 | one: '1', 60 | 'minus one': '-1', 61 | zero: '0', 62 | }, 63 | }; 64 | 65 | it('serializes to proto3 JSON', () => { 66 | const serialized = toProto3JSON(message); 67 | assert.deepStrictEqual(serialized, json); 68 | }); 69 | 70 | it('deserializes from proto3 JSON', () => { 71 | const deserialized = fromProto3JSON(MessageWithMap, json); 72 | assert.deepStrictEqual(deserialized, message); 73 | }); 74 | } 75 | 76 | testTwoTypesOfLoad('map fields', testMap); 77 | -------------------------------------------------------------------------------- /typescript/test/unit/nested.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | import {JSONObject} from '../../src'; 22 | 23 | function testNestedMessage(root: protobuf.Root) { 24 | const MessageWithNestedMessage = root.lookupType( 25 | 'test.MessageWithNestedMessage', 26 | ); 27 | const message = MessageWithNestedMessage.fromObject({ 28 | innerMessage: { 29 | stringField: 'test', 30 | }, 31 | }); 32 | const json = { 33 | innerMessage: { 34 | stringField: 'test', 35 | }, 36 | }; 37 | 38 | const emptyMessage = MessageWithNestedMessage.fromObject({}); 39 | const messageWithNull = MessageWithNestedMessage.fromObject({}); 40 | (messageWithNull as unknown as JSONObject)['innerMessage'] = null; 41 | const jsonWithNull = { 42 | innerMessage: null, 43 | }; 44 | const emptyJson = {}; 45 | 46 | it('serializes to proto3 JSON', () => { 47 | const serialized = toProto3JSON(message); 48 | assert.deepStrictEqual(serialized, json); 49 | }); 50 | 51 | it('deserializes from proto3 JSON', () => { 52 | const deserialized = fromProto3JSON(MessageWithNestedMessage, json); 53 | assert.deepStrictEqual(deserialized, message); 54 | }); 55 | 56 | it('serialized empty message results in empty proto3 JSON', () => { 57 | const serialized = toProto3JSON(emptyMessage); 58 | assert.deepStrictEqual(serialized, emptyJson); 59 | }); 60 | 61 | it('serialized null inner message results in JSON with null', () => { 62 | const serialized = toProto3JSON(messageWithNull); 63 | assert.deepStrictEqual(serialized, jsonWithNull); 64 | }); 65 | 66 | it('deserialized null inner message from proto3 JSON', () => { 67 | const deserialized = fromProto3JSON(MessageWithNestedMessage, jsonWithNull); 68 | assert.deepStrictEqual(deserialized, emptyMessage); 69 | }); 70 | } 71 | 72 | testTwoTypesOfLoad('nested messages', testNestedMessage); 73 | -------------------------------------------------------------------------------- /typescript/test/unit/oneof.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testOneof(root: protobuf.Root) { 23 | const MessageWithOneof = root.lookupType('test.MessageWithOneof'); 24 | const message1 = MessageWithOneof.fromObject({ 25 | stringField: 'test', 26 | }); 27 | const message2 = MessageWithOneof.fromObject({ 28 | integerField: 42, 29 | }); 30 | const message3 = MessageWithOneof.fromObject({ 31 | messageField: { 32 | stringField: 'test', 33 | }, 34 | }); 35 | const json1 = { 36 | stringField: 'test', 37 | }; 38 | const json2 = { 39 | integerField: 42, 40 | }; 41 | const json3 = { 42 | messageField: { 43 | stringField: 'test', 44 | }, 45 | }; 46 | 47 | it('serializes oneof 1 to proto3 JSON', () => { 48 | const serialized = toProto3JSON(message1); 49 | assert.deepStrictEqual(serialized, json1); 50 | }); 51 | 52 | it('deserializes oneof 1 from proto3 JSON', () => { 53 | const deserialized = fromProto3JSON(MessageWithOneof, json1); 54 | assert.deepStrictEqual(deserialized, message1); 55 | }); 56 | 57 | it('serializes oneof 2 to proto3 JSON', () => { 58 | const serialized = toProto3JSON(message2); 59 | assert.deepStrictEqual(serialized, json2); 60 | }); 61 | 62 | it('deserializes oneof 2 from proto3 JSON', () => { 63 | const deserialized = fromProto3JSON(MessageWithOneof, json2); 64 | assert.deepStrictEqual(deserialized, message2); 65 | }); 66 | 67 | it('serializes oneof 3 to proto3 JSON', () => { 68 | const serialized = toProto3JSON(message3); 69 | assert.deepStrictEqual(serialized, json3); 70 | }); 71 | 72 | it('deserializes oneof 3 from proto3 JSON', () => { 73 | const deserialized = fromProto3JSON(MessageWithOneof, json3); 74 | assert.deepStrictEqual(deserialized, message3); 75 | }); 76 | } 77 | 78 | testTwoTypesOfLoad('oneof', testOneof); 79 | -------------------------------------------------------------------------------- /typescript/test/unit/primitive.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testPrimitiveTypes(root: protobuf.Root) { 23 | const PrimitiveTypes = root.lookupType('test.PrimitiveTypes'); 24 | const message = PrimitiveTypes.fromObject({ 25 | integerField: -42, 26 | unsignedIntegerField: 42, 27 | fixedIntegerField: 128, 28 | stringField: 'test', 29 | boolField: true, 30 | int64Field: -43, 31 | uint64Field: 43, 32 | }); 33 | const json = { 34 | integerField: -42, 35 | unsignedIntegerField: 42, 36 | fixedIntegerField: 128, 37 | stringField: 'test', 38 | boolField: true, 39 | int64Field: '-43', 40 | uint64Field: '43', 41 | }; 42 | 43 | it('serializes to proto3 JSON', () => { 44 | const serialized = toProto3JSON(message); 45 | assert.deepStrictEqual(serialized, json); 46 | }); 47 | 48 | it('deserializes from proto3 JSON', () => { 49 | const deserialized = fromProto3JSON(PrimitiveTypes, json); 50 | assert.deepStrictEqual(deserialized, message); 51 | }); 52 | } 53 | 54 | function testLongIntegers(root: protobuf.Root) { 55 | const PrimitiveTypes = root.lookupType('test.PrimitiveTypes'); 56 | const message = PrimitiveTypes.fromObject({ 57 | int64Field: '-5011754511478056813', 58 | uint64Field: '5011754511478056813', 59 | }); 60 | const json = { 61 | int64Field: '-5011754511478056813', 62 | uint64Field: '5011754511478056813', 63 | }; 64 | 65 | it('serializes uint64 to proto3 JSON', () => { 66 | const serialized = toProto3JSON(message); 67 | assert.deepStrictEqual(serialized, json); 68 | }); 69 | 70 | it('deserializes uint64 from proto3 JSON', () => { 71 | const deserialized = fromProto3JSON(PrimitiveTypes, json); 72 | assert.deepStrictEqual(deserialized, message); 73 | }); 74 | } 75 | 76 | function testNotFiniteNumber(root: protobuf.Root) { 77 | const PrimitiveTypes = root.lookupType('test.PrimitiveTypes'); 78 | const messageNaN = PrimitiveTypes.fromObject({ 79 | doubleField: NaN, 80 | }); 81 | const jsonNaN = { 82 | doubleField: 'NaN', 83 | }; 84 | const messageInfinity = PrimitiveTypes.fromObject({ 85 | doubleField: Infinity, 86 | }); 87 | const jsonInfinity = { 88 | doubleField: 'Infinity', 89 | }; 90 | const messageNegInfinity = PrimitiveTypes.fromObject({ 91 | doubleField: Infinity, 92 | }); 93 | const jsonNegInfinity = { 94 | doubleField: 'Infinity', 95 | }; 96 | it('serializes NaN to proto3 JSON', () => { 97 | const serialized = toProto3JSON(messageNaN); 98 | assert.deepStrictEqual(serialized, jsonNaN); 99 | }); 100 | it('deserializes NaN from proto3 JSON', () => { 101 | const deserialized = fromProto3JSON(PrimitiveTypes, jsonNaN); 102 | assert.deepStrictEqual(deserialized, messageNaN); 103 | }); 104 | it('serializes Infinity to proto3 JSON', () => { 105 | const serialized = toProto3JSON(messageInfinity); 106 | assert.deepStrictEqual(serialized, jsonInfinity); 107 | }); 108 | it('deserializes Infinity from proto3 JSON', () => { 109 | const deserialized = fromProto3JSON(PrimitiveTypes, jsonInfinity); 110 | assert.deepStrictEqual(deserialized, messageInfinity); 111 | }); 112 | it('serializes negative Infinity to proto3 JSON', () => { 113 | const serialized = toProto3JSON(messageNegInfinity); 114 | assert.deepStrictEqual(serialized, jsonNegInfinity); 115 | }); 116 | it('deserializes negative Infinity from proto3 JSON', () => { 117 | const deserialized = fromProto3JSON(PrimitiveTypes, jsonNegInfinity); 118 | assert.deepStrictEqual(deserialized, messageNegInfinity); 119 | }); 120 | } 121 | 122 | testTwoTypesOfLoad('primitive types', [ 123 | testPrimitiveTypes, 124 | testLongIntegers, 125 | testNotFiniteNumber, 126 | ]); 127 | -------------------------------------------------------------------------------- /typescript/test/unit/repeated.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testRepeated(root: protobuf.Root) { 23 | const MessageWithRepeated = root.lookupType('test.MessageWithRepeated'); 24 | const message = MessageWithRepeated.fromObject({ 25 | repeatedString: ['value1', 'value2', 'value3'], 26 | repeatedMessage: [ 27 | { 28 | stringField: 'value1', 29 | }, 30 | { 31 | stringField: 'value2', 32 | }, 33 | ], 34 | oneMoreRepeatedString: [], 35 | repeatedLong: ['9223372036854775807', '1', '-1', '0'], 36 | }); 37 | const jsonWithNull = { 38 | repeatedString: ['value1', 'value2', 'value3'], 39 | repeatedMessage: [ 40 | { 41 | stringField: 'value1', 42 | }, 43 | { 44 | stringField: 'value2', 45 | }, 46 | ], 47 | oneMoreRepeatedString: null, 48 | repeatedLong: ['9223372036854775807', '1', '-1', '0'], 49 | }; 50 | const jsonWithEmptyArray = { 51 | repeatedString: ['value1', 'value2', 'value3'], 52 | repeatedMessage: [ 53 | { 54 | stringField: 'value1', 55 | }, 56 | { 57 | stringField: 'value2', 58 | }, 59 | ], 60 | oneMoreRepeatedString: null, 61 | repeatedLong: ['9223372036854775807', '1', '-1', '0'], 62 | }; 63 | const jsonWithoutEmptyArrays = { 64 | repeatedString: ['value1', 'value2', 'value3'], 65 | repeatedMessage: [ 66 | { 67 | stringField: 'value1', 68 | }, 69 | { 70 | stringField: 'value2', 71 | }, 72 | ], 73 | repeatedLong: ['9223372036854775807', '1', '-1', '0'], 74 | }; 75 | 76 | it('serializes to proto3 JSON', () => { 77 | const serialized = toProto3JSON(message); 78 | assert.deepStrictEqual(serialized, jsonWithoutEmptyArrays); 79 | }); 80 | 81 | it('deserializes from proto3 JSON with null', () => { 82 | const deserialized = fromProto3JSON(MessageWithRepeated, jsonWithNull); 83 | assert.deepStrictEqual(deserialized, message); 84 | }); 85 | 86 | it('deserializes from proto3 JSON with an empty array', () => { 87 | const deserialized = fromProto3JSON( 88 | MessageWithRepeated, 89 | jsonWithEmptyArray, 90 | ); 91 | assert.deepStrictEqual(deserialized, message); 92 | }); 93 | 94 | it('deserializes from proto3 JSON with an omitted array field', () => { 95 | const deserialized = fromProto3JSON( 96 | MessageWithRepeated, 97 | jsonWithoutEmptyArrays, 98 | ); 99 | assert.deepStrictEqual(deserialized, message); 100 | }); 101 | } 102 | 103 | function testEmptyRepeated(root: protobuf.Root) { 104 | const MessageWithRepeated = root.lookupType('test.MessageWithRepeated'); 105 | const message = MessageWithRepeated.fromObject({ 106 | repeatedString: [], 107 | repeatedMessage: [], 108 | oneMoreRepeatedString: [], 109 | }); 110 | const json = {}; 111 | 112 | it('serializes to proto3 JSON', () => { 113 | const serialized = toProto3JSON(message); 114 | assert.deepStrictEqual(serialized, json); 115 | }); 116 | 117 | it('deserializes from proto3 JSON', () => { 118 | const deserialized = fromProto3JSON(MessageWithRepeated, json); 119 | assert.deepStrictEqual(deserialized, message); 120 | }); 121 | } 122 | 123 | testTwoTypesOfLoad('repeated fields', testRepeated); 124 | testTwoTypesOfLoad('empty repeated fields', testEmptyRepeated); 125 | -------------------------------------------------------------------------------- /typescript/test/unit/timestamp.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testTimestamp(root: protobuf.Root) { 23 | const MessageWithTimestamp = root.lookupType('test.MessageWithTimestamp'); 24 | const testMapping = [ 25 | // example from google/protobuf/timestamp.proto 26 | { 27 | timestamp: {seconds: 1484443815, nanos: 10000000}, 28 | value: '2017-01-15T01:30:15.010Z', 29 | }, 30 | {timestamp: {nanos: 10000000}, value: '1970-01-01T00:00:00.010Z'}, 31 | {timestamp: {seconds: 1484443815}, value: '2017-01-15T01:30:15.000Z'}, 32 | { 33 | timestamp: {seconds: 1642115149, nanos: 91148000}, 34 | value: '2022-01-13T23:05:49.091148Z', 35 | }, 36 | { 37 | timestamp: {seconds: 1642121565, nanos: 10000}, 38 | value: '2022-01-14T00:52:45.000010Z', 39 | }, 40 | { 41 | timestamp: {seconds: 1642121565, nanos: 123456789}, 42 | value: '2022-01-14T00:52:45.123456789Z', 43 | }, 44 | { 45 | timestamp: {seconds: 1640995200}, 46 | value: '2022-01-01T00:00:00.000Z', 47 | }, 48 | ]; 49 | 50 | for (const mapping of testMapping) { 51 | const message = MessageWithTimestamp.fromObject({ 52 | timestampField: mapping.timestamp, 53 | }); 54 | const json = { 55 | timestampField: mapping.value, 56 | }; 57 | 58 | it(`serializes ${JSON.stringify(mapping.timestamp)} to proto3 JSON`, () => { 59 | const serialized = toProto3JSON(message); 60 | assert.deepStrictEqual(serialized, json); 61 | }); 62 | 63 | it(`deserializes "${mapping.value}" from proto3 JSON`, () => { 64 | const deserialized = fromProto3JSON(MessageWithTimestamp, json); 65 | assert.deepStrictEqual(deserialized, message); 66 | }); 67 | } 68 | 69 | describe('Timestamp has no millisecond', () => { 70 | const message = MessageWithTimestamp.fromObject({ 71 | timestampField: {seconds: 1640995200}, 72 | }); 73 | it('serialized date has no second to proto3 JSON', () => { 74 | const serialized = toProto3JSON(message); 75 | assert.deepStrictEqual(serialized, { 76 | timestampField: '2022-01-01T00:00:00.000Z', 77 | }); 78 | }); 79 | 80 | it('deserializes timestamp has no second from proto3 JSON', () => { 81 | const deserialized = fromProto3JSON(MessageWithTimestamp, { 82 | timestampField: '2022-01-01T00:00:00Z', 83 | }); 84 | assert.deepStrictEqual(deserialized, message); 85 | }); 86 | }); 87 | } 88 | 89 | testTwoTypesOfLoad('google.protobuf.Timestamp', testTimestamp); 90 | -------------------------------------------------------------------------------- /typescript/test/unit/wrappers.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import * as protobuf from 'protobufjs'; 17 | import {it} from 'mocha'; 18 | import {fromProto3JSON} from '../../src/fromproto3json'; 19 | import {toProto3JSON} from '../../src/toproto3json'; 20 | import {testTwoTypesOfLoad} from './common'; 21 | 22 | function testWrapperTypes(root: protobuf.Root) { 23 | const MessageWithWrappers = root.lookupType('test.MessageWithWrappers'); 24 | const buffer = Buffer.from('buffer'); 25 | const message = MessageWithWrappers.fromObject({ 26 | doubleValueField: {value: 3.14}, 27 | floatValueField: {value: 3.14}, 28 | int64ValueField: {value: -42}, 29 | uint64ValueField: {value: 42}, 30 | int32ValueField: {value: -43}, 31 | uint32ValueField: {value: 43}, 32 | boolValueField: {value: true}, 33 | stringValueField: {value: 'test'}, 34 | bytesValueField: {value: buffer}, 35 | nanValueField: {value: NaN}, 36 | infinityValueField: {value: Infinity}, 37 | negativeInfinityValueField: {value: -Infinity}, 38 | }); 39 | const json = { 40 | doubleValueField: 3.14, 41 | floatValueField: 3.14, 42 | int64ValueField: '-42', 43 | uint64ValueField: '42', 44 | int32ValueField: -43, 45 | uint32ValueField: 43, 46 | boolValueField: true, 47 | stringValueField: 'test', 48 | bytesValueField: buffer.toString('base64'), 49 | nanValueField: 'NaN', 50 | infinityValueField: 'Infinity', 51 | negativeInfinityValueField: '-Infinity', 52 | }; 53 | 54 | const messageWithNulls = MessageWithWrappers.fromObject({ 55 | doubleValueField: {}, 56 | floatValueField: {}, 57 | int64ValueField: {}, 58 | uint64ValueField: {}, 59 | int32ValueField: {}, 60 | uint32ValueField: {}, 61 | boolValueField: {}, 62 | stringValueField: {}, 63 | bytesValueField: {}, 64 | }); 65 | const jsonWithNulls = { 66 | doubleValueField: null, 67 | floatValueField: null, 68 | int64ValueField: null, 69 | uint64ValueField: null, 70 | int32ValueField: null, 71 | uint32ValueField: null, 72 | boolValueField: null, 73 | stringValueField: null, 74 | bytesValueField: null, 75 | }; 76 | 77 | it('serializes to proto3 JSON', () => { 78 | const serialized = toProto3JSON(message); 79 | assert.deepStrictEqual(serialized, json); 80 | }); 81 | 82 | it('deserializes from proto3 JSON', () => { 83 | const deserialized = fromProto3JSON(MessageWithWrappers, json); 84 | assert.deepStrictEqual(deserialized, message); 85 | }); 86 | 87 | it('serializes nulls to proto3 JSON', () => { 88 | const serialized = toProto3JSON(messageWithNulls); 89 | assert.deepStrictEqual(serialized, jsonWithNulls); 90 | }); 91 | 92 | it('deserializes nulls from proto3 JSON', () => { 93 | const deserialized = fromProto3JSON(MessageWithWrappers, jsonWithNulls); 94 | assert.deepStrictEqual(deserialized, messageWithNulls); 95 | }); 96 | } 97 | 98 | testTwoTypesOfLoad('wrapper types', testWrapperTypes); 99 | --------------------------------------------------------------------------------